pocketbase-zod-schema 0.1.2 → 0.1.4
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/CHANGELOG.md +15 -0
- package/README.md +329 -99
- package/dist/cli/index.cjs +577 -152
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +575 -150
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrate.cjs +595 -153
- package/dist/cli/migrate.cjs.map +1 -1
- package/dist/cli/migrate.js +592 -151
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/utils/index.cjs +1 -1
- package/dist/cli/utils/index.cjs.map +1 -1
- package/dist/cli/utils/index.js +1 -1
- package/dist/cli/utils/index.js.map +1 -1
- package/dist/index.cjs +688 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +685 -230
- package/dist/index.js.map +1 -1
- package/dist/migration/analyzer.cjs +101 -28
- package/dist/migration/analyzer.cjs.map +1 -1
- package/dist/migration/analyzer.js +101 -28
- package/dist/migration/analyzer.js.map +1 -1
- package/dist/migration/diff.cjs +21 -3
- package/dist/migration/diff.cjs.map +1 -1
- package/dist/migration/diff.js +21 -3
- package/dist/migration/diff.js.map +1 -1
- package/dist/migration/generator.cjs +60 -25
- package/dist/migration/generator.cjs.map +1 -1
- package/dist/migration/generator.d.cts +9 -5
- package/dist/migration/generator.d.ts +9 -5
- package/dist/migration/generator.js +60 -25
- package/dist/migration/generator.js.map +1 -1
- package/dist/migration/index.cjs +614 -171
- package/dist/migration/index.cjs.map +1 -1
- package/dist/migration/index.d.cts +1 -1
- package/dist/migration/index.d.ts +1 -1
- package/dist/migration/index.js +613 -171
- package/dist/migration/index.js.map +1 -1
- package/dist/migration/snapshot.cjs +432 -117
- package/dist/migration/snapshot.cjs.map +1 -1
- package/dist/migration/snapshot.d.cts +34 -12
- package/dist/migration/snapshot.d.ts +34 -12
- package/dist/migration/snapshot.js +430 -116
- package/dist/migration/snapshot.js.map +1 -1
- package/dist/migration/utils/index.cjs +19 -17
- package/dist/migration/utils/index.cjs.map +1 -1
- package/dist/migration/utils/index.d.cts +3 -1
- package/dist/migration/utils/index.d.ts +3 -1
- package/dist/migration/utils/index.js +19 -17
- package/dist/migration/utils/index.js.map +1 -1
- package/dist/mutator.cjs +9 -11
- package/dist/mutator.cjs.map +1 -1
- package/dist/mutator.d.cts +5 -9
- package/dist/mutator.d.ts +5 -9
- package/dist/mutator.js +9 -11
- package/dist/mutator.js.map +1 -1
- package/dist/schema.cjs +54 -23
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +94 -12
- package/dist/schema.d.ts +94 -12
- package/dist/schema.js +54 -24
- package/dist/schema.js.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{user-jS1aYoeD.d.cts → user-_AM523hb.d.cts} +6 -6
- package/dist/{user-jS1aYoeD.d.ts → user-_AM523hb.d.ts} +6 -6
- package/package.json +2 -4
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
1
|
+
import * as path5 from 'path';
|
|
2
|
+
import * as fs5 from 'fs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/generate.ts
|
|
8
|
+
({
|
|
9
|
+
id: z.string().describe("unique id"),
|
|
10
|
+
collectionId: z.string().describe("collection id"),
|
|
11
|
+
collectionName: z.string().describe("collection name"),
|
|
12
|
+
expand: z.record(z.any()).describe("expandable fields")
|
|
13
|
+
});
|
|
14
|
+
({
|
|
15
|
+
created: z.string().describe("creation timestamp"),
|
|
16
|
+
updated: z.string().describe("last update timestamp")
|
|
17
|
+
});
|
|
18
|
+
({
|
|
19
|
+
thumbnailURL: z.string().optional(),
|
|
20
|
+
imageFiles: z.array(z.string())
|
|
21
|
+
});
|
|
22
|
+
({
|
|
23
|
+
imageFiles: z.array(z.instanceof(File))
|
|
24
|
+
});
|
|
25
|
+
var RELATION_METADATA_KEY = "__pocketbase_relation__";
|
|
26
|
+
function extractRelationMetadata(description) {
|
|
27
|
+
if (!description) return null;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(description);
|
|
30
|
+
if (parsed[RELATION_METADATA_KEY]) {
|
|
31
|
+
return parsed[RELATION_METADATA_KEY];
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
8
37
|
|
|
9
38
|
// src/migration/errors.ts
|
|
10
39
|
var MigrationError = class _MigrationError extends Error {
|
|
@@ -103,10 +132,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
|
|
|
103
132
|
operation;
|
|
104
133
|
code;
|
|
105
134
|
originalError;
|
|
106
|
-
constructor(message,
|
|
135
|
+
constructor(message, path7, operation, code, originalError) {
|
|
107
136
|
super(message);
|
|
108
137
|
this.name = "FileSystemError";
|
|
109
|
-
this.path =
|
|
138
|
+
this.path = path7;
|
|
110
139
|
this.operation = operation;
|
|
111
140
|
this.code = code;
|
|
112
141
|
this.originalError = originalError;
|
|
@@ -169,7 +198,7 @@ Cause: ${this.originalError.message}`);
|
|
|
169
198
|
}
|
|
170
199
|
};
|
|
171
200
|
|
|
172
|
-
// src/
|
|
201
|
+
// src/utils/permission-templates.ts
|
|
173
202
|
var PermissionTemplates = {
|
|
174
203
|
/**
|
|
175
204
|
* Public access - anyone can perform all operations
|
|
@@ -828,26 +857,28 @@ function getMaxSelect(fieldName, zodType) {
|
|
|
828
857
|
return 1;
|
|
829
858
|
}
|
|
830
859
|
function getMinSelect(fieldName, zodType) {
|
|
831
|
-
if (
|
|
832
|
-
return
|
|
833
|
-
}
|
|
834
|
-
let unwrappedType = zodType;
|
|
835
|
-
if (zodType instanceof z.ZodOptional) {
|
|
836
|
-
unwrappedType = zodType._def.innerType;
|
|
837
|
-
}
|
|
838
|
-
if (unwrappedType instanceof z.ZodNullable) {
|
|
839
|
-
unwrappedType = unwrappedType._def.innerType;
|
|
840
|
-
}
|
|
841
|
-
if (unwrappedType instanceof z.ZodDefault) {
|
|
842
|
-
unwrappedType = unwrappedType._def.innerType;
|
|
860
|
+
if (isSingleRelationField(fieldName, zodType)) {
|
|
861
|
+
return 0;
|
|
843
862
|
}
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
if (
|
|
847
|
-
|
|
863
|
+
if (isMultipleRelationField(fieldName, zodType)) {
|
|
864
|
+
let unwrappedType = zodType;
|
|
865
|
+
if (zodType instanceof z.ZodOptional) {
|
|
866
|
+
unwrappedType = zodType._def.innerType;
|
|
867
|
+
}
|
|
868
|
+
if (unwrappedType instanceof z.ZodNullable) {
|
|
869
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
870
|
+
}
|
|
871
|
+
if (unwrappedType instanceof z.ZodDefault) {
|
|
872
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
873
|
+
}
|
|
874
|
+
if (unwrappedType instanceof z.ZodArray) {
|
|
875
|
+
const arrayDef = unwrappedType._def;
|
|
876
|
+
if (arrayDef.minLength) {
|
|
877
|
+
return arrayDef.minLength.value;
|
|
878
|
+
}
|
|
848
879
|
}
|
|
849
880
|
}
|
|
850
|
-
return
|
|
881
|
+
return 0;
|
|
851
882
|
}
|
|
852
883
|
function mapZodStringType(zodType) {
|
|
853
884
|
const checks = zodType._def.checks || [];
|
|
@@ -1045,20 +1076,20 @@ function mergeConfig(config) {
|
|
|
1045
1076
|
}
|
|
1046
1077
|
function resolveSchemaDir(config) {
|
|
1047
1078
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1048
|
-
if (
|
|
1079
|
+
if (path5.isAbsolute(config.schemaDir)) {
|
|
1049
1080
|
return config.schemaDir;
|
|
1050
1081
|
}
|
|
1051
|
-
return
|
|
1082
|
+
return path5.join(workspaceRoot, config.schemaDir);
|
|
1052
1083
|
}
|
|
1053
1084
|
function discoverSchemaFiles(config) {
|
|
1054
1085
|
const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
|
|
1055
1086
|
const mergedConfig = mergeConfig(normalizedConfig);
|
|
1056
1087
|
const schemaDir = resolveSchemaDir(normalizedConfig);
|
|
1057
1088
|
try {
|
|
1058
|
-
if (!
|
|
1089
|
+
if (!fs5.existsSync(schemaDir)) {
|
|
1059
1090
|
throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
|
|
1060
1091
|
}
|
|
1061
|
-
const files =
|
|
1092
|
+
const files = fs5.readdirSync(schemaDir);
|
|
1062
1093
|
const schemaFiles = files.filter((file) => {
|
|
1063
1094
|
const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
|
|
1064
1095
|
if (!hasValidExtension) return false;
|
|
@@ -1074,7 +1105,7 @@ function discoverSchemaFiles(config) {
|
|
|
1074
1105
|
});
|
|
1075
1106
|
return schemaFiles.map((file) => {
|
|
1076
1107
|
const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
|
|
1077
|
-
return
|
|
1108
|
+
return path5.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
|
|
1078
1109
|
});
|
|
1079
1110
|
} catch (error) {
|
|
1080
1111
|
if (error instanceof FileSystemError) {
|
|
@@ -1105,13 +1136,32 @@ async function importSchemaModule(filePath, config) {
|
|
|
1105
1136
|
if (config?.pathTransformer) {
|
|
1106
1137
|
importPath = config.pathTransformer(filePath);
|
|
1107
1138
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1139
|
+
let resolvedPath = null;
|
|
1140
|
+
const jsPath = `${importPath}.js`;
|
|
1141
|
+
const tsPath = `${importPath}.ts`;
|
|
1142
|
+
if (fs5.existsSync(jsPath)) {
|
|
1143
|
+
resolvedPath = jsPath;
|
|
1144
|
+
} else if (fs5.existsSync(tsPath)) {
|
|
1145
|
+
resolvedPath = tsPath;
|
|
1146
|
+
} else {
|
|
1147
|
+
resolvedPath = jsPath;
|
|
1110
1148
|
}
|
|
1111
|
-
const fileUrl = new URL(`file://${
|
|
1149
|
+
const fileUrl = new URL(`file://${path5.resolve(resolvedPath)}`);
|
|
1112
1150
|
const module = await import(fileUrl.href);
|
|
1113
1151
|
return module;
|
|
1114
1152
|
} catch (error) {
|
|
1153
|
+
const tsPath = `${filePath}.ts`;
|
|
1154
|
+
const isTypeScriptFile = fs5.existsSync(tsPath);
|
|
1155
|
+
if (isTypeScriptFile) {
|
|
1156
|
+
throw new SchemaParsingError(
|
|
1157
|
+
`Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
|
|
1158
|
+
Please either:
|
|
1159
|
+
1. Compile your schema files to JavaScript first, or
|
|
1160
|
+
2. Use tsx to run the migration tool (e.g., "npx tsx package/dist/cli/migrate.js status" or "tsx package/dist/cli/migrate.js status")`,
|
|
1161
|
+
filePath,
|
|
1162
|
+
error
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1115
1165
|
throw new SchemaParsingError(
|
|
1116
1166
|
`Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
|
|
1117
1167
|
filePath,
|
|
@@ -1120,7 +1170,7 @@ async function importSchemaModule(filePath, config) {
|
|
|
1120
1170
|
}
|
|
1121
1171
|
}
|
|
1122
1172
|
function getCollectionNameFromFile(filePath) {
|
|
1123
|
-
const filename =
|
|
1173
|
+
const filename = path5.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1124
1174
|
return toCollectionName(filename);
|
|
1125
1175
|
}
|
|
1126
1176
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
|
|
@@ -1174,7 +1224,17 @@ function buildFieldDefinition(fieldName, zodType) {
|
|
|
1174
1224
|
required,
|
|
1175
1225
|
options
|
|
1176
1226
|
};
|
|
1177
|
-
|
|
1227
|
+
const relationMetadata = extractRelationMetadata(zodType.description);
|
|
1228
|
+
if (relationMetadata) {
|
|
1229
|
+
fieldDef.type = "relation";
|
|
1230
|
+
fieldDef.relation = {
|
|
1231
|
+
collection: relationMetadata.collection,
|
|
1232
|
+
maxSelect: relationMetadata.maxSelect,
|
|
1233
|
+
minSelect: relationMetadata.minSelect,
|
|
1234
|
+
cascadeDelete: relationMetadata.cascadeDelete
|
|
1235
|
+
};
|
|
1236
|
+
fieldDef.options = void 0;
|
|
1237
|
+
} else if (isRelationField(fieldName, zodType)) {
|
|
1178
1238
|
fieldDef.type = "relation";
|
|
1179
1239
|
const targetCollection = resolveTargetCollection(fieldName);
|
|
1180
1240
|
const maxSelect = getMaxSelect(fieldName, zodType);
|
|
@@ -1186,6 +1246,13 @@ function buildFieldDefinition(fieldName, zodType) {
|
|
|
1186
1246
|
cascadeDelete: false
|
|
1187
1247
|
// Default to false, can be configured later
|
|
1188
1248
|
};
|
|
1249
|
+
if (fieldDef.options) {
|
|
1250
|
+
const { min, max, pattern, ...relationSafeOptions } = fieldDef.options;
|
|
1251
|
+
console.log("min", min);
|
|
1252
|
+
console.log("max", max);
|
|
1253
|
+
console.log("pattern", pattern);
|
|
1254
|
+
fieldDef.options = Object.keys(relationSafeOptions).length > 0 ? relationSafeOptions : void 0;
|
|
1255
|
+
}
|
|
1189
1256
|
}
|
|
1190
1257
|
return fieldDef;
|
|
1191
1258
|
}
|
|
@@ -1238,11 +1305,12 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1238
1305
|
fields,
|
|
1239
1306
|
indexes,
|
|
1240
1307
|
rules: {
|
|
1241
|
-
listRule: null,
|
|
1242
|
-
viewRule: null,
|
|
1243
|
-
createRule: null,
|
|
1244
|
-
updateRule: null,
|
|
1245
|
-
deleteRule: null
|
|
1308
|
+
listRule: permissions?.listRule ?? null,
|
|
1309
|
+
viewRule: permissions?.viewRule ?? null,
|
|
1310
|
+
createRule: permissions?.createRule ?? null,
|
|
1311
|
+
updateRule: permissions?.updateRule ?? null,
|
|
1312
|
+
deleteRule: permissions?.deleteRule ?? null,
|
|
1313
|
+
manageRule: permissions?.manageRule ?? null
|
|
1246
1314
|
},
|
|
1247
1315
|
permissions
|
|
1248
1316
|
};
|
|
@@ -1266,7 +1334,12 @@ async function buildSchemaDefinition(config) {
|
|
|
1266
1334
|
if (normalizedConfig.pathTransformer) {
|
|
1267
1335
|
importPath = normalizedConfig.pathTransformer(filePath);
|
|
1268
1336
|
} else if (mergedConfig.useCompiledFiles) {
|
|
1269
|
-
|
|
1337
|
+
const distPath = filePath.replace(/\/src\//, "/dist/");
|
|
1338
|
+
if (fs5.existsSync(`${distPath}.js`) || fs5.existsSync(`${distPath}.mjs`)) {
|
|
1339
|
+
importPath = distPath;
|
|
1340
|
+
} else {
|
|
1341
|
+
importPath = filePath;
|
|
1342
|
+
}
|
|
1270
1343
|
}
|
|
1271
1344
|
const module = await importSchemaModule(importPath, normalizedConfig);
|
|
1272
1345
|
const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
|
|
@@ -1439,6 +1512,9 @@ function compareFieldOptions(currentField, previousField) {
|
|
|
1439
1512
|
for (const key of allKeys) {
|
|
1440
1513
|
const currentValue = currentOptions[key];
|
|
1441
1514
|
const previousValue = previousOptions[key];
|
|
1515
|
+
if (currentValue === void 0 && previousValue === void 0) {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1442
1518
|
if (!areValuesEqual(currentValue, previousValue)) {
|
|
1443
1519
|
changes.push({
|
|
1444
1520
|
property: `options.${key}`,
|
|
@@ -1459,11 +1535,26 @@ function compareRelationConfigurations(currentField, previousField) {
|
|
|
1459
1535
|
if (!currentRelation || !previousRelation) {
|
|
1460
1536
|
return changes;
|
|
1461
1537
|
}
|
|
1462
|
-
|
|
1538
|
+
const normalizeCollection = (collection) => {
|
|
1539
|
+
if (!collection) return collection;
|
|
1540
|
+
if (collection === "_pb_users_auth_") {
|
|
1541
|
+
return "Users";
|
|
1542
|
+
}
|
|
1543
|
+
const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
1544
|
+
if (nameMatch) {
|
|
1545
|
+
return nameMatch[1];
|
|
1546
|
+
}
|
|
1547
|
+
return collection;
|
|
1548
|
+
};
|
|
1549
|
+
const normalizedCurrent = normalizeCollection(currentRelation.collection);
|
|
1550
|
+
const normalizedPrevious = normalizeCollection(previousRelation.collection);
|
|
1551
|
+
if (normalizedCurrent !== normalizedPrevious) {
|
|
1463
1552
|
changes.push({
|
|
1464
1553
|
property: "relation.collection",
|
|
1465
|
-
oldValue:
|
|
1466
|
-
|
|
1554
|
+
oldValue: normalizedPrevious,
|
|
1555
|
+
// Use normalized value for clarity
|
|
1556
|
+
newValue: normalizedCurrent
|
|
1557
|
+
// Use normalized value for clarity
|
|
1467
1558
|
});
|
|
1468
1559
|
}
|
|
1469
1560
|
if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
|
|
@@ -1664,10 +1755,8 @@ function compare(currentSchema, previousSnapshot, config) {
|
|
|
1664
1755
|
var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
|
|
1665
1756
|
migrate((app) => {
|
|
1666
1757
|
{{UP_CODE}}
|
|
1667
|
-
return true;
|
|
1668
1758
|
}, (app) => {
|
|
1669
1759
|
{{DOWN_CODE}}
|
|
1670
|
-
return true;
|
|
1671
1760
|
});
|
|
1672
1761
|
`;
|
|
1673
1762
|
var DEFAULT_CONFIG3 = {
|
|
@@ -1685,10 +1774,10 @@ function mergeConfig3(config) {
|
|
|
1685
1774
|
}
|
|
1686
1775
|
function resolveMigrationDir(config) {
|
|
1687
1776
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1688
|
-
if (
|
|
1777
|
+
if (path5.isAbsolute(config.migrationDir)) {
|
|
1689
1778
|
return config.migrationDir;
|
|
1690
1779
|
}
|
|
1691
|
-
return
|
|
1780
|
+
return path5.join(workspaceRoot, config.migrationDir);
|
|
1692
1781
|
}
|
|
1693
1782
|
function generateTimestamp(config) {
|
|
1694
1783
|
if (config?.timestampGenerator) {
|
|
@@ -1746,9 +1835,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
|
|
|
1746
1835
|
}
|
|
1747
1836
|
function writeMigrationFile(migrationDir, filename, content) {
|
|
1748
1837
|
try {
|
|
1749
|
-
if (!
|
|
1838
|
+
if (!fs5.existsSync(migrationDir)) {
|
|
1750
1839
|
try {
|
|
1751
|
-
|
|
1840
|
+
fs5.mkdirSync(migrationDir, { recursive: true });
|
|
1752
1841
|
} catch (error) {
|
|
1753
1842
|
const fsError = error;
|
|
1754
1843
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
@@ -1769,15 +1858,15 @@ function writeMigrationFile(migrationDir, filename, content) {
|
|
|
1769
1858
|
);
|
|
1770
1859
|
}
|
|
1771
1860
|
}
|
|
1772
|
-
const filePath =
|
|
1773
|
-
|
|
1861
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1862
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1774
1863
|
return filePath;
|
|
1775
1864
|
} catch (error) {
|
|
1776
1865
|
if (error instanceof FileSystemError) {
|
|
1777
1866
|
throw error;
|
|
1778
1867
|
}
|
|
1779
1868
|
const fsError = error;
|
|
1780
|
-
const filePath =
|
|
1869
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1781
1870
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
1782
1871
|
throw new FileSystemError(
|
|
1783
1872
|
`Permission denied writing migration file. Check file and directory permissions.`,
|
|
@@ -1832,7 +1921,8 @@ function generateFieldDefinitionObject(field) {
|
|
|
1832
1921
|
}
|
|
1833
1922
|
}
|
|
1834
1923
|
if (field.relation) {
|
|
1835
|
-
const
|
|
1924
|
+
const isUsersCollection = field.relation.collection.toLowerCase() === "users";
|
|
1925
|
+
const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
1836
1926
|
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
1837
1927
|
if (field.relation.maxSelect !== void 0) {
|
|
1838
1928
|
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
@@ -1916,7 +2006,7 @@ function generateIndexesArray(indexes) {
|
|
|
1916
2006
|
${indexStrings.join(",\n ")},
|
|
1917
2007
|
]`;
|
|
1918
2008
|
}
|
|
1919
|
-
function generateCollectionCreation(collection, varName = "collection") {
|
|
2009
|
+
function generateCollectionCreation(collection, varName = "collection", isLast = false) {
|
|
1920
2010
|
const lines = [];
|
|
1921
2011
|
lines.push(` const ${varName} = new Collection({`);
|
|
1922
2012
|
lines.push(` name: "${collection.name}",`);
|
|
@@ -1932,7 +2022,7 @@ function generateCollectionCreation(collection, varName = "collection") {
|
|
|
1932
2022
|
lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
|
|
1933
2023
|
lines.push(` });`);
|
|
1934
2024
|
lines.push(``);
|
|
1935
|
-
lines.push(` app.save(${varName});`);
|
|
2025
|
+
lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
|
|
1936
2026
|
return lines.join("\n");
|
|
1937
2027
|
}
|
|
1938
2028
|
function getFieldConstructorName(fieldType) {
|
|
@@ -1963,7 +2053,8 @@ function generateFieldConstructorOptions(field) {
|
|
|
1963
2053
|
}
|
|
1964
2054
|
}
|
|
1965
2055
|
if (field.relation && field.type === "relation") {
|
|
1966
|
-
const
|
|
2056
|
+
const isUsersCollection = field.relation.collection.toLowerCase() === "users";
|
|
2057
|
+
const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
1967
2058
|
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
1968
2059
|
if (field.relation.maxSelect !== void 0) {
|
|
1969
2060
|
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
@@ -1977,7 +2068,7 @@ function generateFieldConstructorOptions(field) {
|
|
|
1977
2068
|
}
|
|
1978
2069
|
return parts.join(",\n");
|
|
1979
2070
|
}
|
|
1980
|
-
function generateFieldAddition(collectionName, field, varName) {
|
|
2071
|
+
function generateFieldAddition(collectionName, field, varName, isLast = false) {
|
|
1981
2072
|
const lines = [];
|
|
1982
2073
|
const constructorName = getFieldConstructorName(field.type);
|
|
1983
2074
|
const collectionVar = varName || `collection_${collectionName}_${field.name}`;
|
|
@@ -1987,10 +2078,10 @@ function generateFieldAddition(collectionName, field, varName) {
|
|
|
1987
2078
|
lines.push(generateFieldConstructorOptions(field));
|
|
1988
2079
|
lines.push(` }));`);
|
|
1989
2080
|
lines.push(``);
|
|
1990
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2081
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
1991
2082
|
return lines.join("\n");
|
|
1992
2083
|
}
|
|
1993
|
-
function generateFieldModification(collectionName, modification, varName) {
|
|
2084
|
+
function generateFieldModification(collectionName, modification, varName, isLast = false) {
|
|
1994
2085
|
const lines = [];
|
|
1995
2086
|
const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
|
|
1996
2087
|
const fieldVar = `${collectionVar}_field`;
|
|
@@ -2004,7 +2095,8 @@ function generateFieldModification(collectionName, modification, varName) {
|
|
|
2004
2095
|
} else if (change.property.startsWith("relation.")) {
|
|
2005
2096
|
const relationKey = change.property.replace("relation.", "");
|
|
2006
2097
|
if (relationKey === "collection") {
|
|
2007
|
-
const
|
|
2098
|
+
const isUsersCollection = String(change.newValue).toLowerCase() === "users";
|
|
2099
|
+
const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
|
|
2008
2100
|
lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
|
|
2009
2101
|
} else {
|
|
2010
2102
|
lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
|
|
@@ -2014,10 +2106,10 @@ function generateFieldModification(collectionName, modification, varName) {
|
|
|
2014
2106
|
}
|
|
2015
2107
|
}
|
|
2016
2108
|
lines.push(``);
|
|
2017
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2109
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2018
2110
|
return lines.join("\n");
|
|
2019
2111
|
}
|
|
2020
|
-
function generateFieldDeletion(collectionName, fieldName, varName) {
|
|
2112
|
+
function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
|
|
2021
2113
|
const lines = [];
|
|
2022
2114
|
const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
|
|
2023
2115
|
const fieldVar = `${collectionVar}_field`;
|
|
@@ -2026,18 +2118,18 @@ function generateFieldDeletion(collectionName, fieldName, varName) {
|
|
|
2026
2118
|
lines.push(``);
|
|
2027
2119
|
lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
|
|
2028
2120
|
lines.push(``);
|
|
2029
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2121
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2030
2122
|
return lines.join("\n");
|
|
2031
2123
|
}
|
|
2032
|
-
function generateIndexAddition(collectionName, index, varName) {
|
|
2124
|
+
function generateIndexAddition(collectionName, index, varName, isLast = false) {
|
|
2033
2125
|
const lines = [];
|
|
2034
2126
|
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
2035
2127
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2036
2128
|
lines.push(` ${collectionVar}.indexes.push("${index}");`);
|
|
2037
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2129
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2038
2130
|
return lines.join("\n");
|
|
2039
2131
|
}
|
|
2040
|
-
function generateIndexRemoval(collectionName, index, varName) {
|
|
2132
|
+
function generateIndexRemoval(collectionName, index, varName, isLast = false) {
|
|
2041
2133
|
const lines = [];
|
|
2042
2134
|
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
2043
2135
|
const indexVar = `${collectionVar}_indexToRemove`;
|
|
@@ -2046,29 +2138,29 @@ function generateIndexRemoval(collectionName, index, varName) {
|
|
|
2046
2138
|
lines.push(` if (${indexVar} !== -1) {`);
|
|
2047
2139
|
lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
|
|
2048
2140
|
lines.push(` }`);
|
|
2049
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2141
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2050
2142
|
return lines.join("\n");
|
|
2051
2143
|
}
|
|
2052
|
-
function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
|
|
2144
|
+
function generateRuleUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
|
|
2053
2145
|
const lines = [];
|
|
2054
2146
|
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
2055
2147
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2056
2148
|
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
2057
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2149
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2058
2150
|
return lines.join("\n");
|
|
2059
2151
|
}
|
|
2060
|
-
function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
|
|
2152
|
+
function generatePermissionUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
|
|
2061
2153
|
const lines = [];
|
|
2062
2154
|
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
2063
2155
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2064
2156
|
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
2065
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2157
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2066
2158
|
return lines.join("\n");
|
|
2067
2159
|
}
|
|
2068
|
-
function generateCollectionDeletion(collectionName, varName = "collection") {
|
|
2160
|
+
function generateCollectionDeletion(collectionName, varName = "collection", isLast = false) {
|
|
2069
2161
|
const lines = [];
|
|
2070
2162
|
lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2071
|
-
lines.push(` app.delete(${varName});`);
|
|
2163
|
+
lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
|
|
2072
2164
|
return lines.join("\n");
|
|
2073
2165
|
}
|
|
2074
2166
|
function generateUpMigration(diff) {
|
|
@@ -2160,7 +2252,24 @@ function generateUpMigration(diff) {
|
|
|
2160
2252
|
lines.push(` // No changes detected`);
|
|
2161
2253
|
lines.push(``);
|
|
2162
2254
|
}
|
|
2163
|
-
|
|
2255
|
+
let code = lines.join("\n");
|
|
2256
|
+
const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
|
|
2257
|
+
const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
|
|
2258
|
+
const saveMatches = [...code.matchAll(savePattern)];
|
|
2259
|
+
const deleteMatches = [...code.matchAll(deletePattern)];
|
|
2260
|
+
const allMatches = [
|
|
2261
|
+
...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
|
|
2262
|
+
...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
|
|
2263
|
+
].sort((a, b) => b.index - a.index);
|
|
2264
|
+
if (allMatches.length > 0) {
|
|
2265
|
+
const lastMatch = allMatches[0];
|
|
2266
|
+
if (lastMatch.type === "save") {
|
|
2267
|
+
code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
|
|
2268
|
+
} else {
|
|
2269
|
+
code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
return code;
|
|
2164
2273
|
}
|
|
2165
2274
|
function generateDownMigration(diff) {
|
|
2166
2275
|
const lines = [];
|
|
@@ -2262,7 +2371,24 @@ function generateDownMigration(diff) {
|
|
|
2262
2371
|
lines.push(` // No changes to revert`);
|
|
2263
2372
|
lines.push(``);
|
|
2264
2373
|
}
|
|
2265
|
-
|
|
2374
|
+
let code = lines.join("\n");
|
|
2375
|
+
const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
|
|
2376
|
+
const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
|
|
2377
|
+
const saveMatches = [...code.matchAll(savePattern)];
|
|
2378
|
+
const deleteMatches = [...code.matchAll(deletePattern)];
|
|
2379
|
+
const allMatches = [
|
|
2380
|
+
...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
|
|
2381
|
+
...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
|
|
2382
|
+
].sort((a, b) => b.index - a.index);
|
|
2383
|
+
if (allMatches.length > 0) {
|
|
2384
|
+
const lastMatch = allMatches[0];
|
|
2385
|
+
if (lastMatch.type === "save") {
|
|
2386
|
+
code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
|
|
2387
|
+
} else {
|
|
2388
|
+
code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
return code;
|
|
2266
2392
|
}
|
|
2267
2393
|
function generate(diff, config) {
|
|
2268
2394
|
const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
|
|
@@ -2285,57 +2411,18 @@ function generate(diff, config) {
|
|
|
2285
2411
|
);
|
|
2286
2412
|
}
|
|
2287
2413
|
}
|
|
2414
|
+
|
|
2415
|
+
// src/migration/pocketbase-converter.ts
|
|
2288
2416
|
var SNAPSHOT_VERSION = "1.0.0";
|
|
2289
|
-
({
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
try {
|
|
2293
|
-
if (!fs4.existsSync(migrationsPath)) {
|
|
2294
|
-
return null;
|
|
2295
|
-
}
|
|
2296
|
-
const files = fs4.readdirSync(migrationsPath);
|
|
2297
|
-
const snapshotFiles = files.filter(
|
|
2298
|
-
(file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
|
|
2299
|
-
);
|
|
2300
|
-
if (snapshotFiles.length === 0) {
|
|
2301
|
-
return null;
|
|
2302
|
-
}
|
|
2303
|
-
snapshotFiles.sort().reverse();
|
|
2304
|
-
const latestSnapshot = snapshotFiles[0];
|
|
2305
|
-
if (!latestSnapshot) {
|
|
2306
|
-
return null;
|
|
2307
|
-
}
|
|
2308
|
-
return path4.join(migrationsPath, latestSnapshot);
|
|
2309
|
-
} catch (error) {
|
|
2310
|
-
console.warn(`Error finding latest snapshot: ${error}`);
|
|
2311
|
-
return null;
|
|
2417
|
+
function resolveCollectionIdToName(collectionId) {
|
|
2418
|
+
if (collectionId === "_pb_users_auth_") {
|
|
2419
|
+
return "Users";
|
|
2312
2420
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
if (!migrationsPath) {
|
|
2317
|
-
return null;
|
|
2421
|
+
const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
2422
|
+
if (nameMatch) {
|
|
2423
|
+
return nameMatch[1];
|
|
2318
2424
|
}
|
|
2319
|
-
|
|
2320
|
-
try {
|
|
2321
|
-
const migrationContent = fs4.readFileSync(migrationsPath, "utf-8");
|
|
2322
|
-
return convertPocketBaseMigration(migrationContent);
|
|
2323
|
-
} catch (error) {
|
|
2324
|
-
console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
|
|
2325
|
-
return null;
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
const latestSnapshotPath = findLatestSnapshot(migrationsPath);
|
|
2329
|
-
if (latestSnapshotPath) {
|
|
2330
|
-
try {
|
|
2331
|
-
const migrationContent = fs4.readFileSync(latestSnapshotPath, "utf-8");
|
|
2332
|
-
return convertPocketBaseMigration(migrationContent);
|
|
2333
|
-
} catch (error) {
|
|
2334
|
-
console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
|
|
2335
|
-
return null;
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
return null;
|
|
2425
|
+
return collectionId;
|
|
2339
2426
|
}
|
|
2340
2427
|
function convertPocketBaseCollection(pbCollection) {
|
|
2341
2428
|
const fields = [];
|
|
@@ -2354,17 +2441,28 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
2354
2441
|
type: pbField.type,
|
|
2355
2442
|
required: pbField.required || false
|
|
2356
2443
|
};
|
|
2357
|
-
|
|
2358
|
-
|
|
2444
|
+
field.options = pbField.options ? { ...pbField.options } : {};
|
|
2445
|
+
if (pbField.type === "select") {
|
|
2446
|
+
if (pbField.values && Array.isArray(pbField.values)) {
|
|
2447
|
+
field.options.values = pbField.values;
|
|
2448
|
+
} else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
|
|
2449
|
+
field.options.values = pbField.options.values;
|
|
2450
|
+
}
|
|
2359
2451
|
}
|
|
2360
2452
|
if (pbField.type === "relation") {
|
|
2453
|
+
const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
|
|
2454
|
+
const collectionName = resolveCollectionIdToName(collectionId);
|
|
2361
2455
|
field.relation = {
|
|
2362
|
-
collection:
|
|
2363
|
-
cascadeDelete: pbField.options?.cascadeDelete
|
|
2364
|
-
maxSelect: pbField.options?.maxSelect,
|
|
2365
|
-
minSelect: pbField.options?.minSelect
|
|
2456
|
+
collection: collectionName,
|
|
2457
|
+
cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
|
|
2458
|
+
maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
|
|
2459
|
+
minSelect: pbField.minSelect ?? pbField.options?.minSelect
|
|
2366
2460
|
};
|
|
2367
2461
|
}
|
|
2462
|
+
const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
|
|
2463
|
+
if (Object.keys(field.options).length === 0) {
|
|
2464
|
+
delete field.options;
|
|
2465
|
+
} else if (pbField.type === "select" && hasOnlyValues) ;
|
|
2368
2466
|
fields.push(field);
|
|
2369
2467
|
}
|
|
2370
2468
|
}
|
|
@@ -2385,6 +2483,7 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
2385
2483
|
if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
|
|
2386
2484
|
if (Object.keys(rules).length > 0) {
|
|
2387
2485
|
schema.rules = rules;
|
|
2486
|
+
schema.permissions = { ...rules };
|
|
2388
2487
|
}
|
|
2389
2488
|
return schema;
|
|
2390
2489
|
}
|
|
@@ -2428,6 +2527,320 @@ function convertPocketBaseMigration(migrationContent) {
|
|
|
2428
2527
|
}
|
|
2429
2528
|
}
|
|
2430
2529
|
|
|
2530
|
+
// src/migration/migration-parser.ts
|
|
2531
|
+
function extractTimestampFromFilename(filename) {
|
|
2532
|
+
const match = filename.match(/^(\d+)_/);
|
|
2533
|
+
if (match) {
|
|
2534
|
+
return parseInt(match[1], 10);
|
|
2535
|
+
}
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
|
|
2539
|
+
try {
|
|
2540
|
+
if (!fs5.existsSync(migrationsPath)) {
|
|
2541
|
+
return [];
|
|
2542
|
+
}
|
|
2543
|
+
const files = fs5.readdirSync(migrationsPath);
|
|
2544
|
+
const migrationFiles = [];
|
|
2545
|
+
for (const file of files) {
|
|
2546
|
+
if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
if (!file.endsWith(".js")) {
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
const timestamp = extractTimestampFromFilename(file);
|
|
2553
|
+
if (timestamp && timestamp > snapshotTimestamp) {
|
|
2554
|
+
migrationFiles.push({
|
|
2555
|
+
path: path5.join(migrationsPath, file),
|
|
2556
|
+
timestamp
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
|
|
2561
|
+
return migrationFiles.map((f) => f.path);
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
console.warn(`Error finding migrations after snapshot: ${error}`);
|
|
2564
|
+
return [];
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
function parseMigrationOperationsFromContent(content) {
|
|
2568
|
+
const collectionsToCreate = [];
|
|
2569
|
+
const collectionsToDelete = [];
|
|
2570
|
+
try {
|
|
2571
|
+
let searchIndex = 0;
|
|
2572
|
+
while (true) {
|
|
2573
|
+
const collectionStart = content.indexOf("new Collection(", searchIndex);
|
|
2574
|
+
if (collectionStart === -1) {
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
const openParen = collectionStart + "new Collection(".length;
|
|
2578
|
+
let braceCount = 0;
|
|
2579
|
+
let parenCount = 1;
|
|
2580
|
+
let inString = false;
|
|
2581
|
+
let stringChar = null;
|
|
2582
|
+
let i = openParen;
|
|
2583
|
+
while (i < content.length && /\s/.test(content[i])) {
|
|
2584
|
+
i++;
|
|
2585
|
+
}
|
|
2586
|
+
if (content[i] !== "{") {
|
|
2587
|
+
searchIndex = i + 1;
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
const objectStart = i;
|
|
2591
|
+
braceCount = 1;
|
|
2592
|
+
i++;
|
|
2593
|
+
while (i < content.length && (braceCount > 0 || parenCount > 0)) {
|
|
2594
|
+
const char = content[i];
|
|
2595
|
+
const prevChar = i > 0 ? content[i - 1] : "";
|
|
2596
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2597
|
+
inString = true;
|
|
2598
|
+
stringChar = char;
|
|
2599
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2600
|
+
inString = false;
|
|
2601
|
+
stringChar = null;
|
|
2602
|
+
}
|
|
2603
|
+
if (!inString) {
|
|
2604
|
+
if (char === "{") braceCount++;
|
|
2605
|
+
if (char === "}") braceCount--;
|
|
2606
|
+
if (char === "(") parenCount++;
|
|
2607
|
+
if (char === ")") parenCount--;
|
|
2608
|
+
}
|
|
2609
|
+
i++;
|
|
2610
|
+
}
|
|
2611
|
+
if (braceCount === 0 && parenCount === 0) {
|
|
2612
|
+
const objectContent = content.substring(objectStart, i - 1);
|
|
2613
|
+
try {
|
|
2614
|
+
const collectionObj = new Function(`return ${objectContent}`)();
|
|
2615
|
+
if (collectionObj && collectionObj.name) {
|
|
2616
|
+
const schema = convertPocketBaseCollection(collectionObj);
|
|
2617
|
+
collectionsToCreate.push(schema);
|
|
2618
|
+
}
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
console.warn(`Failed to parse collection definition: ${error}`);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
searchIndex = i;
|
|
2624
|
+
}
|
|
2625
|
+
const deleteMatches = content.matchAll(
|
|
2626
|
+
/app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
|
|
2627
|
+
);
|
|
2628
|
+
for (const match of deleteMatches) {
|
|
2629
|
+
if (match[1]) {
|
|
2630
|
+
collectionsToDelete.push(match[1]);
|
|
2631
|
+
} else {
|
|
2632
|
+
const varNameMatch = match[0].match(/collection_(\w+)/);
|
|
2633
|
+
if (varNameMatch) {
|
|
2634
|
+
const varName = `collection_${varNameMatch[1]}`;
|
|
2635
|
+
const deleteIndex = content.indexOf(match[0]);
|
|
2636
|
+
const beforeDelete = content.substring(0, deleteIndex);
|
|
2637
|
+
const varDefMatch = beforeDelete.match(
|
|
2638
|
+
new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
|
|
2639
|
+
);
|
|
2640
|
+
if (varDefMatch && varDefMatch.length > 0) {
|
|
2641
|
+
const collectionDefMatch = beforeDelete.match(
|
|
2642
|
+
new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
|
|
2643
|
+
);
|
|
2644
|
+
if (collectionDefMatch) {
|
|
2645
|
+
try {
|
|
2646
|
+
const collectionDefStr = collectionDefMatch[1];
|
|
2647
|
+
const collectionObj = new Function(`return ${collectionDefStr}`)();
|
|
2648
|
+
if (collectionObj && collectionObj.name) {
|
|
2649
|
+
collectionsToDelete.push(collectionObj.name);
|
|
2650
|
+
}
|
|
2651
|
+
} catch {
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
const findAndDeleteMatches = content.matchAll(
|
|
2659
|
+
/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
|
|
2660
|
+
);
|
|
2661
|
+
for (const match of findAndDeleteMatches) {
|
|
2662
|
+
collectionsToDelete.push(match[1]);
|
|
2663
|
+
}
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
console.warn(`Failed to parse migration operations from content: ${error}`);
|
|
2666
|
+
}
|
|
2667
|
+
return { collectionsToCreate, collectionsToDelete };
|
|
2668
|
+
}
|
|
2669
|
+
function parseMigrationOperations(migrationContent) {
|
|
2670
|
+
try {
|
|
2671
|
+
const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
|
|
2672
|
+
if (!migrateMatch) {
|
|
2673
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2674
|
+
}
|
|
2675
|
+
const startIndex = migrateMatch.index + migrateMatch[0].length;
|
|
2676
|
+
let i = startIndex;
|
|
2677
|
+
let parenCount = 0;
|
|
2678
|
+
let foundFirstParen = false;
|
|
2679
|
+
while (i < migrationContent.length) {
|
|
2680
|
+
const char = migrationContent[i];
|
|
2681
|
+
if (char === "(") {
|
|
2682
|
+
parenCount++;
|
|
2683
|
+
foundFirstParen = true;
|
|
2684
|
+
i++;
|
|
2685
|
+
break;
|
|
2686
|
+
}
|
|
2687
|
+
i++;
|
|
2688
|
+
}
|
|
2689
|
+
if (!foundFirstParen) {
|
|
2690
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2691
|
+
}
|
|
2692
|
+
let inString = false;
|
|
2693
|
+
let stringChar = null;
|
|
2694
|
+
let foundBrace = false;
|
|
2695
|
+
let braceStart = -1;
|
|
2696
|
+
while (i < migrationContent.length && !foundBrace) {
|
|
2697
|
+
const char = migrationContent[i];
|
|
2698
|
+
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2699
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2700
|
+
inString = true;
|
|
2701
|
+
stringChar = char;
|
|
2702
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2703
|
+
inString = false;
|
|
2704
|
+
stringChar = null;
|
|
2705
|
+
}
|
|
2706
|
+
if (!inString) {
|
|
2707
|
+
if (char === "(") parenCount++;
|
|
2708
|
+
if (char === ")") {
|
|
2709
|
+
parenCount--;
|
|
2710
|
+
if (parenCount === 0) {
|
|
2711
|
+
i++;
|
|
2712
|
+
while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
|
|
2713
|
+
i++;
|
|
2714
|
+
}
|
|
2715
|
+
if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
|
|
2716
|
+
i += 2;
|
|
2717
|
+
while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
|
|
2718
|
+
i++;
|
|
2719
|
+
}
|
|
2720
|
+
if (i < migrationContent.length && migrationContent[i] === "{") {
|
|
2721
|
+
foundBrace = true;
|
|
2722
|
+
braceStart = i + 1;
|
|
2723
|
+
break;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
i++;
|
|
2730
|
+
}
|
|
2731
|
+
if (!foundBrace || braceStart === -1) {
|
|
2732
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2733
|
+
}
|
|
2734
|
+
let braceCount = 1;
|
|
2735
|
+
i = braceStart;
|
|
2736
|
+
inString = false;
|
|
2737
|
+
stringChar = null;
|
|
2738
|
+
while (i < migrationContent.length && braceCount > 0) {
|
|
2739
|
+
const char = migrationContent[i];
|
|
2740
|
+
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2741
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2742
|
+
inString = true;
|
|
2743
|
+
stringChar = char;
|
|
2744
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2745
|
+
inString = false;
|
|
2746
|
+
stringChar = null;
|
|
2747
|
+
}
|
|
2748
|
+
if (!inString) {
|
|
2749
|
+
if (char === "{") braceCount++;
|
|
2750
|
+
if (char === "}") braceCount--;
|
|
2751
|
+
}
|
|
2752
|
+
i++;
|
|
2753
|
+
}
|
|
2754
|
+
if (braceCount === 0) {
|
|
2755
|
+
const upMigrationContent = migrationContent.substring(braceStart, i - 1);
|
|
2756
|
+
return parseMigrationOperationsFromContent(upMigrationContent);
|
|
2757
|
+
}
|
|
2758
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2759
|
+
} catch (error) {
|
|
2760
|
+
console.warn(`Failed to parse migration operations: ${error}`);
|
|
2761
|
+
return { collectionsToCreate: [], collectionsToDelete: [] };
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
({
|
|
2765
|
+
workspaceRoot: process.cwd()});
|
|
2766
|
+
function findLatestSnapshot(migrationsPath) {
|
|
2767
|
+
try {
|
|
2768
|
+
if (!fs5.existsSync(migrationsPath)) {
|
|
2769
|
+
return null;
|
|
2770
|
+
}
|
|
2771
|
+
const files = fs5.readdirSync(migrationsPath);
|
|
2772
|
+
const snapshotFiles = files.filter(
|
|
2773
|
+
(file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
|
|
2774
|
+
);
|
|
2775
|
+
if (snapshotFiles.length === 0) {
|
|
2776
|
+
return null;
|
|
2777
|
+
}
|
|
2778
|
+
snapshotFiles.sort().reverse();
|
|
2779
|
+
const latestSnapshot = snapshotFiles[0];
|
|
2780
|
+
if (!latestSnapshot) {
|
|
2781
|
+
return null;
|
|
2782
|
+
}
|
|
2783
|
+
return path5.join(migrationsPath, latestSnapshot);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
console.warn(`Error finding latest snapshot: ${error}`);
|
|
2786
|
+
return null;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function applyMigrationOperations(snapshot, operations) {
|
|
2790
|
+
const updatedCollections = new Map(snapshot.collections);
|
|
2791
|
+
for (const collectionName of operations.collectionsToDelete) {
|
|
2792
|
+
updatedCollections.delete(collectionName);
|
|
2793
|
+
}
|
|
2794
|
+
for (const collection of operations.collectionsToCreate) {
|
|
2795
|
+
updatedCollections.set(collection.name, collection);
|
|
2796
|
+
}
|
|
2797
|
+
return {
|
|
2798
|
+
...snapshot,
|
|
2799
|
+
collections: updatedCollections
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
function loadSnapshotWithMigrations(config = {}) {
|
|
2803
|
+
const migrationsPath = config.migrationsPath;
|
|
2804
|
+
if (!migrationsPath) {
|
|
2805
|
+
return null;
|
|
2806
|
+
}
|
|
2807
|
+
if (fs5.existsSync(migrationsPath) && fs5.statSync(migrationsPath).isFile()) {
|
|
2808
|
+
try {
|
|
2809
|
+
const migrationContent = fs5.readFileSync(migrationsPath, "utf-8");
|
|
2810
|
+
return convertPocketBaseMigration(migrationContent);
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
|
|
2813
|
+
return null;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
const latestSnapshotPath = findLatestSnapshot(migrationsPath);
|
|
2817
|
+
if (!latestSnapshotPath) {
|
|
2818
|
+
return null;
|
|
2819
|
+
}
|
|
2820
|
+
try {
|
|
2821
|
+
const migrationContent = fs5.readFileSync(latestSnapshotPath, "utf-8");
|
|
2822
|
+
let snapshot = convertPocketBaseMigration(migrationContent);
|
|
2823
|
+
const snapshotFilename = path5.basename(latestSnapshotPath);
|
|
2824
|
+
const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
|
|
2825
|
+
if (snapshotTimestamp) {
|
|
2826
|
+
const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
|
|
2827
|
+
for (const migrationFile of migrationFiles) {
|
|
2828
|
+
try {
|
|
2829
|
+
const migrationContent2 = fs5.readFileSync(migrationFile, "utf-8");
|
|
2830
|
+
const operations = parseMigrationOperations(migrationContent2);
|
|
2831
|
+
snapshot = applyMigrationOperations(snapshot, operations);
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return snapshot;
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2431
2844
|
// src/migration/validation.ts
|
|
2432
2845
|
function detectCollectionDeletions(diff) {
|
|
2433
2846
|
const changes = [];
|
|
@@ -2596,8 +3009,8 @@ var DEFAULT_CONFIG5 = {
|
|
|
2596
3009
|
};
|
|
2597
3010
|
function findConfigFile(directory) {
|
|
2598
3011
|
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2599
|
-
const filePath =
|
|
2600
|
-
if (
|
|
3012
|
+
const filePath = path5.join(directory, fileName);
|
|
3013
|
+
if (fs5.existsSync(filePath)) {
|
|
2601
3014
|
return filePath;
|
|
2602
3015
|
}
|
|
2603
3016
|
}
|
|
@@ -2605,7 +3018,7 @@ function findConfigFile(directory) {
|
|
|
2605
3018
|
}
|
|
2606
3019
|
function loadJsonConfig(configPath) {
|
|
2607
3020
|
try {
|
|
2608
|
-
const content =
|
|
3021
|
+
const content = fs5.readFileSync(configPath, "utf-8");
|
|
2609
3022
|
return JSON.parse(content);
|
|
2610
3023
|
} catch (error) {
|
|
2611
3024
|
if (error instanceof SyntaxError) {
|
|
@@ -2634,10 +3047,10 @@ async function loadJsConfig(configPath) {
|
|
|
2634
3047
|
}
|
|
2635
3048
|
}
|
|
2636
3049
|
async function loadConfigFile(configPath) {
|
|
2637
|
-
if (!
|
|
3050
|
+
if (!fs5.existsSync(configPath)) {
|
|
2638
3051
|
return null;
|
|
2639
3052
|
}
|
|
2640
|
-
const ext =
|
|
3053
|
+
const ext = path5.extname(configPath).toLowerCase();
|
|
2641
3054
|
if (ext === ".json") {
|
|
2642
3055
|
return loadJsonConfig(configPath);
|
|
2643
3056
|
} else if (ext === ".js" || ext === ".mjs") {
|
|
@@ -2704,10 +3117,10 @@ function validateConfig(config, configPath) {
|
|
|
2704
3117
|
}
|
|
2705
3118
|
const cwd = process.cwd();
|
|
2706
3119
|
const possiblePaths = [
|
|
2707
|
-
|
|
2708
|
-
|
|
3120
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3121
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2709
3122
|
];
|
|
2710
|
-
const schemaDir = possiblePaths.find((p) =>
|
|
3123
|
+
const schemaDir = possiblePaths.find((p) => fs5.existsSync(p));
|
|
2711
3124
|
if (!schemaDir) {
|
|
2712
3125
|
throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
|
|
2713
3126
|
"schema.directory"
|
|
@@ -2719,15 +3132,15 @@ async function loadConfig(options = {}) {
|
|
|
2719
3132
|
let configFilePath;
|
|
2720
3133
|
const cwd = process.cwd();
|
|
2721
3134
|
if (options.config) {
|
|
2722
|
-
const explicitPath =
|
|
2723
|
-
if (!
|
|
3135
|
+
const explicitPath = path5.resolve(cwd, options.config);
|
|
3136
|
+
if (!fs5.existsSync(explicitPath)) {
|
|
2724
3137
|
throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
|
|
2725
3138
|
}
|
|
2726
3139
|
configFilePath = explicitPath;
|
|
2727
3140
|
} else {
|
|
2728
|
-
const searchDirs = [cwd,
|
|
3141
|
+
const searchDirs = [cwd, path5.join(cwd, "shared")];
|
|
2729
3142
|
for (const dir of searchDirs) {
|
|
2730
|
-
if (
|
|
3143
|
+
if (fs5.existsSync(dir)) {
|
|
2731
3144
|
const found = findConfigFile(dir);
|
|
2732
3145
|
if (found) {
|
|
2733
3146
|
configFilePath = found;
|
|
@@ -2756,18 +3169,18 @@ async function loadConfig(options = {}) {
|
|
|
2756
3169
|
function getSchemaDirectory(config) {
|
|
2757
3170
|
const cwd = process.cwd();
|
|
2758
3171
|
const possiblePaths = [
|
|
2759
|
-
|
|
2760
|
-
|
|
3172
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3173
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2761
3174
|
];
|
|
2762
|
-
return possiblePaths.find((p) =>
|
|
3175
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2763
3176
|
}
|
|
2764
3177
|
function getMigrationsDirectory(config) {
|
|
2765
3178
|
const cwd = process.cwd();
|
|
2766
3179
|
const possiblePaths = [
|
|
2767
|
-
|
|
2768
|
-
|
|
3180
|
+
path5.resolve(cwd, config.migrations.directory),
|
|
3181
|
+
path5.resolve(cwd, "shared", config.migrations.directory)
|
|
2769
3182
|
];
|
|
2770
|
-
return possiblePaths.find((p) =>
|
|
3183
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2771
3184
|
}
|
|
2772
3185
|
var currentVerbosity = "normal";
|
|
2773
3186
|
function setVerbosity(level) {
|
|
@@ -3035,10 +3448,16 @@ async function executeGenerate(options) {
|
|
|
3035
3448
|
const schemaDir = getSchemaDirectory(config);
|
|
3036
3449
|
const migrationsDir = getMigrationsDirectory(config);
|
|
3037
3450
|
logSection("\u{1F50D} Analyzing Schema");
|
|
3038
|
-
const
|
|
3451
|
+
const analyzerConfig = {
|
|
3452
|
+
schemaDir,
|
|
3453
|
+
excludePatterns: config.schema.exclude,
|
|
3454
|
+
useCompiledFiles: false
|
|
3455
|
+
// Use source files since we're in development/testing
|
|
3456
|
+
};
|
|
3457
|
+
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
3039
3458
|
logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
|
|
3040
3459
|
logInfo("Loading previous snapshot...");
|
|
3041
|
-
const previousSnapshot =
|
|
3460
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
3042
3461
|
migrationsPath: migrationsDir,
|
|
3043
3462
|
workspaceRoot: process.cwd()
|
|
3044
3463
|
});
|
|
@@ -3065,7 +3484,7 @@ async function executeGenerate(options) {
|
|
|
3065
3484
|
"Creating migration file...",
|
|
3066
3485
|
() => Promise.resolve(generate(diff, migrationsDir))
|
|
3067
3486
|
);
|
|
3068
|
-
logSuccess(`Migration file created: ${
|
|
3487
|
+
logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
|
|
3069
3488
|
logSection("\u2705 Next Steps");
|
|
3070
3489
|
console.log();
|
|
3071
3490
|
console.log(" 1. Review the generated migration file:");
|
|
@@ -3222,10 +3641,16 @@ async function executeStatus(options) {
|
|
|
3222
3641
|
const schemaDir = getSchemaDirectory(config);
|
|
3223
3642
|
const migrationsDir = getMigrationsDirectory(config);
|
|
3224
3643
|
logSection("\u{1F50D} Checking Migration Status");
|
|
3225
|
-
const
|
|
3644
|
+
const analyzerConfig = {
|
|
3645
|
+
schemaDir,
|
|
3646
|
+
excludePatterns: config.schema.exclude,
|
|
3647
|
+
useCompiledFiles: false
|
|
3648
|
+
// Use source files since we're in development/testing
|
|
3649
|
+
};
|
|
3650
|
+
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
3226
3651
|
logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
|
|
3227
3652
|
logInfo("Loading previous snapshot...");
|
|
3228
|
-
const previousSnapshot =
|
|
3653
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
3229
3654
|
migrationsPath: migrationsDir,
|
|
3230
3655
|
workspaceRoot: process.cwd()
|
|
3231
3656
|
});
|