From c19532b917386de0965ad3fc98091eb3245bf1ea Mon Sep 17 00:00:00 2001 From: Demian Leon Florin <74616552+florind12@users.noreply.github.com> Date: Tue, 7 Jun 2022 20:39:29 +0300 Subject: [PATCH] feat: template extension integration (#188) --- generators/add-action/analytics/index.js | 12 +- generators/add-action/asset-compute/index.js | 63 ---- .../asset-compute/templates/.npmignore | 23 -- .../asset-compute/templates/README.md | 1 - .../asset-compute/templates/_worker.js | 20 - .../templates/test/corrupt-input/file.jpg | 0 .../templates/test/corrupt-input/params.json | 5 - .../templates/test/simple-test/file.jpg | Bin 90296 -> 0 bytes .../templates/test/simple-test/params.json | 3 - .../templates/test/simple-test/rendition.jpg | Bin 90296 -> 0 bytes .../add-action/audience-manager-cd/index.js | 12 +- .../add-action/campaign-standard/index.js | 12 +- .../add-action/customer-profile/index.js | 12 +- generators/add-action/generic/index.js | 70 ---- .../generic/templates/fetchExample.test.js | 93 ----- generators/add-action/index.js | 24 +- generators/add-action/target/index.js | 12 +- generators/add-ci/index.js | 3 +- generators/add-events/publish-events/index.js | 12 +- generators/add-web-assets/exc-react/index.js | 73 ---- .../exc-react/templates/index.html | 26 -- .../templates/src/components/About.js | 52 --- .../templates/src/components/ActionsForm.js | 215 ----------- .../exc-react/templates/src/components/App.js | 92 ----- .../templates/src/components/Home.js | 20 - .../templates/src/components/SideBar.js | 33 -- .../exc-react/templates/src/config.json | 1 - .../exc-react/templates/src/exc-runtime.js | 23 -- .../exc-react/templates/src/index.css | 58 --- .../exc-react/templates/src/index.js | 82 ---- .../exc-react/templates/src/utils.js | 66 ---- generators/add-web-assets/index.js | 18 +- generators/add-web-assets/raw/index.js | 44 --- .../add-web-assets/raw/templates/404.html | 12 - .../add-web-assets/raw/templates/index.css | 39 -- .../add-web-assets/raw/templates/index.html | 68 ---- .../raw/templates/src/config.json | 1 - .../raw/templates/src/exc-runtime.js | 23 -- .../add-web-assets/raw/templates/src/index.js | 132 ------- .../add-web-assets/raw/templates/src/utils.js | 65 ---- generators/application/index.js | 4 +- generators/base-app/index.js | 3 +- .../common-templates/stub-action.e2e.js | 32 -- generators/common-templates/stub-action.js | 68 ---- generators/common-templates/utils.js | 146 -------- generators/common-templates/utils.test.js | 120 ------ generators/delete-ci/index.js | 3 +- .../dx-asset-compute-worker-1/index.js | 117 ------ generators/extensions/dx-excshell-1/index.js | 101 ----- generators/index.js | 7 +- lib/ActionGenerator.js | 239 ------------ lib/constants.js | 20 - lib/utils.js | 112 ------ package.json | 15 +- test/generators/add-action/analytics.test.js | 2 +- .../add-action/asset-compute.test.js | 234 ------------ .../add-action/audience-manager-cd.test.js | 2 +- .../add-action/campaign-standard.test.js | 2 +- .../add-action/customer-profile.test.js | 2 +- test/generators/add-action/generic.test.js | 135 ------- test/generators/add-action/index.test.js | 108 +++--- test/generators/add-action/target.test.js | 2 +- test/generators/add-events/index.test.js | 2 +- .../add-events/publish-events.test.js | 2 +- .../add-web-assets/exc-react.test.js | 163 -------- test/generators/add-web-assets/index.test.js | 45 +-- test/generators/add-web-assets/raw.test.js | 71 ---- test/generators/application/index.test.js | 8 +- test/generators/delete-ci/index.test.js | 2 +- .../dx-asset-compute-worker-1/index.test.js | 59 --- .../extensions/dx-excshell-1/index.test.js | 47 --- test/lib/ActionGenerator.test.js | 349 ------------------ test/lib/utils.test.js | 309 ---------------- 73 files changed, 177 insertions(+), 3874 deletions(-) delete mode 100755 generators/add-action/asset-compute/index.js delete mode 100755 generators/add-action/asset-compute/templates/.npmignore delete mode 100755 generators/add-action/asset-compute/templates/README.md delete mode 100755 generators/add-action/asset-compute/templates/_worker.js delete mode 100755 generators/add-action/asset-compute/templates/test/corrupt-input/file.jpg delete mode 100755 generators/add-action/asset-compute/templates/test/corrupt-input/params.json delete mode 100755 generators/add-action/asset-compute/templates/test/simple-test/file.jpg delete mode 100755 generators/add-action/asset-compute/templates/test/simple-test/params.json delete mode 100755 generators/add-action/asset-compute/templates/test/simple-test/rendition.jpg delete mode 100644 generators/add-action/generic/index.js delete mode 100644 generators/add-action/generic/templates/fetchExample.test.js delete mode 100644 generators/add-web-assets/exc-react/index.js delete mode 100644 generators/add-web-assets/exc-react/templates/index.html delete mode 100644 generators/add-web-assets/exc-react/templates/src/components/About.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/components/ActionsForm.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/components/App.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/components/Home.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/components/SideBar.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/config.json delete mode 100644 generators/add-web-assets/exc-react/templates/src/exc-runtime.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/index.css delete mode 100644 generators/add-web-assets/exc-react/templates/src/index.js delete mode 100644 generators/add-web-assets/exc-react/templates/src/utils.js delete mode 100644 generators/add-web-assets/raw/index.js delete mode 100644 generators/add-web-assets/raw/templates/404.html delete mode 100644 generators/add-web-assets/raw/templates/index.css delete mode 100644 generators/add-web-assets/raw/templates/index.html delete mode 100644 generators/add-web-assets/raw/templates/src/config.json delete mode 100644 generators/add-web-assets/raw/templates/src/exc-runtime.js delete mode 100644 generators/add-web-assets/raw/templates/src/index.js delete mode 100644 generators/add-web-assets/raw/templates/src/utils.js delete mode 100644 generators/common-templates/stub-action.e2e.js delete mode 100644 generators/common-templates/stub-action.js delete mode 100644 generators/common-templates/utils.js delete mode 100644 generators/common-templates/utils.test.js delete mode 100644 generators/extensions/dx-asset-compute-worker-1/index.js delete mode 100644 generators/extensions/dx-excshell-1/index.js delete mode 100644 lib/ActionGenerator.js delete mode 100644 lib/constants.js delete mode 100644 lib/utils.js delete mode 100644 test/generators/add-action/asset-compute.test.js delete mode 100644 test/generators/add-action/generic.test.js delete mode 100644 test/generators/add-web-assets/exc-react.test.js delete mode 100644 test/generators/add-web-assets/raw.test.js delete mode 100644 test/generators/extensions/dx-asset-compute-worker-1/index.test.js delete mode 100644 test/generators/extensions/dx-excshell-1/index.test.js delete mode 100644 test/lib/ActionGenerator.test.js delete mode 100644 test/lib/utils.test.js diff --git a/generators/add-action/analytics/index.js b/generators/add-action/analytics/index.js index 68977fa8..f4239d98 100644 --- a/generators/add-action/analytics/index.js +++ b/generators/add-action/analytics/index.js @@ -10,8 +10,8 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class AnalyticsGenerator extends ActionGenerator { constructor (args, opts) { @@ -44,11 +44,11 @@ class AnalyticsGenerator extends ActionGenerator { // this.registerTransformStream(beautify({ indent_size: 2 })) this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/getCollections.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dependencies: { '@adobe/aio-sdk': commonDependencyVersions['@adobe/aio-sdk'] diff --git a/generators/add-action/asset-compute/index.js b/generators/add-action/asset-compute/index.js deleted file mode 100755 index 56c156bd..00000000 --- a/generators/add-action/asset-compute/index.js +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2020 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -// ######################################################################################### -// 3rd party template -// ######################################################################################### - -const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') - -class AssetComputeGenerator extends ActionGenerator { - constructor (args, opts) { - super(args, opts) - this.props = {} - } - - async prompting () { - this.props.actionName = await this.promptForActionName('extends the Adobe Asset Compute service', 'worker') - } - - writing () { - this.sourceRoot(path.join(__dirname, './templates')) - - this.addAction(this.props.actionName, './_worker.js', { - tplContext: this.props, - dependencies: { - '@adobe/asset-compute-sdk': '^2.11.1' - }, - devDependencies: { - '@adobe/aio-cli-plugin-asset-compute': '^2.0.0' - }, - actionManifestConfig: { - limits: { - concurrency: 10 - }, - annotations: { - 'require-adobe-auth': true - } - } - }) - - const extFolder = path.dirname(this.configPath) - - // TODO add support in ActionGenerator for copying test folders instead of files - const destTestFolder = this.destinationPath(extFolder, 'test', 'asset-compute', this.props.actionName) - const workerTemplateTestFiles = `${this.templatePath()}/test/` // copy the rest of the worker template files - this.fs.copyTpl( - workerTemplateTestFiles, - destTestFolder, - this.props - ) - } -} - -module.exports = AssetComputeGenerator diff --git a/generators/add-action/asset-compute/templates/.npmignore b/generators/add-action/asset-compute/templates/.npmignore deleted file mode 100755 index 204032a2..00000000 --- a/generators/add-action/asset-compute/templates/.npmignore +++ /dev/null @@ -1,23 +0,0 @@ -# note that .npmignore must be copied into each project -# it does not lookup .npmignore from parent folders - -.vscode -.nyc_output -.circleci -.eslintrc.js -.eslintignore -build/ -coverage/ -.releaserc.json -.travis.yml - -*.aio -*.env - -# npm pack leftovers -*.tgz - -.webpack - -# npm pack leftovers -*.tgz diff --git a/generators/add-action/asset-compute/templates/README.md b/generators/add-action/asset-compute/templates/README.md deleted file mode 100755 index 44c20126..00000000 --- a/generators/add-action/asset-compute/templates/README.md +++ /dev/null @@ -1 +0,0 @@ -N/A \ No newline at end of file diff --git a/generators/add-action/asset-compute/templates/_worker.js b/generators/add-action/asset-compute/templates/_worker.js deleted file mode 100755 index 82698a3b..00000000 --- a/generators/add-action/asset-compute/templates/_worker.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const { worker, SourceCorruptError } = require('@adobe/asset-compute-sdk'); -const fs = require('fs').promises; - -exports.main = worker(async (source, rendition) => { - // Example of how to throw a standard asset compute error - // if e.g. the file is empty or broken. - const stats = await fs.stat(source.path); - if (stats.size === 0) { - throw new SourceCorruptError('source file is empty'); - } - - // Working with sources and renditions happens through local files, - // downloading and uploading is handled by the asset-compute-sdk. - // In this example, simply copy source 1:1 to rendition: - await fs.copyFile(source.path, rendition.path); - - // Tip: custom worker parameters are available in rendition.instructions -}); \ No newline at end of file diff --git a/generators/add-action/asset-compute/templates/test/corrupt-input/file.jpg b/generators/add-action/asset-compute/templates/test/corrupt-input/file.jpg deleted file mode 100755 index e69de29b..00000000 diff --git a/generators/add-action/asset-compute/templates/test/corrupt-input/params.json b/generators/add-action/asset-compute/templates/test/corrupt-input/params.json deleted file mode 100755 index f5092810..00000000 --- a/generators/add-action/asset-compute/templates/test/corrupt-input/params.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "fmt": "jpg", - "qlt": 10, - "errorReason": "SourceCorrupt" -} diff --git a/generators/add-action/asset-compute/templates/test/simple-test/file.jpg b/generators/add-action/asset-compute/templates/test/simple-test/file.jpg deleted file mode 100755 index e86343d498e2dd5bd58cce6f058599fd9e66f659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90296 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·jORZpzw8uG$ZK!^d9D> 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 UK{6};g>Bk;tzpOu*uSIy1O3)6SpWb4 diff --git a/generators/add-action/asset-compute/templates/test/simple-test/params.json b/generators/add-action/asset-compute/templates/test/simple-test/params.json deleted file mode 100755 index 12d73a0b..00000000 --- a/generators/add-action/asset-compute/templates/test/simple-test/params.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "fmt": "jpg" -} \ No newline at end of file diff --git a/generators/add-action/asset-compute/templates/test/simple-test/rendition.jpg b/generators/add-action/asset-compute/templates/test/simple-test/rendition.jpg deleted file mode 100755 index e86343d498e2dd5bd58cce6f058599fd9e66f659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90296 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·jORZpzw8uG$ZK!^d9D> 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 UK{6};g>Bk;tzpOu*uSIy1O3)6SpWb4 diff --git a/generators/add-action/audience-manager-cd/index.js b/generators/add-action/audience-manager-cd/index.js index 997b4c4a..884e3943 100644 --- a/generators/add-action/audience-manager-cd/index.js +++ b/generators/add-action/audience-manager-cd/index.js @@ -10,8 +10,8 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class AudienceManagerCDGenerator extends ActionGenerator { constructor (args, opts) { @@ -45,11 +45,11 @@ class AudienceManagerCDGenerator extends ActionGenerator { // this.registerTransformStream(beautify({ indent_size: 2 })) this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/getProfile.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dependencies: { '@adobe/aio-sdk': commonDependencyVersions['@adobe/aio-sdk'] diff --git a/generators/add-action/campaign-standard/index.js b/generators/add-action/campaign-standard/index.js index 81e614c8..df9ea122 100644 --- a/generators/add-action/campaign-standard/index.js +++ b/generators/add-action/campaign-standard/index.js @@ -10,8 +10,8 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class CampaignStandardGenerator extends ActionGenerator { constructor (args, opts) { @@ -44,11 +44,11 @@ class CampaignStandardGenerator extends ActionGenerator { // this.registerTransformStream(beautify({ indent_size: 2 })) this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/getAllProfiles.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dotenvStub: { label: 'please provide your Adobe I/O Campaign Standard tenant', diff --git a/generators/add-action/customer-profile/index.js b/generators/add-action/customer-profile/index.js index be6a756a..dab05b62 100644 --- a/generators/add-action/customer-profile/index.js +++ b/generators/add-action/customer-profile/index.js @@ -10,8 +10,8 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class CustomerProfileGenerator extends ActionGenerator { constructor (args, opts) { @@ -46,11 +46,11 @@ class CustomerProfileGenerator extends ActionGenerator { writing () { this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/getProfile.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dotenvStub: { label: 'please provide your Adobe Experience Platform Realtime Customer Profile tenant', diff --git a/generators/add-action/generic/index.js b/generators/add-action/generic/index.js deleted file mode 100644 index d6cf9f99..00000000 --- a/generators/add-action/generic/index.js +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') - -class GenericGenerator extends ActionGenerator { - constructor (args, opts) { - super(args, opts) - this.props = { - description: 'This is a sample action showcasing how to access an external API', - // eslint-disable-next-line quotes - requiredParams: `[/* add required params */]`, - // eslint-disable-next-line quotes - requiredHeaders: `['Authorization']`, - // eslint-disable-next-line quotes - importCode: `const fetch = require('node-fetch') -const { Core } = require('@adobe/aio-sdk')`, - - responseCode: `// replace this with the api you want to access - const apiEndpoint = 'https://adobeioruntime.net/api/v1' - - // fetch content from external api endpoint - const res = await fetch(apiEndpoint) - if (!res.ok) { - throw new Error('request to ' + apiEndpoint + ' failed with status code ' + res.status) - } - const content = await res.json() - const response = { - statusCode: 200, - body: content - }` - } - } - - async prompting () { - this.props.actionName = await this.promptForActionName('showcases how to access an external API', 'generic') - } - - writing () { - this.sourceRoot(path.join(__dirname, '.')) - - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { - testFile: './templates/fetchExample.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', - tplContext: this.props, - dependencies: { - '@adobe/aio-sdk': commonDependencyVersions['@adobe/aio-sdk'], - 'node-fetch': '^2.6.0' - }, - actionManifestConfig: { - inputs: { LOG_LEVEL: 'debug' }, - annotations: { final: true } // makes sure loglevel cannot be overwritten by request param - } - }) - } -} - -module.exports = GenericGenerator diff --git a/generators/add-action/generic/templates/fetchExample.test.js b/generators/add-action/generic/templates/fetchExample.test.js deleted file mode 100644 index cd22d64f..00000000 --- a/generators/add-action/generic/templates/fetchExample.test.js +++ /dev/null @@ -1,93 +0,0 @@ -/*<% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -jest.mock('@adobe/aio-sdk', () => ({ - Core: { - Logger: jest.fn() - } -})) - -const { Core } = require('@adobe/aio-sdk') -const mockLoggerInstance = { info: jest.fn(), debug: jest.fn(), error: jest.fn() } -Core.Logger.mockReturnValue(mockLoggerInstance) - -jest.mock('node-fetch') -const fetch = require('node-fetch') -const action = require('./<%= actionRelPath %>') - -beforeEach(() => { - Core.Logger.mockClear() - mockLoggerInstance.info.mockReset() - mockLoggerInstance.debug.mockReset() - mockLoggerInstance.error.mockReset() -}) - -const fakeParams = { __ow_headers: { authorization: 'Bearer fake' } } -describe('<%= actionName %>', () => { - test('main should be defined', () => { - expect(action.main).toBeInstanceOf(Function) - }) - test('should set logger to use LOG_LEVEL param', async () => { - await action.main({ ...fakeParams, LOG_LEVEL: 'fakeLevel' }) - expect(Core.Logger).toHaveBeenCalledWith(expect.any(String), { level: 'fakeLevel' }) - }) - test('should return an http reponse with the fetched content', async () => { - const mockFetchResponse = { - ok: true, - json: () => Promise.resolve({ content: 'fake' }) - } - fetch.mockResolvedValue(mockFetchResponse) - const response = await action.main(fakeParams) - expect(response).toEqual({ - statusCode: 200, - body: { content: 'fake' } - }) - }) - test('if there is an error should return a 500 and log the error', async () => { - const fakeError = new Error('fake') - fetch.mockRejectedValue(fakeError) - const response = await action.main(fakeParams) - expect(response).toEqual({ - error : { - statusCode: 500, - body: { error: 'server error' } - } - }) - expect(mockLoggerInstance.error).toHaveBeenCalledWith(fakeError) - }) - test('if returned service status code is not ok should return a 500 and log the status', async () => { - const mockFetchResponse = { - ok: false, - status: 404 - } - fetch.mockResolvedValue(mockFetchResponse) - const response = await action.main(fakeParams) - expect(response).toEqual({ - error: { - statusCode: 500, - body: { error: 'server error' } - } - }) - // error message should contain 404 - expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining('404') })) - }) - test('missing input request parameters, should return 400', async () => { - const response = await action.main({}) - expect(response).toEqual({ - error: { - statusCode: 400, - body: { error: 'missing header(s) \'authorization\'' } - } - }) - }) -}) diff --git a/generators/add-action/index.js b/generators/add-action/index.js index b1764733..05890432 100644 --- a/generators/add-action/index.js +++ b/generators/add-action/index.js @@ -9,23 +9,25 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const path = require('path') const Generator = require('yeoman-generator') -const { atLeastOne } = require('../../lib/utils') +const { constants, utils } = require('@adobe/generator-app-common-lib') +const { atLeastOne } = utils +const { sdkCodes, isLoopingPrompts } = constants -const { sdkCodes, isLoopingPrompts } = require('../../lib/constants') +const generic = require('@adobe/generator-add-action-generic') +const assetCompute = require('@adobe/generator-add-action-asset-compute') const inquirer = require('inquirer') // we have one actions generator per service, an action generator could generate different types of actions const sdkCodeToActionGenerator = { - [sdkCodes.target]: path.join(__dirname, 'target/index.js'), - [sdkCodes.analytics]: path.join(__dirname, 'analytics/index.js'), - [sdkCodes.campaign]: path.join(__dirname, 'campaign-standard/index.js'), - [sdkCodes.assetCompute]: path.join(__dirname, 'asset-compute/index.js'), - [sdkCodes.customerProfile]: path.join(__dirname, 'customer-profile/index.js'), - [sdkCodes.audienceManagerCD]: path.join(__dirname, 'audience-manager-cd/index.js') + [sdkCodes.target]: require('./target'), + [sdkCodes.analytics]: require('./analytics'), + [sdkCodes.campaign]: require('./campaign-standard'), + [sdkCodes.assetCompute]: assetCompute, + [sdkCodes.customerProfile]: require('./customer-profile'), + [sdkCodes.audienceManagerCD]: require('./audience-manager-cd') } const sdkCodeToTitle = { @@ -37,7 +39,7 @@ const sdkCodeToTitle = { [sdkCodes.audienceManagerCD]: 'Adobe Audience Manager: Customer Data' } -const genericActionGenerator = path.join(__dirname, 'generic/index.js') +const genericActionGenerator = generic /* 'initializing', @@ -91,7 +93,7 @@ class AddActions extends Generator { } // run selected generators - actionGenerators.forEach(gen => this.composeWith(gen, { + actionGenerators.forEach(gen => this.composeWith({ Generator: gen, path: 'unknown' }, { // forward needed args 'skip-prompt': this.options['skip-prompt'], 'action-folder': this.options['action-folder'], diff --git a/generators/add-action/target/index.js b/generators/add-action/target/index.js index 694cf626..bb362a24 100644 --- a/generators/add-action/target/index.js +++ b/generators/add-action/target/index.js @@ -10,9 +10,9 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class TargetGenerator extends ActionGenerator { constructor (args, opts) { @@ -44,11 +44,11 @@ class TargetGenerator extends ActionGenerator { writing () { this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/getActivities.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dotenvStub: { label: 'please provide your Adobe I/O Target tenant', diff --git a/generators/add-ci/index.js b/generators/add-ci/index.js index 6fffd567..f0cce237 100644 --- a/generators/add-ci/index.js +++ b/generators/add-ci/index.js @@ -11,7 +11,8 @@ governing permissions and limitations under the License. const path = require('path') const Generator = require('yeoman-generator') -const { ciDirName } = require('../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') +const { ciDirName } = constants class CIGenerator extends Generator { constructor (args, opts) { diff --git a/generators/add-events/publish-events/index.js b/generators/add-events/publish-events/index.js index 253d3116..283a222a 100644 --- a/generators/add-events/publish-events/index.js +++ b/generators/add-events/publish-events/index.js @@ -10,8 +10,8 @@ governing permissions and limitations under the License. */ const path = require('path') -const ActionGenerator = require('../../../lib/ActionGenerator') -const { commonDependencyVersions } = require('../../../lib/constants') +const { ActionGenerator, constants, commonTemplates } = require('@adobe/generator-app-common-lib') +const { commonDependencyVersions } = constants class CloudEventsGenerator extends ActionGenerator { constructor (args, opts) { @@ -71,11 +71,11 @@ function createCloudEvent(providerId, eventCode, payload) { // this.registerTransformStream(beautify({ indent_size: 2 })) this.sourceRoot(path.join(__dirname, '.')) - this.addAction(this.props.actionName, '../../common-templates/stub-action.js', { + this.addAction(this.props.actionName, commonTemplates['stub-action'], { testFile: './templates/publishEvents.test.js', - sharedLibFile: '../../common-templates/utils.js', - sharedLibTestFile: '../../common-templates/utils.test.js', - e2eTestFile: '../../common-templates/stub-action.e2e.js', + sharedLibFile: commonTemplates.utils, + sharedLibTestFile: commonTemplates['utils.test'], + e2eTestFile: commonTemplates['stub-action.e2e'], tplContext: this.props, dependencies: { '@adobe/aio-sdk': commonDependencyVersions['@adobe/aio-sdk'], diff --git a/generators/add-web-assets/exc-react/index.js b/generators/add-web-assets/exc-react/index.js deleted file mode 100644 index b9463383..00000000 --- a/generators/add-web-assets/exc-react/index.js +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const path = require('path') -const Generator = require('yeoman-generator') - -const utils = require('../../../lib/utils') - -class ExcReactGenerator extends Generator { - constructor (args, opts) { - super(args, opts) - // required - this.option('web-src-folder', { type: String }) - // this.option('skip-prompt', { default: false }) // useless for now - this.option('config-path', { type: String }) - - // props are used by templates - this.props = {} - this.props.projectName = utils.readPackageJson(this).name - } - - // nothing for now - // async prompting () {} - - writing () { - const destFolder = this.options['web-src-folder'] - this.sourceRoot(path.join(__dirname, './templates/')) - - this.fs.copyTpl( - this.templatePath('./**/*'), - this.destinationPath(destFolder), - this.props - ) - // add .babelrc - /// NOTE this is a global file and might conflict - this.fs.writeJSON(this.destinationPath('.babelrc'), { - presets: [['@babel/preset-env', { targets: { node: 'current' } }]], - plugins: ['@babel/plugin-transform-react-jsx'] - }) - // add dependencies - utils.addDependencies(this, { - 'core-js': '^3.6.4', - react: '^16.13.1', - 'react-dom': '^16.13.1', - 'react-router-dom': '^5.2.0', - 'react-error-boundary': '^1.2.5', - 'regenerator-runtime': '^0.13.5', - '@adobe/exc-app': '^0.2.21', - '@adobe/react-spectrum': '^3.4.0', - '@spectrum-icons/workflow': '^3.2.0' - }) - utils.addDependencies( - this, - { - '@babel/core': '^7.8.7', - '@babel/polyfill': '^7.8.7', - '@babel/preset-env': '^7.8.7', - '@babel/plugin-transform-react-jsx': '^7.8.3' - }, - true - ) - } -} - -module.exports = ExcReactGenerator diff --git a/generators/add-web-assets/exc-react/templates/index.html b/generators/add-web-assets/exc-react/templates/index.html deleted file mode 100644 index 14a806c2..00000000 --- a/generators/add-web-assets/exc-react/templates/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - <%= projectName %> - - - -
- - - diff --git a/generators/add-web-assets/exc-react/templates/src/components/About.js b/generators/add-web-assets/exc-react/templates/src/components/About.js deleted file mode 100644 index 2679d24b..00000000 --- a/generators/add-web-assets/exc-react/templates/src/components/About.js +++ /dev/null @@ -1,52 +0,0 @@ -/* <% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -import React from 'react' -import { Heading, View, Content, Link } from '@adobe/react-spectrum' -export const About = () => ( - - Useful documentation for your app - - - - -) diff --git a/generators/add-web-assets/exc-react/templates/src/components/ActionsForm.js b/generators/add-web-assets/exc-react/templates/src/components/ActionsForm.js deleted file mode 100644 index a82c67eb..00000000 --- a/generators/add-web-assets/exc-react/templates/src/components/ActionsForm.js +++ /dev/null @@ -1,215 +0,0 @@ -/* <% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import ErrorBoundary from 'react-error-boundary' -import { - Flex, - Heading, - Form, - Picker, - TextArea, - Button, - ActionButton, - StatusLight, - ProgressCircle, - Item, - Text, - View -} from '@adobe/react-spectrum' -import Function from '@spectrum-icons/workflow/Function' - -import allActions from '../config.json' -import actionWebInvoke from '../utils' - -// remove the deprecated key -const actions = Object.keys(allActions).reduce((obj, key) => { - if (key.lastIndexOf("/") > -1) { - obj[key] = allActions[key]; - } - return obj; -}, {}); - -const ActionsForm = (props) => { - const [state, setState] = useState({ - actionSelected: null, - actionResponse: null, - actionResponseError: null, - actionHeaders: null, - actionHeadersValid: null, - actionParams: null, - actionParamsValid: null, - actionInvokeInProgress: false, - actionResult: '' - }) - - return ( - - Run your application backend actions - {Object.keys(actions).length > 0 && ( -
- ({ name: k }))} - itemKey="name" - onSelectionChange={(name) => - setState({ - ...state, - actionSelected: name, - actionResponseError: null, - actionResponse: null - }) - } - > - {(item) => {item.name}} - - - - -
- - -
- -
- -
-

results

- -
- - - - \ No newline at end of file diff --git a/generators/add-web-assets/raw/templates/src/config.json b/generators/add-web-assets/raw/templates/src/config.json deleted file mode 100644 index 9e26dfee..00000000 --- a/generators/add-web-assets/raw/templates/src/config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/generators/add-web-assets/raw/templates/src/exc-runtime.js b/generators/add-web-assets/raw/templates/src/exc-runtime.js deleted file mode 100644 index b6d13824..00000000 --- a/generators/add-web-assets/raw/templates/src/exc-runtime.js +++ /dev/null @@ -1,23 +0,0 @@ -/*<% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -/** - * - * Script to load the Adobe Experience Cloud Runtime. - * - * @throws {Error} error in case of failure, most likely when the app is not running in - * the Experience Cloud Shell. - * - */ -/* eslint-disable-next-line */ -(function(e,t){if(t.location===t.parent.location)throw new Error("Module Runtime: Needs to be within an iframe!");var o=function(e){var t=new URL(e.location.href).searchParams.get("_mr");return t||!e.EXC_US_HMR?t:e.sessionStorage.getItem("unifiedShellMRScript")}(t);if(!o)throw new Error("Module Runtime: Missing script!");if("https:"!==(o=new URL(decodeURIComponent(o))).protocol)throw new Error("Module Runtime: Must be HTTPS!");if(!/^(exc-unifiedcontent\.)?experience(-qa|-stage|-cdn|-cdn-stage)?\.adobe\.(com|net)$/.test(o.hostname)&&!/localhost\.corp\.adobe\.com$/.test(o.hostname))throw new Error("Module Runtime: Invalid domain!");if(!/\.js$/.test(o.pathname))throw new Error("Module Runtime: Must be a JavaScript file!");t.EXC_US_HMR&&t.sessionStorage.setItem("unifiedShellMRScript",o.toString());var n=e.createElement("script");n.async=1,n.src=o.toString(),n.onload=n.onreadystatechange=function(){n.readyState&&!/loaded|complete/.test(n.readyState)||(n.onload=n.onreadystatechange=null,n=void 0,"EXC_MR_READY"in t&&t.EXC_MR_READY())},e.head.appendChild(n)})(document,window); diff --git a/generators/add-web-assets/raw/templates/src/index.js b/generators/add-web-assets/raw/templates/src/index.js deleted file mode 100644 index 638e4d50..00000000 --- a/generators/add-web-assets/raw/templates/src/index.js +++ /dev/null @@ -1,132 +0,0 @@ -/*<% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -import regeneratorRuntime from 'regenerator-runtime' -import Runtime, { init } from '@adobe/exc-app' -import actions from './config.json' -import actionWebInvoke from './utils.js' - -let state = {} - -window.onload = () => { - /* Here you can bootstrap your application and configure the integration with the Adobe Experience Cloud Shell */ - try { - // attempt to load the Experience Cloud Runtime - require('./exc-runtime') - // if there are no errors, bootstrap the app in the Experience Cloud Shell - init(initRuntime) - } catch (e) { - console.log('application not running in Adobe Experience Cloud Shell') - // fallback mode, run the application without the Experience Cloud Runtime - } - - showActionsList() - document.getElementById('actionForm').onsubmit = (event) => { - event.preventDefault(); - setTimeout(doSubmit, 1) - } -} - -/** - * Initialize runtime and get IMS profile - */ -function initRuntime() { - // get the Experience Cloud Runtime object - const runtime = Runtime() - // ready event brings in authentication/user info - runtime.on('ready', ({ imsOrg, imsToken, imsProfile, locale }) => { - // tell the exc-runtime object we are done - runtime.done() - state = { imsOrg, imsToken, imsProfile, locale } - console.log('exc-app:ready') - }) - // set solution info, shortTitle is used when window is too small to display full title - runtime.solution = { - icon: 'AdobeExperienceCloud', - title: 'test-raw' - } - runtime.title = 'test-raw' -} - -/** - * Generate list of actions - */ -function showActionsList() { - const container = document.getElementById('action-list') - if (Object.keys(actions).length === 0) { - container.innerHTML = 'you have no actions, run aio app add actions to add one' - } else { - container.innerHTML = '' - } -} -/** - * Quick helper to safely call JSON.parse - * @param {string} val - */ -function safeParse(val) { - let result = null - try { - result = JSON.parse(val) - } catch (e) { } - return result -} - -/** - * Submit the form, and get a result back from the action - */ -function doSubmit() { - const actionIndex = document.getElementById('selAction').selectedIndex || 0; - const taOutput = document.getElementById('taOutput') - taOutput.innerHTML = "calling action ..." - if (actions) { - const selAction = Object.entries(actions)[actionIndex] - const headers = safeParse(document.getElementById('actionHeaders').value) - const params = safeParse(document.getElementById('actionParams').value) - // track the time to a result - const preCallTime = Date.now() - let outputHTML = '' - invokeAction(selAction, headers, params) - .then(actionResponse => { - outputHTML = JSON.stringify(actionResponse, 0, 2) - }).catch(err => { - console.error('Error:', err) - outputHTML = err.message - }).finally(( ) => { - taOutput.innerHTML = `time:${(Date.now() - preCallTime)}ms\n\n ${outputHTML}` - }) - } -} - -async function invokeAction(action, _headers, _params) { - const headers = _headers || {} - const params = _params || {} - // all headers to lowercase - Object.keys(headers).forEach((h) => { - const lowercase = h.toLowerCase() - if (lowercase !== h) { - headers[lowercase] = headers[h] - headers[h] = undefined - delete headers[h] - } - }) - // set the authorization header and org from the ims props object - if (state.imsToken && !headers.authorization) { - headers.authorization = `Bearer ${state.imsToken}` - } - if (state.imsOrg && !headers['x-gw-ims-org-id']) { - headers['x-gw-ims-org-id'] = state.imsOrg - } - // action is [name, url] - const result = await actionWebInvoke(action[1], headers, params) - return result -} diff --git a/generators/add-web-assets/raw/templates/src/utils.js b/generators/add-web-assets/raw/templates/src/utils.js deleted file mode 100644 index bd4df67d..00000000 --- a/generators/add-web-assets/raw/templates/src/utils.js +++ /dev/null @@ -1,65 +0,0 @@ -/*<% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -/* global fetch */ - -/** - * - * Invokes a web action - * - * @param {string} actionUrl - * @param {object} headers - * @param {object} params - * - * @returns {Promise} the response - * - */ -async function actionWebInvoke (actionUrl, headers = {}, params = {}, options = { method: 'POST' }) { - const actionHeaders = { - 'Content-Type': 'application/json', - ...headers - } - - const fetchConfig = { - headers: actionHeaders - } - - if (window.location.hostname === 'localhost') { - actionHeaders['x-ow-extra-logging'] = 'on' - } - - fetchConfig.method = options.method.toUpperCase() - - if (fetchConfig.method === 'GET') { - actionUrl = new URL(actionUrl) - Object.keys(params).forEach(key => actionUrl.searchParams.append(key, params[key])) - } else if (fetchConfig.method === 'POST') { - fetchConfig.body = JSON.stringify(params) - } - - const response = await fetch(actionUrl, fetchConfig) - - let content = await response.text() - - if (!response.ok) { - throw new Error(`failed request to '${actionUrl}' with status: ${response.status} and message: ${content}`) - } - try { - content = JSON.parse(content) - } catch (e) { - // response is not json - } - return content -} - -export default actionWebInvoke diff --git a/generators/application/index.js b/generators/application/index.js index 0ae6b7b0..483d5319 100644 --- a/generators/application/index.js +++ b/generators/application/index.js @@ -12,8 +12,8 @@ governing permissions and limitations under the License. const path = require('path') const Generator = require('yeoman-generator') -const { isLoopingPrompts, runtimeManifestKey } = require('../../lib/constants') -const utils = require('../../lib/utils') +const { utils, constants } = require('@adobe/generator-app-common-lib') +const { isLoopingPrompts, runtimeManifestKey } = constants /* 'initializing', diff --git a/generators/base-app/index.js b/generators/base-app/index.js index 74301204..b902ff06 100644 --- a/generators/base-app/index.js +++ b/generators/base-app/index.js @@ -12,7 +12,8 @@ governing permissions and limitations under the License. const path = require('path') const Generator = require('yeoman-generator') -const { dotenvFilename } = require('../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') +const { dotenvFilename } = constants /* 'initializing', 'prompting', diff --git a/generators/common-templates/stub-action.e2e.js b/generators/common-templates/stub-action.e2e.js deleted file mode 100644 index 0c56ca10..00000000 --- a/generators/common-templates/stub-action.e2e.js +++ /dev/null @@ -1,32 +0,0 @@ -/* <% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -const { Config } = require('@adobe/aio-sdk').Core -const fs = require('fs') -const fetch = require('node-fetch') - -// get action url -const namespace = Config.get('runtime.namespace') -const hostname = Config.get('cna.hostname') || 'adobeioruntime.net' -const packagejson = JSON.parse(fs.readFileSync('package.json').toString()) -const runtimePackage = '<%= runtimePackageName %>' -const actionUrl = `https://${namespace}.${hostname}/api/v1/web/${runtimePackage}/<%= actionName %>` - -// The deployed actions are secured with the `require-adobe-auth` annotation. -// If the authorization header is missing, Adobe I/O Runtime returns with a 401 before the action is executed. -test('returns a 401 when missing Authorization header', async () => { - const res = await fetch(actionUrl) - expect(res).toEqual(expect.objectContaining({ - status: 401 - })) -}) diff --git a/generators/common-templates/stub-action.js b/generators/common-templates/stub-action.js deleted file mode 100644 index 0facd14f..00000000 --- a/generators/common-templates/stub-action.js +++ /dev/null @@ -1,68 +0,0 @@ -/*<% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -/** - * <%= description %> - * - * Note: - * You might want to disable authentication and authorization checks against Adobe Identity Management System for a generic action. In that case: - * - Remove the require-adobe-auth annotation for this action in the manifest.yml of your application - * - Remove the Authorization header from the array passed in checkMissingRequestInputs - * - The two steps above imply that every client knowing the URL to this deployed action will be able to invoke it without any authentication and authorization checks against Adobe Identity Management System - * - Make sure to validate these changes against your security requirements before deploying the action - */ - -<% if (!locals.importCode) { %> -const { Core } = require('@adobe/aio-sdk') -<% } %> -<% if (locals.importCode) { %><%- importCode %><% } %> -const { errorResponse, getBearerToken, stringParameters, checkMissingRequestInputs } = require('../utils') - -// main function that will be executed by Adobe I/O Runtime -async function main (params) { - // create a Logger - const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' }) - - try { - // 'info' is the default level if not set - logger.info('Calling the main action') - - // log parameters, only if params.LOG_LEVEL === 'debug' - logger.debug(stringParameters(params)) - - // check for missing request input parameters and headers - const requiredParams = <%- requiredParams %> - const requiredHeaders = <%- requiredHeaders %> - const errorMessage = checkMissingRequestInputs(params, requiredParams, requiredHeaders) - if (errorMessage) { - // return and log client errors - return errorResponse(400, errorMessage, logger) - } - - // extract the user Bearer token from the Authorization header - const token = getBearerToken(params) - - <%- responseCode %> - - // log the response status code - logger.info(`${response.statusCode}: successful request`) - return response - } catch (error) { - // log any server errors - logger.error(error) - // return with 500 - return errorResponse(500, 'server error', logger) - } -} -<% if (locals.inlineUtilityFunctions) { %><%- inlineUtilityFunctions %><% } %> -exports.main = main diff --git a/generators/common-templates/utils.js b/generators/common-templates/utils.js deleted file mode 100644 index a5c8829d..00000000 --- a/generators/common-templates/utils.js +++ /dev/null @@ -1,146 +0,0 @@ -/* <% if (false) { %> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -/* This file exposes some common utilities for your actions */ - -/** - * - * Returns a log ready string of the action input parameters. - * The `Authorization` header content will be replaced by ''. - * - * @param {object} params action input parameters. - * - * @returns {string} - * - */ -function stringParameters (params) { - // hide authorization token without overriding params - let headers = params.__ow_headers || {} - if (headers.authorization) { - headers = { ...headers, authorization: '' } - } - return JSON.stringify({ ...params, __ow_headers: headers }) -} - -/** - * - * Returns the list of missing keys giving an object and its required keys. - * A parameter is missing if its value is undefined or ''. - * A value of 0 or null is not considered as missing. - * - * @param {object} obj object to check. - * @param {array} required list of required keys. - * Each element can be multi level deep using a '.' separator e.g. 'myRequiredObj.myRequiredKey' - * - * @returns {array} - * @private - */ -function getMissingKeys (obj, required) { - return required.filter(r => { - const splits = r.split('.') - const last = splits[splits.length - 1] - const traverse = splits.slice(0, -1).reduce((tObj, split) => { tObj = (tObj[split] || {}); return tObj }, obj) - return traverse[last] === undefined || traverse[last] === '' // missing default params are empty string - }) -} - -/** - * - * Returns the list of missing keys giving an object and its required keys. - * A parameter is missing if its value is undefined or ''. - * A value of 0 or null is not considered as missing. - * - * @param {object} params action input parameters. - * @param {array} requiredHeaders list of required input headers. - * @param {array} requiredParams list of required input parameters. - * Each element can be multi level deep using a '.' separator e.g. 'myRequiredObj.myRequiredKey'. - * - * @returns {string} if the return value is not null, then it holds an error message describing the missing inputs. - * - */ -function checkMissingRequestInputs (params, requiredParams = [], requiredHeaders = []) { - let errorMessage = null - - // input headers are always lowercase - requiredHeaders = requiredHeaders.map(h => h.toLowerCase()) - // check for missing headers - const missingHeaders = getMissingKeys(params.__ow_headers || {}, requiredHeaders) - if (missingHeaders.length > 0) { - errorMessage = `missing header(s) '${missingHeaders}'` - } - - // check for missing parameters - const missingParams = getMissingKeys(params, requiredParams) - if (missingParams.length > 0) { - if (errorMessage) { - errorMessage += ' and ' - } else { - errorMessage = '' - } - errorMessage += `missing parameter(s) '${missingParams}'` - } - - return errorMessage -} - -/** - * - * Extracts the bearer token string from the Authorization header in the request parameters. - * - * @param {object} params action input parameters. - * - * @returns {string|undefined} the token string or undefined if not set in request headers. - * - */ -function getBearerToken (params) { - if (params.__ow_headers && - params.__ow_headers.authorization && - params.__ow_headers.authorization.startsWith('Bearer ')) { - return params.__ow_headers.authorization.substring('Bearer '.length) - } - return undefined -} -/** - * - * Returns an error response object and attempts to log.info the status code and error message - * - * @param {number} statusCode the error status code. - * e.g. 400 - * @param {string} message the error message. - * e.g. 'missing xyz parameter' - * @param {*} [logger] an optional logger instance object with an `info` method - * e.g. `new require('@adobe/aio-sdk').Core.Logger('name')` - * - * @returns {object} the error object, ready to be returned from the action main's function. - * - */ -function errorResponse (statusCode, message, logger) { - if (logger && typeof logger.info === 'function') { - logger.info(`${statusCode}: ${message}`) - } - return { - error: { - statusCode, - body: { - error: message - } - } - } -} - -module.exports = { - errorResponse, - getBearerToken, - stringParameters, - checkMissingRequestInputs -} diff --git a/generators/common-templates/utils.test.js b/generators/common-templates/utils.test.js deleted file mode 100644 index 23da5a9c..00000000 --- a/generators/common-templates/utils.test.js +++ /dev/null @@ -1,120 +0,0 @@ -/* <% if (false) {%> -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -<% } %> -* -*/ - -const utils = require('./<%= utilsRelPath %>') - -test('interface', () => { - expect(typeof utils.errorResponse).toBe('function') - expect(typeof utils.stringParameters).toBe('function') - expect(typeof utils.checkMissingRequestInputs).toBe('function') - expect(typeof utils.getBearerToken).toBe('function') -}) - -describe('errorResponse', () => { - test('(400, errorMessage)', () => { - const res = utils.errorResponse(400, 'errorMessage') - expect(res).toEqual({ - error: { - statusCode: 400, - body: { error: 'errorMessage' } - } - }) - }) - - test('(400, errorMessage, logger)', () => { - const logger = { - info: jest.fn() - } - const res = utils.errorResponse(400, 'errorMessage', logger) - expect(logger.info).toHaveBeenCalledWith('400: errorMessage') - expect(res).toEqual({ - error: { - statusCode: 400, - body: { error: 'errorMessage' } - } - }) - }) -}) - -describe('stringParameters', () => { - test('no auth header', () => { - const params = { - a: 1, b: 2, __ow_headers: { 'x-api-key': 'fake-api-key' } - } - expect(utils.stringParameters(params)).toEqual(JSON.stringify(params)) - }) - test('with auth header', () => { - const params = { - a: 1, b: 2, __ow_headers: { 'x-api-key': 'fake-api-key', authorization: 'secret' } - } - expect(utils.stringParameters(params)).toEqual(expect.stringContaining('"authorization":""')) - expect(utils.stringParameters(params)).not.toEqual(expect.stringContaining('secret')) - }) -}) - -describe('checkMissingRequestInputs', () => { - test('({ a: 1, b: 2 }, [a])', () => { - expect(utils.checkMissingRequestInputs({ a: 1, b: 2 }, ['a'])).toEqual(null) - }) - test('({ a: 1 }, [a, b])', () => { - expect(utils.checkMissingRequestInputs({ a: 1 }, ['a', 'b'])).toEqual('missing parameter(s) \'b\'') - }) - test('({ a: { b: { c: 1 } }, f: { g: 2 } }, [a.b.c, f.g.h.i])', () => { - expect(utils.checkMissingRequestInputs({ a: { b: { c: 1 } }, f: { g: 2 } }, ['a.b.c', 'f.g.h.i'])).toEqual('missing parameter(s) \'f.g.h.i\'') - }) - test('({ a: { b: { c: 1 } }, f: { g: 2 } }, [a.b.c, f.g.h])', () => { - expect(utils.checkMissingRequestInputs({ a: { b: { c: 1 } }, f: { g: 2 } }, ['a.b.c', 'f'])).toEqual(null) - }) - test('({ a: 1, __ow_headers: { h: 1, i: 2 } }, undefined, [h])', () => { - expect(utils.checkMissingRequestInputs({ a: 1, __ow_headers: { h: 1, i: 2 } }, undefined, ['h'])).toEqual(null) - }) - test('({ a: 1, __ow_headers: { f: 2 } }, [a], [h, i])', () => { - expect(utils.checkMissingRequestInputs({ a: 1, __ow_headers: { f: 2 } }, ['a'], ['h', 'i'])).toEqual('missing header(s) \'h,i\'') - }) - test('({ c: 1, __ow_headers: { f: 2 } }, [a, b], [h, i])', () => { - expect(utils.checkMissingRequestInputs({ c: 1 }, ['a', 'b'], ['h', 'i'])).toEqual('missing header(s) \'h,i\' and missing parameter(s) \'a,b\'') - }) - test('({ a: 0 }, [a])', () => { - expect(utils.checkMissingRequestInputs({ a: 0 }, ['a'])).toEqual(null) - }) - test('({ a: null }, [a])', () => { - expect(utils.checkMissingRequestInputs({ a: null }, ['a'])).toEqual(null) - }) - test('({ a: \'\' }, [a])', () => { - expect(utils.checkMissingRequestInputs({ a: '' }, ['a'])).toEqual('missing parameter(s) \'a\'') - }) - test('({ a: undefined }, [a])', () => { - expect(utils.checkMissingRequestInputs({ a: undefined }, ['a'])).toEqual('missing parameter(s) \'a\'') - }) -}) - -describe('getBearerToken', () => { - test('({})', () => { - expect(utils.getBearerToken({})).toEqual(undefined) - }) - test('({ authorization: Bearer fake, __ow_headers: {} })', () => { - expect(utils.getBearerToken({ authorization: 'Bearer fake', __ow_headers: {} })).toEqual(undefined) - }) - test('({ authorization: Bearer fake, __ow_headers: { authorization: fake } })', () => { - expect(utils.getBearerToken({ authorization: 'Bearer fake', __ow_headers: { authorization: 'fake' } })).toEqual(undefined) - }) - test('({ __ow_headers: { authorization: Bearerfake} })', () => { - expect(utils.getBearerToken({ __ow_headers: { authorization: 'Bearerfake' } })).toEqual(undefined) - }) - test('({ __ow_headers: { authorization: Bearer fake} })', () => { - expect(utils.getBearerToken({ __ow_headers: { authorization: 'Bearer fake' } })).toEqual('fake') - }) - test('({ __ow_headers: { authorization: Bearer fake Bearer fake} })', () => { - expect(utils.getBearerToken({ __ow_headers: { authorization: 'Bearer fake Bearer fake' } })).toEqual('fake Bearer fake') - }) -}) diff --git a/generators/delete-ci/index.js b/generators/delete-ci/index.js index 43e5426f..770d2060 100644 --- a/generators/delete-ci/index.js +++ b/generators/delete-ci/index.js @@ -12,7 +12,8 @@ governing permissions and limitations under the License. const Generator = require('yeoman-generator') const fs = require('fs-extra') -const { ciDirName } = require('../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') +const { ciDirName } = constants const DEPLOY_PROD_FILENAME = '/workflows/deploy_prod.yml' const DEPLOY_STAGE_FILENAME = '/workflows/deploy_stage.yml' const TEST_PR_FILENAME = '/workflows/pr_test.yml' diff --git a/generators/extensions/dx-asset-compute-worker-1/index.js b/generators/extensions/dx-asset-compute-worker-1/index.js deleted file mode 100644 index d8d5a3b8..00000000 --- a/generators/extensions/dx-asset-compute-worker-1/index.js +++ /dev/null @@ -1,117 +0,0 @@ -/* -Copyright 2021 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const path = require('path') -const Generator = require('yeoman-generator') - -const utils = require('../../../lib/utils') -const { runtimeManifestKey } = require('../../../lib/constants') -const upath = require('upath') - -const assetComputeActionGenerator = path.join(__dirname, '../../add-action/asset-compute/index.js') - -/* - 'initializing', - 'prompting', - 'configuring', - 'default', - 'writing', - 'conflicts', - 'install', - 'end' - */ - -class DxAssetComputeWorker1 extends Generator { - constructor (args, opts) { - super(args, opts) - - // options are inputs from CLI or yeoman parent generator - this.option('skip-prompt', { default: false }) - } - - async initializing () { - // all paths are relative to root - this.extFolder = 'src/dx-asset-compute-worker-1' - this.actionFolder = path.join(this.extFolder, 'actions') - this.extConfigPath = path.join(this.extFolder, 'ext.config.yaml') - this.configName = 'dx/asset-compute/worker/1' - // generate the nui action - this.composeWith(assetComputeActionGenerator, { - // forward needed args - 'skip-prompt': true, // do not prompt for action name - 'action-folder': this.actionFolder, - 'config-path': this.extConfigPath, - 'full-key-to-manifest': runtimeManifestKey - }) - } - - async writing () { - const unixExtConfigPath = upath.toUnix(this.extConfigPath) - // add the extension point config in root - utils.writeKeyAppConfig( - this, - // key - 'extensions.' + this.configName, - // value - { - $include: unixExtConfigPath - } - ) - - // add required dotenv vars - utils.appendStubVarsToDotenv( - this, - 'please provide the following environment variables for the Asset Compute devtool. You can use AWS or Azure, not both:', - [ - 'ASSET_COMPUTE_PRIVATE_KEY_FILE_PATH', - 'S3_BUCKET', - 'AWS_ACCESS_KEY_ID', - 'AWS_SECRET_ACCESS_KEY', - 'AWS_REGION', - 'AZURE_STORAGE_ACCOUNT', - 'AZURE_STORAGE_KEY', - 'AZURE_STORAGE_CONTAINER_NAME' - ] - ) - - // add extension point operation - utils.writeKeyYAMLConfig( - this, - this.extConfigPath, - // key - 'operations', { - workerProcess: [ - { type: 'action', impl: 'dx-asset-compute-worker-1/worker' } - ] - } - ) - - // add hooks to ext config - utils.writeKeyYAMLConfig( - this, - this.extConfigPath, - // key - 'hooks', - // value - { - 'post-app-run': 'adobe-asset-compute devtool', - test: 'adobe-asset-compute test-worker' - } - ) - - // add actions path, relative to config path - utils.writeKeyYAMLConfig(this, this.extConfigPath, 'actions', path.relative(this.extFolder, this.actionFolder)) - - // TODO add .npmignore and readme - } -} - -module.exports = DxAssetComputeWorker1 diff --git a/generators/extensions/dx-excshell-1/index.js b/generators/extensions/dx-excshell-1/index.js deleted file mode 100644 index c75f6945..00000000 --- a/generators/extensions/dx-excshell-1/index.js +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2021 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const path = require('path') -const Generator = require('yeoman-generator') -const { runtimeManifestKey } = require('../../../lib/constants') -const upath = require('upath') - -const utils = require('../../../lib/utils') - -const genericActionGenerator = path.join(__dirname, '../../add-action/generic/index.js') -const excReactWebAssetsGenerator = path.join(__dirname, '../../add-web-assets/exc-react/index.js') -/* - 'initializing', - 'prompting', - 'configuring', - 'default', - 'writing', - 'conflicts', - 'install', - 'end' - */ - -class DxExcshell1 extends Generator { - constructor (args, opts) { - super(args, opts) - - // options are inputs from CLI or yeoman parent generator - this.option('skip-prompt', { default: false }) - } - - async initializing () { - // all paths are relative to root - this.extFolder = 'src/dx-excshell-1' - this.actionFolder = path.join(this.extFolder, 'actions') - // todo support multi UI (could be one for each operation) - this.webSrcFolder = path.join(this.extFolder, 'web-src') - this.extConfigPath = path.join(this.extFolder, 'ext.config.yaml') - this.configName = 'dx/excshell/1' - - // generate the generic action - this.composeWith(genericActionGenerator, { - // forward needed args - 'skip-prompt': true, // do not ask for action name - 'action-folder': this.actionFolder, - 'config-path': this.extConfigPath, - 'full-key-to-manifest': runtimeManifestKey - }) - - // generate the UI - this.composeWith(excReactWebAssetsGenerator, { - // forward needed args - 'skip-prompt': this.options['skip-prompt'], - 'web-src-folder': this.webSrcFolder, - 'config-path': this.extConfigPath - }) - } - - async writing () { - const unixExtConfigPath = upath.toUnix(this.extConfigPath) - // add the extension point config in root - utils.writeKeyAppConfig( - this, - // key - 'extensions.' + this.configName, - // value - { - // posix separator - - $include: unixExtConfigPath - } - ) - - // add extension point operation - utils.writeKeyYAMLConfig( - this, - this.extConfigPath, - // key - 'operations', { - view: [ - { type: 'web', impl: 'index.html' } - ] - } - ) - - // add actions path, relative to config file - utils.writeKeyYAMLConfig(this, this.extConfigPath, 'actions', path.relative(this.extFolder, this.actionFolder)) - // add web-src path, relative to config file - utils.writeKeyYAMLConfig(this, this.extConfigPath, 'web', path.relative(this.extFolder, this.webSrcFolder)) - } -} - -module.exports = DxExcshell1 diff --git a/generators/index.js b/generators/index.js index 5e62e6f2..2c2b83fc 100644 --- a/generators/index.js +++ b/generators/index.js @@ -9,6 +9,9 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ +const excshell = require('@adobe/generator-app-excshell') +const assetComputeWorker = require('@adobe/generator-app-asset-compute') + module.exports = { 'add-action': require('./add-action'), 'add-ci': require('./add-ci'), @@ -19,7 +22,7 @@ module.exports = { 'base-app': require('./base-app'), 'delete-ci': require('./delete-ci'), extensions: { - 'dx/excshell/1': require('./extensions/dx-excshell-1'), - 'dx/asset-compute/worker/1': require('./extensions/dx-asset-compute-worker-1') + 'dx/excshell/1': excshell, + 'dx/asset-compute/worker/1': assetComputeWorker } } diff --git a/lib/ActionGenerator.js b/lib/ActionGenerator.js deleted file mode 100644 index 4a38f610..00000000 --- a/lib/ActionGenerator.js +++ /dev/null @@ -1,239 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const path = require('path') -const upath = require('upath') - -const Generator = require('yeoman-generator') -const utils = require('./utils') - -class ActionGenerator extends Generator { - constructor (args, opts) { - super(args, opts) - this.option('skip-prompt', { default: false }) // prompt to ask action name - // required - this.option('action-folder', { type: String }) - this.option('config-path', { type: String }) - this.option('full-key-to-manifest', { type: String, default: '' }) // key in config path that resolves to manifest e.g. 'application.runtimeManifest' - - // path to store action and runtime config - this.actionFolder = this.options['action-folder'] // todo ensure this is relative to root - this.configPath = this.destinationPath(this.options['config-path']) - this.fullKeyToManifest = this.options['full-key-to-manifest'] - // load manifest and package name - this.defaultRuntimePackageName = path.basename(path.dirname(this.configPath)) // todo do better, allow to pass in package name ? - } - - async promptForActionName (actionPurpose, defaultValue) { - const { runtimeManifest, runtimePackageName } = this.loadRuntimeManifest(this.configPath, this.fullKeyToManifest, this.defaultRuntimePackageName) - let actionName = this.getDefaultActionName(defaultValue, runtimeManifest, runtimePackageName) - if (!this.options['skip-prompt']) { - const promptProps = await this.prompt([ - { - type: 'input', - name: 'actionName', - message: `We are about to create a new sample action that ${actionPurpose}.\nHow would you like to name this action?`, - default: actionName, - when: !this.options['skip-prompt'], - validate (input) { - // must be a valid openwhisk action name, this is a simplified set see: - // https://github.com/apache/openwhisk/blob/master/docs/reference.md#entity-names - const valid = /^[a-zA-Z0-9][a-zA-Z0-9-]{2,31}$/ - if (valid.test(input)) { - return true - } - return `'${input}' is not a valid action name, please make sure that: -The name has at least 3 characters or less than 33 characters. -The first character is an alphanumeric character. -The subsequent characters are alphanumeric. -The last character isn't a space. -Note: characters can only be split by '-'. -` - } - } - ]) - actionName = promptProps.actionName - } - - return actionName - } - - /** - * Adds a new action to the project - * - * @param {string} actionName - * @param {string} tplActionPath - * @param {object} [options={}] - * @param {object} [options.testFile] - * @param {object} [options.e2eTestFile] - * @param {object} [options.sharedLibFile] - * @param {object} [options.sharedLibTestFile] - * @param {object} [options.tplContext] - * @param {object} [options.dotenvStub] - * @param {string} options.dotenvStub.label - * @param {Array} options.dotenvStub.vars - * @param {object} [options.dependencies] - * @param {object} [options.devDependencies] - * @param {object} [options.actionManifestConfig] - * @memberof ActionGenerator - */ - addAction (actionName, tplActionPath, options = {}) { - // NOTE: it's important to load a fresh manifest now, as we do want to include the - // latest written data (in case of add multiple actions concurrently) - const { runtimeManifest, runtimePackageName } = this.loadRuntimeManifest(this.configPath, this.fullKeyToManifest, this.defaultRuntimePackageName) - - options.tplContext = options.tplContext || {} - - // relativeToRoot - this.extRoot = path.dirname(this.configPath) - this.actionPath = path.join(this.actionFolder, actionName, 'index.js') - this.relativeActionPath = path.relative(path.dirname(this.configPath), this.actionPath) - - this.writeTplAction(tplActionPath, this.actionPath, options.tplContext) - - this.setRuntimeManifestAction(actionName, this.relativeActionPath, runtimeManifest, runtimePackageName, options.actionManifestConfig) - this.writeRuntimeManifest(this.configPath, this.fullKeyToManifest, runtimeManifest) - - if (options.testFile) { - // this only works if action folder is relative - const testDestPath = this.destinationPath(this.extRoot, 'test', `${actionName}.test.js`) - this.writeActionTest(options.testFile, testDestPath, this.actionPath, options.tplContext) - } - if (options.sharedLibFile) { - // the sharedLibFile is always put in the root of this.actionFolder, this is important for sharedLibTestFile below - const sharedLibFileDestPath = this.destinationPath(path.join(this.actionFolder, path.basename(options.sharedLibFile))) - this.fs.copyTpl(this.templatePath(options.sharedLibFile), sharedLibFileDestPath, options.tplContext) - - if (options.sharedLibTestFile) { - const sharedLibTestFileDestPath = this.destinationPath(this.extRoot, 'test', path.basename(options.sharedLibTestFile)) - this.writeUtilsTest(options.sharedLibTestFile, sharedLibTestFileDestPath, sharedLibFileDestPath, options.tplContext) - } - } - if (options.e2eTestFile) { - // this only works if action folder is relative - const testDestPath = this.destinationPath(this.extRoot, 'e2e', `${actionName}.e2e.test.js`) - this.writeActionE2ETest(options.e2eTestFile, testDestPath, { runtimePackageName, ...options.tplContext }) - } - if (options.dotenvStub) { - utils.appendStubVarsToDotenv(this, options.dotenvStub.label, options.dotenvStub.vars) - } - if (options.dependencies) { - utils.addDependencies(this, options.dependencies) - } - // make sure wskdebug is there - utils.addDependencies(this, { '@openwhisk/wskdebug': '^1.3.0', ...options.devDependencies }, true) - // make sure the node engines are added - this.addPackageJsonNodeEngines() - } - - /** @private */ - loadRuntimeManifest (configPath, fullKeyToManifest, defaultPkgName) { - const config = utils.readYAMLConfig(this, configPath) - const runtimeManifest = fullKeyToManifest.split('.').reduce((obj, k) => obj && obj[k], config) || {} - - let pkgName - - // setup pkg if none - if (!runtimeManifest.packages || Object.keys(runtimeManifest.packages).length <= 0) { - pkgName = defaultPkgName - runtimeManifest.packages = { - [pkgName]: { - license: 'Apache-2.0', - actions: {} - } - } - } - - // else add to first package - pkgName = Object.keys(runtimeManifest.packages)[0] - - return { runtimeManifest, runtimePackageName: pkgName } - } - - /** @private */ - writeRuntimeManifest (configPath, fullKeyToManifest, runtimeManifest) { - utils.writeKeyYAMLConfig(this, configPath, fullKeyToManifest, runtimeManifest) - } - - /** @private */ - setRuntimeManifestAction (actionName, relActionPath, runtimeManifest, pkgName, actionManifestConfig = {}) { - // in place - relActionPath = upath.toUnix(relActionPath) // relative to config File - runtimeManifest.packages[pkgName].actions[actionName] = { - function: relActionPath, - web: 'yes', - runtime: 'nodejs:14', - ...actionManifestConfig, - annotations: { 'require-adobe-auth': true, ...actionManifestConfig.annotations } - } - return runtimeManifest - } - - /** @private */ - getDefaultActionName (defaultActionNameBase, runtimeManifest, pkgName) { - let actionName = defaultActionNameBase - let defaultIndex = 1 - - while (actionName in runtimeManifest.packages[pkgName].actions) { - actionName = defaultActionNameBase + '-' + defaultIndex - defaultIndex++ - } - return actionName - } - - /** @private */ - writeTplAction (tplActionPath, destPath, tplContext) { - // Note: other options of copyTpl (templateOptions && copyOptions) could be useful - this.fs.copyTpl(this.templatePath(tplActionPath), destPath, tplContext, {}, {}) - } - - /** @private */ - writeActionTest (tplActionTest, testDestPath, actionPath, tplContext) { - // enriches tplContext with actionRelPath to be required from test file - tplContext = { actionRelPath: upath.toUnix(path.relative(path.dirname(testDestPath), actionPath)), ...tplContext } - // should we support other options of copyTpl (templateOptions && copyOptions) ? - this.fs.copyTpl(this.templatePath(tplActionTest), testDestPath, tplContext, {}, {}) - - return testDestPath - } - - /** @private */ - writeUtilsTest (tplUtilsTest, testDestPath, utilsPath, tplContext) { - // enriches tplContext with utilsRelPath to be required from test file - tplContext = { utilsRelPath: upath.toUnix(path.relative(path.dirname(testDestPath), utilsPath)), ...tplContext } - // should we support other options of copyTpl (templateOptions && copyOptions) ? - this.fs.copyTpl(this.templatePath(tplUtilsTest), testDestPath, tplContext, {}, {}) - - return testDestPath - } - - /** @private */ - writeActionE2ETest (tplActionTest, testDestPath, tplContext) { - // should we support other options of copyTpl (templateOptions && copyOptions) ? - this.fs.copyTpl(this.templatePath(tplActionTest), testDestPath, tplContext, {}, {}) - - return testDestPath - } - - /** @private */ - addPackageJsonNodeEngines () { - const content = utils.readPackageJson(this) - const engines = content.engines || {} - if (!engines.node) { - // do not overwrite existing node engines - engines.node = '^10 || ^12 || ^14' - utils.writePackageJson(this, { ...content, engines }) - } - } -} - -module.exports = ActionGenerator diff --git a/lib/constants.js b/lib/constants.js deleted file mode 100644 index 04ec57f9..00000000 --- a/lib/constants.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - isLoopingPrompts: false, - actionsDirname: 'actions', - dotenvFilename: '.env', - manifestPackagePlaceholder: '__APP_PACKAGE__', - sdkCodes: { - analytics: 'AdobeAnalyticsSDK', - assetCompute: 'AssetComputeSDK', - campaign: 'CampaignSDK', - customerProfile: 'McDataServicesSdk', - target: 'AdobeTargetSDK', - audienceManagerCD: 'AudienceManagerCustomerSDK' - }, - ciDirName: '.github', - commonDependencyVersions: { - '@adobe/aio-sdk': '^3.0.0' - }, - appConfigFile: 'app.config.yaml', - runtimeManifestKey: 'runtimeManifest' -} diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index b116f376..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,112 +0,0 @@ -const path = require('path') -const yaml = require('js-yaml') -const { EOL } = require('os') -const { appConfigFile, dotenvFilename } = require('./constants') - -function atLeastOne (input) { - if (input.length === 0) { - return 'please choose at least one option' - } - return true -} - -/* app generator specific utilities */ - -// NOTE: supports setting multilayer keys like 'a.b.c=value' -function writeKeyAppConfig (generator, key, value) { - const appConfigPath = generator.destinationPath(appConfigFile) - writeKeyYAMLConfig(generator, appConfigPath, key, value) -} - -// NOTE: supports setting multilayer keys like 'a.b.c=value' -function writeKeyYAMLConfig (generator, configPath, key, value) { - const config = readYAMLConfig(generator, configPath) - writeMultiLayerKeyInObject(config, key, value) - writeYAMLConfig(generator, configPath, config) -} - -function writeMultiLayerKeyInObject (obj, key, value) { - const interKeys = key.split('.') - interKeys.slice(0, -1).forEach(k => { - if (!obj[k]) { - obj[k] = {} - } - obj = obj[k] - }) - // last interKey => write value - const last = interKeys.slice(-1) - // assumes obj[last] is same type of value - if (Array.isArray(value)) { - obj[last] = [...obj[last], ...value] - } else if (typeof value === 'object') { - obj[last] = { ...obj[last], ...value } - } else { - obj[last] = value - } -} - -function readYAMLConfig (generator, configPath) { - if (!generator.fs.exists(configPath)) { - return {} - } - return yaml.safeLoad(generator.fs.read(configPath)) -} - -function writeYAMLConfig (generator, configPath, config) { - generator.fs.write(configPath, yaml.safeDump(config)) -} - -function guessProjectName (generator) { - const packagejsonPath = generator.destinationPath('package.json') - return (generator.fs.exists(packagejsonPath) && generator.fs.readJSON('package.json').name) || path.basename(process.cwd()) -} - -function readPackageJson (generator) { - const packagejsonPath = generator.destinationPath('package.json') - return generator.fs.readJSON(packagejsonPath) || {} -} - -function writePackageJson (generator, content) { - const packagejsonPath = generator.destinationPath('package.json') - return generator.fs.writeJSON(packagejsonPath, content || {}) -} - -function addDependencies (generator, deps, dev = false) { - const content = readPackageJson(generator) - const key = dev ? 'devDependencies' : 'dependencies' - content[key] = { ...content[key], ...deps } - writePackageJson(generator, content) -} - -function addPkgScript (generator, scripts) { - const content = readPackageJson(generator) - content.scripts = { ...content.scripts, ...scripts } - writePackageJson(generator, content) -} - -function appendStubVarsToDotenv (generator, label, vars) { - const content = `## ${label}${EOL}${vars.map(v => `#${v}=`).join(EOL)}${EOL}` - const file = generator.destinationPath(dotenvFilename) - - const prevContent = (generator.fs.exists(file) || '') && generator.fs.read(file) - if (prevContent.includes(label)) { - // if already there do nothing - return - } - generator.fs.append(file, content) -} - -module.exports = { - atLeastOne, - guessProjectName, - addDependencies, - addPkgScript, - readPackageJson, - writePackageJson, - readYAMLConfig, - writeYAMLConfig, - writeKeyAppConfig, - writeKeyYAMLConfig, - appendStubVarsToDotenv, - writeMultiLayerKeyInObject -} diff --git a/package.json b/package.json index 5f467118..1df45415 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "homepage": "https://github.com/adobe/generator-aio-app#readme", "devDependencies": { "@types/jest": "^25.1.0", - "codecov": "^3.6.5", "eol": "^0.9.1", "eslint": "^7.1.0", "eslint-config-standard": "^14.1.0", @@ -38,15 +37,23 @@ "eslint-plugin-node": "^11.0.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", - "jest": "^26.6.3", + "jest": "^27", "stdout-stderr": "^0.1.13", "yeoman-assert": "^3.1.1", - "yeoman-test": "^6.0.0", - "yeoman-environment": "^3.2.0" + "yeoman-environment": "^3.2.0", + "yeoman-test": "^6.0.0" }, "dependencies": { "@adobe/aio-lib-env": "^1.1.0", + "@adobe/generator-add-action-asset-compute": "^0.2.1", + "@adobe/generator-add-action-generic": "^0.2.2", + "@adobe/generator-add-web-assets-exc-raw-html": "^0.2.1", + "@adobe/generator-add-web-assets-exc-react": "^0.2.1", + "@adobe/generator-app-asset-compute": "^0.2.0", + "@adobe/generator-app-common-lib": "^0.3.0", + "@adobe/generator-app-excshell": "^0.2.0", "fs-extra": "^9.0.0", + "inquirer": "^8.2.0", "js-yaml": "^3.14.0", "lodash.clonedeep": "^4.5.0", "upath": "^1.2.0", diff --git a/test/generators/add-action/analytics.test.js b/test/generators/add-action/analytics.test.js index 79b70054..0916a370 100644 --- a/test/generators/add-action/analytics.test.js +++ b/test/generators/add-action/analytics.test.js @@ -21,7 +21,7 @@ const cloneDeep = require('lodash.clonedeep') const theGeneratorPath = require.resolve('../../../generators/add-action/analytics') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-action/asset-compute.test.js b/test/generators/add-action/asset-compute.test.js deleted file mode 100644 index 943ddef1..00000000 --- a/test/generators/add-action/asset-compute.test.js +++ /dev/null @@ -1,234 +0,0 @@ -/* eslint-disable jest/expect-expect */ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const helpers = require('yeoman-test') -const assert = require('yeoman-assert') -const fs = require('fs') -const yaml = require('js-yaml') -const path = require('path') -const { EOL } = require('os') -const cloneDeep = require('lodash.clonedeep') - -const theGeneratorPath = require.resolve('../../../generators/add-action/asset-compute') -const Generator = require('yeoman-generator') - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) - }) -}) - -function assertGeneratedFiles (actionName) { - const actionPath = `actions/${actionName}` - const testPath = `test/asset-compute/${actionName}` - - assert.file(`${actionPath}/index.js`) - - assert.file(`${testPath}/corrupt-input/file.jpg`) - assert.file(`${testPath}/corrupt-input/params.json`) - assert.file(`${testPath}/simple-test/file.jpg`) - assert.file(`${testPath}/simple-test/params.json`) - assert.file(`${testPath}/simple-test/rendition.jpg`) - - assert.file('ext.config.yaml') - assert.file('.env') - assert.file('package.json') -} - -// pkgName is optional -function assertManifestContent (actionName, pkgName) { - const json = yaml.safeLoad(fs.readFileSync('ext.config.yaml').toString()) - expect(json.runtimeManifest.packages).toBeDefined() - - // default packageName is path.basename(path.dirname('ext.config.yaml')) - pkgName = pkgName || path.basename(process.cwd()) - - expect(json.runtimeManifest.packages[pkgName].actions[actionName]).toEqual({ - function: `actions/${actionName}/index.js`, - web: 'yes', - runtime: 'nodejs:14', - limits: { - concurrency: 10 - }, - annotations: { - 'require-adobe-auth': true - } - }) -} - -function assertEnvContent (prevContent) { - // the generator does not write anything to .env - assert.fileContent('.env', prevContent) -} - -function assertActionCodeContent (actionName) { - const theFile = `actions/${actionName}/index.js` - - // a few checks to make sure the action uses the asset compute sdk - assert.fileContent( - theFile, - 'const { worker, SourceCorruptError } = require(\'@adobe/asset-compute-sdk\');' - ) - assert.fileContent( - theFile, - 'exports.main = worker(async (source, rendition) => {' - ) -} - -describe('run', () => { - test('asset-compute: --skip-prompt', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = true - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - const actionName = 'worker' // default value - - await helpers.run(theGeneratorPath) - .withOptions(options) - .inTmpDir(dir => { - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - }) - - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertEnvContent(prevDotEnvContent) - assertDependencies(fs, { '@adobe/asset-compute-sdk': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String), '@adobe/aio-cli-plugin-asset-compute': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('asset-compute: --skip-prompt, and action with default name already exists', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = true - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - - await helpers.run(theGeneratorPath) - .withOptions(options) - .inTmpDir(dir => { - fs.writeFileSync('ext.config.yaml', yaml.dump({ - runtimeManifest: { - packages: { - somepackagename: { - actions: { - worker: { function: 'fake.js' } - } - } - } - } - - })) - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - }) - - const actionName = 'worker-1' - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName, 'somepackagename') - assertEnvContent(prevDotEnvContent) - assertDependencies(fs, { '@adobe/asset-compute-sdk': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String), '@adobe/aio-cli-plugin-asset-compute': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('asset-compute: --skip-prompt, and action already has package.json with scripts', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = true - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - const actionName = 'worker' - - await helpers.run(theGeneratorPath) - .withOptions(options) - .inTmpDir(dir => { - fs.writeFileSync('package.json', JSON.stringify({ - scripts: {} - })) - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - }) - - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertEnvContent(prevDotEnvContent) - assertDependencies(fs, { '@adobe/asset-compute-sdk': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String), '@adobe/aio-cli-plugin-asset-compute': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('asset-compute: user input actionName=new-action', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = false - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - const actionName = 'new-asset-compute-action' - - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: 'new-asset-compute-action' }) - .inTmpDir(dir => { - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - }) - - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertEnvContent(prevDotEnvContent) - assertDependencies(fs, { '@adobe/asset-compute-sdk': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String), '@adobe/aio-cli-plugin-asset-compute': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('asset-compute: adding an action 2 times', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = false - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: 'new-asset-compute-action' }) - .inTmpDir(dir => { - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - }) - - const actionName2 = 'new-asset-compute-action-second-of-its-name' - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: actionName2 }) - }) - - test('asset-compute: verifying scripts are not overridden', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = false - const prevDotEnvContent = `PREVIOUSCONTENT${EOL}` - const dummyPackageJson = JSON.stringify({ - name: 'dummy-app', - version: '0.0.1', - dependencies: { - '@adobe/asset-compute-sdk': '^2.2.1' - }, - devDependencies: { - jest: '^24.9.0' - }, - scripts: { - test: 'nothing meaningful here', - debug: 'nothing meaningful here' - } - }) - - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: 'new-asset-compute-action' }) - .inTmpDir(dir => { - fs.writeFileSync(path.join(dir, '.env'), prevDotEnvContent) - fs.writeFileSync(path.join(dir, 'package.json'), dummyPackageJson) - }) - - const actionName2 = 'new-asset-compute-action-second-of-its-name' - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: actionName2 }) - }) -}) diff --git a/test/generators/add-action/audience-manager-cd.test.js b/test/generators/add-action/audience-manager-cd.test.js index 43f9c8b6..c0aa0d0c 100644 --- a/test/generators/add-action/audience-manager-cd.test.js +++ b/test/generators/add-action/audience-manager-cd.test.js @@ -19,7 +19,7 @@ const path = require('path') const theGeneratorPath = require.resolve('../../../generators/add-action/audience-manager-cd') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-action/campaign-standard.test.js b/test/generators/add-action/campaign-standard.test.js index d40baa0c..e9fc3366 100644 --- a/test/generators/add-action/campaign-standard.test.js +++ b/test/generators/add-action/campaign-standard.test.js @@ -21,7 +21,7 @@ const cloneDeep = require('lodash.clonedeep') const theGeneratorPath = require.resolve('../../../generators/add-action/campaign-standard') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-action/customer-profile.test.js b/test/generators/add-action/customer-profile.test.js index fbc12719..b36c53d4 100644 --- a/test/generators/add-action/customer-profile.test.js +++ b/test/generators/add-action/customer-profile.test.js @@ -21,7 +21,7 @@ const cloneDeep = require('lodash.clonedeep') const theGeneratorPath = require.resolve('../../../generators/add-action/customer-profile') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-action/generic.test.js b/test/generators/add-action/generic.test.js deleted file mode 100644 index 09d6fff1..00000000 --- a/test/generators/add-action/generic.test.js +++ /dev/null @@ -1,135 +0,0 @@ -/* eslint-disable jest/expect-expect */ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const helpers = require('yeoman-test') -const assert = require('yeoman-assert') -const fs = require('fs') -const yaml = require('js-yaml') -const cloneDeep = require('lodash.clonedeep') - -const theGeneratorPath = require.resolve('../../../generators/add-action/generic') -const Generator = require('yeoman-generator') - -const constants = require('../../../lib/constants') - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) - }) -}) - -function assertGeneratedFiles (actionName) { - assert.file(`${constants.actionsDirname}/${actionName}/index.js`) - - assert.file(`test/${actionName}.test.js`) - assert.file(`e2e/${actionName}.e2e.test.js`) - - assert.file(`${constants.actionsDirname}/utils.js`) - assert.file('test/utils.test.js') - - assert.file('ext.config.yaml') -} - -function assertManifestContent (actionName) { - const json = yaml.safeLoad(fs.readFileSync('ext.config.yaml').toString()) - expect(json.runtimeManifest.packages).toBeDefined() - // expect(json.packages[constants.manifestPackagePlaceholder].actions[actionName]).toEqual({ - // function: path.normalize(`${constants.actionsDirname}/${actionName}/index.js`), - // web: 'yes', - // runtime: 'nodejs:14', - // inputs: { - // LOG_LEVEL: 'debug' - // }, - // annotations: { - // final: true, - // 'require-adobe-auth': true - // } - // }) -} - -function assertActionCodeContent (actionName) { - const theFile = `${constants.actionsDirname}/${actionName}/index.js` - assert.fileContent( - theFile, - 'const { Core } = require(\'@adobe/aio-sdk\')' - ) - assert.fileContent( - theFile, - 'const res = await fetch(apiEndpoint)' - ) - assert.fileContent( - theFile, - 'const requiredHeaders = [\'Authorization\']' - ) -} - -describe('run', () => { - test('--skip-prompt', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = true - await helpers.run(theGeneratorPath) - .withOptions(options) - - // default - const actionName = 'generic' - - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertDependencies(fs, { '@adobe/aio-sdk': expect.any(String), 'node-fetch': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('--skip-prompt, and action with default name already exists', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = true - await helpers.run(theGeneratorPath) - .withOptions(options) - .inTmpDir(dir => { - fs.writeFileSync('ext.config.yaml', yaml.dump({ - runtimeManifest: { - packages: { - somepackage: { - actions: { - generic: { function: 'fake.js' } - } - } - } - } - })) - }) - - // default - const actionName = 'generic-1' - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertDependencies(fs, { '@adobe/aio-sdk': expect.any(String), 'node-fetch': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) - - test('user input actionName=fakeAction', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['skip-prompt'] = false - await helpers.run(theGeneratorPath) - .withOptions(options) - .withPrompts({ actionName: 'fakeAction' }) - - const actionName = 'fakeAction' - - assertGeneratedFiles(actionName) - assertActionCodeContent(actionName) - assertManifestContent(actionName) - assertDependencies(fs, { '@adobe/aio-sdk': expect.any(String), 'node-fetch': expect.any(String) }, { '@openwhisk/wskdebug': expect.any(String) }) - assertNodeEngines(fs, '^10 || ^12 || ^14') - }) -}) diff --git a/test/generators/add-action/index.test.js b/test/generators/add-action/index.test.js index e9a6dd4d..7f1a575a 100644 --- a/test/generators/add-action/index.test.js +++ b/test/generators/add-action/index.test.js @@ -10,12 +10,14 @@ governing permissions and limitations under the License. */ const helpers = require('yeoman-test') -const utils = require('../../../lib/utils') -const theGeneratorPath = require.resolve('../../../generators/add-action') +const { utils } = require('@adobe/generator-app-common-lib') +const AddActions = require('../../../generators/add-action') const Generator = require('yeoman-generator') -const { sdkCodes } = require('../../../lib/constants') -const path = require('path') +const { constants } = require('@adobe/generator-app-common-lib') +const { sdkCodes } = constants const cloneDeep = require('lodash.clonedeep') +const generic = require('@adobe/generator-add-action-generic') +const assetCompute = require('@adobe/generator-add-action-asset-compute') const expectedSeparator = expect.objectContaining({ type: 'separator', @@ -24,31 +26,31 @@ const expectedSeparator = expect.objectContaining({ const expectedChoices = { generic: { name: 'Generic', - value: expect.stringContaining(path.normalize('generic/index.js')) + value: generic }, [sdkCodes.analytics]: { name: 'Adobe Analytics', - value: expect.stringContaining(path.normalize('analytics/index.js')) + value: require('../../../generators/add-action/analytics') }, [sdkCodes.target]: { name: 'Adobe Target', - value: expect.stringContaining(path.normalize('target/index.js')) + value: require('../../../generators/add-action/target') }, [sdkCodes.campaign]: { name: 'Adobe Campaign Standard', - value: expect.stringContaining(path.normalize('campaign-standard/index.js')) + value: require('../../../generators/add-action/campaign-standard') }, [sdkCodes.assetCompute]: { name: 'Adobe Asset Compute Worker', - value: expect.stringContaining(path.normalize('asset-compute/index.js')) + value: assetCompute }, [sdkCodes.customerProfile]: { name: 'Adobe Experience Platform: Realtime Customer Profile', - value: expect.stringContaining(path.normalize('customer-profile/index.js')) + value: require('../../../generators/add-action/customer-profile') }, [sdkCodes.audienceManagerCD]: { name: 'Adobe Audience Manager: Customer Data', - value: expect.stringContaining(path.normalize('audience-manager-cd/index.js')) + value: require('../../../generators/add-action/audience-manager-cd') } } @@ -67,11 +69,9 @@ afterAll(() => { composeWith.mockRestore() }) -jest.mock('../../../lib/utils') - describe('prototype', () => { test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) + expect(AddActions.prototype).toBeInstanceOf(Generator) }) }) @@ -80,18 +80,18 @@ describe('run', () => { const options = cloneDeep(global.basicGeneratorOptions) options['skip-prompt'] = true options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.target},${sdkCodes.campaign},${sdkCodes.customerProfile}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) // with skip prompt defaults to generic action // make sure sub generators have been called expect(composeWith).toHaveBeenCalledTimes(1) - expect(composeWith).toHaveBeenCalledWith(expect.stringContaining(n('generic/index.js')), expect.objectContaining({ + expect(composeWith).toHaveBeenCalledWith({ Generator: generic, path: 'unknown' }, expect.objectContaining({ 'skip-prompt': true })) }) test('no input, selects one generator', async () => { - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withPrompts({ actionGenerators: ['a'] }) expect(prompt).toHaveBeenCalledTimes(1) @@ -113,12 +113,12 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(1) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('no input, selects multiple generators', async () => { - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) expect(prompt).toHaveBeenCalledTimes(1) @@ -140,15 +140,15 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="NOTEXISTING" --adobe-supported-services="notexistting" and selects multiple generators', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = 'NOTEXITING' options['--adobe-supported-services'] = 'notexistting' - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -171,14 +171,14 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="analytics,customerProfile"', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.customerProfile}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -202,15 +202,15 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="analytics,customerProfile", supported-adobe-services="analytics,assetCompute,customerProfile,target"', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.customerProfile}` options['supported-adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.target}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -235,16 +235,16 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="analytics,customerProfile", supported-adobe-services=ALL', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.customerProfile}` options['supported-adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -268,16 +268,16 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services=ALL, supported-adobe-services=ALL', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` options['supported-adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -300,15 +300,15 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services=ALL', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions({ 'adobe-services': `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` }) @@ -333,16 +333,16 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="", supported-adobe-services=analytics,assetCompute,customerProfile,target', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = '' options['supported-adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.target}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -367,16 +367,16 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) test('--adobe-services="", supported-adobe-services=ALL', async () => { const options = cloneDeep(global.basicGeneratorOptions) options['adobe-services'] = '' options['supported-adobe-services'] = `${sdkCodes.analytics},${sdkCodes.assetCompute},${sdkCodes.customerProfile},${sdkCodes.campaign},${sdkCodes.target},${sdkCodes.audienceManagerCD}` - await helpers.run(theGeneratorPath) + await helpers.run(AddActions) .withOptions(options) .withPrompts({ actionGenerators: ['a', 'b', 'c'] }) @@ -400,8 +400,8 @@ describe('run', () => { }) ]) expect(composeWith).toHaveBeenCalledTimes(3) - expect(composeWith).toHaveBeenCalledWith('a', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('b', expect.objectContaining({ 'skip-prompt': false })) - expect(composeWith).toHaveBeenCalledWith('c', expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'a', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'b', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) + expect(composeWith).toHaveBeenCalledWith({ Generator: 'c', path: 'unknown' }, expect.objectContaining({ 'skip-prompt': false })) }) }) diff --git a/test/generators/add-action/target.test.js b/test/generators/add-action/target.test.js index f16f99c0..096d85ea 100644 --- a/test/generators/add-action/target.test.js +++ b/test/generators/add-action/target.test.js @@ -21,7 +21,7 @@ const cloneDeep = require('lodash.clonedeep') const theGeneratorPath = require.resolve('../../../generators/add-action/target') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-events/index.test.js b/test/generators/add-events/index.test.js index a9f96c49..36ad64e7 100644 --- a/test/generators/add-events/index.test.js +++ b/test/generators/add-events/index.test.js @@ -31,7 +31,7 @@ afterAll(() => { const expectedDefaultEventsGenerator = expect.stringContaining(n('publish-events/index.js')) -jest.mock('../../../lib/utils') +jest.mock('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-events/publish-events.test.js b/test/generators/add-events/publish-events.test.js index 63ae719f..26b99b6a 100644 --- a/test/generators/add-events/publish-events.test.js +++ b/test/generators/add-events/publish-events.test.js @@ -20,7 +20,7 @@ const cloneDeep = require('lodash.clonedeep') const theGeneratorPath = require.resolve('../../../generators/add-events/publish-events') const Generator = require('yeoman-generator') -const constants = require('../../../lib/constants') +const { constants } = require('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/add-web-assets/exc-react.test.js b/test/generators/add-web-assets/exc-react.test.js deleted file mode 100644 index bbbcc3b3..00000000 --- a/test/generators/add-web-assets/exc-react.test.js +++ /dev/null @@ -1,163 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -/* eslint-disable jest/expect-expect */ // => use assert - -const helpers = require('yeoman-test') -const assert = require('yeoman-assert') -const fs = require('fs') -const path = require('path') -const cloneDeep = require('lodash.clonedeep') - -const theGeneratorPath = require.resolve( - '../../../generators/add-web-assets/exc-react' -) -const Generator = require('yeoman-generator') - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) - }) -}) - -function assertEnvContent (prevContent) { - assert.fileContent('.env', prevContent) -} - -function assertFiles () { - assert.file('web-src/index.html') - assert.file('web-src/src/exc-runtime.js') - assert.file('web-src/src/index.css') - assert.file('web-src/src/index.js') - assert.file('web-src/src/utils.js') - assert.file('web-src/src/components/About.js') - assert.file('web-src/src/components/ActionsForm.js') - assert.file('web-src/src/components/App.js') - assert.file('web-src/src/components/Home.js') - assert.file('web-src/src/components/SideBar.js') -} - -function assertWithActions () { - assert.fileContent( - 'web-src/src/components/ActionsForm.js', - 'Run your application backend actions' - ) - assert.fileContent('web-src/src/components/SideBar.js', 'Actions') -} - -function assertWithNoActions () { - assert.fileContent('web-src/src/components/ActionsForm.js', 'You have no actions !') -} - -function assertWithDoc () { - assert.fileContent( - 'web-src/src/components/About.js', - 'Useful documentation for your app' - ) - assert.fileContent('web-src/src/components/About.js', 'App Builder') - assert.fileContent('web-src/src/components/About.js', 'Adobe I/O SDK') - assert.fileContent('web-src/src/components/About.js', 'React Spectrum') -} - -const prevDotEnv = 'FAKECONTENT' - -describe('run', () => { - test('--project-name abc', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['project-name'] = 'abc' - options['web-src-folder'] = 'web-src' - await helpers - .run(theGeneratorPath) - .withOptions(options) - .inTmpDir((dir) => { - fs.writeFileSync(path.join(dir, '.env'), prevDotEnv) - }) - - assertFiles() - assertDependencies( - fs, - { - react: expect.any(String), - 'react-dom': expect.any(String), - 'react-error-boundary': expect.any(String), - 'core-js': expect.any(String), - 'regenerator-runtime': expect.any(String), - '@adobe/exc-app': expect.any(String), - '@adobe/react-spectrum': expect.any(String), - '@spectrum-icons/workflow': expect.any(String), - 'react-router-dom': expect.any(String) - }, - { - '@babel/core': expect.any(String), - '@babel/polyfill': expect.any(String), - '@babel/preset-env': expect.any(String), - '@babel/plugin-transform-react-jsx': expect.any(String) - } - ) - assertEnvContent(prevDotEnv) - - // greats with projectName - // TODO fix check failing content mismatch, possible bug - // assert.fileContent('web-src/src/components/Home.js', 'Welcome to abc!') - - // make sure html calls js files - assert.fileContent('web-src/index.html', '') - }) - - test('--project-name abc --adobe-services analytics', async () => { - const options = cloneDeep(global.basicGeneratorOptions) - options['project-name'] = 'abc' - options['web-src-folder'] = 'web-src' - options['adobe-services'] = `${sdkCodes.analytics}` - await helpers.run(theGeneratorPath) - .withOptions(options) - - // added files - assert.file('web-src/index.html') - assert.file('web-src/404.html') - assert.file('web-src/src/index.js') - assert.file('web-src/src/exc-runtime.js') - - // greats with projectName - assert.fileContent('web-src/index.html', '

Welcome to abc!

') - - // make sure calls index.js to get the list of actions - assert.fileContent('web-src/index.html', '') - }) -}) diff --git a/test/generators/application/index.test.js b/test/generators/application/index.test.js index 936b6391..73330e97 100644 --- a/test/generators/application/index.test.js +++ b/test/generators/application/index.test.js @@ -28,7 +28,13 @@ afterAll(() => { composeWith.mockRestore() }) -jest.mock('../../../lib/utils') +jest.mock('@adobe/generator-app-common-lib', () => ({ + utils: { + guessProjectName: jest.fn(), + writeKeyAppConfig: jest.fn() + }, + constants: {} +})) describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/delete-ci/index.test.js b/test/generators/delete-ci/index.test.js index f573566f..0bad797b 100644 --- a/test/generators/delete-ci/index.test.js +++ b/test/generators/delete-ci/index.test.js @@ -17,7 +17,7 @@ const assert = require('yeoman-assert') const theGeneratorPath = require.resolve('../../../generators/delete-ci') const Generator = require('yeoman-generator') -jest.mock('../../../lib/utils') +jest.mock('@adobe/generator-app-common-lib') describe('prototype', () => { test('exports a yeoman generator', () => { diff --git a/test/generators/extensions/dx-asset-compute-worker-1/index.test.js b/test/generators/extensions/dx-asset-compute-worker-1/index.test.js deleted file mode 100644 index c757ad81..00000000 --- a/test/generators/extensions/dx-asset-compute-worker-1/index.test.js +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2021 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -/* eslint-disable jest/expect-expect */ // => use assert - -const helpers = require('yeoman-test') -const fs = require('fs') -const path = require('path') -const assert = require('yeoman-assert') -const yaml = require('js-yaml') - -const theGeneratorPath = require.resolve('../../../../generators/extensions/dx-asset-compute-worker-1/') -const Generator = require('yeoman-generator') - -const composeWith = jest.spyOn(Generator.prototype, 'composeWith') -beforeAll(() => { - // mock implementations - composeWith.mockReturnValue(undefined) -}) -beforeEach(() => { - composeWith.mockClear() -}) -afterAll(() => { - composeWith.mockRestore() -}) - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) - }) -}) - -function assertScripts () { - const config = yaml.load(fs.readFileSync('src/dx-asset-compute-worker-1/ext.config.yaml').toString()) - assert.strictEqual(config.hooks.test, 'adobe-asset-compute test-worker') - assert.strictEqual(config.hooks['post-app-run'], 'adobe-asset-compute devtool') -} - -describe('run', () => { - test('test basic ext generator', async () => { - const options = { 'skip-prompt': true } - await helpers.run(theGeneratorPath) - .withOptions(options) - .inTmpDir(dir => { - fs.writeFileSync(path.join(dir, '.env'), 'FAKECONTENT') - }) - expect(composeWith).toHaveBeenCalledTimes(1) - expect(composeWith).toHaveBeenCalledWith(expect.stringContaining(path.normalize('add-action/asset-compute/index.js')), expect.any(Object)) - assertScripts() - }) -}) diff --git a/test/generators/extensions/dx-excshell-1/index.test.js b/test/generators/extensions/dx-excshell-1/index.test.js deleted file mode 100644 index 89bb3292..00000000 --- a/test/generators/extensions/dx-excshell-1/index.test.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2021 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -/* eslint-disable jest/expect-expect */ // => use assert - -const helpers = require('yeoman-test') -const path = require('path') - -const theGeneratorPath = require.resolve('../../../../generators/extensions/dx-excshell-1/') -const Generator = require('yeoman-generator') - -const composeWith = jest.spyOn(Generator.prototype, 'composeWith') -beforeAll(() => { - // mock implementations - composeWith.mockReturnValue(undefined) -}) -beforeEach(() => { - composeWith.mockClear() -}) -afterAll(() => { - composeWith.mockRestore() -}) - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(require(theGeneratorPath).prototype).toBeInstanceOf(Generator) - }) -}) - -describe('run', () => { - test('test basic ext generator', async () => { - const options = { 'skip-prompt': true } - await helpers.run(theGeneratorPath) - .withOptions(options) - expect(composeWith).toHaveBeenCalledTimes(2) - expect(composeWith).toHaveBeenCalledWith(expect.stringContaining(path.normalize('add-action/generic/index.js')), expect.any(Object)) - expect(composeWith).toHaveBeenCalledWith(expect.stringContaining(path.normalize('add-web-assets/exc-react/index.js')), expect.any(Object)) - }) -}) diff --git a/test/lib/ActionGenerator.test.js b/test/lib/ActionGenerator.test.js deleted file mode 100644 index b11a0628..00000000 --- a/test/lib/ActionGenerator.test.js +++ /dev/null @@ -1,349 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ -const path = require('path') -const cloneDeep = require('lodash.clonedeep') - -const constants = require('../../lib/constants') - -jest.mock('yeoman-generator') - -const ActionGenerator = require('../../lib/ActionGenerator') -const Generator = require('yeoman-generator') - -jest.mock('../../lib/utils.js') -const utils = require('../../lib/utils.js') - -const generatorOptions = cloneDeep(global.basicGeneratorOptions) - -describe('prototype', () => { - test('exports a yeoman generator', () => { - expect(ActionGenerator.prototype).toBeInstanceOf(Generator) - }) -}) - -beforeEach(() => { - utils.readYAMLConfig.mockRestore() - utils.readPackageJson.mockRestore() - utils.writeYAMLConfig.mockRestore() - utils.writePackageJson.mockRestore() - utils.appendStubVarsToDotenv.mockRestore() - utils.addDependencies.mockRestore() - utils.writeKeyYAMLConfig.mockRestore() -}) - -describe('implementation', () => { - beforeEach(() => { - ActionGenerator.prototype.templatePath = p => path.join('/fakeTplDir', p) - ActionGenerator.prototype.destinationPath = (...args) => path.join('/fakeDestRoot', ...args) - Generator.prototype.options = generatorOptions - }) - describe('constructor', () => { - test('accept options', () => { - const spy = jest.spyOn(ActionGenerator.prototype, 'option') - // eslint-disable-next-line no-new - new ActionGenerator() - expect(spy).toHaveBeenCalledWith('skip-prompt', { default: false }) - expect(spy).toHaveBeenCalledWith('action-folder', { type: String }) - expect(spy).toHaveBeenCalledWith('config-path', { type: String }) - expect(spy).toHaveBeenCalledWith('full-key-to-manifest', { type: String, default: '' }) - spy.mockRestore() - }) - }) - describe('promptForActionName', () => { - let promptSpy - let actionGenerator - beforeEach(() => { - promptSpy = jest.spyOn(ActionGenerator.prototype, 'prompt') - actionGenerator = new ActionGenerator() - actionGenerator.options = { 'skip-prompt': false } - }) - afterEach(() => { - promptSpy.mockRestore() - }) - test('calls prompt and returns the input actionName', async () => { - promptSpy.mockResolvedValue({ - actionName: 'inputName' - }) - const actionName = await actionGenerator.promptForActionName('fake purpose', 'fake default') - expect(actionName).toEqual('inputName') - expect(promptSpy).toHaveBeenCalledWith([expect.objectContaining({ - when: true, - default: 'fake default', - message: expect.stringContaining('fake purpose') - })]) - expect(promptSpy).toHaveBeenCalledTimes(1) - }) - test('if options.skip-prompt is set should return the default value', async () => { - promptSpy.mockResolvedValue({ - actionName: undefined - }) - actionGenerator.options['skip-prompt'] = true - const actionName = await actionGenerator.promptForActionName('fake purpose', 'fake default') - expect(actionName).toEqual('fake default') - expect(promptSpy).toHaveBeenCalledTimes(0) - }) - test('validates input `abc-1234, 1234-abc, ABC-1234, 1234-ABC`', async () => { - promptSpy.mockReturnValue({ actionName: 'fake' }) - await actionGenerator.promptForActionName() - expect(promptSpy.mock.calls[0][0][0].validate).toBeInstanceOf(Function) - const validate = promptSpy.mock.calls[0][0][0].validate - expect(validate('abc-1234')).toBe(true) - expect(validate('1234-abc')).toBe(true) - expect(validate('ABC-1234')).toBe(true) - expect(validate('1234-ABC')).toBe(true) - }) - test('rejects inputs `a, 1, ab, 12, -abc-1234, abc@, abc_1234, 1234-abc!, abc123456789012345678901234567890`', async () => { - promptSpy.mockReturnValue({ actionName: 'fake' }) - await actionGenerator.promptForActionName() - expect(promptSpy.mock.calls[0][0][0].validate).toBeInstanceOf(Function) - const validate = promptSpy.mock.calls[0][0][0].validate - expect(validate('a')).not.toEqual(true) - expect(validate('1')).not.toEqual(true) - expect(validate('ab')).not.toEqual(true) - expect(validate('12')).not.toEqual(true) - expect(validate('-abc-1234')).not.toEqual(true) - expect(validate('abc_1234')).not.toEqual(true) - expect(validate('1234-abc!')).not.toEqual(true) - expect(validate('abc@')).not.toEqual(true) - expect(validate('abc123456789012345678901234567890')).not.toEqual(true) - }) - - test('rejects invalid name with a message`', async () => { - const invalidName = 'a' - const invalidNameMessage = `'${invalidName}' is not a valid action name, please make sure that: -The name has at least 3 characters or less than 33 characters. -The first character is an alphanumeric character. -The subsequent characters are alphanumeric. -The last character isn't a space. -Note: characters can only be split by '-'. -` - promptSpy.mockReturnValue({ actionName: 'fake' }) - await actionGenerator.promptForActionName() - expect(promptSpy.mock.calls[0][0][0].validate).toBeInstanceOf(Function) - const validate = promptSpy.mock.calls[0][0][0].validate - expect(validate(invalidName)).toEqual(invalidNameMessage) - }) - - test('returns new default name in case of conflict', async () => { - utils.readYAMLConfig.mockReturnValue({ - runtimeManifest: { - license: 'Apache-2.0', - packages: { - mypackage: { - actions: { - defaulttest: { function: 'fake.js' }, - 'defaulttest-1': { function: 'fake.js' } - } - } - } - } - }) - actionGenerator.options = { 'skip-prompt': true } - - const actionName = await actionGenerator.promptForActionName('fakepurpose', 'defaulttest') - expect(actionName).toEqual('defaulttest-2') - }) - }) - - describe('addAction', () => { - let actionGenerator - beforeEach(() => { - actionGenerator = new ActionGenerator() - actionGenerator.options = { 'skip-prompt': false } - actionGenerator.fs = { copyTpl: jest.fn() } - - // mock path resolvers - actionGenerator.templatePath = p => path.join('/fakeTplDir', p) - actionGenerator.destinationPath = (...args) => args[0].startsWith(n('/fakeDestRoot')) ? path.join(...args) : path.join('/fakeDestRoot', ...args) - }) - - test('with no options and manifest does not exist', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js') - - // 1. test copy action template to right destination - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/templateFile.js'), n(`${constants.actionsDirname}/myAction/index.js`), {}, {}, {}) - // 2. test manifest creation with action information - expect(utils.writeKeyYAMLConfig).toHaveBeenCalledWith( - actionGenerator, - n('/fakeDestRoot/ext.config.yaml'), - 'runtimeManifest', - // function path should be checked to be relative to config file - { packages: { fakeDestRoot: { actions: { myAction: { annotations: { 'require-adobe-auth': true }, function: expect.stringContaining('myAction/index.js'), runtime: 'nodejs:14', web: 'yes' } }, license: 'Apache-2.0' } } }) - - // 3. make sure wskdebug dev dependency was added to package.json - expect(utils.addDependencies).toHaveBeenCalledWith(actionGenerator, { '@openwhisk/wskdebug': expect.any(String) }, true) - }) - - test('with extra dependencies and manifest already exists', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({ - runtimeManifest: { - packages: { - somepackage: { - actions: { - actionxyz: { function: 'fake.js' } - - } - } - } - } - }) - - actionGenerator.addAction('myAction', './templateFile.js', { dependencies: { abc: '1.2.3', def: '4.5.6' }, devDependencies: { xyz: '3.2.1', vuw: '6.5.4' } }) - - // 1. test copy action template to right destination - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/templateFile.js'), n(`${constants.actionsDirname}/myAction/index.js`), {}, {}, {}) - // 2. test manifest creation with action information, and preserving previous content - expect(utils.writeKeyYAMLConfig).toHaveBeenCalledWith( - actionGenerator, - n('/fakeDestRoot/ext.config.yaml'), - 'runtimeManifest', - // function path should be checked to be relative to config file - { packages: { somepackage: { actions: { actionxyz: { function: 'fake.js' }, myAction: { annotations: { 'require-adobe-auth': true }, function: expect.stringContaining('myAction/index.js'), runtime: 'nodejs:14', web: 'yes' } } } } }) - // 3. make sure wskdebug dev dependency was added to package.json - // prod - expect(utils.addDependencies).toHaveBeenCalledWith(actionGenerator, { - abc: '1.2.3', def: '4.5.6' - }) - // dev - expect(utils.addDependencies).toHaveBeenCalledWith(actionGenerator, { - xyz: '3.2.1', - vuw: '6.5.4', - '@openwhisk/wskdebug': expect.any(String) - }, true) - }) - - test('with tplContext and no manifest', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { tplContext: { fake: 'context', with: { fake: 'values' } } }) - - // 1. test copy action template to right destination with template options - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/templateFile.js'), n(`${constants.actionsDirname}/myAction/index.js`), { fake: 'context', with: { fake: 'values' } }, {}, {}) - }) - - test('with tplContext option and actionDestPath already set and no maniifest', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { tplContext: { actionDestPath: `${path.sep}fakeDestRoot${path.sep}${constants.actionsDirname}${path.sep}myAction${path.sep}index.js`, fake: 'context', with: { fake: 'values' } } }) - - // 1. test copy action template to predefined destination - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/templateFile.js'), n(`${constants.actionsDirname}${path.sep}myAction${path.sep}index.js`), { actionDestPath: `${path.sep}fakeDestRoot${path.sep}${constants.actionsDirname}${path.sep}myAction${path.sep}index.js`, fake: 'context', with: { fake: 'values' } }, {}, {}) - }) - - test('with extra actionManifestConfig and no manifest', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { actionManifestConfig: { runtime: 'fake:42', inputs: { fake: 'value' } } }) - // test manifest creation with action information - expect(utils.writeKeyYAMLConfig).toHaveBeenCalledWith( - actionGenerator, - n('/fakeDestRoot/ext.config.yaml'), - 'runtimeManifest', - // function path should be checked to be relative to config file - { packages: { fakeDestRoot: { actions: { myAction: { runtime: 'fake:42', inputs: { fake: 'value' }, annotations: { 'require-adobe-auth': true }, function: expect.stringContaining('myAction/index.js'), web: 'yes' } }, license: 'Apache-2.0' } } }) - }) - - test('with dotenvStub option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { dotenvStub: { label: 'fake label', vars: ['FAKE', 'FAKE2'] } }) - expect(utils.appendStubVarsToDotenv).toHaveBeenCalledWith(actionGenerator, 'fake label', ['FAKE', 'FAKE2']) - }) - - test('with testFile option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { testFile: './template.test.js' }) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledTimes(2) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/template.test.js'), n('/fakeDestRoot/test/myAction.test.js'), { actionRelPath: expect.stringContaining('myAction/index.js') }, {}, {}) - }) - - test('with testFile option and tplContext option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { testFile: './template.test.js', tplContext: { fake: 'context', with: { fake: 'values' } } }) - - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledTimes(2) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/template.test.js'), n('/fakeDestRoot/test/myAction.test.js'), { actionRelPath: expect.stringContaining('myAction/index.js'), fake: 'context', with: { fake: 'values' } }, {}, {}) - }) - - test('with e2eTestFile option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { e2eTestFile: './templatee2e.test.js' }) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledTimes(2) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/templatee2e.test.js'), n('/fakeDestRoot/e2e/myAction.e2e.test.js'), { runtimePackageName: 'fakeDestRoot' }, {}, {}) - }) - - test('with sharedLibFile option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { sharedLibFile: './utils.js' }) - - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/utils.js'), n(`/fakeDestRoot/${constants.actionsDirname}/utils.js`), {}) - }) - - test('with sharedLibTestFile option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { sharedLibTestFile: './utils.test.js' }) - - expect(actionGenerator.fs.copyTpl).not.toHaveBeenCalledWith(n('/fakeTplDir/utils.test.js'), n(`/fakeDestRoot/test/${constants.actionsDirname}/utils.test.js`), {}) - }) - - test('with sharedLibFile and sharedLibTestFile option', () => { - // mock files - utils.readPackageJson.mockReturnValue({}) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js', { sharedLibFile: './utils.js', sharedLibTestFile: './utils.test.js' }) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalled() - - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/utils.js'), n(`/fakeDestRoot/${constants.actionsDirname}/utils.js`), {}) - expect(actionGenerator.fs.copyTpl).toHaveBeenCalledWith(n('/fakeTplDir/utils.test.js'), n('/fakeDestRoot/test/utils.test.js'), { utilsRelPath: expect.stringContaining('../actions/utils.js') }, {}, {}) - }) - test('with existing package.json node engines', () => { - // mock files - utils.readPackageJson.mockReturnValue({ engines: { node: '1 || 2' } }) - utils.readYAMLConfig.mockReturnValue({}) - - actionGenerator.addAction('myAction', './templateFile.js') - - expect(utils.writePackageJson).not.toHaveBeenCalledWith(actionGenerator, expect.objectContaining({ - engines: { node: '^10 || ^12 || ^14' } - })) - }) - }) -}) diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js deleted file mode 100644 index 650f1042..00000000 --- a/test/lib/utils.test.js +++ /dev/null @@ -1,309 +0,0 @@ -const utils = require('../../lib/utils') -const eol = require('eol') - -describe('atLeastOne', () => { - test('returns true if input.length > 0', () => { - expect(utils.atLeastOne([1])).toBe(true) - }) - test('returns "please choose at least one option" if input.length === 0', () => { - expect(utils.atLeastOne([])).toBe('please choose at least one option') - }) -}) - -describe('guessProjectName', () => { - test('returns cwd if package.json does not exist', () => { - const spy = jest.spyOn(process, 'cwd') - spy.mockReturnValue('FAKECWD') - expect(utils.guessProjectName({ - destinationPath: () => { }, - fs: { - exists: () => false - } - })).toEqual('FAKECWD') - spy.mockRestore() - }) - - test('returns cwd if package.json[name] is not defined', () => { - const spy = jest.spyOn(process, 'cwd') - spy.mockReturnValue('FAKECWD') - expect(utils.guessProjectName({ - destinationPath: () => { }, - fs: { - exists: () => true, - readJSON: () => ({}) - } - })).toEqual('FAKECWD') - spy.mockRestore() - }) - - test('returns package.json[name] if package.json exists and has a name attribut', () => { - expect(utils.guessProjectName({ - destinationPath: () => { }, - fs: { - exists: () => true, - readJSON: () => ({ name: 'FAKENAME' }) - } - })).toEqual('FAKENAME') - }) -}) - -describe('addPkgScript', () => { - test('adds scripts to package.json', () => { - const mockRead = jest.fn(() => { - return ({ name: 'bob', scripts: { scripta: 'a' } }) - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: () => 'some-path', - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addPkgScript(generator, { scriptb: 'b' }) - - expect(mockRead).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', expect.objectContaining({ name: 'bob', scripts: { scripta: 'a', scriptb: 'b' } })) - }) - - test('overwrites existing scripts package.json', () => { - const mockRead = jest.fn(() => { - return ({ name: 'bob', scripts: { scripta: 'a' } }) - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: () => 'some-path', - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addPkgScript(generator, { scripta: 'b' }) - - expect(mockRead).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', expect.objectContaining({ name: 'bob', scripts: { scripta: 'b' } })) - }) - - test('writes package.json if null', () => { - const mockRead = jest.fn() - const mockWrite = jest.fn() - const generator = { - destinationPath: () => 'some-path', - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addPkgScript(generator, { scripta: 'b' }) - - expect(mockRead).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', expect.objectContaining({ scripts: { scripta: 'b' } })) - }) -}) - -describe('readPackageJson', () => { - test('if package.json is empty', () => { - const mockRead = jest.fn(() => { - return '' - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead - } - } - expect(utils.readPackageJson(generator)).toEqual({}) - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - }) - - test('if package.json is { a: key, scripts: { b: c } }', () => { - const mockRead = jest.fn(() => { - return { a: 'key', scripts: { b: 'c' } } - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead - } - } - expect(utils.readPackageJson(generator)).toEqual({ a: 'key', scripts: { b: 'c' } }) - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - }) -}) - -describe('writePackageJson', () => { - test('if content is empty', () => { - const mockWrite = jest.fn(() => { - return '' - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - writeJSON: mockWrite - } - } - utils.writePackageJson(generator, '') - expect(mockWrite).toHaveBeenCalledWith('some-path', {}) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - }) - - test('if content is { a: key, scripts: { b: c } }', () => { - const mockWrite = jest.fn(() => { - return '' - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - writeJSON: mockWrite - } - } - utils.writePackageJson(generator, { a: 'key', scripts: { b: 'c' } }) - expect(mockWrite).toHaveBeenCalledWith('some-path', { a: 'key', scripts: { b: 'c' } }) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - }) -}) - -describe('addDependencies', () => { - test('adds dependencies to package.json with no existing dependencies', () => { - const mockRead = jest.fn(() => { - return undefined - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addDependencies(generator, { a: 'b', c: 'd' }) - - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(mockRead).toHaveBeenCalledTimes(1) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', { dependencies: { a: 'b', c: 'd' } }) - }) - test('adds devDependencies to package.json with no existing devDependencies', () => { - const mockRead = jest.fn(() => { - return undefined - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addDependencies(generator, { a: 'b', c: 'd' }, true) - - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(mockRead).toHaveBeenCalledTimes(1) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', { devDependencies: { a: 'b', c: 'd' } }) - }) - test('adds and overwrites dependencies in package.json', () => { - const mockRead = jest.fn(() => { - return { dependencies: { a: 'fake', e: 'f' }, devDependencies: { g: 'h' } } - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addDependencies(generator, { a: 'b', c: 'd' }) - - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(mockRead).toHaveBeenCalledTimes(1) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', { dependencies: { a: 'b', c: 'd', e: 'f' }, devDependencies: { g: 'h' } }) - }) - - test('adds and overwrites devDependencies in package.json', () => { - const mockRead = jest.fn(() => { - return { devDependencies: { a: 'fake', e: 'f' }, dependencies: { g: 'h' } } - }) - const mockWrite = jest.fn() - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - readJSON: mockRead, - writeJSON: mockWrite - } - } - utils.addDependencies(generator, { a: 'b', c: 'd' }, true) - - expect(mockRead).toHaveBeenCalledWith('some-path') - expect(mockRead).toHaveBeenCalledTimes(1) - expect(generator.destinationPath).toHaveBeenCalledWith('package.json') - expect(mockWrite).toHaveBeenCalledTimes(1) - expect(mockWrite).toHaveBeenCalledWith('some-path', { devDependencies: { a: 'b', c: 'd', e: 'f' }, dependencies: { g: 'h' } }) - }) - - test('appendStubVarsToDotenv existing label', () => { - const mockRead = jest.fn(() => '# label') - const mockExists = jest.fn(() => { - return true - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - read: mockRead, - append: jest.fn(), - exists: mockExists - } - } - - utils.appendStubVarsToDotenv(generator, 'label', ['a', 'b', 'c']) - expect(generator.fs.append).not.toHaveBeenCalled() - }) - - test('appendStubVarsToDotenv', () => { - const mockRead = jest.fn(() => '') - const mockExists = jest.fn(() => { - return false - }) - const generator = { - destinationPath: jest.fn(() => 'some-path'), - fs: { - read: mockRead, - append: jest.fn(), - exists: mockExists - } - } - - utils.appendStubVarsToDotenv(generator, 'fake', ['a', 'b', 'c']) - expect(generator.fs.append).toHaveBeenCalledWith('some-path', eol.auto(`## fake -#a= -#b= -#c= -`)) - }) - - test('writeMultiLayerKeyInObject', () => { - const obj = { - test: 'somevalue', - a: { - b: { - c: [{ - test1: 'fakevalue', - test2: 'fakevalue' - }] - } - } - } - utils.writeMultiLayerKeyInObject(obj, 'a.b.c', [{ test: 'new value' }]) - expect(obj.a.b.c[1]).toEqual({ test: 'new value' }) - }) -})