UNPKG

5.27 kBJavaScriptView Raw
1import { lifecycleHooks } from './hooks.js';
2import { executeCodegen } from './codegen.js';
3import { createWatcher } from './utils/watcher.js';
4import { fileExists, readFile, writeFile, unlinkFile } from './utils/file-system.js';
5import mkdirp from 'mkdirp';
6import { dirname, join, isAbsolute } from 'path';
7import { debugLog } from './utils/debugging.js';
8import { ensureContext } from './config.js';
9import { createHash } from 'crypto';
10const hash = (content) => createHash('sha1').update(content).digest('base64');
11export async function generate(input, saveToFile = true) {
12 const context = ensureContext(input);
13 const config = context.getConfig();
14 await context.profiler.run(() => lifecycleHooks(config.hooks).afterStart(), 'Lifecycle: afterStart');
15 let previouslyGeneratedFilenames = [];
16 function removeStaleFiles(config, generationResult) {
17 const filenames = generationResult.map(o => o.filename);
18 // find stale files from previous build which are not present in current build
19 const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
20 staleFilenames.forEach(filename => {
21 if (shouldOverwrite(config, filename)) {
22 return unlinkFile(filename, err => {
23 const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
24 if (err) {
25 debugLog(`Cannot remove stale file: ${prettyFilename}\n${err}`);
26 }
27 else {
28 debugLog(`Removed stale file: ${prettyFilename}`);
29 }
30 });
31 }
32 });
33 previouslyGeneratedFilenames = filenames;
34 }
35 const recentOutputHash = new Map();
36 async function writeOutput(generationResult) {
37 if (!saveToFile) {
38 return generationResult;
39 }
40 if (config.watch) {
41 removeStaleFiles(config, generationResult);
42 }
43 await context.profiler.run(async () => {
44 await lifecycleHooks(config.hooks).beforeAllFileWrite(generationResult.map(r => r.filename));
45 }, 'Lifecycle: beforeAllFileWrite');
46 await context.profiler.run(() => Promise.all(generationResult.map(async (result) => {
47 const exists = await fileExists(result.filename);
48 if (!shouldOverwrite(config, result.filename) && exists) {
49 return;
50 }
51 const content = result.content || '';
52 const currentHash = hash(content);
53 let previousHash = recentOutputHash.get(result.filename);
54 if (!previousHash && exists) {
55 previousHash = hash(await readFile(result.filename));
56 }
57 if (previousHash && currentHash === previousHash) {
58 debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
59 return;
60 }
61 else if (context.checkMode) {
62 context.checkModeStaleFiles.push(result.filename);
63 return; // skip updating file in dry mode
64 }
65 if (content.length === 0) {
66 return;
67 }
68 recentOutputHash.set(result.filename, currentHash);
69 const basedir = dirname(result.filename);
70 await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
71 await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
72 await mkdirp(basedir);
73 const absolutePath = isAbsolute(result.filename)
74 ? result.filename
75 : join(input.cwd || process.cwd(), result.filename);
76 await writeFile(absolutePath, result.content);
77 await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
78 await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
79 })), 'Write files');
80 await context.profiler.run(() => lifecycleHooks(config.hooks).afterAllFileWrite(generationResult.map(r => r.filename)), 'Lifecycle: afterAllFileWrite');
81 return generationResult;
82 }
83 // watch mode
84 if (config.watch) {
85 return createWatcher(context, writeOutput);
86 }
87 const outputFiles = await context.profiler.run(() => executeCodegen(context), 'executeCodegen');
88 await context.profiler.run(() => writeOutput(outputFiles), 'writeOutput');
89 await context.profiler.run(() => lifecycleHooks(config.hooks).beforeDone(), 'Lifecycle: beforeDone');
90 if (context.profilerOutput) {
91 await writeFile(join(context.cwd, context.profilerOutput), JSON.stringify(context.profiler.collect()));
92 }
93 return outputFiles;
94}
95function shouldOverwrite(config, outputPath) {
96 const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
97 const outputConfig = config.generates[outputPath];
98 if (!outputConfig) {
99 debugLog(`Couldn't find a config of ${outputPath}`);
100 return globalValue;
101 }
102 if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
103 return outputConfig.overwrite;
104 }
105 return globalValue;
106}
107function isConfiguredOutput(output) {
108 return typeof output.plugins !== 'undefined';
109}