@sassoftware/vi-solution-extension-create
Version:
Creates a development environment for creating SAS Visual Investigator solution extensions.
264 lines (220 loc) • 9.13 kB
JavaScript
const shell = require("shelljs");
const prompt = require("prompt");
const fs = require("fs");
const path = require("path");
const ngPath = getNgPath();
// Display angular version before initialising creation prompt
shell.exec(`${ngPath} version`);
prompt.start();
const promptSchema = {
properties: {
projectName: {
description: "Enter a name for this solution (e.g. my-great-solution)",
type: "string",
required: true
},
sviHostname: {
description: "Enter the name of the server to auto-deploy to (e.g. http://acme.server.na.sas.com",
type: "string",
required: true
},
sviBearerToken: {
description: "Will this project use an existing bearer token to upload the solution? (y/n)",
type: "string",
pattern: /^[YyNn]$/, // Accepts only Y or N
default: "n", // Default value is N
required: false
},
sviUsername: {
description: "Enter an admin username to be used to upload the solution",
type: "string",
// Only required if not using an existing bearer token.
required: function () {
return prompt.history("sviBearerToken").value.toLowerCase() === "n";
},
// Only display this prompt if the user does not have an existing bearer token.
ask: function () {
return prompt.history("sviBearerToken").value.toLowerCase() === "n";
}
},
sviPassword: {
description: "Enter the password for the admin user",
type: "string",
// Only required if not using an existing bearer token.
required: function () {
return prompt.history("sviBearerToken").value.toLowerCase() === "n";
},
// Only display this prompt if the user does not have an existing bearer token.
ask: function () {
return prompt.history("sviBearerToken").value.toLowerCase() === "n";
}
},
includeMobile: {
description: "Will this project also include mobile custom controls? (y/n)",
type: "string",
pattern: /^[YyNn]$/, // Accepts only Y or N
default: "n", // Default value is N
required: false
}
}
};
prompt.get(promptSchema, function (err, result) {
if (err) {
log("Required inputs not provided");
log(err);
return;
}
const { projectName, sviHostname, sviUsername, sviPassword } = result;
const hasBearerToken = result.sviBearerToken.toLowerCase() === "y";
const includeMobile = result.includeMobile.toLowerCase() === "y";
executeNgCommand(`new ${projectName} --create-application false --routing false --style scss`);
shell.cd(projectName);
executeNgCommand("g library components");
executeNgCommand("g application elements --routing false --style scss");
executeNgCommand("add @angular/elements --project elements --skip-confirmation true");
executeNgCommand("add ngx-build-plus@^18.0.0 --project elements --skip-confirmation true");
removeUnnecessaryFiles();
addRequiredPackages();
modifyPackageJSON();
modifyAppModules();
modifyMainTypeScriptFile();
addPathToAngularJSON();
modifyEnvFile(includeMobile, sviHostname, sviUsername, sviPassword, hasBearerToken);
if (includeMobile) {
executeAndLogCommand("npx ng g @sassoftware/vi-solution-extension-angular-schematics:add-mobile");
}
});
/*
The main.ts file is not in the correct state for the purposes of this project after upgrading Angular
version, this function resets it to the required state.
*/
function modifyMainTypeScriptFile() {
const alterMainFileLogger = createLogger("Altering main.ts file");
alterMainFileLogger.start();
const mainFile = fs.readFileSync(path.resolve(__dirname, "./files/main.ts"), "utf-8");
fs.writeFileSync(`${process.cwd()}/projects/elements/src/main.ts`, mainFile);
alterMainFileLogger.end();
}
/*
As part of the build step a "main" property is required instead of "browser"since upgrading the
ngx-build-plus:browser builder version which is not added in the project setup and must be done
as part of the scaffold.
*/
function addPathToAngularJSON() {
const alterAngularJsonLogger = createLogger("Altering angular.json file");
const angularJson = fs.readFileSync(`${process.cwd()}/angular.json`, "utf8");
const config = JSON.parse(angularJson);
if (config.projects && config.projects.elements) {
// Add the 'main' property in architect.build.options
if (!config.projects.elements.architect.build.options.main) {
const browserValue = config.projects.elements.architect.build.options.browser;
config.projects.elements.architect.build.options.main = browserValue || "projects/elements/src/main.ts";
}
// Delete browser property if existing.
if (config.projects.elements.architect.build.options.browser) {
delete config.projects.elements.architect.build.options.browser;
}
const modifiedConfig = JSON.stringify(config, null, 2);
fs.writeFileSync(`${process.cwd()}/angular.json`, modifiedConfig, "utf8");
alterAngularJsonLogger.end();
}
}
function removeUnnecessaryFiles() {
const removeFilesLogger = createLogger("Removing unnecessary files created by ng new");
removeFilesLogger.start();
shell.rm("./projects/elements/src/app/app.component.*");
shell.rm("./projects/elements/src/app/app.config.*");
shell.rm("./projects/components/src/lib/components.*");
shell.rm("-rf", "./projects/environments");
shell.rm("-rf", "./projects/assets");
removeFilesLogger.end();
}
function modifyPackageJSON() {
const alterPackageJsonLogger = createLogger("Altering package.json structure");
alterPackageJsonLogger.start();
const packageJsonPath = `${process.cwd()}/package.json`;
const pkg = require(packageJsonPath);
delete pkg.scripts.serve;
delete pkg.scripts.start;
delete pkg.scripts.ng;
delete pkg.scripts.test;
pkg.scripts.build =
"env-cmd ng build --configuration production --project elements --output-hashing none --single-bundle";
pkg.scripts.watch =
"env-cmd ng build --configuration production --project elements --output-hashing none --single-bundle --watch --plugin @sassoftware/vi-solution-extension-upload/src/upload-bundle.ngx-plugin";
pkg.scripts["watch:debug"] =
"env-cmd ng build --configuration development --project elements --output-hashing none --single-bundle --watch --plugin @sassoftware/vi-solution-extension-upload/src/upload-bundle.ngx-plugin";
pkg.scripts["create:solution-control"] =
"env-cmd ng g @sassoftware/vi-solution-extension-angular-schematics:wc --project components";
pkg.scripts["add-mobile"] = "ng g @sassoftware/vi-solution-extension-angular-schematics:add-mobile";
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
alterPackageJsonLogger.end();
}
function modifyAppModules() {
const alterAppModulesLogger = createLogger("Altering element and component app modules");
alterAppModulesLogger.start();
const componentsAppModule = fs.readFileSync(
path.resolve(__dirname, "./files/app-modules/components.app.module.ts"),
"utf-8"
);
const elementsAppModule = fs.readFileSync(
path.resolve(__dirname, "./files/app-modules/elements.app.module.ts"),
"utf-8"
);
fs.writeFileSync(`${process.cwd()}/projects/components/src/app.module.ts`, componentsAppModule);
fs.writeFileSync(`${process.cwd()}/projects/elements/src/app/app.module.ts`, elementsAppModule);
alterAppModulesLogger.end();
}
function modifyEnvFile(includeMobile, sviHostname, sviUsername, sviPassword, hasBearerToken) {
const envLogger = createLogger("Writing .env");
envLogger.start();
const environment = includeMobile
? `
SVI_HOSTNAME=${sviHostname}
SVI_USERNAME=${sviUsername}
SVI_PASSWORD=${sviPassword}
SVI_BUNDLE_PATH=./dist/elements/main.js
SMI_BUNDLE_PATH=./dist/mobile-elements/main.js
${hasBearerToken ? "SVI_BEARER_TOKEN=" : ""}
`.trim()
: `
SVI_HOSTNAME=${sviHostname}
SVI_USERNAME=${sviUsername}
SVI_PASSWORD=${sviPassword}
SVI_BUNDLE_PATH=./dist/elements/main.js
${hasBearerToken ? "SVI_BEARER_TOKEN=" : ""}
`.trim();
fs.writeFileSync(`${process.cwd()}/.env`, environment);
envLogger.end();
}
function addRequiredPackages() {
executeNgCommand("add @sassoftware/vi-solution-extension-angular-schematics --skip-confirmation true");
executeAndLogCommand("npm i --save-dev @sassoftware/vi-solution-extension-upload");
executeAndLogCommand("npm i --save-dev env-cmd");
}
function executeNgCommand(command) {
return executeAndLogCommand(`node ${ngPath} ${command}`);
}
function executeAndLogCommand(command) {
log(`Executing command: ${command}`);
return shell.exec(command);
}
function log(message) {
console.log(`----- ${message} -----`);
}
function createLogger(message) {
return {
start: () => log(`Start: ${message}.`),
end: () => log(`End: ${message}.`)
};
}
function getNgPath() {
// Default to using node_modules in base npx directory for @angular/cli
const ngPath = require.resolve("@angular/cli/bin/ng");
if (!fs.existsSync(ngPath)) {
console.error("Error: The required dependency `@angular/cli` could not be found.\n");
process.exit(1);
}
return ngPath;
}