@qwik-custom-elements/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +596 -0
- package/dist/__tests__/generator.test.d.ts +1 -0
- package/dist/__tests__/generator.test.js +1331 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +271 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +183 -0
- package/dist/generator.d.ts +6 -0
- package/dist/generator.js +720 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { parseCliArgs, runCli } from '../cli.js';
|
|
6
|
+
import { ConfigValidationError, loadGeneratorConfig } from '../config.js';
|
|
7
|
+
async function withTempDir(run) {
|
|
8
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'qce-core-'));
|
|
9
|
+
try {
|
|
10
|
+
await run(tempDir);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const validProject = {
|
|
17
|
+
id: 'demo',
|
|
18
|
+
adapter: 'stencil',
|
|
19
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
20
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
21
|
+
outDir: './src/generated',
|
|
22
|
+
};
|
|
23
|
+
const validStencilAdapterOptions = {
|
|
24
|
+
runtime: {
|
|
25
|
+
loaderImport: '@acme/stencil-lib/loader',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
describe('loadGeneratorConfig', () => {
|
|
29
|
+
it('rejects legacy string source contract', async () => {
|
|
30
|
+
await withTempDir(async (tempDir) => {
|
|
31
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
32
|
+
await writeFile(configPath, JSON.stringify({
|
|
33
|
+
projects: [
|
|
34
|
+
{
|
|
35
|
+
id: 'demo',
|
|
36
|
+
adapter: 'stencil',
|
|
37
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
38
|
+
source: './custom-elements.json',
|
|
39
|
+
outDir: './src/generated',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}, null, 2), 'utf8');
|
|
43
|
+
await expect(loadGeneratorConfig({ cwd: tempDir })).rejects.toMatchObject({
|
|
44
|
+
code: 'QCE_CONFIG_INVALID_TYPE',
|
|
45
|
+
message: 'Config field "projects[0].source" must be a source object with a valid "type" discriminator.',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('supports discriminated CEM source object contract', async () => {
|
|
50
|
+
await withTempDir(async (tempDir) => {
|
|
51
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
52
|
+
await writeFile(configPath, JSON.stringify({
|
|
53
|
+
projects: [
|
|
54
|
+
{
|
|
55
|
+
...validProject,
|
|
56
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
}, null, 2), 'utf8');
|
|
60
|
+
const loaded = await loadGeneratorConfig({ cwd: tempDir });
|
|
61
|
+
expect(loaded.config.projects[0].source).toEqual({
|
|
62
|
+
type: 'CEM',
|
|
63
|
+
path: './custom-elements.json',
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it('accepts optional project libraryName field', async () => {
|
|
68
|
+
await withTempDir(async (tempDir) => {
|
|
69
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
70
|
+
await writeFile(configPath, JSON.stringify({
|
|
71
|
+
projects: [
|
|
72
|
+
{
|
|
73
|
+
...validProject,
|
|
74
|
+
libraryName: 'test-stencil-lib',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}, null, 2), 'utf8');
|
|
78
|
+
const loaded = await loadGeneratorConfig({ cwd: tempDir });
|
|
79
|
+
expect(loaded.config.projects[0].libraryName).toBe('test-stencil-lib');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it('keeps adapterOptions opaque during config loading', async () => {
|
|
83
|
+
await withTempDir(async (tempDir) => {
|
|
84
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
85
|
+
await writeFile(configPath, JSON.stringify({
|
|
86
|
+
projects: [
|
|
87
|
+
{
|
|
88
|
+
...validProject,
|
|
89
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
90
|
+
adapterOptions: {
|
|
91
|
+
runtime: {
|
|
92
|
+
hydrateImport: '@acme/stencil-lib/hydrate',
|
|
93
|
+
},
|
|
94
|
+
customFlag: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}, null, 2), 'utf8');
|
|
99
|
+
const loaded = await loadGeneratorConfig({ cwd: tempDir });
|
|
100
|
+
expect(loaded.config.projects[0].adapterOptions).toEqual({
|
|
101
|
+
runtime: {
|
|
102
|
+
hydrateImport: '@acme/stencil-lib/hydrate',
|
|
103
|
+
},
|
|
104
|
+
customFlag: true,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('loads default JSON config path', async () => {
|
|
109
|
+
await withTempDir(async (tempDir) => {
|
|
110
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
111
|
+
await writeFile(configPath, JSON.stringify({ projects: [validProject] }, null, 2), 'utf8');
|
|
112
|
+
const loaded = await loadGeneratorConfig({ cwd: tempDir });
|
|
113
|
+
expect(loaded.configPath).toBe(configPath);
|
|
114
|
+
expect(loaded.config.projects).toHaveLength(1);
|
|
115
|
+
expect(loaded.config.projects[0].id).toBe('demo');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('supports optional JS config variant', async () => {
|
|
119
|
+
await withTempDir(async (tempDir) => {
|
|
120
|
+
const configPath = path.join(tempDir, 'custom.config.js');
|
|
121
|
+
await writeFile(configPath, `export default { projects: [${JSON.stringify(validProject)}] };`, 'utf8');
|
|
122
|
+
const loaded = await loadGeneratorConfig({
|
|
123
|
+
cwd: tempDir,
|
|
124
|
+
configPath: './custom.config.js',
|
|
125
|
+
});
|
|
126
|
+
expect(loaded.config.projects[0].adapter).toBe('stencil');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
it('fails fast with deterministic unknown-field error', async () => {
|
|
130
|
+
await withTempDir(async (tempDir) => {
|
|
131
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
132
|
+
await writeFile(configPath, JSON.stringify({ projects: [validProject], unknownRootField: true }, null, 2), 'utf8');
|
|
133
|
+
await expect(loadGeneratorConfig({ cwd: tempDir })).rejects.toMatchObject({
|
|
134
|
+
code: 'QCE_CONFIG_UNKNOWN_FIELD',
|
|
135
|
+
message: 'Unknown config field "root.unknownRootField".',
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
it('fails fast when required project fields are missing', async () => {
|
|
140
|
+
await withTempDir(async (tempDir) => {
|
|
141
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
142
|
+
await writeFile(configPath, JSON.stringify({
|
|
143
|
+
projects: [
|
|
144
|
+
{
|
|
145
|
+
id: 'demo',
|
|
146
|
+
adapter: 'stencil',
|
|
147
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
148
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
}, null, 2), 'utf8');
|
|
152
|
+
await expect(loadGeneratorConfig({ cwd: tempDir })).rejects.toMatchObject({
|
|
153
|
+
code: 'QCE_CONFIG_MISSING_REQUIRED',
|
|
154
|
+
message: 'Config field "projects[0].outDir" must be a non-empty string.',
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('parseCliArgs', () => {
|
|
160
|
+
it('parses --config and --help', () => {
|
|
161
|
+
expect(parseCliArgs(['--config', './some.config.js', '--help'])).toEqual({
|
|
162
|
+
configPath: './some.config.js',
|
|
163
|
+
projectIds: [],
|
|
164
|
+
parallel: false,
|
|
165
|
+
help: true,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
it('parses repeated --project flags in deterministic order', () => {
|
|
169
|
+
expect(parseCliArgs([
|
|
170
|
+
'--project',
|
|
171
|
+
'demo',
|
|
172
|
+
'--project=stencil',
|
|
173
|
+
'--project',
|
|
174
|
+
'lit',
|
|
175
|
+
])).toEqual({
|
|
176
|
+
configPath: undefined,
|
|
177
|
+
projectIds: ['demo', 'stencil', 'lit'],
|
|
178
|
+
parallel: false,
|
|
179
|
+
help: false,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
it('parses --parallel as an explicit mode flag', () => {
|
|
183
|
+
expect(parseCliArgs(['--project', 'demo', '--parallel'])).toEqual({
|
|
184
|
+
configPath: undefined,
|
|
185
|
+
projectIds: ['demo'],
|
|
186
|
+
parallel: true,
|
|
187
|
+
help: false,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
it('throws deterministic error for unknown args', () => {
|
|
191
|
+
expect(() => parseCliArgs(['--wat'])).toThrowError(ConfigValidationError);
|
|
192
|
+
expect(() => parseCliArgs(['--wat'])).toThrowError('Unknown CLI argument "--wat".');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('runCli', () => {
|
|
196
|
+
afterEach(() => {
|
|
197
|
+
vi.restoreAllMocks();
|
|
198
|
+
});
|
|
199
|
+
it('returns non-zero when one project fails in a multi-project run', async () => {
|
|
200
|
+
await withTempDir(async (tempDir) => {
|
|
201
|
+
const previousCwd = process.cwd();
|
|
202
|
+
process.chdir(tempDir);
|
|
203
|
+
try {
|
|
204
|
+
const validSourcePath = './custom-elements-valid.json';
|
|
205
|
+
const missingSourcePath = './custom-elements-missing.json';
|
|
206
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
207
|
+
await writeFile(validSourcePath, JSON.stringify({
|
|
208
|
+
modules: [{ declarations: [{ tagName: 'app-root' }] }],
|
|
209
|
+
}), 'utf8');
|
|
210
|
+
await writeFile(configPath, JSON.stringify({
|
|
211
|
+
projects: [
|
|
212
|
+
{
|
|
213
|
+
id: 'a-project',
|
|
214
|
+
adapter: 'stencil',
|
|
215
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
216
|
+
source: { type: 'CEM', path: validSourcePath },
|
|
217
|
+
outDir: './generated-a',
|
|
218
|
+
adapterOptions: validStencilAdapterOptions,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'b-project',
|
|
222
|
+
adapter: 'stencil',
|
|
223
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
224
|
+
source: { type: 'CEM', path: missingSourcePath },
|
|
225
|
+
outDir: './generated-b',
|
|
226
|
+
adapterOptions: validStencilAdapterOptions,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
}, null, 2), 'utf8');
|
|
230
|
+
const stderrSpy = vi
|
|
231
|
+
.spyOn(process.stderr, 'write')
|
|
232
|
+
.mockImplementation(() => true);
|
|
233
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
234
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
235
|
+
expect(exitCode).toBe(1);
|
|
236
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('QCE_CEM_READ_FAILED'));
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
process.chdir(previousCwd);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
it('writes run summary artifact with observed error codes on failed runs', async () => {
|
|
244
|
+
await withTempDir(async (tempDir) => {
|
|
245
|
+
const previousCwd = process.cwd();
|
|
246
|
+
process.chdir(tempDir);
|
|
247
|
+
try {
|
|
248
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
249
|
+
const summaryPath = path.join(tempDir, 'summary', 'run-summary.json');
|
|
250
|
+
await writeFile(configPath, JSON.stringify({
|
|
251
|
+
dryRun: true,
|
|
252
|
+
summaryPath: './summary/run-summary.json',
|
|
253
|
+
projects: [
|
|
254
|
+
{
|
|
255
|
+
id: 'broken-project',
|
|
256
|
+
adapter: 'stencil',
|
|
257
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
258
|
+
source: {
|
|
259
|
+
type: 'CEM',
|
|
260
|
+
path: './custom-elements-missing.json',
|
|
261
|
+
},
|
|
262
|
+
outDir: './generated/broken',
|
|
263
|
+
adapterOptions: validStencilAdapterOptions,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
}, null, 2), 'utf8');
|
|
267
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
268
|
+
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
269
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
270
|
+
const summary = JSON.parse(await readFile(summaryPath, 'utf8'));
|
|
271
|
+
expect(exitCode).toBe(1);
|
|
272
|
+
expect(summary.dryRun).toBe(true);
|
|
273
|
+
expect(summary.projects).toHaveLength(1);
|
|
274
|
+
expect(summary.projects[0].projectId).toBe('broken-project');
|
|
275
|
+
expect(summary.projects[0].status).toBe('failed');
|
|
276
|
+
expect(summary.projects[0].durationMs).toBeGreaterThanOrEqual(0);
|
|
277
|
+
expect(summary.projects[0].generatedIndexPath).toBe('');
|
|
278
|
+
expect(summary.projects[0].ssrCapabilities).toEqual({
|
|
279
|
+
available: false,
|
|
280
|
+
supportsSsrProbe: false,
|
|
281
|
+
ssrRuntimeSubpath: null,
|
|
282
|
+
});
|
|
283
|
+
expect(summary.projects[0].observedErrorCodes).toEqual([
|
|
284
|
+
'QCE_CEM_READ_FAILED',
|
|
285
|
+
]);
|
|
286
|
+
expect(summary.observedErrorCodes).toEqual(['QCE_CEM_READ_FAILED']);
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
process.chdir(previousCwd);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
it('emits deterministic warning when adapter SSR falls back to CEM-only', async () => {
|
|
294
|
+
await withTempDir(async (tempDir) => {
|
|
295
|
+
const previousCwd = process.cwd();
|
|
296
|
+
process.chdir(tempDir);
|
|
297
|
+
try {
|
|
298
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
299
|
+
await writeFile('./custom-elements.json', JSON.stringify({
|
|
300
|
+
modules: [{ declarations: [{ tagName: 'app-root' }] }],
|
|
301
|
+
}), 'utf8');
|
|
302
|
+
await writeFile('./adapter-unsupported.mjs', [
|
|
303
|
+
'export async function probeSSR() {',
|
|
304
|
+
' return { available: false };',
|
|
305
|
+
'}',
|
|
306
|
+
'',
|
|
307
|
+
'export function createGeneratedOutput() {',
|
|
308
|
+
' return [];',
|
|
309
|
+
'}',
|
|
310
|
+
'',
|
|
311
|
+
].join('\n'), 'utf8');
|
|
312
|
+
await writeFile(configPath, JSON.stringify({
|
|
313
|
+
dryRun: true,
|
|
314
|
+
projects: [
|
|
315
|
+
{
|
|
316
|
+
id: 'demo',
|
|
317
|
+
adapter: 'stencil',
|
|
318
|
+
adapterPackage: './adapter-unsupported.mjs',
|
|
319
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
320
|
+
outDir: './generated/demo',
|
|
321
|
+
adapterOptions: validStencilAdapterOptions,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
}, null, 2), 'utf8');
|
|
325
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
326
|
+
const stderrSpy = vi
|
|
327
|
+
.spyOn(process.stderr, 'write')
|
|
328
|
+
.mockImplementation(() => true);
|
|
329
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
330
|
+
expect(exitCode).toBe(0);
|
|
331
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('QCE_SSR_UNSUPPORTED_FALLBACK'));
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
process.chdir(previousCwd);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
it('emits deterministic loader-only success diagnostics when hydrate runtime is unavailable', async () => {
|
|
339
|
+
await withTempDir(async (tempDir) => {
|
|
340
|
+
const previousCwd = process.cwd();
|
|
341
|
+
process.chdir(tempDir);
|
|
342
|
+
try {
|
|
343
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
344
|
+
const summaryPath = path.join(tempDir, 'generated-run-summary.json');
|
|
345
|
+
await writeFile('./custom-elements.json', JSON.stringify({
|
|
346
|
+
modules: [{ declarations: [{ tagName: 'app-root' }] }],
|
|
347
|
+
}), 'utf8');
|
|
348
|
+
await writeFile('./adapter-loader-only.mjs', [
|
|
349
|
+
'export async function resolveRuntimeImports() {',
|
|
350
|
+
' return {',
|
|
351
|
+
" runtimeImports: { loaderImport: '@acme/stencil-lib/loader' },",
|
|
352
|
+
" observedErrorCodes: ['QCE_STENCIL_RUNTIME_HYDRATE_RESOLVE_FAILED'],",
|
|
353
|
+
' clientOnlyMode: true,',
|
|
354
|
+
' };',
|
|
355
|
+
'}',
|
|
356
|
+
'',
|
|
357
|
+
'export async function probeSSR() {',
|
|
358
|
+
' return { available: false };',
|
|
359
|
+
'}',
|
|
360
|
+
'',
|
|
361
|
+
'export function createGeneratedOutput() {',
|
|
362
|
+
' return [{ relativePath: "runtime-csr.generated.ts", content: "export {};" }];',
|
|
363
|
+
'}',
|
|
364
|
+
'',
|
|
365
|
+
].join('\n'), 'utf8');
|
|
366
|
+
await writeFile(configPath, JSON.stringify({
|
|
367
|
+
dryRun: true,
|
|
368
|
+
projects: [
|
|
369
|
+
{
|
|
370
|
+
id: 'loader-only-demo',
|
|
371
|
+
adapter: 'stencil',
|
|
372
|
+
adapterPackage: './adapter-loader-only.mjs',
|
|
373
|
+
source: { type: 'CEM', path: './custom-elements.json' },
|
|
374
|
+
outDir: './generated/demo',
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
}, null, 2), 'utf8');
|
|
378
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
379
|
+
const stderrSpy = vi
|
|
380
|
+
.spyOn(process.stderr, 'write')
|
|
381
|
+
.mockImplementation(() => true);
|
|
382
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
383
|
+
const summary = JSON.parse(await readFile(summaryPath, 'utf8'));
|
|
384
|
+
expect(exitCode).toBe(0);
|
|
385
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('SSR is unavailable, but client-capable wrapper generation succeeded'));
|
|
386
|
+
expect(summary.projects[0].ssrCapabilities).toEqual({
|
|
387
|
+
available: false,
|
|
388
|
+
supportsSsrProbe: false,
|
|
389
|
+
ssrRuntimeSubpath: null,
|
|
390
|
+
clientOnlyMode: true,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
process.chdir(previousCwd);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
it('prints buffered per-project logs in deterministic id order for parallel runs', async () => {
|
|
399
|
+
await withTempDir(async (tempDir) => {
|
|
400
|
+
const previousCwd = process.cwd();
|
|
401
|
+
process.chdir(tempDir);
|
|
402
|
+
try {
|
|
403
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
404
|
+
await writeFile('./custom-elements-a.json', JSON.stringify({
|
|
405
|
+
modules: [{ declarations: [{ tagName: 'alpha-card' }] }],
|
|
406
|
+
}), 'utf8');
|
|
407
|
+
await writeFile('./custom-elements-z.json', JSON.stringify({
|
|
408
|
+
modules: [{ declarations: [{ tagName: 'zeta-card' }] }],
|
|
409
|
+
}), 'utf8');
|
|
410
|
+
await writeFile(configPath, JSON.stringify({
|
|
411
|
+
parallel: true,
|
|
412
|
+
dryRun: true,
|
|
413
|
+
projects: [
|
|
414
|
+
{
|
|
415
|
+
id: 'z-project',
|
|
416
|
+
adapter: 'stencil',
|
|
417
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
418
|
+
source: { type: 'CEM', path: './custom-elements-z.json' },
|
|
419
|
+
outDir: './generated/z',
|
|
420
|
+
adapterOptions: validStencilAdapterOptions,
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: 'a-project',
|
|
424
|
+
adapter: 'stencil',
|
|
425
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
426
|
+
source: { type: 'CEM', path: './custom-elements-a.json' },
|
|
427
|
+
outDir: './generated/a',
|
|
428
|
+
adapterOptions: validStencilAdapterOptions,
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
}, null, 2), 'utf8');
|
|
432
|
+
const stdoutSpy = vi
|
|
433
|
+
.spyOn(process.stdout, 'write')
|
|
434
|
+
.mockImplementation(() => true);
|
|
435
|
+
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
436
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
437
|
+
expect(exitCode).toBe(0);
|
|
438
|
+
const output = stdoutSpy.mock.calls.map(([chunk]) => String(chunk));
|
|
439
|
+
const aProjectLineIndex = output.findIndex((line) => line.includes('[project:a-project]'));
|
|
440
|
+
const zProjectLineIndex = output.findIndex((line) => line.includes('[project:z-project]'));
|
|
441
|
+
const summaryLineIndex = output.findIndex((line) => line.includes('Generation completed (dry-run)'));
|
|
442
|
+
expect(aProjectLineIndex).toBeGreaterThanOrEqual(0);
|
|
443
|
+
expect(zProjectLineIndex).toBeGreaterThanOrEqual(0);
|
|
444
|
+
expect(aProjectLineIndex).toBeLessThan(zProjectLineIndex);
|
|
445
|
+
expect(summaryLineIndex).toBeGreaterThan(zProjectLineIndex);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
process.chdir(previousCwd);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
it('writes run summary artifact with required baseline fields', async () => {
|
|
453
|
+
await withTempDir(async (tempDir) => {
|
|
454
|
+
const previousCwd = process.cwd();
|
|
455
|
+
process.chdir(tempDir);
|
|
456
|
+
try {
|
|
457
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
458
|
+
const summaryPath = path.join(tempDir, 'generated-run-summary.json');
|
|
459
|
+
await writeFile('./custom-elements-a.json', JSON.stringify({
|
|
460
|
+
modules: [{ declarations: [{ tagName: 'alpha-card' }] }],
|
|
461
|
+
}), 'utf8');
|
|
462
|
+
await writeFile('./custom-elements-z.json', JSON.stringify({
|
|
463
|
+
modules: [{ declarations: [{ tagName: 'zeta-card' }] }],
|
|
464
|
+
}), 'utf8');
|
|
465
|
+
await writeFile(configPath, JSON.stringify({
|
|
466
|
+
dryRun: true,
|
|
467
|
+
projects: [
|
|
468
|
+
{
|
|
469
|
+
id: 'z-project',
|
|
470
|
+
adapter: 'stencil',
|
|
471
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
472
|
+
source: { type: 'CEM', path: './custom-elements-z.json' },
|
|
473
|
+
outDir: './generated/z',
|
|
474
|
+
adapterOptions: validStencilAdapterOptions,
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: 'a-project',
|
|
478
|
+
adapter: 'stencil',
|
|
479
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
480
|
+
source: { type: 'CEM', path: './custom-elements-a.json' },
|
|
481
|
+
outDir: './generated/a',
|
|
482
|
+
adapterOptions: validStencilAdapterOptions,
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
}, null, 2), 'utf8');
|
|
486
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
487
|
+
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
488
|
+
const exitCode = await runCli(['--config', configPath]);
|
|
489
|
+
const summary = JSON.parse(await readFile(summaryPath, 'utf8'));
|
|
490
|
+
expect(exitCode).toBe(0);
|
|
491
|
+
expect(summary.schemaVersion).toBe('1.0.0');
|
|
492
|
+
expect(summary.startedAt).toMatch(/T/);
|
|
493
|
+
expect(summary.finishedAt).toMatch(/T/);
|
|
494
|
+
expect(summary.dryRun).toBe(true);
|
|
495
|
+
expect(summary.observedErrorCodes).toEqual([]);
|
|
496
|
+
expect(summary.projects.map((project) => project.projectId)).toEqual([
|
|
497
|
+
'a-project',
|
|
498
|
+
'z-project',
|
|
499
|
+
]);
|
|
500
|
+
expect(summary.projects[0].status).toBe('success');
|
|
501
|
+
expect(summary.projects[1].status).toBe('success');
|
|
502
|
+
expect(summary.projects[0].durationMs).toBeGreaterThanOrEqual(0);
|
|
503
|
+
expect(summary.projects[1].durationMs).toBeGreaterThanOrEqual(0);
|
|
504
|
+
expect(summary.projects[0].generatedIndexPath).toBe(path.join(tempDir, 'generated', 'a', 'index.ts'));
|
|
505
|
+
expect(summary.projects[1].generatedIndexPath).toBe(path.join(tempDir, 'generated', 'z', 'index.ts'));
|
|
506
|
+
expect(summary.projects[0].observedErrorCodes).toEqual([
|
|
507
|
+
'QCE_SSR_UNSUPPORTED_FALLBACK',
|
|
508
|
+
]);
|
|
509
|
+
expect(summary.projects[1].observedErrorCodes).toEqual([
|
|
510
|
+
'QCE_SSR_UNSUPPORTED_FALLBACK',
|
|
511
|
+
]);
|
|
512
|
+
expect(summary.projects[0].ssrCapabilities).toEqual({
|
|
513
|
+
available: false,
|
|
514
|
+
supportsSsrProbe: true,
|
|
515
|
+
ssrRuntimeSubpath: './ssr',
|
|
516
|
+
});
|
|
517
|
+
expect(summary.projects[1].ssrCapabilities).toEqual({
|
|
518
|
+
available: false,
|
|
519
|
+
supportsSsrProbe: true,
|
|
520
|
+
ssrRuntimeSubpath: './ssr',
|
|
521
|
+
});
|
|
522
|
+
expect(summary.projects[0].resolvedCoreVersion).toBe('1.0.0');
|
|
523
|
+
expect(summary.projects[1].resolvedCoreVersion).toBe('1.0.0');
|
|
524
|
+
expect(summary.projects[0].resolvedAdapterVersion).toBe('1.0.0');
|
|
525
|
+
expect(summary.projects[1].resolvedAdapterVersion).toBe('1.0.0');
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
process.chdir(previousCwd);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
it('marks non-targeted projects as skipped in summary output', async () => {
|
|
533
|
+
await withTempDir(async (tempDir) => {
|
|
534
|
+
const previousCwd = process.cwd();
|
|
535
|
+
process.chdir(tempDir);
|
|
536
|
+
try {
|
|
537
|
+
const configPath = path.join(tempDir, 'qwik-custom-elements.config.json');
|
|
538
|
+
const summaryPath = path.join(tempDir, 'generated-run-summary.json');
|
|
539
|
+
await writeFile('./custom-elements-a.json', JSON.stringify({
|
|
540
|
+
modules: [{ declarations: [{ tagName: 'alpha-card' }] }],
|
|
541
|
+
}), 'utf8');
|
|
542
|
+
await writeFile('./custom-elements-z.json', JSON.stringify({
|
|
543
|
+
modules: [{ declarations: [{ tagName: 'zeta-card' }] }],
|
|
544
|
+
}), 'utf8');
|
|
545
|
+
await writeFile(configPath, JSON.stringify({
|
|
546
|
+
dryRun: true,
|
|
547
|
+
projects: [
|
|
548
|
+
{
|
|
549
|
+
id: 'z-project',
|
|
550
|
+
adapter: 'stencil',
|
|
551
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
552
|
+
source: { type: 'CEM', path: './custom-elements-z.json' },
|
|
553
|
+
outDir: './generated/z',
|
|
554
|
+
adapterOptions: validStencilAdapterOptions,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
id: 'a-project',
|
|
558
|
+
adapter: 'stencil',
|
|
559
|
+
adapterPackage: '@qwik-custom-elements/adapter-stencil',
|
|
560
|
+
source: { type: 'CEM', path: './custom-elements-a.json' },
|
|
561
|
+
outDir: './generated/a',
|
|
562
|
+
adapterOptions: validStencilAdapterOptions,
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
}, null, 2), 'utf8');
|
|
566
|
+
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
567
|
+
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
568
|
+
const exitCode = await runCli([
|
|
569
|
+
'--config',
|
|
570
|
+
configPath,
|
|
571
|
+
'--project',
|
|
572
|
+
'a-project',
|
|
573
|
+
]);
|
|
574
|
+
const summary = JSON.parse(await readFile(summaryPath, 'utf8'));
|
|
575
|
+
expect(exitCode).toBe(0);
|
|
576
|
+
expect(summary.projects.map((project) => project.projectId)).toEqual([
|
|
577
|
+
'a-project',
|
|
578
|
+
'z-project',
|
|
579
|
+
]);
|
|
580
|
+
expect(summary.projects[0].status).toBe('success');
|
|
581
|
+
expect(summary.projects[1].status).toBe('skipped');
|
|
582
|
+
expect(summary.projects[1].durationMs).toBe(0);
|
|
583
|
+
expect(summary.projects[1].generatedIndexPath).toBe(path.join(tempDir, 'generated', 'z', 'index.ts'));
|
|
584
|
+
expect(summary.projects[1].ssrCapabilities).toEqual({
|
|
585
|
+
available: false,
|
|
586
|
+
supportsSsrProbe: false,
|
|
587
|
+
ssrRuntimeSubpath: null,
|
|
588
|
+
});
|
|
589
|
+
expect(summary.projects[1].observedErrorCodes).toEqual([]);
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
process.chdir(previousCwd);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|