@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
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { ConfigValidationError, loadGeneratorConfig } from './config.js';
|
|
5
|
+
import { GenerationError, generateFromConfig } from './generator.js';
|
|
6
|
+
const RUN_SUMMARY_SCHEMA_VERSION = '1.0.0';
|
|
7
|
+
const DEFAULT_RUN_SUMMARY_PATH = './generated-run-summary.json';
|
|
8
|
+
const SSR_UNSUPPORTED_FALLBACK_CODE = 'QCE_SSR_UNSUPPORTED_FALLBACK';
|
|
9
|
+
const CORE_PACKAGE_JSON_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
10
|
+
export function parseCliArgs(argv) {
|
|
11
|
+
let configPath;
|
|
12
|
+
const projectIds = [];
|
|
13
|
+
let parallel = false;
|
|
14
|
+
let help = false;
|
|
15
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
16
|
+
const arg = argv[index];
|
|
17
|
+
if (arg === '--help' || arg === '-h') {
|
|
18
|
+
help = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === '--config') {
|
|
22
|
+
const nextValue = argv[index + 1];
|
|
23
|
+
if (!nextValue || nextValue.startsWith('-')) {
|
|
24
|
+
throw new ConfigValidationError('QCE_CLI_INVALID_ARG', 'Flag "--config" requires a file path value.');
|
|
25
|
+
}
|
|
26
|
+
configPath = nextValue;
|
|
27
|
+
index += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (arg.startsWith('--config=')) {
|
|
31
|
+
const candidate = arg.slice('--config='.length);
|
|
32
|
+
if (!candidate) {
|
|
33
|
+
throw new ConfigValidationError('QCE_CLI_INVALID_ARG', 'Flag "--config" requires a file path value.');
|
|
34
|
+
}
|
|
35
|
+
configPath = candidate;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (arg === '--project') {
|
|
39
|
+
const nextValue = argv[index + 1];
|
|
40
|
+
if (!nextValue || nextValue.startsWith('-')) {
|
|
41
|
+
throw new ConfigValidationError('QCE_CLI_INVALID_ARG', 'Flag "--project" requires a project id value.');
|
|
42
|
+
}
|
|
43
|
+
projectIds.push(nextValue);
|
|
44
|
+
index += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg.startsWith('--project=')) {
|
|
48
|
+
const candidate = arg.slice('--project='.length);
|
|
49
|
+
if (!candidate) {
|
|
50
|
+
throw new ConfigValidationError('QCE_CLI_INVALID_ARG', 'Flag "--project" requires a project id value.');
|
|
51
|
+
}
|
|
52
|
+
projectIds.push(candidate);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--parallel') {
|
|
56
|
+
parallel = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
throw new ConfigValidationError('QCE_CLI_INVALID_ARG', `Unknown CLI argument "${arg}".`);
|
|
60
|
+
}
|
|
61
|
+
return { configPath, projectIds, parallel, help };
|
|
62
|
+
}
|
|
63
|
+
export async function runCli(argv) {
|
|
64
|
+
const runStartedAtMs = Date.now();
|
|
65
|
+
let resolvedConfigPath;
|
|
66
|
+
let resolvedSummaryPath;
|
|
67
|
+
let resolvedDryRun = false;
|
|
68
|
+
let selectedProjectsForSummary = [];
|
|
69
|
+
try {
|
|
70
|
+
const args = parseCliArgs(argv);
|
|
71
|
+
if (args.help) {
|
|
72
|
+
process.stdout.write('Usage: qwik-custom-elements [--config <path>] [--project <id>] [--parallel] [--help]\n' +
|
|
73
|
+
'Default config path: ./qwik-custom-elements.config.json\n' +
|
|
74
|
+
'Optional JS config path: ./qwik-custom-elements.config.js\n' +
|
|
75
|
+
'Parallel mode: add --parallel to run selected projects concurrently.\n' +
|
|
76
|
+
'Project targeting: repeat --project to run a subset by id.\n');
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const { configPath, config } = await loadGeneratorConfig({
|
|
80
|
+
configPath: args.configPath,
|
|
81
|
+
});
|
|
82
|
+
resolvedConfigPath = configPath;
|
|
83
|
+
resolvedSummaryPath = config.summaryPath;
|
|
84
|
+
resolvedDryRun = config.dryRun === true;
|
|
85
|
+
const requestedProjectIdSet = new Set(args.projectIds);
|
|
86
|
+
selectedProjectsForSummary = [...config.projects]
|
|
87
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
88
|
+
.map((project) => ({
|
|
89
|
+
projectId: project.id,
|
|
90
|
+
adapterPackage: project.adapterPackage,
|
|
91
|
+
sourcePath: resolveProjectSourceSummaryPath(project.source),
|
|
92
|
+
outDirPath: path.resolve(process.cwd(), project.outDir),
|
|
93
|
+
isTargeted: requestedProjectIdSet.size === 0 ||
|
|
94
|
+
requestedProjectIdSet.has(project.id),
|
|
95
|
+
}));
|
|
96
|
+
if (args.parallel) {
|
|
97
|
+
config.parallel = true;
|
|
98
|
+
}
|
|
99
|
+
const generationResult = await generateFromConfig(config, {
|
|
100
|
+
targetProjectIds: args.projectIds,
|
|
101
|
+
});
|
|
102
|
+
const mode = generationResult.dryRun ? 'dry-run' : 'write';
|
|
103
|
+
const totalWrites = generationResult.projects.reduce((sum, project) => sum + project.plannedWrites.length, 0);
|
|
104
|
+
if (config.parallel === true) {
|
|
105
|
+
for (const project of generationResult.projects) {
|
|
106
|
+
process.stdout.write(`[project:${project.projectId}] mode=${mode} plannedWrites=${project.plannedWrites.length}\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const project of generationResult.projects) {
|
|
110
|
+
const observedCodeSet = new Set(project.observedErrorCodes);
|
|
111
|
+
const hasClientOnlySuccessSignal = project.ssrCapabilities.clientOnlyMode === true &&
|
|
112
|
+
observedCodeSet.has(SSR_UNSUPPORTED_FALLBACK_CODE) &&
|
|
113
|
+
project.plannedWrites.length > 0;
|
|
114
|
+
if (hasClientOnlySuccessSignal) {
|
|
115
|
+
process.stderr.write(`${SSR_UNSUPPORTED_FALLBACK_CODE}: Project "${project.projectId}" SSR is unavailable, but client-capable wrapper generation succeeded via the client-only CSR surface.
|
|
116
|
+
`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (project.observedErrorCodes.includes(SSR_UNSUPPORTED_FALLBACK_CODE)) {
|
|
120
|
+
process.stderr.write(`${SSR_UNSUPPORTED_FALLBACK_CODE}: Project "${project.projectId}" adapter SSR is unavailable; falling back to CEM-only generation.\n`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const runFinishedAtMs = Date.now();
|
|
124
|
+
await writeRunSummaryArtifact({
|
|
125
|
+
configPath,
|
|
126
|
+
summaryPath: config.summaryPath,
|
|
127
|
+
generationResult,
|
|
128
|
+
runStartedAtMs,
|
|
129
|
+
runFinishedAtMs,
|
|
130
|
+
});
|
|
131
|
+
process.stdout.write(`Generation completed (${mode}) from ${configPath}. Projects: ${generationResult.projects.length}. Planned writes: ${totalWrites}.\n`);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (resolvedConfigPath != null && error instanceof GenerationError) {
|
|
136
|
+
const runFinishedAtMs = Date.now();
|
|
137
|
+
const failedDurationMs = runFinishedAtMs - runStartedAtMs;
|
|
138
|
+
await writeRunSummaryArtifact({
|
|
139
|
+
configPath: resolvedConfigPath,
|
|
140
|
+
summaryPath: resolvedSummaryPath,
|
|
141
|
+
generationResult: {
|
|
142
|
+
dryRun: resolvedDryRun,
|
|
143
|
+
projects: selectedProjectsForSummary.map((project) => ({
|
|
144
|
+
projectId: project.projectId,
|
|
145
|
+
status: project.isTargeted ? 'failed' : 'skipped',
|
|
146
|
+
durationMs: project.isTargeted ? failedDurationMs : 0,
|
|
147
|
+
adapterPackage: project.adapterPackage,
|
|
148
|
+
sourcePath: project.sourcePath,
|
|
149
|
+
outDirPath: project.outDirPath,
|
|
150
|
+
generatedIndexPath: project.isTargeted
|
|
151
|
+
? ''
|
|
152
|
+
: path.join(project.outDirPath, 'index.ts'),
|
|
153
|
+
componentTags: [],
|
|
154
|
+
plannedWrites: [],
|
|
155
|
+
wroteFiles: false,
|
|
156
|
+
ssrCapabilities: {
|
|
157
|
+
available: false,
|
|
158
|
+
supportsSsrProbe: false,
|
|
159
|
+
ssrRuntimeSubpath: null,
|
|
160
|
+
},
|
|
161
|
+
observedErrorCodes: project.isTargeted ? [error.code] : [],
|
|
162
|
+
})),
|
|
163
|
+
},
|
|
164
|
+
runStartedAtMs,
|
|
165
|
+
runFinishedAtMs,
|
|
166
|
+
observedErrorCodes: [error.code],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (error instanceof ConfigValidationError ||
|
|
170
|
+
error instanceof GenerationError) {
|
|
171
|
+
process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
process.stderr.write(`QCE_UNEXPECTED: ${message}\n`);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function resolveProjectSourceSummaryPath(source) {
|
|
180
|
+
if (source.type === 'CEM') {
|
|
181
|
+
return path.resolve(process.cwd(), source.path);
|
|
182
|
+
}
|
|
183
|
+
if (source.cemPath != null) {
|
|
184
|
+
return path.resolve(process.cwd(), source.cemPath);
|
|
185
|
+
}
|
|
186
|
+
return source.packageName;
|
|
187
|
+
}
|
|
188
|
+
async function writeRunSummaryArtifact(params) {
|
|
189
|
+
const { configPath, summaryPath, generationResult, runStartedAtMs, runFinishedAtMs, observedErrorCodes = [], } = params;
|
|
190
|
+
const configDir = path.dirname(configPath);
|
|
191
|
+
const resolvedSummaryPath = path.resolve(configDir, summaryPath ?? DEFAULT_RUN_SUMMARY_PATH);
|
|
192
|
+
const resolvedCoreVersion = await readVersionFromPackageJson(CORE_PACKAGE_JSON_PATH);
|
|
193
|
+
const adapterVersionCache = new Map();
|
|
194
|
+
const summary = {
|
|
195
|
+
schemaVersion: RUN_SUMMARY_SCHEMA_VERSION,
|
|
196
|
+
startedAt: new Date(runStartedAtMs).toISOString(),
|
|
197
|
+
finishedAt: new Date(runFinishedAtMs).toISOString(),
|
|
198
|
+
dryRun: generationResult.dryRun,
|
|
199
|
+
projects: await Promise.all(generationResult.projects.map(async (project) => ({
|
|
200
|
+
projectId: project.projectId,
|
|
201
|
+
status: project.status,
|
|
202
|
+
durationMs: project.durationMs,
|
|
203
|
+
generatedIndexPath: project.generatedIndexPath,
|
|
204
|
+
resolvedCoreVersion,
|
|
205
|
+
resolvedAdapterVersion: await resolveAdapterVersion(project.adapterPackage, adapterVersionCache),
|
|
206
|
+
ssrCapabilities: project.ssrCapabilities,
|
|
207
|
+
observedErrorCodes: Array.from(new Set(project.observedErrorCodes)).sort((a, b) => a.localeCompare(b)),
|
|
208
|
+
}))),
|
|
209
|
+
observedErrorCodes: Array.from(new Set(observedErrorCodes)).sort((a, b) => a.localeCompare(b)),
|
|
210
|
+
};
|
|
211
|
+
await mkdir(path.dirname(resolvedSummaryPath), { recursive: true });
|
|
212
|
+
await writeFile(resolvedSummaryPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
|
|
213
|
+
}
|
|
214
|
+
async function resolveAdapterVersion(adapterPackage, cache) {
|
|
215
|
+
const cached = cache.get(adapterPackage);
|
|
216
|
+
if (cached != null) {
|
|
217
|
+
return cached;
|
|
218
|
+
}
|
|
219
|
+
const packageName = resolveAdapterVersionPackageName(adapterPackage);
|
|
220
|
+
if (packageName == null) {
|
|
221
|
+
cache.set(adapterPackage, 'unknown');
|
|
222
|
+
return 'unknown';
|
|
223
|
+
}
|
|
224
|
+
const adapterPackageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', packageName, 'package.json');
|
|
225
|
+
const resolvedVersion = await readVersionFromPackageJson(adapterPackageJsonPath);
|
|
226
|
+
cache.set(adapterPackage, resolvedVersion);
|
|
227
|
+
return resolvedVersion;
|
|
228
|
+
}
|
|
229
|
+
function resolveAdapterVersionPackageName(adapterPackage) {
|
|
230
|
+
if (!adapterPackage.startsWith('@qwik-custom-elements/')) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
const [scope, packageDirectoryName] = adapterPackage.split('/');
|
|
234
|
+
if (scope !== '@qwik-custom-elements' || !packageDirectoryName) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
return packageDirectoryName;
|
|
238
|
+
}
|
|
239
|
+
async function readVersionFromPackageJson(packageJsonPath) {
|
|
240
|
+
try {
|
|
241
|
+
const packageJsonText = await readFile(packageJsonPath, 'utf8');
|
|
242
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
243
|
+
if (packageJson != null &&
|
|
244
|
+
typeof packageJson.version === 'string' &&
|
|
245
|
+
packageJson.version.length > 0) {
|
|
246
|
+
return packageJson.version;
|
|
247
|
+
}
|
|
248
|
+
return 'unknown';
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return 'unknown';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function isMainModule() {
|
|
255
|
+
const executedPath = process.argv[1];
|
|
256
|
+
if (!executedPath) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return import.meta.url === pathToFileURL(executedPath).href;
|
|
260
|
+
}
|
|
261
|
+
if (isMainModule()) {
|
|
262
|
+
runCli(process.argv.slice(2))
|
|
263
|
+
.then((exitCode) => {
|
|
264
|
+
process.exitCode = exitCode;
|
|
265
|
+
})
|
|
266
|
+
.catch((error) => {
|
|
267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
+
process.stderr.write(`QCE_UNEXPECTED: ${message}\n`);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
});
|
|
271
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GeneratorConfig, LoadConfigOptions, LoadedConfig } from './types.js';
|
|
2
|
+
export declare class ConfigValidationError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(code: string, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function loadGeneratorConfig(options?: LoadConfigOptions): Promise<LoadedConfig>;
|
|
7
|
+
export declare function validateGeneratorConfig(rawConfig: unknown): GeneratorConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
const DEFAULT_JSON_CONFIG = 'qwik-custom-elements.config.json';
|
|
6
|
+
const DEFAULT_JS_CONFIG = 'qwik-custom-elements.config.js';
|
|
7
|
+
const ROOT_ALLOWED_KEYS = new Set([
|
|
8
|
+
'projects',
|
|
9
|
+
'dryRun',
|
|
10
|
+
'parallel',
|
|
11
|
+
'format',
|
|
12
|
+
'summaryPath',
|
|
13
|
+
]);
|
|
14
|
+
const PROJECT_ALLOWED_KEYS = new Set([
|
|
15
|
+
'id',
|
|
16
|
+
'libraryName',
|
|
17
|
+
'adapter',
|
|
18
|
+
'adapterPackage',
|
|
19
|
+
'source',
|
|
20
|
+
'outDir',
|
|
21
|
+
'cleanOutput',
|
|
22
|
+
'adapterOptions',
|
|
23
|
+
]);
|
|
24
|
+
export class ConfigValidationError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
constructor(code, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'ConfigValidationError';
|
|
29
|
+
this.code = code;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function loadGeneratorConfig(options = {}) {
|
|
33
|
+
const cwd = options.cwd ?? process.cwd();
|
|
34
|
+
const configPath = resolveConfigPath(cwd, options.configPath);
|
|
35
|
+
const rawConfig = await readUnknownConfigValue(configPath);
|
|
36
|
+
const config = validateGeneratorConfig(rawConfig);
|
|
37
|
+
return { configPath, config };
|
|
38
|
+
}
|
|
39
|
+
export function validateGeneratorConfig(rawConfig) {
|
|
40
|
+
if (!isPlainObject(rawConfig)) {
|
|
41
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_SHAPE', 'Config must be a plain object.');
|
|
42
|
+
}
|
|
43
|
+
ensureNoUnknownKeys(rawConfig, ROOT_ALLOWED_KEYS, 'root');
|
|
44
|
+
const projects = rawConfig.projects;
|
|
45
|
+
if (!Array.isArray(projects) || projects.length === 0) {
|
|
46
|
+
throw new ConfigValidationError('QCE_CONFIG_MISSING_REQUIRED', 'Config field "projects" must be a non-empty array.');
|
|
47
|
+
}
|
|
48
|
+
const normalizedProjects = projects.map((project, index) => validateProject(project, `projects[${index}]`));
|
|
49
|
+
const config = {
|
|
50
|
+
projects: normalizedProjects,
|
|
51
|
+
};
|
|
52
|
+
config.dryRun = readOptionalBoolean(rawConfig.dryRun, 'dryRun');
|
|
53
|
+
config.parallel = readOptionalBoolean(rawConfig.parallel, 'parallel');
|
|
54
|
+
config.format = readOptionalBoolean(rawConfig.format, 'format');
|
|
55
|
+
config.summaryPath = readOptionalString(rawConfig.summaryPath, 'summaryPath');
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
function validateProject(rawProject, pathPrefix) {
|
|
59
|
+
if (!isPlainObject(rawProject)) {
|
|
60
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_SHAPE', `Config field "${pathPrefix}" must be a plain object.`);
|
|
61
|
+
}
|
|
62
|
+
ensureNoUnknownKeys(rawProject, PROJECT_ALLOWED_KEYS, pathPrefix);
|
|
63
|
+
return {
|
|
64
|
+
id: readRequiredString(rawProject.id, `${pathPrefix}.id`),
|
|
65
|
+
libraryName: readOptionalString(rawProject.libraryName, `${pathPrefix}.libraryName`),
|
|
66
|
+
adapter: readRequiredString(rawProject.adapter, `${pathPrefix}.adapter`),
|
|
67
|
+
adapterPackage: readRequiredString(rawProject.adapterPackage, `${pathPrefix}.adapterPackage`),
|
|
68
|
+
source: readProjectSource(rawProject.source, `${pathPrefix}.source`),
|
|
69
|
+
outDir: readRequiredString(rawProject.outDir, `${pathPrefix}.outDir`),
|
|
70
|
+
cleanOutput: readOptionalBoolean(rawProject.cleanOutput, `${pathPrefix}.cleanOutput`),
|
|
71
|
+
adapterOptions: readOptionalObject(rawProject.adapterOptions, `${pathPrefix}.adapterOptions`),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function readProjectSource(value, field) {
|
|
75
|
+
if (!isPlainObject(value)) {
|
|
76
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_TYPE', `Config field "${field}" must be a source object with a valid "type" discriminator.`);
|
|
77
|
+
}
|
|
78
|
+
const sourceType = readRequiredString(value.type, `${field}.type`);
|
|
79
|
+
if (sourceType === 'CEM') {
|
|
80
|
+
ensureNoUnknownKeys(value, new Set(['type', 'path']), field);
|
|
81
|
+
return {
|
|
82
|
+
type: 'CEM',
|
|
83
|
+
path: readRequiredString(value.path, `${field}.path`),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (sourceType === 'PACKAGE_NAME') {
|
|
87
|
+
ensureNoUnknownKeys(value, new Set(['type', 'packageName', 'cemPath']), field);
|
|
88
|
+
return {
|
|
89
|
+
type: 'PACKAGE_NAME',
|
|
90
|
+
packageName: readRequiredString(value.packageName, `${field}.packageName`),
|
|
91
|
+
cemPath: readOptionalString(value.cemPath, `${field}.cemPath`),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_TYPE', `Config field "${field}.type" must be "CEM" or "PACKAGE_NAME".`);
|
|
95
|
+
}
|
|
96
|
+
function readRequiredString(value, field) {
|
|
97
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
98
|
+
throw new ConfigValidationError('QCE_CONFIG_MISSING_REQUIRED', `Config field "${field}" must be a non-empty string.`);
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function readOptionalString(value, field) {
|
|
103
|
+
if (value == null) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
107
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_TYPE', `Config field "${field}" must be a non-empty string when provided.`);
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
function readOptionalBoolean(value, field) {
|
|
112
|
+
if (value == null) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (typeof value !== 'boolean') {
|
|
116
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_TYPE', `Config field "${field}" must be a boolean when provided.`);
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
function readOptionalObject(value, field) {
|
|
121
|
+
if (value == null) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (!isPlainObject(value)) {
|
|
125
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_TYPE', `Config field "${field}" must be an object when provided.`);
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
function ensureNoUnknownKeys(value, allowedKeys, field) {
|
|
130
|
+
for (const key of Object.keys(value)) {
|
|
131
|
+
if (!allowedKeys.has(key)) {
|
|
132
|
+
throw new ConfigValidationError('QCE_CONFIG_UNKNOWN_FIELD', `Unknown config field "${field}.${key}".`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function isPlainObject(value) {
|
|
137
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
function resolveConfigPath(cwd, cliConfigPath) {
|
|
140
|
+
if (cliConfigPath) {
|
|
141
|
+
return path.resolve(cwd, cliConfigPath);
|
|
142
|
+
}
|
|
143
|
+
const defaultJsonPath = path.resolve(cwd, DEFAULT_JSON_CONFIG);
|
|
144
|
+
if (existsSync(defaultJsonPath)) {
|
|
145
|
+
return defaultJsonPath;
|
|
146
|
+
}
|
|
147
|
+
const defaultJsPath = path.resolve(cwd, DEFAULT_JS_CONFIG);
|
|
148
|
+
if (existsSync(defaultJsPath)) {
|
|
149
|
+
return defaultJsPath;
|
|
150
|
+
}
|
|
151
|
+
throw new ConfigValidationError('QCE_CONFIG_NOT_FOUND', `Could not find config file. Checked ${DEFAULT_JSON_CONFIG} and ${DEFAULT_JS_CONFIG} in ${cwd}.`);
|
|
152
|
+
}
|
|
153
|
+
async function readUnknownConfigValue(configPath) {
|
|
154
|
+
const extension = path.extname(configPath).toLowerCase();
|
|
155
|
+
if (extension === '.json') {
|
|
156
|
+
return readJsonConfig(configPath);
|
|
157
|
+
}
|
|
158
|
+
if (extension === '.js' || extension === '.mjs' || extension === '.cjs') {
|
|
159
|
+
return readModuleConfig(configPath);
|
|
160
|
+
}
|
|
161
|
+
throw new ConfigValidationError('QCE_CONFIG_UNSUPPORTED_EXTENSION', `Unsupported config extension "${extension}". Use .json, .js, .mjs, or .cjs.`);
|
|
162
|
+
}
|
|
163
|
+
async function readJsonConfig(configPath) {
|
|
164
|
+
try {
|
|
165
|
+
const rawText = await readFile(configPath, 'utf8');
|
|
166
|
+
return JSON.parse(rawText);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_JSON', `Invalid JSON config at ${configPath}: ${toErrorMessage(error)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function readModuleConfig(configPath) {
|
|
173
|
+
try {
|
|
174
|
+
const moduleNamespace = await import(pathToFileURL(configPath).href);
|
|
175
|
+
return moduleNamespace.default ?? moduleNamespace;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw new ConfigValidationError('QCE_CONFIG_INVALID_MODULE', `Invalid JS config at ${configPath}: ${toErrorMessage(error)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function toErrorMessage(error) {
|
|
182
|
+
return error instanceof Error ? error.message : String(error);
|
|
183
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GenerateOptions, GenerationResult, GeneratorConfig } from './types.js';
|
|
2
|
+
export declare class GenerationError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(code: string, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function generateFromConfig(config: GeneratorConfig, options?: GenerateOptions): Promise<GenerationResult>;
|