mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor: convert AIO tooling scripts used in Bazel to ESM (#48521)
Since the Bazel setup in this repo will now always use ESM, the tooling scripts/binaries in AIO need to be switched to ESM too. Most of the scripts are already ESM, but a few had to be converted. Note that the Dgeni generation does not use ESM because it's unaffected and the Dgeni CLI is used. In the future we could also update the Dgeni setup to ESM but there is no need currently. PR Close #48521
This commit is contained in:
@@ -190,13 +190,15 @@ def docs_example(name, test = True, test_tags = [], test_exec_properties = {}, f
|
||||
nodejs_test(
|
||||
name = "e2e",
|
||||
data = [
|
||||
":%s" % name,
|
||||
YARN_LABEL,
|
||||
"@aio_npm//@angular/build-tooling/bazel/browsers/chromium",
|
||||
"//aio/tools/examples:run-example-e2e",
|
||||
"//aio/tools:windows-chromium-path",
|
||||
# We install the whole node modules for runtime deps of e2e tests
|
||||
"@{workspace}//:node_modules_files".format(workspace = EXAMPLE_DEPS_WORKSPACE_NAME),
|
||||
],
|
||||
data_for_expansion = [
|
||||
":%s" % name,
|
||||
YARN_LABEL,
|
||||
] + select({
|
||||
"//aio:aio_local_deps": LOCAL_PACKAGE_DEPS,
|
||||
"//conditions:default": [],
|
||||
|
||||
@@ -22,7 +22,7 @@ nodejs_binary(
|
||||
js_library(
|
||||
name = "fast-serve-and-watch",
|
||||
srcs = [
|
||||
"fast-serve-and-watch.js",
|
||||
"fast-serve-and-watch.mjs",
|
||||
],
|
||||
deps = [
|
||||
"//aio/tools/transforms/authors-package:watchdocs",
|
||||
@@ -84,9 +84,9 @@ nodejs_test(
|
||||
testonly = True,
|
||||
data = [
|
||||
":audit-web-app",
|
||||
":audit-web-app-script",
|
||||
"@aio_npm//shelljs",
|
||||
],
|
||||
data_for_expansion = [":audit-web-app-script"],
|
||||
entry_point = "test-aio-a11y.mjs",
|
||||
env = {
|
||||
"AUDIT_SCRIPT_PATH": "$(rootpath :audit-web-app-script)",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Imports
|
||||
const {readFileSync, writeFileSync} = require('fs');
|
||||
const {join, resolve} = require('path');
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import {join, resolve} from 'path';
|
||||
|
||||
// Constants
|
||||
const SOURCE_404_BODY_PATH = resolve(process.argv[2]);
|
||||
@@ -18,13 +18,16 @@ function _main() {
|
||||
|
||||
const srcIndexContent = readFileSync(srcIndexPath, 'utf8');
|
||||
const src404BodyContent = readFileSync(SOURCE_404_BODY_PATH, 'utf8').trim();
|
||||
const dst404PageContent = srcIndexContent
|
||||
.replace(/(<body>)[\s\S]+(<\/body>)/, `$1\n ${src404BodyContent}\n$2`);
|
||||
const dst404PageContent = srcIndexContent.replace(
|
||||
/(<body>)[\s\S]+(<\/body>)/,
|
||||
`$1\n ${src404BodyContent}\n$2`
|
||||
);
|
||||
|
||||
if (dst404PageContent === srcIndexContent) {
|
||||
throw new Error(
|
||||
'Failed to generate \'404.html\'. ' +
|
||||
'The content of \'index.html\' does not match the expected pattern.');
|
||||
"Failed to generate '404.html'. " +
|
||||
"The content of 'index.html' does not match the expected pattern."
|
||||
);
|
||||
}
|
||||
|
||||
writeFileSync(DEST_404_PAGE_PATH, dst404PageContent);
|
||||
@@ -1,13 +1,14 @@
|
||||
// Imports
|
||||
const {basename, dirname, resolve: resolvePath} = require('canonical-path');
|
||||
const {mkdirSync, readFileSync, writeFileSync} = require('fs');
|
||||
const {parse: parseJson} = require('json5');
|
||||
|
||||
import cpath from 'canonical-path';
|
||||
import {mkdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import json5 from 'json5';
|
||||
import url from 'url';
|
||||
|
||||
// Constants
|
||||
const FIREBASE_CONFIG_PATH = resolvePath(__dirname, '../firebase.json');
|
||||
const NGSW_CONFIG_TEMPLATE_PATH = resolvePath(__dirname, '../ngsw-config.template.json');
|
||||
const NGSW_CONFIG_OUTPUT_PATH = resolvePath(__dirname, '../ngsw-config.json');
|
||||
const currentDir = cpath.dirname(url.fileURLToPath(import.meta.url));
|
||||
const FIREBASE_CONFIG_PATH = cpath.resolve(currentDir, '../firebase.json');
|
||||
const NGSW_CONFIG_TEMPLATE_PATH = cpath.resolve(currentDir, '../ngsw-config.template.json');
|
||||
const NGSW_CONFIG_OUTPUT_PATH = cpath.resolve(currentDir, '../ngsw-config.json');
|
||||
|
||||
// Run
|
||||
_main();
|
||||
@@ -15,9 +16,7 @@ _main();
|
||||
// Helpers
|
||||
function _main() {
|
||||
// Allow an alternative output path (used by bazel to output to output dir)
|
||||
const ngswConfigOutputPath = process.argv.length > 2
|
||||
? process.argv[2]
|
||||
: NGSW_CONFIG_OUTPUT_PATH;
|
||||
const ngswConfigOutputPath = process.argv.length > 2 ? process.argv[2] : NGSW_CONFIG_OUTPUT_PATH;
|
||||
|
||||
const firebaseConfig = readJson(FIREBASE_CONFIG_PATH);
|
||||
const ngswConfig = readJson(NGSW_CONFIG_TEMPLATE_PATH);
|
||||
@@ -29,18 +28,21 @@ function _main() {
|
||||
if (regexBasedRedirects.length > 0) {
|
||||
throw new Error(
|
||||
'The following redirects use `regex`, which is currently not supported by ' +
|
||||
`${basename(__filename)}:` +
|
||||
regexBasedRedirects.map(x => `\n - ${JSON.stringify(x)}`).join(''));
|
||||
`${basename(__filename)}:` +
|
||||
regexBasedRedirects.map((x) => `\n - ${JSON.stringify(x)}`).join('')
|
||||
);
|
||||
}
|
||||
|
||||
// Check that there are no unsupported glob characters/patterns.
|
||||
const redirectsWithUnsupportedGlobs = firebaseRedirects
|
||||
.filter(({source}) => !/^(?:[/\w\-.*]|:\w+|@\([\w\-.|]+\))+$/.test(source));
|
||||
const redirectsWithUnsupportedGlobs = firebaseRedirects.filter(
|
||||
({source}) => !/^(?:[/\w\-.*]|:\w+|@\([\w\-.|]+\))+$/.test(source)
|
||||
);
|
||||
if (redirectsWithUnsupportedGlobs.length > 0) {
|
||||
throw new Error(
|
||||
'The following redirects use glob characters/patterns that are currently not supported by ' +
|
||||
`${basename(__filename)}:` +
|
||||
redirectsWithUnsupportedGlobs.map(x => `\n - ${JSON.stringify(x)}`).join(''));
|
||||
`${basename(__filename)}:` +
|
||||
redirectsWithUnsupportedGlobs.map((x) => `\n - ${JSON.stringify(x)}`).join('')
|
||||
);
|
||||
}
|
||||
|
||||
// Compute additional ignore glob patterns to be added to `navigationUrls`.
|
||||
@@ -48,11 +50,11 @@ function _main() {
|
||||
// Grab the redirect source glob.
|
||||
.map(({source}) => source)
|
||||
// Ignore redirects for files (since these are already ignored by the SW).
|
||||
.filter(glob => /\/[^/.]*$/.test(glob))
|
||||
.filter((glob) => /\/[^/.]*$/.test(glob))
|
||||
// Convert each Firebase-specific glob to a SW-compatible glob.
|
||||
.map(glob => `!${glob.replace(/:\w+/g, '*').replace(/@(\([\w\-.|]+\))/g, '$1')}`)
|
||||
.map((glob) => `!${glob.replace(/:\w+/g, '*').replace(/@(\([\w\-.|]+\))/g, '$1')}`)
|
||||
// Add optional trailing `/` for globs that don't end with `*`.
|
||||
.map(glob => /\w$/.test(glob) ? `${glob}\/{0,1}` : glob)
|
||||
.map((glob) => (/\w$/.test(glob) ? `${glob}\/{0,1}` : glob))
|
||||
// Remove more specific globs that are covered by more generic ones.
|
||||
.filter((glob, _i, globs) => !isGlobRedundant(glob, globs))
|
||||
// Sort the generated globs alphabetically.
|
||||
@@ -61,28 +63,29 @@ function _main() {
|
||||
// Add the additional `navigationUrls` globs and save the config.
|
||||
ngswConfig.navigationUrls.push(...additionalNavigationUrls);
|
||||
|
||||
mkdirSync(dirname(ngswConfigOutputPath), {recursive: true});
|
||||
mkdirSync(cpath.dirname(ngswConfigOutputPath), {recursive: true});
|
||||
writeJson(ngswConfigOutputPath, ngswConfig);
|
||||
}
|
||||
|
||||
function isGlobRedundant(glob, globs) {
|
||||
// Find all globs that could cover other globs.
|
||||
// For simplicity, we only consider globs ending with `/**`.
|
||||
const genericGlobs = globs.filter(g => g.endsWith('/**'));
|
||||
const genericGlobs = globs.filter((g) => g.endsWith('/**'));
|
||||
|
||||
// A glob is redundant if it starts with the path of a potentially generic glob (excluding the
|
||||
// trailing `**`) followed by a word character or a `*`.
|
||||
// For example, `/foo/bar/baz` is covered (and thus made redundant) by `/foo/**`, but `/foo/{0,1}`
|
||||
// is not.
|
||||
return genericGlobs.some(g => {
|
||||
return genericGlobs.some((g) => {
|
||||
const pathPrefix = g.slice(0, -2);
|
||||
return (glob !== g) && glob.startsWith(pathPrefix) &&
|
||||
/^[\w*]/.test(glob.slice(pathPrefix.length));
|
||||
return (
|
||||
glob !== g && glob.startsWith(pathPrefix) && /^[\w*]/.test(glob.slice(pathPrefix.length))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function readJson(filePath) {
|
||||
return parseJson(readFileSync(filePath, 'utf8'));
|
||||
return json5.parse(readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function writeJson(filePath, obj) {
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
This script serves the aio app, watches for changes,
|
||||
and runs a fast dgeni build on any changed files.
|
||||
*/
|
||||
|
||||
const spawn = require("cross-spawn");
|
||||
const watchr = require("../tools/transforms/authors-package/watchr.js");
|
||||
const architectCli = require.resolve("@angular-devkit/architect-cli/bin/architect");
|
||||
|
||||
const serve = spawn(process.execPath, ['--preserve-symlinks', architectCli, "site:serve", "--open", "--poll=1000", "--live-reload", "--watch"], {stdio: "inherit"});
|
||||
serve.on("error", error => {
|
||||
console.error("architect serve script failed");
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
serve.on("close", code => {
|
||||
console.error(`architect serve script exited with code ${code}`);
|
||||
process.exit(1);
|
||||
})
|
||||
|
||||
watchr.watch(true);
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
This script serves the aio app, watches for changes,
|
||||
and runs a fast dgeni build on any changed files.
|
||||
*/
|
||||
|
||||
import spawn from 'cross-spawn';
|
||||
import watchr from '../tools/transforms/authors-package/watchr.js';
|
||||
const architectCli = require.resolve('@angular-devkit/architect-cli/bin/architect');
|
||||
|
||||
const serve = spawn(
|
||||
process.execPath,
|
||||
[
|
||||
'--preserve-symlinks',
|
||||
architectCli,
|
||||
'site:serve',
|
||||
'--open',
|
||||
'--poll=1000',
|
||||
'--live-reload',
|
||||
'--watch',
|
||||
],
|
||||
{stdio: 'inherit'}
|
||||
);
|
||||
serve.on('error', (error) => {
|
||||
console.error('architect serve script failed');
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
serve.on('close', (code) => {
|
||||
console.error(`architect serve script exited with code ${code}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
watchr.watch(true);
|
||||
@@ -15,17 +15,19 @@ def local_server_test(name, entry_point, serve_target, data = [], args = [], **k
|
||||
nodejs_test(
|
||||
name = name,
|
||||
testonly = True,
|
||||
data_for_expansion = [
|
||||
serve_target,
|
||||
entry_point,
|
||||
"@aio_npm//light-server/bin:light-server",
|
||||
],
|
||||
args = [
|
||||
"$(rootpath @aio_npm//light-server/bin:light-server)",
|
||||
"$(rootpath %s)" % serve_target,
|
||||
"$(rootpath %s)" % entry_point,
|
||||
] + args,
|
||||
data = [
|
||||
entry_point,
|
||||
serve_target,
|
||||
"//aio/scripts:run-with-local-server.mjs",
|
||||
"@aio_npm//get-port",
|
||||
"@aio_npm//light-server/bin:light-server",
|
||||
"@aio_npm//shelljs",
|
||||
"@aio_npm//tree-kill",
|
||||
] + data,
|
||||
|
||||
@@ -13,8 +13,9 @@ exports_files([
|
||||
js_library(
|
||||
name = "example-boilerplate-lib",
|
||||
srcs = [
|
||||
"constants.js",
|
||||
"example-boilerplate.js",
|
||||
"constants.mjs",
|
||||
"example-boilerplate.mjs",
|
||||
"example-boilerplate-cli.mjs",
|
||||
],
|
||||
deps = [
|
||||
"//aio/tools/examples/shared",
|
||||
@@ -29,20 +30,21 @@ js_library(
|
||||
nodejs_binary(
|
||||
name = "example-boilerplate",
|
||||
data = [":example-boilerplate-lib"],
|
||||
entry_point = "example-boilerplate.js",
|
||||
entry_point = "example-boilerplate-cli.js",
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "example-boilerplate-test",
|
||||
srcs = ["example-boilerplate.spec.js"],
|
||||
srcs = ["example-boilerplate.spec.mjs"],
|
||||
deps = [":example-boilerplate-lib"],
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = "create-example-lib",
|
||||
srcs = [
|
||||
"create-example.js",
|
||||
"constants.js",
|
||||
"create-example.mjs",
|
||||
"create-example-cli.mjs",
|
||||
"constants.mjs",
|
||||
] + glob(["shared/**"]),
|
||||
deps = [
|
||||
"@aio_npm//@bazel/buildozer",
|
||||
@@ -57,12 +59,12 @@ js_library(
|
||||
nodejs_binary(
|
||||
name = "create-example",
|
||||
data = [":create-example-lib"],
|
||||
entry_point = "create-example.js",
|
||||
entry_point = "create-example-cli.mjs",
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "create-example-test",
|
||||
srcs = ["create-example.spec.js"],
|
||||
srcs = ["create-example.spec.mjs"],
|
||||
deps = [":create-example-lib"],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
const path = require('canonical-path');
|
||||
|
||||
exports.RUNFILES_ROOT = path.resolve(process.env.RUNFILES, 'angular');
|
||||
|
||||
exports.getExamplesBasePath = function(root) {
|
||||
return path.join(root, 'aio', 'content', 'examples');
|
||||
}
|
||||
|
||||
exports.getSharedPath = function(root) {
|
||||
return path.join(root, 'aio', 'tools', 'examples', 'shared');
|
||||
}
|
||||
|
||||
exports.EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
exports.STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
@@ -0,0 +1,14 @@
|
||||
import path from 'canonical-path';
|
||||
|
||||
export const RUNFILES_ROOT = path.resolve(process.env.RUNFILES, 'angular');
|
||||
|
||||
export function getExamplesBasePath(root) {
|
||||
return path.join(root, 'aio', 'content', 'examples');
|
||||
}
|
||||
|
||||
export function getSharedPath(root) {
|
||||
return path.join(root, 'aio', 'tools', 'examples', 'shared');
|
||||
}
|
||||
|
||||
export const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
export const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
@@ -0,0 +1,3 @@
|
||||
import {main} from './create-example.mjs';
|
||||
|
||||
main();
|
||||
@@ -1,156 +0,0 @@
|
||||
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');
|
||||
const buildozer = require('@bazel/buildozer');
|
||||
const {RUNFILES_ROOT, getExamplesBasePath, getSharedPath, EXAMPLE_CONFIG_FILENAME, STACKBLITZ_CONFIG_FILENAME} =
|
||||
require('./constants');
|
||||
|
||||
// BUILD_WORKSPACE_DIRECTORY is set by Bazel when calling `bazel run` and points to the
|
||||
// root of the source tree (e.g., for creating a new example in the source tree). Otherwise,
|
||||
// we are in a test so use the runfiles root.
|
||||
const PROJECT_ROOT = path.resolve(process.env.BUILD_WORKSPACE_DIRECTORY || RUNFILES_ROOT);
|
||||
const EXAMPLES_BASE_PATH = getExamplesBasePath(PROJECT_ROOT);
|
||||
const SHARED_PATH = getSharedPath(PROJECT_ROOT);
|
||||
|
||||
const BASIC_SOURCE_PATH = path.resolve(SHARED_PATH, 'example-scaffold');
|
||||
|
||||
shelljs.set('-e');
|
||||
|
||||
if (require.main === module) {
|
||||
const options =
|
||||
yargs(process.argv.slice(2))
|
||||
.command(
|
||||
'$0 <name> [source]',
|
||||
[
|
||||
'Create a new <name> example.',
|
||||
'',
|
||||
'If [source] is provided then the relevant files from the CLI project at that path are copied into the example.',
|
||||
].join('\n'))
|
||||
.strict()
|
||||
.version(false)
|
||||
.argv;
|
||||
|
||||
const exampleName = options.name;
|
||||
const examplePath = path.resolve(EXAMPLES_BASE_PATH, exampleName);
|
||||
|
||||
console.log('Creating new example at', examplePath);
|
||||
createEmptyExample(exampleName, examplePath);
|
||||
|
||||
const sourcePath =
|
||||
options.source !== undefined ? path.resolve(EXAMPLES_BASE_PATH, options.source) : BASIC_SOURCE_PATH;
|
||||
console.log('Copying files from', sourcePath);
|
||||
copyExampleFiles(sourcePath, examplePath, exampleName);
|
||||
|
||||
buildozer.runWithOptions([{commands: [`set name ${exampleName}`], targets: [`//aio/content/examples/${exampleName}:%docs_example`]}], {cwd: PROJECT_ROOT});
|
||||
|
||||
console.log(`The new "${exampleName}" example has been created.`);
|
||||
console.log(`To include this example, add a "${exampleName}" entry in aio/content/examples/examples.bzl`)
|
||||
console.log(
|
||||
'You can find more info on working with docs examples in aio/tools/examples/README.md.')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the directory and marker files for the new example.
|
||||
*/
|
||||
function createEmptyExample(exampleName, examplePath) {
|
||||
validateExampleName(exampleName);
|
||||
ensureExamplePath(examplePath);
|
||||
writeExampleConfigFile(examplePath);
|
||||
writeStackBlitzFile(exampleName, examplePath);
|
||||
}
|
||||
|
||||
function validateExampleName(exampleName) {
|
||||
if (/\s/.test(exampleName)) {
|
||||
throw new Error(
|
||||
`Unable to create example. The example name contains spaces: '${exampleName}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the new example directory exists.
|
||||
*/
|
||||
function ensureExamplePath(examplePath) {
|
||||
if (fs.existsSync(examplePath)) {
|
||||
throw new Error(
|
||||
`Unable to create example. The path to the new example already exists: ${examplePath}`);
|
||||
}
|
||||
fs.ensureDirSync(examplePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `example-config.json` file to the new example.
|
||||
*/
|
||||
function writeExampleConfigFile(examplePath) {
|
||||
fs.writeFileSync(path.resolve(examplePath, EXAMPLE_CONFIG_FILENAME), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `stackblitz.json` file into the new example.
|
||||
*/
|
||||
function writeStackBlitzFile(exampleName, examplePath) {
|
||||
const config = {
|
||||
description: titleize(exampleName),
|
||||
files: ['!**/*.d.ts', '!**/*.js', '!**/*.[1,2].*'],
|
||||
tags: [exampleName.split('-')]
|
||||
};
|
||||
fs.writeFileSync(
|
||||
path.resolve(examplePath, STACKBLITZ_CONFIG_FILENAME),
|
||||
JSON.stringify(config, null, 2) + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all the files from the `sourcePath` to the `examplePath`, except for files
|
||||
* ignored by the source example.
|
||||
*/
|
||||
function copyExampleFiles(sourcePath, examplePath, exampleName) {
|
||||
const gitIgnoreSource = getGitIgnore(sourcePath);
|
||||
|
||||
// Grab the files in the source folder and filter them based on the gitignore rules.
|
||||
const sourceFiles =
|
||||
glob.sync('**/*', {
|
||||
cwd: sourcePath,
|
||||
dot: true,
|
||||
ignore: ['**/node_modules/**', '.git/**', '.gitignore'],
|
||||
mark: true
|
||||
})
|
||||
// Filter out the directories, leaving only files
|
||||
.filter(filePath => !/\/$/.test(filePath))
|
||||
// Filter out files that match the source directory .gitignore rules
|
||||
.filter(filePath => !gitIgnoreSource.ignores(filePath))
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
console.log(' - ', sourceFile);
|
||||
const destPath = path.resolve(examplePath, sourceFile)
|
||||
fs.ensureDirSync(path.dirname(destPath));
|
||||
fs.copySync(path.resolve(sourcePath, sourceFile), destPath);
|
||||
}
|
||||
}
|
||||
|
||||
function getGitIgnore(directory) {
|
||||
const gitIgnoreMatcher = ignore();
|
||||
const gitignoreFilePath = path.resolve(directory, '.gitignore');
|
||||
if (fs.existsSync(gitignoreFilePath)) {
|
||||
const gitignoreFile = fs.readFileSync(gitignoreFilePath, 'utf8');
|
||||
gitIgnoreMatcher.add(gitignoreFile);
|
||||
}
|
||||
return gitIgnoreMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a kebab-case string to space separated Title Case string.
|
||||
*/
|
||||
function titleize(input) {
|
||||
return input.replace(
|
||||
/(-|^)(.)/g, (_, pre, char) => `${pre === '-' ? ' ' : ''}${char.toUpperCase()}`);
|
||||
}
|
||||
|
||||
exports.createEmptyExample = createEmptyExample;
|
||||
exports.ensureExamplePath = ensureExamplePath;
|
||||
exports.writeExampleConfigFile = writeExampleConfigFile;
|
||||
exports.writeStackBlitzFile = writeStackBlitzFile;
|
||||
exports.copyExampleFiles = copyExampleFiles;
|
||||
exports.titleize = titleize;
|
||||
@@ -0,0 +1,169 @@
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'glob';
|
||||
import ignore from 'ignore';
|
||||
import path from 'canonical-path';
|
||||
import shelljs from 'shelljs';
|
||||
import yargs from 'yargs';
|
||||
import buildozer from '@bazel/buildozer';
|
||||
|
||||
import {
|
||||
RUNFILES_ROOT,
|
||||
getExamplesBasePath,
|
||||
getSharedPath,
|
||||
EXAMPLE_CONFIG_FILENAME,
|
||||
STACKBLITZ_CONFIG_FILENAME,
|
||||
} from './constants.mjs';
|
||||
|
||||
// BUILD_WORKSPACE_DIRECTORY is set by Bazel when calling `bazel run` and points to the
|
||||
// root of the source tree (e.g., for creating a new example in the source tree). Otherwise,
|
||||
// we are in a test so use the runfiles root.
|
||||
const PROJECT_ROOT = path.resolve(process.env.BUILD_WORKSPACE_DIRECTORY || RUNFILES_ROOT);
|
||||
const EXAMPLES_BASE_PATH = getExamplesBasePath(PROJECT_ROOT);
|
||||
const SHARED_PATH = getSharedPath(PROJECT_ROOT);
|
||||
|
||||
const BASIC_SOURCE_PATH = path.resolve(SHARED_PATH, 'example-scaffold');
|
||||
|
||||
shelljs.set('-e');
|
||||
|
||||
export function main() {
|
||||
const options = yargs(process.argv.slice(2))
|
||||
.command(
|
||||
'$0 <name> [source]',
|
||||
[
|
||||
'Create a new <name> example.',
|
||||
'',
|
||||
'If [source] is provided then the relevant files from the CLI project at that path are copied into the example.',
|
||||
].join('\n')
|
||||
)
|
||||
.strict()
|
||||
.version(false).argv;
|
||||
|
||||
const exampleName = options.name;
|
||||
const examplePath = path.resolve(EXAMPLES_BASE_PATH, exampleName);
|
||||
|
||||
console.log('Creating new example at', examplePath);
|
||||
createEmptyExample(exampleName, examplePath);
|
||||
|
||||
const sourcePath =
|
||||
options.source !== undefined
|
||||
? path.resolve(EXAMPLES_BASE_PATH, options.source)
|
||||
: BASIC_SOURCE_PATH;
|
||||
console.log('Copying files from', sourcePath);
|
||||
copyExampleFiles(sourcePath, examplePath, exampleName);
|
||||
|
||||
buildozer.runWithOptions(
|
||||
[
|
||||
{
|
||||
commands: [`set name ${exampleName}`],
|
||||
targets: [`//aio/content/examples/${exampleName}:%docs_example`],
|
||||
},
|
||||
],
|
||||
{cwd: PROJECT_ROOT}
|
||||
);
|
||||
|
||||
console.log(`The new "${exampleName}" example has been created.`);
|
||||
console.log(
|
||||
`To include this example, add a "${exampleName}" entry in aio/content/examples/examples.bzl`
|
||||
);
|
||||
console.log(
|
||||
'You can find more info on working with docs examples in aio/tools/examples/README.md.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the directory and marker files for the new example.
|
||||
*/
|
||||
export function createEmptyExample(exampleName, examplePath) {
|
||||
validateExampleName(exampleName);
|
||||
ensureExamplePath(examplePath);
|
||||
writeExampleConfigFile(examplePath);
|
||||
writeStackBlitzFile(exampleName, examplePath);
|
||||
}
|
||||
|
||||
function validateExampleName(exampleName) {
|
||||
if (/\s/.test(exampleName)) {
|
||||
throw new Error(`Unable to create example. The example name contains spaces: '${exampleName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the new example directory exists.
|
||||
*/
|
||||
export function ensureExamplePath(examplePath) {
|
||||
if (fs.existsSync(examplePath)) {
|
||||
throw new Error(
|
||||
`Unable to create example. The path to the new example already exists: ${examplePath}`
|
||||
);
|
||||
}
|
||||
fs.ensureDirSync(examplePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `example-config.json` file to the new example.
|
||||
*/
|
||||
export function writeExampleConfigFile(examplePath) {
|
||||
fs.writeFileSync(path.resolve(examplePath, EXAMPLE_CONFIG_FILENAME), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `stackblitz.json` file into the new example.
|
||||
*/
|
||||
export function writeStackBlitzFile(exampleName, examplePath) {
|
||||
const config = {
|
||||
description: titleize(exampleName),
|
||||
files: ['!**/*.d.ts', '!**/*.js', '!**/*.[1,2].*'],
|
||||
tags: [exampleName.split('-')],
|
||||
};
|
||||
fs.writeFileSync(
|
||||
path.resolve(examplePath, STACKBLITZ_CONFIG_FILENAME),
|
||||
JSON.stringify(config, null, 2) + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all the files from the `sourcePath` to the `examplePath`, except for files
|
||||
* ignored by the source example.
|
||||
*/
|
||||
export function copyExampleFiles(sourcePath, examplePath, exampleName) {
|
||||
const gitIgnoreSource = getGitIgnore(sourcePath);
|
||||
|
||||
// Grab the files in the source folder and filter them based on the gitignore rules.
|
||||
const sourceFiles = glob
|
||||
.sync('**/*', {
|
||||
cwd: sourcePath,
|
||||
dot: true,
|
||||
ignore: ['**/node_modules/**', '.git/**', '.gitignore'],
|
||||
mark: true,
|
||||
})
|
||||
// Filter out the directories, leaving only files
|
||||
.filter((filePath) => !/\/$/.test(filePath))
|
||||
// Filter out files that match the source directory .gitignore rules
|
||||
.filter((filePath) => !gitIgnoreSource.ignores(filePath));
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
console.log(' - ', sourceFile);
|
||||
const destPath = path.resolve(examplePath, sourceFile);
|
||||
fs.ensureDirSync(path.dirname(destPath));
|
||||
fs.copySync(path.resolve(sourcePath, sourceFile), destPath);
|
||||
}
|
||||
}
|
||||
|
||||
function getGitIgnore(directory) {
|
||||
const gitIgnoreMatcher = ignore();
|
||||
const gitignoreFilePath = path.resolve(directory, '.gitignore');
|
||||
if (fs.existsSync(gitignoreFilePath)) {
|
||||
const gitignoreFile = fs.readFileSync(gitignoreFilePath, 'utf8');
|
||||
gitIgnoreMatcher.add(gitignoreFile);
|
||||
}
|
||||
return gitIgnoreMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a kebab-case string to space separated Title Case string.
|
||||
*/
|
||||
export function titleize(input) {
|
||||
return input.replace(
|
||||
/(-|^)(.)/g,
|
||||
(_, pre, char) => `${pre === '-' ? ' ' : ''}${char.toUpperCase()}`
|
||||
);
|
||||
}
|
||||
+42
-40
@@ -1,18 +1,17 @@
|
||||
const path = require('canonical-path');
|
||||
const fs = require('fs-extra');
|
||||
const {glob} = require('glob');
|
||||
import path from 'canonical-path';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'glob';
|
||||
|
||||
const {EXAMPLE_CONFIG_FILENAME, STACKBLITZ_CONFIG_FILENAME} =
|
||||
require('./constants');
|
||||
import {EXAMPLE_CONFIG_FILENAME, STACKBLITZ_CONFIG_FILENAME} from './constants.mjs';
|
||||
|
||||
const {
|
||||
import {
|
||||
copyExampleFiles,
|
||||
createEmptyExample,
|
||||
ensureExamplePath,
|
||||
titleize,
|
||||
writeExampleConfigFile,
|
||||
writeStackBlitzFile
|
||||
} = require('./create-example');
|
||||
writeStackBlitzFile,
|
||||
} from './create-example.mjs';
|
||||
|
||||
describe('create-example tool', () => {
|
||||
describe('createEmptyExample', () => {
|
||||
@@ -23,17 +22,19 @@ describe('create-example tool', () => {
|
||||
|
||||
createEmptyExample('foo-bar', '/path/to/foo-bar');
|
||||
expect(writeFileSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeFileSpy)
|
||||
.toHaveBeenCalledWith(
|
||||
path.resolve(`/path/to/foo-bar/${EXAMPLE_CONFIG_FILENAME}`), jasmine.any(String));
|
||||
expect(writeFileSpy)
|
||||
.toHaveBeenCalledWith(
|
||||
path.resolve(`/path/to/foo-bar/${STACKBLITZ_CONFIG_FILENAME}`), jasmine.any(String));
|
||||
expect(writeFileSpy).toHaveBeenCalledWith(
|
||||
path.resolve(`/path/to/foo-bar/${EXAMPLE_CONFIG_FILENAME}`),
|
||||
jasmine.any(String)
|
||||
);
|
||||
expect(writeFileSpy).toHaveBeenCalledWith(
|
||||
path.resolve(`/path/to/foo-bar/${STACKBLITZ_CONFIG_FILENAME}`),
|
||||
jasmine.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if the example name contains spaces', () => {
|
||||
expect(() => createEmptyExample('foo bar', '/path/to/foo-bar')).toThrowError(
|
||||
`Unable to create example. The example name contains spaces: 'foo bar'`
|
||||
expect(() => createEmptyExample('foo bar', '/path/to/foo-bar')).toThrowError(
|
||||
`Unable to create example. The example name contains spaces: 'foo bar'`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -41,9 +42,9 @@ describe('create-example tool', () => {
|
||||
describe('ensureExamplePath', () => {
|
||||
it('should error if the path already exists', () => {
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
expect(() => ensureExamplePath('foo/bar'))
|
||||
.toThrowError(
|
||||
`Unable to create example. The path to the new example already exists: foo/bar`);
|
||||
expect(() => ensureExamplePath('foo/bar')).toThrowError(
|
||||
`Unable to create example. The path to the new example already exists: foo/bar`
|
||||
);
|
||||
});
|
||||
|
||||
it('should create the directory on disk', () => {
|
||||
@@ -66,23 +67,26 @@ describe('create-example tool', () => {
|
||||
it('should write a JSON file to disk', () => {
|
||||
const spy = spyOn(fs, 'writeFileSync');
|
||||
writeStackBlitzFile('bar-bar', '/foo/bar-bar');
|
||||
expect(spy).toHaveBeenCalledWith(path.resolve(`/foo/bar-bar/${STACKBLITZ_CONFIG_FILENAME}`), [
|
||||
'{',
|
||||
' "description": "Bar Bar",',
|
||||
' "files": [',
|
||||
' "!**/*.d.ts",',
|
||||
' "!**/*.js",',
|
||||
' "!**/*.[1,2].*"',
|
||||
' ],',
|
||||
' "tags": [',
|
||||
' [',
|
||||
' "bar",',
|
||||
' "bar"',
|
||||
' ]',
|
||||
' ]',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'));
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
path.resolve(`/foo/bar-bar/${STACKBLITZ_CONFIG_FILENAME}`),
|
||||
[
|
||||
'{',
|
||||
' "description": "Bar Bar",',
|
||||
' "files": [',
|
||||
' "!**/*.d.ts",',
|
||||
' "!**/*.js",',
|
||||
' "!**/*.[1,2].*"',
|
||||
' ],',
|
||||
' "tags": [',
|
||||
' [',
|
||||
' "bar",',
|
||||
' "bar"',
|
||||
' ]',
|
||||
' ]',
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +96,7 @@ describe('create-example tool', () => {
|
||||
|
||||
spyOn(console, 'log');
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
const readFileSyncSpy = spyOn(fs, 'readFileSync').and.callFake(p => {
|
||||
const readFileSyncSpy = spyOn(fs, 'readFileSync').and.callFake((p) => {
|
||||
switch (p) {
|
||||
case sourceGitIgnorePath:
|
||||
return '**/*.bad';
|
||||
@@ -100,9 +104,7 @@ describe('create-example tool', () => {
|
||||
throw new Error('Unexpected path');
|
||||
}
|
||||
});
|
||||
spyOn(glob, 'sync').and.returnValue([
|
||||
'a/', 'a/b/', 'a/c', 'x.ts', 'x.bad'
|
||||
]);
|
||||
spyOn(glob, 'sync').and.returnValue(['a/', 'a/b/', 'a/c', 'x.ts', 'x.bad']);
|
||||
const ensureDirSyncSpy = spyOn(fs, 'ensureDirSync');
|
||||
const copySyncSpy = spyOn(fs, 'copySync');
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import boilerplate from './example-boilerplate.mjs';
|
||||
|
||||
boilerplate.main();
|
||||
+53
-33
@@ -1,10 +1,15 @@
|
||||
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');
|
||||
const {RUNFILES_ROOT, getExamplesBasePath, getSharedPath, EXAMPLE_CONFIG_FILENAME} = require('./constants');
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'glob';
|
||||
import ignore from 'ignore';
|
||||
import path from 'canonical-path';
|
||||
import shelljs from 'shelljs';
|
||||
import yargs from 'yargs';
|
||||
import {
|
||||
RUNFILES_ROOT,
|
||||
getExamplesBasePath,
|
||||
getSharedPath,
|
||||
EXAMPLE_CONFIG_FILENAME,
|
||||
} from './constants.mjs';
|
||||
|
||||
const PROJECT_ROOT = RUNFILES_ROOT;
|
||||
const EXAMPLES_BASE_PATH = getExamplesBasePath(PROJECT_ROOT);
|
||||
@@ -19,19 +24,24 @@ class ExampleBoilerPlate {
|
||||
* Add boilerplate files to an example
|
||||
*/
|
||||
add(exampleFolder, outputDir) {
|
||||
const gitignore = ignore().add(fs.readFileSync(path.resolve(BOILERPLATE_BASE_PATH, '.gitignore'), 'utf8'));
|
||||
const gitignore = ignore().add(
|
||||
fs.readFileSync(path.resolve(BOILERPLATE_BASE_PATH, '.gitignore'), 'utf8')
|
||||
);
|
||||
|
||||
const exampleConfig = this.loadJsonFile(path.resolve(exampleFolder, EXAMPLE_CONFIG_FILENAME));
|
||||
|
||||
// Compute additional boilerplate files that should not be copied over for this specific example
|
||||
// This allows the example to override boilerplate files locally, perhaps to include doc-regions specific to the example.
|
||||
const overrideBoilerplate = exampleConfig['overrideBoilerplate'] || [];
|
||||
const boilerplateIgnore = ignore().add(gitignore).add(
|
||||
// Note that the `*` here is to skip over the boilerplate folder itself.
|
||||
// E.g. if the override is `a/b` then we what to match `cli/a/b` and `i18n/a/b` etc.
|
||||
overrideBoilerplate.map(p => path.join('*', p))
|
||||
);
|
||||
const isPathIgnored = absolutePath => boilerplateIgnore.ignores(path.relative(BOILERPLATE_BASE_PATH, absolutePath));
|
||||
const boilerplateIgnore = ignore()
|
||||
.add(gitignore)
|
||||
.add(
|
||||
// Note that the `*` here is to skip over the boilerplate folder itself.
|
||||
// E.g. if the override is `a/b` then we what to match `cli/a/b` and `i18n/a/b` etc.
|
||||
overrideBoilerplate.map((p) => path.join('*', p))
|
||||
);
|
||||
const isPathIgnored = (absolutePath) =>
|
||||
boilerplateIgnore.ignores(path.relative(BOILERPLATE_BASE_PATH, absolutePath));
|
||||
|
||||
const boilerPlateType = exampleConfig.projectType || 'cli';
|
||||
const boilerPlateBasePath = path.resolve(BOILERPLATE_BASE_PATH, boilerPlateType);
|
||||
@@ -54,16 +64,21 @@ class ExampleBoilerPlate {
|
||||
}
|
||||
|
||||
listOverrides() {
|
||||
const exampleFolders =
|
||||
this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
|
||||
const exampleFolders = this.getFoldersContaining(
|
||||
EXAMPLES_BASE_PATH,
|
||||
EXAMPLE_CONFIG_FILENAME,
|
||||
'node_modules'
|
||||
);
|
||||
|
||||
const overriddenFiles = [];
|
||||
exampleFolders.forEach(exampleFolder => {
|
||||
exampleFolders.forEach((exampleFolder) => {
|
||||
const exampleConfig = this.loadJsonFile(path.resolve(exampleFolder, EXAMPLE_CONFIG_FILENAME));
|
||||
const overrideBoilerplate = exampleConfig['overrideBoilerplate'] || [];
|
||||
if (overrideBoilerplate.length > 0) {
|
||||
for (const file of overrideBoilerplate) {
|
||||
overriddenFiles.push(path.relative(EXAMPLES_BASE_PATH, path.resolve(exampleFolder, file)));
|
||||
overriddenFiles.push(
|
||||
path.relative(EXAMPLES_BASE_PATH, path.resolve(exampleFolder, file))
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -74,7 +89,9 @@ class ExampleBoilerPlate {
|
||||
console.log(` - ${file}`);
|
||||
}
|
||||
console.log(`(All these paths are relative to ${EXAMPLES_BASE_PATH}.)`);
|
||||
console.log('If you are updating the boilerplate files then also consider updating these too.');
|
||||
console.log(
|
||||
'If you are updating the boilerplate files then also consider updating these too.'
|
||||
);
|
||||
} else {
|
||||
console.log('No boilerplate files have been overridden in examples.');
|
||||
console.log('You are safe to update the boilerplate files.');
|
||||
@@ -82,23 +99,31 @@ class ExampleBoilerPlate {
|
||||
}
|
||||
|
||||
main() {
|
||||
yargs.usage('$0 <cmd> [args]')
|
||||
.command('add <exampleDir> <outputDir>', 'create boilerplate for an example', yrgs => this.add(yrgs.argv._[1], yrgs.argv._[2]))
|
||||
.command('list-overrides', 'list all the boilerplate files that have been overridden in examples', () => this.listOverrides())
|
||||
.demandCommand(1, 'Please supply a command from the list above')
|
||||
.argv;
|
||||
yargs(process.argv.slice(2))
|
||||
.usage('$0 <cmd> [args]')
|
||||
.command('add <exampleDir> <outputDir>', 'create boilerplate for an example', (yrgs) =>
|
||||
this.add(yrgs.argv._[1], yrgs.argv._[2])
|
||||
)
|
||||
.command(
|
||||
'list-overrides',
|
||||
'list all the boilerplate files that have been overridden in examples',
|
||||
() => this.listOverrides()
|
||||
)
|
||||
.demandCommand(1, 'Please supply a command from the list above').argv;
|
||||
}
|
||||
|
||||
getFoldersContaining(basePath, filename, ignore) {
|
||||
const pattern = path.resolve(basePath, '**', filename);
|
||||
const ignorePattern = path.resolve(basePath, '**', ignore, '**');
|
||||
return glob.sync(pattern, {ignore: [ignorePattern]}).map(file => path.dirname(file));
|
||||
return glob.sync(pattern, {ignore: [ignorePattern]}).map((file) => path.dirname(file));
|
||||
}
|
||||
|
||||
loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; }
|
||||
loadJsonFile(filePath) {
|
||||
return fs.readJsonSync(filePath, {throws: false}) || {};
|
||||
}
|
||||
|
||||
copyDirectoryContents(srcDir, dstDir, isPathIgnored) {
|
||||
shelljs.ls('-Al', srcDir).forEach(stat => {
|
||||
shelljs.ls('-Al', srcDir).forEach((stat) => {
|
||||
const srcPath = path.resolve(srcDir, stat.name);
|
||||
const dstPath = path.resolve(dstDir, stat.name);
|
||||
|
||||
@@ -125,9 +150,4 @@ class ExampleBoilerPlate {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ExampleBoilerPlate();
|
||||
|
||||
// If this file was run directly then run the main function,
|
||||
if (require.main === module) {
|
||||
module.exports.main();
|
||||
}
|
||||
export default new ExampleBoilerPlate();
|
||||
+82
-56
@@ -1,15 +1,15 @@
|
||||
const path = require('canonical-path');
|
||||
const fs = require('fs-extra');
|
||||
const glob = require('glob');
|
||||
const shelljs = require('shelljs');
|
||||
import path from 'canonical-path';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'glob';
|
||||
import shelljs from 'shelljs';
|
||||
|
||||
const {RUNFILES_ROOT, getExamplesBasePath, getSharedPath} = require("./constants");
|
||||
import {RUNFILES_ROOT, getExamplesBasePath, getSharedPath} from './constants.mjs';
|
||||
import exampleBoilerPlate from './example-boilerplate.mjs';
|
||||
|
||||
const PROJECT_ROOT = RUNFILES_ROOT;
|
||||
const EXAMPLES_BASE_PATH = getExamplesBasePath(PROJECT_ROOT);
|
||||
const SHARED_PATH = getSharedPath(PROJECT_ROOT);
|
||||
|
||||
const exampleBoilerPlate = require('./example-boilerplate');
|
||||
const outputDir = process.env.TEST_TMPDIR; // Bazel-provided temp dir
|
||||
|
||||
describe('example-boilerplate tool', () => {
|
||||
@@ -25,7 +25,7 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should copy all the source boilerplate files for systemjs', () => {
|
||||
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'systemjs' });
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({projectType: 'systemjs'});
|
||||
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should copy all the source boilerplate files for cli', () => {
|
||||
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'cli' });
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({projectType: 'cli'});
|
||||
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should copy all the source boilerplate files for i18n (on top of the cli ones)', () => {
|
||||
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'i18n' });
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({projectType: 'i18n'});
|
||||
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should copy all the source boilerplate files for universal (on top of the cli ones)', () => {
|
||||
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'universal' });
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({projectType: 'universal'});
|
||||
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('example-boilerplate tool', () => {
|
||||
it('should not copy boilerplate files that match `overrideBoilerplate` in the example-config.json file', () => {
|
||||
const boilerplateDir = path.resolve(sharedDir, 'boilerplate');
|
||||
exampleBoilerPlate.loadJsonFile.and.returnValue({
|
||||
'overrideBoilerplate': [ 'c/d' ]
|
||||
'overrideBoilerplate': ['c/d'],
|
||||
});
|
||||
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
@@ -106,12 +106,18 @@ describe('example-boilerplate tool', () => {
|
||||
it('should try to load the example config file', () => {
|
||||
exampleBoilerPlate.add(exampleFolder, outputDir);
|
||||
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledTimes(1);
|
||||
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledWith(path.resolve(`${exampleFolder}/example-config.json`));
|
||||
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledWith(
|
||||
path.resolve(`${exampleFolder}/example-config.json`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyDirectoryContents', () => {
|
||||
const spyFnFor = fnName => (...args) => { callLog.push(`${fnName}(${args.join(', ')})`); };
|
||||
const spyFnFor =
|
||||
(fnName) =>
|
||||
(...args) => {
|
||||
callLog.push(`${fnName}(${args.join(', ')})`);
|
||||
};
|
||||
let isPathIgnoredSpy;
|
||||
let callLog;
|
||||
|
||||
@@ -154,7 +160,7 @@ describe('example-boilerplate tool', () => {
|
||||
{name: 'file-1.txt', isDirectory: () => false},
|
||||
{name: 'file-2.txt', isDirectory: () => false},
|
||||
]);
|
||||
isPathIgnoredSpy.and.callFake(path => path.endsWith('file-1.txt'));
|
||||
isPathIgnoredSpy.and.callFake((path) => path.endsWith('file-1.txt'));
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
@@ -186,18 +192,19 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should recursively copy sub-directories', () => {
|
||||
spyOn(shelljs, 'ls')
|
||||
.withArgs('-Al', 'source/dir').and.returnValue([
|
||||
.withArgs('-Al', 'source/dir')
|
||||
.and.returnValue([
|
||||
{name: 'file-1.txt', isDirectory: () => false},
|
||||
{name: 'sub-dir-1', isDirectory: () => true},
|
||||
{name: 'file-2.txt', isDirectory: () => false},
|
||||
])
|
||||
.withArgs('-Al', path.resolve('source/dir/sub-dir-1')).and.returnValue([
|
||||
.withArgs('-Al', path.resolve('source/dir/sub-dir-1'))
|
||||
.and.returnValue([
|
||||
{name: 'file-3.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-4.txt', isDirectory: () => false},
|
||||
]);
|
||||
.withArgs('-Al', path.resolve('source/dir/sub-dir-1/sub-dir-2'))
|
||||
.and.returnValue([{name: 'file-4.txt', isDirectory: () => false}]);
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
@@ -210,22 +217,22 @@ describe('example-boilerplate tool', () => {
|
||||
// Create `sub-dir-1` and recursively copy its contents.
|
||||
`mkdir(-p, ${path.resolve('destination/dir/sub-dir-1')})`,
|
||||
|
||||
// Copy `sub-dir-1/file-3.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
|
||||
'cp(' +
|
||||
`${path.resolve('source/dir/sub-dir-1/file-3.txt')}, ` +
|
||||
`${path.resolve('destination/dir/sub-dir-1')})`,
|
||||
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
|
||||
// Copy `sub-dir-1/file-3.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
|
||||
'cp(' +
|
||||
`${path.resolve('source/dir/sub-dir-1/file-3.txt')}, ` +
|
||||
`${path.resolve('destination/dir/sub-dir-1')})`,
|
||||
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`,
|
||||
|
||||
// Create `sub-dir-1/sub-dir-2` and recursively copy its contents.
|
||||
`mkdir(-p, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
|
||||
// Create `sub-dir-1/sub-dir-2` and recursively copy its contents.
|
||||
`mkdir(-p, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
|
||||
|
||||
// Copy `sub-dir-1/sub-dir-2/file-4.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
|
||||
'cp(' +
|
||||
`${path.resolve('source/dir/sub-dir-1/sub-dir-2/file-4.txt')}, ` +
|
||||
`${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
|
||||
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
|
||||
// Copy `sub-dir-1/sub-dir-2/file-4.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
|
||||
'cp(' +
|
||||
`${path.resolve('source/dir/sub-dir-1/sub-dir-2/file-4.txt')}, ` +
|
||||
`${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`,
|
||||
`chmod(444, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`,
|
||||
|
||||
// Copy `file-2.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/file-2.txt')})`,
|
||||
@@ -236,18 +243,19 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should skip ignored directories', () => {
|
||||
spyOn(shelljs, 'ls')
|
||||
.withArgs('-Al', 'source/dir').and.returnValue([
|
||||
.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([
|
||||
.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'));
|
||||
.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);
|
||||
|
||||
@@ -272,30 +280,42 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should list all files that are overridden in examples', () => {
|
||||
spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValues(
|
||||
{"overrideBoilerplate": ["angular.json", "tsconfig.json"]}, // a/b/example-config.json
|
||||
{"overrideBoilerplate": []}, // c/d/example-config.json
|
||||
{}, // e/f/example-config.json
|
||||
{'overrideBoilerplate': ['angular.json', 'tsconfig.json']}, // a/b/example-config.json
|
||||
{'overrideBoilerplate': []}, // c/d/example-config.json
|
||||
{} // e/f/example-config.json
|
||||
);
|
||||
exampleBoilerPlate.listOverrides();
|
||||
expect(exampleBoilerPlate.getFoldersContaining)
|
||||
.toHaveBeenCalledWith(examplesDir, 'example-config.json', 'node_modules');
|
||||
expect(console.log).toHaveBeenCalledWith('Boilerplate files that have been overridden in examples:');
|
||||
expect(exampleBoilerPlate.getFoldersContaining).toHaveBeenCalledWith(
|
||||
examplesDir,
|
||||
'example-config.json',
|
||||
'node_modules'
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Boilerplate files that have been overridden in examples:'
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(' - a/b/angular.json');
|
||||
expect(console.log).toHaveBeenCalledWith(' - a/b/tsconfig.json');
|
||||
expect(console.log).toHaveBeenCalledWith(`(All these paths are relative to ${examplesDir}.)`);
|
||||
expect(console.log).toHaveBeenCalledWith('If you are updating the boilerplate files then also consider updating these too.');
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'If you are updating the boilerplate files then also consider updating these too.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display a helpful message if there are no overridden files', () => {
|
||||
spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValues(
|
||||
{"overrideBoilerplate": null}, // a/b/example-config.json
|
||||
{"overrideBoilerplate": []}, // c/d/example-config.json
|
||||
{}, // e/f/example-config.json
|
||||
{'overrideBoilerplate': null}, // a/b/example-config.json
|
||||
{'overrideBoilerplate': []}, // c/d/example-config.json
|
||||
{} // e/f/example-config.json
|
||||
);
|
||||
exampleBoilerPlate.listOverrides();
|
||||
expect(exampleBoilerPlate.getFoldersContaining)
|
||||
.toHaveBeenCalledWith(examplesDir, 'example-config.json', 'node_modules');
|
||||
expect(console.log).toHaveBeenCalledWith('No boilerplate files have been overridden in examples.');
|
||||
expect(exampleBoilerPlate.getFoldersContaining).toHaveBeenCalledWith(
|
||||
examplesDir,
|
||||
'example-config.json',
|
||||
'node_modules'
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No boilerplate files have been overridden in examples.'
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith('You are safe to update the boilerplate files.');
|
||||
});
|
||||
});
|
||||
@@ -303,18 +323,24 @@ describe('example-boilerplate tool', () => {
|
||||
describe('getFoldersContaining', () => {
|
||||
it('should use glob.sync', () => {
|
||||
spyOn(glob, 'sync').and.returnValue(['a/b/config.json', 'c/d/config.json']);
|
||||
const result = exampleBoilerPlate.getFoldersContaining('base/path', 'config.json', 'node_modules');
|
||||
expect(glob.sync).toHaveBeenCalledWith(path.resolve('base/path/**/config.json'), { ignore: [path.resolve('base/path/**/node_modules/**')] });
|
||||
const result = exampleBoilerPlate.getFoldersContaining(
|
||||
'base/path',
|
||||
'config.json',
|
||||
'node_modules'
|
||||
);
|
||||
expect(glob.sync).toHaveBeenCalledWith(path.resolve('base/path/**/config.json'), {
|
||||
ignore: [path.resolve('base/path/**/node_modules/**')],
|
||||
});
|
||||
expect(result).toEqual(['a/b', 'c/d']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadJsonFile', () => {
|
||||
it('should use fs.readJsonSync', () => {
|
||||
spyOn(fs, 'readJsonSync').and.returnValue({ some: 'value' });
|
||||
spyOn(fs, 'readJsonSync').and.returnValue({some: 'value'});
|
||||
const result = exampleBoilerPlate.loadJsonFile('some/file');
|
||||
expect(fs.readJsonSync).toHaveBeenCalledWith('some/file', {throws: false});
|
||||
expect(result).toEqual({ some: 'value' });
|
||||
expect(result).toEqual({some: 'value'});
|
||||
});
|
||||
|
||||
it('should return an empty object if readJsonSync fails', () => {
|
||||
@@ -9,7 +9,7 @@ js_library(
|
||||
["**/*.js"],
|
||||
exclude = [
|
||||
"**/*/spec.js",
|
||||
"watchr.js",
|
||||
"watchr.mjs",
|
||||
],
|
||||
),
|
||||
deps = [
|
||||
@@ -25,7 +25,7 @@ js_library(
|
||||
js_library(
|
||||
name = "watchdocs",
|
||||
srcs = [
|
||||
"watchr.js",
|
||||
"watchr.mjs",
|
||||
],
|
||||
deps = [
|
||||
":authors-package",
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/* eslint no-console: "off" */
|
||||
const watchr = require('watchr');
|
||||
const {relative} = require('canonical-path');
|
||||
const {generateDocs} = require('./index.js');
|
||||
const { PROJECT_ROOT, CONTENTS_PATH, API_SOURCE_PATH } = require('../config');
|
||||
|
||||
function listener(changeType, fullPath) {
|
||||
try {
|
||||
const relativePath = relative(PROJECT_ROOT, fullPath);
|
||||
console.log('The file', relativePath, `was ${changeType}d at`, new Date().toUTCString());
|
||||
generateDocs(relativePath);
|
||||
} catch(err) {
|
||||
console.log('Error generating docs', err);
|
||||
}
|
||||
}
|
||||
|
||||
function next(error) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function watch() {
|
||||
console.log('============================================================================');
|
||||
console.log('Started watching files in:');
|
||||
console.log(' - ', CONTENTS_PATH);
|
||||
console.log(' - ', API_SOURCE_PATH);
|
||||
console.log('Doc gen will automatically run on any change to a file in either directory.');
|
||||
console.log('============================================================================');
|
||||
|
||||
watchr.open(CONTENTS_PATH, listener, next);
|
||||
watchr.open(API_SOURCE_PATH, listener, next);
|
||||
}
|
||||
|
||||
exports.watch = watch;
|
||||
@@ -0,0 +1,35 @@
|
||||
/* eslint no-console: "off" */
|
||||
import watchr from 'watchr';
|
||||
import canonicalPath from 'canonical-path';
|
||||
import authorsPackage from './index.js';
|
||||
import config from '../config.js';
|
||||
|
||||
function listener(changeType, fullPath) {
|
||||
try {
|
||||
const relativePath = canonicalPath.relative(config.PROJECT_ROOT, fullPath);
|
||||
console.log('The file', relativePath, `was ${changeType}d at`, new Date().toUTCString());
|
||||
authorsPackage.generateDocs(relativePath);
|
||||
} catch (err) {
|
||||
console.log('Error generating docs', err);
|
||||
}
|
||||
}
|
||||
|
||||
function next(error) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function watch() {
|
||||
console.log('============================================================================');
|
||||
console.log('Started watching files in:');
|
||||
console.log(' - ', config.CONTENTS_PATH);
|
||||
console.log(' - ', config.API_SOURCE_PATH);
|
||||
console.log('Doc gen will automatically run on any change to a file in either directory.');
|
||||
console.log('============================================================================');
|
||||
|
||||
watchr.open(config.CONTENTS_PATH, listener, next);
|
||||
watchr.open(config.API_SOURCE_PATH, listener, next);
|
||||
}
|
||||
|
||||
exports.watch = watch;
|
||||
@@ -18,7 +18,7 @@ def partial_compliance_golden(filePath):
|
||||
name = generate_partial_name,
|
||||
testonly = True,
|
||||
data = data,
|
||||
data_for_args = [filePath],
|
||||
data_for_expansion = [filePath],
|
||||
visibility = [":__pkg__"],
|
||||
entry_point = "//packages/compiler-cli/test/compliance/partial:cli.ts",
|
||||
templated_args = ["$(execpath %s)" % filePath],
|
||||
|
||||
@@ -17,7 +17,7 @@ def circular_dependency_test(name, deps, entry_point, **kwargs):
|
||||
name = name,
|
||||
data = ["@npm//madge"] + deps,
|
||||
entry_point = "@npm//:node_modules/madge/bin/cli.js",
|
||||
data_for_args = [MADGE_CONFIG_LABEL],
|
||||
data_for_expansion = [MADGE_CONFIG_LABEL],
|
||||
templated_args = [
|
||||
"--circular",
|
||||
"--no-spinner",
|
||||
|
||||
@@ -6,7 +6,7 @@ load("//tools/esm-interop:extract-esm-output.bzl", "extract_esm_outputs")
|
||||
|
||||
def nodejs_binary(
|
||||
name,
|
||||
data_for_args = [],
|
||||
data_for_expansion = [],
|
||||
linker_enabled = False,
|
||||
npm_workspace = "npm",
|
||||
**kwargs):
|
||||
@@ -25,12 +25,12 @@ def nodejs_binary(
|
||||
extract_esm_outputs(
|
||||
name = "%s_esm_deps" % name,
|
||||
testonly = testonly,
|
||||
deps = data,
|
||||
deps = data + data_for_expansion,
|
||||
)
|
||||
|
||||
_nodejs_binary(
|
||||
name = name,
|
||||
data = [":%s_esm_deps" % name] + data_for_args,
|
||||
data = [":%s_esm_deps" % name] + data_for_expansion,
|
||||
testonly = testonly,
|
||||
entry_point = str(entry_point).replace(".js", ".mjs"),
|
||||
env = env,
|
||||
@@ -41,7 +41,7 @@ def nodejs_binary(
|
||||
|
||||
def nodejs_test(
|
||||
name,
|
||||
data_for_args = [],
|
||||
data_for_expansion = [],
|
||||
linker_enabled = False,
|
||||
npm_workspace = "npm",
|
||||
**kwargs):
|
||||
@@ -58,12 +58,12 @@ def nodejs_test(
|
||||
extract_esm_outputs(
|
||||
name = "%s_esm_deps" % name,
|
||||
testonly = True,
|
||||
deps = data,
|
||||
deps = data + data_for_expansion,
|
||||
)
|
||||
|
||||
_nodejs_test(
|
||||
name = name,
|
||||
data = [":%s_esm_deps" % name] + data_for_args,
|
||||
data = [":%s_esm_deps" % name] + data_for_expansion,
|
||||
env = env,
|
||||
templated_args = templated_args,
|
||||
use_esm = True,
|
||||
|
||||
@@ -23,7 +23,7 @@ def js_expected_symbol_test(name, src, golden, data = [], **kwargs):
|
||||
data = all_data,
|
||||
entry_point = entry_point,
|
||||
tags = kwargs.pop("tags", []) + ["symbol_extractor"],
|
||||
data_for_args = [src, golden],
|
||||
data_for_expansion = [src, golden],
|
||||
templated_args = ["$(rootpath %s)" % src, "$(rootpath %s)" % golden],
|
||||
**kwargs
|
||||
)
|
||||
@@ -33,7 +33,7 @@ def js_expected_symbol_test(name, src, golden, data = [], **kwargs):
|
||||
testonly = True,
|
||||
data = all_data,
|
||||
entry_point = entry_point,
|
||||
data_for_args = [src, golden],
|
||||
data_for_expansion = [src, golden],
|
||||
templated_args = ["$(rootpath %s)" % src, "$(rootpath %s)" % golden, "--accept"],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user