283 lines
7.1 KiB
TypeScript
283 lines
7.1 KiB
TypeScript
import {
|
|
JsonObject,
|
|
logging,
|
|
normalize,
|
|
schema,
|
|
virtualFs
|
|
} from '@angular-devkit/core';
|
|
import { createConsoleLogger, NodeJsSyncHost } from '@angular-devkit/core/node';
|
|
import {
|
|
SchematicEngine,
|
|
UnsuccessfulWorkflowExecution
|
|
} from '@angular-devkit/schematics';
|
|
import {
|
|
NodeModulesEngineHost,
|
|
NodeWorkflow
|
|
} from '@angular-devkit/schematics/tools';
|
|
import * as appRoot from 'app-root-path';
|
|
import { execSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import { readFileSync, statSync, writeFileSync } from 'fs';
|
|
import { copySync, removeSync } from 'fs-extra';
|
|
import * as inquirer from 'inquirer';
|
|
import * as path from 'path';
|
|
import * as yargsParser from 'yargs-parser';
|
|
|
|
const rootDirectory = appRoot.path;
|
|
|
|
export function workspaceSchematic(args: string[]) {
|
|
const parsedArgs = parseOptions(args);
|
|
const logger = createConsoleLogger(
|
|
parsedArgs['verbose'],
|
|
process.stdout,
|
|
process.stderr
|
|
);
|
|
const outDir = compileTools();
|
|
if (parsedArgs['list-schematics']) {
|
|
return listSchematics(
|
|
path.join(outDir, 'workspace-schematics.json'),
|
|
logger
|
|
);
|
|
}
|
|
const schematicName = args[0];
|
|
const workflow = createWorkflow(parsedArgs.dryRun);
|
|
executeSchematic(schematicName, parsedArgs, workflow, outDir, logger);
|
|
}
|
|
|
|
// compile tools
|
|
function compileTools() {
|
|
const toolsOutDir = getToolsOutDir();
|
|
removeSync(toolsOutDir);
|
|
compileToolsDir(toolsOutDir);
|
|
|
|
const schematicsOutDir = path.join(toolsOutDir, 'schematics');
|
|
const collectionData = constructCollection();
|
|
saveCollection(schematicsOutDir, collectionData);
|
|
return schematicsOutDir;
|
|
}
|
|
|
|
function getToolsOutDir() {
|
|
return path.resolve(
|
|
rootDirectory,
|
|
'tools',
|
|
JSON.parse(
|
|
readFileSync(
|
|
path.join(rootDirectory, 'tools', 'tsconfig.tools.json'),
|
|
'UTF-8'
|
|
)
|
|
).compilerOptions.outDir
|
|
);
|
|
}
|
|
|
|
function compileToolsDir(outDir: string) {
|
|
copySync(path.join(rootDirectory, 'tools'), outDir);
|
|
try {
|
|
execSync('tsc -p tools/tsconfig.tools.json', {
|
|
stdio: 'inherit',
|
|
cwd: rootDirectory
|
|
});
|
|
} catch (e) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function constructCollection() {
|
|
const schematics = {};
|
|
fs.readdirSync(schematicsDir()).forEach(c => {
|
|
const childDir = path.join(schematicsDir(), c);
|
|
if (exists(path.join(childDir, 'schema.json'))) {
|
|
schematics[c] = {
|
|
factory: `./${c}`,
|
|
schema: `./${path.join(c, 'schema.json')}`,
|
|
description: `Schematic ${c}`
|
|
};
|
|
}
|
|
});
|
|
return {
|
|
name: 'workspace-schematics',
|
|
version: '1.0',
|
|
schematics
|
|
};
|
|
}
|
|
|
|
function saveCollection(dir: string, collection: any) {
|
|
writeFileSync(
|
|
path.join(dir, 'workspace-schematics.json'),
|
|
JSON.stringify(collection)
|
|
);
|
|
}
|
|
|
|
function schematicsDir() {
|
|
return path.join('tools', 'schematics');
|
|
}
|
|
|
|
function createWorkflow(dryRun: boolean) {
|
|
const root = normalize(rootDirectory);
|
|
const host = new virtualFs.ScopedHost(new NodeJsSyncHost(), root);
|
|
return new NodeWorkflow(host, {
|
|
packageManager: fileExists('yarn.lock') ? 'yarn' : 'npm',
|
|
root,
|
|
dryRun
|
|
});
|
|
}
|
|
|
|
function listSchematics(collectionName: string, logger: logging.Logger) {
|
|
try {
|
|
const engineHost = new NodeModulesEngineHost();
|
|
const engine = new SchematicEngine(engineHost);
|
|
const collection = engine.createCollection(collectionName);
|
|
logger.info(engine.listSchematicNames(collection).join('\n'));
|
|
} catch (error) {
|
|
logger.fatal(error.message);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function createPromptProvider(): schema.PromptProvider {
|
|
return (definitions: Array<schema.PromptDefinition>) => {
|
|
const questions: inquirer.Questions = definitions.map(definition => {
|
|
const question: inquirer.Question = {
|
|
name: definition.id,
|
|
message: definition.message,
|
|
default: definition.default
|
|
};
|
|
|
|
const validator = definition.validator;
|
|
if (validator) {
|
|
question.validate = input => validator(input);
|
|
}
|
|
|
|
switch (definition.type) {
|
|
case 'confirmation':
|
|
return { ...question, type: 'confirm' };
|
|
case 'list':
|
|
return {
|
|
...question,
|
|
type: 'list',
|
|
choices:
|
|
definition.items &&
|
|
definition.items.map(item => {
|
|
if (typeof item == 'string') {
|
|
return item;
|
|
} else {
|
|
return {
|
|
name: item.label,
|
|
value: item.value
|
|
};
|
|
}
|
|
})
|
|
};
|
|
default:
|
|
return { ...question, type: definition.type };
|
|
}
|
|
});
|
|
|
|
return inquirer.prompt(questions);
|
|
};
|
|
}
|
|
|
|
// execute schematic
|
|
async function executeSchematic(
|
|
schematicName: string,
|
|
options: { [p: string]: any },
|
|
workflow: NodeWorkflow,
|
|
outDir: string,
|
|
logger: logging.Logger
|
|
) {
|
|
let nothingDone = true;
|
|
workflow.reporter.subscribe((event: any) => {
|
|
nothingDone = false;
|
|
const eventPath = event.path.startsWith('/')
|
|
? event.path.substr(1)
|
|
: event.path;
|
|
switch (event.kind) {
|
|
case 'error':
|
|
const desc =
|
|
event.description == 'alreadyExist'
|
|
? 'already exists'
|
|
: 'does not exist.';
|
|
console.error(`error! ${eventPath} ${desc}.`);
|
|
break;
|
|
case 'update':
|
|
console.log(`update ${eventPath} (${event.content.length} bytes)`);
|
|
break;
|
|
case 'create':
|
|
console.log(`create ${eventPath} (${event.content.length} bytes)`);
|
|
break;
|
|
case 'delete':
|
|
console.log(`delete ${eventPath}`);
|
|
break;
|
|
case 'rename':
|
|
const eventToPath = event.to.startsWith('/')
|
|
? event.to.substr(1)
|
|
: event.to;
|
|
console.log(`rename ${eventPath} => ${eventToPath}`);
|
|
break;
|
|
}
|
|
});
|
|
|
|
const args = options._.slice(1);
|
|
workflow.registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
|
|
if ('index' in schema) {
|
|
return args[+schema.index];
|
|
} else {
|
|
return args;
|
|
}
|
|
});
|
|
delete options._;
|
|
|
|
// Add support for interactive prompts
|
|
if (!options.noInteractive && options.interactive !== false) {
|
|
workflow.registry.usePromptProvider(createPromptProvider());
|
|
}
|
|
|
|
try {
|
|
await workflow
|
|
.execute({
|
|
collection: path.join(outDir, 'workspace-schematics.json'),
|
|
schematic: schematicName,
|
|
options: options,
|
|
logger: logger
|
|
})
|
|
.toPromise();
|
|
|
|
if (nothingDone) {
|
|
logger.info('Nothing to be done.');
|
|
}
|
|
} catch (err) {
|
|
if (err instanceof UnsuccessfulWorkflowExecution) {
|
|
// "See above" because we already printed the error.
|
|
logger.fatal('The Schematic workflow failed. See above.');
|
|
} else {
|
|
logger.fatal(err.stack || err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseOptions(args: string[]): { [k: string]: any } {
|
|
return yargsParser(args, {
|
|
boolean: ['dryRun'],
|
|
alias: {
|
|
dryRun: ['d']
|
|
}
|
|
});
|
|
}
|
|
|
|
function exists(file: string): boolean {
|
|
try {
|
|
return !!fs.statSync(file);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function fileExists(filePath: string): boolean {
|
|
try {
|
|
return statSync(filePath).isFile();
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|