lombok-typescript 0.1.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 +98 -0
- package/dist/backend-CkLBcDd8.d.cts +59 -0
- package/dist/backend-CkLBcDd8.d.ts +59 -0
- package/dist/cli/index.cjs +585 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +14 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +579 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/codegen/index.cjs +450 -0
- package/dist/codegen/index.cjs.map +1 -0
- package/dist/codegen/index.d.cts +109 -0
- package/dist/codegen/index.d.ts +109 -0
- package/dist/codegen/index.js +443 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/core/index.cjs +132 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +73 -0
- package/dist/core/index.d.ts +73 -0
- package/dist/core/index.js +128 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +142 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy/index.cjs +271 -0
- package/dist/legacy/index.cjs.map +1 -0
- package/dist/legacy/index.d.cts +32 -0
- package/dist/legacy/index.d.ts +32 -0
- package/dist/legacy/index.js +252 -0
- package/dist/legacy/index.js.map +1 -0
- package/dist/memoize-Bj9pm_cK.d.cts +48 -0
- package/dist/memoize-DvelzGDl.d.ts +48 -0
- package/dist/stage3/index.cjs +275 -0
- package/dist/stage3/index.cjs.map +1 -0
- package/dist/stage3/index.d.cts +97 -0
- package/dist/stage3/index.d.ts +97 -0
- package/dist/stage3/index.js +255 -0
- package/dist/stage3/index.js.map +1 -0
- package/package.json +120 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var cac = require('cac');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var tsMorph = require('ts-morph');
|
|
8
|
+
var bundleRequire = require('bundle-require');
|
|
9
|
+
|
|
10
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
|
+
// src/cli/index.ts
|
|
12
|
+
async function runClean(opts = {}) {
|
|
13
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
14
|
+
const paths = opts.paths ?? [".lombok", "dist", "coverage"];
|
|
15
|
+
const log = opts.log ?? ((msg) => console.info(msg));
|
|
16
|
+
const removed = [];
|
|
17
|
+
const skipped = [];
|
|
18
|
+
for (const rel of paths) {
|
|
19
|
+
const abs = path.resolve(cwd, rel);
|
|
20
|
+
if (fs.existsSync(abs)) {
|
|
21
|
+
fs.rmSync(abs, { recursive: true, force: true });
|
|
22
|
+
removed.push(rel);
|
|
23
|
+
log(`Removed ${rel}/`);
|
|
24
|
+
} else {
|
|
25
|
+
skipped.push(rel);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (removed.length === 0) {
|
|
29
|
+
log("Nothing to clean.");
|
|
30
|
+
}
|
|
31
|
+
return { removed, skipped };
|
|
32
|
+
}
|
|
33
|
+
function analyzeFile(sourceFile) {
|
|
34
|
+
return sourceFile.getClasses().map((cls) => analyzeClassDeclaration(cls));
|
|
35
|
+
}
|
|
36
|
+
function analyzeClassDeclaration(cls) {
|
|
37
|
+
const name = cls.getName() ?? "<anonymous>";
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
decorators: cls.getDecorators().map(toDecoratorInfo),
|
|
41
|
+
fields: cls.getProperties().map(toFieldInfo),
|
|
42
|
+
methods: cls.getMethods().map(toMethodInfo)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function toDecoratorInfo(decorator) {
|
|
46
|
+
return {
|
|
47
|
+
name: decorator.getName(),
|
|
48
|
+
arguments: decorator.getArguments().map((arg) => arg.getText())
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function toFieldInfo(prop) {
|
|
52
|
+
const initializer = prop.getInitializer();
|
|
53
|
+
return {
|
|
54
|
+
name: prop.getName(),
|
|
55
|
+
type: prop.getType().getText(prop),
|
|
56
|
+
isOptional: prop.hasQuestionToken(),
|
|
57
|
+
isReadonly: prop.isReadonly(),
|
|
58
|
+
hasDefault: initializer !== void 0,
|
|
59
|
+
defaultValue: initializer?.getText(),
|
|
60
|
+
decorators: prop.getDecorators().map(toDecoratorInfo)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function toMethodInfo(method) {
|
|
64
|
+
return {
|
|
65
|
+
name: method.getName(),
|
|
66
|
+
returnType: method.getReturnType().getText(method),
|
|
67
|
+
parameters: method.getParameters().map(toParameterInfo),
|
|
68
|
+
decorators: method.getDecorators().map(toDecoratorInfo),
|
|
69
|
+
isAsync: method.isAsync(),
|
|
70
|
+
isStatic: method.isStatic()
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function toParameterInfo(param) {
|
|
74
|
+
return {
|
|
75
|
+
name: param.getName(),
|
|
76
|
+
type: param.getType().getText(param),
|
|
77
|
+
isOptional: param.hasQuestionToken() || param.hasInitializer() || param.getDotDotDotToken()?.getKind() === tsMorph.SyntaxKind.DotDotDotToken,
|
|
78
|
+
decorators: param.getDecorators().map(toDecoratorInfo)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function toImportPath(sourcePath, fromDir) {
|
|
82
|
+
let rel = path.relative(fromDir, sourcePath).replace(/\\/g, "/");
|
|
83
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
84
|
+
return rel.replace(/\.tsx?$/u, ".js");
|
|
85
|
+
}
|
|
86
|
+
function hasCodegenClassDecorator(info) {
|
|
87
|
+
return hasClassDecorator(info, "Builder") || hasClassDecorator(info, "Data") || hasClassDecorator(info, "ToString");
|
|
88
|
+
}
|
|
89
|
+
function hasClassDecorator(info, name) {
|
|
90
|
+
return info.decorators.some((d) => d.name === name);
|
|
91
|
+
}
|
|
92
|
+
function fieldExcludesToString(field) {
|
|
93
|
+
return field.decorators.some(
|
|
94
|
+
(d) => d.name === "ToStringExclude" || d.name === "ToString.Exclude"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function visibleFields(info) {
|
|
98
|
+
if (hasClassDecorator(info, "ToString")) {
|
|
99
|
+
return info.fields.filter((f) => !fieldExcludesToString(f));
|
|
100
|
+
}
|
|
101
|
+
return info.fields;
|
|
102
|
+
}
|
|
103
|
+
function builderClassName(className) {
|
|
104
|
+
return `${className}Builder`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/codegen/emitters/builder.ts
|
|
108
|
+
function emitBuilderClass(info) {
|
|
109
|
+
if (!hasClassDecorator(info, "Builder")) {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
const builderName = builderClassName(info.name);
|
|
113
|
+
const fieldLines = info.fields.map((f) => {
|
|
114
|
+
if (f.isOptional) {
|
|
115
|
+
return ` private _${f.name}?: ${f.type};`;
|
|
116
|
+
}
|
|
117
|
+
return ` private _${f.name}!: ${f.type};`;
|
|
118
|
+
});
|
|
119
|
+
const setterMethods = info.fields.map(
|
|
120
|
+
(f) => `
|
|
121
|
+
${f.name}(value: ${f.type}): ${builderName} {
|
|
122
|
+
this._${f.name} = value;
|
|
123
|
+
return this;
|
|
124
|
+
}`.trim()
|
|
125
|
+
);
|
|
126
|
+
return `
|
|
127
|
+
export class ${builderName} {
|
|
128
|
+
${fieldLines.join("\n")}
|
|
129
|
+
|
|
130
|
+
static builder(): ${builderName} {
|
|
131
|
+
return new ${builderName}();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
${setterMethods.join("\n\n")}
|
|
135
|
+
|
|
136
|
+
build(): ${info.name} {
|
|
137
|
+
const instance = new ${info.name}();
|
|
138
|
+
${info.fields.map((f) => ` instance.${f.name} = this._${f.name}${f.isOptional ? "" : "!"};`).join("\n")}
|
|
139
|
+
return instance;
|
|
140
|
+
}
|
|
141
|
+
}`.trim();
|
|
142
|
+
}
|
|
143
|
+
function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
144
|
+
const relSource = toImportPath(sourcePath, path.dirname(companionOutputPath));
|
|
145
|
+
const lines = [
|
|
146
|
+
"// Auto-generated type augmentation by lombok-typescript.",
|
|
147
|
+
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
148
|
+
"",
|
|
149
|
+
"export {};",
|
|
150
|
+
"",
|
|
151
|
+
`declare module '${relSource}' {`
|
|
152
|
+
];
|
|
153
|
+
for (const info of classes) {
|
|
154
|
+
if (hasClassDecorator(info, "Builder")) {
|
|
155
|
+
const builderName = builderClassName(info.name);
|
|
156
|
+
lines.push(` export class ${builderName} {`);
|
|
157
|
+
lines.push(` static builder(): ${builderName};`);
|
|
158
|
+
for (const f of info.fields) {
|
|
159
|
+
lines.push(` ${f.name}(value: ${f.type}): ${builderName};`);
|
|
160
|
+
}
|
|
161
|
+
lines.push(` build(): ${info.name};`);
|
|
162
|
+
lines.push(" }");
|
|
163
|
+
lines.push("");
|
|
164
|
+
}
|
|
165
|
+
if (hasClassDecorator(info, "Builder")) {
|
|
166
|
+
const builderName = builderClassName(info.name);
|
|
167
|
+
lines.push(` export class ${info.name} {`);
|
|
168
|
+
lines.push(` static builder(): ${builderName};`);
|
|
169
|
+
lines.push(" }");
|
|
170
|
+
lines.push("");
|
|
171
|
+
}
|
|
172
|
+
const augments = [];
|
|
173
|
+
if (hasClassDecorator(info, "Data")) {
|
|
174
|
+
for (const f of info.fields) {
|
|
175
|
+
const g = `get${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
176
|
+
augments.push(` ${g}(): ${f.type};`);
|
|
177
|
+
if (!f.isReadonly) {
|
|
178
|
+
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
179
|
+
augments.push(` ${s}(value: ${f.type}): void;`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
augments.push(` equals(other: ${info.name} | null | undefined): boolean;`);
|
|
183
|
+
augments.push(` toString(): string;`);
|
|
184
|
+
} else if (hasClassDecorator(info, "ToString")) {
|
|
185
|
+
augments.push(" toString(): string;");
|
|
186
|
+
}
|
|
187
|
+
if (augments.length > 0) {
|
|
188
|
+
lines.push(` interface ${info.name} {`);
|
|
189
|
+
lines.push(...augments);
|
|
190
|
+
lines.push(" }");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
lines.push("}");
|
|
194
|
+
lines.push("");
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/codegen/emitters/index.ts
|
|
199
|
+
function emitImports(classes, importPath) {
|
|
200
|
+
const names = classes.filter(hasCodegenClassDecorator).map((c) => c.name);
|
|
201
|
+
if (names.length === 0) return "";
|
|
202
|
+
return `import { ${names.join(", ")} } from '${importPath}';
|
|
203
|
+
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
function emitToStringFn(info) {
|
|
207
|
+
if (!hasClassDecorator(info, "ToString") && !hasClassDecorator(info, "Data")) return "";
|
|
208
|
+
const fields = visibleFields(info);
|
|
209
|
+
const parts = fields.map((f) => `${f.name}=\${String(this.${f.name})}`).join(", ");
|
|
210
|
+
return `
|
|
211
|
+
function ${info.name}_toString(this: ${info.name}): string {
|
|
212
|
+
return \`${info.name}(${parts})\`;
|
|
213
|
+
}`.trim();
|
|
214
|
+
}
|
|
215
|
+
function emitApplyMixin(info) {
|
|
216
|
+
const assignments = [];
|
|
217
|
+
if (hasClassDecorator(info, "Data")) {
|
|
218
|
+
for (const f of info.fields) {
|
|
219
|
+
const g = `get${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
220
|
+
assignments.push(`prototype.${g} = ${info.name}_${g};`);
|
|
221
|
+
if (!f.isReadonly) {
|
|
222
|
+
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
223
|
+
assignments.push(`prototype.${s} = ${info.name}_${s};`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
assignments.push(`prototype.equals = ${info.name}_equals;`);
|
|
227
|
+
assignments.push(`prototype.toString = ${info.name}_toString;`);
|
|
228
|
+
} else if (hasClassDecorator(info, "ToString")) {
|
|
229
|
+
assignments.push(`prototype.toString = ${info.name}_toString;`);
|
|
230
|
+
}
|
|
231
|
+
if (hasClassDecorator(info, "Builder")) {
|
|
232
|
+
const bName = builderClassName(info.name);
|
|
233
|
+
assignments.push(
|
|
234
|
+
`(ctor as typeof ${info.name} & { builder(): ${bName} }).builder = ${info.name}_builder;`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (assignments.length === 0) return "";
|
|
238
|
+
return `
|
|
239
|
+
export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
240
|
+
const prototype = ctor.prototype as unknown as Record<string, unknown>;
|
|
241
|
+
${assignments.join("\n ")}
|
|
242
|
+
}`.trim();
|
|
243
|
+
}
|
|
244
|
+
function emitDataFns(info) {
|
|
245
|
+
if (!hasClassDecorator(info, "Data")) return "";
|
|
246
|
+
const fns = [];
|
|
247
|
+
for (const f of info.fields) {
|
|
248
|
+
const g = `get${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
249
|
+
fns.push(
|
|
250
|
+
`
|
|
251
|
+
function ${info.name}_${g}(this: ${info.name}): ${f.type} {
|
|
252
|
+
return this.${f.name};
|
|
253
|
+
}`.trim()
|
|
254
|
+
);
|
|
255
|
+
if (!f.isReadonly) {
|
|
256
|
+
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
257
|
+
fns.push(
|
|
258
|
+
`
|
|
259
|
+
function ${info.name}_${s}(this: ${info.name}, value: ${f.type}): void {
|
|
260
|
+
this.${f.name} = value;
|
|
261
|
+
}`.trim()
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const equalsBody = info.fields.map((f) => `this.${f.name} === other.${f.name}`).join(" &&\n ");
|
|
266
|
+
fns.push(
|
|
267
|
+
`
|
|
268
|
+
function ${info.name}_equals(this: ${info.name}, other: ${info.name} | null | undefined): boolean {
|
|
269
|
+
if (other === null || other === undefined) return false;
|
|
270
|
+
if (!(other instanceof (this.constructor as typeof ${info.name}))) return false;
|
|
271
|
+
return ${equalsBody || "true"};
|
|
272
|
+
}`.trim()
|
|
273
|
+
);
|
|
274
|
+
fns.push(emitToStringFn(info));
|
|
275
|
+
return fns.filter(Boolean).join("\n\n");
|
|
276
|
+
}
|
|
277
|
+
function emitBuilderFn(info) {
|
|
278
|
+
if (!hasClassDecorator(info, "Builder")) return "";
|
|
279
|
+
const bName = builderClassName(info.name);
|
|
280
|
+
return `
|
|
281
|
+
function ${info.name}_builder(): ${bName} {
|
|
282
|
+
return ${bName}.builder();
|
|
283
|
+
}`.trim();
|
|
284
|
+
}
|
|
285
|
+
function emitCompanionFile(sourcePath, companionOutputPath, classes, cwd) {
|
|
286
|
+
const header = [
|
|
287
|
+
"// Auto-generated by lombok-typescript.",
|
|
288
|
+
"// Source: " + path.relative(cwd, sourcePath).replace(/\\/g, "/"),
|
|
289
|
+
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
290
|
+
""
|
|
291
|
+
].join("\n");
|
|
292
|
+
const importPath = toImportPath(sourcePath, path.dirname(companionOutputPath));
|
|
293
|
+
const blocks = [];
|
|
294
|
+
for (const info of classes) {
|
|
295
|
+
const builder = emitBuilderClass(info);
|
|
296
|
+
if (builder) blocks.push(builder);
|
|
297
|
+
if (hasClassDecorator(info, "Data")) {
|
|
298
|
+
blocks.push(emitDataFns(info));
|
|
299
|
+
} else {
|
|
300
|
+
const ts2 = emitToStringFn(info);
|
|
301
|
+
if (ts2) blocks.push(ts2);
|
|
302
|
+
}
|
|
303
|
+
const builderFn = emitBuilderFn(info);
|
|
304
|
+
if (builderFn) blocks.push(builderFn);
|
|
305
|
+
const apply = emitApplyMixin(info);
|
|
306
|
+
if (apply) blocks.push(apply);
|
|
307
|
+
}
|
|
308
|
+
const applyAll = classes.filter((c) => emitApplyMixin(c)).length > 0 ? `
|
|
309
|
+
|
|
310
|
+
export function applyAllGenerated(handlers: {
|
|
311
|
+
${classes.filter((c) => emitApplyMixin(c)).map((c) => ` ${c.name}: typeof ${c.name};`).join("\n")}
|
|
312
|
+
}): void {
|
|
313
|
+
${classes.filter((c) => emitApplyMixin(c)).map((c) => ` apply${c.name}Generated(handlers.${c.name});`).join("\n")}
|
|
314
|
+
}
|
|
315
|
+
` : "\nexport {};\n";
|
|
316
|
+
const imports = emitImports(classes, importPath);
|
|
317
|
+
const ts = header + imports + blocks.join("\n\n") + applyAll;
|
|
318
|
+
const dts = emitDeclarationShim(sourcePath, companionOutputPath, classes);
|
|
319
|
+
return { ts, dts };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/codegen/generator.ts
|
|
323
|
+
var DEFAULT_OPTIONS = {
|
|
324
|
+
outputDir: ".lombok",
|
|
325
|
+
watch: false,
|
|
326
|
+
include: ["src/**/*.ts"],
|
|
327
|
+
exclude: ["node_modules", "**/*.test.ts", "**/*.spec.ts", "dist", ".lombok"],
|
|
328
|
+
tsConfigPath: "tsconfig.json"
|
|
329
|
+
};
|
|
330
|
+
var CodeGenerator = class {
|
|
331
|
+
options;
|
|
332
|
+
constructor(options = {}) {
|
|
333
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Run codegen against all matching source files.
|
|
337
|
+
*
|
|
338
|
+
* Uses the project's `tsConfigPath` if it exists, otherwise an in-memory
|
|
339
|
+
* project that processes the configured globs. For each file with at least
|
|
340
|
+
* one class, writes a companion stub to `<outputDir>/<rel-path>.lombok.ts`.
|
|
341
|
+
*/
|
|
342
|
+
async generate() {
|
|
343
|
+
const project = this.createProject();
|
|
344
|
+
const sourceFiles = project.getSourceFiles();
|
|
345
|
+
const generated = [];
|
|
346
|
+
for (const sourceFile of sourceFiles) {
|
|
347
|
+
if (!this.shouldProcess(sourceFile.getFilePath())) continue;
|
|
348
|
+
const classes = analyzeFile(sourceFile);
|
|
349
|
+
if (classes.length === 0) continue;
|
|
350
|
+
const sourcePath = sourceFile.getFilePath();
|
|
351
|
+
const outputPath = this.computeOutputPath(sourcePath);
|
|
352
|
+
const { ts, dts } = emitCompanionFile(sourcePath, outputPath, classes, process.cwd());
|
|
353
|
+
const content = ts;
|
|
354
|
+
this.writeOutput(outputPath, content);
|
|
355
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
356
|
+
generated.push({
|
|
357
|
+
sourcePath,
|
|
358
|
+
outputPath,
|
|
359
|
+
content,
|
|
360
|
+
processedClasses: classes.map((c) => c.name)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return generated;
|
|
364
|
+
}
|
|
365
|
+
/** Generate code for a single source file path. */
|
|
366
|
+
async generateForFile(filePath) {
|
|
367
|
+
const project = new tsMorph.Project({
|
|
368
|
+
useInMemoryFileSystem: false,
|
|
369
|
+
compilerOptions: { allowJs: false }
|
|
370
|
+
});
|
|
371
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
372
|
+
const classes = analyzeFile(sourceFile);
|
|
373
|
+
if (classes.length === 0) return null;
|
|
374
|
+
const outputPath = this.computeOutputPath(filePath);
|
|
375
|
+
const { ts, dts } = emitCompanionFile(filePath, outputPath, classes, process.cwd());
|
|
376
|
+
const content = ts;
|
|
377
|
+
this.writeOutput(outputPath, content);
|
|
378
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
379
|
+
return {
|
|
380
|
+
sourcePath: filePath,
|
|
381
|
+
outputPath,
|
|
382
|
+
content,
|
|
383
|
+
processedClasses: classes.map((c) => c.name)
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/** Watch mode is not implemented yet. Re-run `lombok-ts generate` for now. */
|
|
387
|
+
async watch() {
|
|
388
|
+
throw new Error(
|
|
389
|
+
"Watch mode is not implemented yet. Re-run `lombok-ts generate` after changes."
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
// Internal helpers
|
|
393
|
+
createProject() {
|
|
394
|
+
const tsConfig = path.resolve(this.options.tsConfigPath);
|
|
395
|
+
if (fs.existsSync(tsConfig)) {
|
|
396
|
+
return new tsMorph.Project({ tsConfigFilePath: tsConfig });
|
|
397
|
+
}
|
|
398
|
+
const project = new tsMorph.Project({
|
|
399
|
+
compilerOptions: {
|
|
400
|
+
allowJs: false,
|
|
401
|
+
target: 99
|
|
402
|
+
/* ESNext */
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
if (this.options.include.length > 0) {
|
|
406
|
+
project.addSourceFilesAtPaths(this.options.include);
|
|
407
|
+
}
|
|
408
|
+
return project;
|
|
409
|
+
}
|
|
410
|
+
shouldProcess(filePath) {
|
|
411
|
+
const abs = filePath.replace(/\\/g, "/");
|
|
412
|
+
const rel = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
413
|
+
const excluded = this.options.exclude.some(
|
|
414
|
+
(pat) => matchesGlob(rel, pat) || matchesGlob(abs, pat)
|
|
415
|
+
);
|
|
416
|
+
if (excluded) return false;
|
|
417
|
+
if (this.options.include.length === 0) return true;
|
|
418
|
+
return this.options.include.some((pat) => matchesGlob(rel, pat) || matchesGlob(abs, pat));
|
|
419
|
+
}
|
|
420
|
+
computeOutputPath(sourcePath) {
|
|
421
|
+
const cwd = process.cwd();
|
|
422
|
+
const rel = path.relative(cwd, sourcePath);
|
|
423
|
+
const base = rel.replace(/\.ts$/u, ".lombok.ts");
|
|
424
|
+
return path.resolve(cwd, this.options.outputDir, base);
|
|
425
|
+
}
|
|
426
|
+
renderCompanion(_sourcePath, _classes) {
|
|
427
|
+
return "export {};\n";
|
|
428
|
+
}
|
|
429
|
+
writeOutput(outputPath, content) {
|
|
430
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
431
|
+
fs.writeFileSync(outputPath, content, "utf8");
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
function matchesGlob(filePath, pattern) {
|
|
435
|
+
const norm = filePath.replace(/\\/g, "/");
|
|
436
|
+
const regex = new RegExp(
|
|
437
|
+
"^" + pattern.replace(/[.+^$()|[\]{}]/g, "\\$&").replace(/\*\*\//g, "__GLOBSTAR_SLASH__").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR_SLASH__/g, "(?:.*/)?").replace(/__GLOBSTAR__/g, ".*") + "$",
|
|
438
|
+
"u"
|
|
439
|
+
);
|
|
440
|
+
return regex.test(norm);
|
|
441
|
+
}
|
|
442
|
+
var CONFIG_FILE_NAMES = [
|
|
443
|
+
"lombok.config.ts",
|
|
444
|
+
"lombok.config.mts",
|
|
445
|
+
"lombok.config.cts",
|
|
446
|
+
"lombok.config.js",
|
|
447
|
+
"lombok.config.mjs",
|
|
448
|
+
"lombok.config.cjs"
|
|
449
|
+
];
|
|
450
|
+
function findConfigFile(searchDir = process.cwd()) {
|
|
451
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
452
|
+
const candidate = path.resolve(searchDir, name);
|
|
453
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
454
|
+
}
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
async function loadConfig(searchDir = process.cwd()) {
|
|
458
|
+
const filepath = findConfigFile(searchDir);
|
|
459
|
+
if (!filepath) return void 0;
|
|
460
|
+
return loadConfigFromFile(filepath);
|
|
461
|
+
}
|
|
462
|
+
async function loadConfigFromFile(filepath) {
|
|
463
|
+
if (!fs.existsSync(filepath)) {
|
|
464
|
+
throw new Error(`Config file not found: ${filepath}`);
|
|
465
|
+
}
|
|
466
|
+
const { mod } = await bundleRequire.bundleRequire({ filepath });
|
|
467
|
+
const hasDefault = mod !== null && typeof mod === "object" && "default" in mod;
|
|
468
|
+
const candidate = hasDefault ? mod.default : mod;
|
|
469
|
+
if (candidate === null || candidate === void 0 || typeof candidate !== "object") {
|
|
470
|
+
throw new Error(
|
|
471
|
+
`Config file ${filepath} did not export a valid config object. Make sure it has a default export of \`defineConfig({...})\`.`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return { filepath, config: candidate };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/cli/commands/generate.ts
|
|
478
|
+
async function runGenerate(opts = {}) {
|
|
479
|
+
const log = opts.log ?? ((msg) => console.info(msg));
|
|
480
|
+
const loaded = await loadConfig(opts.cwd);
|
|
481
|
+
const codegenOpts = {
|
|
482
|
+
...loaded?.config.codegen ?? {},
|
|
483
|
+
...opts.overrides
|
|
484
|
+
};
|
|
485
|
+
if (loaded) {
|
|
486
|
+
log(`Loaded config from ${loaded.filepath}`);
|
|
487
|
+
} else {
|
|
488
|
+
log("No lombok.config.* file found, using defaults.");
|
|
489
|
+
}
|
|
490
|
+
const generator = new CodeGenerator(codegenOpts);
|
|
491
|
+
const generated = await generator.generate();
|
|
492
|
+
log(
|
|
493
|
+
`Generated ${generated.length} companion file${generated.length === 1 ? "" : "s"} under ${generator.options.outputDir}/`
|
|
494
|
+
);
|
|
495
|
+
for (const entry of generated) {
|
|
496
|
+
log(` - ${entry.outputPath} (${entry.processedClasses.join(", ")})`);
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
generatedCount: generated.length,
|
|
500
|
+
outputDir: generator.options.outputDir
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
var TEMPLATE = `import { defineConfig } from 'lombok-typescript';
|
|
504
|
+
|
|
505
|
+
export default defineConfig({
|
|
506
|
+
// Decorator backend: 'legacy' (experimentalDecorators), 'stage3' (ECMAScript),
|
|
507
|
+
// or 'auto' to pick based on your tsconfig.
|
|
508
|
+
backend: 'auto',
|
|
509
|
+
|
|
510
|
+
// Codegen options used by \`lombok-ts generate\`.
|
|
511
|
+
codegen: {
|
|
512
|
+
outputDir: '.lombok',
|
|
513
|
+
include: ['src/**/*.ts'],
|
|
514
|
+
exclude: ['node_modules', '**/*.test.ts', '**/*.spec.ts'],
|
|
515
|
+
tsConfigPath: 'tsconfig.json',
|
|
516
|
+
watch: false,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
`;
|
|
520
|
+
async function runInit(opts = {}) {
|
|
521
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
522
|
+
const fileName = opts.fileName ?? "lombok.config.ts";
|
|
523
|
+
const filepath = path.resolve(cwd, fileName);
|
|
524
|
+
const log = opts.log ?? ((msg) => console.info(msg));
|
|
525
|
+
if (fs.existsSync(filepath) && !opts.force) {
|
|
526
|
+
log(`${fileName} already exists. Use --force to overwrite.`);
|
|
527
|
+
return { filepath, created: false };
|
|
528
|
+
}
|
|
529
|
+
fs.writeFileSync(filepath, TEMPLATE, "utf8");
|
|
530
|
+
log(`Created ${fileName}`);
|
|
531
|
+
return { filepath, created: true };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/cli/commands/watch.ts
|
|
535
|
+
async function runWatch(opts = {}) {
|
|
536
|
+
const log = opts.log ?? ((msg) => console.info(msg));
|
|
537
|
+
log("Watch mode is not implemented yet (Phase 2).");
|
|
538
|
+
log("Re-run `lombok-ts generate` after changes.");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/cli/index.ts
|
|
542
|
+
var CLI_NAME = "lombok-ts";
|
|
543
|
+
var CLI_VERSION = "0.1.0-pre";
|
|
544
|
+
function buildCli() {
|
|
545
|
+
const cli = cac.cac(CLI_NAME);
|
|
546
|
+
cli.command("generate", "Run codegen once against the configured source files").alias("gen").option("--output-dir <dir>", "Override codegen output directory").option("--ts-config <path>", "Path to tsconfig.json (default: tsconfig.json)").action(async (options) => {
|
|
547
|
+
await runGenerate({
|
|
548
|
+
overrides: {
|
|
549
|
+
...options.outputDir ? { outputDir: options.outputDir } : {},
|
|
550
|
+
...options.tsConfig ? { tsConfigPath: options.tsConfig } : {}
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
cli.command("watch", "Watch source files and regenerate on change (Phase 2)").action(async () => {
|
|
555
|
+
await runWatch();
|
|
556
|
+
});
|
|
557
|
+
cli.command("init", "Create a starter lombok.config.ts in the current directory").option("--force", "Overwrite an existing config file").action(async (options) => {
|
|
558
|
+
await runInit({ force: options.force });
|
|
559
|
+
});
|
|
560
|
+
cli.command("clean", "Remove generated files and build artifacts").action(async () => {
|
|
561
|
+
await runClean();
|
|
562
|
+
});
|
|
563
|
+
cli.help();
|
|
564
|
+
cli.version(CLI_VERSION);
|
|
565
|
+
return cli;
|
|
566
|
+
}
|
|
567
|
+
async function runCli(argv = process.argv) {
|
|
568
|
+
const cli = buildCli();
|
|
569
|
+
cli.parse(argv, { run: false });
|
|
570
|
+
await cli.runMatchedCommand();
|
|
571
|
+
}
|
|
572
|
+
var isMain = typeof process !== "undefined" && typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && process.argv[1] !== void 0 && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)).endsWith(process.argv[1].split("/").pop() ?? "");
|
|
573
|
+
if (isMain) {
|
|
574
|
+
runCli().catch((err) => {
|
|
575
|
+
console.error(err);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
exports.CLI_NAME = CLI_NAME;
|
|
581
|
+
exports.CLI_VERSION = CLI_VERSION;
|
|
582
|
+
exports.buildCli = buildCli;
|
|
583
|
+
exports.runCli = runCli;
|
|
584
|
+
//# sourceMappingURL=index.cjs.map
|
|
585
|
+
//# sourceMappingURL=index.cjs.map
|