ts-runtime-validation 1.6.15 → 1.7.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/.claude/settings.local.json +9 -0
- package/CONTRIBUTING.md +430 -0
- package/README.md +444 -64
- package/dist/ICommandOptions.js +3 -0
- package/dist/ICommandOptions.js.map +1 -0
- package/dist/SchemaGenerator.integration.test.js +323 -0
- package/dist/SchemaGenerator.integration.test.js.map +1 -0
- package/dist/SchemaGenerator.js +120 -0
- package/dist/SchemaGenerator.js.map +1 -0
- package/dist/SchemaGenerator.test.js +132 -0
- package/dist/SchemaGenerator.test.js.map +1 -0
- package/dist/errors/index.js +95 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/index.test.js +232 -0
- package/dist/errors/index.test.js.map +1 -0
- package/dist/getPosixPath.js +13 -0
- package/dist/getPosixPath.js.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.js.map +1 -0
- package/dist/services/CodeGenerator.js +305 -0
- package/dist/services/CodeGenerator.js.map +1 -0
- package/dist/services/FileDiscovery.js +121 -0
- package/dist/services/FileDiscovery.js.map +1 -0
- package/dist/services/FileDiscovery.test.js +184 -0
- package/dist/services/FileDiscovery.test.js.map +1 -0
- package/dist/services/SchemaProcessor.js +182 -0
- package/dist/services/SchemaProcessor.js.map +1 -0
- package/dist/services/SchemaProcessor.test.js +395 -0
- package/dist/services/SchemaProcessor.test.js.map +1 -0
- package/dist/services/SchemaWriter.js +76 -0
- package/dist/services/SchemaWriter.js.map +1 -0
- package/dist/services/SchemaWriter.test.js +255 -0
- package/dist/services/SchemaWriter.test.js.map +1 -0
- package/dist/test/basic-scenario/types.jsonschema.js +3 -0
- package/dist/test/basic-scenario/types.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseType.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseType.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js.map +1 -0
- package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js +3 -0
- package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js.map +1 -0
- package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js +49 -0
- package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js.map +1 -0
- package/dist/utils/ProgressReporter.js +67 -0
- package/dist/utils/ProgressReporter.js.map +1 -0
- package/dist/utils/ProgressReporter.test.js +267 -0
- package/dist/utils/ProgressReporter.test.js.map +1 -0
- package/dist/writeLine.js +12 -0
- package/dist/writeLine.js.map +1 -0
- package/package.json +2 -2
- package/src/ICommandOptions.ts +7 -0
- package/src/SchemaGenerator.integration.test.ts +411 -0
- package/src/SchemaGenerator.test.ts +7 -0
- package/src/SchemaGenerator.ts +112 -298
- package/src/errors/index.test.ts +319 -0
- package/src/errors/index.ts +92 -0
- package/src/index.ts +7 -0
- package/src/services/CodeGenerator.ts +352 -0
- package/src/services/FileDiscovery.test.ts +216 -0
- package/src/services/FileDiscovery.ts +137 -0
- package/src/services/SchemaProcessor.test.ts +464 -0
- package/src/services/SchemaProcessor.ts +173 -0
- package/src/services/SchemaWriter.test.ts +304 -0
- package/src/services/SchemaWriter.ts +75 -0
- package/src/utils/ProgressReporter.test.ts +357 -0
- package/src/utils/ProgressReporter.ts +76 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { SchemaWriter } from "./SchemaWriter";
|
|
4
|
+
import { CodeGenerationError } from "../errors";
|
|
5
|
+
|
|
6
|
+
const testDir = path.resolve(__dirname, "../test-tmp/schema-writer");
|
|
7
|
+
|
|
8
|
+
const cleanup = async () => {
|
|
9
|
+
if (fs.existsSync(testDir)) {
|
|
10
|
+
await fs.promises.rm(testDir, { recursive: true, force: true });
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
beforeEach(cleanup);
|
|
15
|
+
afterAll(cleanup);
|
|
16
|
+
|
|
17
|
+
describe("SchemaWriter", () => {
|
|
18
|
+
describe("writeJsonSchema", () => {
|
|
19
|
+
it("should write JSON schema with pretty formatting", async () => {
|
|
20
|
+
const writer = new SchemaWriter({
|
|
21
|
+
outputPath: testDir,
|
|
22
|
+
minify: false
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const schema: any = {
|
|
26
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
27
|
+
definitions: {
|
|
28
|
+
IUser: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
id: { type: "string" },
|
|
32
|
+
name: { type: "string" }
|
|
33
|
+
},
|
|
34
|
+
required: ["id", "name"],
|
|
35
|
+
additionalProperties: false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const outputFile = path.join(testDir, "schema.json");
|
|
41
|
+
await writer.writeJsonSchema(schema, outputFile);
|
|
42
|
+
|
|
43
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
44
|
+
|
|
45
|
+
const content = await fs.promises.readFile(outputFile, 'utf-8');
|
|
46
|
+
const parsed = JSON.parse(content);
|
|
47
|
+
|
|
48
|
+
expect(parsed).toEqual(schema);
|
|
49
|
+
// Pretty formatted should have indentation
|
|
50
|
+
expect(content).toMatch(/\n /);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should write minified JSON schema", async () => {
|
|
54
|
+
const writer = new SchemaWriter({
|
|
55
|
+
outputPath: testDir,
|
|
56
|
+
minify: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const schema: any = {
|
|
60
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
61
|
+
definitions: {
|
|
62
|
+
IUser: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
id: { type: "string" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const outputFile = path.join(testDir, "schema.json");
|
|
72
|
+
await writer.writeJsonSchema(schema, outputFile);
|
|
73
|
+
|
|
74
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
75
|
+
|
|
76
|
+
const content = await fs.promises.readFile(outputFile, 'utf-8');
|
|
77
|
+
const parsed = JSON.parse(content);
|
|
78
|
+
|
|
79
|
+
expect(parsed).toEqual(schema);
|
|
80
|
+
// Minified should not have extra whitespace
|
|
81
|
+
expect(content).not.toMatch(/\n /);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should create output directory if it doesn't exist", async () => {
|
|
85
|
+
const nestedPath = path.join(testDir, "nested", "deep", "path");
|
|
86
|
+
const writer = new SchemaWriter({
|
|
87
|
+
outputPath: nestedPath,
|
|
88
|
+
minify: false
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const schema = { $schema: "test" };
|
|
92
|
+
const outputFile = path.join(nestedPath, "schema.json");
|
|
93
|
+
|
|
94
|
+
await writer.writeJsonSchema(schema, outputFile);
|
|
95
|
+
|
|
96
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should throw CodeGenerationError on write failure", async () => {
|
|
100
|
+
const writer = new SchemaWriter({
|
|
101
|
+
outputPath: testDir,
|
|
102
|
+
minify: false
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const schema = { $schema: "test" };
|
|
106
|
+
// Try to write to an invalid path (file as directory)
|
|
107
|
+
const invalidPath = path.join(testDir, "file.json", "invalid");
|
|
108
|
+
|
|
109
|
+
// Create file first to make path invalid
|
|
110
|
+
await fs.promises.mkdir(testDir, { recursive: true });
|
|
111
|
+
await fs.promises.writeFile(path.join(testDir, "file.json"), "test");
|
|
112
|
+
|
|
113
|
+
await expect(writer.writeJsonSchema(schema, invalidPath))
|
|
114
|
+
.rejects.toThrow(CodeGenerationError);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("writeTypeScriptFile", () => {
|
|
119
|
+
it("should write TypeScript file correctly", async () => {
|
|
120
|
+
const writer = new SchemaWriter({
|
|
121
|
+
outputPath: testDir,
|
|
122
|
+
minify: false
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const content = `export interface IUser {
|
|
126
|
+
id: string;
|
|
127
|
+
name: string;
|
|
128
|
+
}`;
|
|
129
|
+
|
|
130
|
+
const outputFile = path.join(testDir, "types.ts");
|
|
131
|
+
await writer.writeTypeScriptFile(content, outputFile);
|
|
132
|
+
|
|
133
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
134
|
+
|
|
135
|
+
const fileContent = await fs.promises.readFile(outputFile, 'utf-8');
|
|
136
|
+
expect(fileContent).toBe(content);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should create directories for TypeScript files", async () => {
|
|
140
|
+
const nestedPath = path.join(testDir, "src", "types");
|
|
141
|
+
const writer = new SchemaWriter({
|
|
142
|
+
outputPath: nestedPath,
|
|
143
|
+
minify: false
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const content = "export const test = 'value';";
|
|
147
|
+
const outputFile = path.join(nestedPath, "test.ts");
|
|
148
|
+
|
|
149
|
+
await writer.writeTypeScriptFile(content, outputFile);
|
|
150
|
+
|
|
151
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should handle empty content", async () => {
|
|
155
|
+
const writer = new SchemaWriter({
|
|
156
|
+
outputPath: testDir,
|
|
157
|
+
minify: false
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const outputFile = path.join(testDir, "empty.ts");
|
|
161
|
+
await writer.writeTypeScriptFile("", outputFile);
|
|
162
|
+
|
|
163
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
164
|
+
|
|
165
|
+
const content = await fs.promises.readFile(outputFile, 'utf-8');
|
|
166
|
+
expect(content).toBe("");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("cleanOutputDirectory", () => {
|
|
171
|
+
it("should remove TypeScript and JSON files", async () => {
|
|
172
|
+
await fs.promises.mkdir(testDir, { recursive: true });
|
|
173
|
+
|
|
174
|
+
// Create various files
|
|
175
|
+
await fs.promises.writeFile(path.join(testDir, "schema.json"), "{}");
|
|
176
|
+
await fs.promises.writeFile(path.join(testDir, "types.ts"), "export interface Test {}");
|
|
177
|
+
await fs.promises.writeFile(path.join(testDir, "readme.md"), "# Test");
|
|
178
|
+
await fs.promises.writeFile(path.join(testDir, "config.yaml"), "test: value");
|
|
179
|
+
|
|
180
|
+
const writer = new SchemaWriter({
|
|
181
|
+
outputPath: testDir,
|
|
182
|
+
minify: false
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await writer.cleanOutputDirectory();
|
|
186
|
+
|
|
187
|
+
// Should remove .ts and .json files
|
|
188
|
+
expect(fs.existsSync(path.join(testDir, "schema.json"))).toBe(false);
|
|
189
|
+
expect(fs.existsSync(path.join(testDir, "types.ts"))).toBe(false);
|
|
190
|
+
|
|
191
|
+
// Should keep other files
|
|
192
|
+
expect(fs.existsSync(path.join(testDir, "readme.md"))).toBe(true);
|
|
193
|
+
expect(fs.existsSync(path.join(testDir, "config.yaml"))).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should handle non-existent directory", async () => {
|
|
197
|
+
const writer = new SchemaWriter({
|
|
198
|
+
outputPath: path.join(testDir, "nonexistent"),
|
|
199
|
+
minify: false
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Should not throw
|
|
203
|
+
await expect(writer.cleanOutputDirectory()).resolves.not.toThrow();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should not remove subdirectories", async () => {
|
|
207
|
+
await fs.promises.mkdir(path.join(testDir, "subdir"), { recursive: true });
|
|
208
|
+
await fs.promises.writeFile(path.join(testDir, "subdir", "file.ts"), "test");
|
|
209
|
+
await fs.promises.writeFile(path.join(testDir, "main.ts"), "test");
|
|
210
|
+
|
|
211
|
+
const writer = new SchemaWriter({
|
|
212
|
+
outputPath: testDir,
|
|
213
|
+
minify: false
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await writer.cleanOutputDirectory();
|
|
217
|
+
|
|
218
|
+
// Should remove file in root
|
|
219
|
+
expect(fs.existsSync(path.join(testDir, "main.ts"))).toBe(false);
|
|
220
|
+
|
|
221
|
+
// Should keep subdirectory and its contents
|
|
222
|
+
expect(fs.existsSync(path.join(testDir, "subdir"))).toBe(true);
|
|
223
|
+
expect(fs.existsSync(path.join(testDir, "subdir", "file.ts"))).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should handle permission errors gracefully", async () => {
|
|
227
|
+
// Skip this test as it's environment dependent
|
|
228
|
+
// Real permission errors would be caught by the try-catch in SchemaWriter
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("edge cases", () => {
|
|
233
|
+
it("should handle large files", async () => {
|
|
234
|
+
const writer = new SchemaWriter({
|
|
235
|
+
outputPath: testDir,
|
|
236
|
+
minify: false
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Create a large schema
|
|
240
|
+
const largeSchema: any = {
|
|
241
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
242
|
+
definitions: {}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Add many definitions
|
|
246
|
+
for (let i = 0; i < 1000; i++) {
|
|
247
|
+
largeSchema.definitions[`Interface${i}`] = {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
id: { type: "string" },
|
|
251
|
+
name: { type: "string" },
|
|
252
|
+
value: { type: "number" }
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const outputFile = path.join(testDir, "large-schema.json");
|
|
258
|
+
await writer.writeJsonSchema(largeSchema, outputFile);
|
|
259
|
+
|
|
260
|
+
expect(fs.existsSync(outputFile)).toBe(true);
|
|
261
|
+
|
|
262
|
+
const stats = await fs.promises.stat(outputFile);
|
|
263
|
+
expect(stats.size).toBeGreaterThan(1000); // Should be reasonably large
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should handle unicode content", async () => {
|
|
267
|
+
const writer = new SchemaWriter({
|
|
268
|
+
outputPath: testDir,
|
|
269
|
+
minify: false
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const unicodeContent = `export interface IUser {
|
|
273
|
+
// Unicode: café, naïve, résumé
|
|
274
|
+
name: string; // 中文注释
|
|
275
|
+
emoji: "👨💻" | "🚀" | "💡";
|
|
276
|
+
}`;
|
|
277
|
+
|
|
278
|
+
const outputFile = path.join(testDir, "unicode.ts");
|
|
279
|
+
await writer.writeTypeScriptFile(unicodeContent, outputFile);
|
|
280
|
+
|
|
281
|
+
const content = await fs.promises.readFile(outputFile, 'utf-8');
|
|
282
|
+
expect(content).toBe(unicodeContent);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("should overwrite existing files", async () => {
|
|
286
|
+
const writer = new SchemaWriter({
|
|
287
|
+
outputPath: testDir,
|
|
288
|
+
minify: false
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const outputFile = path.join(testDir, "overwrite.ts");
|
|
292
|
+
|
|
293
|
+
// Write first content
|
|
294
|
+
await writer.writeTypeScriptFile("const first = 1;", outputFile);
|
|
295
|
+
const firstContent = await fs.promises.readFile(outputFile, 'utf-8');
|
|
296
|
+
expect(firstContent).toBe("const first = 1;");
|
|
297
|
+
|
|
298
|
+
// Overwrite with second content
|
|
299
|
+
await writer.writeTypeScriptFile("const second = 2;", outputFile);
|
|
300
|
+
const secondContent = await fs.promises.readFile(outputFile, 'utf-8');
|
|
301
|
+
expect(secondContent).toBe("const second = 2;");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Schema } from "ts-json-schema-generator";
|
|
4
|
+
import { CodeGenerationError } from "../errors";
|
|
5
|
+
|
|
6
|
+
export interface SchemaWriterOptions {
|
|
7
|
+
outputPath: string;
|
|
8
|
+
minify?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SchemaWriter {
|
|
12
|
+
constructor(private options: SchemaWriterOptions) {}
|
|
13
|
+
|
|
14
|
+
public async writeJsonSchema(
|
|
15
|
+
schema: Schema,
|
|
16
|
+
outputFile: string
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
try {
|
|
19
|
+
await this.ensureOutputPath();
|
|
20
|
+
|
|
21
|
+
const content = this.options.minify
|
|
22
|
+
? JSON.stringify(schema)
|
|
23
|
+
: JSON.stringify(schema, null, 4);
|
|
24
|
+
|
|
25
|
+
await fs.promises.writeFile(outputFile, content, 'utf-8');
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new CodeGenerationError(
|
|
28
|
+
`Failed to write JSON schema: ${error instanceof Error ? error.message : String(error)}`,
|
|
29
|
+
outputFile
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async writeTypeScriptFile(
|
|
35
|
+
content: string,
|
|
36
|
+
outputFile: string
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
try {
|
|
39
|
+
await this.ensureOutputPath();
|
|
40
|
+
await fs.promises.writeFile(outputFile, content, 'utf-8');
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new CodeGenerationError(
|
|
43
|
+
`Failed to write TypeScript file: ${error instanceof Error ? error.message : String(error)}`,
|
|
44
|
+
outputFile
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async ensureOutputPath(): Promise<void> {
|
|
50
|
+
if (!fs.existsSync(this.options.outputPath)) {
|
|
51
|
+
await fs.promises.mkdir(this.options.outputPath, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async cleanOutputDirectory(): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(this.options.outputPath)) {
|
|
58
|
+
const files = await fs.promises.readdir(this.options.outputPath);
|
|
59
|
+
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const filePath = path.join(this.options.outputPath, file);
|
|
62
|
+
const stat = await fs.promises.stat(filePath);
|
|
63
|
+
|
|
64
|
+
if (stat.isFile() && (file.endsWith('.ts') || file.endsWith('.json'))) {
|
|
65
|
+
await fs.promises.unlink(filePath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new CodeGenerationError(
|
|
71
|
+
`Failed to clean output directory: ${error instanceof Error ? error.message : String(error)}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|