diff --git a/aio/tools/examples/UPDATING.md b/aio/tools/examples/UPDATING.md index 2298d9f678..773d59f230 100644 --- a/aio/tools/examples/UPDATING.md +++ b/aio/tools/examples/UPDATING.md @@ -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= yarn ng update @angular/core --migrate-only --from= - - # 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. diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index bc1c939656..a77ac0dd88 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/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'); @@ -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,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); }); } } diff --git a/aio/tools/examples/example-boilerplate.spec.js b/aio/tools/examples/example-boilerplate.spec.js index cff911a1c3..f716f5c434 100644 --- a/aio/tools/examples/example-boilerplate.spec.js +++ b/aio/tools/examples/example-boilerplate.spec.js @@ -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', () => { diff --git a/aio/tools/examples/shared/boilerplate/.gitignore b/aio/tools/examples/shared/boilerplate/.gitignore index ceed8f2aed..908cc87463 100644 --- a/aio/tools/examples/shared/boilerplate/.gitignore +++ b/aio/tools/examples/shared/boilerplate/.gitignore @@ -1,2 +1,3 @@ +**/node_modules/ +**/package-lock.json **/yarn.lock -**/package-lock.json \ No newline at end of file