@3lineas/d1-orm 1.0.10 → 1.0.12
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/README.md +24 -4
- package/dist/cli/index.cjs +619 -97
- package/dist/cli/index.js +619 -97
- package/dist/index.cjs +171 -1
- package/dist/index.d.cts +75 -2
- package/dist/index.d.ts +75 -2
- package/dist/index.js +170 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -134,7 +134,7 @@ import * as p3 from "@clack/prompts";
|
|
|
134
134
|
import * as fs2 from "fs";
|
|
135
135
|
import * as path2 from "path";
|
|
136
136
|
import * as p2 from "@clack/prompts";
|
|
137
|
-
async function makeMigration(name) {
|
|
137
|
+
async function makeMigration(name, fields) {
|
|
138
138
|
const isStandalone = !name;
|
|
139
139
|
if (isStandalone) {
|
|
140
140
|
p2.intro("Creating a new migration...");
|
|
@@ -159,17 +159,38 @@ async function makeMigration(name) {
|
|
|
159
159
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0].replace("T", "_");
|
|
160
160
|
const filename = `${timestamp}_${migrationName}.mts`;
|
|
161
161
|
const targetPath = path2.join(process.cwd(), migrationsDir, filename);
|
|
162
|
+
const tableName = migrationName.replace("create_", "").replace("_table", "").replace("add_", "").replace("_to_", "").replace("_table", "");
|
|
163
|
+
const isAlter = migrationName.startsWith("add_");
|
|
164
|
+
let fieldsSql = "";
|
|
165
|
+
if (fields && fields.length > 0) {
|
|
166
|
+
fieldsSql = fields.map((f) => {
|
|
167
|
+
let line = "";
|
|
168
|
+
if (f.type === "enum") {
|
|
169
|
+
line = ` table.enum('${f.name}', ${JSON.stringify(f.values)})`;
|
|
170
|
+
} else {
|
|
171
|
+
line = ` table.${f.type}('${f.name}')`;
|
|
172
|
+
}
|
|
173
|
+
if (f.unique) line += ".unique()";
|
|
174
|
+
if (f.nullable) line += ".nullable()";
|
|
175
|
+
if (f.default !== void 0 && f.default !== "") {
|
|
176
|
+
const defVal = typeof f.default === "string" ? `'${f.default}'` : f.default;
|
|
177
|
+
line += `.default(${defVal})`;
|
|
178
|
+
}
|
|
179
|
+
return line + ";";
|
|
180
|
+
}).join("\n");
|
|
181
|
+
}
|
|
182
|
+
const method = isAlter ? "table" : "create";
|
|
183
|
+
const downSql = isAlter ? `// return Schema.table('${tableName}', (table: Blueprint) => { /* drop columns not supported in simple SQLite ALTER */ });` : `return Schema.dropIfExists('${tableName}');`;
|
|
162
184
|
const template = `import { Blueprint, Schema } from '@3lineas/d1-orm';
|
|
163
185
|
|
|
164
186
|
export const up = async () => {
|
|
165
|
-
return Schema
|
|
166
|
-
table.id()
|
|
167
|
-
table.timestamps();
|
|
168
|
-
});
|
|
187
|
+
return Schema.${method}('${tableName}', (table: Blueprint) => {
|
|
188
|
+
${!isAlter ? " table.id();\n" : ""}${fieldsSql}
|
|
189
|
+
${!isAlter ? " table.timestamps();\n" : ""} });
|
|
169
190
|
};
|
|
170
191
|
|
|
171
192
|
export const down = async () => {
|
|
172
|
-
|
|
193
|
+
${downSql}
|
|
173
194
|
};
|
|
174
195
|
`;
|
|
175
196
|
if (!fs2.existsSync(path2.join(process.cwd(), migrationsDir))) {
|
|
@@ -209,46 +230,245 @@ async function makeModel(name) {
|
|
|
209
230
|
const modelPath = await findModelsPath() || "src/database/models";
|
|
210
231
|
const filename = `${modelName}.mts`;
|
|
211
232
|
const targetPath = path3.join(process.cwd(), modelPath, filename);
|
|
233
|
+
if (fs3.existsSync(targetPath)) {
|
|
234
|
+
p3.log.error(`Model ${filename} already exists at ${modelPath}.`);
|
|
235
|
+
p3.outro("Process aborted.");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const fields = [];
|
|
239
|
+
const relations = [];
|
|
240
|
+
let addMore = true;
|
|
241
|
+
p3.log.step("Let's add some attributes to your model!");
|
|
242
|
+
while (addMore) {
|
|
243
|
+
const fieldName = await p3.text({
|
|
244
|
+
message: "New attribute or relation name (leave empty to stop):",
|
|
245
|
+
placeholder: "e.g. title or posts"
|
|
246
|
+
});
|
|
247
|
+
if (p3.isCancel(fieldName)) break;
|
|
248
|
+
if (!fieldName) break;
|
|
249
|
+
const fieldType = await p3.select({
|
|
250
|
+
message: `Type for ${fieldName}:`,
|
|
251
|
+
options: [
|
|
252
|
+
{ value: "string", label: "String" },
|
|
253
|
+
{ value: "integer", label: "Integer" },
|
|
254
|
+
{ value: "boolean", label: "Boolean" },
|
|
255
|
+
{ value: "text", label: "Text" },
|
|
256
|
+
{ value: "float", label: "Float" },
|
|
257
|
+
{ value: "json", label: "JSON" },
|
|
258
|
+
{ value: "enum", label: "Enum" },
|
|
259
|
+
{ value: "date", label: "Date" },
|
|
260
|
+
{ value: "blob", label: "Blob" },
|
|
261
|
+
{ value: "belongsToMany", label: "ManyToMany (Pivot Table)" },
|
|
262
|
+
{ value: "relation", label: "Relation (1:1, 1:N)" }
|
|
263
|
+
]
|
|
264
|
+
});
|
|
265
|
+
if (p3.isCancel(fieldType)) break;
|
|
266
|
+
if (fieldType === "relation" || fieldType === "belongsToMany") {
|
|
267
|
+
let relType = fieldType;
|
|
268
|
+
if (fieldType === "relation") {
|
|
269
|
+
relType = await p3.select({
|
|
270
|
+
message: `Relation type for ${fieldName}:`,
|
|
271
|
+
options: [
|
|
272
|
+
{ value: "hasOne", label: "HasOne" },
|
|
273
|
+
{ value: "hasMany", label: "HasMany" },
|
|
274
|
+
{ value: "belongsTo", label: "BelongsTo" }
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (p3.isCancel(relType)) break;
|
|
279
|
+
const modelsPath = await findModelsPath() || "src/database/models";
|
|
280
|
+
const availableModels = fs3.existsSync(
|
|
281
|
+
path3.join(process.cwd(), modelsPath)
|
|
282
|
+
) ? fs3.readdirSync(path3.join(process.cwd(), modelPath)).map((f) => f.replace(".mts", "").replace(".ts", "")) : [];
|
|
283
|
+
const targetModel = await p3.text({
|
|
284
|
+
message: "Target model for this relation:",
|
|
285
|
+
placeholder: "e.g. Post",
|
|
286
|
+
validate: (v) => !v ? "Target model is required" : void 0
|
|
287
|
+
});
|
|
288
|
+
if (p3.isCancel(targetModel)) break;
|
|
289
|
+
let foreignKey = "";
|
|
290
|
+
let pivotTable = "";
|
|
291
|
+
if (relType === "belongsToMany") {
|
|
292
|
+
p3.log.info("Automatically creating a pivot table migration.");
|
|
293
|
+
pivotTable = await p3.text({
|
|
294
|
+
message: "Pivot table name:",
|
|
295
|
+
initialValue: [modelName.toLowerCase(), targetModel.toLowerCase()].sort().join("_")
|
|
296
|
+
});
|
|
297
|
+
if (p3.isCancel(pivotTable)) break;
|
|
298
|
+
} else if (relType === "belongsTo") {
|
|
299
|
+
foreignKey = await p3.text({
|
|
300
|
+
message: "Foreign key column name:",
|
|
301
|
+
placeholder: `e.g. ${targetModel.toLowerCase()}_id`,
|
|
302
|
+
initialValue: `${targetModel.toLowerCase()}_id`
|
|
303
|
+
});
|
|
304
|
+
if (p3.isCancel(foreignKey)) break;
|
|
305
|
+
fields.push({
|
|
306
|
+
name: foreignKey,
|
|
307
|
+
type: "foreign",
|
|
308
|
+
nullable: false,
|
|
309
|
+
unique: false
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
relations.push({
|
|
313
|
+
method: fieldName,
|
|
314
|
+
type: relType,
|
|
315
|
+
model: targetModel,
|
|
316
|
+
foreignKey,
|
|
317
|
+
pivotTable
|
|
318
|
+
});
|
|
319
|
+
const addInverse = await p3.confirm({
|
|
320
|
+
message: `Add the inverse relation in ${targetModel}.mts?`,
|
|
321
|
+
initialValue: true
|
|
322
|
+
});
|
|
323
|
+
if (addInverse && !p3.isCancel(addInverse)) {
|
|
324
|
+
let inverseType = "belongsTo";
|
|
325
|
+
if (relType === "belongsTo") inverseType = "hasMany";
|
|
326
|
+
if (relType === "hasOne") inverseType = "belongsTo";
|
|
327
|
+
if (relType === "belongsToMany") inverseType = "belongsToMany";
|
|
328
|
+
const inverseMethod = await p3.text({
|
|
329
|
+
message: "Inverse method name:",
|
|
330
|
+
initialValue: relType === "belongsTo" ? modelName.toLowerCase() + "s" : modelName.toLowerCase()
|
|
331
|
+
});
|
|
332
|
+
if (!p3.isCancel(inverseMethod)) {
|
|
333
|
+
await updateTargetModel(
|
|
334
|
+
targetModel,
|
|
335
|
+
inverseMethod,
|
|
336
|
+
inverseType,
|
|
337
|
+
modelName,
|
|
338
|
+
pivotTable
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
let enumValues = [];
|
|
344
|
+
if (fieldType === "enum") {
|
|
345
|
+
const valuesStr = await p3.text({
|
|
346
|
+
message: "Allowed values (comma separated):",
|
|
347
|
+
placeholder: "active, inactive, pending"
|
|
348
|
+
});
|
|
349
|
+
if (p3.isCancel(valuesStr)) break;
|
|
350
|
+
enumValues = valuesStr.split(",").map((v) => v.trim());
|
|
351
|
+
}
|
|
352
|
+
const isNullable = await p3.confirm({
|
|
353
|
+
message: "Is it nullable?",
|
|
354
|
+
initialValue: false
|
|
355
|
+
});
|
|
356
|
+
if (p3.isCancel(isNullable)) break;
|
|
357
|
+
const isUnique = await p3.confirm({
|
|
358
|
+
message: "Is it unique?",
|
|
359
|
+
initialValue: false
|
|
360
|
+
});
|
|
361
|
+
if (p3.isCancel(isUnique)) break;
|
|
362
|
+
fields.push({
|
|
363
|
+
name: fieldName,
|
|
364
|
+
type: fieldType,
|
|
365
|
+
nullable: isNullable,
|
|
366
|
+
unique: isUnique,
|
|
367
|
+
values: enumValues
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const fieldProperties = fields.filter(
|
|
372
|
+
(f) => f.type !== "foreign" || !relations.some((r) => r.foreignKey === f.name)
|
|
373
|
+
).map((f) => {
|
|
374
|
+
let tsType = "string";
|
|
375
|
+
if (f.type === "integer" || f.type === "float") tsType = "number";
|
|
376
|
+
else if (f.type === "boolean") tsType = "boolean";
|
|
377
|
+
else if (f.type === "json") tsType = "any";
|
|
378
|
+
else if (f.type === "blob") tsType = "Uint8Array";
|
|
379
|
+
return ` declare ${f.name}${f.nullable ? "?" : ""}: ${tsType};`;
|
|
380
|
+
}).join("\n");
|
|
381
|
+
const relationMethods = relations.map((r) => {
|
|
382
|
+
if (r.type === "belongsToMany") {
|
|
383
|
+
return ` ${r.method}() {
|
|
384
|
+
return this.belongsToMany(${r.model}, '${r.pivotTable}');
|
|
385
|
+
}`;
|
|
386
|
+
}
|
|
387
|
+
return ` ${r.method}() {
|
|
388
|
+
return this.${r.type}(${r.model});
|
|
389
|
+
}`;
|
|
390
|
+
}).join("\n\n");
|
|
212
391
|
const template = `import { Model } from '@3lineas/d1-orm';
|
|
392
|
+
${relations.map((r) => `import { ${r.model} } from './${r.model}.mts';`).join("\n")}
|
|
213
393
|
|
|
214
394
|
export class ${modelName} extends Model {
|
|
215
395
|
// protected static table = '${modelName.toLowerCase()}s';
|
|
216
396
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
397
|
+
declare id: number;
|
|
398
|
+
${fieldProperties}
|
|
399
|
+
declare created_at: string;
|
|
400
|
+
declare updated_at: string;
|
|
401
|
+
|
|
402
|
+
${relationMethods}
|
|
220
403
|
}
|
|
221
404
|
`;
|
|
222
|
-
if (fs3.existsSync(targetPath)) {
|
|
223
|
-
p3.log.error(`Model ${filename} already exists at ${modelPath}.`);
|
|
224
|
-
p3.outro("Process aborted.");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
405
|
if (!fs3.existsSync(path3.join(process.cwd(), modelPath))) {
|
|
228
406
|
fs3.mkdirSync(path3.join(process.cwd(), modelPath), { recursive: true });
|
|
229
407
|
}
|
|
230
408
|
fs3.writeFileSync(targetPath, template);
|
|
231
409
|
p3.log.success(`Created model: ${modelPath}/${filename}`);
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
410
|
+
const migrationName = `create_${modelName.toLowerCase()}s_table`;
|
|
411
|
+
await makeMigration(migrationName, fields);
|
|
412
|
+
for (const rel of relations) {
|
|
413
|
+
if (rel.type === "belongsToMany") {
|
|
414
|
+
const pivotMigrationName = `create_${rel.pivotTable}_table`;
|
|
415
|
+
const pivotFields = [
|
|
416
|
+
{
|
|
417
|
+
name: `${modelName.toLowerCase()}_id`,
|
|
418
|
+
type: "integer",
|
|
419
|
+
nullable: false
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: `${rel.model.toLowerCase()}_id`,
|
|
423
|
+
type: "integer",
|
|
424
|
+
nullable: false
|
|
425
|
+
}
|
|
426
|
+
];
|
|
427
|
+
await makeMigration(pivotMigrationName, pivotFields);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const wantSeeder = await p3.confirm({
|
|
431
|
+
message: "Would you like to create a seeder for this model?",
|
|
432
|
+
initialValue: true
|
|
239
433
|
});
|
|
240
|
-
if (
|
|
241
|
-
|
|
434
|
+
if (wantSeeder) {
|
|
435
|
+
await makeSeeder(modelName, modelPath, fields);
|
|
436
|
+
}
|
|
437
|
+
p3.outro("Model generation complete!");
|
|
438
|
+
}
|
|
439
|
+
async function updateTargetModel(targetModel, methodName, relType, sourceModel, pivotTable = "") {
|
|
440
|
+
const modelPath = await findModelsPath() || "src/database/models";
|
|
441
|
+
const filename = `${targetModel}.mts`;
|
|
442
|
+
const targetPath = path3.join(process.cwd(), modelPath, filename);
|
|
443
|
+
if (!fs3.existsSync(targetPath)) {
|
|
444
|
+
p3.log.warn(
|
|
445
|
+
`Model ${targetModel} not found at ${modelPath}. Skipping inverse relation.`
|
|
446
|
+
);
|
|
242
447
|
return;
|
|
243
448
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
449
|
+
let content = fs3.readFileSync(targetPath, "utf-8");
|
|
450
|
+
if (!content.includes(`import { ${sourceModel} }`)) {
|
|
451
|
+
content = `import { ${sourceModel} } from './${sourceModel}.mts';
|
|
452
|
+
` + content;
|
|
247
453
|
}
|
|
248
|
-
|
|
249
|
-
|
|
454
|
+
let methodString = "";
|
|
455
|
+
if (relType === "belongsToMany") {
|
|
456
|
+
methodString = ` ${methodName}() {
|
|
457
|
+
return this.belongsToMany(${sourceModel}, '${pivotTable}');
|
|
458
|
+
}`;
|
|
459
|
+
} else {
|
|
460
|
+
methodString = ` ${methodName}() {
|
|
461
|
+
return this.${relType}(${sourceModel});
|
|
462
|
+
}`;
|
|
463
|
+
}
|
|
464
|
+
const lastBraceIndex = content.lastIndexOf("}");
|
|
465
|
+
if (lastBraceIndex !== -1) {
|
|
466
|
+
content = content.substring(0, lastBraceIndex) + "\n" + methodString + "\n" + content.substring(lastBraceIndex);
|
|
467
|
+
fs3.writeFileSync(targetPath, content);
|
|
468
|
+
p3.log.success(
|
|
469
|
+
`Updated ${targetModel}.mts with inverse relation: ${methodName}()`
|
|
470
|
+
);
|
|
250
471
|
}
|
|
251
|
-
p3.outro("Model generation complete!");
|
|
252
472
|
}
|
|
253
473
|
async function findModelsPath() {
|
|
254
474
|
const commonPaths = [
|
|
@@ -258,12 +478,12 @@ async function findModelsPath() {
|
|
|
258
478
|
"models",
|
|
259
479
|
"app/Models"
|
|
260
480
|
];
|
|
261
|
-
for (const
|
|
262
|
-
if (fs3.existsSync(path3.join(process.cwd(),
|
|
481
|
+
for (const p8 of commonPaths) {
|
|
482
|
+
if (fs3.existsSync(path3.join(process.cwd(), p8))) return p8;
|
|
263
483
|
}
|
|
264
484
|
return null;
|
|
265
485
|
}
|
|
266
|
-
async function makeSeeder(modelName, modelPath) {
|
|
486
|
+
async function makeSeeder(modelName, modelPath, fields = []) {
|
|
267
487
|
const srcPath = path3.join(process.cwd(), "src");
|
|
268
488
|
const useSrc = fs3.existsSync(srcPath) && fs3.lstatSync(srcPath).isDirectory();
|
|
269
489
|
const seederDir = useSrc ? path3.join(process.cwd(), "src/database/seeders") : path3.join(process.cwd(), "database/seeders");
|
|
@@ -273,10 +493,25 @@ async function makeSeeder(modelName, modelPath) {
|
|
|
273
493
|
fs3.mkdirSync(seederDir, { recursive: true });
|
|
274
494
|
}
|
|
275
495
|
const relativeModelPath = path3.relative(seederDir, path3.join(process.cwd(), modelPath, modelName)).replace(/\\/g, "/");
|
|
496
|
+
const dummyData = fields.map((f) => {
|
|
497
|
+
let val = "''";
|
|
498
|
+
if (f.type === "integer" || f.type === "foreign") val = "1";
|
|
499
|
+
if (f.type === "float") val = "1.5";
|
|
500
|
+
if (f.type === "boolean") val = "true";
|
|
501
|
+
if (f.type === "string" || f.type === "text") val = `'Sample ${f.name}'`;
|
|
502
|
+
if (f.type === "json") val = "'{}'";
|
|
503
|
+
if (f.type === "enum")
|
|
504
|
+
val = f.values && f.values.length > 0 ? `'${f.values[0]}'` : "''";
|
|
505
|
+
if (f.type === "date" || f.type === "datetime")
|
|
506
|
+
val = `'${(/* @__PURE__ */ new Date()).toISOString()}'`;
|
|
507
|
+
return ` ${f.name}: ${val},`;
|
|
508
|
+
}).join("\n");
|
|
276
509
|
const template = `import { ${modelName} } from '${relativeModelPath}.mts';
|
|
277
510
|
|
|
278
511
|
export const seed = async () => {
|
|
279
|
-
|
|
512
|
+
await ${modelName}.create({
|
|
513
|
+
${dummyData}
|
|
514
|
+
});
|
|
280
515
|
};
|
|
281
516
|
`;
|
|
282
517
|
if (fs3.existsSync(targetPath)) {
|
|
@@ -284,18 +519,281 @@ export const seed = async () => {
|
|
|
284
519
|
return;
|
|
285
520
|
}
|
|
286
521
|
fs3.writeFileSync(targetPath, template);
|
|
287
|
-
p3.log.success(`Created seeder:
|
|
522
|
+
p3.log.success(`Created seeder: ${seederDir}/${seederName}`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/cli/commands/model-add.ts
|
|
526
|
+
import * as fs4 from "fs";
|
|
527
|
+
import * as path4 from "path";
|
|
528
|
+
import * as p4 from "@clack/prompts";
|
|
529
|
+
async function modelAdd() {
|
|
530
|
+
p4.intro("Adding attributes to an existing model...");
|
|
531
|
+
const modelPath = await findModelsPath2() || "src/database/models";
|
|
532
|
+
const fullModelPath = path4.join(process.cwd(), modelPath);
|
|
533
|
+
if (!fs4.existsSync(fullModelPath)) {
|
|
534
|
+
p4.log.error(`Models directory not found at ${modelPath}.`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const modelFiles = fs4.readdirSync(fullModelPath).filter((f) => f.endsWith(".mts") || f.endsWith(".ts"));
|
|
538
|
+
const options = modelFiles.map((f) => ({
|
|
539
|
+
value: f,
|
|
540
|
+
label: f
|
|
541
|
+
}));
|
|
542
|
+
options.unshift({ value: "new", label: "[ Create new model ]" });
|
|
543
|
+
const selectedModel = await p4.select({
|
|
544
|
+
message: "Select a model to add attributes to:",
|
|
545
|
+
options
|
|
546
|
+
});
|
|
547
|
+
if (p4.isCancel(selectedModel)) {
|
|
548
|
+
p4.cancel("Operation cancelled.");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (selectedModel === "new") {
|
|
552
|
+
await makeModel();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const modelName = selectedModel.replace(".mts", "").replace(".ts", "");
|
|
556
|
+
const targetPath = path4.join(fullModelPath, selectedModel);
|
|
557
|
+
const fields = [];
|
|
558
|
+
const relations = [];
|
|
559
|
+
let addMore = true;
|
|
560
|
+
p4.log.step(`Adding attributes to ${modelName}...`);
|
|
561
|
+
while (addMore) {
|
|
562
|
+
const fieldName = await p4.text({
|
|
563
|
+
message: "New attribute or relation name (leave empty to stop):",
|
|
564
|
+
placeholder: "e.g. description or comments"
|
|
565
|
+
});
|
|
566
|
+
if (p4.isCancel(fieldName)) break;
|
|
567
|
+
if (!fieldName) break;
|
|
568
|
+
const fieldType = await p4.select({
|
|
569
|
+
message: `Type for ${fieldName}:`,
|
|
570
|
+
options: [
|
|
571
|
+
{ value: "string", label: "String" },
|
|
572
|
+
{ value: "integer", label: "Integer" },
|
|
573
|
+
{ value: "boolean", label: "Boolean" },
|
|
574
|
+
{ value: "text", label: "Text" },
|
|
575
|
+
{ value: "float", label: "Float" },
|
|
576
|
+
{ value: "json", label: "JSON" },
|
|
577
|
+
{ value: "enum", label: "Enum" },
|
|
578
|
+
{ value: "date", label: "Date" },
|
|
579
|
+
{ value: "blob", label: "Blob" },
|
|
580
|
+
{ value: "belongsToMany", label: "ManyToMany (Pivot Table)" },
|
|
581
|
+
{ value: "relation", label: "Relation (1:1, 1:N)" }
|
|
582
|
+
]
|
|
583
|
+
});
|
|
584
|
+
if (p4.isCancel(fieldType)) break;
|
|
585
|
+
if (fieldType === "relation" || fieldType === "belongsToMany") {
|
|
586
|
+
let relType = fieldType;
|
|
587
|
+
if (fieldType === "relation") {
|
|
588
|
+
relType = await p4.select({
|
|
589
|
+
message: `Relation type for ${fieldName}:`,
|
|
590
|
+
options: [
|
|
591
|
+
{ value: "hasOne", label: "HasOne" },
|
|
592
|
+
{ value: "hasMany", label: "HasMany" },
|
|
593
|
+
{ value: "belongsTo", label: "BelongsTo" }
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (p4.isCancel(relType)) break;
|
|
598
|
+
const targetModel = await p4.text({
|
|
599
|
+
message: "Target model for this relation:",
|
|
600
|
+
placeholder: "e.g. Comment",
|
|
601
|
+
validate: (v) => !v ? "Target model is required" : void 0
|
|
602
|
+
});
|
|
603
|
+
if (p4.isCancel(targetModel)) break;
|
|
604
|
+
let foreignKey = "";
|
|
605
|
+
let pivotTable = "";
|
|
606
|
+
if (relType === "belongsToMany") {
|
|
607
|
+
p4.log.info("Automatically creating a pivot table migration.");
|
|
608
|
+
pivotTable = await p4.text({
|
|
609
|
+
message: "Pivot table name:",
|
|
610
|
+
initialValue: [modelName.toLowerCase(), targetModel.toLowerCase()].sort().join("_")
|
|
611
|
+
});
|
|
612
|
+
if (p4.isCancel(pivotTable)) break;
|
|
613
|
+
} else if (relType === "belongsTo") {
|
|
614
|
+
foreignKey = await p4.text({
|
|
615
|
+
message: "Foreign key column name:",
|
|
616
|
+
placeholder: `e.g. ${targetModel.toLowerCase()}_id`,
|
|
617
|
+
initialValue: `${targetModel.toLowerCase()}_id`
|
|
618
|
+
});
|
|
619
|
+
if (p4.isCancel(foreignKey)) break;
|
|
620
|
+
fields.push({
|
|
621
|
+
name: foreignKey,
|
|
622
|
+
type: "foreign",
|
|
623
|
+
nullable: false,
|
|
624
|
+
unique: false
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
relations.push({
|
|
628
|
+
method: fieldName,
|
|
629
|
+
type: relType,
|
|
630
|
+
model: targetModel,
|
|
631
|
+
foreignKey,
|
|
632
|
+
pivotTable
|
|
633
|
+
});
|
|
634
|
+
const addInverse = await p4.confirm({
|
|
635
|
+
message: `Add the inverse relation in ${targetModel}.mts?`,
|
|
636
|
+
initialValue: true
|
|
637
|
+
});
|
|
638
|
+
if (addInverse && !p4.isCancel(addInverse)) {
|
|
639
|
+
let inverseType = "belongsTo";
|
|
640
|
+
if (relType === "belongsTo") inverseType = "hasMany";
|
|
641
|
+
if (relType === "hasOne") inverseType = "belongsTo";
|
|
642
|
+
if (relType === "belongsToMany") inverseType = "belongsToMany";
|
|
643
|
+
const inverseMethod = await p4.text({
|
|
644
|
+
message: "Inverse method name:",
|
|
645
|
+
initialValue: relType === "belongsTo" ? modelName.toLowerCase() + "s" : modelName.toLowerCase()
|
|
646
|
+
});
|
|
647
|
+
if (!p4.isCancel(inverseMethod)) {
|
|
648
|
+
await updateTargetModel2(
|
|
649
|
+
targetModel,
|
|
650
|
+
inverseMethod,
|
|
651
|
+
inverseType,
|
|
652
|
+
modelName,
|
|
653
|
+
pivotTable
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
let enumValues = [];
|
|
659
|
+
if (fieldType === "enum") {
|
|
660
|
+
const valuesStr = await p4.text({
|
|
661
|
+
message: "Allowed values (comma separated):",
|
|
662
|
+
placeholder: "active, inactive, pending"
|
|
663
|
+
});
|
|
664
|
+
if (p4.isCancel(valuesStr)) break;
|
|
665
|
+
enumValues = valuesStr.split(",").map((v) => v.trim());
|
|
666
|
+
}
|
|
667
|
+
const isNullable = await p4.confirm({
|
|
668
|
+
message: "Is it nullable?",
|
|
669
|
+
initialValue: false
|
|
670
|
+
});
|
|
671
|
+
if (p4.isCancel(isNullable)) break;
|
|
672
|
+
const isUnique = await p4.confirm({
|
|
673
|
+
message: "Is it unique?",
|
|
674
|
+
initialValue: false
|
|
675
|
+
});
|
|
676
|
+
if (p4.isCancel(isUnique)) break;
|
|
677
|
+
fields.push({
|
|
678
|
+
name: fieldName,
|
|
679
|
+
type: fieldType,
|
|
680
|
+
nullable: isNullable,
|
|
681
|
+
unique: isUnique,
|
|
682
|
+
values: enumValues
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (fields.length === 0 && relations.length === 0) {
|
|
687
|
+
p4.outro("No changes made.");
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
let modelContent = fs4.readFileSync(targetPath, "utf-8");
|
|
691
|
+
const newImports = relations.filter((r) => !modelContent.includes(`import { ${r.model} }`)).map((r) => `import { ${r.model} } from './${r.model}.mts';`).join("\n");
|
|
692
|
+
if (newImports) {
|
|
693
|
+
modelContent = newImports + "\n" + modelContent;
|
|
694
|
+
}
|
|
695
|
+
const fieldProperties = fields.filter(
|
|
696
|
+
(f) => f.type !== "foreign" || !relations.some((r) => r.foreignKey === f.name)
|
|
697
|
+
).map((f) => {
|
|
698
|
+
let tsType = "string";
|
|
699
|
+
if (f.type === "integer" || f.type === "float") tsType = "number";
|
|
700
|
+
else if (f.type === "boolean") tsType = "boolean";
|
|
701
|
+
else if (f.type === "json") tsType = "any";
|
|
702
|
+
else if (f.type === "blob") tsType = "Uint8Array";
|
|
703
|
+
return ` declare ${f.name}${f.nullable ? "?" : ""}: ${tsType};`;
|
|
704
|
+
}).join("\n");
|
|
705
|
+
const relationMethods = relations.map((r) => {
|
|
706
|
+
return ` ${r.method}() {
|
|
707
|
+
return this.${r.type}(${r.model});
|
|
708
|
+
}`;
|
|
709
|
+
}).join("\n\n");
|
|
710
|
+
const lastBraceIndex = modelContent.lastIndexOf("}");
|
|
711
|
+
if (lastBraceIndex !== -1) {
|
|
712
|
+
modelContent = modelContent.substring(0, lastBraceIndex) + fieldProperties + "\n" + relationMethods + "\n" + modelContent.substring(lastBraceIndex);
|
|
713
|
+
fs4.writeFileSync(targetPath, modelContent);
|
|
714
|
+
p4.log.success(`Updated model: ${modelPath}/${selectedModel}`);
|
|
715
|
+
}
|
|
716
|
+
if (fields.length > 0) {
|
|
717
|
+
const migrationName = `add_fields_to_${modelName.toLowerCase()}s_table`;
|
|
718
|
+
await makeMigration(migrationName, fields);
|
|
719
|
+
}
|
|
720
|
+
for (const rel of relations) {
|
|
721
|
+
if (rel.type === "belongsToMany") {
|
|
722
|
+
const pivotMigrationName = `create_${rel.pivotTable}_table`;
|
|
723
|
+
const pivotFields = [
|
|
724
|
+
{
|
|
725
|
+
name: `${modelName.toLowerCase()}_id`,
|
|
726
|
+
type: "integer",
|
|
727
|
+
nullable: false
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: `${rel.model.toLowerCase()}_id`,
|
|
731
|
+
type: "integer",
|
|
732
|
+
nullable: false
|
|
733
|
+
}
|
|
734
|
+
];
|
|
735
|
+
await makeMigration(pivotMigrationName, pivotFields);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
p4.outro("Model update complete!");
|
|
739
|
+
}
|
|
740
|
+
async function updateTargetModel2(targetModel, methodName, relType, sourceModel, pivotTable = "") {
|
|
741
|
+
const modelPath = await findModelsPath2() || "src/database/models";
|
|
742
|
+
const filename = `${targetModel}.mts`;
|
|
743
|
+
const targetPath = path4.join(process.cwd(), modelPath, filename);
|
|
744
|
+
if (!fs4.existsSync(targetPath)) {
|
|
745
|
+
p4.log.warn(
|
|
746
|
+
`Model ${targetModel} not found at ${modelPath}. Skipping inverse relation.`
|
|
747
|
+
);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
let content = fs4.readFileSync(targetPath, "utf-8");
|
|
751
|
+
if (!content.includes(`import { ${sourceModel} }`)) {
|
|
752
|
+
content = `import { ${sourceModel} } from './${sourceModel}.mts';
|
|
753
|
+
` + content;
|
|
754
|
+
}
|
|
755
|
+
let methodString = "";
|
|
756
|
+
if (relType === "belongsToMany") {
|
|
757
|
+
methodString = ` ${methodName}() {
|
|
758
|
+
return this.belongsToMany(${sourceModel}, '${pivotTable}');
|
|
759
|
+
}`;
|
|
760
|
+
} else {
|
|
761
|
+
methodString = ` ${methodName}() {
|
|
762
|
+
return this.${relType}(${sourceModel});
|
|
763
|
+
}`;
|
|
764
|
+
}
|
|
765
|
+
const lastBraceIndex = content.lastIndexOf("}");
|
|
766
|
+
if (lastBraceIndex !== -1) {
|
|
767
|
+
content = content.substring(0, lastBraceIndex) + "\n" + methodString + "\n" + content.substring(lastBraceIndex);
|
|
768
|
+
fs4.writeFileSync(targetPath, content);
|
|
769
|
+
p4.log.success(
|
|
770
|
+
`Updated ${targetModel}.mts with inverse relation: ${methodName}()`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function findModelsPath2() {
|
|
775
|
+
const commonPaths = [
|
|
776
|
+
"src/database/models",
|
|
777
|
+
"database/models",
|
|
778
|
+
"src/models",
|
|
779
|
+
"models",
|
|
780
|
+
"app/Models"
|
|
781
|
+
];
|
|
782
|
+
for (const p8 of commonPaths) {
|
|
783
|
+
if (fs4.existsSync(path4.join(process.cwd(), p8))) return p8;
|
|
784
|
+
}
|
|
785
|
+
return null;
|
|
288
786
|
}
|
|
289
787
|
|
|
290
788
|
// src/cli/commands/migrate.ts
|
|
291
|
-
import * as
|
|
292
|
-
import * as
|
|
789
|
+
import * as fs7 from "fs";
|
|
790
|
+
import * as path7 from "path";
|
|
293
791
|
import { execSync as execSync2 } from "child_process";
|
|
294
|
-
import * as
|
|
792
|
+
import * as p6 from "@clack/prompts";
|
|
295
793
|
|
|
296
794
|
// src/cli/utils/config.ts
|
|
297
|
-
import * as
|
|
298
|
-
import * as
|
|
795
|
+
import * as fs5 from "fs";
|
|
796
|
+
import * as path5 from "path";
|
|
299
797
|
var Config = class {
|
|
300
798
|
/**
|
|
301
799
|
* Detect the D1 database binding from Wrangler configuration or d1-orm config.
|
|
@@ -303,24 +801,24 @@ var Config = class {
|
|
|
303
801
|
* @returns The binding name (e.g., "DB").
|
|
304
802
|
*/
|
|
305
803
|
static getD1Binding() {
|
|
306
|
-
const srcConfig =
|
|
307
|
-
const srcConfigTs =
|
|
308
|
-
const rootConfig =
|
|
309
|
-
const rootConfigTs =
|
|
310
|
-
const configPath =
|
|
804
|
+
const srcConfig = path5.join(process.cwd(), "src/database/config.mts");
|
|
805
|
+
const srcConfigTs = path5.join(process.cwd(), "src/database/config.ts");
|
|
806
|
+
const rootConfig = path5.join(process.cwd(), "database/config.mts");
|
|
807
|
+
const rootConfigTs = path5.join(process.cwd(), "database/config.ts");
|
|
808
|
+
const configPath = fs5.existsSync(srcConfig) ? srcConfig : fs5.existsSync(srcConfigTs) ? srcConfigTs : fs5.existsSync(rootConfig) ? rootConfig : fs5.existsSync(rootConfigTs) ? rootConfigTs : null;
|
|
311
809
|
if (configPath) {
|
|
312
|
-
const content =
|
|
810
|
+
const content = fs5.readFileSync(configPath, "utf-8");
|
|
313
811
|
const match = content.match(/binding\s*:\s*["'](.+?)["']/);
|
|
314
812
|
if (match) return match[1];
|
|
315
813
|
}
|
|
316
814
|
const wranglerPaths = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
317
815
|
for (const configName of wranglerPaths) {
|
|
318
|
-
const fullPath =
|
|
319
|
-
if (
|
|
320
|
-
const content =
|
|
816
|
+
const fullPath = path5.join(process.cwd(), configName);
|
|
817
|
+
if (fs5.existsSync(fullPath)) {
|
|
818
|
+
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
321
819
|
if (configName.endsWith(".json") || configName.endsWith(".jsonc")) {
|
|
322
820
|
try {
|
|
323
|
-
const jsonStr = content.replace(
|
|
821
|
+
const jsonStr = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
324
822
|
const config = JSON.parse(jsonStr);
|
|
325
823
|
const d1Databases = config.d1_databases;
|
|
326
824
|
if (Array.isArray(d1Databases) && d1Databases.length > 0) {
|
|
@@ -337,14 +835,28 @@ var Config = class {
|
|
|
337
835
|
}
|
|
338
836
|
return "DB";
|
|
339
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Get a filtered environment object to avoid system warnings.
|
|
840
|
+
* Filters out npm_config_* variables and sets NODE_NO_WARNINGS.
|
|
841
|
+
*/
|
|
842
|
+
static getCleanEnv() {
|
|
843
|
+
const cleanEnv = { ...process.env };
|
|
844
|
+
Object.keys(cleanEnv).forEach((key) => {
|
|
845
|
+
if (key.startsWith("npm_config_")) {
|
|
846
|
+
delete cleanEnv[key];
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
cleanEnv.NODE_NO_WARNINGS = "1";
|
|
850
|
+
return cleanEnv;
|
|
851
|
+
}
|
|
340
852
|
/**
|
|
341
853
|
* Detect if the project is ESM.
|
|
342
854
|
*/
|
|
343
855
|
static isESM() {
|
|
344
|
-
const pkgPath =
|
|
345
|
-
if (
|
|
856
|
+
const pkgPath = path5.join(process.cwd(), "package.json");
|
|
857
|
+
if (fs5.existsSync(pkgPath)) {
|
|
346
858
|
try {
|
|
347
|
-
const pkg = JSON.parse(
|
|
859
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
348
860
|
return pkg.type === "module";
|
|
349
861
|
} catch (e) {
|
|
350
862
|
return false;
|
|
@@ -355,8 +867,8 @@ var Config = class {
|
|
|
355
867
|
};
|
|
356
868
|
|
|
357
869
|
// src/cli/commands/seed.ts
|
|
358
|
-
import * as
|
|
359
|
-
import * as
|
|
870
|
+
import * as fs6 from "fs";
|
|
871
|
+
import * as path6 from "path";
|
|
360
872
|
|
|
361
873
|
// src/cli/cli-connection.ts
|
|
362
874
|
import { execSync } from "child_process";
|
|
@@ -467,38 +979,38 @@ var CLIPREparedStatement = class {
|
|
|
467
979
|
};
|
|
468
980
|
|
|
469
981
|
// src/cli/commands/seed.ts
|
|
470
|
-
import * as
|
|
982
|
+
import * as p5 from "@clack/prompts";
|
|
471
983
|
async function seed(args2) {
|
|
472
|
-
|
|
473
|
-
const srcSeedersDir =
|
|
474
|
-
const rootSeedersDir =
|
|
475
|
-
const seedersDir =
|
|
476
|
-
if (!
|
|
477
|
-
|
|
984
|
+
p5.intro("Seeding database...");
|
|
985
|
+
const srcSeedersDir = path6.join(process.cwd(), "src/database/seeders");
|
|
986
|
+
const rootSeedersDir = path6.join(process.cwd(), "database/seeders");
|
|
987
|
+
const seedersDir = fs6.existsSync(srcSeedersDir) ? srcSeedersDir : rootSeedersDir;
|
|
988
|
+
if (!fs6.existsSync(seedersDir)) {
|
|
989
|
+
p5.log.warn(
|
|
478
990
|
`No seeders directory found (checked ${srcSeedersDir} and ${rootSeedersDir}).`
|
|
479
991
|
);
|
|
480
|
-
|
|
992
|
+
p5.outro("Nothing to seed.");
|
|
481
993
|
return;
|
|
482
994
|
}
|
|
483
995
|
const isRemote = args2.includes("--remote");
|
|
484
996
|
const dbName = Config.getD1Binding();
|
|
485
|
-
const s =
|
|
997
|
+
const s = p5.spinner();
|
|
486
998
|
s.start(`Initializing ORM (Binding: ${dbName})...`);
|
|
487
999
|
try {
|
|
488
1000
|
const connection = new CLIConnection(dbName, isRemote);
|
|
489
1001
|
Database.setup(connection);
|
|
490
1002
|
s.stop(`ORM initialized successfully with binding "${dbName}".`);
|
|
491
|
-
const files =
|
|
1003
|
+
const files = fs6.readdirSync(seedersDir).filter(
|
|
492
1004
|
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mts") || f.endsWith(".mjs")
|
|
493
1005
|
).sort();
|
|
494
1006
|
if (files.length === 0) {
|
|
495
|
-
|
|
496
|
-
|
|
1007
|
+
p5.log.info("No seeder files found.");
|
|
1008
|
+
p5.outro("Seeding complete.");
|
|
497
1009
|
return;
|
|
498
1010
|
}
|
|
499
1011
|
for (const file of files) {
|
|
500
1012
|
s.start(`Running seeder: ${file}`);
|
|
501
|
-
const filePath =
|
|
1013
|
+
const filePath = path6.join(seedersDir, file);
|
|
502
1014
|
try {
|
|
503
1015
|
const seeder = await import(filePath);
|
|
504
1016
|
if (seeder.seed) {
|
|
@@ -509,59 +1021,62 @@ async function seed(args2) {
|
|
|
509
1021
|
}
|
|
510
1022
|
} catch (error) {
|
|
511
1023
|
s.stop(`Failed: ${file}`, 1);
|
|
512
|
-
|
|
1024
|
+
p5.log.error(`Error running seeder ${file}: ${error}`);
|
|
513
1025
|
}
|
|
514
1026
|
}
|
|
515
|
-
|
|
1027
|
+
p5.outro("Seeding completed successfully.");
|
|
516
1028
|
} catch (error) {
|
|
517
1029
|
s.stop("Initialization failed.", 1);
|
|
518
|
-
|
|
1030
|
+
p5.log.error(`Seeding error: ${error}`);
|
|
519
1031
|
}
|
|
520
1032
|
}
|
|
521
1033
|
|
|
522
1034
|
// src/cli/commands/migrate.ts
|
|
523
1035
|
async function migrate(args2) {
|
|
524
|
-
|
|
525
|
-
const srcMigrationsDir =
|
|
526
|
-
const rootMigrationsDir =
|
|
527
|
-
const migrationsDir =
|
|
528
|
-
if (!
|
|
529
|
-
|
|
1036
|
+
p6.intro("Running migrations...");
|
|
1037
|
+
const srcMigrationsDir = path7.join(process.cwd(), "src/database/migrations");
|
|
1038
|
+
const rootMigrationsDir = path7.join(process.cwd(), "database/migrations");
|
|
1039
|
+
const migrationsDir = fs7.existsSync(srcMigrationsDir) ? srcMigrationsDir : rootMigrationsDir;
|
|
1040
|
+
if (!fs7.existsSync(migrationsDir)) {
|
|
1041
|
+
p6.log.warn(
|
|
530
1042
|
`No migrations directory found (checked ${srcMigrationsDir} and ${rootMigrationsDir}).`
|
|
531
1043
|
);
|
|
532
|
-
|
|
1044
|
+
p6.outro("Nothing to migrate.");
|
|
533
1045
|
return;
|
|
534
1046
|
}
|
|
535
|
-
const files =
|
|
1047
|
+
const files = fs7.readdirSync(migrationsDir).filter(
|
|
536
1048
|
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mts") || f.endsWith(".mjs")
|
|
537
1049
|
).sort();
|
|
538
1050
|
if (files.length === 0) {
|
|
539
|
-
|
|
540
|
-
|
|
1051
|
+
p6.log.info("No migration files found.");
|
|
1052
|
+
p6.outro("Migrations complete.");
|
|
541
1053
|
return;
|
|
542
1054
|
}
|
|
543
|
-
const s =
|
|
1055
|
+
const s = p6.spinner();
|
|
544
1056
|
const dbName = Config.getD1Binding();
|
|
545
1057
|
for (const file of files) {
|
|
546
1058
|
s.start(`Processing migration: ${file} (Binding: ${dbName})`);
|
|
547
|
-
const filePath =
|
|
1059
|
+
const filePath = path7.join(migrationsDir, file);
|
|
548
1060
|
try {
|
|
549
1061
|
const migration = await import(filePath);
|
|
550
1062
|
if (migration.up) {
|
|
551
1063
|
const sql = await migration.up();
|
|
552
1064
|
if (sql) {
|
|
1065
|
+
const sqlStatements = Array.isArray(sql) ? sql : [sql];
|
|
553
1066
|
const isRemote = args2.includes("--remote");
|
|
554
1067
|
const command2 = isRemote ? "--remote" : "--local";
|
|
555
1068
|
try {
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1069
|
+
for (const statement of sqlStatements) {
|
|
1070
|
+
const execCmd = `npx wrangler d1 execute ${dbName} --command "${statement.replace(/"/g, '\\"')}" ${command2}`;
|
|
1071
|
+
execSync2(execCmd, {
|
|
1072
|
+
stdio: "pipe",
|
|
1073
|
+
env: Config.getCleanEnv()
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
561
1076
|
s.stop(`Migrated: ${file}`);
|
|
562
1077
|
} catch (e) {
|
|
563
1078
|
s.stop(`Failed: ${file}`, 1);
|
|
564
|
-
|
|
1079
|
+
p6.log.error(`Failed to execute migration: ${file}`);
|
|
565
1080
|
throw e;
|
|
566
1081
|
}
|
|
567
1082
|
} else {
|
|
@@ -572,10 +1087,10 @@ async function migrate(args2) {
|
|
|
572
1087
|
}
|
|
573
1088
|
} catch (error) {
|
|
574
1089
|
s.stop(`Error: ${file}`, 1);
|
|
575
|
-
|
|
1090
|
+
p6.log.error(`Error processing migration ${file}: ${error}`);
|
|
576
1091
|
}
|
|
577
1092
|
}
|
|
578
|
-
|
|
1093
|
+
p6.outro("All migrations executed.");
|
|
579
1094
|
if (args2.includes("--seed")) {
|
|
580
1095
|
await seed(args2);
|
|
581
1096
|
}
|
|
@@ -583,19 +1098,20 @@ async function migrate(args2) {
|
|
|
583
1098
|
|
|
584
1099
|
// src/cli/commands/migrate-fresh.ts
|
|
585
1100
|
import { execSync as execSync3 } from "child_process";
|
|
586
|
-
import * as
|
|
1101
|
+
import * as p7 from "@clack/prompts";
|
|
587
1102
|
async function migrateFresh(args2) {
|
|
588
|
-
|
|
1103
|
+
p7.intro("Resetting database...");
|
|
589
1104
|
const isRemote = args2.includes("--remote");
|
|
590
1105
|
const dbName = Config.getD1Binding();
|
|
591
1106
|
const flag = isRemote ? "--remote" : "--local";
|
|
592
|
-
const s =
|
|
1107
|
+
const s = p7.spinner();
|
|
593
1108
|
s.start("Scanning for tables to drop...");
|
|
594
1109
|
try {
|
|
595
1110
|
const listTablesCmd = `npx wrangler d1 execute ${dbName} --command "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%'" ${flag} --json`;
|
|
596
1111
|
const output = execSync3(listTablesCmd, {
|
|
597
1112
|
encoding: "utf-8",
|
|
598
|
-
stdio: ["ignore", "pipe", "
|
|
1113
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1114
|
+
env: Config.getCleanEnv()
|
|
599
1115
|
});
|
|
600
1116
|
const jsonStart = output.indexOf("[");
|
|
601
1117
|
if (jsonStart !== -1) {
|
|
@@ -605,7 +1121,10 @@ async function migrateFresh(args2) {
|
|
|
605
1121
|
s.message(`Dropping ${tables.length} tables...`);
|
|
606
1122
|
const dropCommands = tables.map((t) => `DROP TABLE IF EXISTS ${t.name};`).join(" ");
|
|
607
1123
|
const dropCmd = `npx wrangler d1 execute ${dbName} --command "${dropCommands}" ${flag}`;
|
|
608
|
-
execSync3(dropCmd, {
|
|
1124
|
+
execSync3(dropCmd, {
|
|
1125
|
+
stdio: "pipe",
|
|
1126
|
+
env: Config.getCleanEnv()
|
|
1127
|
+
});
|
|
609
1128
|
s.stop("All tables dropped successfully.");
|
|
610
1129
|
} else {
|
|
611
1130
|
s.stop("No tables found to drop.");
|
|
@@ -616,7 +1135,7 @@ async function migrateFresh(args2) {
|
|
|
616
1135
|
await migrate(args2);
|
|
617
1136
|
} catch (error) {
|
|
618
1137
|
s.stop("Process failed.", 1);
|
|
619
|
-
|
|
1138
|
+
p7.log.error(`Error during migrate:fresh: ${error}`);
|
|
620
1139
|
}
|
|
621
1140
|
}
|
|
622
1141
|
|
|
@@ -634,6 +1153,9 @@ switch (command) {
|
|
|
634
1153
|
case "make:migration":
|
|
635
1154
|
makeMigration(param);
|
|
636
1155
|
break;
|
|
1156
|
+
case "model:add":
|
|
1157
|
+
modelAdd();
|
|
1158
|
+
break;
|
|
637
1159
|
case "migrate":
|
|
638
1160
|
migrate(args);
|
|
639
1161
|
break;
|
|
@@ -645,7 +1167,7 @@ switch (command) {
|
|
|
645
1167
|
break;
|
|
646
1168
|
default:
|
|
647
1169
|
console.log(
|
|
648
|
-
"Available commands: init, make:model, make:migration, migrate, migrate:fresh, db:seed"
|
|
1170
|
+
"Available commands: init, make:model, make:migration, model:add, migrate, migrate:fresh, db:seed"
|
|
649
1171
|
);
|
|
650
1172
|
break;
|
|
651
1173
|
}
|