forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreate-example.mjs
169 lines (149 loc) · 5.06 KB
/
create-example.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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()}`
);
}