js-bao 0.4.0 → 0.4.1
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/browser.cjs +76 -16
- package/dist/browser.d.cts +20 -1
- package/dist/browser.d.ts +20 -1
- package/dist/browser.js +76 -16
- package/dist/client.d.cts +7 -0
- package/dist/client.d.ts +7 -0
- package/dist/cloudflare-do.cjs +70 -5
- package/dist/cloudflare-do.d.cts +22 -3
- package/dist/cloudflare-do.d.ts +22 -3
- package/dist/cloudflare-do.js +70 -5
- package/dist/cloudflare.cjs +70 -5
- package/dist/cloudflare.d.cts +20 -1
- package/dist/cloudflare.d.ts +20 -1
- package/dist/cloudflare.js +70 -5
- package/dist/codegen-v2.cjs +1766 -0
- package/dist/codegen-v2.d.cts +1 -0
- package/dist/codegen.cjs +7 -10
- package/dist/index.cjs +78 -18
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +78 -18
- package/dist/node.cjs +78 -18
- package/dist/node.d.cts +21 -2
- package/dist/node.d.ts +21 -2
- package/dist/node.js +78 -18
- package/package.json +7 -10
|
@@ -0,0 +1,1766 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// src/models/StringSet.ts
|
|
30
|
+
var init_StringSet = __esm({
|
|
31
|
+
"src/models/StringSet.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// src/types/documentTypes.ts
|
|
37
|
+
var init_documentTypes = __esm({
|
|
38
|
+
"src/types/documentTypes.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/types/queryTypes.ts
|
|
44
|
+
var init_queryTypes = __esm({
|
|
45
|
+
"src/types/queryTypes.ts"() {
|
|
46
|
+
"use strict";
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// src/query/CursorManager.ts
|
|
51
|
+
var init_CursorManager = __esm({
|
|
52
|
+
"src/query/CursorManager.ts"() {
|
|
53
|
+
"use strict";
|
|
54
|
+
init_queryTypes();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// src/utils/sql.ts
|
|
59
|
+
var init_sql = __esm({
|
|
60
|
+
"src/utils/sql.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// src/utils/patterns.ts
|
|
66
|
+
var init_patterns = __esm({
|
|
67
|
+
"src/utils/patterns.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/query/DocumentQueryTranslator.ts
|
|
73
|
+
var init_DocumentQueryTranslator = __esm({
|
|
74
|
+
"src/query/DocumentQueryTranslator.ts"() {
|
|
75
|
+
"use strict";
|
|
76
|
+
init_queryTypes();
|
|
77
|
+
init_CursorManager();
|
|
78
|
+
init_sql();
|
|
79
|
+
init_patterns();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// src/models/metaSync.ts
|
|
84
|
+
function registerFunctionDefault(fn, name) {
|
|
85
|
+
KNOWN_FUNCTION_DEFAULTS.set(fn, name);
|
|
86
|
+
}
|
|
87
|
+
var Y, KNOWN_FUNCTION_DEFAULTS;
|
|
88
|
+
var init_metaSync = __esm({
|
|
89
|
+
"src/models/metaSync.ts"() {
|
|
90
|
+
"use strict";
|
|
91
|
+
Y = __toESM(require("yjs"), 1);
|
|
92
|
+
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/models/BaseModel.ts
|
|
97
|
+
function generateULID() {
|
|
98
|
+
return (0, import_ulid.ulid)();
|
|
99
|
+
}
|
|
100
|
+
var Y2, import_ulid;
|
|
101
|
+
var init_BaseModel = __esm({
|
|
102
|
+
"src/models/BaseModel.ts"() {
|
|
103
|
+
"use strict";
|
|
104
|
+
Y2 = __toESM(require("yjs"), 1);
|
|
105
|
+
import_ulid = require("ulid");
|
|
106
|
+
init_StringSet();
|
|
107
|
+
init_documentTypes();
|
|
108
|
+
init_DocumentQueryTranslator();
|
|
109
|
+
init_CursorManager();
|
|
110
|
+
init_sql();
|
|
111
|
+
init_metaSync();
|
|
112
|
+
registerFunctionDefault(generateULID, "generate_ulid");
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/models/relationshipManager.ts
|
|
117
|
+
var init_relationshipManager = __esm({
|
|
118
|
+
"src/models/relationshipManager.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
init_BaseModel();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// src/models/ModelRegistry.ts
|
|
125
|
+
var init_ModelRegistry = __esm({
|
|
126
|
+
"src/models/ModelRegistry.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
init_BaseModel();
|
|
129
|
+
init_relationshipManager();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// src/cli/codegen-v2.ts
|
|
134
|
+
var import_commander = require("commander");
|
|
135
|
+
var path3 = __toESM(require("path"), 1);
|
|
136
|
+
|
|
137
|
+
// src/cli/v2/generator.ts
|
|
138
|
+
var path = __toESM(require("path"), 1);
|
|
139
|
+
var import_fs = require("fs");
|
|
140
|
+
|
|
141
|
+
// src/models/tomlLoader.ts
|
|
142
|
+
var import_smol_toml = require("smol-toml");
|
|
143
|
+
|
|
144
|
+
// src/models/schema.ts
|
|
145
|
+
init_BaseModel();
|
|
146
|
+
init_ModelRegistry();
|
|
147
|
+
function defineModelSchema(input) {
|
|
148
|
+
const { name, fields } = input;
|
|
149
|
+
const options = {
|
|
150
|
+
name,
|
|
151
|
+
className: input.options?.className,
|
|
152
|
+
uniqueConstraints: input.options?.uniqueConstraints ? [...input.options.uniqueConstraints] : void 0,
|
|
153
|
+
relationships: input.options?.relationships
|
|
154
|
+
};
|
|
155
|
+
const schema = {
|
|
156
|
+
name,
|
|
157
|
+
fields,
|
|
158
|
+
options,
|
|
159
|
+
runtimeShape: void 0,
|
|
160
|
+
buildRuntimeShape(modelClass) {
|
|
161
|
+
if (schema.runtimeShape && schema.runtimeShape.class === modelClass) {
|
|
162
|
+
return schema.runtimeShape;
|
|
163
|
+
}
|
|
164
|
+
const fieldsMap = /* @__PURE__ */ new Map();
|
|
165
|
+
for (const [fieldName, fieldOptions] of Object.entries(fields)) {
|
|
166
|
+
fieldsMap.set(fieldName, { ...fieldOptions });
|
|
167
|
+
}
|
|
168
|
+
const resolvedUniqueConstraints = resolveUniqueConstraints(
|
|
169
|
+
name,
|
|
170
|
+
fieldsMap,
|
|
171
|
+
options.uniqueConstraints
|
|
172
|
+
);
|
|
173
|
+
schema.runtimeShape = {
|
|
174
|
+
class: modelClass,
|
|
175
|
+
options,
|
|
176
|
+
fields: fieldsMap,
|
|
177
|
+
resolvedUniqueConstraints
|
|
178
|
+
};
|
|
179
|
+
return schema.runtimeShape;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return schema;
|
|
183
|
+
}
|
|
184
|
+
function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
185
|
+
const resolved = [];
|
|
186
|
+
for (const [fieldName, options] of fields.entries()) {
|
|
187
|
+
if (options.unique) {
|
|
188
|
+
resolved.push({
|
|
189
|
+
name: `${modelName}_${fieldName}_unique`,
|
|
190
|
+
fields: [fieldName]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (customConstraints) {
|
|
195
|
+
for (const constraint of customConstraints) {
|
|
196
|
+
const missingField = constraint.fields.find(
|
|
197
|
+
(field) => !fields.has(field)
|
|
198
|
+
);
|
|
199
|
+
if (missingField) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`[defineModelSchema] Unique constraint "${constraint.name}" for model "${modelName}" references unknown field "${missingField}". Skipping.`
|
|
202
|
+
);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (resolved.some((item) => item.name === constraint.name)) {
|
|
206
|
+
console.warn(
|
|
207
|
+
`[defineModelSchema] Duplicate unique constraint name "${constraint.name}" in model "${modelName}".`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
resolved.push({
|
|
211
|
+
name: constraint.name,
|
|
212
|
+
fields: [...constraint.fields]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return resolved;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/models/tomlLoader.ts
|
|
220
|
+
var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
221
|
+
"string",
|
|
222
|
+
"number",
|
|
223
|
+
"boolean",
|
|
224
|
+
"date",
|
|
225
|
+
"id",
|
|
226
|
+
"stringset"
|
|
227
|
+
]);
|
|
228
|
+
var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
229
|
+
"type",
|
|
230
|
+
"indexed",
|
|
231
|
+
"unique",
|
|
232
|
+
"required",
|
|
233
|
+
"auto_assign",
|
|
234
|
+
"max_length",
|
|
235
|
+
"max_count",
|
|
236
|
+
"default"
|
|
237
|
+
]);
|
|
238
|
+
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
239
|
+
"fields",
|
|
240
|
+
"relationships",
|
|
241
|
+
"unique_constraints",
|
|
242
|
+
"class_name"
|
|
243
|
+
]);
|
|
244
|
+
var KNOWN_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
245
|
+
"type",
|
|
246
|
+
"model",
|
|
247
|
+
"related_id_field",
|
|
248
|
+
"join_model",
|
|
249
|
+
"join_model_local_field",
|
|
250
|
+
"join_model_related_field",
|
|
251
|
+
"order_by_field",
|
|
252
|
+
"order_direction",
|
|
253
|
+
"join_model_order_by_field",
|
|
254
|
+
"join_model_order_direction"
|
|
255
|
+
]);
|
|
256
|
+
var KNOWN_UNIQUE_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["name", "fields"]);
|
|
257
|
+
function checkUnknownKeys(raw, known, context, strict) {
|
|
258
|
+
if (!strict) return;
|
|
259
|
+
for (const key of Object.keys(raw)) {
|
|
260
|
+
if (!known.has(key)) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`${context}: unknown key "${key}". Allowed: ${[...known].join(", ")}`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function parseFieldOptions(raw, context, strict) {
|
|
268
|
+
if (!raw.type || !VALID_FIELD_TYPES.has(raw.type)) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Invalid field type "${raw.type}". Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
checkUnknownKeys(raw, KNOWN_FIELD_KEYS, context, strict);
|
|
274
|
+
const opts = { type: raw.type };
|
|
275
|
+
if (raw.indexed === true) opts.indexed = true;
|
|
276
|
+
if (raw.unique === true) opts.unique = true;
|
|
277
|
+
if (raw.required === true) opts.required = true;
|
|
278
|
+
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
279
|
+
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
280
|
+
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
281
|
+
if (raw.default !== void 0) opts.default = raw.default;
|
|
282
|
+
return opts;
|
|
283
|
+
}
|
|
284
|
+
function requireField(raw, field, context) {
|
|
285
|
+
if (!raw[field]) {
|
|
286
|
+
throw new Error(`Relationship ${context}: missing required field "${field}"`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function parseRelationship(raw, context, strict) {
|
|
290
|
+
const type = raw.type;
|
|
291
|
+
checkUnknownKeys(raw, KNOWN_RELATIONSHIP_KEYS, context, strict);
|
|
292
|
+
if (type === "refersTo") {
|
|
293
|
+
requireField(raw, "model", "refersTo");
|
|
294
|
+
requireField(raw, "related_id_field", "refersTo");
|
|
295
|
+
return {
|
|
296
|
+
type: "refersTo",
|
|
297
|
+
model: raw.model,
|
|
298
|
+
relatedIdField: raw.related_id_field
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (type === "hasMany") {
|
|
302
|
+
requireField(raw, "model", "hasMany");
|
|
303
|
+
requireField(raw, "related_id_field", "hasMany");
|
|
304
|
+
const rel = {
|
|
305
|
+
type: "hasMany",
|
|
306
|
+
model: raw.model,
|
|
307
|
+
relatedIdField: raw.related_id_field
|
|
308
|
+
};
|
|
309
|
+
if (raw.order_by_field) rel.orderByField = raw.order_by_field;
|
|
310
|
+
if (raw.order_direction) rel.orderDirection = raw.order_direction;
|
|
311
|
+
return rel;
|
|
312
|
+
}
|
|
313
|
+
if (type === "hasManyThrough") {
|
|
314
|
+
requireField(raw, "model", "hasManyThrough");
|
|
315
|
+
requireField(raw, "join_model", "hasManyThrough");
|
|
316
|
+
requireField(raw, "join_model_local_field", "hasManyThrough");
|
|
317
|
+
requireField(raw, "join_model_related_field", "hasManyThrough");
|
|
318
|
+
const rel = {
|
|
319
|
+
type: "hasManyThrough",
|
|
320
|
+
model: raw.model,
|
|
321
|
+
joinModel: raw.join_model,
|
|
322
|
+
joinModelLocalField: raw.join_model_local_field,
|
|
323
|
+
joinModelRelatedField: raw.join_model_related_field
|
|
324
|
+
};
|
|
325
|
+
if (raw.join_model_order_by_field)
|
|
326
|
+
rel.joinModelOrderByField = raw.join_model_order_by_field;
|
|
327
|
+
if (raw.join_model_order_direction)
|
|
328
|
+
rel.joinModelOrderDirection = raw.join_model_order_direction;
|
|
329
|
+
return rel;
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`Unknown relationship type: ${type}`);
|
|
332
|
+
}
|
|
333
|
+
function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
334
|
+
const strict = options.strict !== false;
|
|
335
|
+
const parsed = (0, import_smol_toml.parse)(tomlString);
|
|
336
|
+
const models = parsed.models;
|
|
337
|
+
if (!models || typeof models !== "object") {
|
|
338
|
+
throw new Error("TOML schema must have a [models] section");
|
|
339
|
+
}
|
|
340
|
+
const schemas = [];
|
|
341
|
+
for (const [modelName, modelDef] of Object.entries(models)) {
|
|
342
|
+
checkUnknownKeys(
|
|
343
|
+
modelDef,
|
|
344
|
+
KNOWN_MODEL_KEYS,
|
|
345
|
+
`[models.${modelName}]`,
|
|
346
|
+
strict
|
|
347
|
+
);
|
|
348
|
+
const fields = {};
|
|
349
|
+
if (modelDef.fields) {
|
|
350
|
+
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
351
|
+
fields[fieldName] = parseFieldOptions(
|
|
352
|
+
fieldDef,
|
|
353
|
+
`[models.${modelName}.fields.${fieldName}]`,
|
|
354
|
+
strict
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
let relationships;
|
|
359
|
+
if (modelDef.relationships) {
|
|
360
|
+
relationships = {};
|
|
361
|
+
for (const [relName, relDef] of Object.entries(
|
|
362
|
+
modelDef.relationships
|
|
363
|
+
)) {
|
|
364
|
+
relationships[relName] = parseRelationship(
|
|
365
|
+
relDef,
|
|
366
|
+
`[models.${modelName}.relationships.${relName}]`,
|
|
367
|
+
strict
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
let uniqueConstraints;
|
|
372
|
+
if (modelDef.unique_constraints) {
|
|
373
|
+
uniqueConstraints = [];
|
|
374
|
+
for (const raw of modelDef.unique_constraints) {
|
|
375
|
+
checkUnknownKeys(
|
|
376
|
+
raw,
|
|
377
|
+
KNOWN_UNIQUE_CONSTRAINT_KEYS,
|
|
378
|
+
`[[models.${modelName}.unique_constraints]]`,
|
|
379
|
+
strict
|
|
380
|
+
);
|
|
381
|
+
uniqueConstraints.push({
|
|
382
|
+
name: raw.name,
|
|
383
|
+
fields: [...raw.fields]
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const className = typeof modelDef.class_name === "string" ? modelDef.class_name : void 0;
|
|
388
|
+
schemas.push(
|
|
389
|
+
defineModelSchema({
|
|
390
|
+
name: modelName,
|
|
391
|
+
fields,
|
|
392
|
+
options: {
|
|
393
|
+
className,
|
|
394
|
+
uniqueConstraints,
|
|
395
|
+
relationships
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return schemas;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/cli/v2/pluralization.ts
|
|
404
|
+
function singularizeToPascalCase(snakePlural) {
|
|
405
|
+
if (!snakePlural || snakePlural.length === 0) return null;
|
|
406
|
+
const parts = snakePlural.split("_");
|
|
407
|
+
if (parts.some((p) => p.length === 0)) return null;
|
|
408
|
+
const last = parts[parts.length - 1];
|
|
409
|
+
const singularizedLast = singularizeWord(last);
|
|
410
|
+
if (singularizedLast === null) return null;
|
|
411
|
+
parts[parts.length - 1] = singularizedLast;
|
|
412
|
+
return parts.map(pascalSegment).join("");
|
|
413
|
+
}
|
|
414
|
+
function singularizeWord(word) {
|
|
415
|
+
if (!word) return null;
|
|
416
|
+
if (!endsWithS(word)) return null;
|
|
417
|
+
if (word.endsWith("ies") && word.length > 3) {
|
|
418
|
+
return word.slice(0, -3) + "y";
|
|
419
|
+
}
|
|
420
|
+
if (word.endsWith("xes") && word.length > 3) {
|
|
421
|
+
return word.slice(0, -2);
|
|
422
|
+
}
|
|
423
|
+
if (word.endsWith("ses") && word.length > 3) {
|
|
424
|
+
return word.slice(0, -2);
|
|
425
|
+
}
|
|
426
|
+
if (word.endsWith("shes") && word.length > 4) {
|
|
427
|
+
return word.slice(0, -2);
|
|
428
|
+
}
|
|
429
|
+
if (word.endsWith("ches") && word.length > 4) {
|
|
430
|
+
return word.slice(0, -2);
|
|
431
|
+
}
|
|
432
|
+
if (word.endsWith("s") && word.length > 1) {
|
|
433
|
+
return word.slice(0, -1);
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
function pascalSegment(segment) {
|
|
438
|
+
if (!segment) return segment;
|
|
439
|
+
return segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
440
|
+
}
|
|
441
|
+
function endsWithS(word) {
|
|
442
|
+
return word.endsWith("s");
|
|
443
|
+
}
|
|
444
|
+
function resolveClassName(modelName, classNameOverride) {
|
|
445
|
+
if (classNameOverride && classNameOverride.length > 0) {
|
|
446
|
+
return classNameOverride;
|
|
447
|
+
}
|
|
448
|
+
const singular = singularizeToPascalCase(modelName);
|
|
449
|
+
if (singular === null) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Cannot derive a class name from TOML model "${modelName}". Add an explicit "class_name" override under [models.${modelName}], e.g. class_name = "MyClass".`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return singular;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/cli/v2/fingerprint.ts
|
|
458
|
+
var import_crypto = require("crypto");
|
|
459
|
+
function fingerprintToml(tomlContent) {
|
|
460
|
+
return (0, import_crypto.createHash)("sha256").update(tomlContent, "utf-8").digest("hex").slice(0, 16);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/cli/v2/tsTypes.ts
|
|
464
|
+
function tsTypeForFieldType(fieldType) {
|
|
465
|
+
switch (fieldType) {
|
|
466
|
+
case "string":
|
|
467
|
+
case "id":
|
|
468
|
+
case "date":
|
|
469
|
+
return "string";
|
|
470
|
+
case "number":
|
|
471
|
+
return "number";
|
|
472
|
+
case "boolean":
|
|
473
|
+
return "boolean";
|
|
474
|
+
case "stringset":
|
|
475
|
+
return "StringSet";
|
|
476
|
+
default: {
|
|
477
|
+
const _exhaustive = fieldType;
|
|
478
|
+
throw new Error(`Unknown TOML field type: ${_exhaustive}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/cli/v2/templates.ts
|
|
484
|
+
var HEADER_BANNER = "// AUTO-GENERATED FROM models.toml \u2014 DO NOT EDIT.";
|
|
485
|
+
var REGEN_INSTRUCTION = "// Run `npx js-bao-codegen-v2` to regenerate.";
|
|
486
|
+
function renderModelFile(input) {
|
|
487
|
+
const {
|
|
488
|
+
className,
|
|
489
|
+
modelName,
|
|
490
|
+
fingerprint,
|
|
491
|
+
fields,
|
|
492
|
+
relationships,
|
|
493
|
+
classNamesByModelName
|
|
494
|
+
} = input;
|
|
495
|
+
const usesStringSet = Object.values(fields).some(
|
|
496
|
+
(opts) => opts.type === "stringset"
|
|
497
|
+
);
|
|
498
|
+
const rels = relationships ? Object.entries(relationships) : [];
|
|
499
|
+
const usesPagination = rels.some(
|
|
500
|
+
([, cfg]) => cfg.type === "hasMany" || cfg.type === "hasManyThrough"
|
|
501
|
+
);
|
|
502
|
+
const targetClassNames = /* @__PURE__ */ new Set();
|
|
503
|
+
for (const [, cfg] of rels) {
|
|
504
|
+
const targetName = classNamesByModelName[cfg.model];
|
|
505
|
+
if (!targetName) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`Relationship in [models.${modelName}] references model "${cfg.model}" which is not present in models.toml. Cross-TOML references are not supported.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
if (targetName !== className) {
|
|
511
|
+
targetClassNames.add(targetName);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const lines = [];
|
|
515
|
+
lines.push(HEADER_BANNER);
|
|
516
|
+
lines.push(REGEN_INSTRUCTION);
|
|
517
|
+
lines.push(`// fingerprint: ${fingerprint}`);
|
|
518
|
+
lines.push("");
|
|
519
|
+
const jsBaoTypeImports = ["BaseModel"];
|
|
520
|
+
if (usesStringSet) jsBaoTypeImports.push("StringSet");
|
|
521
|
+
if (usesPagination) {
|
|
522
|
+
jsBaoTypeImports.push("PaginationOptions", "PaginatedResult");
|
|
523
|
+
}
|
|
524
|
+
lines.push(
|
|
525
|
+
`import type { ${jsBaoTypeImports.join(", ")} } from "js-bao";`
|
|
526
|
+
);
|
|
527
|
+
lines.push(`import { BaseModel as BaseModelImpl } from "js-bao";`);
|
|
528
|
+
const sortedTargets = [...targetClassNames].sort();
|
|
529
|
+
for (const target of sortedTargets) {
|
|
530
|
+
lines.push(`import type { ${target} } from "./${target}.generated";`);
|
|
531
|
+
}
|
|
532
|
+
lines.push("");
|
|
533
|
+
lines.push(`export interface ${className}Attrs {`);
|
|
534
|
+
for (const [fieldName, opts] of Object.entries(fields)) {
|
|
535
|
+
const tsType = tsTypeForFieldType(opts.type);
|
|
536
|
+
const optional = opts.required === true ? "" : "?";
|
|
537
|
+
lines.push(` ${fieldName}${optional}: ${tsType};`);
|
|
538
|
+
}
|
|
539
|
+
lines.push(`}`);
|
|
540
|
+
lines.push("");
|
|
541
|
+
lines.push(`export interface ${className} extends ${className}Attrs, BaseModel {`);
|
|
542
|
+
const seenCompanionTargets = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const [relName, cfg] of rels) {
|
|
544
|
+
const targetClass = classNamesByModelName[cfg.model];
|
|
545
|
+
if (cfg.type === "refersTo") {
|
|
546
|
+
lines.push(` ${relName}(): Promise<${targetClass} | null>;`);
|
|
547
|
+
} else if (cfg.type === "hasMany") {
|
|
548
|
+
lines.push(
|
|
549
|
+
` ${relName}(options?: PaginationOptions): Promise<PaginatedResult<${targetClass}>>;`
|
|
550
|
+
);
|
|
551
|
+
} else if (cfg.type === "hasManyThrough") {
|
|
552
|
+
const priorRel = seenCompanionTargets.get(targetClass);
|
|
553
|
+
if (priorRel !== void 0) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Generator-time collision: model [models.${modelName}] has two hasManyThrough relationships ("${priorRel}" and "${relName}") both targeting class "${targetClass}", which would produce duplicate add${targetClass}/remove${targetClass} methods. Rename one of the relationships or split into different target models.`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
seenCompanionTargets.set(targetClass, relName);
|
|
559
|
+
lines.push(
|
|
560
|
+
` ${relName}(options?: PaginationOptions): Promise<PaginatedResult<${targetClass}>>;`
|
|
561
|
+
);
|
|
562
|
+
lines.push(` add${targetClass}(target: ${targetClass} | string): Promise<void>;`);
|
|
563
|
+
lines.push(` remove${targetClass}(target: ${targetClass} | string): Promise<void>;`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
lines.push(`}`);
|
|
567
|
+
lines.push("");
|
|
568
|
+
lines.push(`export class ${className} extends BaseModelImpl {}`);
|
|
569
|
+
lines.push("");
|
|
570
|
+
lines.push(
|
|
571
|
+
`export const ${className}_modelName: "${escapeStringLiteral(modelName)}" = ${JSON.stringify(modelName)};`
|
|
572
|
+
);
|
|
573
|
+
lines.push("");
|
|
574
|
+
return lines.join("\n");
|
|
575
|
+
}
|
|
576
|
+
function renderBarrel(input) {
|
|
577
|
+
const { fingerprint, tomlFilename, entries } = input;
|
|
578
|
+
const lines = [];
|
|
579
|
+
lines.push(HEADER_BANNER);
|
|
580
|
+
lines.push(REGEN_INSTRUCTION);
|
|
581
|
+
lines.push(`// fingerprint: ${fingerprint}`);
|
|
582
|
+
lines.push("//");
|
|
583
|
+
lines.push(
|
|
584
|
+
"// Importing this barrel registers every model with js-bao as a side"
|
|
585
|
+
);
|
|
586
|
+
lines.push(
|
|
587
|
+
"// effect (via `attachAndRegisterModel`). Apps should import models from"
|
|
588
|
+
);
|
|
589
|
+
lines.push(
|
|
590
|
+
"// this barrel rather than the per-model `*.generated` files so"
|
|
591
|
+
);
|
|
592
|
+
lines.push("// registration runs exactly once.");
|
|
593
|
+
lines.push("");
|
|
594
|
+
lines.push(`import type { BaseModel } from "js-bao";`);
|
|
595
|
+
lines.push(
|
|
596
|
+
`import { attachAndRegisterModel, loadSchemaFromTomlString } from "js-bao";`
|
|
597
|
+
);
|
|
598
|
+
lines.push(`import modelsToml from "./${tomlFilename}?raw";`);
|
|
599
|
+
for (const { className } of entries) {
|
|
600
|
+
lines.push(`import { ${className} } from "./${className}.generated";`);
|
|
601
|
+
}
|
|
602
|
+
lines.push("");
|
|
603
|
+
for (const { className } of entries) {
|
|
604
|
+
lines.push(`export { ${className} } from "./${className}.generated";`);
|
|
605
|
+
}
|
|
606
|
+
lines.push("");
|
|
607
|
+
lines.push(`const _modelPairs: ReadonlyArray<{`);
|
|
608
|
+
lines.push(` modelName: string;`);
|
|
609
|
+
lines.push(` class: typeof BaseModel;`);
|
|
610
|
+
lines.push(`}> = [`);
|
|
611
|
+
for (const { modelName, className } of entries) {
|
|
612
|
+
lines.push(
|
|
613
|
+
` { modelName: ${JSON.stringify(modelName)}, class: ${className} },`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
lines.push(`];`);
|
|
617
|
+
lines.push("");
|
|
618
|
+
lines.push(`const _schemasByName = Object.fromEntries(`);
|
|
619
|
+
lines.push(` loadSchemaFromTomlString(modelsToml).map((s) => [s.name, s])`);
|
|
620
|
+
lines.push(`);`);
|
|
621
|
+
lines.push("");
|
|
622
|
+
lines.push(`for (const { modelName, class: ModelClass } of _modelPairs) {`);
|
|
623
|
+
lines.push(` const schema = _schemasByName[modelName];`);
|
|
624
|
+
lines.push(` if (!schema) {`);
|
|
625
|
+
lines.push(` throw new Error(`);
|
|
626
|
+
lines.push(
|
|
627
|
+
' `Generated model ${ModelClass.name} expected TOML schema "${modelName}" \u2014 did models.toml change without re-running codegen?`'
|
|
628
|
+
);
|
|
629
|
+
lines.push(` );`);
|
|
630
|
+
lines.push(` }`);
|
|
631
|
+
lines.push(` attachAndRegisterModel(ModelClass, schema);`);
|
|
632
|
+
lines.push(`}`);
|
|
633
|
+
lines.push("");
|
|
634
|
+
lines.push(`const _knownModelNames = new Set(_modelPairs.map((p) => p.modelName));`);
|
|
635
|
+
lines.push(`for (const schemaName of Object.keys(_schemasByName)) {`);
|
|
636
|
+
lines.push(` if (!_knownModelNames.has(schemaName)) {`);
|
|
637
|
+
lines.push(` throw new Error(`);
|
|
638
|
+
lines.push(
|
|
639
|
+
' `TOML model "${schemaName}" has no generated class. Run \\`npx js-bao-codegen-v2\\` to regenerate.`'
|
|
640
|
+
);
|
|
641
|
+
lines.push(` );`);
|
|
642
|
+
lines.push(` }`);
|
|
643
|
+
lines.push(`}`);
|
|
644
|
+
lines.push("");
|
|
645
|
+
lines.push(`export const allModels: unknown[] = _modelPairs.map((m) => m.class);`);
|
|
646
|
+
lines.push("");
|
|
647
|
+
return lines.join("\n");
|
|
648
|
+
}
|
|
649
|
+
function escapeStringLiteral(s) {
|
|
650
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/cli/v2/generator.ts
|
|
654
|
+
var GENERATED_FILE_SUFFIX = ".generated.ts";
|
|
655
|
+
var BARREL_FILENAME = "index.ts";
|
|
656
|
+
async function generate(options) {
|
|
657
|
+
const inputAbs = path.resolve(options.inputTomlPath);
|
|
658
|
+
const outAbs = path.resolve(options.outputDir);
|
|
659
|
+
const tomlContent = await import_fs.promises.readFile(inputAbs, "utf-8");
|
|
660
|
+
const fingerprint = fingerprintToml(tomlContent);
|
|
661
|
+
const loaderOpts = { strict: options.strict !== false };
|
|
662
|
+
const schemas = loadSchemaFromTomlString(tomlContent, loaderOpts);
|
|
663
|
+
if (schemas.length === 0) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
`models.toml at ${inputAbs} has no [models.*] sections \u2014 nothing to generate.`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const classNamesByModelName = {};
|
|
669
|
+
const seenClassNames = /* @__PURE__ */ new Map();
|
|
670
|
+
for (const schema of schemas) {
|
|
671
|
+
const className = resolveClassName(schema.name, schema.options.className);
|
|
672
|
+
if (seenClassNames.has(className)) {
|
|
673
|
+
const otherModel = seenClassNames.get(className);
|
|
674
|
+
throw new Error(
|
|
675
|
+
`Class name collision: TOML models "${otherModel}" and "${schema.name}" both produce class "${className}". Add a "class_name" override to one of them.`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
seenClassNames.set(className, schema.name);
|
|
679
|
+
classNamesByModelName[schema.name] = className;
|
|
680
|
+
}
|
|
681
|
+
const entries = schemas.map((s) => ({
|
|
682
|
+
modelName: s.name,
|
|
683
|
+
className: classNamesByModelName[s.name]
|
|
684
|
+
})).sort((a, b) => a.className.localeCompare(b.className));
|
|
685
|
+
const tomlFilename = path.basename(inputAbs);
|
|
686
|
+
const filesToWrite = /* @__PURE__ */ new Map();
|
|
687
|
+
for (const schema of schemas) {
|
|
688
|
+
const className = classNamesByModelName[schema.name];
|
|
689
|
+
const filePath = path.join(outAbs, `${className}${GENERATED_FILE_SUFFIX}`);
|
|
690
|
+
filesToWrite.set(
|
|
691
|
+
filePath,
|
|
692
|
+
renderModelFile({
|
|
693
|
+
className,
|
|
694
|
+
modelName: schema.name,
|
|
695
|
+
fingerprint,
|
|
696
|
+
fields: extractFields(schema),
|
|
697
|
+
relationships: schema.options.relationships,
|
|
698
|
+
classNamesByModelName
|
|
699
|
+
})
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const barrelPath = path.join(outAbs, BARREL_FILENAME);
|
|
703
|
+
filesToWrite.set(
|
|
704
|
+
barrelPath,
|
|
705
|
+
renderBarrel({
|
|
706
|
+
fingerprint,
|
|
707
|
+
tomlFilename,
|
|
708
|
+
entries
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
if (options.check === true) {
|
|
712
|
+
return await runCheck(outAbs, filesToWrite);
|
|
713
|
+
}
|
|
714
|
+
await import_fs.promises.mkdir(outAbs, { recursive: true });
|
|
715
|
+
const deletedFiles = await cleanupGeneratedOutputs(outAbs, filesToWrite);
|
|
716
|
+
const writtenFiles = [];
|
|
717
|
+
for (const [filePath, content] of filesToWrite) {
|
|
718
|
+
await import_fs.promises.writeFile(filePath, content, "utf-8");
|
|
719
|
+
writtenFiles.push(filePath);
|
|
720
|
+
}
|
|
721
|
+
writtenFiles.sort();
|
|
722
|
+
deletedFiles.sort();
|
|
723
|
+
return { writtenFiles, deletedFiles, mismatches: [] };
|
|
724
|
+
}
|
|
725
|
+
function extractFields(schema) {
|
|
726
|
+
return schema.fields;
|
|
727
|
+
}
|
|
728
|
+
async function runCheck(outAbs, filesToWrite) {
|
|
729
|
+
const mismatches = [];
|
|
730
|
+
for (const [filePath, expected] of filesToWrite) {
|
|
731
|
+
let actual;
|
|
732
|
+
try {
|
|
733
|
+
actual = await import_fs.promises.readFile(filePath, "utf-8");
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (err && err.code === "ENOENT") {
|
|
736
|
+
actual = null;
|
|
737
|
+
} else {
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (actual === null) {
|
|
742
|
+
mismatches.push({ filePath, reason: "missing" });
|
|
743
|
+
} else if (actual !== expected) {
|
|
744
|
+
mismatches.push({ filePath, reason: "differs" });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (await dirExists(outAbs)) {
|
|
748
|
+
const onDisk = await listOwnedGeneratedFiles(outAbs);
|
|
749
|
+
const expectedPaths = new Set(filesToWrite.keys());
|
|
750
|
+
for (const filePath of onDisk) {
|
|
751
|
+
if (!expectedPaths.has(filePath)) {
|
|
752
|
+
mismatches.push({ filePath, reason: "stale" });
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
writtenFiles: [...filesToWrite.keys()].sort(),
|
|
758
|
+
deletedFiles: [],
|
|
759
|
+
mismatches
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async function cleanupGeneratedOutputs(outDir, filesToWrite) {
|
|
763
|
+
const onDisk = await listOwnedGeneratedFiles(outDir);
|
|
764
|
+
const expectedPaths = new Set(filesToWrite.keys());
|
|
765
|
+
const deleted = [];
|
|
766
|
+
for (const filePath of onDisk) {
|
|
767
|
+
if (!expectedPaths.has(filePath)) {
|
|
768
|
+
await import_fs.promises.unlink(filePath);
|
|
769
|
+
deleted.push(filePath);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return deleted;
|
|
773
|
+
}
|
|
774
|
+
async function listOwnedGeneratedFiles(outDir) {
|
|
775
|
+
if (!await dirExists(outDir)) return [];
|
|
776
|
+
const entries = await import_fs.promises.readdir(outDir, { withFileTypes: true });
|
|
777
|
+
const owned = [];
|
|
778
|
+
for (const entry of entries) {
|
|
779
|
+
if (!entry.isFile()) continue;
|
|
780
|
+
if (entry.name.endsWith(GENERATED_FILE_SUFFIX) || entry.name === BARREL_FILENAME) {
|
|
781
|
+
owned.push(path.join(outDir, entry.name));
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return owned;
|
|
785
|
+
}
|
|
786
|
+
async function dirExists(dir) {
|
|
787
|
+
try {
|
|
788
|
+
const stat = await import_fs.promises.stat(dir);
|
|
789
|
+
return stat.isDirectory();
|
|
790
|
+
} catch (err) {
|
|
791
|
+
if (err && err.code === "ENOENT") return false;
|
|
792
|
+
throw err;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/cli/v2/migrate.ts
|
|
797
|
+
var path2 = __toESM(require("path"), 1);
|
|
798
|
+
var import_fs2 = require("fs");
|
|
799
|
+
var ts = __toESM(require("typescript"), 1);
|
|
800
|
+
|
|
801
|
+
// src/cli/v2/toml-writer.ts
|
|
802
|
+
var CAMEL_TO_SNAKE = {
|
|
803
|
+
autoAssign: "auto_assign",
|
|
804
|
+
maxLength: "max_length",
|
|
805
|
+
maxCount: "max_count",
|
|
806
|
+
relatedIdField: "related_id_field",
|
|
807
|
+
joinModel: "join_model",
|
|
808
|
+
joinModelLocalField: "join_model_local_field",
|
|
809
|
+
joinModelRelatedField: "join_model_related_field",
|
|
810
|
+
joinModelOrderByField: "join_model_order_by_field",
|
|
811
|
+
joinModelOrderDirection: "join_model_order_direction",
|
|
812
|
+
orderByField: "order_by_field",
|
|
813
|
+
orderDirection: "order_direction"
|
|
814
|
+
};
|
|
815
|
+
function toSnake(key) {
|
|
816
|
+
return CAMEL_TO_SNAKE[key] ?? key;
|
|
817
|
+
}
|
|
818
|
+
function tomlValue(v) {
|
|
819
|
+
if (typeof v === "string") {
|
|
820
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
821
|
+
}
|
|
822
|
+
if (typeof v === "number" || typeof v === "boolean") {
|
|
823
|
+
return String(v);
|
|
824
|
+
}
|
|
825
|
+
throw new Error(
|
|
826
|
+
`tomlValue: unsupported value kind ${typeof v} (${JSON.stringify(v)})`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
function renderModelsToml(models) {
|
|
830
|
+
const lines = [];
|
|
831
|
+
let first = true;
|
|
832
|
+
for (const model of models) {
|
|
833
|
+
if (!first) lines.push("", "");
|
|
834
|
+
first = false;
|
|
835
|
+
lines.push(`[models.${model.name}]`);
|
|
836
|
+
if (model.className !== void 0) {
|
|
837
|
+
lines.push(`class_name = ${tomlValue(model.className)}`);
|
|
838
|
+
}
|
|
839
|
+
for (const [fieldName, opts] of Object.entries(model.fields)) {
|
|
840
|
+
lines.push("");
|
|
841
|
+
lines.push(`[models.${model.name}.fields.${fieldName}]`);
|
|
842
|
+
lines.push(`type = ${tomlValue(opts.type)}`);
|
|
843
|
+
if (opts.autoAssign === true) lines.push("auto_assign = true");
|
|
844
|
+
if (opts.indexed === true) lines.push("indexed = true");
|
|
845
|
+
if (opts.unique === true) lines.push("unique = true");
|
|
846
|
+
if (opts.required === true) lines.push("required = true");
|
|
847
|
+
if (opts.maxLength !== void 0) lines.push(`max_length = ${opts.maxLength}`);
|
|
848
|
+
if (opts.maxCount !== void 0) lines.push(`max_count = ${opts.maxCount}`);
|
|
849
|
+
if (opts.default !== void 0) {
|
|
850
|
+
lines.push(`default = ${tomlValue(opts.default)}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (model.relationships) {
|
|
854
|
+
for (const [relName, cfg] of Object.entries(model.relationships)) {
|
|
855
|
+
lines.push("");
|
|
856
|
+
lines.push(`[models.${model.name}.relationships.${relName}]`);
|
|
857
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
858
|
+
if (v === void 0) continue;
|
|
859
|
+
lines.push(`${toSnake(k)} = ${tomlValue(v)}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (model.uniqueConstraints) {
|
|
864
|
+
for (const c of model.uniqueConstraints) {
|
|
865
|
+
lines.push("");
|
|
866
|
+
lines.push(`[[models.${model.name}.unique_constraints]]`);
|
|
867
|
+
lines.push(`name = ${tomlValue(c.name)}`);
|
|
868
|
+
lines.push(
|
|
869
|
+
`fields = [${c.fields.map((f) => tomlValue(f)).join(", ")}]`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
lines.push("");
|
|
875
|
+
return lines.join("\n");
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/cli/v2/migrate.ts
|
|
879
|
+
async function migrate(options) {
|
|
880
|
+
const inputAbs = path2.resolve(options.inputDir);
|
|
881
|
+
const outputAbs = path2.resolve(options.outputDir);
|
|
882
|
+
let inputStat;
|
|
883
|
+
try {
|
|
884
|
+
inputStat = await import_fs2.promises.stat(inputAbs);
|
|
885
|
+
} catch {
|
|
886
|
+
throw new Error(`Input directory does not exist: ${inputAbs}`);
|
|
887
|
+
}
|
|
888
|
+
if (!inputStat.isDirectory()) {
|
|
889
|
+
throw new Error(`Input path is not a directory: ${inputAbs}`);
|
|
890
|
+
}
|
|
891
|
+
const tomlPath = path2.join(outputAbs, "models.toml");
|
|
892
|
+
const reportJsonPath = path2.join(outputAbs, "migration-report.json");
|
|
893
|
+
const reportMdPath = path2.join(outputAbs, "migration-report.md");
|
|
894
|
+
if (!options.dryRun) {
|
|
895
|
+
if (await fileExists(tomlPath)) {
|
|
896
|
+
if (!options.force) {
|
|
897
|
+
throw new Error(
|
|
898
|
+
`models.toml already exists at ${tomlPath}; refusing to overwrite. Pass --force to overwrite, or remove the file first.`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const sourceFiles = await findV1SourceFiles(inputAbs);
|
|
904
|
+
const extracted = parseV1SourceFiles(sourceFiles);
|
|
905
|
+
if (extracted.length === 0) {
|
|
906
|
+
throw new Error(
|
|
907
|
+
`No v1 models found in ${inputAbs}. migrate scans for files containing \`defineModelSchema(...)\`; make sure the input dir points at v1 model sources.`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const tomlContent = renderTomlFromExtracted(extracted);
|
|
911
|
+
const report = buildReport(extracted, inputAbs);
|
|
912
|
+
const reportMd = renderReportMarkdown(report, inputAbs);
|
|
913
|
+
const reportJsonStr = JSON.stringify(report, null, 2) + "\n";
|
|
914
|
+
if (options.dryRun === true) {
|
|
915
|
+
return {
|
|
916
|
+
tomlPath,
|
|
917
|
+
reportJsonPath,
|
|
918
|
+
reportMdPath,
|
|
919
|
+
dryRun: true,
|
|
920
|
+
tomlContent,
|
|
921
|
+
reportJson: report,
|
|
922
|
+
reportMd
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
await import_fs2.promises.mkdir(outputAbs, { recursive: true });
|
|
926
|
+
await import_fs2.promises.writeFile(tomlPath, tomlContent, "utf-8");
|
|
927
|
+
await import_fs2.promises.writeFile(reportJsonPath, reportJsonStr, "utf-8");
|
|
928
|
+
await import_fs2.promises.writeFile(reportMdPath, reportMd, "utf-8");
|
|
929
|
+
return {
|
|
930
|
+
tomlPath,
|
|
931
|
+
reportJsonPath,
|
|
932
|
+
reportMdPath,
|
|
933
|
+
dryRun: false,
|
|
934
|
+
tomlContent,
|
|
935
|
+
reportJson: report,
|
|
936
|
+
reportMd
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
async function findV1SourceFiles(dir) {
|
|
940
|
+
const out = [];
|
|
941
|
+
const walk = async (current) => {
|
|
942
|
+
const entries = await import_fs2.promises.readdir(current, { withFileTypes: true });
|
|
943
|
+
for (const entry of entries) {
|
|
944
|
+
const full = path2.join(current, entry.name);
|
|
945
|
+
if (entry.isDirectory()) {
|
|
946
|
+
if (entry.name === "node_modules") continue;
|
|
947
|
+
if (entry.name === "generated") continue;
|
|
948
|
+
if (entry.name === "__tests__") continue;
|
|
949
|
+
await walk(full);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (!entry.isFile()) continue;
|
|
953
|
+
if (!entry.name.endsWith(".ts")) continue;
|
|
954
|
+
if (entry.name.endsWith(".d.ts")) continue;
|
|
955
|
+
if (entry.name.endsWith(".generated.ts")) continue;
|
|
956
|
+
if (entry.name.endsWith(".test.ts")) continue;
|
|
957
|
+
if (entry.name.endsWith(".spec.ts")) continue;
|
|
958
|
+
out.push(full);
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
await walk(dir);
|
|
962
|
+
out.sort();
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
function parseV1SourceFiles(files) {
|
|
966
|
+
const out = [];
|
|
967
|
+
for (const file of files) {
|
|
968
|
+
const text = readFileSyncSafe(file);
|
|
969
|
+
if (text === null) continue;
|
|
970
|
+
if (!text.includes("defineModelSchema")) continue;
|
|
971
|
+
const sf = ts.createSourceFile(
|
|
972
|
+
file,
|
|
973
|
+
text,
|
|
974
|
+
ts.ScriptTarget.ES2020,
|
|
975
|
+
/* setParentNodes */
|
|
976
|
+
true,
|
|
977
|
+
ts.ScriptKind.TS
|
|
978
|
+
);
|
|
979
|
+
const schemas = collectSchemas(sf);
|
|
980
|
+
if (schemas.length === 0) continue;
|
|
981
|
+
const classByExtendsBaseModel = collectBaseModelClasses(sf);
|
|
982
|
+
for (const schema of schemas) {
|
|
983
|
+
const klass = classByExtendsBaseModel.length === 1 ? classByExtendsBaseModel[0] : classByExtendsBaseModel.find(
|
|
984
|
+
(c) => schemaIdentifierProbablyMatches(c, schema.identifier)
|
|
985
|
+
) ?? null;
|
|
986
|
+
const className = klass?.name?.text ?? deriveFallbackClassName(schema.modelName);
|
|
987
|
+
out.push({
|
|
988
|
+
modelName: schema.modelName,
|
|
989
|
+
className,
|
|
990
|
+
sourceFile: file,
|
|
991
|
+
fields: schema.fields,
|
|
992
|
+
relationships: schema.relationships,
|
|
993
|
+
uniqueConstraints: schema.uniqueConstraints,
|
|
994
|
+
functionDefaults: schema.functionDefaults,
|
|
995
|
+
classCustomContent: klass ? extractCustomClassContent(klass, file) : []
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
out.sort((a, b) => a.modelName.localeCompare(b.modelName));
|
|
1000
|
+
return out;
|
|
1001
|
+
}
|
|
1002
|
+
function readFileSyncSafe(file) {
|
|
1003
|
+
try {
|
|
1004
|
+
return (0, import_fs2.readFileSync)(file, "utf-8");
|
|
1005
|
+
} catch {
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function collectSchemas(sf) {
|
|
1010
|
+
const out = [];
|
|
1011
|
+
const visit = (node) => {
|
|
1012
|
+
if (ts.isVariableStatement(node) && node.declarationList.declarations.length > 0) {
|
|
1013
|
+
for (const decl of node.declarationList.declarations) {
|
|
1014
|
+
if (decl.initializer && ts.isCallExpression(decl.initializer) && ts.isIdentifier(decl.initializer.expression) && decl.initializer.expression.text === "defineModelSchema" && decl.initializer.arguments.length > 0 && ts.isIdentifier(decl.name)) {
|
|
1015
|
+
const arg = decl.initializer.arguments[0];
|
|
1016
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
1017
|
+
const parsed = parseDefineModelSchemaArg(arg, sf);
|
|
1018
|
+
if (parsed) {
|
|
1019
|
+
out.push({ identifier: decl.name.text, ...parsed });
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
ts.forEachChild(node, visit);
|
|
1026
|
+
};
|
|
1027
|
+
visit(sf);
|
|
1028
|
+
return out;
|
|
1029
|
+
}
|
|
1030
|
+
function parseDefineModelSchemaArg(obj, sf) {
|
|
1031
|
+
let modelName = null;
|
|
1032
|
+
let fields = {};
|
|
1033
|
+
let relationships;
|
|
1034
|
+
let uniqueConstraints;
|
|
1035
|
+
const functionDefaults = [];
|
|
1036
|
+
for (const property of obj.properties) {
|
|
1037
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
1038
|
+
continue;
|
|
1039
|
+
const name = property.name.text;
|
|
1040
|
+
if (name === "name" && ts.isStringLiteral(property.initializer)) {
|
|
1041
|
+
modelName = property.initializer.text;
|
|
1042
|
+
} else if (name === "fields" && ts.isObjectLiteralExpression(property.initializer)) {
|
|
1043
|
+
const parsed = parseFields(property.initializer, sf);
|
|
1044
|
+
fields = parsed.fields;
|
|
1045
|
+
functionDefaults.push(...parsed.functionDefaults);
|
|
1046
|
+
} else if (name === "options" && ts.isObjectLiteralExpression(property.initializer)) {
|
|
1047
|
+
for (const opt of property.initializer.properties) {
|
|
1048
|
+
if (!ts.isPropertyAssignment(opt) || !ts.isIdentifier(opt.name))
|
|
1049
|
+
continue;
|
|
1050
|
+
const optName = opt.name.text;
|
|
1051
|
+
if (optName === "relationships" && ts.isObjectLiteralExpression(opt.initializer)) {
|
|
1052
|
+
relationships = parseRelationships(opt.initializer);
|
|
1053
|
+
} else if (optName === "uniqueConstraints" && ts.isArrayLiteralExpression(opt.initializer)) {
|
|
1054
|
+
uniqueConstraints = parseUniqueConstraints(opt.initializer);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (!modelName) return null;
|
|
1060
|
+
for (const fd of functionDefaults) {
|
|
1061
|
+
const opts = fields[fd.field];
|
|
1062
|
+
if (opts && opts.type === "id") {
|
|
1063
|
+
fd.severity = "info";
|
|
1064
|
+
opts.autoAssign = true;
|
|
1065
|
+
} else {
|
|
1066
|
+
fd.severity = "warn";
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return { modelName, fields, relationships, uniqueConstraints, functionDefaults };
|
|
1070
|
+
}
|
|
1071
|
+
function parseFields(obj, sf) {
|
|
1072
|
+
const fields = {};
|
|
1073
|
+
const functionDefaults = [];
|
|
1074
|
+
for (const property of obj.properties) {
|
|
1075
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
1076
|
+
continue;
|
|
1077
|
+
const fieldName = property.name.text;
|
|
1078
|
+
if (!ts.isObjectLiteralExpression(property.initializer)) continue;
|
|
1079
|
+
const opts = { type: "string" };
|
|
1080
|
+
for (const fp of property.initializer.properties) {
|
|
1081
|
+
if (!ts.isPropertyAssignment(fp) || !ts.isIdentifier(fp.name)) continue;
|
|
1082
|
+
const optName = fp.name.text;
|
|
1083
|
+
const value = parsePropertyValue(fp.initializer);
|
|
1084
|
+
switch (optName) {
|
|
1085
|
+
case "type":
|
|
1086
|
+
if (typeof value === "string") opts.type = value;
|
|
1087
|
+
break;
|
|
1088
|
+
case "indexed":
|
|
1089
|
+
if (typeof value === "boolean") opts.indexed = value;
|
|
1090
|
+
break;
|
|
1091
|
+
case "unique":
|
|
1092
|
+
if (typeof value === "boolean") opts.unique = value;
|
|
1093
|
+
break;
|
|
1094
|
+
case "required":
|
|
1095
|
+
if (typeof value === "boolean") opts.required = value;
|
|
1096
|
+
break;
|
|
1097
|
+
case "autoAssign":
|
|
1098
|
+
if (typeof value === "boolean") opts.autoAssign = value;
|
|
1099
|
+
break;
|
|
1100
|
+
case "maxLength":
|
|
1101
|
+
if (typeof value === "number") opts.maxLength = value;
|
|
1102
|
+
break;
|
|
1103
|
+
case "maxCount":
|
|
1104
|
+
if (typeof value === "number") opts.maxCount = value;
|
|
1105
|
+
break;
|
|
1106
|
+
case "default": {
|
|
1107
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1108
|
+
opts.default = value;
|
|
1109
|
+
} else if (isFunctionLikeInitializer(fp.initializer)) {
|
|
1110
|
+
const lc = sf.getLineAndCharacterOfPosition(fp.getStart(sf));
|
|
1111
|
+
functionDefaults.push({
|
|
1112
|
+
field: fieldName,
|
|
1113
|
+
line: lc.line + 1,
|
|
1114
|
+
snippet: snippetOf(fp.initializer.getText(sf)),
|
|
1115
|
+
severity: "warn"
|
|
1116
|
+
// re-classified later if id+autoAssign
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
fields[fieldName] = opts;
|
|
1124
|
+
}
|
|
1125
|
+
return { fields, functionDefaults };
|
|
1126
|
+
}
|
|
1127
|
+
function isFunctionLikeInitializer(node) {
|
|
1128
|
+
if (ts.isArrowFunction(node)) return true;
|
|
1129
|
+
if (ts.isFunctionExpression(node)) return true;
|
|
1130
|
+
if (ts.isIdentifier(node)) return true;
|
|
1131
|
+
if (ts.isCallExpression(node)) return true;
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
function parsePropertyValue(node) {
|
|
1135
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
1136
|
+
if (ts.isNumericLiteral(node)) return parseFloat(node.text);
|
|
1137
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
1138
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
1139
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
1140
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
|
|
1141
|
+
return -parseFloat(node.operand.text);
|
|
1142
|
+
}
|
|
1143
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
1144
|
+
return node.elements.map((el) => parsePropertyValue(el));
|
|
1145
|
+
}
|
|
1146
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
1147
|
+
const obj = {};
|
|
1148
|
+
for (const prop of node.properties) {
|
|
1149
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
1150
|
+
obj[prop.name.text] = parsePropertyValue(prop.initializer);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return obj;
|
|
1154
|
+
}
|
|
1155
|
+
return void 0;
|
|
1156
|
+
}
|
|
1157
|
+
function parseRelationships(obj) {
|
|
1158
|
+
const out = {};
|
|
1159
|
+
for (const property of obj.properties) {
|
|
1160
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
1161
|
+
continue;
|
|
1162
|
+
const relName = property.name.text;
|
|
1163
|
+
let inner = null;
|
|
1164
|
+
if (ts.isObjectLiteralExpression(property.initializer)) {
|
|
1165
|
+
inner = property.initializer;
|
|
1166
|
+
} else if (ts.isAsExpression(property.initializer) && ts.isObjectLiteralExpression(property.initializer.expression)) {
|
|
1167
|
+
inner = property.initializer.expression;
|
|
1168
|
+
}
|
|
1169
|
+
if (!inner) continue;
|
|
1170
|
+
const cfg = parseRelationshipConfig(inner);
|
|
1171
|
+
if (cfg) out[relName] = cfg;
|
|
1172
|
+
}
|
|
1173
|
+
return out;
|
|
1174
|
+
}
|
|
1175
|
+
function parseRelationshipConfig(obj) {
|
|
1176
|
+
const raw = {};
|
|
1177
|
+
for (const property of obj.properties) {
|
|
1178
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
1179
|
+
continue;
|
|
1180
|
+
if (ts.isStringLiteral(property.initializer)) {
|
|
1181
|
+
raw[property.name.text] = property.initializer.text;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (!raw.type || !raw.model) return null;
|
|
1185
|
+
if (raw.type === "refersTo") {
|
|
1186
|
+
if (!raw.relatedIdField) return null;
|
|
1187
|
+
const cfg = {
|
|
1188
|
+
type: "refersTo",
|
|
1189
|
+
model: raw.model,
|
|
1190
|
+
relatedIdField: raw.relatedIdField
|
|
1191
|
+
};
|
|
1192
|
+
return cfg;
|
|
1193
|
+
}
|
|
1194
|
+
if (raw.type === "hasMany") {
|
|
1195
|
+
if (!raw.relatedIdField) return null;
|
|
1196
|
+
const cfg = {
|
|
1197
|
+
type: "hasMany",
|
|
1198
|
+
model: raw.model,
|
|
1199
|
+
relatedIdField: raw.relatedIdField
|
|
1200
|
+
};
|
|
1201
|
+
if (raw.orderByField) cfg.orderByField = raw.orderByField;
|
|
1202
|
+
if (raw.orderDirection) cfg.orderDirection = raw.orderDirection;
|
|
1203
|
+
return cfg;
|
|
1204
|
+
}
|
|
1205
|
+
if (raw.type === "hasManyThrough") {
|
|
1206
|
+
if (!raw.joinModel || !raw.joinModelLocalField || !raw.joinModelRelatedField)
|
|
1207
|
+
return null;
|
|
1208
|
+
const cfg = {
|
|
1209
|
+
type: "hasManyThrough",
|
|
1210
|
+
model: raw.model,
|
|
1211
|
+
joinModel: raw.joinModel,
|
|
1212
|
+
joinModelLocalField: raw.joinModelLocalField,
|
|
1213
|
+
joinModelRelatedField: raw.joinModelRelatedField
|
|
1214
|
+
};
|
|
1215
|
+
if (raw.joinModelOrderByField)
|
|
1216
|
+
cfg.joinModelOrderByField = raw.joinModelOrderByField;
|
|
1217
|
+
if (raw.joinModelOrderDirection)
|
|
1218
|
+
cfg.joinModelOrderDirection = raw.joinModelOrderDirection;
|
|
1219
|
+
return cfg;
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
function parseUniqueConstraints(arr) {
|
|
1224
|
+
const out = [];
|
|
1225
|
+
for (const el of arr.elements) {
|
|
1226
|
+
if (!ts.isObjectLiteralExpression(el)) continue;
|
|
1227
|
+
let name = null;
|
|
1228
|
+
let fields = null;
|
|
1229
|
+
for (const property of el.properties) {
|
|
1230
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
1231
|
+
continue;
|
|
1232
|
+
if (property.name.text === "name" && ts.isStringLiteral(property.initializer)) {
|
|
1233
|
+
name = property.initializer.text;
|
|
1234
|
+
} else if (property.name.text === "fields" && ts.isArrayLiteralExpression(property.initializer)) {
|
|
1235
|
+
const items = [];
|
|
1236
|
+
for (const item of property.initializer.elements) {
|
|
1237
|
+
if (ts.isStringLiteral(item)) items.push(item.text);
|
|
1238
|
+
}
|
|
1239
|
+
fields = items;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (name && fields) out.push({ name, fields });
|
|
1243
|
+
}
|
|
1244
|
+
return out;
|
|
1245
|
+
}
|
|
1246
|
+
function collectBaseModelClasses(sf) {
|
|
1247
|
+
const out = [];
|
|
1248
|
+
const visit = (node) => {
|
|
1249
|
+
if (ts.isClassDeclaration(node) && classExtendsBaseModel(node)) {
|
|
1250
|
+
out.push(node);
|
|
1251
|
+
}
|
|
1252
|
+
ts.forEachChild(node, visit);
|
|
1253
|
+
};
|
|
1254
|
+
visit(sf);
|
|
1255
|
+
return out;
|
|
1256
|
+
}
|
|
1257
|
+
function classExtendsBaseModel(node) {
|
|
1258
|
+
if (!node.heritageClauses) return false;
|
|
1259
|
+
for (const heritage of node.heritageClauses) {
|
|
1260
|
+
if (heritage.token !== ts.SyntaxKind.ExtendsKeyword) continue;
|
|
1261
|
+
for (const t of heritage.types) {
|
|
1262
|
+
if (ts.isIdentifier(t.expression) && t.expression.text === "BaseModel")
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
function schemaIdentifierProbablyMatches(klass, schemaIdentifier) {
|
|
1269
|
+
if (!klass.name) return false;
|
|
1270
|
+
const want = klass.name.text;
|
|
1271
|
+
const stripped = schemaIdentifier.replace(/Schema$/, "");
|
|
1272
|
+
return stripped.localeCompare(want, void 0, { sensitivity: "accent" }) === 0;
|
|
1273
|
+
}
|
|
1274
|
+
function extractCustomClassContent(klass, file) {
|
|
1275
|
+
const sf = klass.getSourceFile();
|
|
1276
|
+
const out = [];
|
|
1277
|
+
for (const member of klass.members) {
|
|
1278
|
+
let kind = null;
|
|
1279
|
+
let name = "<anonymous>";
|
|
1280
|
+
if (ts.isMethodDeclaration(member)) {
|
|
1281
|
+
kind = hasStaticModifier(member) ? "static-method" : "instance-method";
|
|
1282
|
+
if (member.name && ts.isIdentifier(member.name)) name = member.name.text;
|
|
1283
|
+
} else if (ts.isGetAccessor(member)) {
|
|
1284
|
+
kind = "getter";
|
|
1285
|
+
if (member.name && ts.isIdentifier(member.name)) name = member.name.text;
|
|
1286
|
+
} else if (ts.isSetAccessor(member)) {
|
|
1287
|
+
kind = "setter";
|
|
1288
|
+
if (member.name && ts.isIdentifier(member.name)) name = member.name.text;
|
|
1289
|
+
} else if (ts.isPropertyDeclaration(member)) {
|
|
1290
|
+
kind = "property";
|
|
1291
|
+
if (member.name && ts.isIdentifier(member.name)) name = member.name.text;
|
|
1292
|
+
} else if (ts.isConstructorDeclaration(member)) {
|
|
1293
|
+
if (member.body && member.body.statements.length > 0) {
|
|
1294
|
+
kind = "ctor-override";
|
|
1295
|
+
name = "constructor";
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (kind === null) continue;
|
|
1299
|
+
const lc = sf.getLineAndCharacterOfPosition(member.getStart(sf));
|
|
1300
|
+
out.push({
|
|
1301
|
+
kind,
|
|
1302
|
+
name,
|
|
1303
|
+
file,
|
|
1304
|
+
line: lc.line + 1,
|
|
1305
|
+
snippet: snippetOf(member.getText(sf))
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
return out;
|
|
1309
|
+
}
|
|
1310
|
+
function hasStaticModifier(node) {
|
|
1311
|
+
const mods = ts.getModifiers(node) ?? [];
|
|
1312
|
+
return mods.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
1313
|
+
}
|
|
1314
|
+
function snippetOf(text) {
|
|
1315
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
1316
|
+
return collapsed.length > 120 ? collapsed.slice(0, 117) + "..." : collapsed;
|
|
1317
|
+
}
|
|
1318
|
+
function deriveFallbackClassName(modelName) {
|
|
1319
|
+
const parts = modelName.split(/_+/).filter(Boolean);
|
|
1320
|
+
return parts.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
1321
|
+
}
|
|
1322
|
+
function renderTomlFromExtracted(extracted) {
|
|
1323
|
+
const models = extracted.slice().sort((a, b) => a.modelName.localeCompare(b.modelName)).map((e) => {
|
|
1324
|
+
const computedDefault = singularizeToPascalCase(e.modelName);
|
|
1325
|
+
const className = e.className && e.className !== computedDefault ? e.className : void 0;
|
|
1326
|
+
return {
|
|
1327
|
+
name: e.modelName,
|
|
1328
|
+
className,
|
|
1329
|
+
fields: e.fields,
|
|
1330
|
+
relationships: e.relationships,
|
|
1331
|
+
uniqueConstraints: e.uniqueConstraints
|
|
1332
|
+
};
|
|
1333
|
+
});
|
|
1334
|
+
return renderModelsToml(models);
|
|
1335
|
+
}
|
|
1336
|
+
function buildReport(extracted, inputAbs) {
|
|
1337
|
+
const models = extracted.map((e) => {
|
|
1338
|
+
const customContent = [
|
|
1339
|
+
...e.classCustomContent,
|
|
1340
|
+
...e.functionDefaults.map((fd) => ({
|
|
1341
|
+
kind: "function-default",
|
|
1342
|
+
name: fd.field,
|
|
1343
|
+
file: e.sourceFile,
|
|
1344
|
+
line: fd.line,
|
|
1345
|
+
snippet: fd.snippet,
|
|
1346
|
+
severity: fd.severity
|
|
1347
|
+
}))
|
|
1348
|
+
];
|
|
1349
|
+
const hasNonInfoCustom = customContent.some(
|
|
1350
|
+
(c) => c.kind !== "function-default" || c.severity !== "info"
|
|
1351
|
+
);
|
|
1352
|
+
return {
|
|
1353
|
+
modelName: e.modelName,
|
|
1354
|
+
className: e.className,
|
|
1355
|
+
sourceFile: e.sourceFile,
|
|
1356
|
+
classification: hasNonInfoCustom ? "needs-manual-migration" : "safe-to-delete",
|
|
1357
|
+
customContent
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1360
|
+
const summary = {
|
|
1361
|
+
safeToDelete: models.filter((m) => m.classification === "safe-to-delete").length,
|
|
1362
|
+
needsManualMigration: models.filter(
|
|
1363
|
+
(m) => m.classification === "needs-manual-migration"
|
|
1364
|
+
).length,
|
|
1365
|
+
functionDefaults: models.reduce(
|
|
1366
|
+
(acc, m) => acc + m.customContent.filter((c) => c.kind === "function-default").length,
|
|
1367
|
+
0
|
|
1368
|
+
)
|
|
1369
|
+
};
|
|
1370
|
+
const v1GeneratedFiles = listLikelyV1OutputFiles(inputAbs);
|
|
1371
|
+
return {
|
|
1372
|
+
summary,
|
|
1373
|
+
models,
|
|
1374
|
+
v1GeneratedFiles
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function listLikelyV1OutputFiles(inputAbs) {
|
|
1378
|
+
const out = [];
|
|
1379
|
+
const candidates = [
|
|
1380
|
+
path2.join(inputAbs, "generated", "relationships.ts"),
|
|
1381
|
+
path2.join(inputAbs, "generated", "relationships.d.ts"),
|
|
1382
|
+
path2.join(inputAbs, "generated", "index.ts")
|
|
1383
|
+
];
|
|
1384
|
+
for (const c of candidates) {
|
|
1385
|
+
try {
|
|
1386
|
+
if ((0, import_fs2.statSync)(c).isFile()) out.push(c);
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
const genDir = path2.join(inputAbs, "generated");
|
|
1391
|
+
try {
|
|
1392
|
+
const entries = (0, import_fs2.readdirSync)(genDir);
|
|
1393
|
+
for (const e of entries) {
|
|
1394
|
+
if (e.endsWith(".relationships.d.ts")) {
|
|
1395
|
+
out.push(path2.join(genDir, e));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
return out;
|
|
1401
|
+
}
|
|
1402
|
+
function renderReportMarkdown(report, inputAbs) {
|
|
1403
|
+
const lines = [];
|
|
1404
|
+
lines.push("# Migration Report");
|
|
1405
|
+
lines.push("");
|
|
1406
|
+
lines.push(`Input directory: \`${inputAbs}\``);
|
|
1407
|
+
lines.push("");
|
|
1408
|
+
lines.push("## Summary");
|
|
1409
|
+
lines.push("");
|
|
1410
|
+
lines.push(`- safe-to-delete: ${report.summary.safeToDelete}`);
|
|
1411
|
+
lines.push(`- needs-manual-migration: ${report.summary.needsManualMigration}`);
|
|
1412
|
+
lines.push(`- function-defaults: ${report.summary.functionDefaults}`);
|
|
1413
|
+
lines.push("");
|
|
1414
|
+
if (report.v1GeneratedFiles.length > 0) {
|
|
1415
|
+
lines.push("## v1-generated files (informational; not deleted)");
|
|
1416
|
+
lines.push("");
|
|
1417
|
+
for (const f of report.v1GeneratedFiles) {
|
|
1418
|
+
lines.push(`- \`${f}\``);
|
|
1419
|
+
}
|
|
1420
|
+
lines.push("");
|
|
1421
|
+
}
|
|
1422
|
+
lines.push("## Models");
|
|
1423
|
+
lines.push("");
|
|
1424
|
+
for (const m of report.models) {
|
|
1425
|
+
lines.push(`### ${m.className} (\`${m.modelName}\`) \u2014 ${m.classification}`);
|
|
1426
|
+
lines.push("");
|
|
1427
|
+
lines.push(`Source: \`${m.sourceFile}\``);
|
|
1428
|
+
lines.push("");
|
|
1429
|
+
if (m.customContent.length > 0) {
|
|
1430
|
+
lines.push("Custom content:");
|
|
1431
|
+
lines.push("");
|
|
1432
|
+
for (const c of m.customContent) {
|
|
1433
|
+
const sev = c.severity ? ` (${c.severity})` : "";
|
|
1434
|
+
lines.push(`- \`${c.kind}\` \`${c.name}\`${sev} at line ${c.line}`);
|
|
1435
|
+
if (c.snippet) lines.push(` - \`${c.snippet}\``);
|
|
1436
|
+
}
|
|
1437
|
+
lines.push("");
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return lines.join("\n");
|
|
1441
|
+
}
|
|
1442
|
+
async function fileExists(p) {
|
|
1443
|
+
try {
|
|
1444
|
+
const stat = await import_fs2.promises.stat(p);
|
|
1445
|
+
return stat.isFile();
|
|
1446
|
+
} catch {
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/cli/utils/Logger.ts
|
|
1452
|
+
var Logger2 = class _Logger {
|
|
1453
|
+
static isVerbose = false;
|
|
1454
|
+
static setVerbose(verbose) {
|
|
1455
|
+
_Logger.isVerbose = verbose;
|
|
1456
|
+
}
|
|
1457
|
+
static log(message, prefix) {
|
|
1458
|
+
const fullMessage = prefix ? `[${prefix}] ${message}` : message;
|
|
1459
|
+
console.log(fullMessage);
|
|
1460
|
+
}
|
|
1461
|
+
static verbose(message, prefix) {
|
|
1462
|
+
if (_Logger.isVerbose) {
|
|
1463
|
+
const fullMessage = prefix ? `[${prefix}] ${message}` : message;
|
|
1464
|
+
console.log(fullMessage);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
static warn(message, prefix) {
|
|
1468
|
+
const fullMessage = prefix ? `[${prefix}] ${message}` : message;
|
|
1469
|
+
console.warn(fullMessage);
|
|
1470
|
+
}
|
|
1471
|
+
static error(message, prefix) {
|
|
1472
|
+
const fullMessage = prefix ? `[${prefix}] ${message}` : message;
|
|
1473
|
+
console.error(fullMessage);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
// package.json
|
|
1478
|
+
var package_default = {
|
|
1479
|
+
name: "js-bao",
|
|
1480
|
+
version: "0.4.1",
|
|
1481
|
+
description: "A library providing data modeling capabilities which support live updates and queries.",
|
|
1482
|
+
types: "dist/index.d.ts",
|
|
1483
|
+
type: "module",
|
|
1484
|
+
files: [
|
|
1485
|
+
"dist"
|
|
1486
|
+
],
|
|
1487
|
+
exports: {
|
|
1488
|
+
".": {
|
|
1489
|
+
types: "./dist/index.d.ts",
|
|
1490
|
+
node: {
|
|
1491
|
+
import: "./dist/node.js",
|
|
1492
|
+
require: "./dist/node.cjs"
|
|
1493
|
+
},
|
|
1494
|
+
browser: {
|
|
1495
|
+
import: "./dist/browser.js",
|
|
1496
|
+
require: "./dist/browser.cjs"
|
|
1497
|
+
},
|
|
1498
|
+
default: {
|
|
1499
|
+
import: "./dist/index.js",
|
|
1500
|
+
require: "./dist/index.cjs"
|
|
1501
|
+
}
|
|
1502
|
+
},
|
|
1503
|
+
"./node": {
|
|
1504
|
+
types: "./dist/node.d.ts",
|
|
1505
|
+
import: "./dist/node.js",
|
|
1506
|
+
require: "./dist/node.cjs"
|
|
1507
|
+
},
|
|
1508
|
+
"./browser": {
|
|
1509
|
+
types: "./dist/browser.d.ts",
|
|
1510
|
+
import: "./dist/browser.js",
|
|
1511
|
+
require: "./dist/browser.cjs"
|
|
1512
|
+
},
|
|
1513
|
+
"./cloudflare": {
|
|
1514
|
+
types: "./dist/cloudflare.d.ts",
|
|
1515
|
+
import: "./dist/cloudflare.js",
|
|
1516
|
+
require: "./dist/cloudflare.cjs"
|
|
1517
|
+
},
|
|
1518
|
+
"./cloudflare/do": {
|
|
1519
|
+
types: "./dist/cloudflare-do.d.ts",
|
|
1520
|
+
import: "./dist/cloudflare-do.js",
|
|
1521
|
+
require: "./dist/cloudflare-do.cjs"
|
|
1522
|
+
},
|
|
1523
|
+
"./client": {
|
|
1524
|
+
types: "./dist/client.d.ts",
|
|
1525
|
+
import: "./dist/client.js",
|
|
1526
|
+
require: "./dist/client.cjs"
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
bin: {
|
|
1530
|
+
"js-bao-codegen": "./dist/codegen.cjs",
|
|
1531
|
+
"jsbao-codegen": "./dist/codegen.cjs",
|
|
1532
|
+
"js-bao-codegen-v2": "./dist/codegen-v2.cjs",
|
|
1533
|
+
"jsbao-codegen-v2": "./dist/codegen-v2.cjs"
|
|
1534
|
+
},
|
|
1535
|
+
scripts: {
|
|
1536
|
+
build: "pnpm build:cli && pnpm build:cli-v2 && pnpm codegen && rm -f dist/index.* dist/node.* dist/browser.* dist/cloudflare.* dist/cloudflare-do.* dist/client.* && pnpm build:main",
|
|
1537
|
+
"build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
|
|
1538
|
+
"build:browser": "tsup src/browser.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1539
|
+
"build:node": "tsup src/node.ts --format esm,cjs --dts --platform node --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --no-splitting",
|
|
1540
|
+
"build:universal": "tsup src/index.ts --format esm,cjs --dts --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --no-splitting",
|
|
1541
|
+
"build:cloudflare": "tsup src/cloudflare.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1542
|
+
"build:cloudflare-do": "tsup src/cloudflare-do.ts --format esm,cjs --dts --platform neutral --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1543
|
+
"build:client": "tsup src/client.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1544
|
+
"build:cli": "tsup src/cli/codegen.ts --format cjs --dts --platform node --target node18 --external commander --external typescript --external fs --external path --external util --no-splitting",
|
|
1545
|
+
"build:cli-v2": "tsup src/cli/codegen-v2.ts --format cjs --dts --platform node --target node18 --external commander --external typescript --external fs --external path --external util --no-splitting",
|
|
1546
|
+
dev: 'pnpm build:cli && concurrently "pnpm codegen:watch" "pnpm dev:main"',
|
|
1547
|
+
"dev:main": 'concurrently "pnpm build:browser:dev --watch" "pnpm build:node:dev --watch" "pnpm build:universal:dev --watch"',
|
|
1548
|
+
"dev:simple": "pnpm build:cli && pnpm codegen && pnpm build:main --watch",
|
|
1549
|
+
"build:browser:dev": "tsup src/browser.ts --format esm,cjs --dts --sourcemap --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs",
|
|
1550
|
+
"build:node:dev": "tsup src/node.ts --format esm,cjs --dts --sourcemap --platform node --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs",
|
|
1551
|
+
"build:universal:dev": "tsup src/index.ts --format esm,cjs --dts --sourcemap --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs",
|
|
1552
|
+
codegen: "./dist/codegen.cjs --config js-bao.config.cjs || echo 'Codegen skipped (likely in consumer project)'",
|
|
1553
|
+
"codegen:watch": "./dist/codegen.cjs --config js-bao.config.cjs --watch",
|
|
1554
|
+
"codegen:consumer": `node -e "require('child_process').spawn('./node_modules/.bin/js-bao-codegen', process.argv.slice(2), {stdio: 'inherit'})"`,
|
|
1555
|
+
"publish:alpha": "node scripts/publish.js --tag alpha",
|
|
1556
|
+
"publish:stable": "node scripts/publish.js",
|
|
1557
|
+
test: 'echo "Error: no test specified" && exit 1',
|
|
1558
|
+
prepare: "pnpm build",
|
|
1559
|
+
prepublishOnly: "pnpm build"
|
|
1560
|
+
},
|
|
1561
|
+
dependencies: {
|
|
1562
|
+
"async-mutex": "^0.5.0",
|
|
1563
|
+
"smol-toml": "^1.3.1",
|
|
1564
|
+
"sql.js": "^1.13.0",
|
|
1565
|
+
ulid: "^3.0.0"
|
|
1566
|
+
},
|
|
1567
|
+
devDependencies: {
|
|
1568
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
1569
|
+
"@types/node": "^20.17.51",
|
|
1570
|
+
"@types/sql.js": "^1.4.9",
|
|
1571
|
+
commander: "^11.0.0",
|
|
1572
|
+
concurrently: "^9.1.2",
|
|
1573
|
+
tsup: "^8.0.2",
|
|
1574
|
+
tsx: "^4.19.4",
|
|
1575
|
+
typescript: "^5.4.5",
|
|
1576
|
+
yjs: "^13.6.18"
|
|
1577
|
+
},
|
|
1578
|
+
peerDependencies: {
|
|
1579
|
+
yjs: "^13.6.18"
|
|
1580
|
+
},
|
|
1581
|
+
optionalDependencies: {
|
|
1582
|
+
"better-sqlite3": "^12.6.2"
|
|
1583
|
+
},
|
|
1584
|
+
keywords: [
|
|
1585
|
+
"yjs",
|
|
1586
|
+
"crdt",
|
|
1587
|
+
"orm",
|
|
1588
|
+
"sqljs",
|
|
1589
|
+
"database",
|
|
1590
|
+
"reactivedb",
|
|
1591
|
+
"nodejs",
|
|
1592
|
+
"browser",
|
|
1593
|
+
"sqlite",
|
|
1594
|
+
"cloudflare",
|
|
1595
|
+
"durable-objects",
|
|
1596
|
+
"workers"
|
|
1597
|
+
],
|
|
1598
|
+
author: "Primitive LLC",
|
|
1599
|
+
license: "UNLICENSED"
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
// src/cli/codegen-v2.ts
|
|
1603
|
+
var CodegenV2CLI = class {
|
|
1604
|
+
program;
|
|
1605
|
+
constructor() {
|
|
1606
|
+
this.program = new import_commander.Command();
|
|
1607
|
+
this.setupCommands();
|
|
1608
|
+
}
|
|
1609
|
+
setupCommands() {
|
|
1610
|
+
this.program.name("js-bao-codegen-v2").description(
|
|
1611
|
+
"Generate typed model classes + auto-registration barrel from a models.toml"
|
|
1612
|
+
).version(package_default.version ?? "unknown");
|
|
1613
|
+
this.program.command("generate", { isDefault: true }).description(
|
|
1614
|
+
"Generate typed model classes + auto-registration barrel from models.toml"
|
|
1615
|
+
).requiredOption("-i, --input <file>", "Path to models.toml").requiredOption(
|
|
1616
|
+
"-o, --output <dir>",
|
|
1617
|
+
"Output directory for *.generated.ts and index.ts"
|
|
1618
|
+
).option(
|
|
1619
|
+
"--check",
|
|
1620
|
+
"Exit non-zero if generated output would change (CI-friendly)",
|
|
1621
|
+
false
|
|
1622
|
+
).option(
|
|
1623
|
+
"--no-strict",
|
|
1624
|
+
"Do not fail on unknown TOML keys (legacy behavior)"
|
|
1625
|
+
).option("-v, --verbose", "Show detailed logging information", false).action(async (options) => {
|
|
1626
|
+
await this.run(options);
|
|
1627
|
+
});
|
|
1628
|
+
this.program.command("migrate").description(
|
|
1629
|
+
"Scan a v1 codegen app and produce a models.toml + migration report"
|
|
1630
|
+
).requiredOption(
|
|
1631
|
+
"-i, --input <dir>",
|
|
1632
|
+
"Directory containing v1 model .ts source files"
|
|
1633
|
+
).requiredOption(
|
|
1634
|
+
"-o, --output <dir>",
|
|
1635
|
+
"Directory where models.toml and migration-report.{json,md} are written"
|
|
1636
|
+
).option(
|
|
1637
|
+
"--dry-run",
|
|
1638
|
+
"Print planned outputs without writing to disk",
|
|
1639
|
+
false
|
|
1640
|
+
).option(
|
|
1641
|
+
"--force",
|
|
1642
|
+
"Overwrite an existing models.toml in the output directory",
|
|
1643
|
+
false
|
|
1644
|
+
).option("-v, --verbose", "Show detailed logging information", false).action(async (options) => {
|
|
1645
|
+
await this.runMigrate(options);
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
async run(options) {
|
|
1649
|
+
Logger2.setVerbose(options.verbose);
|
|
1650
|
+
Logger2.log(
|
|
1651
|
+
`js-bao-codegen-v2 v${package_default.version ?? "unknown"}`,
|
|
1652
|
+
"js-bao-codegen-v2"
|
|
1653
|
+
);
|
|
1654
|
+
const inputAbs = path3.resolve(options.input);
|
|
1655
|
+
const outputAbs = path3.resolve(options.output);
|
|
1656
|
+
Logger2.verbose(`Input: ${inputAbs}`, "js-bao-codegen-v2");
|
|
1657
|
+
Logger2.verbose(`Output: ${outputAbs}`, "js-bao-codegen-v2");
|
|
1658
|
+
let result;
|
|
1659
|
+
try {
|
|
1660
|
+
result = await generate({
|
|
1661
|
+
inputTomlPath: inputAbs,
|
|
1662
|
+
outputDir: outputAbs,
|
|
1663
|
+
check: options.check,
|
|
1664
|
+
// commander turns `--no-strict` into `strict: false`; otherwise
|
|
1665
|
+
// `options.strict` is `true` by default (its negation-flag
|
|
1666
|
+
// behavior — see the option declaration above).
|
|
1667
|
+
strict: options.strict !== false
|
|
1668
|
+
});
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
Logger2.error(String(err instanceof Error ? err.message : err), "js-bao-codegen-v2");
|
|
1671
|
+
process.exit(1);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (options.check) {
|
|
1675
|
+
if (result.mismatches.length > 0) {
|
|
1676
|
+
Logger2.error(
|
|
1677
|
+
`Check failed: ${result.mismatches.length} file(s) out of date.`,
|
|
1678
|
+
"js-bao-codegen-v2"
|
|
1679
|
+
);
|
|
1680
|
+
for (const m of result.mismatches) {
|
|
1681
|
+
Logger2.error(` ${m.reason}: ${path3.relative(process.cwd(), m.filePath)}`, "js-bao-codegen-v2");
|
|
1682
|
+
}
|
|
1683
|
+
Logger2.error(
|
|
1684
|
+
"Run `npx js-bao-codegen-v2 --input <toml> --output <dir>` to regenerate.",
|
|
1685
|
+
"js-bao-codegen-v2"
|
|
1686
|
+
);
|
|
1687
|
+
process.exit(1);
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
Logger2.log(
|
|
1691
|
+
`Check passed: ${result.writtenFiles.length} file(s) up to date.`,
|
|
1692
|
+
"js-bao-codegen-v2"
|
|
1693
|
+
);
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
for (const f of result.deletedFiles) {
|
|
1697
|
+
Logger2.verbose(`deleted ${path3.relative(process.cwd(), f)}`, "js-bao-codegen-v2");
|
|
1698
|
+
}
|
|
1699
|
+
for (const f of result.writtenFiles) {
|
|
1700
|
+
Logger2.verbose(`wrote ${path3.relative(process.cwd(), f)}`, "js-bao-codegen-v2");
|
|
1701
|
+
}
|
|
1702
|
+
Logger2.log(
|
|
1703
|
+
`Generated ${result.writtenFiles.length} file(s)` + (result.deletedFiles.length > 0 ? `, deleted ${result.deletedFiles.length} stale file(s).` : "."),
|
|
1704
|
+
"js-bao-codegen-v2"
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
async runMigrate(options) {
|
|
1708
|
+
Logger2.setVerbose(options.verbose);
|
|
1709
|
+
Logger2.log(
|
|
1710
|
+
`js-bao-codegen-v2 migrate v${package_default.version ?? "unknown"}`,
|
|
1711
|
+
"js-bao-codegen-v2"
|
|
1712
|
+
);
|
|
1713
|
+
const inputAbs = path3.resolve(options.input);
|
|
1714
|
+
const outputAbs = path3.resolve(options.output);
|
|
1715
|
+
Logger2.verbose(`Input: ${inputAbs}`, "js-bao-codegen-v2");
|
|
1716
|
+
Logger2.verbose(`Output: ${outputAbs}`, "js-bao-codegen-v2");
|
|
1717
|
+
if (options.dryRun) Logger2.log("(dry-run; no files will be written)", "js-bao-codegen-v2");
|
|
1718
|
+
let result;
|
|
1719
|
+
try {
|
|
1720
|
+
result = await migrate({
|
|
1721
|
+
inputDir: inputAbs,
|
|
1722
|
+
outputDir: outputAbs,
|
|
1723
|
+
dryRun: options.dryRun === true,
|
|
1724
|
+
force: options.force === true
|
|
1725
|
+
});
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
Logger2.error(
|
|
1728
|
+
String(err instanceof Error ? err.message : err),
|
|
1729
|
+
"js-bao-codegen-v2"
|
|
1730
|
+
);
|
|
1731
|
+
process.exit(1);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
if (result.dryRun) {
|
|
1735
|
+
Logger2.log("--- planned models.toml ---", "js-bao-codegen-v2");
|
|
1736
|
+
Logger2.log(result.tomlContent, "js-bao-codegen-v2");
|
|
1737
|
+
Logger2.log("--- planned migration report (Markdown) ---", "js-bao-codegen-v2");
|
|
1738
|
+
Logger2.log(result.reportMd, "js-bao-codegen-v2");
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
Logger2.log(
|
|
1742
|
+
`Wrote ${path3.relative(process.cwd(), result.tomlPath)}`,
|
|
1743
|
+
"js-bao-codegen-v2"
|
|
1744
|
+
);
|
|
1745
|
+
Logger2.log(
|
|
1746
|
+
`Wrote ${path3.relative(process.cwd(), result.reportJsonPath)}`,
|
|
1747
|
+
"js-bao-codegen-v2"
|
|
1748
|
+
);
|
|
1749
|
+
Logger2.log(
|
|
1750
|
+
`Wrote ${path3.relative(process.cwd(), result.reportMdPath)}`,
|
|
1751
|
+
"js-bao-codegen-v2"
|
|
1752
|
+
);
|
|
1753
|
+
Logger2.log(
|
|
1754
|
+
`Summary: ${result.reportJson.summary.safeToDelete} safe-to-delete, ${result.reportJson.summary.needsManualMigration} needs-manual-migration, ${result.reportJson.summary.functionDefaults} function-default(s) flagged.`,
|
|
1755
|
+
"js-bao-codegen-v2"
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
execute() {
|
|
1759
|
+
this.program.parseAsync(process.argv).catch((err) => {
|
|
1760
|
+
Logger2.error(String(err), "js-bao-codegen-v2");
|
|
1761
|
+
process.exit(1);
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
var cli = new CodegenV2CLI();
|
|
1766
|
+
cli.execute();
|