1 | import { lifecycleHooks } from './hooks.js';
|
2 | import { executeCodegen } from './codegen.js';
|
3 | import { createWatcher } from './utils/watcher.js';
|
4 | import { fileExists, readFile, writeFile, unlinkFile } from './utils/file-system.js';
|
5 | import mkdirp from 'mkdirp';
|
6 | import { dirname, join, isAbsolute } from 'path';
|
7 | import { debugLog } from './utils/debugging.js';
|
8 | import { ensureContext } from './config.js';
|
9 | import { createHash } from 'crypto';
|
10 | const hash = (content) => createHash('sha1').update(content).digest('base64');
|
11 | export 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 |
|
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;
|
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 |
|
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 | }
|
95 | function 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 | }
|
107 | function isConfiguredOutput(output) {
|
108 | return typeof output.plugins !== 'undefined';
|
109 | }
|