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/migrate.js
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
4
|
+
import * as fs5 from 'fs';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import * as path5 from 'path';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
6
9
|
import { z } from 'zod';
|
|
7
10
|
import ora from 'ora';
|
|
8
11
|
|
|
12
|
+
({
|
|
13
|
+
id: z.string().describe("unique id"),
|
|
14
|
+
collectionId: z.string().describe("collection id"),
|
|
15
|
+
collectionName: z.string().describe("collection name"),
|
|
16
|
+
expand: z.record(z.any()).describe("expandable fields")
|
|
17
|
+
});
|
|
18
|
+
({
|
|
19
|
+
created: z.string().describe("creation timestamp"),
|
|
20
|
+
updated: z.string().describe("last update timestamp")
|
|
21
|
+
});
|
|
22
|
+
({
|
|
23
|
+
thumbnailURL: z.string().optional(),
|
|
24
|
+
imageFiles: z.array(z.string())
|
|
25
|
+
});
|
|
26
|
+
({
|
|
27
|
+
imageFiles: z.array(z.instanceof(File))
|
|
28
|
+
});
|
|
29
|
+
var RELATION_METADATA_KEY = "__pocketbase_relation__";
|
|
30
|
+
function extractRelationMetadata(description) {
|
|
31
|
+
if (!description) return null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(description);
|
|
34
|
+
if (parsed[RELATION_METADATA_KEY]) {
|
|
35
|
+
return parsed[RELATION_METADATA_KEY];
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
9
42
|
// src/migration/errors.ts
|
|
10
43
|
var MigrationError = class _MigrationError extends Error {
|
|
11
44
|
constructor(message) {
|
|
@@ -103,10 +136,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
|
|
|
103
136
|
operation;
|
|
104
137
|
code;
|
|
105
138
|
originalError;
|
|
106
|
-
constructor(message,
|
|
139
|
+
constructor(message, path7, operation, code, originalError) {
|
|
107
140
|
super(message);
|
|
108
141
|
this.name = "FileSystemError";
|
|
109
|
-
this.path =
|
|
142
|
+
this.path = path7;
|
|
110
143
|
this.operation = operation;
|
|
111
144
|
this.code = code;
|
|
112
145
|
this.originalError = originalError;
|
|
@@ -169,7 +202,7 @@ Cause: ${this.originalError.message}`);
|
|
|
169
202
|
}
|
|
170
203
|
};
|
|
171
204
|
|
|
172
|
-
// src/
|
|
205
|
+
// src/utils/permission-templates.ts
|
|
173
206
|
var PermissionTemplates = {
|
|
174
207
|
/**
|
|
175
208
|
* Public access - anyone can perform all operations
|
|
@@ -828,26 +861,28 @@ function getMaxSelect(fieldName, zodType) {
|
|
|
828
861
|
return 1;
|
|
829
862
|
}
|
|
830
863
|
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;
|
|
864
|
+
if (isSingleRelationField(fieldName, zodType)) {
|
|
865
|
+
return 0;
|
|
843
866
|
}
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
if (
|
|
847
|
-
|
|
867
|
+
if (isMultipleRelationField(fieldName, zodType)) {
|
|
868
|
+
let unwrappedType = zodType;
|
|
869
|
+
if (zodType instanceof z.ZodOptional) {
|
|
870
|
+
unwrappedType = zodType._def.innerType;
|
|
871
|
+
}
|
|
872
|
+
if (unwrappedType instanceof z.ZodNullable) {
|
|
873
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
874
|
+
}
|
|
875
|
+
if (unwrappedType instanceof z.ZodDefault) {
|
|
876
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
877
|
+
}
|
|
878
|
+
if (unwrappedType instanceof z.ZodArray) {
|
|
879
|
+
const arrayDef = unwrappedType._def;
|
|
880
|
+
if (arrayDef.minLength) {
|
|
881
|
+
return arrayDef.minLength.value;
|
|
882
|
+
}
|
|
848
883
|
}
|
|
849
884
|
}
|
|
850
|
-
return
|
|
885
|
+
return 0;
|
|
851
886
|
}
|
|
852
887
|
function mapZodStringType(zodType) {
|
|
853
888
|
const checks = zodType._def.checks || [];
|
|
@@ -1045,20 +1080,20 @@ function mergeConfig(config) {
|
|
|
1045
1080
|
}
|
|
1046
1081
|
function resolveSchemaDir(config) {
|
|
1047
1082
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1048
|
-
if (
|
|
1083
|
+
if (path5.isAbsolute(config.schemaDir)) {
|
|
1049
1084
|
return config.schemaDir;
|
|
1050
1085
|
}
|
|
1051
|
-
return
|
|
1086
|
+
return path5.join(workspaceRoot, config.schemaDir);
|
|
1052
1087
|
}
|
|
1053
1088
|
function discoverSchemaFiles(config) {
|
|
1054
1089
|
const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
|
|
1055
1090
|
const mergedConfig = mergeConfig(normalizedConfig);
|
|
1056
1091
|
const schemaDir = resolveSchemaDir(normalizedConfig);
|
|
1057
1092
|
try {
|
|
1058
|
-
if (!
|
|
1093
|
+
if (!fs5.existsSync(schemaDir)) {
|
|
1059
1094
|
throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
|
|
1060
1095
|
}
|
|
1061
|
-
const files =
|
|
1096
|
+
const files = fs5.readdirSync(schemaDir);
|
|
1062
1097
|
const schemaFiles = files.filter((file) => {
|
|
1063
1098
|
const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
|
|
1064
1099
|
if (!hasValidExtension) return false;
|
|
@@ -1074,7 +1109,7 @@ function discoverSchemaFiles(config) {
|
|
|
1074
1109
|
});
|
|
1075
1110
|
return schemaFiles.map((file) => {
|
|
1076
1111
|
const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
|
|
1077
|
-
return
|
|
1112
|
+
return path5.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
|
|
1078
1113
|
});
|
|
1079
1114
|
} catch (error) {
|
|
1080
1115
|
if (error instanceof FileSystemError) {
|
|
@@ -1105,13 +1140,32 @@ async function importSchemaModule(filePath, config) {
|
|
|
1105
1140
|
if (config?.pathTransformer) {
|
|
1106
1141
|
importPath = config.pathTransformer(filePath);
|
|
1107
1142
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1143
|
+
let resolvedPath = null;
|
|
1144
|
+
const jsPath = `${importPath}.js`;
|
|
1145
|
+
const tsPath = `${importPath}.ts`;
|
|
1146
|
+
if (fs5.existsSync(jsPath)) {
|
|
1147
|
+
resolvedPath = jsPath;
|
|
1148
|
+
} else if (fs5.existsSync(tsPath)) {
|
|
1149
|
+
resolvedPath = tsPath;
|
|
1150
|
+
} else {
|
|
1151
|
+
resolvedPath = jsPath;
|
|
1110
1152
|
}
|
|
1111
|
-
const fileUrl = new URL(`file://${
|
|
1153
|
+
const fileUrl = new URL(`file://${path5.resolve(resolvedPath)}`);
|
|
1112
1154
|
const module = await import(fileUrl.href);
|
|
1113
1155
|
return module;
|
|
1114
1156
|
} catch (error) {
|
|
1157
|
+
const tsPath = `${filePath}.ts`;
|
|
1158
|
+
const isTypeScriptFile = fs5.existsSync(tsPath);
|
|
1159
|
+
if (isTypeScriptFile) {
|
|
1160
|
+
throw new SchemaParsingError(
|
|
1161
|
+
`Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
|
|
1162
|
+
Please either:
|
|
1163
|
+
1. Compile your schema files to JavaScript first, or
|
|
1164
|
+
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")`,
|
|
1165
|
+
filePath,
|
|
1166
|
+
error
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1115
1169
|
throw new SchemaParsingError(
|
|
1116
1170
|
`Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
|
|
1117
1171
|
filePath,
|
|
@@ -1120,7 +1174,7 @@ async function importSchemaModule(filePath, config) {
|
|
|
1120
1174
|
}
|
|
1121
1175
|
}
|
|
1122
1176
|
function getCollectionNameFromFile(filePath) {
|
|
1123
|
-
const filename =
|
|
1177
|
+
const filename = path5.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1124
1178
|
return toCollectionName(filename);
|
|
1125
1179
|
}
|
|
1126
1180
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
|
|
@@ -1174,7 +1228,17 @@ function buildFieldDefinition(fieldName, zodType) {
|
|
|
1174
1228
|
required,
|
|
1175
1229
|
options
|
|
1176
1230
|
};
|
|
1177
|
-
|
|
1231
|
+
const relationMetadata = extractRelationMetadata(zodType.description);
|
|
1232
|
+
if (relationMetadata) {
|
|
1233
|
+
fieldDef.type = "relation";
|
|
1234
|
+
fieldDef.relation = {
|
|
1235
|
+
collection: relationMetadata.collection,
|
|
1236
|
+
maxSelect: relationMetadata.maxSelect,
|
|
1237
|
+
minSelect: relationMetadata.minSelect,
|
|
1238
|
+
cascadeDelete: relationMetadata.cascadeDelete
|
|
1239
|
+
};
|
|
1240
|
+
fieldDef.options = void 0;
|
|
1241
|
+
} else if (isRelationField(fieldName, zodType)) {
|
|
1178
1242
|
fieldDef.type = "relation";
|
|
1179
1243
|
const targetCollection = resolveTargetCollection(fieldName);
|
|
1180
1244
|
const maxSelect = getMaxSelect(fieldName, zodType);
|
|
@@ -1186,6 +1250,13 @@ function buildFieldDefinition(fieldName, zodType) {
|
|
|
1186
1250
|
cascadeDelete: false
|
|
1187
1251
|
// Default to false, can be configured later
|
|
1188
1252
|
};
|
|
1253
|
+
if (fieldDef.options) {
|
|
1254
|
+
const { min, max, pattern, ...relationSafeOptions } = fieldDef.options;
|
|
1255
|
+
console.log("min", min);
|
|
1256
|
+
console.log("max", max);
|
|
1257
|
+
console.log("pattern", pattern);
|
|
1258
|
+
fieldDef.options = Object.keys(relationSafeOptions).length > 0 ? relationSafeOptions : void 0;
|
|
1259
|
+
}
|
|
1189
1260
|
}
|
|
1190
1261
|
return fieldDef;
|
|
1191
1262
|
}
|
|
@@ -1238,11 +1309,12 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1238
1309
|
fields,
|
|
1239
1310
|
indexes,
|
|
1240
1311
|
rules: {
|
|
1241
|
-
listRule: null,
|
|
1242
|
-
viewRule: null,
|
|
1243
|
-
createRule: null,
|
|
1244
|
-
updateRule: null,
|
|
1245
|
-
deleteRule: null
|
|
1312
|
+
listRule: permissions?.listRule ?? null,
|
|
1313
|
+
viewRule: permissions?.viewRule ?? null,
|
|
1314
|
+
createRule: permissions?.createRule ?? null,
|
|
1315
|
+
updateRule: permissions?.updateRule ?? null,
|
|
1316
|
+
deleteRule: permissions?.deleteRule ?? null,
|
|
1317
|
+
manageRule: permissions?.manageRule ?? null
|
|
1246
1318
|
},
|
|
1247
1319
|
permissions
|
|
1248
1320
|
};
|
|
@@ -1266,7 +1338,12 @@ async function buildSchemaDefinition(config) {
|
|
|
1266
1338
|
if (normalizedConfig.pathTransformer) {
|
|
1267
1339
|
importPath = normalizedConfig.pathTransformer(filePath);
|
|
1268
1340
|
} else if (mergedConfig.useCompiledFiles) {
|
|
1269
|
-
|
|
1341
|
+
const distPath = filePath.replace(/\/src\//, "/dist/");
|
|
1342
|
+
if (fs5.existsSync(`${distPath}.js`) || fs5.existsSync(`${distPath}.mjs`)) {
|
|
1343
|
+
importPath = distPath;
|
|
1344
|
+
} else {
|
|
1345
|
+
importPath = filePath;
|
|
1346
|
+
}
|
|
1270
1347
|
}
|
|
1271
1348
|
const module = await importSchemaModule(importPath, normalizedConfig);
|
|
1272
1349
|
const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
|
|
@@ -1439,6 +1516,9 @@ function compareFieldOptions(currentField, previousField) {
|
|
|
1439
1516
|
for (const key of allKeys) {
|
|
1440
1517
|
const currentValue = currentOptions[key];
|
|
1441
1518
|
const previousValue = previousOptions[key];
|
|
1519
|
+
if (currentValue === void 0 && previousValue === void 0) {
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1442
1522
|
if (!areValuesEqual(currentValue, previousValue)) {
|
|
1443
1523
|
changes.push({
|
|
1444
1524
|
property: `options.${key}`,
|
|
@@ -1459,11 +1539,26 @@ function compareRelationConfigurations(currentField, previousField) {
|
|
|
1459
1539
|
if (!currentRelation || !previousRelation) {
|
|
1460
1540
|
return changes;
|
|
1461
1541
|
}
|
|
1462
|
-
|
|
1542
|
+
const normalizeCollection = (collection) => {
|
|
1543
|
+
if (!collection) return collection;
|
|
1544
|
+
if (collection === "_pb_users_auth_") {
|
|
1545
|
+
return "Users";
|
|
1546
|
+
}
|
|
1547
|
+
const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
1548
|
+
if (nameMatch) {
|
|
1549
|
+
return nameMatch[1];
|
|
1550
|
+
}
|
|
1551
|
+
return collection;
|
|
1552
|
+
};
|
|
1553
|
+
const normalizedCurrent = normalizeCollection(currentRelation.collection);
|
|
1554
|
+
const normalizedPrevious = normalizeCollection(previousRelation.collection);
|
|
1555
|
+
if (normalizedCurrent !== normalizedPrevious) {
|
|
1463
1556
|
changes.push({
|
|
1464
1557
|
property: "relation.collection",
|
|
1465
|
-
oldValue:
|
|
1466
|
-
|
|
1558
|
+
oldValue: normalizedPrevious,
|
|
1559
|
+
// Use normalized value for clarity
|
|
1560
|
+
newValue: normalizedCurrent
|
|
1561
|
+
// Use normalized value for clarity
|
|
1467
1562
|
});
|
|
1468
1563
|
}
|
|
1469
1564
|
if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
|
|
@@ -1664,10 +1759,8 @@ function compare(currentSchema, previousSnapshot, config) {
|
|
|
1664
1759
|
var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
|
|
1665
1760
|
migrate((app) => {
|
|
1666
1761
|
{{UP_CODE}}
|
|
1667
|
-
return true;
|
|
1668
1762
|
}, (app) => {
|
|
1669
1763
|
{{DOWN_CODE}}
|
|
1670
|
-
return true;
|
|
1671
1764
|
});
|
|
1672
1765
|
`;
|
|
1673
1766
|
var DEFAULT_CONFIG3 = {
|
|
@@ -1685,10 +1778,10 @@ function mergeConfig3(config) {
|
|
|
1685
1778
|
}
|
|
1686
1779
|
function resolveMigrationDir(config) {
|
|
1687
1780
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1688
|
-
if (
|
|
1781
|
+
if (path5.isAbsolute(config.migrationDir)) {
|
|
1689
1782
|
return config.migrationDir;
|
|
1690
1783
|
}
|
|
1691
|
-
return
|
|
1784
|
+
return path5.join(workspaceRoot, config.migrationDir);
|
|
1692
1785
|
}
|
|
1693
1786
|
function generateTimestamp(config) {
|
|
1694
1787
|
if (config?.timestampGenerator) {
|
|
@@ -1746,9 +1839,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
|
|
|
1746
1839
|
}
|
|
1747
1840
|
function writeMigrationFile(migrationDir, filename, content) {
|
|
1748
1841
|
try {
|
|
1749
|
-
if (!
|
|
1842
|
+
if (!fs5.existsSync(migrationDir)) {
|
|
1750
1843
|
try {
|
|
1751
|
-
|
|
1844
|
+
fs5.mkdirSync(migrationDir, { recursive: true });
|
|
1752
1845
|
} catch (error) {
|
|
1753
1846
|
const fsError = error;
|
|
1754
1847
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
@@ -1769,15 +1862,15 @@ function writeMigrationFile(migrationDir, filename, content) {
|
|
|
1769
1862
|
);
|
|
1770
1863
|
}
|
|
1771
1864
|
}
|
|
1772
|
-
const filePath =
|
|
1773
|
-
|
|
1865
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1866
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1774
1867
|
return filePath;
|
|
1775
1868
|
} catch (error) {
|
|
1776
1869
|
if (error instanceof FileSystemError) {
|
|
1777
1870
|
throw error;
|
|
1778
1871
|
}
|
|
1779
1872
|
const fsError = error;
|
|
1780
|
-
const filePath =
|
|
1873
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1781
1874
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
1782
1875
|
throw new FileSystemError(
|
|
1783
1876
|
`Permission denied writing migration file. Check file and directory permissions.`,
|
|
@@ -1832,7 +1925,8 @@ function generateFieldDefinitionObject(field) {
|
|
|
1832
1925
|
}
|
|
1833
1926
|
}
|
|
1834
1927
|
if (field.relation) {
|
|
1835
|
-
const
|
|
1928
|
+
const isUsersCollection = field.relation.collection.toLowerCase() === "users";
|
|
1929
|
+
const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
1836
1930
|
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
1837
1931
|
if (field.relation.maxSelect !== void 0) {
|
|
1838
1932
|
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
@@ -1916,7 +2010,7 @@ function generateIndexesArray(indexes) {
|
|
|
1916
2010
|
${indexStrings.join(",\n ")},
|
|
1917
2011
|
]`;
|
|
1918
2012
|
}
|
|
1919
|
-
function generateCollectionCreation(collection, varName = "collection") {
|
|
2013
|
+
function generateCollectionCreation(collection, varName = "collection", isLast = false) {
|
|
1920
2014
|
const lines = [];
|
|
1921
2015
|
lines.push(` const ${varName} = new Collection({`);
|
|
1922
2016
|
lines.push(` name: "${collection.name}",`);
|
|
@@ -1932,7 +2026,7 @@ function generateCollectionCreation(collection, varName = "collection") {
|
|
|
1932
2026
|
lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
|
|
1933
2027
|
lines.push(` });`);
|
|
1934
2028
|
lines.push(``);
|
|
1935
|
-
lines.push(` app.save(${varName});`);
|
|
2029
|
+
lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
|
|
1936
2030
|
return lines.join("\n");
|
|
1937
2031
|
}
|
|
1938
2032
|
function getFieldConstructorName(fieldType) {
|
|
@@ -1963,7 +2057,8 @@ function generateFieldConstructorOptions(field) {
|
|
|
1963
2057
|
}
|
|
1964
2058
|
}
|
|
1965
2059
|
if (field.relation && field.type === "relation") {
|
|
1966
|
-
const
|
|
2060
|
+
const isUsersCollection = field.relation.collection.toLowerCase() === "users";
|
|
2061
|
+
const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
1967
2062
|
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
1968
2063
|
if (field.relation.maxSelect !== void 0) {
|
|
1969
2064
|
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
@@ -1977,7 +2072,7 @@ function generateFieldConstructorOptions(field) {
|
|
|
1977
2072
|
}
|
|
1978
2073
|
return parts.join(",\n");
|
|
1979
2074
|
}
|
|
1980
|
-
function generateFieldAddition(collectionName, field, varName) {
|
|
2075
|
+
function generateFieldAddition(collectionName, field, varName, isLast = false) {
|
|
1981
2076
|
const lines = [];
|
|
1982
2077
|
const constructorName = getFieldConstructorName(field.type);
|
|
1983
2078
|
const collectionVar = varName || `collection_${collectionName}_${field.name}`;
|
|
@@ -1987,10 +2082,10 @@ function generateFieldAddition(collectionName, field, varName) {
|
|
|
1987
2082
|
lines.push(generateFieldConstructorOptions(field));
|
|
1988
2083
|
lines.push(` }));`);
|
|
1989
2084
|
lines.push(``);
|
|
1990
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2085
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
1991
2086
|
return lines.join("\n");
|
|
1992
2087
|
}
|
|
1993
|
-
function generateFieldModification(collectionName, modification, varName) {
|
|
2088
|
+
function generateFieldModification(collectionName, modification, varName, isLast = false) {
|
|
1994
2089
|
const lines = [];
|
|
1995
2090
|
const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
|
|
1996
2091
|
const fieldVar = `${collectionVar}_field`;
|
|
@@ -2004,7 +2099,8 @@ function generateFieldModification(collectionName, modification, varName) {
|
|
|
2004
2099
|
} else if (change.property.startsWith("relation.")) {
|
|
2005
2100
|
const relationKey = change.property.replace("relation.", "");
|
|
2006
2101
|
if (relationKey === "collection") {
|
|
2007
|
-
const
|
|
2102
|
+
const isUsersCollection = String(change.newValue).toLowerCase() === "users";
|
|
2103
|
+
const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
|
|
2008
2104
|
lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
|
|
2009
2105
|
} else {
|
|
2010
2106
|
lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
|
|
@@ -2014,10 +2110,10 @@ function generateFieldModification(collectionName, modification, varName) {
|
|
|
2014
2110
|
}
|
|
2015
2111
|
}
|
|
2016
2112
|
lines.push(``);
|
|
2017
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2113
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2018
2114
|
return lines.join("\n");
|
|
2019
2115
|
}
|
|
2020
|
-
function generateFieldDeletion(collectionName, fieldName, varName) {
|
|
2116
|
+
function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
|
|
2021
2117
|
const lines = [];
|
|
2022
2118
|
const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
|
|
2023
2119
|
const fieldVar = `${collectionVar}_field`;
|
|
@@ -2026,18 +2122,18 @@ function generateFieldDeletion(collectionName, fieldName, varName) {
|
|
|
2026
2122
|
lines.push(``);
|
|
2027
2123
|
lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
|
|
2028
2124
|
lines.push(``);
|
|
2029
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2125
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2030
2126
|
return lines.join("\n");
|
|
2031
2127
|
}
|
|
2032
|
-
function generateIndexAddition(collectionName, index, varName) {
|
|
2128
|
+
function generateIndexAddition(collectionName, index, varName, isLast = false) {
|
|
2033
2129
|
const lines = [];
|
|
2034
2130
|
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
2035
2131
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2036
2132
|
lines.push(` ${collectionVar}.indexes.push("${index}");`);
|
|
2037
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2133
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2038
2134
|
return lines.join("\n");
|
|
2039
2135
|
}
|
|
2040
|
-
function generateIndexRemoval(collectionName, index, varName) {
|
|
2136
|
+
function generateIndexRemoval(collectionName, index, varName, isLast = false) {
|
|
2041
2137
|
const lines = [];
|
|
2042
2138
|
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
2043
2139
|
const indexVar = `${collectionVar}_indexToRemove`;
|
|
@@ -2046,29 +2142,29 @@ function generateIndexRemoval(collectionName, index, varName) {
|
|
|
2046
2142
|
lines.push(` if (${indexVar} !== -1) {`);
|
|
2047
2143
|
lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
|
|
2048
2144
|
lines.push(` }`);
|
|
2049
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2145
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2050
2146
|
return lines.join("\n");
|
|
2051
2147
|
}
|
|
2052
|
-
function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
|
|
2148
|
+
function generateRuleUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
|
|
2053
2149
|
const lines = [];
|
|
2054
2150
|
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
2055
2151
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2056
2152
|
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
2057
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2153
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2058
2154
|
return lines.join("\n");
|
|
2059
2155
|
}
|
|
2060
|
-
function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
|
|
2156
|
+
function generatePermissionUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
|
|
2061
2157
|
const lines = [];
|
|
2062
2158
|
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
2063
2159
|
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2064
2160
|
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
2065
|
-
lines.push(` app.save(${collectionVar});`);
|
|
2161
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
2066
2162
|
return lines.join("\n");
|
|
2067
2163
|
}
|
|
2068
|
-
function generateCollectionDeletion(collectionName, varName = "collection") {
|
|
2164
|
+
function generateCollectionDeletion(collectionName, varName = "collection", isLast = false) {
|
|
2069
2165
|
const lines = [];
|
|
2070
2166
|
lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2071
|
-
lines.push(` app.delete(${varName});`);
|
|
2167
|
+
lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
|
|
2072
2168
|
return lines.join("\n");
|
|
2073
2169
|
}
|
|
2074
2170
|
function generateUpMigration(diff) {
|
|
@@ -2160,7 +2256,24 @@ function generateUpMigration(diff) {
|
|
|
2160
2256
|
lines.push(` // No changes detected`);
|
|
2161
2257
|
lines.push(``);
|
|
2162
2258
|
}
|
|
2163
|
-
|
|
2259
|
+
let code = lines.join("\n");
|
|
2260
|
+
const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
|
|
2261
|
+
const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
|
|
2262
|
+
const saveMatches = [...code.matchAll(savePattern)];
|
|
2263
|
+
const deleteMatches = [...code.matchAll(deletePattern)];
|
|
2264
|
+
const allMatches = [
|
|
2265
|
+
...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
|
|
2266
|
+
...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
|
|
2267
|
+
].sort((a, b) => b.index - a.index);
|
|
2268
|
+
if (allMatches.length > 0) {
|
|
2269
|
+
const lastMatch = allMatches[0];
|
|
2270
|
+
if (lastMatch.type === "save") {
|
|
2271
|
+
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);
|
|
2272
|
+
} else {
|
|
2273
|
+
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);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return code;
|
|
2164
2277
|
}
|
|
2165
2278
|
function generateDownMigration(diff) {
|
|
2166
2279
|
const lines = [];
|
|
@@ -2262,7 +2375,24 @@ function generateDownMigration(diff) {
|
|
|
2262
2375
|
lines.push(` // No changes to revert`);
|
|
2263
2376
|
lines.push(``);
|
|
2264
2377
|
}
|
|
2265
|
-
|
|
2378
|
+
let code = lines.join("\n");
|
|
2379
|
+
const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
|
|
2380
|
+
const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
|
|
2381
|
+
const saveMatches = [...code.matchAll(savePattern)];
|
|
2382
|
+
const deleteMatches = [...code.matchAll(deletePattern)];
|
|
2383
|
+
const allMatches = [
|
|
2384
|
+
...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
|
|
2385
|
+
...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
|
|
2386
|
+
].sort((a, b) => b.index - a.index);
|
|
2387
|
+
if (allMatches.length > 0) {
|
|
2388
|
+
const lastMatch = allMatches[0];
|
|
2389
|
+
if (lastMatch.type === "save") {
|
|
2390
|
+
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);
|
|
2391
|
+
} else {
|
|
2392
|
+
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);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
return code;
|
|
2266
2396
|
}
|
|
2267
2397
|
function generate(diff, config) {
|
|
2268
2398
|
const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
|
|
@@ -2285,57 +2415,18 @@ function generate(diff, config) {
|
|
|
2285
2415
|
);
|
|
2286
2416
|
}
|
|
2287
2417
|
}
|
|
2418
|
+
|
|
2419
|
+
// src/migration/pocketbase-converter.ts
|
|
2288
2420
|
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;
|
|
2421
|
+
function resolveCollectionIdToName(collectionId) {
|
|
2422
|
+
if (collectionId === "_pb_users_auth_") {
|
|
2423
|
+
return "Users";
|
|
2312
2424
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
if (!migrationsPath) {
|
|
2317
|
-
return null;
|
|
2425
|
+
const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
2426
|
+
if (nameMatch) {
|
|
2427
|
+
return nameMatch[1];
|
|
2318
2428
|
}
|
|
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;
|
|
2429
|
+
return collectionId;
|
|
2339
2430
|
}
|
|
2340
2431
|
function convertPocketBaseCollection(pbCollection) {
|
|
2341
2432
|
const fields = [];
|
|
@@ -2354,17 +2445,28 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
2354
2445
|
type: pbField.type,
|
|
2355
2446
|
required: pbField.required || false
|
|
2356
2447
|
};
|
|
2357
|
-
|
|
2358
|
-
|
|
2448
|
+
field.options = pbField.options ? { ...pbField.options } : {};
|
|
2449
|
+
if (pbField.type === "select") {
|
|
2450
|
+
if (pbField.values && Array.isArray(pbField.values)) {
|
|
2451
|
+
field.options.values = pbField.values;
|
|
2452
|
+
} else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
|
|
2453
|
+
field.options.values = pbField.options.values;
|
|
2454
|
+
}
|
|
2359
2455
|
}
|
|
2360
2456
|
if (pbField.type === "relation") {
|
|
2457
|
+
const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
|
|
2458
|
+
const collectionName = resolveCollectionIdToName(collectionId);
|
|
2361
2459
|
field.relation = {
|
|
2362
|
-
collection:
|
|
2363
|
-
cascadeDelete: pbField.options?.cascadeDelete
|
|
2364
|
-
maxSelect: pbField.options?.maxSelect,
|
|
2365
|
-
minSelect: pbField.options?.minSelect
|
|
2460
|
+
collection: collectionName,
|
|
2461
|
+
cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
|
|
2462
|
+
maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
|
|
2463
|
+
minSelect: pbField.minSelect ?? pbField.options?.minSelect
|
|
2366
2464
|
};
|
|
2367
2465
|
}
|
|
2466
|
+
const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
|
|
2467
|
+
if (Object.keys(field.options).length === 0) {
|
|
2468
|
+
delete field.options;
|
|
2469
|
+
} else if (pbField.type === "select" && hasOnlyValues) ;
|
|
2368
2470
|
fields.push(field);
|
|
2369
2471
|
}
|
|
2370
2472
|
}
|
|
@@ -2385,6 +2487,7 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
2385
2487
|
if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
|
|
2386
2488
|
if (Object.keys(rules).length > 0) {
|
|
2387
2489
|
schema.rules = rules;
|
|
2490
|
+
schema.permissions = { ...rules };
|
|
2388
2491
|
}
|
|
2389
2492
|
return schema;
|
|
2390
2493
|
}
|
|
@@ -2428,6 +2531,320 @@ function convertPocketBaseMigration(migrationContent) {
|
|
|
2428
2531
|
}
|
|
2429
2532
|
}
|
|
2430
2533
|
|
|
2534
|
+
// src/migration/migration-parser.ts
|
|
2535
|
+
function extractTimestampFromFilename(filename) {
|
|
2536
|
+
const match = filename.match(/^(\d+)_/);
|
|
2537
|
+
if (match) {
|
|
2538
|
+
return parseInt(match[1], 10);
|
|
2539
|
+
}
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
|
|
2543
|
+
try {
|
|
2544
|
+
if (!fs5.existsSync(migrationsPath)) {
|
|
2545
|
+
return [];
|
|
2546
|
+
}
|
|
2547
|
+
const files = fs5.readdirSync(migrationsPath);
|
|
2548
|
+
const migrationFiles = [];
|
|
2549
|
+
for (const file of files) {
|
|
2550
|
+
if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
if (!file.endsWith(".js")) {
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
const timestamp = extractTimestampFromFilename(file);
|
|
2557
|
+
if (timestamp && timestamp > snapshotTimestamp) {
|
|
2558
|
+
migrationFiles.push({
|
|
2559
|
+
path: path5.join(migrationsPath, file),
|
|
2560
|
+
timestamp
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
|
|
2565
|
+
return migrationFiles.map((f) => f.path);
|
|
2566
|
+
} catch (error) {
|
|
2567
|
+
console.warn(`Error finding migrations after snapshot: ${error}`);
|
|
2568
|
+
return [];
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
function parseMigrationOperationsFromContent(content) {
|
|
2572
|
+
const collectionsToCreate = [];
|
|
2573
|
+
const collectionsToDelete = [];
|
|
2574
|
+
try {
|
|
2575
|
+
let searchIndex = 0;
|
|
2576
|
+
while (true) {
|
|
2577
|
+
const collectionStart = content.indexOf("new Collection(", searchIndex);
|
|
2578
|
+
if (collectionStart === -1) {
|
|
2579
|
+
break;
|
|
2580
|
+
}
|
|
2581
|
+
const openParen = collectionStart + "new Collection(".length;
|
|
2582
|
+
let braceCount = 0;
|
|
2583
|
+
let parenCount = 1;
|
|
2584
|
+
let inString = false;
|
|
2585
|
+
let stringChar = null;
|
|
2586
|
+
let i = openParen;
|
|
2587
|
+
while (i < content.length && /\s/.test(content[i])) {
|
|
2588
|
+
i++;
|
|
2589
|
+
}
|
|
2590
|
+
if (content[i] !== "{") {
|
|
2591
|
+
searchIndex = i + 1;
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
const objectStart = i;
|
|
2595
|
+
braceCount = 1;
|
|
2596
|
+
i++;
|
|
2597
|
+
while (i < content.length && (braceCount > 0 || parenCount > 0)) {
|
|
2598
|
+
const char = content[i];
|
|
2599
|
+
const prevChar = i > 0 ? content[i - 1] : "";
|
|
2600
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2601
|
+
inString = true;
|
|
2602
|
+
stringChar = char;
|
|
2603
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2604
|
+
inString = false;
|
|
2605
|
+
stringChar = null;
|
|
2606
|
+
}
|
|
2607
|
+
if (!inString) {
|
|
2608
|
+
if (char === "{") braceCount++;
|
|
2609
|
+
if (char === "}") braceCount--;
|
|
2610
|
+
if (char === "(") parenCount++;
|
|
2611
|
+
if (char === ")") parenCount--;
|
|
2612
|
+
}
|
|
2613
|
+
i++;
|
|
2614
|
+
}
|
|
2615
|
+
if (braceCount === 0 && parenCount === 0) {
|
|
2616
|
+
const objectContent = content.substring(objectStart, i - 1);
|
|
2617
|
+
try {
|
|
2618
|
+
const collectionObj = new Function(`return ${objectContent}`)();
|
|
2619
|
+
if (collectionObj && collectionObj.name) {
|
|
2620
|
+
const schema = convertPocketBaseCollection(collectionObj);
|
|
2621
|
+
collectionsToCreate.push(schema);
|
|
2622
|
+
}
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
console.warn(`Failed to parse collection definition: ${error}`);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
searchIndex = i;
|
|
2628
|
+
}
|
|
2629
|
+
const deleteMatches = content.matchAll(
|
|
2630
|
+
/app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
|
|
2631
|
+
);
|
|
2632
|
+
for (const match of deleteMatches) {
|
|
2633
|
+
if (match[1]) {
|
|
2634
|
+
collectionsToDelete.push(match[1]);
|
|
2635
|
+
} else {
|
|
2636
|
+
const varNameMatch = match[0].match(/collection_(\w+)/);
|
|
2637
|
+
if (varNameMatch) {
|
|
2638
|
+
const varName = `collection_${varNameMatch[1]}`;
|
|
2639
|
+
const deleteIndex = content.indexOf(match[0]);
|
|
2640
|
+
const beforeDelete = content.substring(0, deleteIndex);
|
|
2641
|
+
const varDefMatch = beforeDelete.match(
|
|
2642
|
+
new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
|
|
2643
|
+
);
|
|
2644
|
+
if (varDefMatch && varDefMatch.length > 0) {
|
|
2645
|
+
const collectionDefMatch = beforeDelete.match(
|
|
2646
|
+
new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
|
|
2647
|
+
);
|
|
2648
|
+
if (collectionDefMatch) {
|
|
2649
|
+
try {
|
|
2650
|
+
const collectionDefStr = collectionDefMatch[1];
|
|
2651
|
+
const collectionObj = new Function(`return ${collectionDefStr}`)();
|
|
2652
|
+
if (collectionObj && collectionObj.name) {
|
|
2653
|
+
collectionsToDelete.push(collectionObj.name);
|
|
2654
|
+
}
|
|
2655
|
+
} catch {
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
const findAndDeleteMatches = content.matchAll(
|
|
2663
|
+
/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
|
|
2664
|
+
);
|
|
2665
|
+
for (const match of findAndDeleteMatches) {
|
|
2666
|
+
collectionsToDelete.push(match[1]);
|
|
2667
|
+
}
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
console.warn(`Failed to parse migration operations from content: ${error}`);
|
|
2670
|
+
}
|
|
2671
|
+
return { collectionsToCreate, collectionsToDelete };
|
|
2672
|
+
}
|
|
2673
|
+
function parseMigrationOperations(migrationContent) {
|
|
2674
|
+
try {
|
|
2675
|
+
const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
|
|
2676
|
+
if (!migrateMatch) {
|
|
2677
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2678
|
+
}
|
|
2679
|
+
const startIndex = migrateMatch.index + migrateMatch[0].length;
|
|
2680
|
+
let i = startIndex;
|
|
2681
|
+
let parenCount = 0;
|
|
2682
|
+
let foundFirstParen = false;
|
|
2683
|
+
while (i < migrationContent.length) {
|
|
2684
|
+
const char = migrationContent[i];
|
|
2685
|
+
if (char === "(") {
|
|
2686
|
+
parenCount++;
|
|
2687
|
+
foundFirstParen = true;
|
|
2688
|
+
i++;
|
|
2689
|
+
break;
|
|
2690
|
+
}
|
|
2691
|
+
i++;
|
|
2692
|
+
}
|
|
2693
|
+
if (!foundFirstParen) {
|
|
2694
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2695
|
+
}
|
|
2696
|
+
let inString = false;
|
|
2697
|
+
let stringChar = null;
|
|
2698
|
+
let foundBrace = false;
|
|
2699
|
+
let braceStart = -1;
|
|
2700
|
+
while (i < migrationContent.length && !foundBrace) {
|
|
2701
|
+
const char = migrationContent[i];
|
|
2702
|
+
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2703
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2704
|
+
inString = true;
|
|
2705
|
+
stringChar = char;
|
|
2706
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2707
|
+
inString = false;
|
|
2708
|
+
stringChar = null;
|
|
2709
|
+
}
|
|
2710
|
+
if (!inString) {
|
|
2711
|
+
if (char === "(") parenCount++;
|
|
2712
|
+
if (char === ")") {
|
|
2713
|
+
parenCount--;
|
|
2714
|
+
if (parenCount === 0) {
|
|
2715
|
+
i++;
|
|
2716
|
+
while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
|
|
2717
|
+
i++;
|
|
2718
|
+
}
|
|
2719
|
+
if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
|
|
2720
|
+
i += 2;
|
|
2721
|
+
while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
|
|
2722
|
+
i++;
|
|
2723
|
+
}
|
|
2724
|
+
if (i < migrationContent.length && migrationContent[i] === "{") {
|
|
2725
|
+
foundBrace = true;
|
|
2726
|
+
braceStart = i + 1;
|
|
2727
|
+
break;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
i++;
|
|
2734
|
+
}
|
|
2735
|
+
if (!foundBrace || braceStart === -1) {
|
|
2736
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2737
|
+
}
|
|
2738
|
+
let braceCount = 1;
|
|
2739
|
+
i = braceStart;
|
|
2740
|
+
inString = false;
|
|
2741
|
+
stringChar = null;
|
|
2742
|
+
while (i < migrationContent.length && braceCount > 0) {
|
|
2743
|
+
const char = migrationContent[i];
|
|
2744
|
+
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2745
|
+
if (!inString && (char === '"' || char === "'")) {
|
|
2746
|
+
inString = true;
|
|
2747
|
+
stringChar = char;
|
|
2748
|
+
} else if (inString && char === stringChar && prevChar !== "\\") {
|
|
2749
|
+
inString = false;
|
|
2750
|
+
stringChar = null;
|
|
2751
|
+
}
|
|
2752
|
+
if (!inString) {
|
|
2753
|
+
if (char === "{") braceCount++;
|
|
2754
|
+
if (char === "}") braceCount--;
|
|
2755
|
+
}
|
|
2756
|
+
i++;
|
|
2757
|
+
}
|
|
2758
|
+
if (braceCount === 0) {
|
|
2759
|
+
const upMigrationContent = migrationContent.substring(braceStart, i - 1);
|
|
2760
|
+
return parseMigrationOperationsFromContent(upMigrationContent);
|
|
2761
|
+
}
|
|
2762
|
+
return parseMigrationOperationsFromContent(migrationContent);
|
|
2763
|
+
} catch (error) {
|
|
2764
|
+
console.warn(`Failed to parse migration operations: ${error}`);
|
|
2765
|
+
return { collectionsToCreate: [], collectionsToDelete: [] };
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
({
|
|
2769
|
+
workspaceRoot: process.cwd()});
|
|
2770
|
+
function findLatestSnapshot(migrationsPath) {
|
|
2771
|
+
try {
|
|
2772
|
+
if (!fs5.existsSync(migrationsPath)) {
|
|
2773
|
+
return null;
|
|
2774
|
+
}
|
|
2775
|
+
const files = fs5.readdirSync(migrationsPath);
|
|
2776
|
+
const snapshotFiles = files.filter(
|
|
2777
|
+
(file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
|
|
2778
|
+
);
|
|
2779
|
+
if (snapshotFiles.length === 0) {
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
snapshotFiles.sort().reverse();
|
|
2783
|
+
const latestSnapshot = snapshotFiles[0];
|
|
2784
|
+
if (!latestSnapshot) {
|
|
2785
|
+
return null;
|
|
2786
|
+
}
|
|
2787
|
+
return path5.join(migrationsPath, latestSnapshot);
|
|
2788
|
+
} catch (error) {
|
|
2789
|
+
console.warn(`Error finding latest snapshot: ${error}`);
|
|
2790
|
+
return null;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
function applyMigrationOperations(snapshot, operations) {
|
|
2794
|
+
const updatedCollections = new Map(snapshot.collections);
|
|
2795
|
+
for (const collectionName of operations.collectionsToDelete) {
|
|
2796
|
+
updatedCollections.delete(collectionName);
|
|
2797
|
+
}
|
|
2798
|
+
for (const collection of operations.collectionsToCreate) {
|
|
2799
|
+
updatedCollections.set(collection.name, collection);
|
|
2800
|
+
}
|
|
2801
|
+
return {
|
|
2802
|
+
...snapshot,
|
|
2803
|
+
collections: updatedCollections
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
function loadSnapshotWithMigrations(config = {}) {
|
|
2807
|
+
const migrationsPath = config.migrationsPath;
|
|
2808
|
+
if (!migrationsPath) {
|
|
2809
|
+
return null;
|
|
2810
|
+
}
|
|
2811
|
+
if (fs5.existsSync(migrationsPath) && fs5.statSync(migrationsPath).isFile()) {
|
|
2812
|
+
try {
|
|
2813
|
+
const migrationContent = fs5.readFileSync(migrationsPath, "utf-8");
|
|
2814
|
+
return convertPocketBaseMigration(migrationContent);
|
|
2815
|
+
} catch (error) {
|
|
2816
|
+
console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
|
|
2817
|
+
return null;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
const latestSnapshotPath = findLatestSnapshot(migrationsPath);
|
|
2821
|
+
if (!latestSnapshotPath) {
|
|
2822
|
+
return null;
|
|
2823
|
+
}
|
|
2824
|
+
try {
|
|
2825
|
+
const migrationContent = fs5.readFileSync(latestSnapshotPath, "utf-8");
|
|
2826
|
+
let snapshot = convertPocketBaseMigration(migrationContent);
|
|
2827
|
+
const snapshotFilename = path5.basename(latestSnapshotPath);
|
|
2828
|
+
const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
|
|
2829
|
+
if (snapshotTimestamp) {
|
|
2830
|
+
const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
|
|
2831
|
+
for (const migrationFile of migrationFiles) {
|
|
2832
|
+
try {
|
|
2833
|
+
const migrationContent2 = fs5.readFileSync(migrationFile, "utf-8");
|
|
2834
|
+
const operations = parseMigrationOperations(migrationContent2);
|
|
2835
|
+
snapshot = applyMigrationOperations(snapshot, operations);
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
return snapshot;
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2431
2848
|
// src/migration/validation.ts
|
|
2432
2849
|
function detectCollectionDeletions(diff) {
|
|
2433
2850
|
const changes = [];
|
|
@@ -2596,8 +3013,8 @@ var DEFAULT_CONFIG5 = {
|
|
|
2596
3013
|
};
|
|
2597
3014
|
function findConfigFile(directory) {
|
|
2598
3015
|
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2599
|
-
const filePath =
|
|
2600
|
-
if (
|
|
3016
|
+
const filePath = path5.join(directory, fileName);
|
|
3017
|
+
if (fs5.existsSync(filePath)) {
|
|
2601
3018
|
return filePath;
|
|
2602
3019
|
}
|
|
2603
3020
|
}
|
|
@@ -2605,7 +3022,7 @@ function findConfigFile(directory) {
|
|
|
2605
3022
|
}
|
|
2606
3023
|
function loadJsonConfig(configPath) {
|
|
2607
3024
|
try {
|
|
2608
|
-
const content =
|
|
3025
|
+
const content = fs5.readFileSync(configPath, "utf-8");
|
|
2609
3026
|
return JSON.parse(content);
|
|
2610
3027
|
} catch (error) {
|
|
2611
3028
|
if (error instanceof SyntaxError) {
|
|
@@ -2634,10 +3051,10 @@ async function loadJsConfig(configPath) {
|
|
|
2634
3051
|
}
|
|
2635
3052
|
}
|
|
2636
3053
|
async function loadConfigFile(configPath) {
|
|
2637
|
-
if (!
|
|
3054
|
+
if (!fs5.existsSync(configPath)) {
|
|
2638
3055
|
return null;
|
|
2639
3056
|
}
|
|
2640
|
-
const ext =
|
|
3057
|
+
const ext = path5.extname(configPath).toLowerCase();
|
|
2641
3058
|
if (ext === ".json") {
|
|
2642
3059
|
return loadJsonConfig(configPath);
|
|
2643
3060
|
} else if (ext === ".js" || ext === ".mjs") {
|
|
@@ -2704,10 +3121,10 @@ function validateConfig(config, configPath) {
|
|
|
2704
3121
|
}
|
|
2705
3122
|
const cwd = process.cwd();
|
|
2706
3123
|
const possiblePaths = [
|
|
2707
|
-
|
|
2708
|
-
|
|
3124
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3125
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2709
3126
|
];
|
|
2710
|
-
const schemaDir = possiblePaths.find((p) =>
|
|
3127
|
+
const schemaDir = possiblePaths.find((p) => fs5.existsSync(p));
|
|
2711
3128
|
if (!schemaDir) {
|
|
2712
3129
|
throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
|
|
2713
3130
|
"schema.directory"
|
|
@@ -2719,15 +3136,15 @@ async function loadConfig(options = {}) {
|
|
|
2719
3136
|
let configFilePath;
|
|
2720
3137
|
const cwd = process.cwd();
|
|
2721
3138
|
if (options.config) {
|
|
2722
|
-
const explicitPath =
|
|
2723
|
-
if (!
|
|
3139
|
+
const explicitPath = path5.resolve(cwd, options.config);
|
|
3140
|
+
if (!fs5.existsSync(explicitPath)) {
|
|
2724
3141
|
throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
|
|
2725
3142
|
}
|
|
2726
3143
|
configFilePath = explicitPath;
|
|
2727
3144
|
} else {
|
|
2728
|
-
const searchDirs = [cwd,
|
|
3145
|
+
const searchDirs = [cwd, path5.join(cwd, "shared")];
|
|
2729
3146
|
for (const dir of searchDirs) {
|
|
2730
|
-
if (
|
|
3147
|
+
if (fs5.existsSync(dir)) {
|
|
2731
3148
|
const found = findConfigFile(dir);
|
|
2732
3149
|
if (found) {
|
|
2733
3150
|
configFilePath = found;
|
|
@@ -2756,18 +3173,18 @@ async function loadConfig(options = {}) {
|
|
|
2756
3173
|
function getSchemaDirectory(config) {
|
|
2757
3174
|
const cwd = process.cwd();
|
|
2758
3175
|
const possiblePaths = [
|
|
2759
|
-
|
|
2760
|
-
|
|
3176
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3177
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2761
3178
|
];
|
|
2762
|
-
return possiblePaths.find((p) =>
|
|
3179
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2763
3180
|
}
|
|
2764
3181
|
function getMigrationsDirectory(config) {
|
|
2765
3182
|
const cwd = process.cwd();
|
|
2766
3183
|
const possiblePaths = [
|
|
2767
|
-
|
|
2768
|
-
|
|
3184
|
+
path5.resolve(cwd, config.migrations.directory),
|
|
3185
|
+
path5.resolve(cwd, "shared", config.migrations.directory)
|
|
2769
3186
|
];
|
|
2770
|
-
return possiblePaths.find((p) =>
|
|
3187
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2771
3188
|
}
|
|
2772
3189
|
var currentVerbosity = "normal";
|
|
2773
3190
|
function setVerbosity(level) {
|
|
@@ -2975,10 +3392,16 @@ async function executeGenerate(options) {
|
|
|
2975
3392
|
const schemaDir = getSchemaDirectory(config);
|
|
2976
3393
|
const migrationsDir = getMigrationsDirectory(config);
|
|
2977
3394
|
logSection("\u{1F50D} Analyzing Schema");
|
|
2978
|
-
const
|
|
3395
|
+
const analyzerConfig = {
|
|
3396
|
+
schemaDir,
|
|
3397
|
+
excludePatterns: config.schema.exclude,
|
|
3398
|
+
useCompiledFiles: false
|
|
3399
|
+
// Use source files since we're in development/testing
|
|
3400
|
+
};
|
|
3401
|
+
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
2979
3402
|
logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
|
|
2980
3403
|
logInfo("Loading previous snapshot...");
|
|
2981
|
-
const previousSnapshot =
|
|
3404
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
2982
3405
|
migrationsPath: migrationsDir,
|
|
2983
3406
|
workspaceRoot: process.cwd()
|
|
2984
3407
|
});
|
|
@@ -3005,7 +3428,7 @@ async function executeGenerate(options) {
|
|
|
3005
3428
|
"Creating migration file...",
|
|
3006
3429
|
() => Promise.resolve(generate(diff, migrationsDir))
|
|
3007
3430
|
);
|
|
3008
|
-
logSuccess(`Migration file created: ${
|
|
3431
|
+
logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
|
|
3009
3432
|
logSection("\u2705 Next Steps");
|
|
3010
3433
|
console.log();
|
|
3011
3434
|
console.log(" 1. Review the generated migration file:");
|
|
@@ -3174,10 +3597,16 @@ async function executeStatus(options) {
|
|
|
3174
3597
|
const schemaDir = getSchemaDirectory(config);
|
|
3175
3598
|
const migrationsDir = getMigrationsDirectory(config);
|
|
3176
3599
|
logSection("\u{1F50D} Checking Migration Status");
|
|
3177
|
-
const
|
|
3600
|
+
const analyzerConfig = {
|
|
3601
|
+
schemaDir,
|
|
3602
|
+
excludePatterns: config.schema.exclude,
|
|
3603
|
+
useCompiledFiles: false
|
|
3604
|
+
// Use source files since we're in development/testing
|
|
3605
|
+
};
|
|
3606
|
+
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
3178
3607
|
logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
|
|
3179
3608
|
logInfo("Loading previous snapshot...");
|
|
3180
|
-
const previousSnapshot =
|
|
3609
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
3181
3610
|
migrationsPath: migrationsDir,
|
|
3182
3611
|
workspaceRoot: process.cwd()
|
|
3183
3612
|
});
|
|
@@ -3290,7 +3719,19 @@ Examples:
|
|
|
3290
3719
|
}
|
|
3291
3720
|
|
|
3292
3721
|
// src/cli/migrate.ts
|
|
3293
|
-
|
|
3722
|
+
function getVersion() {
|
|
3723
|
+
try {
|
|
3724
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
3725
|
+
const __dirname2 = dirname(__filename2);
|
|
3726
|
+
const packageJsonPath = join(__dirname2, "../../package.json");
|
|
3727
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
3728
|
+
return packageJson.version || "0.0.0";
|
|
3729
|
+
} catch {
|
|
3730
|
+
console.warn("Warning: Could not read version from package.json");
|
|
3731
|
+
return "0.0.0";
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
var VERSION = getVersion();
|
|
3294
3735
|
function displayBanner() {
|
|
3295
3736
|
console.log();
|
|
3296
3737
|
console.log(chalk.cyan.bold(" PocketBase Zod Migration Tool"));
|