Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: ACNA-1650 - Template Registry Api integration #569

Merged
merged 52 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
397751b
add Template Registry API lib
shazron Jun 10, 2022
8e5d4eb
add test of Template Registry API: filter by supported apis (apis sup…
shazron Jun 10, 2022
9c477f7
update: template download and list in table (multi-select), then inst…
shazron Jun 22, 2022
fff6595
update require.resolve usage
shazron Jun 22, 2022
76de71f
remove hard-coded extension point list
shazron Jun 22, 2022
b4c883d
add standalone-app flag support
shazron Jun 26, 2022
c13c9b7
separated out installTemplates
shazron Jun 26, 2022
9eff758
use `next` tag versions of aio-lib-web and generator-aio-app (revert …
shazron Jun 27, 2022
2cd3e11
fix: env.run was not awaited (package.json was not written properly b…
shazron Jun 27, 2022
0558174
verify that the template plugin is installed
shazron Jun 27, 2022
979471f
use new prompt with better radio-button
shazron Jun 27, 2022
e05664d
fix for @adobe/aio-cli-plugin-app-templates@1.0.0-beta.3
shazron Jul 15, 2022
9d33bf9
fix: pass --yes flag if available to the template plugin
shazron Jul 18, 2022
9149f87
fix: DEVX-2367: no login use case support
shazron Jul 26, 2022
2f6fadd
update @adobe/aio-lib-templates to v2
shazron Aug 12, 2022
b5a720b
Merge branch 'master' into story/ACNA-1650
shazron Aug 25, 2022
ed323eb
fix: add missing @adobe/aio-lib-templates
shazron Aug 26, 2022
ee4d001
fix: update for new extensions key in install.yml
shazron Aug 26, 2022
8fd3429
fix: no word wrap on boundary for categories, add hyperlink for foote…
shazron Aug 26, 2022
51af318
fix: add filter for searching templates 1) All Templates 2) All Exten…
shazron Aug 26, 2022
7ae0129
Merge branch 'master' into story/ACNA-1650
shazron Sep 15, 2022
de855c8
fix: filter out templates with `helper-template` category
shazron Sep 15, 2022
f1b4d24
chore(package): update @adobe/aio-lib-templates to v2.1.0
shazron Sep 15, 2022
aceb28b
fix: extract helper functions for templates use
shazron Sep 15, 2022
5f85a0a
fix: app add extension to use Template Registry
shazron Sep 15, 2022
3e0d36a
fix: update app init to use template helper
shazron Sep 15, 2022
c1dcbe2
Merge branch 'master' into story/ACNA-1650
shazron Sep 16, 2022
c33ee64
fix: filter for any installed extensions
shazron Sep 19, 2022
c2f4946
add dynamic sizing of columns based on terminal size
shazron Sep 19, 2022
a995a3b
if no extensions were installed, output an error
shazron Sep 19, 2022
e9c058a
move lib/templates-helper to TemplatesCommand
shazron Sep 21, 2022
c95a939
finalized template implementation
shazron Sep 21, 2022
c86c46d
add filter and notice for --no-login
shazron Sep 21, 2022
c739255
downgrade term-size to 2.x so we don't have to deal with ESM (will co…
shazron Sep 21, 2022
eb54372
fix: app delete extension
shazron Sep 22, 2022
f6208ae
re-add --extension flag for app add extension
shazron Sep 22, 2022
ad0826c
update code coverage for BaseCommand test
shazron Sep 22, 2022
853c029
93% coverage, 6 failures
shazron Sep 22, 2022
88a455e
test coverage updates
shazron Sep 22, 2022
0556baf
Merge branch 'master' into story/ACNA-1650
shazron Sep 22, 2022
782125d
100% coverage
shazron Sep 22, 2022
7ea9725
remove unused eslint config options
shazron Sep 23, 2022
9f2be2e
add TemplatesCommand.getTemplates test
shazron Sep 23, 2022
49a4ee5
update eslint config to support nullish coaelescing
shazron Sep 23, 2022
bf92d4e
completed src/TemplatesCommand tests with full coverage
shazron Sep 23, 2022
9f76ab9
moved installExtensionsByName from app/add/extension to src/Templates…
shazron Sep 24, 2022
0ede0c3
Merge branch 'master' into story/ACNA-1650
shazron Sep 24, 2022
c7ca610
Merge branch 'master' into story/ACNA-1650
shazron Sep 24, 2022
e14bb57
some refactoring
shazron Sep 25, 2022
4b606bc
Merge branch 'master' into story/ACNA-1650
purplecabbage Sep 26, 2022
a4111bf
Merge branch 'master' into story/ACNA-1650
shazron Sep 27, 2022
800b36c
fix: update the inquirer plugin to @adobe/inquirer-table-checkbox
shazron Sep 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"jsdoc": {
"ignorePrivate": true
}
},
"parserOptions": {
"ecmaVersion": "latest"
}
}
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module.exports = {
],
collectCoverageFrom: [
'src/commands/**/*.js',
'src/lib/*.js'
'src/lib/*.js',
'src/*.js'
],
coverageThreshold: {
global: {
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
"@adobe/aio-lib-env": "^2.0.0",
"@adobe/aio-lib-ims": "^6.0.0",
"@adobe/aio-lib-runtime": "^5.0.0",
"@adobe/aio-lib-templates": "^2.1.0",
"@adobe/aio-lib-web": "^6.0.1",
"@adobe/generator-aio-app": "^4.0.0",
"@adobe/generator-app-common-lib": "^0.3.3",
"@adobe/inquirer-table-checkbox": "^1.0.1",
"@oclif/core": "^1.15.0",
"@parcel/core": "^2.7.0",
"@parcel/reporter-cli": "^2.7.0",
Expand All @@ -38,6 +40,7 @@
"ora": "^5",
"pure-http": "^3",
"serve-static": "^1.14.1",
"term-size": "^2.2.1",
"upath": "^2",
"which": "^2.0.1",
"yeoman-environment": "^3.2.0"
Expand All @@ -60,6 +63,7 @@
"eslint-plugin-promise": "^6.0.0",
"jest": "^28",
"jest-plugin-fs": "^2.9.0",
"nock": "^13.2.9",
"oclif": "^3.2.0",
"stdout-stderr": "^0.1.9"
},
Expand Down
8 changes: 1 addition & 7 deletions src/AddCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const { installPackages } = require('./lib/app-helper')

class AddCommand extends BaseCommand {
async runInstallPackages (flags, spinner) {
const doInstall = flags.install && !flags['skip-install']
if (doInstall) {
if (flags.install) {
await installPackages('.', { spinner, verbose: flags.verbose })
} else {
this.log('skipped installation, make sure to run \'npm install\' later on')
Expand All @@ -25,11 +24,6 @@ class AddCommand extends BaseCommand {
}

AddCommand.flags = {
'skip-install': Flags.boolean({
description: '[deprecated] Please use --no-install',
char: 's',
default: false
}),
install: Flags.boolean({
description: '[default: true] Run npm installation after files are created',
default: true,
Expand Down
246 changes: 246 additions & 0 deletions src/TemplatesCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
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 AddCommand = require('./AddCommand')
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:TemplatesCommand', { provider: 'debug' })
const inquirerTableCheckbox = require('@adobe/inquirer-table-checkbox')
const inquirer = require('inquirer')
const TemplateRegistryAPI = require('@adobe/aio-lib-templates')
const hyperlinker = require('hyperlinker')
const ora = require('ora')
const terminalSize = require('term-size')

class TemplatesCommand extends AddCommand {
/**
* Gets a list of templates from the Template Registry API using the criteria provided.
*
* @param {object} searchCriteria the Template Registry API search criteria
* @param {object} orderByCriteria the Template Registry API orderBy criteria
* @param {object} [templateRegistryConfig={}] the optional Template Registry API config
* @returns {Array<object>} list of templates
*/
async getTemplates (searchCriteria, orderByCriteria, templateRegistryConfig = {}) {
const templateRegistryClient = TemplateRegistryAPI.init(templateRegistryConfig)
const templateList = []

const templatesIterator = templateRegistryClient.getTemplates(searchCriteria, orderByCriteria)

for await (const templates of templatesIterator) {
for (const template of templates) {
templateList.push(template)
}
}
aioLogger.debug('template list', JSON.stringify(templateList, null, 2))

return templateList
}

/**
* Select templates from the Template Registry API, via a cli table.
*
* @param {object} searchCriteria the Template Registry API search criteria
* @param {object} orderByCriteria the Template Registry API orderBy criteria
* @param {object} [templateRegistryConfig={}] the optional Template Registry API config
* @returns {Array<string>} an array of selected template module name(s)
*/
async selectTemplates (searchCriteria, orderByCriteria, templateRegistryConfig = {}) {
aioLogger.debug('searchCriteria', JSON.stringify(searchCriteria, null, 2))
aioLogger.debug('orderByCriteria', JSON.stringify(orderByCriteria, null, 2))

const spinner = ora()
spinner.start('Getting available templates')

const templateList = await this.getTemplates(searchCriteria, orderByCriteria, templateRegistryConfig)
aioLogger.debug('templateList', JSON.stringify(templateList, null, 2))
spinner.succeed('Downloaded the list of templates')

if (templateList.length === 0) {
throw new Error('There are no templates that match the query for selection')
}

const { columns: terminalColumns } = terminalSize()

const colPadding = 3
const colWidths = [
Math.round(0.3 * terminalColumns) - colPadding,
Math.round(0.3 * terminalColumns) - colPadding,
Math.round(0.2 * terminalColumns) - colPadding,
Math.round(0.2 * terminalColumns) - colPadding]

const COLUMNS = {
COL_TEMPLATE: 'Template',
COL_DESCRIPTION: 'Description',
COL_EXTENSION_POINT: 'Extension Point',
COL_CATEGORIES: 'Categories'
}

const rows = templateList.map(template => {
const extensionPoint = template.extensions ? template.extensions.map(ext => ext.extensionPointId).join(',') : 'N/A'
const name = template.adobeRecommended ? `${template.name} *` : template.name
return {
value: template.name,
[COLUMNS.COL_TEMPLATE]: name,
[COLUMNS.COL_DESCRIPTION]: template.description,
[COLUMNS.COL_EXTENSION_POINT]: extensionPoint,
[COLUMNS.COL_CATEGORIES]: template?.categories?.join(', ')
}
})
const promptName = 'select template'

inquirer.registerPrompt('table', inquirerTableCheckbox)
const answers = await inquirer
.prompt([
{
type: 'table',
name: promptName,
bottomContent: `* = recommended by Adobe; to learn more about the templates, go to ${hyperlinker('https://adobe.ly/templates', 'https://adobe.ly/templates')}`,
message: 'Choose the template(s) to install:',
style: { head: [], border: [] },
wordWrap: true,
wrapOnWordBoundary: false,
colWidths,
columns: [
{ name: COLUMNS.COL_TEMPLATE },
{ name: COLUMNS.COL_DESCRIPTION, wrapOnWordBoundary: true },
{ name: COLUMNS.COL_EXTENSION_POINT },
{ name: COLUMNS.COL_CATEGORIES, wrapOnWordBoundary: false }
],
rows
}
])

return answers[promptName]
}

/**
* Install the templates.
*
* @param {object} templateData the template data
* @param {boolean} [templateData.useDefaultValues=false] use default values when installing the template
* @param {boolean} [templateData.installConfig=true] process the install.yml of the template
* @param {boolean} [templateData.installNpm=true] run npm install after installing the template
* @param {object} [templateData.templateOptions=null] set the template options for installation
* @param {Array} templateData.templates the list of templates to install
*/
async installTemplates ({
useDefaultValues = false,
installConfig = true,
installNpm = true,
templateOptions = null,
templates = []
} = {}) {
const spinner = ora()

// install the templates in sequence
for (const template of templates) {
spinner.info(`Installing template ${template}`)
const installArgs = [template]
if (useDefaultValues) {
installArgs.push('--yes')
}
if (!installConfig) {
installArgs.push('--no-process-install-config')
}
if (!installNpm) {
installArgs.push('--no-install')
}

if (templateOptions) {
if (typeof templateOptions !== 'object' || Array.isArray(templateOptions)) { // must be a non-array object
aioLogger.debug('malformed templateOptions', templateOptions)
throw new Error('The templateOptions is not a JavaScript object.')
}
const jsonString = JSON.stringify(templateOptions)
installArgs.push(`--template-options=${Buffer.from(jsonString).toString('base64')}`)
}

await this.config.runCommand('templates:install', installArgs)
spinner.succeed(`Installed template ${template}`)
}
}

/** @private */
_uniqueArray (array) {
return Array.from(new Set(array))
}

/**
* Get templates by extension point ids.
*
* @param {Array<string>} extensionsToInstall an array of extension point ids to install.
* @param {object} [templateRegistryConfig={}] the optional Template Registry API config
* @returns {object} returns the result
*/
async getTemplatesByExtensionPointIds (extensionsToInstall, templateRegistryConfig = {}) {
const orderByCriteria = {
[TemplateRegistryAPI.ORDER_BY_CRITERIA_PUBLISH_DATE]: TemplateRegistryAPI.ORDER_BY_CRITERIA_SORT_DESC
}

const searchCriteria = {
[TemplateRegistryAPI.SEARCH_CRITERIA_STATUSES]: TemplateRegistryAPI.TEMPLATE_STATUS_APPROVED,
[TemplateRegistryAPI.SEARCH_CRITERIA_EXTENSIONS]: extensionsToInstall
}

const templates = await this.getTemplates(searchCriteria, orderByCriteria, templateRegistryConfig)
aioLogger.debug('templateList', JSON.stringify(templates, null, 2))

// check whether we got all extensions
const found = this._uniqueArray(templates
.map(t => t.extensions.map(e => e.extensionPointId)) // array of array of extensionPointIds
.filter(ids => extensionsToInstall.some(item => ids.includes(item)))
.flat()
)

const notFound = this._uniqueArray(extensionsToInstall).filter(ext => !found.includes(ext))

return {
found,
notFound,
templates
}
}

/**
* Install templates by extension point ids.
*
* @param {Array<string>} extensionsToInstall an array of extension point ids to install.
* @param {Array<string>} extensionsAlreadyImplemented an array of extension point ids that have already been implemented (to filter)
* @param {boolean} [useDefaultValues=false] use default values when installing the template
* @param {boolean} [installNpm=true] run npm install after installing the template
* @param {object} [templateRegistryConfig={}] the optional Template Registry API config
*/
async installTemplatesByExtensionPointIds (extensionsToInstall, extensionsAlreadyImplemented, useDefaultValues = false, installNpm = true, templateRegistryConfig = {}) {
// no prompt
const alreadyThere = extensionsToInstall.filter(i => extensionsAlreadyImplemented.includes(i))
if (alreadyThere.length > 0) {
throw new Error(`'${alreadyThere.join(', ')}' extension(s) are already implemented in this project.`)
}

const { found, notFound, templates } = await this.getTemplatesByExtensionPointIds(extensionsToInstall, templateRegistryConfig)

if (notFound.length > 0) {
this.error(`Extension(s) '${notFound.join(', ')}' not found in the Template Registry.`)
}

this.log(`Extension(s) '${found.join(', ')}' found in the Template Registry. Installing...`)
await this.installTemplates({
useDefaultValues,
installNpm,
templates: templates.map(t => t.name)
})
}
}

TemplatesCommand.flags = {
...AddCommand.flags
}

module.exports = TemplatesCommand
Loading