Skip to content

Commit

Permalink
feat: enable npm 7 support and fix for reusing already installed temp…
Browse files Browse the repository at this point in the history
…late (#517)
  • Loading branch information
derberg authored Mar 3, 2021
1 parent 2eb2340 commit 2f64f1b
Show file tree
Hide file tree
Showing 20 changed files with 18,733 additions and 1,817 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
output
node_modules
.github/templates-list-validator/dist
test/temp
30 changes: 30 additions & 0 deletions .github/workflows/pr-testing-with-test-project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#This workflow runs the tests in the test projects to make sure the generator works as a library where it is a Node dependency along with the template.
name: Test using test project

on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]

jobs:
test:
if: github.event.pull_request.draft == false
name: Test generator as dependency with Node ${{ matrix.node }}
runs-on: macos-latest
strategy:
matrix:
node: [ '12', '14', '15' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: Install generator
run: npm install
- name: Install dependencies in test project
run: npm install
working-directory: ./test/test-project
- name: Run test
run: npm test
working-directory: ./test/test-project
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ output
*.orig
out/
coverage
test/temp/integrationTestResult
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ You can find above templates and the ones provided by the community in **[this l

## Requirements

* v12.16+ < Node.js
* v6.13.7+ < npm
* Node.js v12.16 and higher
* npm v6.13.7 and higher

Install both packages using [official installer](https://nodejs.org/en/download/). After installation make sure both packages have proper version by running `node -v` and `npm -v`. To upgrade invalid npm version run `npm install npm@latest -g`

Expand Down
4 changes: 0 additions & 4 deletions lib/__mocks__/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ utils.isFileSystemPath = jest.fn(() => utils.__isFileSystemPathValue);
utils.__isLocalTemplateValue = false;
utils.isLocalTemplate = jest.fn(async () => utils.__isLocalTemplateValue);

utils.getLocalTemplateDetails = jest.fn(async () => ({
resolvedLink: utils.__getLocalTemplateDetailsResolvedLinkValue || '',
}));

utils.__generatorVersion = '';
utils.getGeneratorVersion = jest.fn(() => utils.__generatorVersion);

Expand Down
14 changes: 10 additions & 4 deletions lib/filtersRegistry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const path = require('path');
const fs = require('fs');
const xfs = require('fs.extra');
const { isLocalTemplate } = require('./utils');

/**
* Registers all template filters.
Expand Down Expand Up @@ -69,9 +68,16 @@ async function registerConfigFilters(nunjucks, templateDir, templateConfig) {
const DEFAULT_MODULES_DIR = 'node_modules';
if (!Array.isArray(confFilters)) return;

const promises = confFilters.map(async el => {
const modulePath = await isLocalTemplate(templateDir) ? path.resolve(templateDir, DEFAULT_MODULES_DIR, el) : el;
const mod = require(modulePath);
const promises = confFilters.map(async filtersModule => {
let mod;
try {
//first we try to grab module with filters by the module name
//this is when generation is used on production using remote templates
mod = require(path.resolve(DEFAULT_MODULES_DIR, filtersModule));
} catch (error) {
//in case template is local but was not installed in node_modules of the generator then we need to explicitly provide modules location
mod = require(path.resolve(templateDir, DEFAULT_MODULES_DIR, filtersModule));
}

addFilters(nunjucks, mod);
});
Expand Down
91 changes: 50 additions & 41 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ const avroSchemaParser = require('@asyncapi/avro-schema-parser');
const jmespath = require('jmespath');
const filenamify = require('filenamify');
const git = require('simple-git/promise');
const npmi = require('npmi');
const log = require('loglevel');
const Arborist = require('@npmcli/arborist');
const resolvePkg = require('resolve-pkg');
const { validateTemplateConfig } = require('./templateConfigValidator');
const {
convertMapToObject,
isFileSystemPath,
beautifyNpmiResult,
isLocalTemplate,
getLocalTemplateDetails,
readFile,
readDir,
writeFile,
Expand Down Expand Up @@ -340,56 +338,67 @@ class Generator {
installTemplate(force = false) {
return new Promise(async (resolve, reject) => {
if (!force) {
try {
let installedPkg;
let pkgPath;
let installedPkg;
let packageVersion;

try {
if (isFileSystemPath(this.templateName)) {
const pkg = require(path.resolve(this.templateName, PACKAGE_JSON_FILENAME));
installedPkg = require(path.resolve(DEFAULT_TEMPLATES_DIR, pkg.name, PACKAGE_JSON_FILENAME));
} else { // Template is not a filesystem path...
const templatePath = path.resolve(DEFAULT_TEMPLATES_DIR, this.templateName);
if (await isLocalTemplate(templatePath)) {
// This "if" is covering the following workflow:
// ag asyncapi.yaml ../html-template
// The previous command installs a template called @asyncapi/html-template
// And now we run the command again but with the resolved name:
// ag asyncapi.yaml @asyncapi/html-template
// The template name doesn't look like a file system path but we find
// that the package is already installed and it's a symbolic link.
const { resolvedLink } = await getLocalTemplateDetails(templatePath);
log.debug(`This template has already been installed and it's pointing to your filesystem at ${resolvedLink}.`);
}
installedPkg = require(path.resolve(templatePath, PACKAGE_JSON_FILENAME));
pkgPath = path.resolve(this.templateName);
installedPkg = require(path.resolve(pkgPath, PACKAGE_JSON_FILENAME));

log.debug('Remember that your local template must have its own node_modules, "npm install" is not triggered.');
} else {
pkgPath = resolvePkg(this.templateName);
installedPkg = require(path.join(pkgPath, PACKAGE_JSON_FILENAME));
}
packageVersion = installedPkg.version;
log.debug(`Template sources taken from ${pkgPath}.`);
if (packageVersion) log.debug(`Version of the template is ${packageVersion}.`);

return resolve({
name: installedPkg.name,
version: installedPkg.version,
path: path.resolve(DEFAULT_TEMPLATES_DIR, installedPkg.name),
path: pkgPath
});
} catch (e) {
log.debug(`Errors while trying to resolve package location at ${pkgPath}`, e);
// We did our best. Proceed with installation...
}
}

// NOTE: npmi creates symbolic links inside DEFAULT_TEMPLATES_DIR
// (node_modules) for local packages, i.e., those located in the file system.
npmi({
name: this.templateName,
install: force,
path: ROOT_DIR,
pkgName: 'dummy value so it does not force installation always',
npmLoad: {
loglevel: 'http',
save: false,
audit: false,
progress: false,
only: 'prod'
},
}, (err, result) => {
if (err) return reject(err);
resolve(beautifyNpmiResult(result));
const debugMessage = force ? 'because you passed --install flag' : 'because the template cannot be found on disk';
log.debug(`Template installation started ${ debugMessage}`);

if (isFileSystemPath(this.templateName)) log.debug('Installation of template located on disk technically means symlink creation betweed node_modules of the generator and template sources. Your local template must have its own node_modules, "npm install" is not triggered.');

const arb = new Arborist({
path: ROOT_DIR
});

try {
const installResult = await arb.reify({
add: [this.templateName],
saveType: 'prod',
save: false
});

const addResult = arb[Symbol.for('resolvedAdd')];
if (!addResult) return reject('Unable to resolve the name of the added package. It was most probably not added to node_modules successfully');

const packageName = addResult[0].name;
const packageVersion = installResult.children.get(packageName).version;
const packagePath = installResult.children.get(packageName).path;

if (!isFileSystemPath(this.templateName)) log.debug(`Template ${packageName} successfully installed in ${packagePath}`);
if (packageVersion) log.debug(`Version of used template is ${packageVersion}.`);

return resolve({
name: packageName,
path: packagePath,
});
} catch (err) {
reject(err);
}
});
}

Expand Down
13 changes: 10 additions & 3 deletions lib/hooksRegistry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
const xfs = require('fs.extra');
const { isLocalTemplate, exists } = require('./utils');
const { exists } = require('./utils');

/**
* Registers all template hooks.
Expand Down Expand Up @@ -73,8 +73,15 @@ async function registerConfigHooks(hooks, templateDir, templateConfig) {
if (typeof configHooks !== 'object' || !Object.keys(configHooks).length > 0) return;

const promises = Object.keys(configHooks).map(async hooksModuleName => {
const modulePath = await isLocalTemplate(templateDir) ? path.resolve(templateDir, DEFAULT_MODULES_DIR, hooksModuleName) : hooksModuleName;
const mod = require(modulePath);
let mod;
try {
//first we try to grab module with hooks by the module name
//this is when generation is used on production using remote templates
mod = require(path.resolve(DEFAULT_MODULES_DIR, hooksModuleName));
} catch (error) {
//in case template is local but was not installed in node_modules of the generator then we need to explicitly provide modules location
mod = require(path.resolve(templateDir, DEFAULT_MODULES_DIR, hooksModuleName));
}
const configHooksArray = [].concat(configHooks[hooksModuleName]);

addHook(hooks, mod, configHooksArray);
Expand Down
33 changes: 0 additions & 33 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,6 @@ utils.isFileSystemPath = (string) => {
|| string.startsWith('~');
};

/**
* Takes the result of calling the npmi module and returns it in a more consumable form.
* @private
* @param {Array} result The result of calling npmi.
* @returns {Object}
*/
utils.beautifyNpmiResult = (result) => {
const [nameWithVersion, pkgPath] = result[result.length-1];
const nameWithVersionArray = nameWithVersion.split('@');
const version = nameWithVersionArray[nameWithVersionArray.length - 1];
const name = nameWithVersionArray.splice(0, nameWithVersionArray.length - 1).join('@');

return {
name,
path: pkgPath,
version,
};
};

/**
* Converts a Map into an object.
* @private
Expand Down Expand Up @@ -82,20 +63,6 @@ utils.isReactTemplate = (templateConfig) => {
return templateConfig !== undefined && templateConfig.renderer === 'react';
};

/**
* Gets the details (link and resolved link) of a local template.
* @private
* @param {String} templatePath The path to the template.
* @returns {Promise<Object>}
*/
utils.getLocalTemplateDetails = async (templatePath) => {
const linkTarget = await utils.readlink(templatePath);
return {
link: linkTarget,
resolvedLink: path.resolve(path.dirname(templatePath), linkTarget),
};
};

/**
* Fetches an AsyncAPI document from the given URL and return its content as string
*
Expand Down
Loading

0 comments on commit 2f64f1b

Please sign in to comment.