@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.
@@ -0,0 +1,720 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
+ export class GenerationError extends Error {
7
+ code;
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = 'GenerationError';
11
+ this.code = code;
12
+ }
13
+ }
14
+ const SSR_UNSUPPORTED_FALLBACK_CODE = 'QCE_SSR_UNSUPPORTED_FALLBACK';
15
+ const PACKAGE_NAME_CEM_DISCOVERY_CANDIDATES = [
16
+ 'custom-elements.json',
17
+ path.join('dist', 'custom-elements.json'),
18
+ ];
19
+ const require = createRequire(import.meta.url);
20
+ export async function generateFromConfig(config, options = {}) {
21
+ const cwd = options.cwd ?? process.cwd();
22
+ const requestedProjectIds = options.targetProjectIds ?? [];
23
+ const requestedProjectIdSet = new Set(requestedProjectIds);
24
+ const sortedProjects = [...config.projects].sort((a, b) => a.id.localeCompare(b.id));
25
+ for (const requestedProjectId of requestedProjectIds) {
26
+ if (!config.projects.some((project) => project.id === requestedProjectId)) {
27
+ throw new GenerationError('QCE_PROJECT_TARGET_UNKNOWN', `Unknown project id "${requestedProjectId}" requested via CLI targeting.`);
28
+ }
29
+ }
30
+ const filteredProjects = requestedProjectIdSet.size === 0
31
+ ? sortedProjects
32
+ : sortedProjects.filter((project) => requestedProjectIdSet.has(project.id));
33
+ validateProjectOutputSafety(filteredProjects, cwd);
34
+ const projectResults = config.parallel === true
35
+ ? await generateProjectsInParallel(filteredProjects, cwd, config.dryRun === true)
36
+ : await generateProjectsSequentially(filteredProjects, cwd, config.dryRun === true);
37
+ const skippedProjectResults = requestedProjectIdSet.size === 0
38
+ ? []
39
+ : sortedProjects
40
+ .filter((project) => !requestedProjectIdSet.has(project.id))
41
+ .map((project) => createSkippedProjectResult(project, cwd));
42
+ return {
43
+ dryRun: config.dryRun === true,
44
+ projects: [...projectResults, ...skippedProjectResults].sort((a, b) => a.projectId.localeCompare(b.projectId)),
45
+ };
46
+ }
47
+ async function generateProjectsSequentially(projects, cwd, dryRun) {
48
+ const projectResults = [];
49
+ for (const project of projects) {
50
+ const result = await generateProject(project, cwd, dryRun);
51
+ projectResults.push(result);
52
+ }
53
+ return projectResults;
54
+ }
55
+ async function generateProjectsInParallel(projects, cwd, dryRun) {
56
+ const settledResults = await Promise.allSettled(projects.map((project) => generateProject(project, cwd, dryRun)));
57
+ const projectResults = [];
58
+ const failures = [];
59
+ for (let index = 0; index < settledResults.length; index += 1) {
60
+ const settledResult = settledResults[index];
61
+ const project = projects[index];
62
+ if (settledResult.status === 'fulfilled') {
63
+ projectResults.push(settledResult.value);
64
+ continue;
65
+ }
66
+ failures.push({
67
+ projectId: project.id,
68
+ error: settledResult.reason,
69
+ });
70
+ }
71
+ if (failures.length > 0) {
72
+ const details = failures
73
+ .map(({ projectId, error }) => {
74
+ const errorCode = error instanceof GenerationError ? error.code : 'QCE_UNEXPECTED';
75
+ const errorMessage = toErrorMessage(error);
76
+ return `${projectId} (${errorCode}): ${errorMessage}`;
77
+ })
78
+ .join(' | ');
79
+ throw new GenerationError('QCE_PARALLEL_PROJECT_FAILURES', `Parallel generation failed for ${failures.length} project(s): ${details}`);
80
+ }
81
+ return projectResults;
82
+ }
83
+ async function generateProject(project, cwd, dryRun) {
84
+ const startedAtMs = Date.now();
85
+ const adapterModule = await loadAdapterModule(project.adapterPackage, cwd);
86
+ validateAdapterSourceCompatibility(project, adapterModule);
87
+ await validateAdapterProject(project, adapterModule);
88
+ const runtimeImportResult = await resolveAdapterRuntimeImports(project, adapterModule, cwd);
89
+ const { runtimeImports } = runtimeImportResult;
90
+ const sourcePath = resolveProjectSourcePath(project.id, project.source, cwd);
91
+ const outDirPath = path.resolve(cwd, project.outDir);
92
+ const componentDefinitions = await readComponentDefinitionsFromCem(sourcePath);
93
+ const augmentedComponentDefinitions = await augmentAdapterComponentDefinitions(componentDefinitions, adapterModule, runtimeImports, outDirPath, cwd);
94
+ const componentTags = augmentedComponentDefinitions.map((componentDefinition) => componentDefinition.tagName);
95
+ const adapterSsrCapabilities = resolveAdapterSsrCapabilities(adapterModule);
96
+ const ssrProbe = await probeProjectSsrAvailability(project, adapterModule, runtimeImports);
97
+ const adapterPlannedWrites = await createAdapterPlannedWrites(project, outDirPath, augmentedComponentDefinitions, adapterModule, runtimeImports, ssrProbe.available);
98
+ const plannedWrites = createPlannedWrites(project.id, outDirPath, augmentedComponentDefinitions, adapterModule, ssrProbe.available, adapterPlannedWrites);
99
+ const observedErrorCodes = [...runtimeImportResult.observedErrorCodes];
100
+ if (!ssrProbe.available) {
101
+ observedErrorCodes.push(SSR_UNSUPPORTED_FALLBACK_CODE);
102
+ }
103
+ observedErrorCodes.sort((a, b) => a.localeCompare(b));
104
+ if (!dryRun) {
105
+ await mkdir(outDirPath, { recursive: true });
106
+ for (const plannedWrite of plannedWrites) {
107
+ await writeFile(plannedWrite.path, plannedWrite.content, 'utf8');
108
+ }
109
+ }
110
+ const hasClientOnlySuccessSignal = runtimeImportResult.clientOnlyMode === true &&
111
+ !ssrProbe.available &&
112
+ plannedWrites.length > 0;
113
+ const generatedIndexPath = path.join(outDirPath, 'index.ts');
114
+ const durationMs = Date.now() - startedAtMs;
115
+ return {
116
+ projectId: project.id,
117
+ status: 'success',
118
+ durationMs,
119
+ adapterPackage: project.adapterPackage,
120
+ sourcePath,
121
+ outDirPath,
122
+ generatedIndexPath,
123
+ componentTags,
124
+ plannedWrites,
125
+ wroteFiles: !dryRun,
126
+ ssrCapabilities: {
127
+ available: ssrProbe.available,
128
+ supportsSsrProbe: adapterSsrCapabilities.supportsSsrProbe,
129
+ ssrRuntimeSubpath: adapterSsrCapabilities.ssrRuntimeSubpath,
130
+ ...(hasClientOnlySuccessSignal ? { clientOnlyMode: true } : {}),
131
+ },
132
+ observedErrorCodes,
133
+ };
134
+ }
135
+ function resolveAdapterSsrCapabilities(adapterModule) {
136
+ const metadata = adapterModule != null && typeof adapterModule.metadata === 'object'
137
+ ? adapterModule.metadata
138
+ : undefined;
139
+ const supportsSsrProbe = metadata?.supportsSsrProbe === true;
140
+ const ssrRuntimeSubpath = typeof metadata?.ssrRuntimeSubpath === 'string'
141
+ ? metadata.ssrRuntimeSubpath
142
+ : null;
143
+ return {
144
+ supportsSsrProbe,
145
+ ssrRuntimeSubpath,
146
+ };
147
+ }
148
+ async function augmentAdapterComponentDefinitions(componentDefinitions, adapterModule, runtimeImports, outDirPath, cwd) {
149
+ const augmentFn = adapterModule != null &&
150
+ typeof adapterModule.augmentComponentDefinitions === 'function'
151
+ ? adapterModule.augmentComponentDefinitions
152
+ : undefined;
153
+ if (augmentFn == null) {
154
+ return componentDefinitions;
155
+ }
156
+ const resolvedRuntimeImports = resolveProbeRuntimeImports(runtimeImports, outDirPath, cwd);
157
+ const result = (await augmentFn({
158
+ componentDefinitions,
159
+ runtimeImports: resolvedRuntimeImports,
160
+ }));
161
+ if (!Array.isArray(result)) {
162
+ return componentDefinitions;
163
+ }
164
+ return result;
165
+ }
166
+ function resolveProbeRuntimeImports(runtimeImports, outDirPath, cwd) {
167
+ if (runtimeImports == null)
168
+ return undefined;
169
+ const hydrateImport = runtimeImports.hydrateImport;
170
+ if (typeof hydrateImport !== 'string' || hydrateImport.trim() === '') {
171
+ return runtimeImports;
172
+ }
173
+ const resolved = tryResolveSpecifier(hydrateImport, outDirPath, cwd);
174
+ if (resolved === hydrateImport)
175
+ return runtimeImports;
176
+ return { ...runtimeImports, hydrateImport: resolved };
177
+ }
178
+ function tryResolveSpecifier(specifier, outDirPath, cwd) {
179
+ if (specifier.startsWith('file://') ||
180
+ specifier.startsWith('.') ||
181
+ path.isAbsolute(specifier)) {
182
+ return specifier;
183
+ }
184
+ for (const dir of [outDirPath, cwd]) {
185
+ try {
186
+ const resolved = createRequire(path.join(dir, '__qce_probe__.cjs')).resolve(specifier);
187
+ return pathToFileURL(resolved).href;
188
+ }
189
+ catch {
190
+ // try next candidate
191
+ }
192
+ }
193
+ return specifier;
194
+ }
195
+ async function probeProjectSsrAvailability(project, adapterModule, runtimeImports) {
196
+ const probeSSR = adapterModule != null && typeof adapterModule.probeSSR === 'function'
197
+ ? adapterModule.probeSSR
198
+ : undefined;
199
+ if (probeSSR == null) {
200
+ return { available: false };
201
+ }
202
+ const probeResult = (await probeSSR({
203
+ projectId: project.id,
204
+ source: project.source,
205
+ adapterOptions: project.adapterOptions ?? {},
206
+ runtimeImports,
207
+ }));
208
+ if (probeResult != null && probeResult.available === true) {
209
+ return { available: true };
210
+ }
211
+ return { available: false };
212
+ }
213
+ async function resolveAdapterRuntimeImports(project, adapterModule, cwd) {
214
+ const resolveRuntimeImports = adapterModule != null &&
215
+ typeof adapterModule.resolveRuntimeImports === 'function'
216
+ ? adapterModule.resolveRuntimeImports
217
+ : undefined;
218
+ if (resolveRuntimeImports == null) {
219
+ return {
220
+ runtimeImports: undefined,
221
+ observedErrorCodes: [],
222
+ clientOnlyMode: false,
223
+ };
224
+ }
225
+ try {
226
+ const resolvedRuntimeImports = await resolveRuntimeImports({
227
+ projectId: project.id,
228
+ cwd,
229
+ runtimeResolution: {
230
+ resolveSourcePackageRoot: (packageName) => resolvePackageRootForProject(project.id, { type: 'PACKAGE_NAME', packageName }, cwd),
231
+ resolveImportSpecifier: (specifier, packageRoot) => resolveRuntimeImportSpecifier(specifier, cwd, packageRoot),
232
+ },
233
+ source: project.source,
234
+ adapterOptions: project.adapterOptions ?? {},
235
+ });
236
+ const observedErrorCodes = extractObservedRuntimeImportErrorCodes(resolvedRuntimeImports);
237
+ const runtimeImports = extractResolvedRuntimeImports(resolvedRuntimeImports);
238
+ const clientOnlyMode = extractClientOnlyModeSignal(resolvedRuntimeImports);
239
+ return {
240
+ runtimeImports,
241
+ observedErrorCodes,
242
+ clientOnlyMode,
243
+ };
244
+ }
245
+ catch (error) {
246
+ if (error instanceof GenerationError) {
247
+ throw error;
248
+ }
249
+ const errorCode = typeof error === 'object' &&
250
+ error !== null &&
251
+ 'code' in error &&
252
+ typeof error.code === 'string'
253
+ ? error.code
254
+ : 'QCE_ADAPTER_RUNTIME_IMPORTS_INVALID';
255
+ throw new GenerationError(errorCode, toErrorMessage(error));
256
+ }
257
+ }
258
+ function extractObservedRuntimeImportErrorCodes(resolvedRuntimeImports) {
259
+ if (resolvedRuntimeImports == null ||
260
+ typeof resolvedRuntimeImports !== 'object' ||
261
+ !('observedErrorCodes' in resolvedRuntimeImports) ||
262
+ !Array.isArray(resolvedRuntimeImports.observedErrorCodes)) {
263
+ return [];
264
+ }
265
+ return resolvedRuntimeImports.observedErrorCodes.filter((errorCode) => typeof errorCode === 'string');
266
+ }
267
+ function extractResolvedRuntimeImports(resolvedRuntimeImports) {
268
+ if (resolvedRuntimeImports == null ||
269
+ typeof resolvedRuntimeImports !== 'object') {
270
+ return undefined;
271
+ }
272
+ if ('runtimeImports' in resolvedRuntimeImports &&
273
+ resolvedRuntimeImports.runtimeImports != null &&
274
+ typeof resolvedRuntimeImports.runtimeImports === 'object') {
275
+ return resolvedRuntimeImports.runtimeImports;
276
+ }
277
+ return resolvedRuntimeImports;
278
+ }
279
+ function extractClientOnlyModeSignal(resolvedRuntimeImports) {
280
+ if (resolvedRuntimeImports == null ||
281
+ typeof resolvedRuntimeImports !== 'object' ||
282
+ !('clientOnlyMode' in resolvedRuntimeImports)) {
283
+ return false;
284
+ }
285
+ return resolvedRuntimeImports.clientOnlyMode === true;
286
+ }
287
+ function validateAdapterSourceCompatibility(project, adapterModule) {
288
+ const metadata = adapterModule != null && typeof adapterModule.metadata === 'object'
289
+ ? adapterModule.metadata
290
+ : undefined;
291
+ const supportedSourceTypes = metadata != null && Array.isArray(metadata.supportedSourceTypes)
292
+ ? metadata.supportedSourceTypes
293
+ : ['CEM'];
294
+ const isSourceSupported = supportedSourceTypes.some((supportedSourceType) => supportedSourceType === project.source.type);
295
+ if (!isSourceSupported) {
296
+ throw new GenerationError('QCE_ADAPTER_SOURCE_INCOMPATIBLE', `Project "${project.id}" source type "${project.source.type}" is not supported by adapter "${project.adapterPackage}".`);
297
+ }
298
+ }
299
+ async function validateAdapterProject(project, adapterModule) {
300
+ const validateProject = adapterModule != null && typeof adapterModule.validateProject === 'function'
301
+ ? adapterModule.validateProject
302
+ : undefined;
303
+ if (validateProject == null) {
304
+ return;
305
+ }
306
+ try {
307
+ await validateProject({
308
+ projectId: project.id,
309
+ source: project.source,
310
+ adapterOptions: project.adapterOptions ?? {},
311
+ });
312
+ }
313
+ catch (error) {
314
+ if (error instanceof GenerationError) {
315
+ throw error;
316
+ }
317
+ const errorCode = typeof error === 'object' &&
318
+ error !== null &&
319
+ 'code' in error &&
320
+ typeof error.code === 'string'
321
+ ? error.code
322
+ : 'QCE_ADAPTER_PROJECT_INVALID';
323
+ throw new GenerationError(errorCode, toErrorMessage(error));
324
+ }
325
+ }
326
+ async function loadAdapterModule(adapterPackage, cwd) {
327
+ if (isRelativeOrAbsoluteSpecifier(adapterPackage)) {
328
+ const resolvedPath = path.isAbsolute(adapterPackage)
329
+ ? adapterPackage
330
+ : path.resolve(cwd, adapterPackage);
331
+ return (await import(pathToFileURL(resolvedPath).href));
332
+ }
333
+ try {
334
+ return (await import(adapterPackage));
335
+ }
336
+ catch (error) {
337
+ const workspaceLocalPath = resolveWorkspaceLocalAdapterPath(adapterPackage);
338
+ if (workspaceLocalPath == null) {
339
+ throw new GenerationError('QCE_ADAPTER_LOAD_FAILED', `Could not load adapter package "${adapterPackage}": ${toErrorMessage(error)}`);
340
+ }
341
+ try {
342
+ return (await import(pathToFileURL(workspaceLocalPath).href));
343
+ }
344
+ catch (workspaceError) {
345
+ throw new GenerationError('QCE_ADAPTER_LOAD_FAILED', `Could not load adapter package "${adapterPackage}": ${toErrorMessage(workspaceError)}`);
346
+ }
347
+ }
348
+ }
349
+ function isRelativeOrAbsoluteSpecifier(specifier) {
350
+ return (specifier.startsWith('.') ||
351
+ specifier.startsWith('/') ||
352
+ path.isAbsolute(specifier));
353
+ }
354
+ function resolveWorkspaceLocalAdapterPath(adapterPackage) {
355
+ if (!adapterPackage.startsWith('@qwik-custom-elements/')) {
356
+ return undefined;
357
+ }
358
+ const [scope, packageDirectoryName, ...subpathSegments] = adapterPackage.split('/');
359
+ if (scope !== '@qwik-custom-elements' || !packageDirectoryName) {
360
+ return undefined;
361
+ }
362
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', packageDirectoryName);
363
+ const packageJsonPath = path.join(packageRoot, 'package.json');
364
+ const exportKey = subpathSegments.length === 0 ? '.' : `./${subpathSegments.join('/')}`;
365
+ try {
366
+ const packageJson = JSON.parse(require('node:fs').readFileSync(packageJsonPath, 'utf8'));
367
+ const exportTarget = resolvePackageExportImportTarget(packageJson.exports?.[exportKey]);
368
+ if (typeof exportTarget === 'string') {
369
+ return path.resolve(packageRoot, exportTarget);
370
+ }
371
+ }
372
+ catch {
373
+ // Fall through to legacy dist-path heuristics.
374
+ }
375
+ const subpath = subpathSegments.length === 0
376
+ ? path.join('dist', 'index.js')
377
+ : path.join('dist', subpathSegments.join('/'));
378
+ const subpathWithExtension = path.extname(subpath) === '' ? `${subpath}.js` : subpath;
379
+ return path.resolve(packageRoot, subpathWithExtension);
380
+ }
381
+ function resolvePackageExportImportTarget(exportValue) {
382
+ if (typeof exportValue === 'string') {
383
+ return exportValue;
384
+ }
385
+ if (exportValue == null || typeof exportValue !== 'object') {
386
+ return undefined;
387
+ }
388
+ const exportRecord = exportValue;
389
+ if (typeof exportRecord.import === 'string') {
390
+ return exportRecord.import;
391
+ }
392
+ if (typeof exportRecord.default === 'string') {
393
+ return exportRecord.default;
394
+ }
395
+ return undefined;
396
+ }
397
+ function createSkippedProjectResult(project, cwd) {
398
+ const outDirPath = path.resolve(cwd, project.outDir);
399
+ return {
400
+ projectId: project.id,
401
+ status: 'skipped',
402
+ durationMs: 0,
403
+ adapterPackage: project.adapterPackage,
404
+ sourcePath: resolveSkippedProjectSourcePath(project.source, cwd),
405
+ outDirPath,
406
+ generatedIndexPath: path.join(outDirPath, 'index.ts'),
407
+ componentTags: [],
408
+ plannedWrites: [],
409
+ wroteFiles: false,
410
+ ssrCapabilities: {
411
+ available: false,
412
+ supportsSsrProbe: false,
413
+ ssrRuntimeSubpath: null,
414
+ },
415
+ observedErrorCodes: [],
416
+ };
417
+ }
418
+ function resolveSkippedProjectSourcePath(source, cwd) {
419
+ if (source.type === 'CEM') {
420
+ return path.resolve(cwd, source.path);
421
+ }
422
+ return source.cemPath == null
423
+ ? `package:${source.packageName}`
424
+ : `package:${source.packageName}#${source.cemPath}`;
425
+ }
426
+ function resolveProjectSourcePath(projectId, source, cwd) {
427
+ if (source.type === 'CEM') {
428
+ return path.resolve(cwd, source.path);
429
+ }
430
+ const packageRoot = resolvePackageRootForProject(projectId, source, cwd);
431
+ if (source.cemPath != null) {
432
+ return resolvePackageNameOverrideCemPath(projectId, source, packageRoot);
433
+ }
434
+ return discoverPackageNameCemPath(projectId, source, packageRoot);
435
+ }
436
+ function resolvePackageRootForProject(projectId, source, cwd) {
437
+ const packageSpecifier = `${source.packageName}/package.json`;
438
+ try {
439
+ const packageJsonPath = require.resolve(packageSpecifier, { paths: [cwd] });
440
+ return path.dirname(packageJsonPath);
441
+ }
442
+ catch (error) {
443
+ throw new GenerationError('QCE_PACKAGE_NAME_RESOLVE_FAILED', `Project "${projectId}" could not resolve source package "${source.packageName}" from ${cwd}: ${toErrorMessage(error)}`);
444
+ }
445
+ }
446
+ function resolvePackageNameOverrideCemPath(projectId, source, packageRoot) {
447
+ const cemPath = source.cemPath;
448
+ if (path.isAbsolute(cemPath)) {
449
+ throw new GenerationError('QCE_PACKAGE_NAME_CEM_PATH_ABSOLUTE', `Project "${projectId}" PACKAGE_NAME source.cemPath must be relative to package root; received absolute path "${cemPath}".`);
450
+ }
451
+ const resolvedCemPath = path.resolve(packageRoot, cemPath);
452
+ const relativeToPackageRoot = path.relative(packageRoot, resolvedCemPath);
453
+ const resolvesOutsidePackageRoot = relativeToPackageRoot === '..' ||
454
+ relativeToPackageRoot.startsWith(`..${path.sep}`) ||
455
+ path.isAbsolute(relativeToPackageRoot);
456
+ if (resolvesOutsidePackageRoot) {
457
+ throw new GenerationError('QCE_PACKAGE_NAME_CEM_PATH_OUTSIDE_PACKAGE', `Project "${projectId}" PACKAGE_NAME source.cemPath resolves outside package root: "${cemPath}".`);
458
+ }
459
+ return resolvedCemPath;
460
+ }
461
+ function discoverPackageNameCemPath(projectId, source, packageRoot) {
462
+ const candidatePaths = PACKAGE_NAME_CEM_DISCOVERY_CANDIDATES.map((candidate) => path.resolve(packageRoot, candidate));
463
+ const existingCandidates = [];
464
+ for (const candidatePath of candidatePaths) {
465
+ if (existsSync(candidatePath)) {
466
+ existingCandidates.push(candidatePath);
467
+ }
468
+ }
469
+ if (existingCandidates.length === 1) {
470
+ return existingCandidates[0];
471
+ }
472
+ if (existingCandidates.length === 0) {
473
+ throw new GenerationError('QCE_PACKAGE_NAME_CEM_NOT_FOUND', `Project "${projectId}" could not discover a CEM file for source package "${source.packageName}". Checked: ${PACKAGE_NAME_CEM_DISCOVERY_CANDIDATES.join(', ')}. Set source.cemPath to the manifest path relative to the package root.`);
474
+ }
475
+ throw new GenerationError('QCE_PACKAGE_NAME_CEM_AMBIGUOUS', `Project "${projectId}" discovered multiple CEM candidates for source package "${source.packageName}": ${existingCandidates.join(', ')}. Set source.cemPath to disambiguate.`);
476
+ }
477
+ function resolveRuntimeImportSpecifier(specifier, cwd, packageRoot) {
478
+ const resolver = specifier.startsWith('.') && packageRoot != null
479
+ ? createRequire(path.join(packageRoot, 'package.json'))
480
+ : createRequire(path.resolve(cwd, '__qce_runtime_resolution__.cjs'));
481
+ return resolver.resolve(specifier);
482
+ }
483
+ function validateProjectOutputSafety(projects, workspaceRoot) {
484
+ const outputDirOwners = new Map();
485
+ for (const project of projects) {
486
+ const resolvedOutDir = path.resolve(workspaceRoot, project.outDir);
487
+ validateResolvedOutDirWithinWorkspace(project, workspaceRoot, resolvedOutDir);
488
+ const existingOwner = outputDirOwners.get(resolvedOutDir);
489
+ if (existingOwner != null) {
490
+ throw new GenerationError('QCE_OUTPUT_PATH_COLLISION', `Projects "${existingOwner}" and "${project.id}" resolve to the same output directory: ${resolvedOutDir}`);
491
+ }
492
+ outputDirOwners.set(resolvedOutDir, project.id);
493
+ }
494
+ }
495
+ function validateResolvedOutDirWithinWorkspace(project, workspaceRoot, resolvedOutDir) {
496
+ const relativeToWorkspace = path.relative(workspaceRoot, resolvedOutDir);
497
+ const resolvesOutsideWorkspace = relativeToWorkspace === '..' ||
498
+ relativeToWorkspace.startsWith(`..${path.sep}`) ||
499
+ path.isAbsolute(relativeToWorkspace);
500
+ if (resolvesOutsideWorkspace) {
501
+ throw new GenerationError('QCE_OUTPUT_OUTSIDE_WORKSPACE', `Project "${project.id}" output path resolves outside workspace root: ${project.outDir}`);
502
+ }
503
+ }
504
+ function createPlannedWrites(_projectId, _outDirPath, _componentDefinitions, _adapterModule, _ssrAvailable, adapterPlannedWrites = []) {
505
+ return adapterPlannedWrites;
506
+ }
507
+ async function createAdapterPlannedWrites(project, outDirPath, componentDefinitions, adapterModule, runtimeImports, ssrAvailable) {
508
+ const createGeneratedOutput = adapterModule != null &&
509
+ typeof adapterModule.createGeneratedOutput === 'function'
510
+ ? adapterModule.createGeneratedOutput
511
+ : undefined;
512
+ if (createGeneratedOutput == null) {
513
+ throw new GenerationError('QCE_ADAPTER_GENERATION_CONTRACT_REQUIRED', `Project "${project.id}" adapter "${project.adapterPackage}" must export createGeneratedOutput() as its primary generated-output contract.`);
514
+ }
515
+ try {
516
+ const plannedWrites = (await createGeneratedOutput({
517
+ projectId: project.id,
518
+ libraryName: project.libraryName,
519
+ source: project.source,
520
+ adapterOptions: project.adapterOptions ?? {},
521
+ runtimeImports,
522
+ componentDefinitions,
523
+ ssrAvailable,
524
+ }));
525
+ if (!Array.isArray(plannedWrites)) {
526
+ return [];
527
+ }
528
+ return plannedWrites
529
+ .filter((plannedWrite) => plannedWrite != null &&
530
+ typeof plannedWrite === 'object' &&
531
+ 'relativePath' in plannedWrite &&
532
+ typeof plannedWrite.relativePath === 'string' &&
533
+ 'content' in plannedWrite &&
534
+ typeof plannedWrite.content === 'string')
535
+ .map((plannedWrite) => ({
536
+ path: path.join(outDirPath, plannedWrite.relativePath),
537
+ content: plannedWrite.content,
538
+ }));
539
+ }
540
+ catch (error) {
541
+ const errorCode = typeof error === 'object' &&
542
+ error !== null &&
543
+ 'code' in error &&
544
+ typeof error.code === 'string'
545
+ ? error.code
546
+ : 'QCE_ADAPTER_PLANNED_WRITES_INVALID';
547
+ throw new GenerationError(errorCode, toErrorMessage(error));
548
+ }
549
+ }
550
+ async function readComponentDefinitionsFromCem(sourcePath) {
551
+ let parsed;
552
+ try {
553
+ const rawText = await readFile(sourcePath, 'utf8');
554
+ parsed = JSON.parse(rawText);
555
+ }
556
+ catch (error) {
557
+ throw new GenerationError('QCE_CEM_READ_FAILED', `Could not read CEM source at ${sourcePath}: ${toErrorMessage(error)}`);
558
+ }
559
+ const manifest = parsed;
560
+ if (!Array.isArray(manifest.modules)) {
561
+ throw new GenerationError('QCE_CEM_INVALID_SHAPE', `CEM at ${sourcePath} must include a "modules" array.`);
562
+ }
563
+ const definitions = new Map();
564
+ for (let moduleIndex = 0; moduleIndex < manifest.modules.length; moduleIndex += 1) {
565
+ const module = manifest.modules[moduleIndex];
566
+ if (module.declarations != null && !Array.isArray(module.declarations)) {
567
+ throw new GenerationError('QCE_CEM_INVALID_SHAPE', `CEM at ${sourcePath} has invalid shape: modules[${moduleIndex}].declarations must be an array when provided.`);
568
+ }
569
+ const declarations = module.declarations ?? [];
570
+ for (let declarationIndex = 0; declarationIndex < declarations.length; declarationIndex += 1) {
571
+ const declaration = declarations[declarationIndex];
572
+ if (!Object.prototype.hasOwnProperty.call(declaration, 'tagName')) {
573
+ continue;
574
+ }
575
+ if (typeof declaration.tagName !== 'string' ||
576
+ declaration.tagName.trim() === '') {
577
+ throw new GenerationError('QCE_CEM_INVALID_SHAPE', `CEM at ${sourcePath} has invalid shape: modules[${moduleIndex}].declarations[${declarationIndex}].tagName must be a non-empty string when provided.`);
578
+ }
579
+ const tagName = declaration.tagName.trim();
580
+ const existingDefinition = definitions.get(tagName);
581
+ const definition = existingDefinition ?? {
582
+ tagName,
583
+ props: [],
584
+ events: [],
585
+ slots: [],
586
+ };
587
+ const propsByName = new Map(definition.props.map((prop) => [prop.name, prop]));
588
+ const eventsByName = new Map(definition.events.map((event) => [event.name, event]));
589
+ const slotsByName = new Map(definition.slots.map((slot) => [slot.name, slot]));
590
+ const declarationRecord = declaration;
591
+ for (const prop of readComponentPropsFromAttributes(declarationRecord.attributes)) {
592
+ propsByName.set(prop.name, prop);
593
+ }
594
+ for (const prop of readComponentPropsFromMembers(declarationRecord.members)) {
595
+ const existingProp = propsByName.get(prop.name);
596
+ if (existingProp == null ||
597
+ existingProp.type === 'unknown' ||
598
+ existingProp.type.trim() === '') {
599
+ propsByName.set(prop.name, prop);
600
+ }
601
+ }
602
+ for (const event of readComponentEventsFromDeclaration(declarationRecord.events)) {
603
+ if (!eventsByName.has(event.name)) {
604
+ eventsByName.set(event.name, event);
605
+ }
606
+ }
607
+ for (const slot of readComponentSlotsFromDeclaration(declarationRecord.slots)) {
608
+ if (!slotsByName.has(slot.name)) {
609
+ slotsByName.set(slot.name, slot);
610
+ }
611
+ }
612
+ definition.props = Array.from(propsByName.values()).sort((a, b) => a.name.localeCompare(b.name));
613
+ definition.events = Array.from(eventsByName.values()).sort((a, b) => a.name.localeCompare(b.name));
614
+ definition.slots = Array.from(slotsByName.values()).sort((a, b) => a.name.localeCompare(b.name));
615
+ definitions.set(tagName, definition);
616
+ }
617
+ }
618
+ return Array.from(definitions.values()).sort((a, b) => a.tagName.localeCompare(b.tagName));
619
+ }
620
+ function readComponentPropsFromAttributes(attributes) {
621
+ if (!Array.isArray(attributes)) {
622
+ return [];
623
+ }
624
+ const props = [];
625
+ for (const attribute of attributes) {
626
+ if (typeof attribute !== 'object' || attribute === null) {
627
+ continue;
628
+ }
629
+ const attributeRecord = attribute;
630
+ const rawName = typeof attributeRecord.fieldName === 'string' &&
631
+ attributeRecord.fieldName.trim() !== ''
632
+ ? attributeRecord.fieldName
633
+ : attributeRecord.name;
634
+ if (typeof rawName !== 'string' || rawName.trim() === '') {
635
+ continue;
636
+ }
637
+ props.push({
638
+ name: rawName.trim(),
639
+ type: normalizeCemTypeText(attributeRecord.type?.text),
640
+ required: false,
641
+ });
642
+ }
643
+ return props;
644
+ }
645
+ function readComponentPropsFromMembers(members) {
646
+ if (!Array.isArray(members)) {
647
+ return [];
648
+ }
649
+ const props = [];
650
+ for (const member of members) {
651
+ if (typeof member !== 'object' || member === null) {
652
+ continue;
653
+ }
654
+ const memberRecord = member;
655
+ if (memberRecord.kind !== 'field') {
656
+ continue;
657
+ }
658
+ if (memberRecord.privacy === 'private') {
659
+ continue;
660
+ }
661
+ if (typeof memberRecord.name !== 'string' ||
662
+ memberRecord.name.trim() === '') {
663
+ continue;
664
+ }
665
+ props.push({
666
+ name: memberRecord.name.trim(),
667
+ type: normalizeCemTypeText(memberRecord.type?.text),
668
+ required: false,
669
+ });
670
+ }
671
+ return props;
672
+ }
673
+ function readComponentEventsFromDeclaration(events) {
674
+ if (!Array.isArray(events)) {
675
+ return [];
676
+ }
677
+ const componentEvents = [];
678
+ for (const event of events) {
679
+ if (typeof event !== 'object' || event === null) {
680
+ continue;
681
+ }
682
+ const eventRecord = event;
683
+ if (typeof eventRecord.name !== 'string' ||
684
+ eventRecord.name.trim() === '') {
685
+ continue;
686
+ }
687
+ componentEvents.push({
688
+ name: eventRecord.name.trim(),
689
+ type: normalizeCemTypeText(eventRecord.type?.text),
690
+ });
691
+ }
692
+ return componentEvents;
693
+ }
694
+ function readComponentSlotsFromDeclaration(slots) {
695
+ if (!Array.isArray(slots)) {
696
+ return [];
697
+ }
698
+ const componentSlots = [];
699
+ for (const slot of slots) {
700
+ if (typeof slot !== 'object' || slot === null) {
701
+ continue;
702
+ }
703
+ const slotRecord = slot;
704
+ if (typeof slotRecord.name !== 'string' || slotRecord.name.trim() === '') {
705
+ continue;
706
+ }
707
+ componentSlots.push({
708
+ name: slotRecord.name.trim(),
709
+ });
710
+ }
711
+ return componentSlots;
712
+ }
713
+ function normalizeCemTypeText(typeText) {
714
+ return typeof typeText === 'string' && typeText.trim() !== ''
715
+ ? typeText.trim()
716
+ : 'unknown';
717
+ }
718
+ function toErrorMessage(error) {
719
+ return error instanceof Error ? error.message : String(error);
720
+ }