@3lineas/d1-orm 1.0.11 → 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 +598 -94
- package/dist/cli/index.js +598 -94
- 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,21 +801,21 @@ 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
821
|
const jsonStr = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
@@ -355,10 +853,10 @@ var Config = class {
|
|
|
355
853
|
* Detect if the project is ESM.
|
|
356
854
|
*/
|
|
357
855
|
static isESM() {
|
|
358
|
-
const pkgPath =
|
|
359
|
-
if (
|
|
856
|
+
const pkgPath = path5.join(process.cwd(), "package.json");
|
|
857
|
+
if (fs5.existsSync(pkgPath)) {
|
|
360
858
|
try {
|
|
361
|
-
const pkg = JSON.parse(
|
|
859
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
362
860
|
return pkg.type === "module";
|
|
363
861
|
} catch (e) {
|
|
364
862
|
return false;
|
|
@@ -369,8 +867,8 @@ var Config = class {
|
|
|
369
867
|
};
|
|
370
868
|
|
|
371
869
|
// src/cli/commands/seed.ts
|
|
372
|
-
import * as
|
|
373
|
-
import * as
|
|
870
|
+
import * as fs6 from "fs";
|
|
871
|
+
import * as path6 from "path";
|
|
374
872
|
|
|
375
873
|
// src/cli/cli-connection.ts
|
|
376
874
|
import { execSync } from "child_process";
|
|
@@ -481,38 +979,38 @@ var CLIPREparedStatement = class {
|
|
|
481
979
|
};
|
|
482
980
|
|
|
483
981
|
// src/cli/commands/seed.ts
|
|
484
|
-
import * as
|
|
982
|
+
import * as p5 from "@clack/prompts";
|
|
485
983
|
async function seed(args2) {
|
|
486
|
-
|
|
487
|
-
const srcSeedersDir =
|
|
488
|
-
const rootSeedersDir =
|
|
489
|
-
const seedersDir =
|
|
490
|
-
if (!
|
|
491
|
-
|
|
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(
|
|
492
990
|
`No seeders directory found (checked ${srcSeedersDir} and ${rootSeedersDir}).`
|
|
493
991
|
);
|
|
494
|
-
|
|
992
|
+
p5.outro("Nothing to seed.");
|
|
495
993
|
return;
|
|
496
994
|
}
|
|
497
995
|
const isRemote = args2.includes("--remote");
|
|
498
996
|
const dbName = Config.getD1Binding();
|
|
499
|
-
const s =
|
|
997
|
+
const s = p5.spinner();
|
|
500
998
|
s.start(`Initializing ORM (Binding: ${dbName})...`);
|
|
501
999
|
try {
|
|
502
1000
|
const connection = new CLIConnection(dbName, isRemote);
|
|
503
1001
|
Database.setup(connection);
|
|
504
1002
|
s.stop(`ORM initialized successfully with binding "${dbName}".`);
|
|
505
|
-
const files =
|
|
1003
|
+
const files = fs6.readdirSync(seedersDir).filter(
|
|
506
1004
|
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mts") || f.endsWith(".mjs")
|
|
507
1005
|
).sort();
|
|
508
1006
|
if (files.length === 0) {
|
|
509
|
-
|
|
510
|
-
|
|
1007
|
+
p5.log.info("No seeder files found.");
|
|
1008
|
+
p5.outro("Seeding complete.");
|
|
511
1009
|
return;
|
|
512
1010
|
}
|
|
513
1011
|
for (const file of files) {
|
|
514
1012
|
s.start(`Running seeder: ${file}`);
|
|
515
|
-
const filePath =
|
|
1013
|
+
const filePath = path6.join(seedersDir, file);
|
|
516
1014
|
try {
|
|
517
1015
|
const seeder = await import(filePath);
|
|
518
1016
|
if (seeder.seed) {
|
|
@@ -523,59 +1021,62 @@ async function seed(args2) {
|
|
|
523
1021
|
}
|
|
524
1022
|
} catch (error) {
|
|
525
1023
|
s.stop(`Failed: ${file}`, 1);
|
|
526
|
-
|
|
1024
|
+
p5.log.error(`Error running seeder ${file}: ${error}`);
|
|
527
1025
|
}
|
|
528
1026
|
}
|
|
529
|
-
|
|
1027
|
+
p5.outro("Seeding completed successfully.");
|
|
530
1028
|
} catch (error) {
|
|
531
1029
|
s.stop("Initialization failed.", 1);
|
|
532
|
-
|
|
1030
|
+
p5.log.error(`Seeding error: ${error}`);
|
|
533
1031
|
}
|
|
534
1032
|
}
|
|
535
1033
|
|
|
536
1034
|
// src/cli/commands/migrate.ts
|
|
537
1035
|
async function migrate(args2) {
|
|
538
|
-
|
|
539
|
-
const srcMigrationsDir =
|
|
540
|
-
const rootMigrationsDir =
|
|
541
|
-
const migrationsDir =
|
|
542
|
-
if (!
|
|
543
|
-
|
|
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(
|
|
544
1042
|
`No migrations directory found (checked ${srcMigrationsDir} and ${rootMigrationsDir}).`
|
|
545
1043
|
);
|
|
546
|
-
|
|
1044
|
+
p6.outro("Nothing to migrate.");
|
|
547
1045
|
return;
|
|
548
1046
|
}
|
|
549
|
-
const files =
|
|
1047
|
+
const files = fs7.readdirSync(migrationsDir).filter(
|
|
550
1048
|
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".mts") || f.endsWith(".mjs")
|
|
551
1049
|
).sort();
|
|
552
1050
|
if (files.length === 0) {
|
|
553
|
-
|
|
554
|
-
|
|
1051
|
+
p6.log.info("No migration files found.");
|
|
1052
|
+
p6.outro("Migrations complete.");
|
|
555
1053
|
return;
|
|
556
1054
|
}
|
|
557
|
-
const s =
|
|
1055
|
+
const s = p6.spinner();
|
|
558
1056
|
const dbName = Config.getD1Binding();
|
|
559
1057
|
for (const file of files) {
|
|
560
1058
|
s.start(`Processing migration: ${file} (Binding: ${dbName})`);
|
|
561
|
-
const filePath =
|
|
1059
|
+
const filePath = path7.join(migrationsDir, file);
|
|
562
1060
|
try {
|
|
563
1061
|
const migration = await import(filePath);
|
|
564
1062
|
if (migration.up) {
|
|
565
1063
|
const sql = await migration.up();
|
|
566
1064
|
if (sql) {
|
|
1065
|
+
const sqlStatements = Array.isArray(sql) ? sql : [sql];
|
|
567
1066
|
const isRemote = args2.includes("--remote");
|
|
568
1067
|
const command2 = isRemote ? "--remote" : "--local";
|
|
569
1068
|
try {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
+
}
|
|
575
1076
|
s.stop(`Migrated: ${file}`);
|
|
576
1077
|
} catch (e) {
|
|
577
1078
|
s.stop(`Failed: ${file}`, 1);
|
|
578
|
-
|
|
1079
|
+
p6.log.error(`Failed to execute migration: ${file}`);
|
|
579
1080
|
throw e;
|
|
580
1081
|
}
|
|
581
1082
|
} else {
|
|
@@ -586,10 +1087,10 @@ async function migrate(args2) {
|
|
|
586
1087
|
}
|
|
587
1088
|
} catch (error) {
|
|
588
1089
|
s.stop(`Error: ${file}`, 1);
|
|
589
|
-
|
|
1090
|
+
p6.log.error(`Error processing migration ${file}: ${error}`);
|
|
590
1091
|
}
|
|
591
1092
|
}
|
|
592
|
-
|
|
1093
|
+
p6.outro("All migrations executed.");
|
|
593
1094
|
if (args2.includes("--seed")) {
|
|
594
1095
|
await seed(args2);
|
|
595
1096
|
}
|
|
@@ -597,13 +1098,13 @@ async function migrate(args2) {
|
|
|
597
1098
|
|
|
598
1099
|
// src/cli/commands/migrate-fresh.ts
|
|
599
1100
|
import { execSync as execSync3 } from "child_process";
|
|
600
|
-
import * as
|
|
1101
|
+
import * as p7 from "@clack/prompts";
|
|
601
1102
|
async function migrateFresh(args2) {
|
|
602
|
-
|
|
1103
|
+
p7.intro("Resetting database...");
|
|
603
1104
|
const isRemote = args2.includes("--remote");
|
|
604
1105
|
const dbName = Config.getD1Binding();
|
|
605
1106
|
const flag = isRemote ? "--remote" : "--local";
|
|
606
|
-
const s =
|
|
1107
|
+
const s = p7.spinner();
|
|
607
1108
|
s.start("Scanning for tables to drop...");
|
|
608
1109
|
try {
|
|
609
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`;
|
|
@@ -634,7 +1135,7 @@ async function migrateFresh(args2) {
|
|
|
634
1135
|
await migrate(args2);
|
|
635
1136
|
} catch (error) {
|
|
636
1137
|
s.stop("Process failed.", 1);
|
|
637
|
-
|
|
1138
|
+
p7.log.error(`Error during migrate:fresh: ${error}`);
|
|
638
1139
|
}
|
|
639
1140
|
}
|
|
640
1141
|
|
|
@@ -652,6 +1153,9 @@ switch (command) {
|
|
|
652
1153
|
case "make:migration":
|
|
653
1154
|
makeMigration(param);
|
|
654
1155
|
break;
|
|
1156
|
+
case "model:add":
|
|
1157
|
+
modelAdd();
|
|
1158
|
+
break;
|
|
655
1159
|
case "migrate":
|
|
656
1160
|
migrate(args);
|
|
657
1161
|
break;
|
|
@@ -663,7 +1167,7 @@ switch (command) {
|
|
|
663
1167
|
break;
|
|
664
1168
|
default:
|
|
665
1169
|
console.log(
|
|
666
|
-
"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"
|
|
667
1171
|
);
|
|
668
1172
|
break;
|
|
669
1173
|
}
|