build(docs-infra): simplify update workflow for CLI-based docs examples boilerplate (#38992)

When updating the boilerplate for CLI-based docs examples, one needed to
install dependencies inside the
`aio/tools/examples/shared/boilerplate/cli/` directory, which resulted
in a `node_modules/` directory and a `yarn.lock` file. These were not
supposed to be part of the boilerplate, so they had to be manually
removed after the boilerplate was updated.

This commit simplifies the workflow by allowing boilerplate files to be
ignored (both by git and the `example-boilerplate.js` script) via a
`.gitignore` file. This way, it is no longer necessary to manually
remove the unneeded directories/files.

PR Close #38992
This commit is contained in:
George Kalpakas 2020-09-27 13:37:43 +03:00 committed by Alex Rickabaugh
parent 37ed4bd9ff
commit f6052a915d
4 changed files with 120 additions and 69 deletions

View File

@ -32,14 +32,11 @@ Any necessary changes to boilerplate files will be done automatically through mi
# Migrate project to new versions.
yarn ng update @angular/cli --migrate-only --from=<previous-cli-version>
yarn ng update @angular/core --migrate-only --from=<previous-core-version>
# Remove `node_modules/` and `yarn.lock`.
rm -rf node_modules yarn.lock
```
> NOTE:
> In order for `ng update` to work, there must be a `node_modules/` directory with installed dependencies inside the [shared/boilerplate/cli/](./shared/boilerplate/cli) directory.
> This `node_modules/` directory is only needed during the update operation and must be subsequently removed to avoid its being copied into all CLI-based examples.
> This `node_modules/` directory is only needed during the update operation and is otherwise ignored (both by git and by the [example-boilerplate.js](./example-boilerplate.js) script) by means of the [shared/boilerplate/.gitignore](./shared/boilerplate/.gitignore) file.
- The previous command made any necessary changes to boilerplate files inside the `cli/` folder, but the same changes need to be applied to the other CLI-based boilerplate folders.
Inspect the changes in `cli/` and manually apply the necessary ones to other CLI-based boilerplate folders.

View File

@ -1,5 +1,6 @@
const fs = require('fs-extra');
const glob = require('glob');
const ignore = require('ignore');
const path = require('canonical-path');
const shelljs = require('shelljs');
const yargs = require('yargs');
@ -23,6 +24,8 @@ class ExampleBoilerPlate {
// Get all the examples folders, indicated by those that contain a `example-config.json` file
const exampleFolders =
this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
const gitignore = ignore().add(fs.readFileSync(path.resolve(BOILERPLATE_BASE_PATH, '.gitignore'), 'utf8'));
const isPathIgnored = absolutePath => gitignore.ignores(path.relative(BOILERPLATE_BASE_PATH, absolutePath));
if (!fs.existsSync(SHARED_NODE_MODULES_PATH)) {
throw new Error(
@ -48,22 +51,22 @@ class ExampleBoilerPlate {
// boilerplate files first.
// (Some of these files might be later overwritten by type-specific files.)
if (boilerPlateType !== 'cli' && boilerPlateType !== 'systemjs') {
this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder);
this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder, isPathIgnored);
}
// Copy the type-specific boilerplate files.
this.copyDirectoryContents(boilerPlateBasePath, exampleFolder);
this.copyDirectoryContents(boilerPlateBasePath, exampleFolder, isPathIgnored);
// Copy the common boilerplate files (unless explicitly not used).
if (exampleConfig.useCommonBoilerplate !== false) {
this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder);
this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder, isPathIgnored);
}
// Copy ViewEngine (pre-Ivy) specific files
if (viewengine) {
const veBoilerPlateType = boilerPlateType === 'systemjs' ? 'systemjs' : 'cli';
const veBoilerPlateBasePath = path.resolve(BOILERPLATE_VIEWENGINE_PATH, veBoilerPlateType);
this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder);
this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder, isPathIgnored);
}
});
}
@ -89,16 +92,22 @@ class ExampleBoilerPlate {
loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; }
copyDirectoryContents(srcDir, dstDir) {
copyDirectoryContents(srcDir, dstDir, isPathIgnored) {
shelljs.ls('-Al', srcDir).forEach(stat => {
const srcPath = path.resolve(srcDir, stat.name);
const dstPath = path.resolve(dstDir, stat.name);
if (isPathIgnored(srcPath)) {
// `srcPath` is ignored (e.g. by a `.gitignore` file): Ignore it.
return;
}
if (stat.isDirectory()) {
// `srcPath` is a directory: Recursively copy it to `dstDir`.
shelljs.mkdir('-p', dstPath);
return this.copyDirectoryContents(srcPath, dstPath);
} else {
return this.copyDirectoryContents(srcPath, dstPath, isPathIgnored);
}
// `srcPath` is a file: Copy it to `dstDir`.
// (Also make the file non-writable to avoid accidental editing of boilerplate files).
if (shelljs.test('-f', dstPath)) {
@ -107,7 +116,6 @@ class ExampleBoilerPlate {
}
shelljs.cp(srcPath, dstDir);
shelljs.chmod(444, dstPath);
}
});
}
}

View File

@ -56,10 +56,10 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/systemjs`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/systemjs`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/systemjs`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/systemjs`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
]);
});
@ -71,10 +71,10 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
]);
});
@ -86,10 +86,10 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
]);
});
@ -101,12 +101,12 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/i18n`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/i18n`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/i18n`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/i18n`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
]);
});
@ -118,12 +118,12 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/universal`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/universal`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/universal`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/universal`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
]);
});
@ -148,12 +148,12 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/systemjs`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/systemjs`, 'a/b'],
[`${boilerplateDir}/systemjs`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/systemjs`, 'c/d'],
[`${boilerplateDir}/systemjs`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/systemjs`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/systemjs`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/systemjs`, 'c/d', jasmine.any(Function)],
]);
});
@ -165,12 +165,12 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/cli`, 'c/d', jasmine.any(Function)],
]);
});
@ -182,14 +182,14 @@ describe('example-boilerplate tool', () => {
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(8);
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
[`${boilerplateDir}/cli`, 'a/b'],
[`${boilerplateDir}/elements`, 'a/b'],
[`${boilerplateDir}/common`, 'a/b'],
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
[`${boilerplateDir}/cli`, 'c/d'],
[`${boilerplateDir}/elements`, 'c/d'],
[`${boilerplateDir}/common`, 'c/d'],
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/elements`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/cli`, 'a/b', jasmine.any(Function)],
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/elements`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
[`${boilerplateDir}/viewengine/cli`, 'c/d', jasmine.any(Function)],
]);
});
});
@ -214,10 +214,12 @@ describe('example-boilerplate tool', () => {
describe('copyDirectoryContents', () => {
const spyFnFor = fnName => (...args) => { callLog.push(`${fnName}(${args.join(', ')})`); };
let isPathIgnoredSpy;
let callLog;
beforeEach(() => {
callLog = [];
isPathIgnoredSpy = jasmine.createSpy('isPathIgnored').and.returnValue(false);
spyOn(shelljs, 'chmod').and.callFake(spyFnFor('chmod'));
spyOn(shelljs, 'cp').and.callFake(spyFnFor('cp'));
spyOn(shelljs, 'mkdir').and.callFake(spyFnFor('mkdir'));
@ -226,17 +228,17 @@ describe('example-boilerplate tool', () => {
it('should list all contents of a directory', () => {
const lsSpy = spyOn(shelljs, 'ls').and.returnValue([]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(lsSpy).toHaveBeenCalledWith('-Al', 'source/dir');
});
it('should use copy files and make them read-only', () => {
it('should copy files and make them read-only', () => {
spyOn(shelljs, 'ls').and.returnValue([
{name: 'file-1.txt', isDirectory: () => false},
{name: 'file-2.txt', isDirectory: () => false},
]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(callLog).toEqual([
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
@ -249,6 +251,22 @@ describe('example-boilerplate tool', () => {
]);
});
it('should skip files that are ignored', () => {
spyOn(shelljs, 'ls').and.returnValue([
{name: 'file-1.txt', isDirectory: () => false},
{name: 'file-2.txt', isDirectory: () => false},
]);
isPathIgnoredSpy.and.callFake(path => path.endsWith('file-1.txt'));
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(callLog).toEqual([
`test(-f, ${path.resolve('destination/dir/file-2.txt')})`,
`cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
]);
});
it('should make existing files in destination writable before overwriting', () => {
spyOn(shelljs, 'ls').and.returnValue([
{name: 'new-file.txt', isDirectory: () => false},
@ -256,7 +274,7 @@ describe('example-boilerplate tool', () => {
]);
shelljs.test.and.callFake((_, filePath) => filePath.endsWith('existing-file.txt'));
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(callLog).toEqual([
`cp(${path.resolve('source/dir/new-file.txt')}, destination/dir)`,
@ -283,7 +301,7 @@ describe('example-boilerplate tool', () => {
{name: 'file-4.txt', isDirectory: () => false},
]);
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(callLog).toEqual([
// Copy `file-1.txt`.
@ -317,6 +335,33 @@ describe('example-boilerplate tool', () => {
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
]);
});
it('should skip ignored directories', () => {
spyOn(shelljs, 'ls')
.withArgs('-Al', 'source/dir').and.returnValue([
{name: 'file-1.txt', isDirectory: () => false},
{name: 'sub-dir-1', isDirectory: () => true},
])
.withArgs('-Al', path.resolve('source/dir/sub-dir-1')).and.returnValue([
{name: 'file-2.txt', isDirectory: () => false},
{name: 'sub-dir-2', isDirectory: () => true},
])
.withArgs('-Al', path.resolve('source/dir/sub-dir-1/sub-dir-2')).and.returnValue([
{name: 'file-3.txt', isDirectory: () => false},
]);
isPathIgnoredSpy.and.callFake(path => path.endsWith('sub-dir-1'));
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
expect(callLog).toEqual([
// Copy `file-1.txt`.
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
`cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`,
`chmod(444, ${path.resolve('destination/dir/file-1.txt')})`,
// Skip `sub-dir-1` and all its contents.
]);
});
});
describe('loadJsonFile', () => {

View File

@ -1,2 +1,3 @@
**/yarn.lock
**/node_modules/
**/package-lock.json
**/yarn.lock