Skip to content

Commit

Permalink
build(docs-infra): simplify update workflow for CLI-based docs exampl…
Browse files Browse the repository at this point in the history
…es boilerplate

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.
  • Loading branch information
gkalpak committed Sep 23, 2020
1 parent 4c9853a commit 128bbb9
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 69 deletions.
5 changes: 1 addition & 4 deletions aio/tools/examples/UPDATING.md
Expand Up @@ -31,14 +31,11 @@ Any necessary changes to boilerplate files will be done automatically through mi

# Update Angular dependencies.
yarn ng update @angular/cli@<version> @angular/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 folder.
Inspect the changes in `cli/` and manually apply the necessary ones to other CLI-based boilerplate folders.
Expand Down
38 changes: 23 additions & 15 deletions aio/tools/examples/example-boilerplate.js
@@ -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');
Expand All @@ -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(
Expand All @@ -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);
}
});
}
Expand All @@ -89,25 +92,30 @@ 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 {
// `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)) {
// If the file already exists, ensure it is writable (so it can be overwritten).
shelljs.chmod(666, dstPath);
}
shelljs.cp(srcPath, dstDir);
shelljs.chmod(444, dstPath);
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)) {
// If the file already exists, ensure it is writable (so it can be overwritten).
shelljs.chmod(666, dstPath);
}
shelljs.cp(srcPath, dstDir);
shelljs.chmod(444, dstPath);
});
}
}
Expand Down
143 changes: 94 additions & 49 deletions aio/tools/examples/example-boilerplate.spec.js
Expand Up @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});

Expand All @@ -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)],
]);
});
});
Expand All @@ -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'));
Expand All @@ -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')})`,
Expand All @@ -249,14 +251,30 @@ 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},
{name: 'existing-file.txt', isDirectory: () => false},
]);
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)`,
Expand All @@ -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`.
Expand Down Expand Up @@ -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', () => {
Expand Down
3 changes: 2 additions & 1 deletion aio/tools/examples/shared/boilerplate/.gitignore
@@ -1,2 +1,3 @@
**/node_modules/
**/package-lock.json
**/yarn.lock
**/package-lock.json

0 comments on commit 128bbb9

Please sign in to comment.