@famgia/omnify-laravel 0.0.16 → 0.0.18
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/{chunk-CAWNYSF3.js → chunk-H37M25AK.js} +210 -19
- package/dist/chunk-H37M25AK.js.map +1 -0
- package/dist/index.cjs +208 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +489 -17
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-CAWNYSF3.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
|
|
1
|
+
import { CustomTypeDefinition, PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
|
|
2
2
|
import { SchemaChange } from '@famgia/omnify-atlas';
|
|
3
3
|
export { LaravelPluginOptions, laravelPlugin } from './plugin.cjs';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ export { LaravelPluginOptions, laravelPlugin } from './plugin.cjs';
|
|
|
7
7
|
*
|
|
8
8
|
* Types for Laravel migration generation.
|
|
9
9
|
*/
|
|
10
|
+
|
|
10
11
|
/**
|
|
11
12
|
* Laravel migration file structure.
|
|
12
13
|
*/
|
|
@@ -34,6 +35,8 @@ interface MigrationOptions {
|
|
|
34
35
|
readonly generateDown?: boolean | undefined;
|
|
35
36
|
/** Database connection name */
|
|
36
37
|
readonly connection?: string | undefined;
|
|
38
|
+
/** Custom types from plugins (for compound type expansion) */
|
|
39
|
+
readonly customTypes?: ReadonlyMap<string, CustomTypeDefinition> | undefined;
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* Schema Builder column method.
|
|
@@ -161,10 +164,17 @@ declare function generateForeignKey(propertyName: string, property: PropertyDefi
|
|
|
161
164
|
foreignKey: ForeignKeyDefinition;
|
|
162
165
|
index: IndexDefinition;
|
|
163
166
|
} | null;
|
|
167
|
+
/**
|
|
168
|
+
* Options for schema to blueprint conversion.
|
|
169
|
+
*/
|
|
170
|
+
interface SchemaToBlueprintOptions {
|
|
171
|
+
/** Custom types from plugins (for compound type expansion) */
|
|
172
|
+
customTypes?: ReadonlyMap<string, CustomTypeDefinition>;
|
|
173
|
+
}
|
|
164
174
|
/**
|
|
165
175
|
* Generates table blueprint from schema.
|
|
166
176
|
*/
|
|
167
|
-
declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection): TableBlueprint;
|
|
177
|
+
declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection, options?: SchemaToBlueprintOptions): TableBlueprint;
|
|
168
178
|
/**
|
|
169
179
|
* Formats a column method to PHP code.
|
|
170
180
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
|
|
1
|
+
import { CustomTypeDefinition, PropertyDefinition, SchemaCollection, LoadedSchema } from '@famgia/omnify-types';
|
|
2
2
|
import { SchemaChange } from '@famgia/omnify-atlas';
|
|
3
3
|
export { LaravelPluginOptions, laravelPlugin } from './plugin.js';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ export { LaravelPluginOptions, laravelPlugin } from './plugin.js';
|
|
|
7
7
|
*
|
|
8
8
|
* Types for Laravel migration generation.
|
|
9
9
|
*/
|
|
10
|
+
|
|
10
11
|
/**
|
|
11
12
|
* Laravel migration file structure.
|
|
12
13
|
*/
|
|
@@ -34,6 +35,8 @@ interface MigrationOptions {
|
|
|
34
35
|
readonly generateDown?: boolean | undefined;
|
|
35
36
|
/** Database connection name */
|
|
36
37
|
readonly connection?: string | undefined;
|
|
38
|
+
/** Custom types from plugins (for compound type expansion) */
|
|
39
|
+
readonly customTypes?: ReadonlyMap<string, CustomTypeDefinition> | undefined;
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* Schema Builder column method.
|
|
@@ -161,10 +164,17 @@ declare function generateForeignKey(propertyName: string, property: PropertyDefi
|
|
|
161
164
|
foreignKey: ForeignKeyDefinition;
|
|
162
165
|
index: IndexDefinition;
|
|
163
166
|
} | null;
|
|
167
|
+
/**
|
|
168
|
+
* Options for schema to blueprint conversion.
|
|
169
|
+
*/
|
|
170
|
+
interface SchemaToBlueprintOptions {
|
|
171
|
+
/** Custom types from plugins (for compound type expansion) */
|
|
172
|
+
customTypes?: ReadonlyMap<string, CustomTypeDefinition>;
|
|
173
|
+
}
|
|
164
174
|
/**
|
|
165
175
|
* Generates table blueprint from schema.
|
|
166
176
|
*/
|
|
167
|
-
declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection): TableBlueprint;
|
|
177
|
+
declare function schemaToBlueprint(schema: LoadedSchema, allSchemas: SchemaCollection, options?: SchemaToBlueprintOptions): TableBlueprint;
|
|
168
178
|
/**
|
|
169
179
|
* Formats a column method to PHP code.
|
|
170
180
|
*/
|
package/dist/index.js
CHANGED
package/dist/plugin.cjs
CHANGED
|
@@ -39,6 +39,7 @@ var TYPE_METHOD_MAP = {
|
|
|
39
39
|
LongText: "longText",
|
|
40
40
|
Date: "date",
|
|
41
41
|
Time: "time",
|
|
42
|
+
DateTime: "dateTime",
|
|
42
43
|
Timestamp: "timestamp",
|
|
43
44
|
Json: "json",
|
|
44
45
|
Email: "string",
|
|
@@ -111,6 +112,10 @@ function propertyToColumnMethod(propertyName, property) {
|
|
|
111
112
|
if (baseProp.unsigned && (method === "integer" || method === "bigInteger")) {
|
|
112
113
|
modifiers.push({ method: "unsigned" });
|
|
113
114
|
}
|
|
115
|
+
const displayName = property.displayName;
|
|
116
|
+
if (displayName) {
|
|
117
|
+
modifiers.push({ method: "comment", args: [displayName] });
|
|
118
|
+
}
|
|
114
119
|
return {
|
|
115
120
|
name: columnName,
|
|
116
121
|
method,
|
|
@@ -239,12 +244,15 @@ function generateForeignKey(propertyName, property, allSchemas) {
|
|
|
239
244
|
method = "string";
|
|
240
245
|
}
|
|
241
246
|
const modifiers = [];
|
|
242
|
-
if (assocProp.nullable
|
|
247
|
+
if (assocProp.nullable === true) {
|
|
243
248
|
modifiers.push({ method: "nullable" });
|
|
244
249
|
}
|
|
245
250
|
if (assocProp.default !== void 0 && assocProp.default !== null) {
|
|
246
251
|
modifiers.push({ method: "default", args: [assocProp.default] });
|
|
247
252
|
}
|
|
253
|
+
if (assocProp.displayName) {
|
|
254
|
+
modifiers.push({ method: "comment", args: [assocProp.displayName] });
|
|
255
|
+
}
|
|
248
256
|
const column = {
|
|
249
257
|
name: columnName,
|
|
250
258
|
method,
|
|
@@ -264,7 +272,65 @@ function generateForeignKey(propertyName, property, allSchemas) {
|
|
|
264
272
|
};
|
|
265
273
|
return { column, foreignKey, index };
|
|
266
274
|
}
|
|
267
|
-
function
|
|
275
|
+
function expandCompoundType(propName, property, customTypes) {
|
|
276
|
+
const typeDef = customTypes.get(property.type);
|
|
277
|
+
if (!typeDef || !typeDef.compound || !typeDef.expand) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const expanded = [];
|
|
281
|
+
const baseProp = property;
|
|
282
|
+
for (const field of typeDef.expand) {
|
|
283
|
+
const suffixSnake = toColumnName(field.suffix);
|
|
284
|
+
const columnName = `${propName}_${suffixSnake}`;
|
|
285
|
+
const expandedProp = {
|
|
286
|
+
type: "String"
|
|
287
|
+
// Default type, will be overridden by sql definition
|
|
288
|
+
};
|
|
289
|
+
if (field.sql) {
|
|
290
|
+
const sqlType = field.sql.sqlType.toUpperCase();
|
|
291
|
+
if (sqlType === "VARCHAR" || sqlType === "CHAR" || sqlType === "STRING") {
|
|
292
|
+
expandedProp.type = "String";
|
|
293
|
+
if (field.sql.length) {
|
|
294
|
+
expandedProp.length = field.sql.length;
|
|
295
|
+
}
|
|
296
|
+
} else if (sqlType === "INT" || sqlType === "INTEGER") {
|
|
297
|
+
expandedProp.type = "Int";
|
|
298
|
+
} else if (sqlType === "BIGINT") {
|
|
299
|
+
expandedProp.type = "BigInt";
|
|
300
|
+
} else if (sqlType === "TEXT") {
|
|
301
|
+
expandedProp.type = "Text";
|
|
302
|
+
} else if (sqlType === "BOOLEAN" || sqlType === "BOOL") {
|
|
303
|
+
expandedProp.type = "Boolean";
|
|
304
|
+
} else if (sqlType === "DECIMAL") {
|
|
305
|
+
expandedProp.type = "Decimal";
|
|
306
|
+
if (field.sql.precision) expandedProp.precision = field.sql.precision;
|
|
307
|
+
if (field.sql.scale) expandedProp.scale = field.sql.scale;
|
|
308
|
+
} else if (sqlType === "DATE") {
|
|
309
|
+
expandedProp.type = "Date";
|
|
310
|
+
} else if (sqlType === "TIMESTAMP" || sqlType === "DATETIME") {
|
|
311
|
+
expandedProp.type = "Timestamp";
|
|
312
|
+
}
|
|
313
|
+
if (field.sql.nullable !== void 0) {
|
|
314
|
+
expandedProp.nullable = field.sql.nullable;
|
|
315
|
+
} else if (baseProp.nullable !== void 0) {
|
|
316
|
+
expandedProp.nullable = baseProp.nullable;
|
|
317
|
+
}
|
|
318
|
+
if (field.sql.default !== void 0) {
|
|
319
|
+
expandedProp.default = field.sql.default;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (baseProp.displayName) {
|
|
323
|
+
expandedProp.displayName = `${baseProp.displayName} (${field.suffix})`;
|
|
324
|
+
}
|
|
325
|
+
expanded.push({
|
|
326
|
+
name: columnName,
|
|
327
|
+
property: expandedProp
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return expanded;
|
|
331
|
+
}
|
|
332
|
+
function schemaToBlueprint(schema, allSchemas, options = {}) {
|
|
333
|
+
const { customTypes = /* @__PURE__ */ new Map() } = options;
|
|
268
334
|
const tableName = toTableName(schema.name);
|
|
269
335
|
const columns = [];
|
|
270
336
|
const foreignKeys = [];
|
|
@@ -275,6 +341,16 @@ function schemaToBlueprint(schema, allSchemas) {
|
|
|
275
341
|
}
|
|
276
342
|
if (schema.properties) {
|
|
277
343
|
for (const [propName, property] of Object.entries(schema.properties)) {
|
|
344
|
+
const expandedProps = expandCompoundType(propName, property, customTypes);
|
|
345
|
+
if (expandedProps) {
|
|
346
|
+
for (const { name: expandedName, property: expandedProp } of expandedProps) {
|
|
347
|
+
const columnMethod2 = propertyToColumnMethod(expandedName, expandedProp);
|
|
348
|
+
if (columnMethod2) {
|
|
349
|
+
columns.push(columnMethod2);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
278
354
|
const columnMethod = propertyToColumnMethod(propName, property);
|
|
279
355
|
if (columnMethod) {
|
|
280
356
|
columns.push(columnMethod);
|
|
@@ -300,36 +376,67 @@ function schemaToBlueprint(schema, allSchemas) {
|
|
|
300
376
|
columns.push(generateSoftDeleteColumn());
|
|
301
377
|
}
|
|
302
378
|
if (schema.options?.indexes) {
|
|
379
|
+
const propToColName = (propName) => {
|
|
380
|
+
const colName = toColumnName(propName);
|
|
381
|
+
const prop = schema.properties?.[propName];
|
|
382
|
+
if (prop?.type === "Association") {
|
|
383
|
+
const assoc = prop;
|
|
384
|
+
if ((assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") && !assoc.mappedBy) {
|
|
385
|
+
return colName + "_id";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return colName;
|
|
389
|
+
};
|
|
303
390
|
for (const index of schema.options.indexes) {
|
|
304
391
|
if (typeof index === "string") {
|
|
305
392
|
indexes.push({
|
|
306
|
-
columns: [
|
|
393
|
+
columns: [propToColName(index)],
|
|
307
394
|
unique: false
|
|
308
395
|
});
|
|
309
396
|
} else {
|
|
310
397
|
indexes.push({
|
|
311
398
|
name: index.name,
|
|
312
|
-
columns: index.columns.map(
|
|
399
|
+
columns: index.columns.map(propToColName),
|
|
313
400
|
unique: index.unique ?? false
|
|
314
401
|
});
|
|
315
402
|
}
|
|
316
403
|
}
|
|
317
404
|
}
|
|
318
405
|
if (schema.options?.unique) {
|
|
406
|
+
const propToColName = (propName) => {
|
|
407
|
+
const colName = toColumnName(propName);
|
|
408
|
+
const prop = schema.properties?.[propName];
|
|
409
|
+
if (prop?.type === "Association") {
|
|
410
|
+
const assoc = prop;
|
|
411
|
+
if ((assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") && !assoc.mappedBy) {
|
|
412
|
+
return colName + "_id";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return colName;
|
|
416
|
+
};
|
|
319
417
|
const uniqueConstraints = Array.isArray(schema.options.unique[0]) ? schema.options.unique : [schema.options.unique];
|
|
320
418
|
for (const constraint of uniqueConstraints) {
|
|
321
419
|
indexes.push({
|
|
322
|
-
columns: constraint.map(
|
|
420
|
+
columns: constraint.map(propToColName),
|
|
323
421
|
unique: true
|
|
324
422
|
});
|
|
325
423
|
}
|
|
326
424
|
}
|
|
425
|
+
const seenIndexes = /* @__PURE__ */ new Set();
|
|
426
|
+
const uniqueIndexes = indexes.filter((idx) => {
|
|
427
|
+
const key = idx.columns.join(",") + (idx.unique ? ":unique" : "");
|
|
428
|
+
if (seenIndexes.has(key)) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
seenIndexes.add(key);
|
|
432
|
+
return true;
|
|
433
|
+
});
|
|
327
434
|
return {
|
|
328
435
|
tableName,
|
|
329
436
|
columns,
|
|
330
437
|
primaryKey: ["id"],
|
|
331
438
|
foreignKeys,
|
|
332
|
-
indexes
|
|
439
|
+
indexes: uniqueIndexes
|
|
333
440
|
};
|
|
334
441
|
}
|
|
335
442
|
function formatColumnMethod(column) {
|
|
@@ -650,7 +757,9 @@ function generateMigrations(schemas, options = {}) {
|
|
|
650
757
|
const timestamp = options.timestamp ?? generateTimestamp();
|
|
651
758
|
const offsetTimestamp = incrementTimestamp(timestamp, timestampOffset);
|
|
652
759
|
timestampOffset++;
|
|
653
|
-
const blueprint = schemaToBlueprint(schema, schemas
|
|
760
|
+
const blueprint = schemaToBlueprint(schema, schemas, {
|
|
761
|
+
customTypes: options.customTypes
|
|
762
|
+
});
|
|
654
763
|
const migration = generateCreateMigration(blueprint, {
|
|
655
764
|
...options,
|
|
656
765
|
timestamp: offsetTimestamp
|
|
@@ -706,6 +815,288 @@ function getMigrationPath(migration, outputDir = "database/migrations") {
|
|
|
706
815
|
return `${outputDir}/${migration.fileName}`;
|
|
707
816
|
}
|
|
708
817
|
|
|
818
|
+
// src/migration/alter-generator.ts
|
|
819
|
+
var TYPE_METHOD_MAP2 = {
|
|
820
|
+
String: "string",
|
|
821
|
+
Int: "integer",
|
|
822
|
+
BigInt: "bigInteger",
|
|
823
|
+
Float: "double",
|
|
824
|
+
Decimal: "decimal",
|
|
825
|
+
Boolean: "boolean",
|
|
826
|
+
Text: "text",
|
|
827
|
+
LongText: "longText",
|
|
828
|
+
Date: "date",
|
|
829
|
+
Time: "time",
|
|
830
|
+
DateTime: "dateTime",
|
|
831
|
+
Timestamp: "timestamp",
|
|
832
|
+
Json: "json",
|
|
833
|
+
Email: "string",
|
|
834
|
+
Password: "string",
|
|
835
|
+
File: "string",
|
|
836
|
+
MultiFile: "json",
|
|
837
|
+
Enum: "enum",
|
|
838
|
+
Select: "string",
|
|
839
|
+
Lookup: "unsignedBigInteger"
|
|
840
|
+
};
|
|
841
|
+
function generateTimestamp2() {
|
|
842
|
+
const now = /* @__PURE__ */ new Date();
|
|
843
|
+
const year = now.getFullYear();
|
|
844
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
845
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
846
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
847
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
848
|
+
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
849
|
+
return `${year}_${month}_${day}_${hours}${minutes}${seconds}`;
|
|
850
|
+
}
|
|
851
|
+
function formatAddColumn(columnName, prop) {
|
|
852
|
+
const snakeColumn = toColumnName(columnName);
|
|
853
|
+
const method = TYPE_METHOD_MAP2[prop.type] ?? "string";
|
|
854
|
+
let code;
|
|
855
|
+
if (prop.type === "Decimal") {
|
|
856
|
+
const precision = prop.precision ?? 8;
|
|
857
|
+
const scale = prop.scale ?? 2;
|
|
858
|
+
code = `$table->${method}('${snakeColumn}', ${precision}, ${scale})`;
|
|
859
|
+
} else {
|
|
860
|
+
code = `$table->${method}('${snakeColumn}')`;
|
|
861
|
+
}
|
|
862
|
+
if (prop.nullable) code += "->nullable()";
|
|
863
|
+
if (prop.unique) code += "->unique()";
|
|
864
|
+
if (prop.default !== void 0) {
|
|
865
|
+
const defaultValue = typeof prop.default === "string" ? `'${prop.default}'` : JSON.stringify(prop.default);
|
|
866
|
+
code += `->default(${defaultValue})`;
|
|
867
|
+
}
|
|
868
|
+
return code + ";";
|
|
869
|
+
}
|
|
870
|
+
function formatDropColumn(columnName) {
|
|
871
|
+
const snakeColumn = toColumnName(columnName);
|
|
872
|
+
return `$table->dropColumn('${snakeColumn}');`;
|
|
873
|
+
}
|
|
874
|
+
function formatRenameColumn(oldName, newName) {
|
|
875
|
+
const oldSnake = toColumnName(oldName);
|
|
876
|
+
const newSnake = toColumnName(newName);
|
|
877
|
+
return `$table->renameColumn('${oldSnake}', '${newSnake}');`;
|
|
878
|
+
}
|
|
879
|
+
function formatModifyColumn(columnName, _prevProp, currProp) {
|
|
880
|
+
const snakeColumn = toColumnName(columnName);
|
|
881
|
+
const method = TYPE_METHOD_MAP2[currProp.type] ?? "string";
|
|
882
|
+
let code;
|
|
883
|
+
if (currProp.type === "Decimal") {
|
|
884
|
+
const precision = currProp.precision ?? 8;
|
|
885
|
+
const scale = currProp.scale ?? 2;
|
|
886
|
+
code = `$table->${method}('${snakeColumn}', ${precision}, ${scale})`;
|
|
887
|
+
} else {
|
|
888
|
+
code = `$table->${method}('${snakeColumn}')`;
|
|
889
|
+
}
|
|
890
|
+
if (currProp.nullable) code += "->nullable()";
|
|
891
|
+
if (currProp.unique) code += "->unique()";
|
|
892
|
+
if (currProp.default !== void 0) {
|
|
893
|
+
const defaultValue = typeof currProp.default === "string" ? `'${currProp.default}'` : JSON.stringify(currProp.default);
|
|
894
|
+
code += `->default(${defaultValue})`;
|
|
895
|
+
}
|
|
896
|
+
return code + "->change();";
|
|
897
|
+
}
|
|
898
|
+
function formatAddIndex(columns, unique) {
|
|
899
|
+
const snakeColumns = columns.map(toColumnName);
|
|
900
|
+
const method = unique ? "unique" : "index";
|
|
901
|
+
const colsArg = snakeColumns.length === 1 ? `'${snakeColumns[0]}'` : `[${snakeColumns.map((c) => `'${c}'`).join(", ")}]`;
|
|
902
|
+
return `$table->${method}(${colsArg});`;
|
|
903
|
+
}
|
|
904
|
+
function formatDropIndex(tableName, columns, unique) {
|
|
905
|
+
const snakeColumns = columns.map(toColumnName);
|
|
906
|
+
const method = unique ? "dropUnique" : "dropIndex";
|
|
907
|
+
const suffix = unique ? "unique" : "index";
|
|
908
|
+
const indexName = `${tableName}_${snakeColumns.join("_")}_${suffix}`;
|
|
909
|
+
return `$table->${method}('${indexName}');`;
|
|
910
|
+
}
|
|
911
|
+
function generateAlterMigrationContent(tableName, change, options = {}) {
|
|
912
|
+
const upLines = [];
|
|
913
|
+
const downLines = [];
|
|
914
|
+
if (change.columnChanges) {
|
|
915
|
+
for (const col of change.columnChanges) {
|
|
916
|
+
if (col.changeType === "added" && col.currentDef) {
|
|
917
|
+
upLines.push(` ${formatAddColumn(col.column, col.currentDef)}`);
|
|
918
|
+
downLines.push(` ${formatDropColumn(col.column)}`);
|
|
919
|
+
} else if (col.changeType === "removed" && col.previousDef) {
|
|
920
|
+
upLines.push(` ${formatDropColumn(col.column)}`);
|
|
921
|
+
downLines.push(` ${formatAddColumn(col.column, col.previousDef)}`);
|
|
922
|
+
} else if (col.changeType === "modified" && col.previousDef && col.currentDef) {
|
|
923
|
+
upLines.push(` ${formatModifyColumn(col.column, col.previousDef, col.currentDef)}`);
|
|
924
|
+
downLines.push(` ${formatModifyColumn(col.column, col.currentDef, col.previousDef)}`);
|
|
925
|
+
} else if (col.changeType === "renamed" && col.previousColumn) {
|
|
926
|
+
upLines.push(` ${formatRenameColumn(col.previousColumn, col.column)}`);
|
|
927
|
+
downLines.push(` ${formatRenameColumn(col.column, col.previousColumn)}`);
|
|
928
|
+
if (col.modifications && col.modifications.length > 0 && col.previousDef && col.currentDef) {
|
|
929
|
+
upLines.push(` ${formatModifyColumn(col.column, col.previousDef, col.currentDef)}`);
|
|
930
|
+
downLines.push(` ${formatModifyColumn(col.column, col.currentDef, col.previousDef)}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (change.indexChanges) {
|
|
936
|
+
for (const idx of change.indexChanges) {
|
|
937
|
+
if (idx.changeType === "added") {
|
|
938
|
+
upLines.push(` ${formatAddIndex(idx.index.columns, idx.index.unique)}`);
|
|
939
|
+
downLines.push(` ${formatDropIndex(tableName, idx.index.columns, idx.index.unique)}`);
|
|
940
|
+
} else {
|
|
941
|
+
upLines.push(` ${formatDropIndex(tableName, idx.index.columns, idx.index.unique)}`);
|
|
942
|
+
downLines.push(` ${formatAddIndex(idx.index.columns, idx.index.unique)}`);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (change.optionChanges) {
|
|
947
|
+
if (change.optionChanges.timestamps) {
|
|
948
|
+
const { from, to } = change.optionChanges.timestamps;
|
|
949
|
+
if (to && !from) {
|
|
950
|
+
upLines.push(` $table->timestamps();`);
|
|
951
|
+
downLines.push(` $table->dropTimestamps();`);
|
|
952
|
+
} else if (from && !to) {
|
|
953
|
+
upLines.push(` $table->dropTimestamps();`);
|
|
954
|
+
downLines.push(` $table->timestamps();`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (change.optionChanges.softDelete) {
|
|
958
|
+
const { from, to } = change.optionChanges.softDelete;
|
|
959
|
+
if (to && !from) {
|
|
960
|
+
upLines.push(` $table->softDeletes();`);
|
|
961
|
+
downLines.push(` $table->dropSoftDeletes();`);
|
|
962
|
+
} else if (from && !to) {
|
|
963
|
+
upLines.push(` $table->dropSoftDeletes();`);
|
|
964
|
+
downLines.push(` $table->softDeletes();`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const connection = options.connection ? `
|
|
969
|
+
protected $connection = '${options.connection}';
|
|
970
|
+
` : "";
|
|
971
|
+
return `<?php
|
|
972
|
+
|
|
973
|
+
use Illuminate\\Database\\Migrations\\Migration;
|
|
974
|
+
use Illuminate\\Database\\Schema\\Blueprint;
|
|
975
|
+
use Illuminate\\Support\\Facades\\Schema;
|
|
976
|
+
|
|
977
|
+
return new class extends Migration
|
|
978
|
+
{${connection}
|
|
979
|
+
/**
|
|
980
|
+
* Run the migrations.
|
|
981
|
+
*/
|
|
982
|
+
public function up(): void
|
|
983
|
+
{
|
|
984
|
+
Schema::table('${tableName}', function (Blueprint $table) {
|
|
985
|
+
${upLines.join("\n")}
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Reverse the migrations.
|
|
991
|
+
*/
|
|
992
|
+
public function down(): void
|
|
993
|
+
{
|
|
994
|
+
Schema::table('${tableName}', function (Blueprint $table) {
|
|
995
|
+
${downLines.join("\n")}
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
`;
|
|
1000
|
+
}
|
|
1001
|
+
function generateAlterMigration(change, options = {}) {
|
|
1002
|
+
if (change.changeType !== "modified") {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
const hasChanges = change.columnChanges && change.columnChanges.length > 0 || change.indexChanges && change.indexChanges.length > 0 || change.optionChanges && (change.optionChanges.timestamps || change.optionChanges.softDelete);
|
|
1006
|
+
if (!hasChanges) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
const tableName = toTableName(change.schemaName);
|
|
1010
|
+
const timestamp = options.timestamp ?? generateTimestamp2();
|
|
1011
|
+
const fileName = `${timestamp}_update_${tableName}_table.php`;
|
|
1012
|
+
const content = generateAlterMigrationContent(tableName, change, options);
|
|
1013
|
+
return {
|
|
1014
|
+
fileName,
|
|
1015
|
+
className: `Update${change.schemaName}Table`,
|
|
1016
|
+
content,
|
|
1017
|
+
tables: [tableName],
|
|
1018
|
+
type: "alter"
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function generateDropTableMigration(schemaName, options = {}) {
|
|
1022
|
+
const tableName = toTableName(schemaName);
|
|
1023
|
+
const timestamp = options.timestamp ?? generateTimestamp2();
|
|
1024
|
+
const fileName = `${timestamp}_drop_${tableName}_table.php`;
|
|
1025
|
+
const connection = options.connection ? `
|
|
1026
|
+
protected $connection = '${options.connection}';
|
|
1027
|
+
` : "";
|
|
1028
|
+
const content = `<?php
|
|
1029
|
+
|
|
1030
|
+
use Illuminate\\Database\\Migrations\\Migration;
|
|
1031
|
+
use Illuminate\\Database\\Schema\\Blueprint;
|
|
1032
|
+
use Illuminate\\Support\\Facades\\Schema;
|
|
1033
|
+
|
|
1034
|
+
return new class extends Migration
|
|
1035
|
+
{${connection}
|
|
1036
|
+
/**
|
|
1037
|
+
* Run the migrations.
|
|
1038
|
+
*/
|
|
1039
|
+
public function up(): void
|
|
1040
|
+
{
|
|
1041
|
+
Schema::dropIfExists('${tableName}');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Reverse the migrations.
|
|
1046
|
+
*/
|
|
1047
|
+
public function down(): void
|
|
1048
|
+
{
|
|
1049
|
+
// Cannot recreate table without full schema
|
|
1050
|
+
// Consider restoring from backup if needed
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
`;
|
|
1054
|
+
return {
|
|
1055
|
+
fileName,
|
|
1056
|
+
className: `Drop${schemaName}Table`,
|
|
1057
|
+
content,
|
|
1058
|
+
tables: [tableName],
|
|
1059
|
+
type: "drop"
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function generateMigrationsFromChanges(changes, options = {}) {
|
|
1063
|
+
const migrations = [];
|
|
1064
|
+
let timestampOffset = 0;
|
|
1065
|
+
const getNextTimestamp = () => {
|
|
1066
|
+
const ts = options.timestamp ?? generateTimestamp2();
|
|
1067
|
+
const offset = timestampOffset++;
|
|
1068
|
+
if (offset === 0) return ts;
|
|
1069
|
+
const parts = ts.split("_");
|
|
1070
|
+
if (parts.length >= 4) {
|
|
1071
|
+
const timePart = parts[3] ?? "000000";
|
|
1072
|
+
const secs = parseInt(timePart.substring(4, 6), 10) + offset;
|
|
1073
|
+
const newSecs = String(secs % 60).padStart(2, "0");
|
|
1074
|
+
parts[3] = timePart.substring(0, 4) + newSecs;
|
|
1075
|
+
return parts.join("_");
|
|
1076
|
+
}
|
|
1077
|
+
return ts;
|
|
1078
|
+
};
|
|
1079
|
+
for (const change of changes) {
|
|
1080
|
+
if (change.changeType === "modified") {
|
|
1081
|
+
const migration = generateAlterMigration(change, {
|
|
1082
|
+
...options,
|
|
1083
|
+
timestamp: getNextTimestamp()
|
|
1084
|
+
});
|
|
1085
|
+
if (migration) {
|
|
1086
|
+
migrations.push(migration);
|
|
1087
|
+
}
|
|
1088
|
+
} else if (change.changeType === "removed") {
|
|
1089
|
+
migrations.push(
|
|
1090
|
+
generateDropTableMigration(change.schemaName, {
|
|
1091
|
+
...options,
|
|
1092
|
+
timestamp: getNextTimestamp()
|
|
1093
|
+
})
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return migrations;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
709
1100
|
// src/utils.ts
|
|
710
1101
|
function toSnakeCase(str) {
|
|
711
1102
|
return str.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
|
|
@@ -1717,6 +2108,24 @@ function getFactoryPath(factory) {
|
|
|
1717
2108
|
}
|
|
1718
2109
|
|
|
1719
2110
|
// src/plugin.ts
|
|
2111
|
+
function getExistingMigrationTables(migrationsDir) {
|
|
2112
|
+
const existingTables = /* @__PURE__ */ new Set();
|
|
2113
|
+
if (!(0, import_node_fs.existsSync)(migrationsDir)) {
|
|
2114
|
+
return existingTables;
|
|
2115
|
+
}
|
|
2116
|
+
try {
|
|
2117
|
+
const files = (0, import_node_fs.readdirSync)(migrationsDir);
|
|
2118
|
+
const createMigrationPattern = /^\d{4}_\d{2}_\d{2}_\d{6}_create_(.+)_table\.php$/;
|
|
2119
|
+
for (const file of files) {
|
|
2120
|
+
const match = file.match(createMigrationPattern);
|
|
2121
|
+
if (match) {
|
|
2122
|
+
existingTables.add(match[1]);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
} catch {
|
|
2126
|
+
}
|
|
2127
|
+
return existingTables;
|
|
2128
|
+
}
|
|
1720
2129
|
var LARAVEL_CONFIG_SCHEMA = {
|
|
1721
2130
|
fields: [
|
|
1722
2131
|
{
|
|
@@ -1800,18 +2209,81 @@ function laravelPlugin(options) {
|
|
|
1800
2209
|
generate: async (ctx) => {
|
|
1801
2210
|
const migrationOptions = {
|
|
1802
2211
|
connection: resolved.connection,
|
|
1803
|
-
timestamp: resolved.timestamp
|
|
2212
|
+
timestamp: resolved.timestamp,
|
|
2213
|
+
customTypes: ctx.customTypes
|
|
1804
2214
|
};
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
tableName: migration.tables[0],
|
|
1812
|
-
migrationType: migration.type
|
|
2215
|
+
const outputs = [];
|
|
2216
|
+
const migrationsDir = (0, import_node_path.join)(ctx.cwd, resolved.migrationsPath);
|
|
2217
|
+
const existingTables = getExistingMigrationTables(migrationsDir);
|
|
2218
|
+
if (ctx.changes !== void 0) {
|
|
2219
|
+
if (ctx.changes.length === 0) {
|
|
2220
|
+
return outputs;
|
|
1813
2221
|
}
|
|
1814
|
-
|
|
2222
|
+
const addedSchemaNames = new Set(
|
|
2223
|
+
ctx.changes.filter((c) => c.changeType === "added").map((c) => c.schemaName)
|
|
2224
|
+
);
|
|
2225
|
+
if (addedSchemaNames.size > 0) {
|
|
2226
|
+
const addedSchemas = Object.fromEntries(
|
|
2227
|
+
Object.entries(ctx.schemas).filter(([name]) => addedSchemaNames.has(name))
|
|
2228
|
+
);
|
|
2229
|
+
const createMigrations = generateMigrations(addedSchemas, migrationOptions);
|
|
2230
|
+
for (const migration of createMigrations) {
|
|
2231
|
+
const tableName = migration.tables[0];
|
|
2232
|
+
if (existingTables.has(tableName)) {
|
|
2233
|
+
ctx.logger.debug(`Skipping CREATE for ${tableName} (already exists)`);
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
outputs.push({
|
|
2237
|
+
path: getMigrationPath(migration, resolved.migrationsPath),
|
|
2238
|
+
content: migration.content,
|
|
2239
|
+
type: "migration",
|
|
2240
|
+
metadata: {
|
|
2241
|
+
tableName,
|
|
2242
|
+
migrationType: "create"
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const alterChanges = ctx.changes.filter(
|
|
2248
|
+
(c) => c.changeType === "modified" || c.changeType === "removed"
|
|
2249
|
+
);
|
|
2250
|
+
if (alterChanges.length > 0) {
|
|
2251
|
+
const alterMigrations = generateMigrationsFromChanges(
|
|
2252
|
+
alterChanges,
|
|
2253
|
+
migrationOptions
|
|
2254
|
+
);
|
|
2255
|
+
for (const migration of alterMigrations) {
|
|
2256
|
+
outputs.push({
|
|
2257
|
+
path: getMigrationPath(migration, resolved.migrationsPath),
|
|
2258
|
+
content: migration.content,
|
|
2259
|
+
type: "migration",
|
|
2260
|
+
metadata: {
|
|
2261
|
+
tableName: migration.tables[0],
|
|
2262
|
+
migrationType: migration.type
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
} else {
|
|
2268
|
+
const migrations = generateMigrations(ctx.schemas, migrationOptions);
|
|
2269
|
+
for (const migration of migrations) {
|
|
2270
|
+
const tableName = migration.tables[0];
|
|
2271
|
+
if (migration.type === "create" && existingTables.has(tableName)) {
|
|
2272
|
+
ctx.logger.debug(`Skipping migration for ${tableName} (already exists)`);
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
outputs.push({
|
|
2276
|
+
path: getMigrationPath(migration, resolved.migrationsPath),
|
|
2277
|
+
content: migration.content,
|
|
2278
|
+
type: "migration",
|
|
2279
|
+
metadata: {
|
|
2280
|
+
tableName,
|
|
2281
|
+
migrationType: migration.type
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
return outputs;
|
|
1815
2287
|
}
|
|
1816
2288
|
};
|
|
1817
2289
|
const modelGenerator = {
|