2021-03-11 09:56:49 +00:00
|
|
|
import { compile, readSchema } from '../index';
|
2021-03-09 18:29:31 +00:00
|
|
|
import fs from 'fs';
|
2021-03-11 09:56:49 +00:00
|
|
|
import path from 'path';
|
|
|
|
import { glob } from 'glob';
|
2021-03-11 13:43:06 +00:00
|
|
|
import minimatch from 'minimatch';
|
2021-03-11 09:56:49 +00:00
|
|
|
import yargs from 'yargs/yargs';
|
|
|
|
import * as M from '../meta';
|
2021-03-11 13:43:06 +00:00
|
|
|
import { SchemaSyntaxError } from '../error';
|
|
|
|
import chalk from 'chalk';
|
|
|
|
import { formatPosition } from '@preserves/core';
|
2021-03-11 09:56:49 +00:00
|
|
|
|
|
|
|
export type CommandLineArguments = {
|
|
|
|
input: string;
|
|
|
|
base: string | undefined;
|
|
|
|
output: string | undefined;
|
|
|
|
core: string;
|
2021-03-11 13:43:06 +00:00
|
|
|
watch: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type CompilationResult = {
|
|
|
|
options: CommandLineArguments,
|
|
|
|
inputFiles: Array<InputFile>,
|
|
|
|
failures: Array<[string, Error]>,
|
|
|
|
base: string,
|
|
|
|
output: string,
|
|
|
|
};
|
|
|
|
|
|
|
|
export type InputFile = {
|
|
|
|
inputFilePath: string,
|
|
|
|
outputFilePath: string,
|
|
|
|
schemaPath: M.ModulePath,
|
|
|
|
schema: M.Schema,
|
2021-03-11 09:56:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export function computeBase(paths: string[]): string {
|
|
|
|
if (paths.length === 0) {
|
|
|
|
return '';
|
|
|
|
} else if (paths.length === 1) {
|
|
|
|
return path.dirname(paths[0]) + '/';
|
|
|
|
} else {
|
|
|
|
let i = 0;
|
|
|
|
while (true) {
|
|
|
|
let ch: string | null = null
|
|
|
|
for (const p of paths) {
|
|
|
|
if (i >= p.length) return p.slice(0, i);
|
|
|
|
if (ch === null) ch = p[i];
|
2021-03-11 13:43:06 +00:00
|
|
|
if (p[i] !== ch) return p.slice(0, i);
|
2021-03-11 09:56:49 +00:00
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
export function run(options: CommandLineArguments): void {
|
|
|
|
if (!options.watch) {
|
|
|
|
if (runOnce(options).failures.length !== 0) {
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
function runWatch() {
|
|
|
|
console.clear();
|
|
|
|
console.log(chalk.yellow(new Date().toISOString()) +
|
|
|
|
' Compiling Schemas in watch mode...\n');
|
|
|
|
const r = runOnce(options);
|
|
|
|
const hasErrors = r.failures.length > 0;
|
|
|
|
const errorSummary = (hasErrors ? chalk.redBright : chalk.greenBright)(
|
|
|
|
`${r.failures.length} error(s)`);
|
|
|
|
console.log(chalk.yellow(new Date().toISOString()) +
|
|
|
|
` Processed ${r.inputFiles.length} file(s) with ${errorSummary}. Waiting for changes.`);
|
|
|
|
const watcher = fs.watch(r.base, { recursive: true }, (_event, filename) => {
|
|
|
|
filename = path.join(r.base, filename);
|
|
|
|
if (minimatch(filename, options.input)) {
|
|
|
|
watcher.close();
|
|
|
|
runWatch();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
runWatch();
|
|
|
|
}
|
|
|
|
}
|
2021-03-11 09:56:49 +00:00
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
function changeExt(p: string, newext: string): string {
|
2021-03-11 09:56:49 +00:00
|
|
|
return p.slice(0, -path.extname(p).length) + newext;
|
|
|
|
}
|
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
export function modulePathTo(file1: string, file2: string): string | null {
|
|
|
|
if (file1 === file2) return null;
|
|
|
|
let naive = path.relative(path.dirname(file1), file2);
|
|
|
|
if (naive[0] !== '.' && naive[0] !== '/') naive = './' + naive;
|
|
|
|
return changeExt(naive, '');
|
|
|
|
}
|
2021-03-11 09:56:49 +00:00
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
export function runOnce(options: CommandLineArguments): CompilationResult {
|
|
|
|
const matches = glob.sync(options.input);
|
|
|
|
const failures: Array<[string, Error]> = [];
|
2021-03-11 09:56:49 +00:00
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
const base = options.base ?? computeBase(matches);
|
|
|
|
const output = options.output ?? base;
|
|
|
|
|
|
|
|
const inputFiles: Array<InputFile> = matches.flatMap(inputFilePath => {
|
|
|
|
if (!inputFilePath.startsWith(base)) {
|
|
|
|
throw new Error(`Input filename ${inputFilePath} falls outside base ${base}`);
|
|
|
|
}
|
|
|
|
const relPath = inputFilePath.slice(base.length);
|
|
|
|
const outputFilePath = path.join(output, changeExt(relPath, '.ts'));
|
|
|
|
try {
|
2021-03-11 09:56:49 +00:00
|
|
|
const src = fs.readFileSync(inputFilePath, 'utf-8');
|
2021-03-11 13:43:06 +00:00
|
|
|
const schema = readSchema(src, { name: inputFilePath });
|
|
|
|
const schemaPath = relPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
|
|
|
|
return [{ inputFilePath, outputFilePath, schemaPath, schema }];
|
|
|
|
} catch (e) {
|
|
|
|
failures.push([inputFilePath, e]);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
});
|
2021-03-11 09:56:49 +00:00
|
|
|
|
2021-03-11 13:43:06 +00:00
|
|
|
inputFiles.forEach(c => {
|
|
|
|
const env: M.Environment = inputFiles.map(cc => ({
|
|
|
|
schema: cc.schema,
|
|
|
|
schemaModulePath: cc.schemaPath,
|
|
|
|
typescriptModulePath: modulePathTo(c.outputFilePath, cc.outputFilePath),
|
|
|
|
}));
|
|
|
|
fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true });
|
|
|
|
try {
|
2021-03-11 09:56:49 +00:00
|
|
|
fs.writeFileSync(c.outputFilePath, compile(env, c.schema, options.core), 'utf-8');
|
2021-03-11 13:43:06 +00:00
|
|
|
} catch (e) {
|
|
|
|
failures.push([c.inputFilePath, e]);
|
|
|
|
}
|
2021-03-11 09:56:49 +00:00
|
|
|
});
|
2021-03-11 13:43:06 +00:00
|
|
|
|
|
|
|
for (const [inputFile, err] of failures) {
|
|
|
|
if (err instanceof SchemaSyntaxError) {
|
|
|
|
console.log(chalk.blueBright(formatPosition(err.pos ?? inputFile)) + ': ' + err.message);
|
|
|
|
} else {
|
|
|
|
console.log(chalk.blueBright(inputFile) + ': ' + err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
|
|
console.log();
|
|
|
|
}
|
|
|
|
|
|
|
|
return { options, inputFiles, failures, base, output };
|
2021-03-11 09:56:49 +00:00
|
|
|
}
|
2021-03-09 18:29:31 +00:00
|
|
|
|
|
|
|
export function main(argv: Array<string>) {
|
2021-03-11 09:56:49 +00:00
|
|
|
const options: CommandLineArguments = yargs(argv)
|
|
|
|
.command('$0 <input>',
|
|
|
|
'Compile Preserves schema definitions to TypeScript',
|
|
|
|
yargs => yargs
|
|
|
|
.positional('input', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Input filename or glob',
|
|
|
|
demandOption: true,
|
|
|
|
})
|
|
|
|
.option('output', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Output directory for sources (default: next to sources)',
|
|
|
|
})
|
|
|
|
.option('base', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Base directory for sources (default: common prefix)',
|
|
|
|
})
|
|
|
|
.option('core', {
|
|
|
|
type: 'string',
|
|
|
|
description: 'Import path for @preserves/core',
|
|
|
|
default: '@preserves/core',
|
2021-03-11 13:43:06 +00:00
|
|
|
})
|
|
|
|
.option('watch', {
|
|
|
|
type: 'boolean',
|
|
|
|
descripion: 'Watch base directory for changes',
|
|
|
|
default: false,
|
2021-03-11 09:56:49 +00:00
|
|
|
}),
|
|
|
|
argv => argv)
|
|
|
|
.argv;
|
2021-03-11 13:43:06 +00:00
|
|
|
options.input = path.normalize(options.input);
|
2021-03-11 09:56:49 +00:00
|
|
|
run(options);
|
2021-03-09 18:29:31 +00:00
|
|
|
}
|