/
generate.ts
120 lines (113 loc) · 4.09 KB
/
generate.ts
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
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import {Analyzer} from '@lit-labs/analyzer';
import {AbsolutePath} from '@lit-labs/analyzer/lib/paths.js';
import {FileTree, writeFileTree} from '@lit-labs/gen-utils/lib/file-utils.js';
import {LitCli} from '../lit-cli.js';
import * as path from 'path';
import {Command, ResolvedCommand} from '../command.js';
import {Package} from '@lit-labs/analyzer/lib/model.js';
const reactCommand: Command = {
name: 'react',
description: 'Generate React wrapper for a LitElement',
kind: 'reference',
installFrom: '@lit-labs/gen-wrapper-react',
importSpecifier: '@lit-labs/gen-wrapper-react/index.js',
};
const vueCommand: Command = {
name: 'vue',
description: 'Generate Vue wrapper for a LitElement',
kind: 'reference',
installFrom: '@lit-labs/gen-wrapper-vue',
importSpecifier: '@lit-labs/gen-wrapper-vue/index.js',
};
// A generate command has a generate method instead of a run method.
interface GenerateCommand extends Omit<ResolvedCommand, 'run'> {
generate(options: {analysis: Package}, console: Console): Promise<FileTree>;
}
const frameworkCommands = {
react: reactCommand,
vue: vueCommand,
};
type FrameworkName = keyof typeof frameworkCommands;
export const run = async (
{
cli,
packages,
frameworks: frameworkNames,
outDir,
}: {packages: string[]; frameworks: string[]; outDir: string; cli: LitCli},
console: Console
) => {
for (const packageRoot of packages) {
// Ensure separators in input paths are normalized and resolved to absolute
const root = path.normalize(path.resolve(packageRoot)) as AbsolutePath;
const out = path.normalize(path.resolve(outDir)) as AbsolutePath;
const analyzer = new Analyzer(root);
const analysis = analyzer.analyzePackage();
if (!analysis.packageJson.name) {
throw new Error(
`Package at '${packageRoot}' did not have a name in package.json. The 'gen' command requires that packages have a name.`
);
}
const generatorReferences = [];
for (const name of frameworkNames as FrameworkName[]) {
const framework = frameworkCommands[name];
if (framework == null) {
throw new Error(`No generator exists for framework '${framework}'`);
}
generatorReferences.push(framework);
}
// Optimistically try to import all generators in parallel.
// If any aren't installed we need to ask for permission to install it
// below, but in the common happy case this will do all the loading work.
await Promise.all(
generatorReferences.map(async (ref) => {
const specifier = cli.resolveImportForReference(ref);
if (specifier != null) {
await import(specifier);
}
})
);
// Now go through one by one and install any as necessary.
// This must be one by one in case we need to ask for permission.
const generators: GenerateCommand[] = [];
for (const reference of generatorReferences) {
const resolved = await cli.resolveCommandAndMaybeInstallNeededDeps(
reference
);
if (resolved == null) {
throw new Error(`Could not load generator for ${reference.name}`);
}
generators.push(resolved as unknown as GenerateCommand);
}
const options = {
analysis,
};
const results = await Promise.allSettled(
generators.map(async (generator) => {
// TODO(kschaaf): Add try/catches around each of these operations and
// throw more contextural errors
await writeFileTree(out, await generator.generate(options, console));
})
);
// `allSettled` will swallow errors, so we need to filter them out of
// the results and throw a new error up the stack describing all the errors
// that happened
const errors = results
.map((r, i) =>
r.status === 'rejected'
? `Error generating '${generators[i].name}' wrapper for package '${packageRoot}': ` +
(r.reason as Error).stack ?? r.reason
: ''
)
.filter((e) => e)
.join('\n');
if (errors.length > 0) {
throw new Error(errors);
}
}
};