Skip to content

Issue: 902 - Added a new feature to the CLI workflow that allows users to create an SPA #914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions packages/node-cli/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,6 @@
"COMMAND_CREATEFILE_SELECT_FOLDER": "Select the folder where you want to create the SuiteScript file",
"COMMAND_CREATEFILE_SELECT_SUITESCRIPT_MODULES": "Select the SuiteScript modules you want to add to the SuiteScript file",

"COMMAND_CREATEPROJECT_QUESTIONS_CHOOSE_PROJECT_TYPE": "Select the project type you want to create.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_ID": "Enter the project ID.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_NAME": "Enter the project name.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_VERSION": "Enter the project version.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PUBLISHER_ID": "Enter the publisher ID.",
"COMMAND_CREATEPROJECT_QUESTIONS_INCLUDE_UNIT_TESTING": "Do you want to include unit testing with the Jest testing framework? ***This will install NPM module dependencies.",
"COMMAND_CREATEPROJECT_QUESTIONS_OVERWRITE_PROJECT": "Do you want to overwrite the {0} project? All the files and folders from the former project will be deleted and replaced by the new project.",
"COMMAND_CREATEPROJECT_MESSAGES_CREATING_PROJECT_STRUCTURE": "Creating the project structure...",
"COMMAND_CREATEPROJECT_MESSAGES_INIT_NPM_DEPENDENCIES": "Initializing npm dependencies for the testing environment...",
"COMMAND_CREATEPROJECT_MESSAGES_INIT_NPM_DEPENDENCIES_FAILED": "There was an error when installing npm dependencies. Check the npm log and run \"npm install\" again inside of the project you created.",
Expand All @@ -60,7 +53,17 @@
"COMMAND_CREATEPROJECT_MESSAGES_PROJECT_CREATED": "The {0} project was created successfully.",
"COMMAND_CREATEPROJECT_MESSAGES_PROJECT_CREATION_CANCELED": "The creation process has been canceled.",
"COMMAND_CREATEPROJECT_MESSAGES_SAMPLE_UNIT_TEST_ADDED": "A sample unit test has been added in the \"__tests__\" folder of your project.",
"COMMAND_CREATEPROJECT_MESSAGES_SETUP_SPA_PROJECT": "Setting up the SPA project...",
"COMMAND_CREATEPROJECT_MESSAGES_SETUP_TEST_ENV": "Setting up the testing environment...",
"COMMAND_CREATEPROJECT_QUESTIONS_CHOOSE_PROJECT_TYPE": "Select the project type you want to create.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_ID": "Enter the project ID.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_NAME": "Enter the project name.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PROJECT_VERSION": "Enter the project version.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_PUBLISHER_ID": "Enter the publisher ID.",
"COMMAND_CREATEPROJECT_QUESTIONS_ENTER_SPA_PROJECT_NAME": "Enter the SPA project name.",
"COMMAND_CREATEPROJECT_QUESTIONS_CREATE_SPA": "Do you want to create an SPA project? ***This will install NPM module dependencies and create needed files and folders.",
"COMMAND_CREATEPROJECT_QUESTIONS_INCLUDE_UNIT_TESTING": "Do you want to include unit testing with the Jest testing framework? ***This will install NPM module dependencies.",
"COMMAND_CREATEPROJECT_QUESTIONS_OVERWRITE_PROJECT": "Do you want to overwrite the {0} project? All the files and folders from the former project will be deleted and replaced by the new project.",

"COMMAND_DEPLOY_ERRORS_APPLY_INSTALLATION_PREFERENCES_IN_ACP": "Installation preferences cannot be applied in an Account Customization Project.",
"COMMAND_DEPLOY_ERRORS_WRONG_ACCOUNT_SPECIFIC_VALUES_OPTION": "You have specified an invalid value for the \"--accountspecficvalues\" option. Enter either WARNING or ERROR.",
Expand Down
227 changes: 215 additions & 12 deletions packages/node-cli/src/commands/project/create/CreateProjectAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,41 @@ const PACKAGE_JSON_DEFAULT_VERSION = '1.0.0';
const PACKAGE_JSON_REPLACE_STRING_VERSION = '{{version}}';

const SOURCE_FOLDER = 'src';
const OBJECTS_FOLDER = 'Objects';
const UNIT_TEST_TEST_FOLDER = '__tests__';

const SPA_SUITEAPPS_FOLDER = 'SuiteApps';
const SPA_ASSETS_FOLDER = 'assets';
const SPA_PROJECT_NAME_REPLACE_STRING = '{{projectName}}';
const SPA_PROJECT_PATH_REPLACE_STRING = '{{projectPath}}';
const SPA_SPA_CLIENT_TEMPLATE_KEY = 'spaclient';
const SPA_SPA_CLIENT_FILENAME = 'SpaClient';
const SPA_SPA_CLIENT_EXTENSION = 'tsx';
const SPA_SPA_SERVER_TEMPLATE_KEY = 'spaserver';
const SPA_SPA_SERVER_FILENAME = 'SpaServer';
const SPA_SPA_SERVER_EXTENSION = 'ts';
const SPA_HELLO_WORLD_TEMPLATE_KEY = 'helloworld';
const SPA_HELLO_WORLD_FILENAME = 'HelloWorld';
const SPA_HELLO_WORLD_EXTENSION = 'tsx';
const SPA_CUSTSPA_TEMPLATE_KEY = 'custspa';
const SPA_CUSTSPA_FILENAME = 'custspa_';
const SPA_CUSTSPA_EXTENSION = 'xml';
const SPA_GULPFILE_TEMPLATE_KEY = 'gulpfile';
const SPA_GULPFILE_FILENAME = 'gulpfile';
const SPA_GULPFILE_EXTENSION = 'mjs';
const SPA_ESLINT_TEMPLATE_KEY = 'eslint';
const SPA_ESLINT_FILENAME = 'eslint.config';
const SPA_ESLINT_EXTENSION = 'mjs';
const SPA_TS_CONFIG_TEMPLATE_KEY = 'tsconfig';
const SPA_TS_CONFIG_FILENAME = 'tsconfig';
const SPA_TS_CONFIG_EXTENSION = 'json';
const SPA_TS_CONFIG_TEMPLATE_KEY_TEST = 'tsconfigtest';
const SPA_TS_CONFIG_FILENAME_TEST = 'tsconfig.test';
const SPA_TS_CONFIG_EXTENSION_TEST = 'json';
const SPA_PACKAGE_TEMPLATE_KEY = 'package';
const SPA_PACKAGE_FILENAME = 'package';
const SPA_PACKAGE_EXTENSION = 'json';

const CLI_CONFIG_TEMPLATE_KEY = 'cliconfig';
const GITIGNORE_TEMPLATE_KEY = 'gitignore';
const CLI_CONFIG_FILENAME = 'suitecloud.config';
Expand All @@ -65,15 +98,17 @@ const UNIT_TEST_JSCONFIG_FILENAME = 'jsconfig';
const UNIT_TEST_JSCONFIG_EXTENSION = 'json';

const COMMAND_OPTIONS = {
CREATE_SPA: 'createspa',
INCLUDE_UNIT_TESTING: 'includeunittesting',
OVERWRITE: 'overwrite',
PARENT_DIRECTORY: 'parentdirectory',
PROJECT_FOLDER_NAME: 'projectfoldername',
PROJECT_ID: 'projectid',
PROJECT_NAME: 'projectname',
PROJECT_VERSION: 'projectversion',
PUBLISHER_ID: 'publisherid',
SPA_PROJECT_NAME: 'spaprojectname',
TYPE: 'type',
INCLUDE_UNIT_TESTING: 'includeunittesting',
PROJECT_FOLDER_NAME: 'projectfoldername',
};

module.exports = class CreateProjectAction extends BaseAction {
Expand Down Expand Up @@ -138,27 +173,42 @@ module.exports = class CreateProjectAction extends BaseAction {

const projectName = params[COMMAND_OPTIONS.PROJECT_NAME];
const includeUnitTesting = this._getIncludeUnitTestingBoolean(params[COMMAND_OPTIONS.INCLUDE_UNIT_TESTING]);
const createSpa = this._getCreateSpaBoolean(params[COMMAND_OPTIONS.CREATE_SPA]);
const spaProjectName = params[COMMAND_OPTIONS.SPA_PROJECT_NAME];

//fixing project name for not interactive output before building results
const commandParameters = { ...createProjectParams, [`${COMMAND_OPTIONS.PROJECT_NAME}`]: params[COMMAND_OPTIONS.PROJECT_NAME] };

return createProjectActionData.operationResult.status === SdkOperationResultUtils.STATUS.SUCCESS
? CreateProjectActionResult.Builder.withData(createProjectActionData.operationResult.data)
.withResultMessage(createProjectActionData.operationResult.resultMessage)
.withProjectType(projectType)
.withProjectName(projectName)
.withProjectDirectory(createProjectActionData.projectDirectory)
.withUnitTesting(includeUnitTesting)
.withNpmPackageInitialized(createProjectActionData.npmInstallSuccess)
.withCommandParameters(commandParameters)
.build()
.withResultMessage(createProjectActionData.operationResult.resultMessage)
.withProjectType(projectType)
.withProjectName(projectName)
.withProjectDirectory(createProjectActionData.projectDirectory)
.withUnitTesting(includeUnitTesting)
.withSpaProject(createSpa)
.withSpaProjectName(spaProjectName)
.withNpmPackageInitialized(createProjectActionData.npmInstallSuccess)
.withCommandParameters(commandParameters)
.build()
: CreateProjectActionResult.Builder.withErrors(createProjectActionData.operationResult.errorMessages)
.withCommandParameters(commandParameters)
.build();
.withCommandParameters(commandParameters)
.build();
} catch (error) {
return CreateProjectActionResult.Builder.withErrors([unwrapExceptionMessage(error)]).build();
}
}

withIncludeSpa(includeSpa) {
this.includeSpa = includeSpa;
return this;
}

withSpaProjectName(spaProjectName) {
this.spaProjectName = spaProjectName;
return this;
}

createProject(executionContextCreateProject, params, projectAbsolutePath, projectFolderName, manifestFilePath) {
return async (resolve, reject) => {
try {
Expand All @@ -185,6 +235,17 @@ module.exports = class CreateProjectAction extends BaseAction {
}
this._fileSystemService.replaceStringInFile(manifestFilePath, SOURCE_FOLDER, params[COMMAND_OPTIONS.PROJECT_NAME]);
let npmInstallSuccess;

//SPA
const createSpa = this._getCreateSpaBoolean(params[COMMAND_OPTIONS.CREATE_SPA]);
if (createSpa) {
this._log.info(NodeTranslationService.getMessage(MESSAGES.SETUP_SPA_PROJECT));
await this._createSpaFiles(params[COMMAND_OPTIONS.SPA_PROJECT_NAME], projectAbsolutePath);
// this._log.info(NodeTranslationService.getMessage(MESSAGES.INIT_NPM_DEPENDENCIES));
// npmInstallSuccess = await this._runNpmInstall(this._getSpaProjectFolderSource(projectAbsolutePath));
}

//Unit Testing
let includeUnitTesting = this._getIncludeUnitTestingBoolean(params[COMMAND_OPTIONS.INCLUDE_UNIT_TESTING]);
if (includeUnitTesting) {
this._log.info(NodeTranslationService.getMessage(MESSAGES.SETUP_TEST_ENV));
Expand Down Expand Up @@ -238,6 +299,143 @@ module.exports = class CreateProjectAction extends BaseAction {
}
}

_getCreateSpaBoolean(createSpaParam) {
return typeof createSpaParam === 'string' ? createSpaParam.toLowerCase() === 'true' : Boolean(createSpaParam);
}

_getSpaProjectFolderSource(projectAbsolutePath) {
return path.join(projectAbsolutePath, SOURCE_FOLDER);
}

_getSpaProjectFolderSourceObjects(projectAbsolutePath) {
return path.join(projectAbsolutePath, SOURCE_FOLDER, OBJECTS_FOLDER);
}

_getSpaProjectFolderSuiteApps(projectAbsolutePath) {
return path.join(projectAbsolutePath, SOURCE_FOLDER, SPA_SUITEAPPS_FOLDER);
}

_getSpaProjectFolderProjectName(projectAbsolutePath, projectName) {
return path.join(projectAbsolutePath, SOURCE_FOLDER, SPA_SUITEAPPS_FOLDER, projectName);
}

async _createSpaFiles(projectName, projectAbsolutePath) {
const spaProjectFolderSource = this._getSpaProjectFolderSource(projectAbsolutePath);
const spaProjectFolderProjectName = this._getSpaProjectFolderProjectName(projectAbsolutePath, projectName);
const spaProjectFolderSourceObjects = this._getSpaProjectFolderSourceObjects(projectAbsolutePath);

await this._createSpaFolders(projectName, projectAbsolutePath);
await this._createSpaClientFile(spaProjectFolderProjectName);
await this._createSpaServerFile(spaProjectFolderProjectName);
await this._createHelloWorldFile(spaProjectFolderProjectName);
await this._createCustspaFile(projectName, spaProjectFolderSourceObjects);
await this._createGulpFile(spaProjectFolderSource);
await this._createEslintFile(spaProjectFolderSource);
await this._createTsConfigFile(spaProjectFolderSource);
await this._createTsConfigTestFile(spaProjectFolderSource);
await this._createPackageFile(projectName, spaProjectFolderSource);
}

async _createSpaFolders(projectName, projectAbsolutePath) {
const spaProjectFolderSource = this._getSpaProjectFolderSource(projectAbsolutePath);
const spaProjectFolderSuiteApps = this._getSpaProjectFolderSuiteApps(projectAbsolutePath);
const spaProjectFolderProjectName = this._getSpaProjectFolderProjectName(projectAbsolutePath, projectName);

await this._fileSystemService.createFolder(spaProjectFolderSource, SPA_SUITEAPPS_FOLDER); //SuiteApps folder
await this._fileSystemService.createFolder(spaProjectFolderSuiteApps, projectName); //Project Name folder
await this._fileSystemService.createFolder(spaProjectFolderProjectName, SPA_ASSETS_FOLDER); //Assets folder
}

async _createSpaClientFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_SPA_CLIENT_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_SPA_CLIENT_FILENAME,
fileExtension: SPA_SPA_CLIENT_EXTENSION,
});
}

async _createSpaServerFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_SPA_SERVER_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_SPA_SERVER_FILENAME,
fileExtension: SPA_SPA_SERVER_EXTENSION,
});
}

async _createHelloWorldFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_HELLO_WORLD_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_HELLO_WORLD_FILENAME,
fileExtension: SPA_HELLO_WORLD_EXTENSION,
});
}

async _createCustspaFile(projectName, projectAbsolutePath) {
const spaProjectFolderProjectName = '/' + SPA_SUITEAPPS_FOLDER;

await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_CUSTSPA_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_CUSTSPA_FILENAME + projectName,
fileExtension: SPA_CUSTSPA_EXTENSION,
});

const custSpaFilePath = path.join(projectAbsolutePath, SPA_CUSTSPA_FILENAME + projectName + '.' + SPA_CUSTSPA_EXTENSION);
await this._fileSystemService.replaceStringInFile(custSpaFilePath, SPA_PROJECT_NAME_REPLACE_STRING, projectName);
await this._fileSystemService.replaceStringInFile(custSpaFilePath, SPA_PROJECT_PATH_REPLACE_STRING, spaProjectFolderProjectName);
}

async _createGulpFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_GULPFILE_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_GULPFILE_FILENAME,
fileExtension: SPA_GULPFILE_EXTENSION,
});
}

async _createEslintFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_ESLINT_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_ESLINT_FILENAME,
fileExtension: SPA_ESLINT_EXTENSION,
});
}

async _createTsConfigFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_TS_CONFIG_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_TS_CONFIG_FILENAME,
fileExtension: SPA_TS_CONFIG_EXTENSION,
});
}

async _createTsConfigTestFile(projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_TS_CONFIG_TEMPLATE_KEY_TEST],
destinationFolder: projectAbsolutePath,
fileName: SPA_TS_CONFIG_FILENAME_TEST,
fileExtension: SPA_TS_CONFIG_EXTENSION_TEST,
});
}

async _createPackageFile(projectName, projectAbsolutePath) {
await this._fileSystemService.createFileFromTemplate({
template: TemplateKeys.SPA_PROJECT[SPA_PACKAGE_TEMPLATE_KEY],
destinationFolder: projectAbsolutePath,
fileName: SPA_PACKAGE_FILENAME,
fileExtension: SPA_PACKAGE_EXTENSION,
});

const packageJsonFilePath = path.join(projectAbsolutePath, SPA_PACKAGE_FILENAME + '.' + SPA_PACKAGE_EXTENSION);
await this._fileSystemService.replaceStringInFile(packageJsonFilePath, SPA_PROJECT_NAME_REPLACE_STRING, projectName);
}

async _createUnitTestFiles(type, projectName, projectVersion, projectAbsolutePath) {
await this._createUnitTestCliConfigFile(projectAbsolutePath);
await this._createUnitTestPackageJsonFile(type, projectName, projectVersion, projectAbsolutePath);
Expand Down Expand Up @@ -324,6 +522,11 @@ module.exports = class CreateProjectAction extends BaseAction {
});
}

_createMyCustomFolder(params) {
const customFolderPath = path.join(params[COMMAND_OPTIONS.PARENT_DIRECTORY], 'mycustomfolder');
this._fileSystemService.createFolderFromAbsolutePath(customFolderPath);
}

async _runNpmInstall(projectAbsolutePath) {
try {
await NpmInstallRunner.run(projectAbsolutePath);
Expand Down
Loading