Skip to content

Commit 920fbfc

Browse files
matz3RandomByte
andauthored
[FEATURE] Add "ui5 use/add" commands (#315)
Co-authored-by: Merlin Beutlberger <[email protected]>
1 parent 7539675 commit 920fbfc

File tree

14 files changed

+2946
-3
lines changed

14 files changed

+2946
-3
lines changed

bin/ui5.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ setTimeout(() => {
4545
shouldNotifyInNpmScript: true
4646
}).notify();
4747

48+
cli.parserConfiguration({
49+
"parse-numbers": false
50+
});
51+
4852
// Explicitly set CLI version as the yargs default might
4953
// be wrong in case a local CLI installation is used
5054
// Also add CLI location

lib/cli/commands/add.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Add
2+
const addCommand = {
3+
command: "add [--development] [--optional] <framework-libraries..>",
4+
describe: "Add SAPUI5/OpenUI5 framework libraries to the project configuration.",
5+
middlewares: [require("../middlewares/base.js")]
6+
};
7+
8+
addCommand.builder = function(cli) {
9+
return cli
10+
.positional("framework-libraries", {
11+
describe: "Framework library names",
12+
type: "string"
13+
}).option("development", {
14+
describe: "Add as development dependency",
15+
alias: ["D", "dev"],
16+
default: false,
17+
type: "boolean"
18+
}).option("optional", {
19+
describe: "Add as optional dependency",
20+
alias: ["O"],
21+
default: false,
22+
type: "boolean"
23+
})
24+
.example("$0 add sap.ui.core sap.m", "Add the framework libraries sap.ui.core and sap.m as dependencies")
25+
.example("$0 add -D sap.ui.support", "Add the framework library sap.ui.support as development dependency")
26+
.example("$0 add --optional themelib_sap_fiori_3",
27+
"Add the framework library themelib_sap_fiori_3 as optional dependency");
28+
};
29+
30+
addCommand.handler = async function(argv) {
31+
const libraryNames = argv["framework-libraries"] || [];
32+
const development = argv["development"];
33+
const optional = argv["optional"];
34+
35+
if (libraryNames.length === 0) {
36+
// Should not happen via yargs as parameter is mandatory
37+
throw new Error("Missing mandatory parameter framework-libraries");
38+
}
39+
40+
if (development && optional) {
41+
throw new Error("Options 'development' and 'optional' cannot be combined");
42+
}
43+
44+
const normalizerOptions = {
45+
translatorName: argv.translator,
46+
configPath: argv.config
47+
};
48+
49+
const libraries = libraryNames.map((name) => {
50+
const library = {name};
51+
if (optional) {
52+
library.optional = true;
53+
} else if (development) {
54+
library.development = true;
55+
}
56+
return library;
57+
});
58+
59+
const {yamlUpdated} = await require("../../framework/add")({
60+
normalizerOptions,
61+
libraries
62+
});
63+
64+
const library = libraries.length === 1 ? "library": "libraries";
65+
if (!yamlUpdated) {
66+
if (argv.config) {
67+
throw new Error(
68+
`Internal error while adding framework ${library} ${libraryNames.join(" ")} to config at ${argv.config}`
69+
);
70+
} else {
71+
throw new Error(
72+
`Internal error while adding framework ${library} ${libraryNames.join(" ")} to ui5.yaml`
73+
);
74+
}
75+
} else {
76+
console.log(`Updated configuration written to ${argv.config || "ui5.yaml"}`);
77+
let logMessage = `Added framework ${library} ${libraryNames.join(" ")} as`;
78+
if (development) {
79+
logMessage += " development";
80+
} else if (optional) {
81+
logMessage += " optional";
82+
}
83+
logMessage += libraries.length === 1 ? " dependency": " dependencies";
84+
console.log(logMessage);
85+
}
86+
};
87+
88+
module.exports = addCommand;

lib/cli/commands/use.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Use
2+
const useCommand = {
3+
command: "use <framework-info>",
4+
describe: "Initialize or update the project's framework configuration.",
5+
middlewares: [require("../middlewares/base.js")]
6+
};
7+
8+
useCommand.builder = function(cli) {
9+
return cli
10+
.positional("framework-info", {
11+
describe: "Framework name, version or both (name@version).\n" +
12+
"Name can be \"SAPUI5\" or \"OpenUI5\" (case-insensitive).\n" +
13+
"Version can be \"latest\", \"1.xx\" or \"1.xx.x\".",
14+
type: "string"
15+
})
16+
.example("$0 use sapui5@latest", "Use SAPUI5 in the latest available version")
17+
.example("$0 use [email protected]", "Use OpenUI5 in the latest available 1.76 patch version")
18+
.example("$0 use latest", "Use the latest available version of the configured framework")
19+
.example("$0 use openui5", "Use OpenUI5 without a version (or use existing version)");
20+
};
21+
22+
const versionRegExp = /^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?$/;
23+
24+
function parseFrameworkInfo(frameworkInfo) {
25+
const parts = frameworkInfo.split("@");
26+
if (parts.length > 2) {
27+
// More than one @ sign
28+
throw new Error("Invalid framework info: " + frameworkInfo);
29+
}
30+
if (parts.length === 1) {
31+
// No @ sign, only name or version
32+
const nameOrVersion = parts[0];
33+
if (!nameOrVersion) {
34+
throw new Error("Invalid framework info: " + frameworkInfo);
35+
}
36+
if (nameOrVersion === "latest" || versionRegExp.test(nameOrVersion)) {
37+
return {
38+
name: null,
39+
version: nameOrVersion
40+
};
41+
} else {
42+
return {
43+
name: nameOrVersion,
44+
version: null
45+
};
46+
}
47+
} else {
48+
const [name, version] = parts;
49+
if (!name || !version) {
50+
throw new Error("Invalid framework info: " + frameworkInfo);
51+
}
52+
return {name, version};
53+
}
54+
}
55+
56+
useCommand.handler = async function(argv) {
57+
const frameworkOptions = parseFrameworkInfo(argv["framework-info"]);
58+
59+
const normalizerOptions = {
60+
translatorName: argv.translator,
61+
configPath: argv.config
62+
};
63+
64+
const {usedFramework, usedVersion, yamlUpdated} = await require("../../framework/use")({
65+
normalizerOptions,
66+
frameworkOptions
67+
});
68+
69+
if (!yamlUpdated) {
70+
if (argv.config) {
71+
throw new Error(
72+
`Internal error while updating config at ${argv.config} to ${usedFramework} version ${usedVersion}`
73+
);
74+
} else {
75+
throw new Error(`Internal error while updating ui5.yaml to ${usedFramework} version ${usedVersion}`);
76+
}
77+
} else {
78+
console.log(`Updated configuration written to ${argv.config || "ui5.yaml"}`);
79+
console.log(`This project is now using ${usedFramework} version ${usedVersion}`);
80+
}
81+
};
82+
83+
module.exports = useCommand;

lib/framework/add.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const {getRootProjectConfiguration, getFrameworkResolver, isValidSpecVersion} = require("./utils");
2+
3+
module.exports = async function({normalizerOptions, libraries}) {
4+
const project = await getRootProjectConfiguration({normalizerOptions});
5+
6+
if (!isValidSpecVersion(project.specVersion)) {
7+
throw new Error(
8+
`ui5 add command requires specVersion "2.0" or higher. ` +
9+
`Project ${project.metadata.name} uses specVersion "${project.specVersion}"`
10+
);
11+
}
12+
13+
if (!project.framework) {
14+
throw new Error(
15+
`Project ${project.metadata.name} is missing a framework configuration. ` +
16+
`Please use "ui5 use" to configure a framework and version.`
17+
);
18+
}
19+
if (!project.framework.version) {
20+
throw new Error(
21+
`Project ${project.metadata.name} does not define a framework version configuration. ` +
22+
`Please use "ui5 use" to configure a version.`
23+
);
24+
}
25+
26+
const Resolver = getFrameworkResolver(project.framework.name);
27+
28+
const resolver = new Resolver({
29+
cwd: project.path,
30+
version: project.framework.version
31+
});
32+
33+
// Get metadata of all libraries to verify that they can be installed
34+
await Promise.all(libraries.map(async ({name}) => {
35+
try {
36+
await resolver.getLibraryMetadata(name);
37+
} catch (err) {
38+
throw new Error(`Failed to find ${project.framework.name} framework library ${name}: ` + err.message);
39+
}
40+
}));
41+
42+
// Shallow copy of given libraries to not modify the input parameter when pushing other libraries
43+
const allLibraries = [...libraries];
44+
45+
if (project.framework.libraries) {
46+
project.framework.libraries.forEach((library) => {
47+
// Don't add libraries twice!
48+
if (allLibraries.findIndex(($) => $.name === library.name) === -1) {
49+
allLibraries.push(library);
50+
}
51+
});
52+
}
53+
allLibraries.sort((a, b) => {
54+
return a.name.localeCompare(b.name);
55+
});
56+
57+
// Try to update YAML file but still return with name and resolved version in case it failed
58+
let yamlUpdated = false;
59+
try {
60+
await require("./updateYaml")({
61+
project,
62+
data: {
63+
framework: {
64+
libraries: allLibraries
65+
}
66+
}
67+
});
68+
yamlUpdated = true;
69+
} catch (err) {
70+
if (err.name !== "FrameworkUpdateYamlFailed") {
71+
throw err;
72+
}
73+
}
74+
return {
75+
yamlUpdated
76+
};
77+
};

0 commit comments

Comments
 (0)