@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.mjs
CHANGED
|
@@ -6,42 +6,83 @@ import { createPluginContextBuilder, createPluginRegistry, normalizeSpec, render
|
|
|
6
6
|
import { TypesPlugin } from "@rexeus/typeweaver-types";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import { build } from "rolldown";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
9
10
|
import { HttpMethod, HttpStatusCode } from "@rexeus/typeweaver-core";
|
|
11
|
+
//#region src/errors/InvalidConfigExportError.ts
|
|
12
|
+
var InvalidConfigExportError = class extends Error {
|
|
13
|
+
name = "InvalidConfigExportError";
|
|
14
|
+
constructor(configPath, reason) {
|
|
15
|
+
super(getInvalidConfigExportMessage(configPath, reason));
|
|
16
|
+
this.configPath = configPath;
|
|
17
|
+
this.reason = reason;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const getInvalidConfigExportMessage = (configPath, reason) => {
|
|
21
|
+
switch (reason) {
|
|
22
|
+
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.`;
|
|
23
|
+
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 = ...'.`;
|
|
24
|
+
case "missing-config-export": return `Configuration file '${configPath}' must export its config via 'export default' or 'export const config = ...'.`;
|
|
25
|
+
case "non-object-config": return `Configuration file '${configPath}' must export a config object via 'export default' or 'export const config = ...'.`;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/errors/UnsupportedConfigExtensionError.ts
|
|
30
|
+
var UnsupportedConfigExtensionError = class extends Error {
|
|
31
|
+
name = "UnsupportedConfigExtensionError";
|
|
32
|
+
constructor(configPath, extension, supportedExtensions) {
|
|
33
|
+
super(`Unsupported config file extension '${extension}' for '${configPath}'. TypeWeaver accepts only these config extensions: ${supportedExtensions.join(", ")}.`);
|
|
34
|
+
this.configPath = configPath;
|
|
35
|
+
this.extension = extension;
|
|
36
|
+
this.supportedExtensions = supportedExtensions;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/errors/UnsupportedTypeScriptConfigError.ts
|
|
41
|
+
var UnsupportedTypeScriptConfigError = class extends Error {
|
|
42
|
+
name = "UnsupportedTypeScriptConfigError";
|
|
43
|
+
constructor(configPath, extension) {
|
|
44
|
+
super(`TypeScript config files are not supported: '${configPath}' uses '${extension}'. Use a JavaScript config file with one of these extensions: .js, .mjs, or .cjs.`);
|
|
45
|
+
this.configPath = configPath;
|
|
46
|
+
this.extension = extension;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
10
50
|
//#region src/configLoader.ts
|
|
11
|
-
const SUPPORTED_CONFIG_EXTENSIONS =
|
|
51
|
+
const SUPPORTED_CONFIG_EXTENSIONS = [
|
|
12
52
|
".js",
|
|
13
53
|
".mjs",
|
|
14
54
|
".cjs"
|
|
15
|
-
]
|
|
55
|
+
];
|
|
56
|
+
const SUPPORTED_CONFIG_EXTENSION_SET = new Set(SUPPORTED_CONFIG_EXTENSIONS);
|
|
16
57
|
const UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS = new Set([
|
|
17
58
|
".ts",
|
|
18
59
|
".mts",
|
|
19
60
|
".cts"
|
|
20
61
|
]);
|
|
21
62
|
const getResolvedConfigPath = (configPath, currentWorkingDirectory = process.cwd()) => {
|
|
22
|
-
return path.isAbsolute(configPath) ? configPath : path.
|
|
63
|
+
return path.isAbsolute(configPath) ? configPath : path.resolve(currentWorkingDirectory, configPath);
|
|
23
64
|
};
|
|
24
65
|
const assertSupportedConfigPath = (configPath) => {
|
|
25
66
|
const extension = path.extname(configPath).toLowerCase();
|
|
26
|
-
if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new
|
|
27
|
-
if (!
|
|
67
|
+
if (UNSUPPORTED_TYPESCRIPT_CONFIG_EXTENSIONS.has(extension)) throw new UnsupportedTypeScriptConfigError(configPath, extension);
|
|
68
|
+
if (!SUPPORTED_CONFIG_EXTENSION_SET.has(extension)) throw new UnsupportedConfigExtensionError(configPath, extension, SUPPORTED_CONFIG_EXTENSIONS);
|
|
28
69
|
};
|
|
29
70
|
const loadConfig = async (configPath) => {
|
|
30
71
|
assertSupportedConfigPath(configPath);
|
|
31
72
|
const loadedConfig = getConfigExport(await import(pathToFileURL(path.resolve(configPath)).toString()), configPath);
|
|
32
|
-
if (!isConfigObject(loadedConfig)) throw new
|
|
73
|
+
if (!isConfigObject(loadedConfig)) throw new InvalidConfigExportError(configPath, "non-object-config");
|
|
33
74
|
return loadedConfig;
|
|
34
75
|
};
|
|
35
76
|
const getConfigExport = (configModule, configPath) => {
|
|
36
77
|
const hasDefaultExport = Object.hasOwn(configModule, "default");
|
|
37
78
|
const hasNamedConfigExport = Object.hasOwn(configModule, "config");
|
|
38
|
-
if (hasDefaultExport && hasNamedConfigExport) throw new
|
|
79
|
+
if (hasDefaultExport && hasNamedConfigExport) throw new InvalidConfigExportError(configPath, "both-default-and-named-config");
|
|
39
80
|
if (hasDefaultExport) {
|
|
40
|
-
if (isNamespaceLikeConfigExport(configModule.default)) throw new
|
|
81
|
+
if (isNamespaceLikeConfigExport(configModule.default)) throw new InvalidConfigExportError(configPath, "default-namespace-wrapper");
|
|
41
82
|
return configModule.default;
|
|
42
83
|
}
|
|
43
84
|
if (hasNamedConfigExport) return configModule.config;
|
|
44
|
-
throw new
|
|
85
|
+
throw new InvalidConfigExportError(configPath, "missing-config-export");
|
|
45
86
|
};
|
|
46
87
|
const isConfigObject = (value) => {
|
|
47
88
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -51,6 +92,35 @@ const isNamespaceLikeConfigExport = (value) => {
|
|
|
51
92
|
return Object.hasOwn(value, "default") || Object.hasOwn(value, "config");
|
|
52
93
|
};
|
|
53
94
|
//#endregion
|
|
95
|
+
//#region src/generators/errors/UnsafeCleanTargetError.ts
|
|
96
|
+
var UnsafeCleanTargetError = class extends Error {
|
|
97
|
+
name = "UnsafeCleanTargetError";
|
|
98
|
+
resolvedOutputDir;
|
|
99
|
+
currentWorkingDirectory;
|
|
100
|
+
protectedWorkspaceRoot;
|
|
101
|
+
filesystemRoot;
|
|
102
|
+
constructor(outputDir, reason, diagnostics = {}) {
|
|
103
|
+
super(getUnsafeCleanTargetMessage(outputDir, reason, diagnostics));
|
|
104
|
+
this.outputDir = outputDir;
|
|
105
|
+
this.reason = reason;
|
|
106
|
+
this.resolvedOutputDir = diagnostics.resolvedOutputDir;
|
|
107
|
+
this.currentWorkingDirectory = diagnostics.currentWorkingDirectory;
|
|
108
|
+
this.protectedWorkspaceRoot = diagnostics.protectedWorkspaceRoot;
|
|
109
|
+
this.filesystemRoot = diagnostics.filesystemRoot;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const getUnsafeCleanTargetMessage = (outputDir, reason, diagnostics) => {
|
|
113
|
+
const targetDescription = `Refusing to clean '${outputDir}'`;
|
|
114
|
+
const suffix = "Use a dedicated generated output directory instead.";
|
|
115
|
+
switch (reason) {
|
|
116
|
+
case "empty-path": return `Refusing to clean an empty output directory path. ${suffix}`;
|
|
117
|
+
case "filesystem-root": return `${targetDescription} because it resolves to the filesystem root '${diagnostics.filesystemRoot ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
118
|
+
case "current-working-directory": return `${targetDescription} because it resolves to the current working directory '${diagnostics.currentWorkingDirectory ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
119
|
+
case "workspace-root": return `${targetDescription} because it resolves to the protected workspace root '${diagnostics.protectedWorkspaceRoot ?? diagnostics.resolvedOutputDir ?? outputDir}'. ${suffix}`;
|
|
120
|
+
case "ancestor-of-current-working-directory": return `${targetDescription} because it resolves to an ancestor directory of the current working directory '${diagnostics.currentWorkingDirectory ?? ""}'. ${suffix}`;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
54
124
|
//#region src/generators/formatter.ts
|
|
55
125
|
async function formatCode(outputDir, startDir) {
|
|
56
126
|
const format = await loadFormatter();
|
|
@@ -87,11 +157,16 @@ function generateIndexFiles(templateDir, context) {
|
|
|
87
157
|
const withJsExt = normalizedFile.replace(/\.ts$/, ".js");
|
|
88
158
|
const stripped = normalizedFile.replace(/\.ts$/, "");
|
|
89
159
|
const firstSlash = stripped.indexOf("/");
|
|
160
|
+
if (stripped === "index") continue;
|
|
90
161
|
if (firstSlash === -1) {
|
|
91
162
|
rootFiles.add(`./${withJsExt}`);
|
|
92
163
|
continue;
|
|
93
164
|
}
|
|
94
165
|
const firstSegment = stripped.slice(0, firstSlash);
|
|
166
|
+
if (stripped === "lib/index") {
|
|
167
|
+
existingBarrels.add("lib");
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
95
170
|
if (firstSegment === "lib") {
|
|
96
171
|
const secondSlash = stripped.indexOf("/", firstSlash + 1);
|
|
97
172
|
const groupKey = secondSlash === -1 ? stripped : stripped.slice(0, secondSlash);
|
|
@@ -140,30 +215,29 @@ async function loadPlugins(registry, requiredPlugins, strategies, config) {
|
|
|
140
215
|
if (!config?.plugins) return;
|
|
141
216
|
const successful = [];
|
|
142
217
|
for (const plugin of config.plugins) {
|
|
143
|
-
|
|
144
|
-
if (typeof plugin === "string") result = await loadPlugin(plugin, strategies);
|
|
145
|
-
else result = await loadPlugin(plugin[0], strategies);
|
|
218
|
+
const result = await loadPlugin(typeof plugin === "string" ? plugin : plugin[0], strategies, typeof plugin === "string" ? void 0 : plugin[1]);
|
|
146
219
|
if (result.success === false) throw new PluginLoadingFailure(result.error.pluginName, result.error.attempts);
|
|
147
220
|
successful.push(result.value);
|
|
148
|
-
registry.register(result.value.plugin);
|
|
221
|
+
registry.register(result.value.plugin, result.value.config);
|
|
149
222
|
}
|
|
150
223
|
reportSuccessfulLoads(successful);
|
|
151
224
|
}
|
|
152
|
-
async function loadPlugin(pluginName, strategies) {
|
|
225
|
+
async function loadPlugin(pluginName, strategies, pluginConfig) {
|
|
153
226
|
const possiblePaths = generatePluginPaths(pluginName, strategies);
|
|
154
227
|
const attempts = [];
|
|
155
228
|
for (const possiblePath of possiblePaths) try {
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
229
|
+
const plugin = createPluginInstance(await import(possiblePath), pluginConfig);
|
|
230
|
+
if (plugin.success) return {
|
|
158
231
|
success: true,
|
|
159
232
|
value: {
|
|
160
|
-
plugin:
|
|
161
|
-
source: possiblePath
|
|
233
|
+
plugin: plugin.value,
|
|
234
|
+
source: possiblePath,
|
|
235
|
+
config: pluginConfig
|
|
162
236
|
}
|
|
163
237
|
};
|
|
164
238
|
attempts.push({
|
|
165
239
|
path: possiblePath,
|
|
166
|
-
error:
|
|
240
|
+
error: plugin.error
|
|
167
241
|
});
|
|
168
242
|
} catch (error) {
|
|
169
243
|
attempts.push({
|
|
@@ -179,10 +253,46 @@ async function loadPlugin(pluginName, strategies) {
|
|
|
179
253
|
}
|
|
180
254
|
};
|
|
181
255
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
256
|
+
function createPluginInstance(pluginModule, pluginConfig) {
|
|
257
|
+
const candidates = findPluginConstructorCandidates(pluginModule);
|
|
258
|
+
if (candidates.length === 0) return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: "No plugin constructor export found"
|
|
261
|
+
};
|
|
262
|
+
const errors = [];
|
|
263
|
+
for (const candidate of candidates) try {
|
|
264
|
+
const plugin = new candidate.constructor(pluginConfig);
|
|
265
|
+
if (isTypeweaverPlugin(plugin)) return {
|
|
266
|
+
success: true,
|
|
267
|
+
value: plugin
|
|
268
|
+
};
|
|
269
|
+
errors.push(`Export '${candidate.exportName}' did not produce a valid plugin with a string name`);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
errors.push(`Export '${candidate.exportName}' could not be instantiated: ${formatError(error)}`);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
error: errors.join("; ")
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function findPluginConstructorCandidates(pluginModule) {
|
|
279
|
+
const candidates = [];
|
|
280
|
+
for (const [key, value] of Object.entries(pluginModule)) if (key !== "default" && typeof value === "function") candidates.push({
|
|
281
|
+
exportName: key,
|
|
282
|
+
constructor: value
|
|
283
|
+
});
|
|
184
284
|
const defaultExport = pluginModule.default;
|
|
185
|
-
if (typeof defaultExport === "function")
|
|
285
|
+
if (typeof defaultExport === "function") candidates.push({
|
|
286
|
+
exportName: "default",
|
|
287
|
+
constructor: defaultExport
|
|
288
|
+
});
|
|
289
|
+
return candidates;
|
|
290
|
+
}
|
|
291
|
+
function isTypeweaverPlugin(value) {
|
|
292
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && value.name.length > 0;
|
|
293
|
+
}
|
|
294
|
+
function formatError(error) {
|
|
295
|
+
return error instanceof Error ? error.message : String(error);
|
|
186
296
|
}
|
|
187
297
|
function generatePluginPaths(pluginName, strategies) {
|
|
188
298
|
const paths = [];
|
|
@@ -192,11 +302,19 @@ function generatePluginPaths(pluginName, strategies) {
|
|
|
192
302
|
paths.push(`@rexeus/${pluginName}`);
|
|
193
303
|
break;
|
|
194
304
|
case "local":
|
|
305
|
+
paths.push(toLocalImportSpecifier(pluginName));
|
|
306
|
+
break;
|
|
307
|
+
case "scoped":
|
|
195
308
|
paths.push(pluginName);
|
|
196
309
|
break;
|
|
197
310
|
}
|
|
198
311
|
return paths;
|
|
199
312
|
}
|
|
313
|
+
function toLocalImportSpecifier(pluginName) {
|
|
314
|
+
if (pluginName.startsWith("file:")) return pluginName;
|
|
315
|
+
if (path.isAbsolute(pluginName)) return pathToFileURL(pluginName).href;
|
|
316
|
+
return pluginName;
|
|
317
|
+
}
|
|
200
318
|
function reportSuccessfulLoads(successful) {
|
|
201
319
|
if (successful.length > 0) {
|
|
202
320
|
console.info(`Successfully loaded ${successful.length} plugin(s):`);
|
|
@@ -204,6 +322,17 @@ function reportSuccessfulLoads(successful) {
|
|
|
204
322
|
}
|
|
205
323
|
}
|
|
206
324
|
//#endregion
|
|
325
|
+
//#region src/generators/spec/errors/SpecBundleOutputMissingError.ts
|
|
326
|
+
var SpecBundleOutputMissingError = class extends Error {
|
|
327
|
+
name = "SpecBundleOutputMissingError";
|
|
328
|
+
constructor(inputFile, bundledSpecFile, specOutputDir) {
|
|
329
|
+
super(`Spec bundling completed but did not create the expected output '${bundledSpecFile}' for entrypoint '${inputFile}'. Expected the bundle inside '${specOutputDir}'.`);
|
|
330
|
+
this.inputFile = inputFile;
|
|
331
|
+
this.bundledSpecFile = bundledSpecFile;
|
|
332
|
+
this.specOutputDir = specOutputDir;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
//#endregion
|
|
207
336
|
//#region src/generators/spec/specBundler.ts
|
|
208
337
|
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
|
|
209
338
|
const WINDOWS_UNC_PATH_PATTERN = /^\\\\/;
|
|
@@ -217,7 +346,7 @@ function createWrapperImportSpecifier(wrapperFile, inputFile) {
|
|
|
217
346
|
if (relativeInputFile.startsWith(".") || relativeInputFile.startsWith("..")) return relativeInputFile;
|
|
218
347
|
return `./${relativeInputFile}`;
|
|
219
348
|
}
|
|
220
|
-
async function bundle(config) {
|
|
349
|
+
async function bundle(config, deps = {}) {
|
|
221
350
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "typeweaver-spec-loader-"));
|
|
222
351
|
const wrapperFile = path.join(tempDir, "spec-entrypoint.ts");
|
|
223
352
|
const bundledSpecFile = path.join(config.specOutputDir, "spec.js");
|
|
@@ -233,7 +362,7 @@ async function bundle(config) {
|
|
|
233
362
|
""
|
|
234
363
|
].join("\n"));
|
|
235
364
|
try {
|
|
236
|
-
await build({
|
|
365
|
+
await (deps.build ?? build)({
|
|
237
366
|
cwd: tempDir,
|
|
238
367
|
input: wrapperFile,
|
|
239
368
|
treeshake: true,
|
|
@@ -253,7 +382,7 @@ async function bundle(config) {
|
|
|
253
382
|
force: true
|
|
254
383
|
});
|
|
255
384
|
}
|
|
256
|
-
if (!fs.existsSync(bundledSpecFile)) throw new
|
|
385
|
+
if (!(deps.existsSync ?? fs.existsSync)(bundledSpecFile)) throw new SpecBundleOutputMissingError(config.inputFile, bundledSpecFile, config.specOutputDir);
|
|
257
386
|
return bundledSpecFile;
|
|
258
387
|
}
|
|
259
388
|
function resolveBundledInputFile(inputFile) {
|
|
@@ -303,7 +432,10 @@ const isSpecDefinition = (value) => {
|
|
|
303
432
|
//#endregion
|
|
304
433
|
//#region src/generators/spec/specImporter.ts
|
|
305
434
|
async function importDefinition(bundledSpecFile) {
|
|
306
|
-
const
|
|
435
|
+
const contentHash = createHash("sha256").update(fs.readFileSync(bundledSpecFile)).digest("hex");
|
|
436
|
+
const moduleUrl = pathToFileURL(bundledSpecFile);
|
|
437
|
+
moduleUrl.searchParams.set("content", contentHash);
|
|
438
|
+
const specModule = await import(moduleUrl.toString());
|
|
307
439
|
const definition = specModule.spec ?? specModule.default ?? specModule;
|
|
308
440
|
if (!isSpecDefinition(definition)) throw new InvalidSpecEntrypointError(bundledSpecFile);
|
|
309
441
|
return definition;
|
|
@@ -332,13 +464,32 @@ function writeSpecDeclarationFile(specOutputDir) {
|
|
|
332
464
|
const moduleDir$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
333
465
|
const assertSafeCleanTarget = (outputDir, currentWorkingDirectory) => {
|
|
334
466
|
const trimmedOutputDir = outputDir.trim();
|
|
335
|
-
if (trimmedOutputDir.length === 0) throw new
|
|
467
|
+
if (trimmedOutputDir.length === 0) throw new UnsafeCleanTargetError(outputDir, "empty-path");
|
|
336
468
|
const resolvedWorkingDirectory = path.resolve(currentWorkingDirectory);
|
|
469
|
+
const canonicalWorkingDirectory = fs.realpathSync.native(resolvedWorkingDirectory);
|
|
337
470
|
const resolvedOutputDir = path.resolve(resolvedWorkingDirectory, trimmedOutputDir);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
471
|
+
const canonicalOutputDir = canonicalizePathForContainment(resolvedOutputDir);
|
|
472
|
+
const filesystemRoot = path.parse(canonicalOutputDir).root;
|
|
473
|
+
if (canonicalOutputDir === filesystemRoot) throw new UnsafeCleanTargetError(outputDir, "filesystem-root", {
|
|
474
|
+
resolvedOutputDir,
|
|
475
|
+
currentWorkingDirectory: resolvedWorkingDirectory,
|
|
476
|
+
filesystemRoot
|
|
477
|
+
});
|
|
478
|
+
if (resolvedOutputDir === resolvedWorkingDirectory || canonicalOutputDir === canonicalWorkingDirectory) throw new UnsafeCleanTargetError(outputDir, "current-working-directory", {
|
|
479
|
+
resolvedOutputDir,
|
|
480
|
+
currentWorkingDirectory: resolvedWorkingDirectory
|
|
481
|
+
});
|
|
482
|
+
const protectedWorkspaceRoots = [findProtectedWorkspaceRoot(resolvedWorkingDirectory), findProtectedWorkspaceRoot(canonicalWorkingDirectory)].filter((root) => root !== void 0);
|
|
483
|
+
const protectedWorkspaceRootTarget = protectedWorkspaceRoots.find((protectedWorkspaceRoot) => resolvedOutputDir === protectedWorkspaceRoot || canonicalOutputDir === fs.realpathSync.native(protectedWorkspaceRoot));
|
|
484
|
+
if (protectedWorkspaceRootTarget !== void 0) throw new UnsafeCleanTargetError(outputDir, "workspace-root", {
|
|
485
|
+
resolvedOutputDir,
|
|
486
|
+
currentWorkingDirectory: resolvedWorkingDirectory,
|
|
487
|
+
protectedWorkspaceRoot: protectedWorkspaceRootTarget
|
|
488
|
+
});
|
|
489
|
+
if (protectedWorkspaceRoots.length > 0 && (isSameOrDescendantOf(resolvedWorkingDirectory, resolvedOutputDir) || isSameOrDescendantOf(canonicalWorkingDirectory, canonicalOutputDir))) throw new UnsafeCleanTargetError(outputDir, "ancestor-of-current-working-directory", {
|
|
490
|
+
resolvedOutputDir,
|
|
491
|
+
currentWorkingDirectory: resolvedWorkingDirectory
|
|
492
|
+
});
|
|
342
493
|
};
|
|
343
494
|
/**
|
|
344
495
|
* Main generator for typeweaver
|
|
@@ -432,6 +583,58 @@ const findProtectedWorkspaceRoot = (startDirectory) => {
|
|
|
432
583
|
const hasWorkspaceMarker = (directory) => {
|
|
433
584
|
return ["pnpm-workspace.yaml", ".git"].some((marker) => fs.existsSync(path.join(directory, marker)));
|
|
434
585
|
};
|
|
586
|
+
const canonicalizePathForContainment = (targetPath) => {
|
|
587
|
+
const remainingSegments = [];
|
|
588
|
+
let nearestExistingPath = path.resolve(targetPath);
|
|
589
|
+
while (!fs.existsSync(nearestExistingPath)) {
|
|
590
|
+
const parentPath = path.dirname(nearestExistingPath);
|
|
591
|
+
if (parentPath === nearestExistingPath) break;
|
|
592
|
+
remainingSegments.unshift(path.basename(nearestExistingPath));
|
|
593
|
+
nearestExistingPath = parentPath;
|
|
594
|
+
}
|
|
595
|
+
const canonicalExistingPath = fs.realpathSync.native(nearestExistingPath);
|
|
596
|
+
return path.join(canonicalExistingPath, ...remainingSegments);
|
|
597
|
+
};
|
|
598
|
+
const isSameOrDescendantOf = (directory, ancestor) => {
|
|
599
|
+
const relativePath = path.relative(ancestor, directory);
|
|
600
|
+
const parentTraversalPrefix = `..${path.sep}`;
|
|
601
|
+
const escapesAncestor = relativePath === ".." || relativePath.startsWith(parentTraversalPrefix);
|
|
602
|
+
return relativePath === "" || !escapesAncestor && !path.isAbsolute(relativePath);
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/errors/MissingGenerateOptionError.ts
|
|
606
|
+
var MissingGenerateOptionError = class extends Error {
|
|
607
|
+
name = "MissingGenerateOptionError";
|
|
608
|
+
constructor(optionName, flag, configKey) {
|
|
609
|
+
super(`Missing required generate option '${optionName}'. Pass ${flag} or set '${configKey}' in the TypeWeaver config file.`);
|
|
610
|
+
this.optionName = optionName;
|
|
611
|
+
this.flag = flag;
|
|
612
|
+
this.configKey = configKey;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/resolveGenerateOptions.ts
|
|
617
|
+
const resolveGenerateOptions = (options, config, currentWorkingDirectory) => {
|
|
618
|
+
const inputPath = options.input ?? config.input;
|
|
619
|
+
const outputDir = options.output ?? config.output;
|
|
620
|
+
if (!inputPath) throw new MissingGenerateOptionError("input", "--input", "input");
|
|
621
|
+
if (!outputDir) throw new MissingGenerateOptionError("output", "--output", "output");
|
|
622
|
+
const resolvedInputPath = path.isAbsolute(inputPath) ? inputPath : path.join(currentWorkingDirectory, inputPath);
|
|
623
|
+
const resolvedOutputDir = path.isAbsolute(outputDir) ? outputDir : path.join(currentWorkingDirectory, outputDir);
|
|
624
|
+
const finalConfig = {
|
|
625
|
+
input: resolvedInputPath,
|
|
626
|
+
output: resolvedOutputDir,
|
|
627
|
+
format: options.format ?? config.format ?? true,
|
|
628
|
+
clean: options.clean ?? config.clean ?? true
|
|
629
|
+
};
|
|
630
|
+
if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((plugin) => plugin.trim());
|
|
631
|
+
else if (config.plugins) finalConfig.plugins = config.plugins;
|
|
632
|
+
return {
|
|
633
|
+
inputPath: resolvedInputPath,
|
|
634
|
+
outputDir: resolvedOutputDir,
|
|
635
|
+
config: finalConfig
|
|
636
|
+
};
|
|
637
|
+
};
|
|
435
638
|
//#endregion
|
|
436
639
|
//#region src/cli.ts
|
|
437
640
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -452,21 +655,8 @@ program.command("generate").description("Generate types, validators, and clients
|
|
|
452
655
|
process.exit(1);
|
|
453
656
|
}
|
|
454
657
|
}
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
if (!inputPath) throw new Error("No input spec entrypoint provided. Use --input or specify in config file.");
|
|
458
|
-
if (!outputDir) throw new Error("No output directory provided. Use --output or specify in config file.");
|
|
459
|
-
const resolvedInputPath = path.isAbsolute(inputPath) ? inputPath : path.join(execDir, inputPath);
|
|
460
|
-
const resolvedOutputDir = path.isAbsolute(outputDir) ? outputDir : path.join(execDir, outputDir);
|
|
461
|
-
const finalConfig = {
|
|
462
|
-
input: resolvedInputPath,
|
|
463
|
-
output: resolvedOutputDir,
|
|
464
|
-
format: options.format ?? config.format ?? true,
|
|
465
|
-
clean: options.clean ?? config.clean ?? true
|
|
466
|
-
};
|
|
467
|
-
if (options.plugins) finalConfig.plugins = options.plugins.split(",").map((p) => p.trim());
|
|
468
|
-
else if (config.plugins) finalConfig.plugins = config.plugins;
|
|
469
|
-
return new Generator().generate(resolvedInputPath, resolvedOutputDir, finalConfig, execDir);
|
|
658
|
+
const resolvedGenerateOptions = resolveGenerateOptions(options, config, execDir);
|
|
659
|
+
return new Generator().generate(resolvedGenerateOptions.inputPath, resolvedGenerateOptions.outputDir, resolvedGenerateOptions.config, execDir);
|
|
470
660
|
});
|
|
471
661
|
program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
|
|
472
662
|
console.log("The init command is coming soon!");
|