@rexeus/typeweaver 0.10.3 → 0.10.5
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/dist/{cli-Dz7saa33.mjs → cli-BVUW7VcY.mjs} +236 -46
- package/dist/cli.cjs +236 -46
- package/dist/cli.mjs +236 -46
- package/dist/cli.mjs.map +1 -1
- package/dist/entry.mjs +1 -1
- package/dist/templates/Index.ejs +2 -2
- package/package.json +8 -8
package/dist/cli.cjs
CHANGED
|
@@ -31,42 +31,83 @@ let _rexeus_typeweaver_types = require("@rexeus/typeweaver-types");
|
|
|
31
31
|
let node_os = require("node:os");
|
|
32
32
|
node_os = __toESM(node_os);
|
|
33
33
|
let rolldown = require("rolldown");
|
|
34
|
+
let node_crypto = require("node:crypto");
|
|
34
35
|
let _rexeus_typeweaver_core = require("@rexeus/typeweaver-core");
|
|
36
|
+
//#region src/errors/InvalidConfigExportError.ts
|
|
37
|
+
var InvalidConfigExportError = class extends Error {
|
|
38
|
+
name = "InvalidConfigExportError";
|
|
39
|
+
constructor(configPath, reason) {
|
|
40
|
+
super(getInvalidConfigExportMessage(configPath, reason));
|
|
41
|
+
this.configPath = configPath;
|
|
42
|
+
this.reason = reason;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const getInvalidConfigExportMessage = (configPath, reason) => {
|
|
46
|
+
switch (reason) {
|
|
47
|
+
case "both-default-and-named-config": return `Configuration file '${configPath}' must choose one export style: use either 'export default' or 'export const config = ...', but not both.`;
|
|
48
|
+
case "default-namespace-wrapper": return `Configuration file '${configPath}' default export must be the config object itself, not a module namespace-like wrapper. Export the config directly with 'export default { ... }' or use 'export const config = ...'.`;
|
|
49
|
+
case "missing-config-export": return `Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`;
|
|
50
|
+
case "non-object-config": return `Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/errors/UnsupportedConfigExtensionError.ts
|
|
55
|
+
var UnsupportedConfigExtensionError = class extends Error {
|
|
56
|
+
name = "UnsupportedConfigExtensionError";
|
|
57
|
+
constructor(configPath, extension, supportedExtensions) {
|
|
58
|
+
super(`Unsupported config file extension '${extension}' for '${configPath}'. TypeWeaver accepts only these config extensions: ${supportedExtensions.join(", ")}.`);
|
|
59
|
+
this.configPath = configPath;
|
|
60
|
+
this.extension = extension;
|
|
61
|
+
this.supportedExtensions = supportedExtensions;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/errors/UnsupportedTypeScriptConfigError.ts
|
|
66
|
+
var UnsupportedTypeScriptConfigError = class extends Error {
|
|
67
|
+
name = "UnsupportedTypeScriptConfigError";
|
|
68
|
+
constructor(configPath, extension) {
|
|
69
|
+
super(`TypeScript config files are not supported: '${configPath}' uses '${extension}'. Use a JavaScript config file with one of these extensions: .js, .mjs, or .cjs.`);
|
|
70
|
+
this.configPath = configPath;
|
|
71
|
+
this.extension = extension;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
35
75
|
//#region src/configLoader.ts
|
|
36
|
-
const SUPPORTED_CONFIG_EXTENSIONS =
|
|
76
|
+
const SUPPORTED_CONFIG_EXTENSIONS = [
|
|
37
77
|
".js",
|
|
38
78
|
".mjs",
|
|
39
79
|
".cjs"
|
|
40
|
-
]
|
|
80
|
+
];
|
|
81
|
+
const SUPPORTED_CONFIG_EXTENSION_SET = new Set(SUPPORTED_CONFIG_EXTENSIONS);
|
|
41
82
|
const UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([
|
|
42
83
|
".ts",
|
|
43
84
|
".mts",
|
|
44
85
|
".cts"
|
|
45
86
|
]);
|
|
46
87
|
const getResolvedConfigPath = (configPath, currentWorkingDirectory = process.cwd()) => {
|
|
47
|
-
return node_path.default.isAbsolute(configPath) ? configPath : node_path.default.
|
|
88
|
+
return node_path.default.isAbsolute(configPath) ? configPath : node_path.default.resolve(currentWorkingDirectory, configPath);
|
|
48
89
|
};
|
|
49
90
|
const assertSupportedConfigPath = (configPath) => {
|
|
50
91
|
const extension = node_path.default.extname(configPath).toLowerCase();
|
|
51
|
-
if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new
|
|
52
|
-
if (!
|
|
92
|
+
if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new UnsupportedTypeScriptConfigError(configPath, extension);
|
|
93
|
+
if (!SUPPORTED_CONFIG_EXTENSION_SET.has(extension)) throw new UnsupportedConfigExtensionError(configPath, extension, SUPPORTED_CONFIG_EXTENSIONS);
|
|
53
94
|
};
|
|
54
95
|
const loadConfig = async (configPath) => {
|
|
55
96
|
assertSupportedConfigPath(configPath);
|
|
56
97
|
const loadedConfig = getConfigExport(await import((0, node_url.pathToFileURL)(node_path.default.resolve(configPath)).toString()), configPath);
|
|
57
|
-
if (!isConfigObject(loadedConfig)) throw new
|
|
98
|
+
if (!isConfigObject(loadedConfig)) throw new InvalidConfigExportError(configPath, "non-object-config");
|
|
58
99
|
return loadedConfig;
|
|
59
100
|
};
|
|
60
101
|
const getConfigExport = (configModule, configPath) => {
|
|
61
102
|
const hasDefaultExport = Object.hasOwn(configModule, "default");
|
|
62
103
|
const hasNamedConfigExport = Object.hasOwn(configModule, "config");
|
|
63
|
-
if (hasDefaultExport && hasNamedConfigExport) throw new
|
|
104
|
+
if (hasDefaultExport && hasNamedConfigExport) throw new InvalidConfigExportError(configPath, "both-default-and-named-config");
|
|
64
105
|
if (hasDefaultExport) {
|
|
65
|
-
if (isNamespaceLikeConfigExport(configModule.default)) throw new
|
|
106
|
+
if (isNamespaceLikeConfigExport(configModule.default)) throw new InvalidConfigExportError(configPath, "default-namespace-wrapper");
|
|
66
107
|
return configModule.default;
|
|
67
108
|
}
|
|
68
109
|
if (hasNamedConfigExport) return configModule.config;
|
|
69
|
-
throw new
|
|
110
|
+
throw new InvalidConfigExportError(configPath, "missing-config-export");
|
|
70
111
|
};
|
|
71
112
|
const isConfigObject = (value) => {
|
|
72
113
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -76,6 +117,35 @@ const isNamespaceLikeConfigExport = (value) => {
|
|
|
76
117
|
return Object.hasOwn(value, "default") || Object.hasOwn(value, "config");
|
|
77
118
|
};
|
|
78
119
|
//#endregion
|
|
120
|
+
//#region src/generators/errors/UnsafeCleanTargetError.ts
|
|
121
|
+
var UnsafeCleanTargetError = class extends Error {
|
|
122
|
+
name = "UnsafeCleanTargetError";
|
|
123
|
+
resolvedOutputDir;
|
|
124
|
+
currentWorkingDirectory;
|
|
125
|
+
protectedWorkspaceRoot;
|
|
126
|
+
filesystemRoot;
|
|
127
|
+
constructor(outputDir, reason, diagnostics = {}) {
|
|
128
|
+
super(getUnsafeCleanTargetMessage(outputDir, reason, diagnostics));
|
|
129
|
+
this.outputDir = outputDir;
|
|
130
|
+
this.reason = reason;
|
|
131
|
+
this.resolvedOutputDir = diagnostics.resolvedOutputDir;
|
|
132
|
+
this.currentWorkingDirectory = diagnostics.currentWorkingDirectory;
|
|
133
|
+
this.protectedWorkspaceRoot = diagnostics.protectedWorkspaceRoot;
|
|
134
|
+
this.filesystemRoot = diagnostics.filesystemRoot;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const getUnsafeCleanTargetMessage = (outputDir, reason, diagnostics) => {
|
|
138
|
+
const targetDescription = `Refusing to clean '${outputDir}'`;
|
|
139
|
+
const suffix = "Use a dedicated generated output directory instead.";
|
|
140
|
+
switch (reason) {
|
|
141
|
+
case "empty-path": return `Refusing to clean an empty output directory path. ${suffix}`;
|
|
142
|
+
case "filesystem-root": return `${targetDescription} because it resolves to the filesystem root '${diagnostics.filesystemRoot ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
143
|
+
case "current-working-directory": return `${targetDescription} because it resolves to the current working directory '${diagnostics.currentWorkingDirectory ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
144
|
+
case "workspace-root": return `${targetDescription} because it resolves to the protected workspace root '${diagnostics.protectedWorkspaceRoot ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
145
|
+
case "ancestor-of-current-working-directory": return `${targetDescription} because it resolves to an ancestor directory of the current working directory '${diagnostics.currentWorkingDirectory ?? ""}'. ${suffix}`;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
//#endregion
|
|
79
149
|
//#region src/generators/formatter.ts
|
|
80
150
|
async function formatCode(outputDir, startDir) {
|
|
81
151
|
const format = await loadFormatter();
|
|
@@ -112,11 +182,16 @@ function generateIndexFiles(templateDir, context) {
|
|
|
112
182
|
const withJsExt = normalizedFile.replace(/\.ts$/, ".js");
|
|
113
183
|
const stripped = normalizedFile.replace(/\.ts$/, "");
|
|
114
184
|
const firstSlash = stripped.indexOf("/");
|
|
185
|
+
if (stripped === "index") continue;
|
|
115
186
|
if (firstSlash === -1) {
|
|
116
187
|
rootFiles.add(`./${withJsExt}`);
|
|
117
188
|
continue;
|
|
118
189
|
}
|
|
119
190
|
const firstSegment = stripped.slice(0, firstSlash);
|
|
191
|
+
if (stripped === "lib/index") {
|
|
192
|
+
existingBarrels.add("lib");
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
120
195
|
if (firstSegment === "lib") {
|
|
121
196
|
const secondSlash = stripped.indexOf("/", firstSlash + 1);
|
|
122
197
|
const groupKey = secondSlash === -1 ? stripped : stripped.slice(0, secondSlash);
|
|
@@ -165,30 +240,29 @@ async function loadPlugins(registry, requiredPlugins, strategies, config) {
|
|
|
165
240
|
if (!config?.plugins) return;
|
|
166
241
|
const successful = [];
|
|
167
242
|
for (const plugin of config.plugins) {
|
|
168
|
-
|
|
169
|
-
if (typeof plugin === "string") result = await loadPlugin(plugin, strategies);
|
|
170
|
-
else result = await loadPlugin(plugin[0], strategies);
|
|
243
|
+
const result = await loadPlugin(typeof plugin === "string" ? plugin : plugin[0], strategies, typeof plugin === "string" ? void 0 : plugin[1]);
|
|
171
244
|
if (result.success === false) throw new PluginLoadingFailure(result.error.pluginName, result.error.attempts);
|
|
172
245
|
successful.push(result.value);
|
|
173
|
-
registry.register(result.value.plugin);
|
|
246
|
+
registry.register(result.value.plugin, result.value.config);
|
|
174
247
|
}
|
|
175
248
|
reportSuccessfulLoads(successful);
|
|
176
249
|
}
|
|
177
|
-
async function loadPlugin(pluginName, strategies) {
|
|
250
|
+
async function loadPlugin(pluginName, strategies, pluginConfig) {
|
|
178
251
|
const possiblePaths = generatePluginPaths(pluginName, strategies);
|
|
179
252
|
const attempts = [];
|
|
180
253
|
for (const possiblePath of possiblePaths) try {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
254
|
+
const plugin = createPluginInstance(await import(possiblePath), pluginConfig);
|
|
255
|
+
if (plugin.success) return {
|
|
183
256
|
success: true,
|
|
184
257
|
value: {
|
|
185
|
-
plugin:
|
|
186
|
-
source: possiblePath
|
|
258
|
+
plugin: plugin.value,
|
|
259
|
+
source: possiblePath,
|
|
260
|
+
config: pluginConfig
|
|
187
261
|
}
|
|
188
262
|
};
|
|
189
263
|
attempts.push({
|
|
190
264
|
path: possiblePath,
|
|
191
|
-
error:
|
|
265
|
+
error: plugin.error
|
|
192
266
|
});
|
|
193
267
|
} catch (error) {
|
|
194
268
|
attempts.push({
|
|
@@ -204,10 +278,46 @@ async function loadPlugin(pluginName, strategies) {
|
|
|
204
278
|
}
|
|
205
279
|
};
|
|
206
280
|
}
|
|
207
|
-
function
|
|
208
|
-
|
|
281
|
+
function createPluginInstance(pluginModule, pluginConfig) {
|
|
282
|
+
const candidates = findPluginConstructorCandidates(pluginModule);
|
|
283
|
+
if (candidates.length === 0) return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: "No plugin constructor export found"
|
|
286
|
+
};
|
|
287
|
+
const errors = [];
|
|
288
|
+
for (const candidate of candidates) try {
|
|
289
|
+
const plugin = new candidate.constructor(pluginConfig);
|
|
290
|
+
if (isTypeweaverPlugin(plugin)) return {
|
|
291
|
+
success: true,
|
|
292
|
+
value: plugin
|
|
293
|
+
};
|
|
294
|
+
errors.push(`Export '${candidate.exportName}' did not produce a valid plugin with a string name`);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
errors.push(`Export '${candidate.exportName}' could not be instantiated: ${formatError(error)}`);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
error: errors.join("; ")
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function findPluginConstructorCandidates(pluginModule) {
|
|
304
|
+
const candidates = [];
|
|
305
|
+
for (const [key, value] of Object.entries(pluginModule)) if (key !== "default" && typeof value === "function") candidates.push({
|
|
306
|
+
exportName: key,
|
|
307
|
+
constructor: value
|
|
308
|
+
});
|
|
209
309
|
const defaultExport = pluginModule.default;
|
|
210
|
-
if (typeof defaultExport === "function")
|
|
310
|
+
if (typeof defaultExport === "function") candidates.push({
|
|
311
|
+
exportName: "default",
|
|
312
|
+
constructor: defaultExport
|
|
313
|
+
});
|
|
314
|
+
return candidates;
|
|
315
|
+
}
|
|
316
|
+
function isTypeweaverPlugin(value) {
|
|
317
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && value.name.length > 0;
|
|
318
|
+
}
|
|
319
|
+
function formatError(error) {
|
|
320
|
+
return error instanceof Error ? error.message : String(error);
|
|
211
321
|
}
|
|
212
322
|
function generatePluginPaths(pluginName, strategies) {
|
|
213
323
|
const paths = [];
|
|
@@ -217,11 +327,19 @@ function generatePluginPaths(pluginName, strategies) {
|
|
|
217
327
|
paths.push(`@rexeus/${pluginName}`);
|
|
218
328
|
break;
|
|
219
329
|
case "local":
|
|
330
|
+
paths.push(toLocalImportSpecifier(pluginName));
|
|
331
|
+
break;
|
|
332
|
+
case "scoped":
|
|
220
333
|
paths.push(pluginName);
|
|
221
334
|
break;
|
|
222
335
|
}
|
|
223
336
|
return paths;
|
|
224
337
|
}
|
|
338
|
+
function toLocalImportSpecifier(pluginName) {
|
|
339
|
+
if (pluginName.startsWith("file:")) return pluginName;
|
|
340
|
+
if (node_path.default.isAbsolute(pluginName)) return (0, node_url.pathToFileURL)(pluginName).href;
|
|
341
|
+
return pluginName;
|
|
342
|
+
}
|
|
225
343
|
function reportSuccessfulLoads(successful) {
|
|
226
344
|
if (successful.length > 0) {
|
|
227
345
|
console.info(`Successfully loaded ${successful.length} plugin(s):`);
|
|
@@ -229,6 +347,17 @@ function reportSuccessfulLoads(successful) {
|
|
|
229
347
|
}
|
|
230
348
|
}
|
|
231
349
|
//#endregion
|
|
350
|
+
//#region src/generators/spec/errors/SpecBundleOutputMissingError.ts
|
|
351
|
+
var SpecBundleOutputMissingError = class extends Error {
|
|
352
|
+
name = "SpecBundleOutputMissingError";
|
|
353
|
+
constructor(inputFile, bundledSpecFile, specOutputDir) {
|
|
354
|
+
super(`Spec bundling completed but did not create the expected output '${bundledSpecFile}' for entrypoint '${inputFile}'. Expected the bundle inside '${specOutputDir}'.`);
|
|
355
|
+
this.inputFile = inputFile;
|
|
356
|
+
this.bundledSpecFile = bundledSpecFile;
|
|
357
|
+
this.specOutputDir = specOutputDir;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
//#endregion
|
|
232
361
|
//#region src/generators/spec/specBundler.ts
|
|
233
362
|
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
|
|
234
363
|
const WINDOWS_UNC_PATH_PATTERN = /^\\\\/;
|
|
@@ -242,7 +371,7 @@ function createWrapperImportSpecifier(wrapperFile, inputFile) {
|
|
|
242
371
|
if (relativeInputFile.startsWith(".") || relativeInputFile.startsWith("..")) return relativeInputFile;
|
|
243
372
|
return `./${relativeInputFile}`;
|
|
244
373
|
}
|
|
245
|
-
async function bundle(config) {
|
|
374
|
+
async function bundle(config, deps = {}) {
|
|
246
375
|
const tempDir = node_fs.default.mkdtempSync(node_path.default.join(node_os.default.tmpdir(), "typeweaver-spec-loader-"));
|
|
247
376
|
const wrapperFile = node_path.default.join(tempDir, "spec-entrypoint.ts");
|
|
248
377
|
const bundledSpecFile = node_path.default.join(config.specOutputDir, "spec.js");
|
|
@@ -258,7 +387,7 @@ async function bundle(config) {
|
|
|
258
387
|
""
|
|
259
388
|
].join("\n"));
|
|
260
389
|
try {
|
|
261
|
-
await (
|
|
390
|
+
await (deps.build ?? rolldown.build)({
|
|
262
391
|
cwd: tempDir,
|
|
263
392
|
input: wrapperFile,
|
|
264
393
|
treeshake: true,
|
|
@@ -278,7 +407,7 @@ async function bundle(config) {
|
|
|
278
407
|
force: true
|
|
279
408
|
});
|
|
280
409
|
}
|
|
281
|
-
if (!node_fs.default.existsSync(bundledSpecFile)) throw new
|
|
410
|
+
if (!(deps.existsSync ?? node_fs.default.existsSync)(bundledSpecFile)) throw new SpecBundleOutputMissingError(config.inputFile, bundledSpecFile, config.specOutputDir);
|
|
282
411
|
return bundledSpecFile;
|
|
283
412
|
}
|
|
284
413
|
function resolveBundledInputFile(inputFile) {
|
|
@@ -328,7 +457,10 @@ const isSpecDefinition = (value) => {
|
|
|
328
457
|
//#endregion
|
|
329
458
|
//#region src/generators/spec/specImporter.ts
|
|
330
459
|
async function importDefinition(bundledSpecFile) {
|
|
331
|
-
const
|
|
460
|
+
const contentHash = (0, node_crypto.createHash)("sha256").update(node_fs.default.readFileSync(bundledSpecFile)).digest("hex");
|
|
461
|
+
const moduleUrl = (0, node_url.pathToFileURL)(bundledSpecFile);
|
|
462
|
+
moduleUrl.searchParams.set("content", contentHash);
|
|
463
|
+
const specModule = await import(moduleUrl.toString());
|
|
332
464
|
const definition = specModule.spec ?? specModule.default ?? specModule;
|
|
333
465
|
if (!isSpecDefinition(definition)) throw new InvalidSpecEntrypointError(bundledSpecFile);
|
|
334
466
|
return definition;
|
|
@@ -357,13 +489,32 @@ function writeSpecDeclarationFile(specOutputDir) {
|
|
|
357
489
|
const moduleDir$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
358
490
|
const assertSafeCleanTarget = (outputDir, currentWorkingDirectory) => {
|
|
359
491
|
const trimmedOutputDir = outputDir.trim();
|
|
360
|
-
if (trimmedOutputDir.length === 0) throw new
|
|
492
|
+
if (trimmedOutputDir.length === 0) throw new UnsafeCleanTargetError(outputDir, "empty-path");
|
|
361
493
|
const resolvedWorkingDirectory = node_path.default.resolve(currentWorkingDirectory);
|
|
494
|
+
const canonicalWorkingDirectory = node_fs.default.realpathSync.native(resolvedWorkingDirectory);
|
|
362
495
|
const resolvedOutputDir = node_path.default.resolve(resolvedWorkingDirectory, trimmedOutputDir);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
496
|
+
const canonicalOutputDir = canonicalizePathForContainment(resolvedOutputDir);
|
|
497
|
+
const filesystemRoot = node_path.default.parse(canonicalOutputDir).root;
|
|
498
|
+
if (canonicalOutputDir === filesystemRoot) throw new UnsafeCleanTargetError(outputDir, "filesystem-root", {
|
|
499
|
+
resolvedOutputDir,
|
|
500
|
+
currentWorkingDirectory: resolvedWorkingDirectory,
|
|
501
|
+
filesystemRoot
|
|
502
|
+
});
|
|
503
|
+
if (resolvedOutputDir === resolvedWorkingDirectory || canonicalOutputDir === canonicalWorkingDirectory) throw new UnsafeCleanTargetError(outputDir, "current-working-directory", {
|
|
504
|
+
resolvedOutputDir,
|
|
505
|
+
currentWorkingDirectory: resolvedWorkingDirectory
|
|
506
|
+
});
|
|
507
|
+
const protectedWorkspaceRoots = [findProtectedWorkspaceRoot(resolvedWorkingDirectory), findProtectedWorkspaceRoot(canonicalWorkingDirectory)].filter((root) => root !== void 0);
|
|
508
|
+
const protectedWorkspaceRootTarget = protectedWorkspaceRoots.find((protectedWorkspaceRoot) => resolvedOutputDir === protectedWorkspaceRoot || canonicalOutputDir === node_fs.default.realpathSync.native(protectedWorkspaceRoot));
|
|
509
|
+
if (protectedWorkspaceRootTarget !== void 0) throw new UnsafeCleanTargetError(outputDir, "workspace-root", {
|
|
510
|
+
resolvedOutputDir,
|
|
511
|
+
currentWorkingDirectory: resolvedWorkingDirectory,
|
|
512
|
+
protectedWorkspaceRoot: protectedWorkspaceRootTarget
|
|
513
|
+
});
|
|
514
|
+
if (protectedWorkspaceRoots.length > 0 && (isSameOrDescendantOf(resolvedWorkingDirectory, resolvedOutputDir) || isSameOrDescendantOf(canonicalWorkingDirectory, canonicalOutputDir))) throw new UnsafeCleanTargetError(outputDir, "ancestor-of-current-working-directory", {
|
|
515
|
+
resolvedOutputDir,
|
|
516
|
+
currentWorkingDirectory: resolvedWorkingDirectory
|
|
517
|
+
});
|
|
367
518
|
};
|
|
368
519
|
/**
|
|
369
520
|
* Main generator for typeweaver
|
|
@@ -457,6 +608,58 @@ const findProtectedWorkspaceRoot = (startDirectory) => {
|
|
|
457
608
|
const hasWorkspaceMarker = (directory) => {
|
|
458
609
|
return ["pnpm-workspace.yaml", ".git"].some((marker) => node_fs.default.existsSync(node_path.default.join(directory, marker)));
|
|
459
610
|
};
|
|
611
|
+
const canonicalizePathForContainment = (targetPath) => {
|
|
612
|
+
const remainingSegments = [];
|
|
613
|
+
let nearestExistingPath = node_path.default.resolve(targetPath);
|
|
614
|
+
while (!node_fs.default.existsSync(nearestExistingPath)) {
|
|
615
|
+
const parentPath = node_path.default.dirname(nearestExistingPath);
|
|
616
|
+
if (parentPath === nearestExistingPath) break;
|
|
617
|
+
remainingSegments.unshift(node_path.default.basename(nearestExistingPath));
|
|
618
|
+
nearestExistingPath = parentPath;
|
|
619
|
+
}
|
|
620
|
+
const canonicalExistingPath = node_fs.default.realpathSync.native(nearestExistingPath);
|
|
621
|
+
return node_path.default.join(canonicalExistingPath, ...remainingSegments);
|
|
622
|
+
};
|
|
623
|
+
const isSameOrDescendantOf = (directory, ancestor) => {
|
|
624
|
+
const relativePath = node_path.default.relative(ancestor, directory);
|
|
625
|
+
const parentTraversalPrefix = `..${node_path.default.sep}`;
|
|
626
|
+
const escapesAncestor = relativePath === ".." || relativePath.startsWith(parentTraversalPrefix);
|
|
627
|
+
return relativePath === "" || !escapesAncestor && !node_path.default.isAbsolute(relativePath);
|
|
628
|
+
};
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/errors/MissingGenerateOptionError.ts
|
|
631
|
+
var MissingGenerateOptionError = class extends Error {
|
|
632
|
+
name = "MissingGenerateOptionError";
|
|
633
|
+
constructor(optionName, flag, configKey) {
|
|
634
|
+
super(`Missing required generate option '${optionName}'. Pass ${flag} or set '${configKey}' in the TypeWeaver config file.`);
|
|
635
|
+
this.optionName = optionName;
|
|
636
|
+
this.flag = flag;
|
|
637
|
+
this.configKey = configKey;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region src/resolveGenerateOptions.ts
|
|
642
|
+
const resolveGenerateOptions = (options, config, currentWorkingDirectory) => {
|
|
643
|
+
const inputPath = options.input ?? config.input;
|
|
644
|
+
const outputDir = options.output ?? config.output;
|
|
645
|
+
if (!inputPath) throw new MissingGenerateOptionError("input", "--input", "input");
|
|
646
|
+
if (!outputDir) throw new MissingGenerateOptionError("output", "--output", "output");
|
|
647
|
+
const resolvedInputPath = node_path.default.isAbsolute(inputPath) ? inputPath : node_path.default.join(currentWorkingDirectory, inputPath);
|
|
648
|
+
const resolvedOutputDir = node_path.default.isAbsolute(outputDir) ? outputDir : node_path.default.join(currentWorkingDirectory, outputDir);
|
|
649
|
+
const finalConfig = {
|
|
650
|
+
input: resolvedInputPath,
|
|
651
|
+
output: resolvedOutputDir,
|
|
652
|
+
format: options.format ?? config.format ?? true,
|
|
653
|
+
clean: options.clean ?? config.clean ?? true
|
|
654
|
+
};
|
|
655
|
+
if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((plugin) => plugin.trim());
|
|
656
|
+
else if (config.plugins) finalConfig.plugins = config.plugins;
|
|
657
|
+
return {
|
|
658
|
+
inputPath: resolvedInputPath,
|
|
659
|
+
outputDir: resolvedOutputDir,
|
|
660
|
+
config: finalConfig
|
|
661
|
+
};
|
|
662
|
+
};
|
|
460
663
|
//#endregion
|
|
461
664
|
//#region src/cli.ts
|
|
462
665
|
const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
@@ -477,21 +680,8 @@ program.command("generate").description("Generate types, validators, and clients
|
|
|
477
680
|
process.exit(1);
|
|
478
681
|
}
|
|
479
682
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
if (!inputPath) throw new Error("No input spec entrypoint provided. Use --input or specify in config file.");
|
|
483
|
-
if (!outputDir) throw new Error("No output directory provided. Use --output or specify in config file.");
|
|
484
|
-
const resolvedInputPath = node_path.default.isAbsolute(inputPath) ? inputPath : node_path.default.join(execDir, inputPath);
|
|
485
|
-
const resolvedOutputDir = node_path.default.isAbsolute(outputDir) ? outputDir : node_path.default.join(execDir, outputDir);
|
|
486
|
-
const finalConfig = {
|
|
487
|
-
input: resolvedInputPath,
|
|
488
|
-
output: resolvedOutputDir,
|
|
489
|
-
format: options.format ?? config.format ?? true,
|
|
490
|
-
clean: options.clean ?? config.clean ?? true
|
|
491
|
-
};
|
|
492
|
-
if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((p) => p.trim());
|
|
493
|
-
else if (config.plugins) finalConfig.plugins = config.plugins;
|
|
494
|
-
return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig, execDir);
|
|
683
|
+
const resolvedGenerateOptions = resolveGenerateOptions(options, config, execDir);
|
|
684
|
+
return new Generator().generate(resolvedGenerateOptions.inputPath, resolvedGenerateOptions.outputDir, resolvedGenerateOptions.config, execDir);
|
|
495
685
|
});
|
|
496
686
|
program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
|
|
497
687
|
console.log("The init command is coming soon!");
|