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.
Files changed (69) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +329 -99
  3. package/dist/cli/index.cjs +577 -152
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +575 -150
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +595 -153
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +592 -151
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/cli/utils/index.cjs +1 -1
  12. package/dist/cli/utils/index.cjs.map +1 -1
  13. package/dist/cli/utils/index.js +1 -1
  14. package/dist/cli/utils/index.js.map +1 -1
  15. package/dist/index.cjs +688 -231
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +685 -230
  20. package/dist/index.js.map +1 -1
  21. package/dist/migration/analyzer.cjs +101 -28
  22. package/dist/migration/analyzer.cjs.map +1 -1
  23. package/dist/migration/analyzer.js +101 -28
  24. package/dist/migration/analyzer.js.map +1 -1
  25. package/dist/migration/diff.cjs +21 -3
  26. package/dist/migration/diff.cjs.map +1 -1
  27. package/dist/migration/diff.js +21 -3
  28. package/dist/migration/diff.js.map +1 -1
  29. package/dist/migration/generator.cjs +60 -25
  30. package/dist/migration/generator.cjs.map +1 -1
  31. package/dist/migration/generator.d.cts +9 -5
  32. package/dist/migration/generator.d.ts +9 -5
  33. package/dist/migration/generator.js +60 -25
  34. package/dist/migration/generator.js.map +1 -1
  35. package/dist/migration/index.cjs +614 -171
  36. package/dist/migration/index.cjs.map +1 -1
  37. package/dist/migration/index.d.cts +1 -1
  38. package/dist/migration/index.d.ts +1 -1
  39. package/dist/migration/index.js +613 -171
  40. package/dist/migration/index.js.map +1 -1
  41. package/dist/migration/snapshot.cjs +432 -117
  42. package/dist/migration/snapshot.cjs.map +1 -1
  43. package/dist/migration/snapshot.d.cts +34 -12
  44. package/dist/migration/snapshot.d.ts +34 -12
  45. package/dist/migration/snapshot.js +430 -116
  46. package/dist/migration/snapshot.js.map +1 -1
  47. package/dist/migration/utils/index.cjs +19 -17
  48. package/dist/migration/utils/index.cjs.map +1 -1
  49. package/dist/migration/utils/index.d.cts +3 -1
  50. package/dist/migration/utils/index.d.ts +3 -1
  51. package/dist/migration/utils/index.js +19 -17
  52. package/dist/migration/utils/index.js.map +1 -1
  53. package/dist/mutator.cjs +9 -11
  54. package/dist/mutator.cjs.map +1 -1
  55. package/dist/mutator.d.cts +5 -9
  56. package/dist/mutator.d.ts +5 -9
  57. package/dist/mutator.js +9 -11
  58. package/dist/mutator.js.map +1 -1
  59. package/dist/schema.cjs +54 -23
  60. package/dist/schema.cjs.map +1 -1
  61. package/dist/schema.d.cts +94 -12
  62. package/dist/schema.d.ts +94 -12
  63. package/dist/schema.js +54 -24
  64. package/dist/schema.js.map +1 -1
  65. package/dist/types.d.cts +1 -1
  66. package/dist/types.d.ts +1 -1
  67. package/dist/{user-jS1aYoeD.d.cts → user-_AM523hb.d.cts} +6 -6
  68. package/dist/{user-jS1aYoeD.d.ts → user-_AM523hb.d.ts} +6 -6
  69. package/package.json +2 -4
@@ -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 path4 from 'path';
5
- import * as fs4 from 'fs';
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, path6, operation, code, originalError) {
139
+ constructor(message, path7, operation, code, originalError) {
107
140
  super(message);
108
141
  this.name = "FileSystemError";
109
- this.path = path6;
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/schema/permission-templates.ts
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 (!isMultipleRelationField(fieldName, zodType)) {
832
- return void 0;
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 (unwrappedType instanceof z.ZodArray) {
845
- const arrayDef = unwrappedType._def;
846
- if (arrayDef.minLength) {
847
- return arrayDef.minLength.value;
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 void 0;
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 (path4.isAbsolute(config.schemaDir)) {
1083
+ if (path5.isAbsolute(config.schemaDir)) {
1049
1084
  return config.schemaDir;
1050
1085
  }
1051
- return path4.join(workspaceRoot, config.schemaDir);
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 (!fs4.existsSync(schemaDir)) {
1093
+ if (!fs5.existsSync(schemaDir)) {
1059
1094
  throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1060
1095
  }
1061
- const files = fs4.readdirSync(schemaDir);
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 path4.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
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
- if (!importPath.endsWith(".js")) {
1109
- importPath = `${importPath}.js`;
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://${path4.resolve(importPath)}`);
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 = path4.basename(filePath).replace(/\.(ts|js)$/, "");
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
- if (isRelationField(fieldName, zodType)) {
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
- importPath = filePath.replace(/\/src\//, "/dist/");
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
- if (currentRelation.collection !== previousRelation.collection) {
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: previousRelation.collection,
1466
- newValue: currentRelation.collection
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 (path4.isAbsolute(config.migrationDir)) {
1781
+ if (path5.isAbsolute(config.migrationDir)) {
1689
1782
  return config.migrationDir;
1690
1783
  }
1691
- return path4.join(workspaceRoot, config.migrationDir);
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 (!fs4.existsSync(migrationDir)) {
1842
+ if (!fs5.existsSync(migrationDir)) {
1750
1843
  try {
1751
- fs4.mkdirSync(migrationDir, { recursive: true });
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 = path4.join(migrationDir, filename);
1773
- fs4.writeFileSync(filePath, content, "utf-8");
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 = path4.join(migrationDir, filename);
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 collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
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 collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
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 collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
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
- return lines.join("\n");
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
- return lines.join("\n");
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
- workspaceRoot: process.cwd()});
2291
- function findLatestSnapshot(migrationsPath) {
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
- function loadSnapshotIfExists(config = {}) {
2315
- const migrationsPath = config.migrationsPath;
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
- if (fs4.existsSync(migrationsPath) && fs4.statSync(migrationsPath).isFile()) {
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
- if (pbField.options) {
2358
- field.options = pbField.options;
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: pbField.options?.collectionId || "",
2363
- cascadeDelete: pbField.options?.cascadeDelete || false,
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 = path4.join(directory, fileName);
2600
- if (fs4.existsSync(filePath)) {
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 = fs4.readFileSync(configPath, "utf-8");
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 (!fs4.existsSync(configPath)) {
3054
+ if (!fs5.existsSync(configPath)) {
2638
3055
  return null;
2639
3056
  }
2640
- const ext = path4.extname(configPath).toLowerCase();
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
- path4.resolve(cwd, config.schema.directory),
2708
- path4.resolve(cwd, "shared", config.schema.directory)
3124
+ path5.resolve(cwd, config.schema.directory),
3125
+ path5.resolve(cwd, "shared", config.schema.directory)
2709
3126
  ];
2710
- const schemaDir = possiblePaths.find((p) => fs4.existsSync(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 = path4.resolve(cwd, options.config);
2723
- if (!fs4.existsSync(explicitPath)) {
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, path4.join(cwd, "shared")];
3145
+ const searchDirs = [cwd, path5.join(cwd, "shared")];
2729
3146
  for (const dir of searchDirs) {
2730
- if (fs4.existsSync(dir)) {
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
- path4.resolve(cwd, config.schema.directory),
2760
- path4.resolve(cwd, "shared", config.schema.directory)
3176
+ path5.resolve(cwd, config.schema.directory),
3177
+ path5.resolve(cwd, "shared", config.schema.directory)
2761
3178
  ];
2762
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
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
- path4.resolve(cwd, config.migrations.directory),
2768
- path4.resolve(cwd, "shared", config.migrations.directory)
3184
+ path5.resolve(cwd, config.migrations.directory),
3185
+ path5.resolve(cwd, "shared", config.migrations.directory)
2769
3186
  ];
2770
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
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 currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(schemaDir));
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 = loadSnapshotIfExists({
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: ${path4.basename(migrationPath)}`);
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 currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(schemaDir));
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 = loadSnapshotIfExists({
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
- var VERSION = "0.1.0";
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"));