From dff9266bd6a1b5a94943993077688127d69ecb60 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 10:23:19 -0400 Subject: [PATCH 01/10] initial commit --- packages/create-sitecore-jss/src/common/args/base.ts | 4 ++++ .../src/templates/angular-xmcloud/angular.txt | 0 .../src/templates/node-xmcloud-proxy/proxy.txt | 1 + 3 files changed, 5 insertions(+) create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt diff --git a/packages/create-sitecore-jss/src/common/args/base.ts b/packages/create-sitecore-jss/src/common/args/base.ts index 324b9febad..da6b80c5b8 100644 --- a/packages/create-sitecore-jss/src/common/args/base.ts +++ b/packages/create-sitecore-jss/src/common/args/base.ts @@ -33,6 +33,10 @@ export type BaseArgs = { * Pre-push hook for linting check */ prePushHook?: boolean; + /** + * Destination for auxillary proxy app, when initialized alongside the main one + */ + nodeAppDestination?: string; }; /** diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt b/packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt new file mode 100644 index 0000000000..2901b76aa6 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt @@ -0,0 +1 @@ +this is proxy \ No newline at end of file From bbc2ee506c7ca69b75249e4562d245808f7e3d77 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:47:25 -0400 Subject: [PATCH 02/10] mock node-xmcloud-proxy logic --- .../initializers/node-xmcloud-proxy/index.ts | 9 ++++-- .../templates/node-xmcloud-proxy/package.json | 29 +++++++++++++++++++ .../templates/node-xmcloud-proxy/proxy.txt | 1 - .../templates/node-xmcloud-proxy/src/index.ts | 1 + 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json delete mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt create mode 100644 packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts diff --git a/packages/create-sitecore-jss/src/initializers/node-xmcloud-proxy/index.ts b/packages/create-sitecore-jss/src/initializers/node-xmcloud-proxy/index.ts index 2f29a3752b..e6e2c82c73 100644 --- a/packages/create-sitecore-jss/src/initializers/node-xmcloud-proxy/index.ts +++ b/packages/create-sitecore-jss/src/initializers/node-xmcloud-proxy/index.ts @@ -1,11 +1,14 @@ -import { Initializer } from '../../common'; +import path from 'path'; +import { Initializer, transform, BaseArgs } from '../../common'; export default class AngularXmCloudInitializer implements Initializer { get isBase(): boolean { - return false; + return true; } - async init() { + async init(args: BaseArgs) { + const templatePath = path.resolve(__dirname, '../../templates/node-xmcloud-proxy'); + await transform(templatePath, args); const response = { appName: 'node-xmcloud-proxy', }; diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json new file mode 100644 index 0000000000..f9b9ad86a6 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/package.json @@ -0,0 +1,29 @@ +{ + "name": "node-xmcloud-sample", + "version": "22.1.0-canary", + "description": "Node server-side-rendering proxy sample for running JSS apps under Node hosting for XM Cloud", + "scripts": { + "start": "ts-node ./src/index.ts" + }, + "author": { + "name": "Sitecore Corporation", + "url": "https://jss.sitecore.com" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sitecore/jss.git" + }, + "bugs": { + "url": "https://github.com/sitecore/jss/issues" + }, + "homepage": "https://jss.sitecore.com", + "license": "Apache-2.0", + "dependencies": { + "ts-node": "^10.9.1" + }, + "devDependencies": { + "@types/node": "^20.14.2" + }, + "private": true +} + \ No newline at end of file diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt deleted file mode 100644 index 2901b76aa6..0000000000 --- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/proxy.txt +++ /dev/null @@ -1 +0,0 @@ -this is proxy \ No newline at end of file diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts new file mode 100644 index 0000000000..e8ba475ed0 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts @@ -0,0 +1 @@ +console.log('Proxy online, systems nominal'); \ No newline at end of file From 5f96a5d9ea5c3b6d147ee0354db239a54fd65707 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:48:24 -0400 Subject: [PATCH 03/10] mock angular-xmcloud content --- .../src/templates/angular-xmcloud/angular.txt | 0 .../src/templates/angular-xmcloud/package.json | 4 ++++ 2 files changed, 4 insertions(+) delete mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt create mode 100644 packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt b/packages/create-sitecore-jss/src/templates/angular-xmcloud/angular.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json new file mode 100644 index 0000000000..92f4d857e1 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/angular-xmcloud/package.json @@ -0,0 +1,4 @@ +{ + "license": "yo-ho-ho" +} + \ No newline at end of file From e294d0af63287abe3c6ffc467afe6c67d05e4532 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:48:38 -0400 Subject: [PATCH 04/10] fix angular initializer --- packages/create-sitecore-jss/src/initializers/angular/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/initializers/angular/index.ts b/packages/create-sitecore-jss/src/initializers/angular/index.ts index d33cdc579a..07f16f3a23 100644 --- a/packages/create-sitecore-jss/src/initializers/angular/index.ts +++ b/packages/create-sitecore-jss/src/initializers/angular/index.ts @@ -29,7 +29,8 @@ export default class AngularInitializer implements Initializer { addInitializers.push('node-xmcloud-proxy'); } } else { - if (!args.templates.includes('angular-sxp')) { + // invoke default non-XMC init + if (!args.templates.includes('angular-sxp') && !args.templates.includes('angular-xmcloud')) { addInitializers.push('angular-sxp'); } } From f8c71a5f6b5bc71d8cb4598f642bc47d74d07e78 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:50:09 -0400 Subject: [PATCH 05/10] Support initializing proxy apps alongside main apps --- packages/create-sitecore-jss/src/bin.test.ts | 227 ++++++++++++------ packages/create-sitecore-jss/src/bin.ts | 109 ++++++--- .../src/common/args/base.ts | 2 +- .../src/common/processes/transform.test.ts | 36 +++ .../src/common/processes/transform.ts | 10 +- .../src/init-runner.test.ts | 43 ++++ .../create-sitecore-jss/src/init-runner.ts | 21 +- 7 files changed, 328 insertions(+), 120 deletions(-) diff --git a/packages/create-sitecore-jss/src/bin.test.ts b/packages/create-sitecore-jss/src/bin.test.ts index 09fe4d1ea8..1ede6abce7 100644 --- a/packages/create-sitecore-jss/src/bin.test.ts +++ b/packages/create-sitecore-jss/src/bin.test.ts @@ -6,7 +6,7 @@ import { sep } from 'path'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { ParsedArgs } from 'minimist'; -import { parseArgs, main } from './bin'; +import { parseArgs, main, promptDestination, getDestinations } from './bin'; import * as helpers from './common/utils/helpers'; import * as initRunner from './init-runner'; @@ -37,6 +37,8 @@ describe('bin', () => { 'test', '--destination', '.\\test\\path', + '--nodeAppDestination', + '.\\test\\proxypath', '--templates', 'foo,bar', '--hostName', @@ -231,34 +233,6 @@ describe('bin', () => { }); }); - it('should prompt for destination if missing', async () => { - getAllTemplatesStub.returns(['foo', 'bar']); - getBaseTemplatesStub.returns(['foo']); - fsExistsSyncStub.returns(false); - fsReaddirSyncStub.returns([]); - inquirerPromptStub.returns({ - prePushHook: true, - }); - - const mockDestination = 'my\\path'; - inquirerPromptStub.returns({ - destination: mockDestination, - }); - - const args = mockArgs({ - templates: 'foo', - }); - const expectedTemplates = ['foo']; - await main(args); - - expect(inquirerPromptStub).to.have.been.called; - expect(initRunnerStub).to.have.been.calledWith(expectedTemplates, { - ...args, - destination: mockDestination, - templates: expectedTemplates, - }); - }); - it('should prompt for prePushHook if missing', async () => { getAllTemplatesStub.returns(['foo', 'bar']); getBaseTemplatesStub.returns(['foo']); @@ -287,88 +261,189 @@ describe('bin', () => { }); }); - describe('destination default', () => { - it('should use appName', async () => { - getAllTemplatesStub.returns(['foo', 'bar']); - getBaseTemplatesStub.returns(['foo']); - fsExistsSyncStub.returns(false); - fsReaddirSyncStub.returns([]); - + describe('promptDestination', () => { + it('should prompt with provided prompt text and return input value', async () => { const mockDestination = 'my\\path'; + const mockPrompt = 'Enter the mocking path'; inquirerPromptStub.returns({ destination: mockDestination, }); + const result = await promptDestination(mockPrompt, 'default'); + expect(inquirerPromptStub).to.have.been.called; + expect(inquirerPromptStub.getCall(0).args[0].message).to.be.equal(mockPrompt); + expect(result).to.be.equal(mockDestination); + }); - const args = mockArgs({ - templates: 'foo', - appName: 'testApp', + it('should use default value', async () => { + inquirerPromptStub.returns({ + destination: undefined, }); - const expectedTemplates = ['foo']; - const expectedDestinationDefault = `${process.cwd()}${sep + args.appName}`; + const defaultDestination = 'defa\\ult'; + await promptDestination('use default here', defaultDestination); + expect(inquirerPromptStub).to.have.been.called; + expect(inquirerPromptStub.getCall(0).args[0].default()).to.be.equal(defaultDestination); + }); + }); - await main(args); + describe('getDestinations', () => { + const testTemplates = ['foo', 'bar']; - expect(inquirerPromptStub).to.have.been.called; - expect(inquirerPromptStub.getCall(0).args[0].default()).to.equal( - expectedDestinationDefault - ); - expect(initRunnerStub).to.have.been.calledWith(expectedTemplates, { - ...args, - destination: mockDestination, - templates: expectedTemplates, + it('should return base args.destination value only when provided', async () => { + const testPath = 'test\\path'; + const testArgs = mockArgs({ + destination: testPath, + }); + expect(await getDestinations(testArgs, testTemplates)).to.deep.equal({ + destination: testPath, }); }); - it('should use template if appName not provided', async () => { - getAllTemplatesStub.returns(['foo', 'bar']); - getBaseTemplatesStub.returns(['foo']); - fsExistsSyncStub.returns(false); - fsReaddirSyncStub.returns([]); + it('should return base and proxy destinations from args when templates contain proxy app', async () => { + const testPath = 'test\\path'; + const proxyPath = 'proxy\\path'; + const testArgs = mockArgs({ + destination: testPath, + nodeAppDestination: proxyPath, + }); + const templatesWithProxy = [...testTemplates, 'node-app-proxy']; + expect(await getDestinations(testArgs, templatesWithProxy)).to.deep.equal({ + destination: testPath, + nodeAppDestination: proxyPath, + }); + }); - const mockDestination = 'my\\path'; + it('should prompt to get base destination when args.destination is empty', async () => { + const testPath = 'test\\path'; inquirerPromptStub.returns({ - destination: mockDestination, + destination: testPath, + }); + const testArgs = mockArgs({ + destination: undefined, }); + await getDestinations(testArgs, testTemplates); + expect(inquirerPromptStub).to.have.been.calledOnce; + expect(inquirerPromptStub.getCall(0).args[0].message).to.be.equal( + 'Where would you like your new app created?' + ); + }); - const args = mockArgs({ - templates: 'foo,bar', + it('should prompt for both base and proxy when destinations are missing in args and templates contain proxy app', async () => { + const testPath = 'test\\path'; + const proxyPath = 'proxy\\path'; + inquirerPromptStub.onCall(0).returns({ + destination: testPath, }); - const expectedTemplates = ['foo', 'bar']; - const expectedDestinationDefault = `${process.cwd()}${sep + expectedTemplates[0]}`; + // avoid paths being equal - this case is tested further down + inquirerPromptStub.onCall(1).returns({ + destination: proxyPath, + }); + const testArgs = mockArgs({ + destination: undefined, + nodeAppDestination: undefined, + }); + const templatesWithProxy = [...testTemplates, 'node-app-proxy']; + await getDestinations(testArgs, templatesWithProxy); + expect(inquirerPromptStub).to.have.been.calledTwice; + expect(inquirerPromptStub.getCall(0).args[0].message).to.be.equal( + 'Where would you like your new app created?' + ); + expect(inquirerPromptStub.getCall(1).args[0].message).to.be.equal( + 'Where would you like your proxy app created?' + ); + }); - await main(args); + it('should return default base destination with base template when --yes arg is used', async () => { + const testArgs = mockArgs({ + destination: undefined, + yes: true, + }); + const expectedDestination = `${process.cwd()}${sep + testTemplates[0]}`; + expect(await getDestinations(testArgs, testTemplates)).to.deep.equal({ + destination: expectedDestination, + }); + }); - expect(inquirerPromptStub).to.have.been.called; - expect(inquirerPromptStub.getCall(0).args[0].default()).to.equal( - expectedDestinationDefault + it('should return default base destination with args.appName when provided and --yes arg is used', async () => { + const testAppName = 'myapp'; + const testArgs = mockArgs({ + destination: undefined, + appName: testAppName, + yes: true, + }); + const expectedDestination = `${process.cwd()}${sep + testAppName}`; + expect(await getDestinations(testArgs, testTemplates)).to.deep.equal({ + destination: expectedDestination, + }); + }); + + it('should return default proxy destination when -- yes arg is used', async () => { + const testPath = 'test\\path'; + const testArgs = mockArgs({ + destination: testPath, + yes: true, + }); + const templatesWithProxy = [...testTemplates, 'node-app-proxy']; + const expectedProxyDestination = `${process.cwd()}${sep + 'node-app-proxy'}`; + expect(await getDestinations(testArgs, templatesWithProxy)).to.deep.equal({ + destination: testPath, + nodeAppDestination: expectedProxyDestination, + }); + }); + + it('should prompt for proxy destination again if proxy destination is the same as base destination', async () => { + const testPath = 'test\\path'; + const proxyPath = 'proxy\\path'; + // avoid paths being equal - this case is tested further down + inquirerPromptStub.onCall(0).returns({ + destination: proxyPath, + }); + const testArgs = mockArgs({ + destination: testPath, + nodeAppDestination: testPath, + }); + const templatesWithProxy = [...testTemplates, 'node-app-proxy']; + await getDestinations(testArgs, templatesWithProxy); + expect(inquirerPromptStub).to.have.been.calledOnce; + expect(inquirerPromptStub.getCall(0).args[0].message).to.be.equal( + 'Proxy app and base app cannot be located in the same folder. Please input another path for proxy' ); - expect(initRunnerStub).to.have.been.calledWith(expectedTemplates, { - ...args, - destination: mockDestination, - templates: expectedTemplates, + }); + + it('should throw when templates are empty', async () => { + const testArgs = mockArgs(); + await getDestinations(testArgs, []).catch((error) => { + expect(error.message).to.be.equal( + 'Unable to get destinations, provided templates are empty' + ); }); }); + }); - it('should respect yes', async () => { + // this partially duplicates tests for getDestinations, but we need to ensure initRunnerStub is called with correct values + // no way around it however - sinon cannot mock getDestinations on its own, which could've prevented this + describe('main with destinations from args', () => { + it('should call initRunnerStub with values from getDestinations', async () => { getAllTemplatesStub.returns(['foo', 'bar']); getBaseTemplatesStub.returns(['foo']); fsExistsSyncStub.returns(false); fsReaddirSyncStub.returns([]); + const mockDestination = 'my\\path'; + const proxyDestination = 'my\\proxy'; + const args = mockArgs({ templates: 'foo', - appName: 'testApp', - yes: true, + destination: mockDestination, + nodeAppDestination: proxyDestination, }); const expectedTemplates = ['foo']; - const expectedDestinationDefault = `${process.cwd()}${sep + args.appName}`; await main(args); - expect(inquirerPromptStub).to.not.have.been.called; - expect(initRunnerStub).to.have.been.calledOnceWith(expectedTemplates, { + expect(initRunnerStub).to.have.been.calledWith(expectedTemplates, { ...args, - destination: expectedDestinationDefault, + destination: mockDestination, + nodeAppDestination: proxyDestination, templates: expectedTemplates, }); }); diff --git a/packages/create-sitecore-jss/src/bin.ts b/packages/create-sitecore-jss/src/bin.ts index 88d6d6a86b..96b0643c7f 100644 --- a/packages/create-sitecore-jss/src/bin.ts +++ b/packages/create-sitecore-jss/src/bin.ts @@ -6,13 +6,23 @@ import { initRunner } from './init-runner'; import minimist, { ParsedArgs } from 'minimist'; import { getAllTemplates, getBaseTemplates } from './common'; +const proxyAppMatcher = /node-.+-proxy/g; + export const parseArgs = (): ParsedArgs => { // parse any command line arguments passed into `init sitecore-jss` // to pass to the generator prompts and skip them. // useful for CI and testing purposes const options = { boolean: ['appPrefix', 'force', 'noInstall', 'yes', 'silent', 'prePushHook'], - string: ['appName', 'destination', 'templates', 'hostName', 'fetchWith', 'language'], + string: [ + 'appName', + 'destination', + 'nodeAppDestination', + 'templates', + 'hostName', + 'fetchWith', + 'language', + ], default: { prePushHook: null }, }; const args: ParsedArgs = minimist(process.argv.slice(2), options); @@ -26,6 +36,60 @@ export const parseArgs = (): ParsedArgs => { return args; }; +export const getDestinations = async (args: ParsedArgs, templates: string[]) => { + if (templates.length === 0) { + throw new Error('Unable to get destinations, provided templates are empty'); + } + // validate/gather destination + const defaultBaseDestination = `${process.cwd()}${ + args.appName ? sep + args.appName : `${sep}${templates[0]}` + }`; + const destination = args.destination + ? args.destination + : args.yes + ? defaultBaseDestination + : await promptDestination('Where would you like your new app created?', defaultBaseDestination); + + // work with node-proxy destination if needed + const proxyApp = templates.find((template) => template.match(proxyAppMatcher)); + if (proxyApp) { + const defaultProxyDestination = proxyApp && `${process.cwd()}${sep}${proxyApp}`; + let nodeAppDestination = args.nodeAppDestination + ? args.nodeAppDestination + : args.yes + ? defaultProxyDestination + : await promptDestination( + 'Where would you like your proxy app created?', + defaultProxyDestination + ); + while (nodeAppDestination === destination) { + nodeAppDestination = await promptDestination( + 'Proxy app and base app cannot be located in the same folder. Please input another path for proxy', + defaultProxyDestination + ); + } + return { + destination, + nodeAppDestination, + }; + } + + return { + destination, + }; +}; + +export const promptDestination = async (prompt: string, defaultDestination: string) => { + return ( + await inquirer.prompt({ + type: 'input', + name: 'destination', + message: prompt, + default: () => defaultDestination, + }) + ).destination; +}; + export const main = async (args: ParsedArgs) => { let templates: string[] = []; @@ -64,39 +128,22 @@ export const main = async (args: ParsedArgs) => { templates.push(answer.template); } - // validate/gather destination - const defaultDestination = `${process.cwd()}${ - args.appName ? sep + args.appName : `${sep}${templates[0]}` - }`; + const destinations = await getDestinations(args, templates); - let destination = args.destination; - - if (!destination) { - if (args.yes) { - destination = defaultDestination; - } else { + for (const destination of [destinations.destination, destinations.nodeAppDestination]) { + if (!destination) continue; + if (!args.force && fs.existsSync(destination) && fs.readdirSync(destination).length > 0) { const answer = await inquirer.prompt({ - type: 'input', - name: 'destination', - message: 'Where would you like your new app created?', - default: () => defaultDestination, + type: 'confirm', + name: 'continue', + message: `Directory '${destination}' not empty. Are you sure you want to continue?`, }); - - destination = answer.destination; - } - } - - if (!args.force && fs.existsSync(destination) && fs.readdirSync(destination).length > 0) { - const answer = await inquirer.prompt({ - type: 'confirm', - name: 'continue', - message: `Directory '${destination}' not empty. Are you sure you want to continue?`, - }); - if (!answer.continue) { - process.exit(); + if (!answer.continue) { + process.exit(); + } + } else { + args.force = true; } - } else { - args.force = true; } if (!args.yes) { @@ -117,7 +164,7 @@ export const main = async (args: ParsedArgs) => { } try { - await initRunner(templates.slice(), { ...args, destination, templates }); + await initRunner(templates.slice(), { ...args, ...destinations, templates }); } catch (error) { console.log(chalk.red('An error occurred: ', error)); process.exit(1); diff --git a/packages/create-sitecore-jss/src/common/args/base.ts b/packages/create-sitecore-jss/src/common/args/base.ts index da6b80c5b8..1e5ac02469 100644 --- a/packages/create-sitecore-jss/src/common/args/base.ts +++ b/packages/create-sitecore-jss/src/common/args/base.ts @@ -34,7 +34,7 @@ export type BaseArgs = { */ prePushHook?: boolean; /** - * Destination for auxillary proxy app, when initialized alongside the main one + * Optional destination for proxy app, whether it's initialized alongside the main one or standalone */ nodeAppDestination?: string; }; diff --git a/packages/create-sitecore-jss/src/common/processes/transform.test.ts b/packages/create-sitecore-jss/src/common/processes/transform.test.ts index e9a1cc8d13..677d5761cb 100644 --- a/packages/create-sitecore-jss/src/common/processes/transform.test.ts +++ b/packages/create-sitecore-jss/src/common/processes/transform.test.ts @@ -533,6 +533,42 @@ describe('transform', () => { }); }); + it('should transform file in proxy destination when processing proxy and proxy location destination', async () => { + const templatePath = path.resolve('templates/node-app-proxy'); + const destinationPath = path.resolve('samples/next'); + const destinationProxy = path.resolve('samples/proxy'); + const file = 'file.ts'; + const renderFileOutput = 'file output'; + + globSyncStub = sinon.stub(glob, 'sync').returns([file]); + ejsRenderFileStub = sinon.stub(ejs, 'renderFile').returns(Promise.resolve(renderFileOutput)); + diffAndWriteFilesStub = sinon.stub(transform, 'diffAndWriteFiles'); + + const answers = { + destination: destinationPath, + nodeAppDestination: destinationProxy, + templates: [], + appPrefix: false, + force: false, + }; + + await transformFunc(templatePath, answers); + + expect(ejsRenderFileStub).to.have.been.calledOnceWith(path.join(templatePath, file), { + ...answers, + helper: { + isDev: false, + getPascalCaseName: helpers.getPascalCaseName, + getAppPrefix: helpers.getAppPrefix, + }, + }); + expect(diffAndWriteFilesStub).to.have.been.calledOnceWith({ + rendered: renderFileOutput, + pathToNewFile: path.join(destinationProxy, file), + answers, + }); + }); + it('should skip if isFileForSkip', async () => { const templatePath = path.resolve('templates/next'); const destinationPath = path.resolve('samples/next'); diff --git a/packages/create-sitecore-jss/src/common/processes/transform.ts b/packages/create-sitecore-jss/src/common/processes/transform.ts index 2533866c60..157b3982a3 100644 --- a/packages/create-sitecore-jss/src/common/processes/transform.ts +++ b/packages/create-sitecore-jss/src/common/processes/transform.ts @@ -230,8 +230,12 @@ export const transform = async ( options: TransformOptions = {} ) => { const { isFileForCopy, isFileForSkip, fileForCopyRegExp = FILE_FOR_COPY_REGEXP } = options; - - const destinationPath = path.resolve(answers.destination); + let destination = undefined; + // allow proxy app to be installed separately alongside base app + if (templatePath.match(/.*node-.+-proxy$/g)) { + destination = answers.nodeAppDestination; + } + const destinationPath = path.resolve(destination || answers.destination); if (!answers.appPrefix) { answers.appPrefix = false; @@ -241,7 +245,7 @@ export const transform = async ( const ejsData: Data = { ...answers, helper: { - isDev: isDevEnvironment(answers.destination), + isDev: isDevEnvironment(destination || answers.destination), getPascalCaseName: getPascalCaseName, getAppPrefix: getAppPrefix, }, diff --git a/packages/create-sitecore-jss/src/init-runner.test.ts b/packages/create-sitecore-jss/src/init-runner.test.ts index cd8d8bb648..800ea799bc 100644 --- a/packages/create-sitecore-jss/src/init-runner.test.ts +++ b/packages/create-sitecore-jss/src/init-runner.test.ts @@ -74,6 +74,49 @@ describe('initRunner', () => { expect(nextStepsStub).to.be.calledOnceWith(appName, []); }); + it('should run for both base and proxy path when latter is provided', async () => { + const templates = ['foo', 'bar']; + const appName = 'test-app'; + const args = { + silent: false, + destination: 'samples/next', + nodeAppDestination: 'samples/proxy', + templates, + }; + + const mockFoo = mockInitializer(true, { appName }); + const mockBar = mockInitializer(false, { appName }); + createStub = sinon.stub(InitializerFactory.prototype, 'create'); + createStub.withArgs('foo').returns(mockFoo); + createStub.withArgs('bar').returns(mockBar); + + await initRunner(templates, args); + + expect(log.getCalls().length).to.equal(2); + templates.forEach((template, i) => { + expect(log.getCall(i).args[0]).to.equal(chalk.cyan(`Initializing '${template}'...`)); + }); + expect(mockFoo.init).to.be.calledOnceWith(args); + expect(mockBar.init).to.be.calledOnceWith(args); + expect(saveConfigurationStub).to.be.calledTwice; + expect(saveConfigurationStub.getCall(0).args[0]).to.deep.equal( + templates, + path.resolve(`${args.destination}${sep}package.json`) + ); + expect(saveConfigurationStub.getCall(1).args[0]).to.deep.equal( + templates, + path.resolve(`${args.nodeAppDestination}${sep}package.json`) + ); + + expect(installPackagesStub).to.be.calledTwice + expect(installPackagesStub.getCall(0).args[0]).to.equal(args.destination); + expect(installPackagesStub.getCall(1).args[0]).to.equal(args.nodeAppDestination); + + expect(lintFixStub).to.be.calledTwice; + expect(lintFixStub.getCall(0).args[0]).to.equal(args.destination); + expect(lintFixStub.getCall(1).args[0]).to.equal(args.nodeAppDestination); + }); + it('should process returned initializers', async () => { const templates = ['foo', 'bar', 'zoo']; const appName = 'test-app'; diff --git a/packages/create-sitecore-jss/src/init-runner.ts b/packages/create-sitecore-jss/src/init-runner.ts index 1fa258094e..552cc18de4 100644 --- a/packages/create-sitecore-jss/src/init-runner.ts +++ b/packages/create-sitecore-jss/src/init-runner.ts @@ -41,16 +41,19 @@ export const initRunner = async (initializers: string[], args: BaseArgs) => { await runner(initializers); - saveConfiguration(args.templates, path.resolve(`${args.destination}${sep}package.json`)); - // final steps (install, lint, etc) - if (!args.noInstall) { - installPackages(args.destination, args.silent); - lintFix(args.destination, args.silent); - } + for (const destination of [args.destination, args.nodeAppDestination]) { + if (!destination) continue; + saveConfiguration(args.templates, path.resolve(`${destination}${sep}package.json`)); + // final steps (install, lint, etc) + if (!args.noInstall) { + installPackages(destination, args.silent); + lintFix(destination, args.silent); + } - // install pre-push hook if user opts-in - if (args.prePushHook) { - await installPrePushHook(args.destination, args.silent); + // install pre-push hook if user opts-in + if (args.prePushHook) { + await installPrePushHook(destination, args.silent); + } } if (!args.silent) { From e3f71d78ac131d8d2ca8152d19953db28d682c5b Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:50:47 -0400 Subject: [PATCH 06/10] lint --- packages/create-sitecore-jss/src/init-runner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/init-runner.test.ts b/packages/create-sitecore-jss/src/init-runner.test.ts index 800ea799bc..2be7f0dcdb 100644 --- a/packages/create-sitecore-jss/src/init-runner.test.ts +++ b/packages/create-sitecore-jss/src/init-runner.test.ts @@ -108,7 +108,7 @@ describe('initRunner', () => { path.resolve(`${args.nodeAppDestination}${sep}package.json`) ); - expect(installPackagesStub).to.be.calledTwice + expect(installPackagesStub).to.be.calledTwice; expect(installPackagesStub.getCall(0).args[0]).to.equal(args.destination); expect(installPackagesStub.getCall(1).args[0]).to.equal(args.nodeAppDestination); From 12352d81efca8ecf26c2148ab88c00c8cd3ef93c Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 19:52:43 -0400 Subject: [PATCH 07/10] small prettier for proxy's index.ts --- .../src/templates/node-xmcloud-proxy/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts index e8ba475ed0..b316592aa9 100644 --- a/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts +++ b/packages/create-sitecore-jss/src/templates/node-xmcloud-proxy/src/index.ts @@ -1 +1 @@ -console.log('Proxy online, systems nominal'); \ No newline at end of file +console.log('Proxy online, systems nominal'); From 8de43cc52533a16a5a28cd2ef95c499be6d7c37e Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Thu, 25 Jul 2024 20:03:07 -0400 Subject: [PATCH 08/10] changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2df353156a..cc9f29465e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ Our versioning strategy is as follows: ### ๐Ÿ›  Breaking Change -* `[create-sitecore-jss]` Rework Angular initializer to support XMCloud and SXP journeys ([#1845](https://github.com/Sitecore/jss/pull/1845)) +* `[create-sitecore-jss]` Rework Angular initializer to support XMCloud and SXP journeys ([#1845](https://github.com/Sitecore/jss/pull/1845))([#1858](https://github.com/Sitecore/jss/pull/1858)) +* `[create-sitecore-jss]` Allows proxy apps to be installed alongside main apps ([#1858](https://github.com/Sitecore/jss/pull/1858)) + * `nodeAppDestination` arg can be passed into `create-sitecore-jss` command to define path for proxy to be installed in ### ๐Ÿงน Chores From cebb55dd1f6d65143568760f71c63bd58e0f9d6b Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Mon, 29 Jul 2024 13:00:47 -0400 Subject: [PATCH 09/10] Pr comments - part 1 --- packages/create-sitecore-jss/src/bin.test.ts | 16 +++---- packages/create-sitecore-jss/src/bin.ts | 43 +++++++++++-------- .../src/common/args/base.ts | 2 +- .../src/common/processes/transform.test.ts | 2 +- .../src/common/processes/transform.ts | 2 +- .../src/init-runner.test.ts | 15 ++----- .../create-sitecore-jss/src/init-runner.ts | 5 ++- 7 files changed, 41 insertions(+), 44 deletions(-) diff --git a/packages/create-sitecore-jss/src/bin.test.ts b/packages/create-sitecore-jss/src/bin.test.ts index 1ede6abce7..946748b019 100644 --- a/packages/create-sitecore-jss/src/bin.test.ts +++ b/packages/create-sitecore-jss/src/bin.test.ts @@ -37,7 +37,7 @@ describe('bin', () => { 'test', '--destination', '.\\test\\path', - '--nodeAppDestination', + '--proxyAppDestination', '.\\test\\proxypath', '--templates', 'foo,bar', @@ -303,12 +303,12 @@ describe('bin', () => { const proxyPath = 'proxy\\path'; const testArgs = mockArgs({ destination: testPath, - nodeAppDestination: proxyPath, + proxyAppDestination: proxyPath, }); const templatesWithProxy = [...testTemplates, 'node-app-proxy']; expect(await getDestinations(testArgs, templatesWithProxy)).to.deep.equal({ destination: testPath, - nodeAppDestination: proxyPath, + proxyAppDestination: proxyPath, }); }); @@ -339,7 +339,7 @@ describe('bin', () => { }); const testArgs = mockArgs({ destination: undefined, - nodeAppDestination: undefined, + proxyAppDestination: undefined, }); const templatesWithProxy = [...testTemplates, 'node-app-proxy']; await getDestinations(testArgs, templatesWithProxy); @@ -386,7 +386,7 @@ describe('bin', () => { const expectedProxyDestination = `${process.cwd()}${sep + 'node-app-proxy'}`; expect(await getDestinations(testArgs, templatesWithProxy)).to.deep.equal({ destination: testPath, - nodeAppDestination: expectedProxyDestination, + proxyAppDestination: expectedProxyDestination, }); }); @@ -399,7 +399,7 @@ describe('bin', () => { }); const testArgs = mockArgs({ destination: testPath, - nodeAppDestination: testPath, + proxyAppDestination: testPath, }); const templatesWithProxy = [...testTemplates, 'node-app-proxy']; await getDestinations(testArgs, templatesWithProxy); @@ -434,7 +434,7 @@ describe('bin', () => { const args = mockArgs({ templates: 'foo', destination: mockDestination, - nodeAppDestination: proxyDestination, + proxyAppDestination: proxyDestination, }); const expectedTemplates = ['foo']; @@ -443,7 +443,7 @@ describe('bin', () => { expect(initRunnerStub).to.have.been.calledWith(expectedTemplates, { ...args, destination: mockDestination, - nodeAppDestination: proxyDestination, + proxyAppDestination: proxyDestination, templates: expectedTemplates, }); }); diff --git a/packages/create-sitecore-jss/src/bin.ts b/packages/create-sitecore-jss/src/bin.ts index 96b0643c7f..c4156985ff 100644 --- a/packages/create-sitecore-jss/src/bin.ts +++ b/packages/create-sitecore-jss/src/bin.ts @@ -17,7 +17,7 @@ export const parseArgs = (): ParsedArgs => { string: [ 'appName', 'destination', - 'nodeAppDestination', + 'proxyAppDestination', 'templates', 'hostName', 'fetchWith', @@ -40,37 +40,42 @@ export const getDestinations = async (args: ParsedArgs, templates: string[]) => if (templates.length === 0) { throw new Error('Unable to get destinations, provided templates are empty'); } - // validate/gather destination + // validate/gather destinations const defaultBaseDestination = `${process.cwd()}${ args.appName ? sep + args.appName : `${sep}${templates[0]}` }`; - const destination = args.destination - ? args.destination - : args.yes - ? defaultBaseDestination - : await promptDestination('Where would you like your new app created?', defaultBaseDestination); + let destination = args.destination; + if (!destination) { + destination = args.yes + ? defaultBaseDestination + : await promptDestination( + 'Where would you like your new app created?', + defaultBaseDestination + ); + } // work with node-proxy destination if needed const proxyApp = templates.find((template) => template.match(proxyAppMatcher)); if (proxyApp) { const defaultProxyDestination = proxyApp && `${process.cwd()}${sep}${proxyApp}`; - let nodeAppDestination = args.nodeAppDestination - ? args.nodeAppDestination - : args.yes - ? defaultProxyDestination - : await promptDestination( - 'Where would you like your proxy app created?', - defaultProxyDestination - ); - while (nodeAppDestination === destination) { - nodeAppDestination = await promptDestination( + let proxyAppDestination = args.proxyAppDestination; + if (!proxyAppDestination) { + proxyAppDestination = args.yes + ? defaultProxyDestination + : await promptDestination( + 'Where would you like your proxy app created?', + defaultProxyDestination + ); + } + while (proxyAppDestination === destination) { + proxyAppDestination = await promptDestination( 'Proxy app and base app cannot be located in the same folder. Please input another path for proxy', defaultProxyDestination ); } return { destination, - nodeAppDestination, + proxyAppDestination, }; } @@ -130,7 +135,7 @@ export const main = async (args: ParsedArgs) => { const destinations = await getDestinations(args, templates); - for (const destination of [destinations.destination, destinations.nodeAppDestination]) { + for (const destination of [destinations.destination, destinations.proxyAppDestination]) { if (!destination) continue; if (!args.force && fs.existsSync(destination) && fs.readdirSync(destination).length > 0) { const answer = await inquirer.prompt({ diff --git a/packages/create-sitecore-jss/src/common/args/base.ts b/packages/create-sitecore-jss/src/common/args/base.ts index 1e5ac02469..9733217106 100644 --- a/packages/create-sitecore-jss/src/common/args/base.ts +++ b/packages/create-sitecore-jss/src/common/args/base.ts @@ -36,7 +36,7 @@ export type BaseArgs = { /** * Optional destination for proxy app, whether it's initialized alongside the main one or standalone */ - nodeAppDestination?: string; + proxyAppDestination?: string; }; /** diff --git a/packages/create-sitecore-jss/src/common/processes/transform.test.ts b/packages/create-sitecore-jss/src/common/processes/transform.test.ts index 677d5761cb..6b91bef4f0 100644 --- a/packages/create-sitecore-jss/src/common/processes/transform.test.ts +++ b/packages/create-sitecore-jss/src/common/processes/transform.test.ts @@ -546,7 +546,7 @@ describe('transform', () => { const answers = { destination: destinationPath, - nodeAppDestination: destinationProxy, + proxyAppDestination: destinationProxy, templates: [], appPrefix: false, force: false, diff --git a/packages/create-sitecore-jss/src/common/processes/transform.ts b/packages/create-sitecore-jss/src/common/processes/transform.ts index 157b3982a3..f8034dc631 100644 --- a/packages/create-sitecore-jss/src/common/processes/transform.ts +++ b/packages/create-sitecore-jss/src/common/processes/transform.ts @@ -233,7 +233,7 @@ export const transform = async ( let destination = undefined; // allow proxy app to be installed separately alongside base app if (templatePath.match(/.*node-.+-proxy$/g)) { - destination = answers.nodeAppDestination; + destination = answers.proxyAppDestination; } const destinationPath = path.resolve(destination || answers.destination); diff --git a/packages/create-sitecore-jss/src/init-runner.test.ts b/packages/create-sitecore-jss/src/init-runner.test.ts index 2be7f0dcdb..25f24c21de 100644 --- a/packages/create-sitecore-jss/src/init-runner.test.ts +++ b/packages/create-sitecore-jss/src/init-runner.test.ts @@ -80,7 +80,7 @@ describe('initRunner', () => { const args = { silent: false, destination: 'samples/next', - nodeAppDestination: 'samples/proxy', + proxyAppDestination: 'samples/proxy', templates, }; @@ -98,23 +98,14 @@ describe('initRunner', () => { }); expect(mockFoo.init).to.be.calledOnceWith(args); expect(mockBar.init).to.be.calledOnceWith(args); - expect(saveConfigurationStub).to.be.calledTwice; - expect(saveConfigurationStub.getCall(0).args[0]).to.deep.equal( - templates, - path.resolve(`${args.destination}${sep}package.json`) - ); - expect(saveConfigurationStub.getCall(1).args[0]).to.deep.equal( - templates, - path.resolve(`${args.nodeAppDestination}${sep}package.json`) - ); expect(installPackagesStub).to.be.calledTwice; expect(installPackagesStub.getCall(0).args[0]).to.equal(args.destination); - expect(installPackagesStub.getCall(1).args[0]).to.equal(args.nodeAppDestination); + expect(installPackagesStub.getCall(1).args[0]).to.equal(args.proxyAppDestination); expect(lintFixStub).to.be.calledTwice; expect(lintFixStub.getCall(0).args[0]).to.equal(args.destination); - expect(lintFixStub.getCall(1).args[0]).to.equal(args.nodeAppDestination); + expect(lintFixStub.getCall(1).args[0]).to.equal(args.proxyAppDestination); }); it('should process returned initializers', async () => { diff --git a/packages/create-sitecore-jss/src/init-runner.ts b/packages/create-sitecore-jss/src/init-runner.ts index 552cc18de4..a174a0b78a 100644 --- a/packages/create-sitecore-jss/src/init-runner.ts +++ b/packages/create-sitecore-jss/src/init-runner.ts @@ -41,9 +41,10 @@ export const initRunner = async (initializers: string[], args: BaseArgs) => { await runner(initializers); - for (const destination of [args.destination, args.nodeAppDestination]) { + saveConfiguration(args.templates, path.resolve(`${args.destination}${sep}package.json`)); + + for (const destination of [args.destination, args.proxyAppDestination]) { if (!destination) continue; - saveConfiguration(args.templates, path.resolve(`${destination}${sep}package.json`)); // final steps (install, lint, etc) if (!args.noInstall) { installPackages(destination, args.silent); From 14ca180c80a8ddc2eed5b1ebb57b9c975af75ed6 Mon Sep 17 00:00:00 2001 From: Artem Alexeyenko Date: Mon, 29 Jul 2024 13:26:03 -0400 Subject: [PATCH 10/10] check for prePushHook command before attempting install --- .../src/common/processes/install.test.ts | 39 +++++++++++++++++-- .../src/common/processes/install.ts | 14 +++++-- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/create-sitecore-jss/src/common/processes/install.test.ts b/packages/create-sitecore-jss/src/common/processes/install.test.ts index 7f2c3bb6ae..6bf316b66d 100644 --- a/packages/create-sitecore-jss/src/common/processes/install.test.ts +++ b/packages/create-sitecore-jss/src/common/processes/install.test.ts @@ -151,11 +151,22 @@ describe('install', () => { }); describe('installPrePushHook', () => { - const execStub = sinon.stub(childProcess, 'exec'); + let execStub: SinonStub; + beforeEach(() => { + execStub = sinon.stub(childProcess, 'exec'); + }); + + afterEach(() => { + execStub?.restore(); + }); it('should run exec function', () => { const destination = './some/path'; - + openPackageJson = sinon.stub(helpers, 'openPackageJson').returns({ + scripts: { + 'install-pre-push-hook': 'stub', + }, + }); installPrePushHook(destination); expect(log).to.have.been.calledOnceWith(chalk.cyan('Installing pre-push hook...')); @@ -167,7 +178,11 @@ describe('install', () => { it('should respect silent', () => { const destination = './some/path'; const silent = true; - + openPackageJson = sinon.stub(helpers, 'openPackageJson').returns({ + scripts: { + 'install-pre-push-hook': 'stub', + }, + }); installPrePushHook(destination, silent); expect(log).to.not.have.been.called; @@ -176,11 +191,27 @@ describe('install', () => { ); }); + it('should skip if installPrePushHook script not defined', () => { + const destination = './some/path'; + openPackageJson = sinon.stub(helpers, 'openPackageJson').returns({ + scripts: {}, + }); + + installPrePushHook(destination); + + expect(log).to.not.have.been.called; + expect(execStub).to.not.have.been.called; + }); + it('should log a warning message if there is an error', async () => { const destination = './some/path'; const error = new Error('some error'); execStub.yields(error); - + openPackageJson = sinon.stub(helpers, 'openPackageJson').returns({ + scripts: { + 'install-pre-push-hook': 'stub', + }, + }); try { await installPrePushHook(destination); } catch (err) { diff --git a/packages/create-sitecore-jss/src/common/processes/install.ts b/packages/create-sitecore-jss/src/common/processes/install.ts index 16f22207ea..17fcc27789 100644 --- a/packages/create-sitecore-jss/src/common/processes/install.ts +++ b/packages/create-sitecore-jss/src/common/processes/install.ts @@ -43,8 +43,7 @@ export const installPackages = (projectFolder: string, silent?: boolean) => { * @param {boolean} [silent] suppress logs */ export const lintFix = (projectFolder: string, silent?: boolean) => { - const packagePath = path.join(projectFolder, 'package.json'); - const pkg = openPackageJson(packagePath); + const pkg = getPackageJson(projectFolder); if (!pkg?.scripts?.lint) { return; } @@ -67,8 +66,12 @@ export const lintFix = (projectFolder: string, silent?: boolean) => { * @param {boolean} [silent] suppress logs */ export const installPrePushHook = async (destination: string, silent?: boolean) => { - silent || console.log(chalk.cyan('Installing pre-push hook...')); + const pkg = getPackageJson(destination); + if (!pkg?.scripts || !pkg.scripts['install-pre-push-hook']) { + return; + } + silent || console.log(chalk.cyan('Installing pre-push hook...')); await new Promise((resolve, reject) => { exec(`cd ${destination} && git init && npm run install-pre-push-hook`, (err) => { if (err) { @@ -81,3 +84,8 @@ export const installPrePushHook = async (destination: string, silent?: boolean) }); }); }; + +const getPackageJson = (projectFolder: string) => { + const packagePath = path.join(projectFolder, 'package.json'); + return openPackageJson(packagePath); +};