pocketbase-zod-schema 0.1.3 → 0.2.0

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 (57) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +233 -98
  3. package/dist/cli/index.cjs +449 -108
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +447 -106
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +452 -111
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +447 -106
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/index.cjs +593 -175
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +4 -4
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +583 -172
  16. package/dist/index.js.map +1 -1
  17. package/dist/migration/analyzer.cjs +44 -6
  18. package/dist/migration/analyzer.cjs.map +1 -1
  19. package/dist/migration/analyzer.d.cts +11 -1
  20. package/dist/migration/analyzer.d.ts +11 -1
  21. package/dist/migration/analyzer.js +44 -7
  22. package/dist/migration/analyzer.js.map +1 -1
  23. package/dist/migration/diff.cjs +21 -3
  24. package/dist/migration/diff.cjs.map +1 -1
  25. package/dist/migration/diff.js +21 -3
  26. package/dist/migration/diff.js.map +1 -1
  27. package/dist/migration/index.cjs +500 -129
  28. package/dist/migration/index.cjs.map +1 -1
  29. package/dist/migration/index.d.cts +1 -1
  30. package/dist/migration/index.d.ts +1 -1
  31. package/dist/migration/index.js +499 -129
  32. package/dist/migration/index.js.map +1 -1
  33. package/dist/migration/snapshot.cjs +432 -118
  34. package/dist/migration/snapshot.cjs.map +1 -1
  35. package/dist/migration/snapshot.d.cts +34 -12
  36. package/dist/migration/snapshot.d.ts +34 -12
  37. package/dist/migration/snapshot.js +430 -117
  38. package/dist/migration/snapshot.js.map +1 -1
  39. package/dist/mutator.cjs +20 -21
  40. package/dist/mutator.cjs.map +1 -1
  41. package/dist/mutator.d.cts +4 -4
  42. package/dist/mutator.d.ts +4 -4
  43. package/dist/mutator.js +20 -21
  44. package/dist/mutator.js.map +1 -1
  45. package/dist/schema.cjs +69 -10
  46. package/dist/schema.cjs.map +1 -1
  47. package/dist/schema.d.cts +98 -8
  48. package/dist/schema.d.ts +98 -8
  49. package/dist/schema.js +62 -9
  50. package/dist/schema.js.map +1 -1
  51. package/dist/types.d.cts +5 -2
  52. package/dist/types.d.ts +5 -2
  53. package/dist/user-DTJQIj4K.d.cts +149 -0
  54. package/dist/user-DTJQIj4K.d.ts +149 -0
  55. package/package.json +3 -3
  56. package/dist/user-C39DQ40N.d.cts +0 -53
  57. package/dist/user-C39DQ40N.d.ts +0 -53
package/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import * as path4 from 'path';
2
- import * as fs4 from 'fs';
1
+ import * as path5 from 'path';
2
+ import * as fs5 from 'fs';
3
3
  import { z } from 'zod';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
@@ -132,10 +132,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
132
132
  operation;
133
133
  code;
134
134
  originalError;
135
- constructor(message, path6, operation, code, originalError) {
135
+ constructor(message, path7, operation, code, originalError) {
136
136
  super(message);
137
137
  this.name = "FileSystemError";
138
- this.path = path6;
138
+ this.path = path7;
139
139
  this.operation = operation;
140
140
  this.code = code;
141
141
  this.originalError = originalError;
@@ -1049,6 +1049,16 @@ function isFieldRequired(zodType) {
1049
1049
  }
1050
1050
 
1051
1051
  // src/migration/analyzer.ts
1052
+ var tsxLoaderRegistered = false;
1053
+ async function ensureTsxLoader() {
1054
+ if (tsxLoaderRegistered) return;
1055
+ try {
1056
+ await import('tsx/esm');
1057
+ tsxLoaderRegistered = true;
1058
+ } catch {
1059
+ tsxLoaderRegistered = false;
1060
+ }
1061
+ }
1052
1062
  var DEFAULT_CONFIG = {
1053
1063
  workspaceRoot: process.cwd(),
1054
1064
  excludePatterns: [
@@ -1076,20 +1086,20 @@ function mergeConfig(config) {
1076
1086
  }
1077
1087
  function resolveSchemaDir(config) {
1078
1088
  const workspaceRoot = config.workspaceRoot || process.cwd();
1079
- if (path4.isAbsolute(config.schemaDir)) {
1089
+ if (path5.isAbsolute(config.schemaDir)) {
1080
1090
  return config.schemaDir;
1081
1091
  }
1082
- return path4.join(workspaceRoot, config.schemaDir);
1092
+ return path5.join(workspaceRoot, config.schemaDir);
1083
1093
  }
1084
1094
  function discoverSchemaFiles(config) {
1085
1095
  const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
1086
1096
  const mergedConfig = mergeConfig(normalizedConfig);
1087
1097
  const schemaDir = resolveSchemaDir(normalizedConfig);
1088
1098
  try {
1089
- if (!fs4.existsSync(schemaDir)) {
1099
+ if (!fs5.existsSync(schemaDir)) {
1090
1100
  throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1091
1101
  }
1092
- const files = fs4.readdirSync(schemaDir);
1102
+ const files = fs5.readdirSync(schemaDir);
1093
1103
  const schemaFiles = files.filter((file) => {
1094
1104
  const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
1095
1105
  if (!hasValidExtension) return false;
@@ -1105,7 +1115,7 @@ function discoverSchemaFiles(config) {
1105
1115
  });
1106
1116
  return schemaFiles.map((file) => {
1107
1117
  const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
1108
- return path4.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1118
+ return path5.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1109
1119
  });
1110
1120
  } catch (error) {
1111
1121
  if (error instanceof FileSystemError) {
@@ -1139,40 +1149,66 @@ async function importSchemaModule(filePath, config) {
1139
1149
  let resolvedPath = null;
1140
1150
  const jsPath = `${importPath}.js`;
1141
1151
  const tsPath = `${importPath}.ts`;
1142
- if (fs4.existsSync(jsPath)) {
1152
+ if (fs5.existsSync(jsPath)) {
1143
1153
  resolvedPath = jsPath;
1144
- } else if (fs4.existsSync(tsPath)) {
1154
+ } else if (fs5.existsSync(tsPath)) {
1145
1155
  resolvedPath = tsPath;
1146
1156
  } else {
1147
1157
  resolvedPath = jsPath;
1148
1158
  }
1149
- const fileUrl = new URL(`file://${path4.resolve(resolvedPath)}`);
1159
+ if (resolvedPath.endsWith(".ts")) {
1160
+ await ensureTsxLoader();
1161
+ if (!tsxLoaderRegistered) {
1162
+ throw new SchemaParsingError(
1163
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1164
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1165
+ Alternatively, compile your schema files to JavaScript first.`,
1166
+ filePath
1167
+ );
1168
+ }
1169
+ }
1170
+ const fileUrl = new URL(`file://${path5.resolve(resolvedPath)}`);
1150
1171
  const module = await import(fileUrl.href);
1151
1172
  return module;
1152
1173
  } catch (error) {
1153
1174
  const tsPath = `${filePath}.ts`;
1154
- const isTypeScriptFile = fs4.existsSync(tsPath);
1175
+ const isTypeScriptFile = fs5.existsSync(tsPath);
1176
+ if (isTypeScriptFile && error instanceof SchemaParsingError) {
1177
+ throw error;
1178
+ }
1155
1179
  if (isTypeScriptFile) {
1156
1180
  throw new SchemaParsingError(
1157
- `Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
1158
- Please either:
1159
- 1. Compile your schema files to JavaScript first, or
1160
- 2. Use tsx to run the migration tool (e.g., "npx tsx package/dist/cli/migrate.js status" or "tsx package/dist/cli/migrate.js status")`,
1181
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1182
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1183
+ Alternatively, compile your schema files to JavaScript first.`,
1161
1184
  filePath,
1162
1185
  error
1163
1186
  );
1164
1187
  }
1165
1188
  throw new SchemaParsingError(
1166
- `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1189
+ `Failed to import schema module. Make sure the schema files exist and are valid.`,
1167
1190
  filePath,
1168
1191
  error
1169
1192
  );
1170
1193
  }
1171
1194
  }
1172
1195
  function getCollectionNameFromFile(filePath) {
1173
- const filename = path4.basename(filePath).replace(/\.(ts|js)$/, "");
1196
+ const filename = path5.basename(filePath).replace(/\.(ts|js)$/, "");
1174
1197
  return toCollectionName(filename);
1175
1198
  }
1199
+ function extractCollectionNameFromSchema(zodSchema) {
1200
+ if (!zodSchema.description) {
1201
+ return null;
1202
+ }
1203
+ try {
1204
+ const metadata = JSON.parse(zodSchema.description);
1205
+ if (metadata.collectionName && typeof metadata.collectionName === "string") {
1206
+ return metadata.collectionName;
1207
+ }
1208
+ } catch {
1209
+ }
1210
+ return null;
1211
+ }
1176
1212
  function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
1177
1213
  const result = {};
1178
1214
  for (const [key, value] of Object.entries(module)) {
@@ -1335,7 +1371,7 @@ async function buildSchemaDefinition(config) {
1335
1371
  importPath = normalizedConfig.pathTransformer(filePath);
1336
1372
  } else if (mergedConfig.useCompiledFiles) {
1337
1373
  const distPath = filePath.replace(/\/src\//, "/dist/");
1338
- if (fs4.existsSync(`${distPath}.js`) || fs4.existsSync(`${distPath}.mjs`)) {
1374
+ if (fs5.existsSync(`${distPath}.js`) || fs5.existsSync(`${distPath}.mjs`)) {
1339
1375
  importPath = distPath;
1340
1376
  } else {
1341
1377
  importPath = filePath;
@@ -1348,7 +1384,8 @@ async function buildSchemaDefinition(config) {
1348
1384
  console.warn(`No valid schema found in ${filePath}, skipping...`);
1349
1385
  continue;
1350
1386
  }
1351
- const collectionName = getCollectionNameFromFile(filePath);
1387
+ const collectionNameFromSchema = extractCollectionNameFromSchema(zodSchema);
1388
+ const collectionName = collectionNameFromSchema ?? getCollectionNameFromFile(filePath);
1352
1389
  const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
1353
1390
  collections.set(collectionName, collectionSchema);
1354
1391
  } catch (error) {
@@ -1512,6 +1549,9 @@ function compareFieldOptions(currentField, previousField) {
1512
1549
  for (const key of allKeys) {
1513
1550
  const currentValue = currentOptions[key];
1514
1551
  const previousValue = previousOptions[key];
1552
+ if (currentValue === void 0 && previousValue === void 0) {
1553
+ continue;
1554
+ }
1515
1555
  if (!areValuesEqual(currentValue, previousValue)) {
1516
1556
  changes.push({
1517
1557
  property: `options.${key}`,
@@ -1532,11 +1572,26 @@ function compareRelationConfigurations(currentField, previousField) {
1532
1572
  if (!currentRelation || !previousRelation) {
1533
1573
  return changes;
1534
1574
  }
1535
- if (currentRelation.collection !== previousRelation.collection) {
1575
+ const normalizeCollection = (collection) => {
1576
+ if (!collection) return collection;
1577
+ if (collection === "_pb_users_auth_") {
1578
+ return "Users";
1579
+ }
1580
+ const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1581
+ if (nameMatch) {
1582
+ return nameMatch[1];
1583
+ }
1584
+ return collection;
1585
+ };
1586
+ const normalizedCurrent = normalizeCollection(currentRelation.collection);
1587
+ const normalizedPrevious = normalizeCollection(previousRelation.collection);
1588
+ if (normalizedCurrent !== normalizedPrevious) {
1536
1589
  changes.push({
1537
1590
  property: "relation.collection",
1538
- oldValue: previousRelation.collection,
1539
- newValue: currentRelation.collection
1591
+ oldValue: normalizedPrevious,
1592
+ // Use normalized value for clarity
1593
+ newValue: normalizedCurrent
1594
+ // Use normalized value for clarity
1540
1595
  });
1541
1596
  }
1542
1597
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -1756,10 +1811,10 @@ function mergeConfig3(config) {
1756
1811
  }
1757
1812
  function resolveMigrationDir(config) {
1758
1813
  const workspaceRoot = config.workspaceRoot || process.cwd();
1759
- if (path4.isAbsolute(config.migrationDir)) {
1814
+ if (path5.isAbsolute(config.migrationDir)) {
1760
1815
  return config.migrationDir;
1761
1816
  }
1762
- return path4.join(workspaceRoot, config.migrationDir);
1817
+ return path5.join(workspaceRoot, config.migrationDir);
1763
1818
  }
1764
1819
  function generateTimestamp(config) {
1765
1820
  if (config?.timestampGenerator) {
@@ -1817,9 +1872,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
1817
1872
  }
1818
1873
  function writeMigrationFile(migrationDir, filename, content) {
1819
1874
  try {
1820
- if (!fs4.existsSync(migrationDir)) {
1875
+ if (!fs5.existsSync(migrationDir)) {
1821
1876
  try {
1822
- fs4.mkdirSync(migrationDir, { recursive: true });
1877
+ fs5.mkdirSync(migrationDir, { recursive: true });
1823
1878
  } catch (error) {
1824
1879
  const fsError = error;
1825
1880
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
@@ -1840,15 +1895,15 @@ function writeMigrationFile(migrationDir, filename, content) {
1840
1895
  );
1841
1896
  }
1842
1897
  }
1843
- const filePath = path4.join(migrationDir, filename);
1844
- fs4.writeFileSync(filePath, content, "utf-8");
1898
+ const filePath = path5.join(migrationDir, filename);
1899
+ fs5.writeFileSync(filePath, content, "utf-8");
1845
1900
  return filePath;
1846
1901
  } catch (error) {
1847
1902
  if (error instanceof FileSystemError) {
1848
1903
  throw error;
1849
1904
  }
1850
1905
  const fsError = error;
1851
- const filePath = path4.join(migrationDir, filename);
1906
+ const filePath = path5.join(migrationDir, filename);
1852
1907
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
1853
1908
  throw new FileSystemError(
1854
1909
  `Permission denied writing migration file. Check file and directory permissions.`,
@@ -2393,57 +2448,18 @@ function generate(diff, config) {
2393
2448
  );
2394
2449
  }
2395
2450
  }
2451
+
2452
+ // src/migration/pocketbase-converter.ts
2396
2453
  var SNAPSHOT_VERSION = "1.0.0";
2397
- ({
2398
- workspaceRoot: process.cwd()});
2399
- function findLatestSnapshot(migrationsPath) {
2400
- try {
2401
- if (!fs4.existsSync(migrationsPath)) {
2402
- return null;
2403
- }
2404
- const files = fs4.readdirSync(migrationsPath);
2405
- const snapshotFiles = files.filter(
2406
- (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
2407
- );
2408
- if (snapshotFiles.length === 0) {
2409
- return null;
2410
- }
2411
- snapshotFiles.sort().reverse();
2412
- const latestSnapshot = snapshotFiles[0];
2413
- if (!latestSnapshot) {
2414
- return null;
2415
- }
2416
- return path4.join(migrationsPath, latestSnapshot);
2417
- } catch (error) {
2418
- console.warn(`Error finding latest snapshot: ${error}`);
2419
- return null;
2454
+ function resolveCollectionIdToName(collectionId) {
2455
+ if (collectionId === "_pb_users_auth_") {
2456
+ return "Users";
2420
2457
  }
2421
- }
2422
- function loadSnapshotIfExists(config = {}) {
2423
- const migrationsPath = config.migrationsPath;
2424
- if (!migrationsPath) {
2425
- return null;
2426
- }
2427
- if (fs4.existsSync(migrationsPath) && fs4.statSync(migrationsPath).isFile()) {
2428
- try {
2429
- const migrationContent = fs4.readFileSync(migrationsPath, "utf-8");
2430
- return convertPocketBaseMigration(migrationContent);
2431
- } catch (error) {
2432
- console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2433
- return null;
2434
- }
2458
+ const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2459
+ if (nameMatch) {
2460
+ return nameMatch[1];
2435
2461
  }
2436
- const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2437
- if (latestSnapshotPath) {
2438
- try {
2439
- const migrationContent = fs4.readFileSync(latestSnapshotPath, "utf-8");
2440
- return convertPocketBaseMigration(migrationContent);
2441
- } catch (error) {
2442
- console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2443
- return null;
2444
- }
2445
- }
2446
- return null;
2462
+ return collectionId;
2447
2463
  }
2448
2464
  function convertPocketBaseCollection(pbCollection) {
2449
2465
  const fields = [];
@@ -2462,17 +2478,28 @@ function convertPocketBaseCollection(pbCollection) {
2462
2478
  type: pbField.type,
2463
2479
  required: pbField.required || false
2464
2480
  };
2465
- if (pbField.options) {
2466
- field.options = pbField.options;
2481
+ field.options = pbField.options ? { ...pbField.options } : {};
2482
+ if (pbField.type === "select") {
2483
+ if (pbField.values && Array.isArray(pbField.values)) {
2484
+ field.options.values = pbField.values;
2485
+ } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
2486
+ field.options.values = pbField.options.values;
2487
+ }
2467
2488
  }
2468
2489
  if (pbField.type === "relation") {
2490
+ const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
2491
+ const collectionName = resolveCollectionIdToName(collectionId);
2469
2492
  field.relation = {
2470
- collection: pbField.options?.collectionId || "",
2471
- cascadeDelete: pbField.options?.cascadeDelete || false,
2472
- maxSelect: pbField.options?.maxSelect,
2473
- minSelect: pbField.options?.minSelect
2493
+ collection: collectionName,
2494
+ cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
2495
+ maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
2496
+ minSelect: pbField.minSelect ?? pbField.options?.minSelect
2474
2497
  };
2475
2498
  }
2499
+ const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
2500
+ if (Object.keys(field.options).length === 0) {
2501
+ delete field.options;
2502
+ } else if (pbField.type === "select" && hasOnlyValues) ;
2476
2503
  fields.push(field);
2477
2504
  }
2478
2505
  }
@@ -2537,6 +2564,320 @@ function convertPocketBaseMigration(migrationContent) {
2537
2564
  }
2538
2565
  }
2539
2566
 
2567
+ // src/migration/migration-parser.ts
2568
+ function extractTimestampFromFilename(filename) {
2569
+ const match = filename.match(/^(\d+)_/);
2570
+ if (match) {
2571
+ return parseInt(match[1], 10);
2572
+ }
2573
+ return null;
2574
+ }
2575
+ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
2576
+ try {
2577
+ if (!fs5.existsSync(migrationsPath)) {
2578
+ return [];
2579
+ }
2580
+ const files = fs5.readdirSync(migrationsPath);
2581
+ const migrationFiles = [];
2582
+ for (const file of files) {
2583
+ if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
2584
+ continue;
2585
+ }
2586
+ if (!file.endsWith(".js")) {
2587
+ continue;
2588
+ }
2589
+ const timestamp = extractTimestampFromFilename(file);
2590
+ if (timestamp && timestamp > snapshotTimestamp) {
2591
+ migrationFiles.push({
2592
+ path: path5.join(migrationsPath, file),
2593
+ timestamp
2594
+ });
2595
+ }
2596
+ }
2597
+ migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
2598
+ return migrationFiles.map((f) => f.path);
2599
+ } catch (error) {
2600
+ console.warn(`Error finding migrations after snapshot: ${error}`);
2601
+ return [];
2602
+ }
2603
+ }
2604
+ function parseMigrationOperationsFromContent(content) {
2605
+ const collectionsToCreate = [];
2606
+ const collectionsToDelete = [];
2607
+ try {
2608
+ let searchIndex = 0;
2609
+ while (true) {
2610
+ const collectionStart = content.indexOf("new Collection(", searchIndex);
2611
+ if (collectionStart === -1) {
2612
+ break;
2613
+ }
2614
+ const openParen = collectionStart + "new Collection(".length;
2615
+ let braceCount = 0;
2616
+ let parenCount = 1;
2617
+ let inString = false;
2618
+ let stringChar = null;
2619
+ let i = openParen;
2620
+ while (i < content.length && /\s/.test(content[i])) {
2621
+ i++;
2622
+ }
2623
+ if (content[i] !== "{") {
2624
+ searchIndex = i + 1;
2625
+ continue;
2626
+ }
2627
+ const objectStart = i;
2628
+ braceCount = 1;
2629
+ i++;
2630
+ while (i < content.length && (braceCount > 0 || parenCount > 0)) {
2631
+ const char = content[i];
2632
+ const prevChar = i > 0 ? content[i - 1] : "";
2633
+ if (!inString && (char === '"' || char === "'")) {
2634
+ inString = true;
2635
+ stringChar = char;
2636
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2637
+ inString = false;
2638
+ stringChar = null;
2639
+ }
2640
+ if (!inString) {
2641
+ if (char === "{") braceCount++;
2642
+ if (char === "}") braceCount--;
2643
+ if (char === "(") parenCount++;
2644
+ if (char === ")") parenCount--;
2645
+ }
2646
+ i++;
2647
+ }
2648
+ if (braceCount === 0 && parenCount === 0) {
2649
+ const objectContent = content.substring(objectStart, i - 1);
2650
+ try {
2651
+ const collectionObj = new Function(`return ${objectContent}`)();
2652
+ if (collectionObj && collectionObj.name) {
2653
+ const schema = convertPocketBaseCollection(collectionObj);
2654
+ collectionsToCreate.push(schema);
2655
+ }
2656
+ } catch (error) {
2657
+ console.warn(`Failed to parse collection definition: ${error}`);
2658
+ }
2659
+ }
2660
+ searchIndex = i;
2661
+ }
2662
+ const deleteMatches = content.matchAll(
2663
+ /app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
2664
+ );
2665
+ for (const match of deleteMatches) {
2666
+ if (match[1]) {
2667
+ collectionsToDelete.push(match[1]);
2668
+ } else {
2669
+ const varNameMatch = match[0].match(/collection_(\w+)/);
2670
+ if (varNameMatch) {
2671
+ const varName = `collection_${varNameMatch[1]}`;
2672
+ const deleteIndex = content.indexOf(match[0]);
2673
+ const beforeDelete = content.substring(0, deleteIndex);
2674
+ const varDefMatch = beforeDelete.match(
2675
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
2676
+ );
2677
+ if (varDefMatch && varDefMatch.length > 0) {
2678
+ const collectionDefMatch = beforeDelete.match(
2679
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
2680
+ );
2681
+ if (collectionDefMatch) {
2682
+ try {
2683
+ const collectionDefStr = collectionDefMatch[1];
2684
+ const collectionObj = new Function(`return ${collectionDefStr}`)();
2685
+ if (collectionObj && collectionObj.name) {
2686
+ collectionsToDelete.push(collectionObj.name);
2687
+ }
2688
+ } catch {
2689
+ }
2690
+ }
2691
+ }
2692
+ }
2693
+ }
2694
+ }
2695
+ const findAndDeleteMatches = content.matchAll(
2696
+ /app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
2697
+ );
2698
+ for (const match of findAndDeleteMatches) {
2699
+ collectionsToDelete.push(match[1]);
2700
+ }
2701
+ } catch (error) {
2702
+ console.warn(`Failed to parse migration operations from content: ${error}`);
2703
+ }
2704
+ return { collectionsToCreate, collectionsToDelete };
2705
+ }
2706
+ function parseMigrationOperations(migrationContent) {
2707
+ try {
2708
+ const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
2709
+ if (!migrateMatch) {
2710
+ return parseMigrationOperationsFromContent(migrationContent);
2711
+ }
2712
+ const startIndex = migrateMatch.index + migrateMatch[0].length;
2713
+ let i = startIndex;
2714
+ let parenCount = 0;
2715
+ let foundFirstParen = false;
2716
+ while (i < migrationContent.length) {
2717
+ const char = migrationContent[i];
2718
+ if (char === "(") {
2719
+ parenCount++;
2720
+ foundFirstParen = true;
2721
+ i++;
2722
+ break;
2723
+ }
2724
+ i++;
2725
+ }
2726
+ if (!foundFirstParen) {
2727
+ return parseMigrationOperationsFromContent(migrationContent);
2728
+ }
2729
+ let inString = false;
2730
+ let stringChar = null;
2731
+ let foundBrace = false;
2732
+ let braceStart = -1;
2733
+ while (i < migrationContent.length && !foundBrace) {
2734
+ const char = migrationContent[i];
2735
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2736
+ if (!inString && (char === '"' || char === "'")) {
2737
+ inString = true;
2738
+ stringChar = char;
2739
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2740
+ inString = false;
2741
+ stringChar = null;
2742
+ }
2743
+ if (!inString) {
2744
+ if (char === "(") parenCount++;
2745
+ if (char === ")") {
2746
+ parenCount--;
2747
+ if (parenCount === 0) {
2748
+ i++;
2749
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2750
+ i++;
2751
+ }
2752
+ if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
2753
+ i += 2;
2754
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2755
+ i++;
2756
+ }
2757
+ if (i < migrationContent.length && migrationContent[i] === "{") {
2758
+ foundBrace = true;
2759
+ braceStart = i + 1;
2760
+ break;
2761
+ }
2762
+ }
2763
+ }
2764
+ }
2765
+ }
2766
+ i++;
2767
+ }
2768
+ if (!foundBrace || braceStart === -1) {
2769
+ return parseMigrationOperationsFromContent(migrationContent);
2770
+ }
2771
+ let braceCount = 1;
2772
+ i = braceStart;
2773
+ inString = false;
2774
+ stringChar = null;
2775
+ while (i < migrationContent.length && braceCount > 0) {
2776
+ const char = migrationContent[i];
2777
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2778
+ if (!inString && (char === '"' || char === "'")) {
2779
+ inString = true;
2780
+ stringChar = char;
2781
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2782
+ inString = false;
2783
+ stringChar = null;
2784
+ }
2785
+ if (!inString) {
2786
+ if (char === "{") braceCount++;
2787
+ if (char === "}") braceCount--;
2788
+ }
2789
+ i++;
2790
+ }
2791
+ if (braceCount === 0) {
2792
+ const upMigrationContent = migrationContent.substring(braceStart, i - 1);
2793
+ return parseMigrationOperationsFromContent(upMigrationContent);
2794
+ }
2795
+ return parseMigrationOperationsFromContent(migrationContent);
2796
+ } catch (error) {
2797
+ console.warn(`Failed to parse migration operations: ${error}`);
2798
+ return { collectionsToCreate: [], collectionsToDelete: [] };
2799
+ }
2800
+ }
2801
+ ({
2802
+ workspaceRoot: process.cwd()});
2803
+ function findLatestSnapshot(migrationsPath) {
2804
+ try {
2805
+ if (!fs5.existsSync(migrationsPath)) {
2806
+ return null;
2807
+ }
2808
+ const files = fs5.readdirSync(migrationsPath);
2809
+ const snapshotFiles = files.filter(
2810
+ (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
2811
+ );
2812
+ if (snapshotFiles.length === 0) {
2813
+ return null;
2814
+ }
2815
+ snapshotFiles.sort().reverse();
2816
+ const latestSnapshot = snapshotFiles[0];
2817
+ if (!latestSnapshot) {
2818
+ return null;
2819
+ }
2820
+ return path5.join(migrationsPath, latestSnapshot);
2821
+ } catch (error) {
2822
+ console.warn(`Error finding latest snapshot: ${error}`);
2823
+ return null;
2824
+ }
2825
+ }
2826
+ function applyMigrationOperations(snapshot, operations) {
2827
+ const updatedCollections = new Map(snapshot.collections);
2828
+ for (const collectionName of operations.collectionsToDelete) {
2829
+ updatedCollections.delete(collectionName);
2830
+ }
2831
+ for (const collection of operations.collectionsToCreate) {
2832
+ updatedCollections.set(collection.name, collection);
2833
+ }
2834
+ return {
2835
+ ...snapshot,
2836
+ collections: updatedCollections
2837
+ };
2838
+ }
2839
+ function loadSnapshotWithMigrations(config = {}) {
2840
+ const migrationsPath = config.migrationsPath;
2841
+ if (!migrationsPath) {
2842
+ return null;
2843
+ }
2844
+ if (fs5.existsSync(migrationsPath) && fs5.statSync(migrationsPath).isFile()) {
2845
+ try {
2846
+ const migrationContent = fs5.readFileSync(migrationsPath, "utf-8");
2847
+ return convertPocketBaseMigration(migrationContent);
2848
+ } catch (error) {
2849
+ console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2850
+ return null;
2851
+ }
2852
+ }
2853
+ const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2854
+ if (!latestSnapshotPath) {
2855
+ return null;
2856
+ }
2857
+ try {
2858
+ const migrationContent = fs5.readFileSync(latestSnapshotPath, "utf-8");
2859
+ let snapshot = convertPocketBaseMigration(migrationContent);
2860
+ const snapshotFilename = path5.basename(latestSnapshotPath);
2861
+ const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
2862
+ if (snapshotTimestamp) {
2863
+ const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
2864
+ for (const migrationFile of migrationFiles) {
2865
+ try {
2866
+ const migrationContent2 = fs5.readFileSync(migrationFile, "utf-8");
2867
+ const operations = parseMigrationOperations(migrationContent2);
2868
+ snapshot = applyMigrationOperations(snapshot, operations);
2869
+ } catch (error) {
2870
+ console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
2871
+ }
2872
+ }
2873
+ }
2874
+ return snapshot;
2875
+ } catch (error) {
2876
+ console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2877
+ return null;
2878
+ }
2879
+ }
2880
+
2540
2881
  // src/migration/validation.ts
2541
2882
  function detectCollectionDeletions(diff) {
2542
2883
  const changes = [];
@@ -2705,8 +3046,8 @@ var DEFAULT_CONFIG5 = {
2705
3046
  };
2706
3047
  function findConfigFile(directory) {
2707
3048
  for (const fileName of CONFIG_FILE_NAMES) {
2708
- const filePath = path4.join(directory, fileName);
2709
- if (fs4.existsSync(filePath)) {
3049
+ const filePath = path5.join(directory, fileName);
3050
+ if (fs5.existsSync(filePath)) {
2710
3051
  return filePath;
2711
3052
  }
2712
3053
  }
@@ -2714,7 +3055,7 @@ function findConfigFile(directory) {
2714
3055
  }
2715
3056
  function loadJsonConfig(configPath) {
2716
3057
  try {
2717
- const content = fs4.readFileSync(configPath, "utf-8");
3058
+ const content = fs5.readFileSync(configPath, "utf-8");
2718
3059
  return JSON.parse(content);
2719
3060
  } catch (error) {
2720
3061
  if (error instanceof SyntaxError) {
@@ -2743,10 +3084,10 @@ async function loadJsConfig(configPath) {
2743
3084
  }
2744
3085
  }
2745
3086
  async function loadConfigFile(configPath) {
2746
- if (!fs4.existsSync(configPath)) {
3087
+ if (!fs5.existsSync(configPath)) {
2747
3088
  return null;
2748
3089
  }
2749
- const ext = path4.extname(configPath).toLowerCase();
3090
+ const ext = path5.extname(configPath).toLowerCase();
2750
3091
  if (ext === ".json") {
2751
3092
  return loadJsonConfig(configPath);
2752
3093
  } else if (ext === ".js" || ext === ".mjs") {
@@ -2813,10 +3154,10 @@ function validateConfig(config, configPath) {
2813
3154
  }
2814
3155
  const cwd = process.cwd();
2815
3156
  const possiblePaths = [
2816
- path4.resolve(cwd, config.schema.directory),
2817
- path4.resolve(cwd, "shared", config.schema.directory)
3157
+ path5.resolve(cwd, config.schema.directory),
3158
+ path5.resolve(cwd, "shared", config.schema.directory)
2818
3159
  ];
2819
- const schemaDir = possiblePaths.find((p) => fs4.existsSync(p));
3160
+ const schemaDir = possiblePaths.find((p) => fs5.existsSync(p));
2820
3161
  if (!schemaDir) {
2821
3162
  throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
2822
3163
  "schema.directory"
@@ -2828,15 +3169,15 @@ async function loadConfig(options = {}) {
2828
3169
  let configFilePath;
2829
3170
  const cwd = process.cwd();
2830
3171
  if (options.config) {
2831
- const explicitPath = path4.resolve(cwd, options.config);
2832
- if (!fs4.existsSync(explicitPath)) {
3172
+ const explicitPath = path5.resolve(cwd, options.config);
3173
+ if (!fs5.existsSync(explicitPath)) {
2833
3174
  throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
2834
3175
  }
2835
3176
  configFilePath = explicitPath;
2836
3177
  } else {
2837
- const searchDirs = [cwd, path4.join(cwd, "shared")];
3178
+ const searchDirs = [cwd, path5.join(cwd, "shared")];
2838
3179
  for (const dir of searchDirs) {
2839
- if (fs4.existsSync(dir)) {
3180
+ if (fs5.existsSync(dir)) {
2840
3181
  const found = findConfigFile(dir);
2841
3182
  if (found) {
2842
3183
  configFilePath = found;
@@ -2865,18 +3206,18 @@ async function loadConfig(options = {}) {
2865
3206
  function getSchemaDirectory(config) {
2866
3207
  const cwd = process.cwd();
2867
3208
  const possiblePaths = [
2868
- path4.resolve(cwd, config.schema.directory),
2869
- path4.resolve(cwd, "shared", config.schema.directory)
3209
+ path5.resolve(cwd, config.schema.directory),
3210
+ path5.resolve(cwd, "shared", config.schema.directory)
2870
3211
  ];
2871
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
3212
+ return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
2872
3213
  }
2873
3214
  function getMigrationsDirectory(config) {
2874
3215
  const cwd = process.cwd();
2875
3216
  const possiblePaths = [
2876
- path4.resolve(cwd, config.migrations.directory),
2877
- path4.resolve(cwd, "shared", config.migrations.directory)
3217
+ path5.resolve(cwd, config.migrations.directory),
3218
+ path5.resolve(cwd, "shared", config.migrations.directory)
2878
3219
  ];
2879
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
3220
+ return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
2880
3221
  }
2881
3222
  var currentVerbosity = "normal";
2882
3223
  function setVerbosity(level) {
@@ -3153,7 +3494,7 @@ async function executeGenerate(options) {
3153
3494
  const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
3154
3495
  logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
3155
3496
  logInfo("Loading previous snapshot...");
3156
- const previousSnapshot = loadSnapshotIfExists({
3497
+ const previousSnapshot = loadSnapshotWithMigrations({
3157
3498
  migrationsPath: migrationsDir,
3158
3499
  workspaceRoot: process.cwd()
3159
3500
  });
@@ -3180,7 +3521,7 @@ async function executeGenerate(options) {
3180
3521
  "Creating migration file...",
3181
3522
  () => Promise.resolve(generate(diff, migrationsDir))
3182
3523
  );
3183
- logSuccess(`Migration file created: ${path4.basename(migrationPath)}`);
3524
+ logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
3184
3525
  logSection("\u2705 Next Steps");
3185
3526
  console.log();
3186
3527
  console.log(" 1. Review the generated migration file:");
@@ -3346,7 +3687,7 @@ async function executeStatus(options) {
3346
3687
  const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
3347
3688
  logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
3348
3689
  logInfo("Loading previous snapshot...");
3349
- const previousSnapshot = loadSnapshotIfExists({
3690
+ const previousSnapshot = loadSnapshotWithMigrations({
3350
3691
  migrationsPath: migrationsDir,
3351
3692
  workspaceRoot: process.cwd()
3352
3693
  });