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
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk from 'chalk';
3
3
  import { Command } from 'commander';
4
- import * as fs4 from 'fs';
4
+ import * as fs5 from 'fs';
5
5
  import { readFileSync } from 'fs';
6
- import * as path4 from 'path';
6
+ import * as path5 from 'path';
7
7
  import { dirname, join } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { z } from 'zod';
@@ -136,10 +136,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
136
136
  operation;
137
137
  code;
138
138
  originalError;
139
- constructor(message, path6, operation, code, originalError) {
139
+ constructor(message, path7, operation, code, originalError) {
140
140
  super(message);
141
141
  this.name = "FileSystemError";
142
- this.path = path6;
142
+ this.path = path7;
143
143
  this.operation = operation;
144
144
  this.code = code;
145
145
  this.originalError = originalError;
@@ -1053,6 +1053,16 @@ function isFieldRequired(zodType) {
1053
1053
  }
1054
1054
 
1055
1055
  // src/migration/analyzer.ts
1056
+ var tsxLoaderRegistered = false;
1057
+ async function ensureTsxLoader() {
1058
+ if (tsxLoaderRegistered) return;
1059
+ try {
1060
+ await import('tsx/esm');
1061
+ tsxLoaderRegistered = true;
1062
+ } catch {
1063
+ tsxLoaderRegistered = false;
1064
+ }
1065
+ }
1056
1066
  var DEFAULT_CONFIG = {
1057
1067
  workspaceRoot: process.cwd(),
1058
1068
  excludePatterns: [
@@ -1080,20 +1090,20 @@ function mergeConfig(config) {
1080
1090
  }
1081
1091
  function resolveSchemaDir(config) {
1082
1092
  const workspaceRoot = config.workspaceRoot || process.cwd();
1083
- if (path4.isAbsolute(config.schemaDir)) {
1093
+ if (path5.isAbsolute(config.schemaDir)) {
1084
1094
  return config.schemaDir;
1085
1095
  }
1086
- return path4.join(workspaceRoot, config.schemaDir);
1096
+ return path5.join(workspaceRoot, config.schemaDir);
1087
1097
  }
1088
1098
  function discoverSchemaFiles(config) {
1089
1099
  const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
1090
1100
  const mergedConfig = mergeConfig(normalizedConfig);
1091
1101
  const schemaDir = resolveSchemaDir(normalizedConfig);
1092
1102
  try {
1093
- if (!fs4.existsSync(schemaDir)) {
1103
+ if (!fs5.existsSync(schemaDir)) {
1094
1104
  throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1095
1105
  }
1096
- const files = fs4.readdirSync(schemaDir);
1106
+ const files = fs5.readdirSync(schemaDir);
1097
1107
  const schemaFiles = files.filter((file) => {
1098
1108
  const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
1099
1109
  if (!hasValidExtension) return false;
@@ -1109,7 +1119,7 @@ function discoverSchemaFiles(config) {
1109
1119
  });
1110
1120
  return schemaFiles.map((file) => {
1111
1121
  const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
1112
- return path4.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1122
+ return path5.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1113
1123
  });
1114
1124
  } catch (error) {
1115
1125
  if (error instanceof FileSystemError) {
@@ -1143,40 +1153,66 @@ async function importSchemaModule(filePath, config) {
1143
1153
  let resolvedPath = null;
1144
1154
  const jsPath = `${importPath}.js`;
1145
1155
  const tsPath = `${importPath}.ts`;
1146
- if (fs4.existsSync(jsPath)) {
1156
+ if (fs5.existsSync(jsPath)) {
1147
1157
  resolvedPath = jsPath;
1148
- } else if (fs4.existsSync(tsPath)) {
1158
+ } else if (fs5.existsSync(tsPath)) {
1149
1159
  resolvedPath = tsPath;
1150
1160
  } else {
1151
1161
  resolvedPath = jsPath;
1152
1162
  }
1153
- const fileUrl = new URL(`file://${path4.resolve(resolvedPath)}`);
1163
+ if (resolvedPath.endsWith(".ts")) {
1164
+ await ensureTsxLoader();
1165
+ if (!tsxLoaderRegistered) {
1166
+ throw new SchemaParsingError(
1167
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1168
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1169
+ Alternatively, compile your schema files to JavaScript first.`,
1170
+ filePath
1171
+ );
1172
+ }
1173
+ }
1174
+ const fileUrl = new URL(`file://${path5.resolve(resolvedPath)}`);
1154
1175
  const module = await import(fileUrl.href);
1155
1176
  return module;
1156
1177
  } catch (error) {
1157
1178
  const tsPath = `${filePath}.ts`;
1158
- const isTypeScriptFile = fs4.existsSync(tsPath);
1179
+ const isTypeScriptFile = fs5.existsSync(tsPath);
1180
+ if (isTypeScriptFile && error instanceof SchemaParsingError) {
1181
+ throw error;
1182
+ }
1159
1183
  if (isTypeScriptFile) {
1160
1184
  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")`,
1185
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1186
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1187
+ Alternatively, compile your schema files to JavaScript first.`,
1165
1188
  filePath,
1166
1189
  error
1167
1190
  );
1168
1191
  }
1169
1192
  throw new SchemaParsingError(
1170
- `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1193
+ `Failed to import schema module. Make sure the schema files exist and are valid.`,
1171
1194
  filePath,
1172
1195
  error
1173
1196
  );
1174
1197
  }
1175
1198
  }
1176
1199
  function getCollectionNameFromFile(filePath) {
1177
- const filename = path4.basename(filePath).replace(/\.(ts|js)$/, "");
1200
+ const filename = path5.basename(filePath).replace(/\.(ts|js)$/, "");
1178
1201
  return toCollectionName(filename);
1179
1202
  }
1203
+ function extractCollectionNameFromSchema(zodSchema) {
1204
+ if (!zodSchema.description) {
1205
+ return null;
1206
+ }
1207
+ try {
1208
+ const metadata = JSON.parse(zodSchema.description);
1209
+ if (metadata.collectionName && typeof metadata.collectionName === "string") {
1210
+ return metadata.collectionName;
1211
+ }
1212
+ } catch {
1213
+ }
1214
+ return null;
1215
+ }
1180
1216
  function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
1181
1217
  const result = {};
1182
1218
  for (const [key, value] of Object.entries(module)) {
@@ -1339,7 +1375,7 @@ async function buildSchemaDefinition(config) {
1339
1375
  importPath = normalizedConfig.pathTransformer(filePath);
1340
1376
  } else if (mergedConfig.useCompiledFiles) {
1341
1377
  const distPath = filePath.replace(/\/src\//, "/dist/");
1342
- if (fs4.existsSync(`${distPath}.js`) || fs4.existsSync(`${distPath}.mjs`)) {
1378
+ if (fs5.existsSync(`${distPath}.js`) || fs5.existsSync(`${distPath}.mjs`)) {
1343
1379
  importPath = distPath;
1344
1380
  } else {
1345
1381
  importPath = filePath;
@@ -1352,7 +1388,8 @@ async function buildSchemaDefinition(config) {
1352
1388
  console.warn(`No valid schema found in ${filePath}, skipping...`);
1353
1389
  continue;
1354
1390
  }
1355
- const collectionName = getCollectionNameFromFile(filePath);
1391
+ const collectionNameFromSchema = extractCollectionNameFromSchema(zodSchema);
1392
+ const collectionName = collectionNameFromSchema ?? getCollectionNameFromFile(filePath);
1356
1393
  const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
1357
1394
  collections.set(collectionName, collectionSchema);
1358
1395
  } catch (error) {
@@ -1516,6 +1553,9 @@ function compareFieldOptions(currentField, previousField) {
1516
1553
  for (const key of allKeys) {
1517
1554
  const currentValue = currentOptions[key];
1518
1555
  const previousValue = previousOptions[key];
1556
+ if (currentValue === void 0 && previousValue === void 0) {
1557
+ continue;
1558
+ }
1519
1559
  if (!areValuesEqual(currentValue, previousValue)) {
1520
1560
  changes.push({
1521
1561
  property: `options.${key}`,
@@ -1536,11 +1576,26 @@ function compareRelationConfigurations(currentField, previousField) {
1536
1576
  if (!currentRelation || !previousRelation) {
1537
1577
  return changes;
1538
1578
  }
1539
- if (currentRelation.collection !== previousRelation.collection) {
1579
+ const normalizeCollection = (collection) => {
1580
+ if (!collection) return collection;
1581
+ if (collection === "_pb_users_auth_") {
1582
+ return "Users";
1583
+ }
1584
+ const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1585
+ if (nameMatch) {
1586
+ return nameMatch[1];
1587
+ }
1588
+ return collection;
1589
+ };
1590
+ const normalizedCurrent = normalizeCollection(currentRelation.collection);
1591
+ const normalizedPrevious = normalizeCollection(previousRelation.collection);
1592
+ if (normalizedCurrent !== normalizedPrevious) {
1540
1593
  changes.push({
1541
1594
  property: "relation.collection",
1542
- oldValue: previousRelation.collection,
1543
- newValue: currentRelation.collection
1595
+ oldValue: normalizedPrevious,
1596
+ // Use normalized value for clarity
1597
+ newValue: normalizedCurrent
1598
+ // Use normalized value for clarity
1544
1599
  });
1545
1600
  }
1546
1601
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -1760,10 +1815,10 @@ function mergeConfig3(config) {
1760
1815
  }
1761
1816
  function resolveMigrationDir(config) {
1762
1817
  const workspaceRoot = config.workspaceRoot || process.cwd();
1763
- if (path4.isAbsolute(config.migrationDir)) {
1818
+ if (path5.isAbsolute(config.migrationDir)) {
1764
1819
  return config.migrationDir;
1765
1820
  }
1766
- return path4.join(workspaceRoot, config.migrationDir);
1821
+ return path5.join(workspaceRoot, config.migrationDir);
1767
1822
  }
1768
1823
  function generateTimestamp(config) {
1769
1824
  if (config?.timestampGenerator) {
@@ -1821,9 +1876,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
1821
1876
  }
1822
1877
  function writeMigrationFile(migrationDir, filename, content) {
1823
1878
  try {
1824
- if (!fs4.existsSync(migrationDir)) {
1879
+ if (!fs5.existsSync(migrationDir)) {
1825
1880
  try {
1826
- fs4.mkdirSync(migrationDir, { recursive: true });
1881
+ fs5.mkdirSync(migrationDir, { recursive: true });
1827
1882
  } catch (error) {
1828
1883
  const fsError = error;
1829
1884
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
@@ -1844,15 +1899,15 @@ function writeMigrationFile(migrationDir, filename, content) {
1844
1899
  );
1845
1900
  }
1846
1901
  }
1847
- const filePath = path4.join(migrationDir, filename);
1848
- fs4.writeFileSync(filePath, content, "utf-8");
1902
+ const filePath = path5.join(migrationDir, filename);
1903
+ fs5.writeFileSync(filePath, content, "utf-8");
1849
1904
  return filePath;
1850
1905
  } catch (error) {
1851
1906
  if (error instanceof FileSystemError) {
1852
1907
  throw error;
1853
1908
  }
1854
1909
  const fsError = error;
1855
- const filePath = path4.join(migrationDir, filename);
1910
+ const filePath = path5.join(migrationDir, filename);
1856
1911
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
1857
1912
  throw new FileSystemError(
1858
1913
  `Permission denied writing migration file. Check file and directory permissions.`,
@@ -2397,57 +2452,18 @@ function generate(diff, config) {
2397
2452
  );
2398
2453
  }
2399
2454
  }
2455
+
2456
+ // src/migration/pocketbase-converter.ts
2400
2457
  var SNAPSHOT_VERSION = "1.0.0";
2401
- ({
2402
- workspaceRoot: process.cwd()});
2403
- function findLatestSnapshot(migrationsPath) {
2404
- try {
2405
- if (!fs4.existsSync(migrationsPath)) {
2406
- return null;
2407
- }
2408
- const files = fs4.readdirSync(migrationsPath);
2409
- const snapshotFiles = files.filter(
2410
- (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
2411
- );
2412
- if (snapshotFiles.length === 0) {
2413
- return null;
2414
- }
2415
- snapshotFiles.sort().reverse();
2416
- const latestSnapshot = snapshotFiles[0];
2417
- if (!latestSnapshot) {
2418
- return null;
2419
- }
2420
- return path4.join(migrationsPath, latestSnapshot);
2421
- } catch (error) {
2422
- console.warn(`Error finding latest snapshot: ${error}`);
2423
- return null;
2458
+ function resolveCollectionIdToName(collectionId) {
2459
+ if (collectionId === "_pb_users_auth_") {
2460
+ return "Users";
2424
2461
  }
2425
- }
2426
- function loadSnapshotIfExists(config = {}) {
2427
- const migrationsPath = config.migrationsPath;
2428
- if (!migrationsPath) {
2429
- return null;
2430
- }
2431
- if (fs4.existsSync(migrationsPath) && fs4.statSync(migrationsPath).isFile()) {
2432
- try {
2433
- const migrationContent = fs4.readFileSync(migrationsPath, "utf-8");
2434
- return convertPocketBaseMigration(migrationContent);
2435
- } catch (error) {
2436
- console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2437
- return null;
2438
- }
2462
+ const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2463
+ if (nameMatch) {
2464
+ return nameMatch[1];
2439
2465
  }
2440
- const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2441
- if (latestSnapshotPath) {
2442
- try {
2443
- const migrationContent = fs4.readFileSync(latestSnapshotPath, "utf-8");
2444
- return convertPocketBaseMigration(migrationContent);
2445
- } catch (error) {
2446
- console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2447
- return null;
2448
- }
2449
- }
2450
- return null;
2466
+ return collectionId;
2451
2467
  }
2452
2468
  function convertPocketBaseCollection(pbCollection) {
2453
2469
  const fields = [];
@@ -2466,17 +2482,28 @@ function convertPocketBaseCollection(pbCollection) {
2466
2482
  type: pbField.type,
2467
2483
  required: pbField.required || false
2468
2484
  };
2469
- if (pbField.options) {
2470
- field.options = pbField.options;
2485
+ field.options = pbField.options ? { ...pbField.options } : {};
2486
+ if (pbField.type === "select") {
2487
+ if (pbField.values && Array.isArray(pbField.values)) {
2488
+ field.options.values = pbField.values;
2489
+ } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
2490
+ field.options.values = pbField.options.values;
2491
+ }
2471
2492
  }
2472
2493
  if (pbField.type === "relation") {
2494
+ const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
2495
+ const collectionName = resolveCollectionIdToName(collectionId);
2473
2496
  field.relation = {
2474
- collection: pbField.options?.collectionId || "",
2475
- cascadeDelete: pbField.options?.cascadeDelete || false,
2476
- maxSelect: pbField.options?.maxSelect,
2477
- minSelect: pbField.options?.minSelect
2497
+ collection: collectionName,
2498
+ cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
2499
+ maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
2500
+ minSelect: pbField.minSelect ?? pbField.options?.minSelect
2478
2501
  };
2479
2502
  }
2503
+ const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
2504
+ if (Object.keys(field.options).length === 0) {
2505
+ delete field.options;
2506
+ } else if (pbField.type === "select" && hasOnlyValues) ;
2480
2507
  fields.push(field);
2481
2508
  }
2482
2509
  }
@@ -2541,6 +2568,320 @@ function convertPocketBaseMigration(migrationContent) {
2541
2568
  }
2542
2569
  }
2543
2570
 
2571
+ // src/migration/migration-parser.ts
2572
+ function extractTimestampFromFilename(filename) {
2573
+ const match = filename.match(/^(\d+)_/);
2574
+ if (match) {
2575
+ return parseInt(match[1], 10);
2576
+ }
2577
+ return null;
2578
+ }
2579
+ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
2580
+ try {
2581
+ if (!fs5.existsSync(migrationsPath)) {
2582
+ return [];
2583
+ }
2584
+ const files = fs5.readdirSync(migrationsPath);
2585
+ const migrationFiles = [];
2586
+ for (const file of files) {
2587
+ if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
2588
+ continue;
2589
+ }
2590
+ if (!file.endsWith(".js")) {
2591
+ continue;
2592
+ }
2593
+ const timestamp = extractTimestampFromFilename(file);
2594
+ if (timestamp && timestamp > snapshotTimestamp) {
2595
+ migrationFiles.push({
2596
+ path: path5.join(migrationsPath, file),
2597
+ timestamp
2598
+ });
2599
+ }
2600
+ }
2601
+ migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
2602
+ return migrationFiles.map((f) => f.path);
2603
+ } catch (error) {
2604
+ console.warn(`Error finding migrations after snapshot: ${error}`);
2605
+ return [];
2606
+ }
2607
+ }
2608
+ function parseMigrationOperationsFromContent(content) {
2609
+ const collectionsToCreate = [];
2610
+ const collectionsToDelete = [];
2611
+ try {
2612
+ let searchIndex = 0;
2613
+ while (true) {
2614
+ const collectionStart = content.indexOf("new Collection(", searchIndex);
2615
+ if (collectionStart === -1) {
2616
+ break;
2617
+ }
2618
+ const openParen = collectionStart + "new Collection(".length;
2619
+ let braceCount = 0;
2620
+ let parenCount = 1;
2621
+ let inString = false;
2622
+ let stringChar = null;
2623
+ let i = openParen;
2624
+ while (i < content.length && /\s/.test(content[i])) {
2625
+ i++;
2626
+ }
2627
+ if (content[i] !== "{") {
2628
+ searchIndex = i + 1;
2629
+ continue;
2630
+ }
2631
+ const objectStart = i;
2632
+ braceCount = 1;
2633
+ i++;
2634
+ while (i < content.length && (braceCount > 0 || parenCount > 0)) {
2635
+ const char = content[i];
2636
+ const prevChar = i > 0 ? content[i - 1] : "";
2637
+ if (!inString && (char === '"' || char === "'")) {
2638
+ inString = true;
2639
+ stringChar = char;
2640
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2641
+ inString = false;
2642
+ stringChar = null;
2643
+ }
2644
+ if (!inString) {
2645
+ if (char === "{") braceCount++;
2646
+ if (char === "}") braceCount--;
2647
+ if (char === "(") parenCount++;
2648
+ if (char === ")") parenCount--;
2649
+ }
2650
+ i++;
2651
+ }
2652
+ if (braceCount === 0 && parenCount === 0) {
2653
+ const objectContent = content.substring(objectStart, i - 1);
2654
+ try {
2655
+ const collectionObj = new Function(`return ${objectContent}`)();
2656
+ if (collectionObj && collectionObj.name) {
2657
+ const schema = convertPocketBaseCollection(collectionObj);
2658
+ collectionsToCreate.push(schema);
2659
+ }
2660
+ } catch (error) {
2661
+ console.warn(`Failed to parse collection definition: ${error}`);
2662
+ }
2663
+ }
2664
+ searchIndex = i;
2665
+ }
2666
+ const deleteMatches = content.matchAll(
2667
+ /app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
2668
+ );
2669
+ for (const match of deleteMatches) {
2670
+ if (match[1]) {
2671
+ collectionsToDelete.push(match[1]);
2672
+ } else {
2673
+ const varNameMatch = match[0].match(/collection_(\w+)/);
2674
+ if (varNameMatch) {
2675
+ const varName = `collection_${varNameMatch[1]}`;
2676
+ const deleteIndex = content.indexOf(match[0]);
2677
+ const beforeDelete = content.substring(0, deleteIndex);
2678
+ const varDefMatch = beforeDelete.match(
2679
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
2680
+ );
2681
+ if (varDefMatch && varDefMatch.length > 0) {
2682
+ const collectionDefMatch = beforeDelete.match(
2683
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
2684
+ );
2685
+ if (collectionDefMatch) {
2686
+ try {
2687
+ const collectionDefStr = collectionDefMatch[1];
2688
+ const collectionObj = new Function(`return ${collectionDefStr}`)();
2689
+ if (collectionObj && collectionObj.name) {
2690
+ collectionsToDelete.push(collectionObj.name);
2691
+ }
2692
+ } catch {
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+ }
2698
+ }
2699
+ const findAndDeleteMatches = content.matchAll(
2700
+ /app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
2701
+ );
2702
+ for (const match of findAndDeleteMatches) {
2703
+ collectionsToDelete.push(match[1]);
2704
+ }
2705
+ } catch (error) {
2706
+ console.warn(`Failed to parse migration operations from content: ${error}`);
2707
+ }
2708
+ return { collectionsToCreate, collectionsToDelete };
2709
+ }
2710
+ function parseMigrationOperations(migrationContent) {
2711
+ try {
2712
+ const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
2713
+ if (!migrateMatch) {
2714
+ return parseMigrationOperationsFromContent(migrationContent);
2715
+ }
2716
+ const startIndex = migrateMatch.index + migrateMatch[0].length;
2717
+ let i = startIndex;
2718
+ let parenCount = 0;
2719
+ let foundFirstParen = false;
2720
+ while (i < migrationContent.length) {
2721
+ const char = migrationContent[i];
2722
+ if (char === "(") {
2723
+ parenCount++;
2724
+ foundFirstParen = true;
2725
+ i++;
2726
+ break;
2727
+ }
2728
+ i++;
2729
+ }
2730
+ if (!foundFirstParen) {
2731
+ return parseMigrationOperationsFromContent(migrationContent);
2732
+ }
2733
+ let inString = false;
2734
+ let stringChar = null;
2735
+ let foundBrace = false;
2736
+ let braceStart = -1;
2737
+ while (i < migrationContent.length && !foundBrace) {
2738
+ const char = migrationContent[i];
2739
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2740
+ if (!inString && (char === '"' || char === "'")) {
2741
+ inString = true;
2742
+ stringChar = char;
2743
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2744
+ inString = false;
2745
+ stringChar = null;
2746
+ }
2747
+ if (!inString) {
2748
+ if (char === "(") parenCount++;
2749
+ if (char === ")") {
2750
+ parenCount--;
2751
+ if (parenCount === 0) {
2752
+ i++;
2753
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2754
+ i++;
2755
+ }
2756
+ if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
2757
+ i += 2;
2758
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2759
+ i++;
2760
+ }
2761
+ if (i < migrationContent.length && migrationContent[i] === "{") {
2762
+ foundBrace = true;
2763
+ braceStart = i + 1;
2764
+ break;
2765
+ }
2766
+ }
2767
+ }
2768
+ }
2769
+ }
2770
+ i++;
2771
+ }
2772
+ if (!foundBrace || braceStart === -1) {
2773
+ return parseMigrationOperationsFromContent(migrationContent);
2774
+ }
2775
+ let braceCount = 1;
2776
+ i = braceStart;
2777
+ inString = false;
2778
+ stringChar = null;
2779
+ while (i < migrationContent.length && braceCount > 0) {
2780
+ const char = migrationContent[i];
2781
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2782
+ if (!inString && (char === '"' || char === "'")) {
2783
+ inString = true;
2784
+ stringChar = char;
2785
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2786
+ inString = false;
2787
+ stringChar = null;
2788
+ }
2789
+ if (!inString) {
2790
+ if (char === "{") braceCount++;
2791
+ if (char === "}") braceCount--;
2792
+ }
2793
+ i++;
2794
+ }
2795
+ if (braceCount === 0) {
2796
+ const upMigrationContent = migrationContent.substring(braceStart, i - 1);
2797
+ return parseMigrationOperationsFromContent(upMigrationContent);
2798
+ }
2799
+ return parseMigrationOperationsFromContent(migrationContent);
2800
+ } catch (error) {
2801
+ console.warn(`Failed to parse migration operations: ${error}`);
2802
+ return { collectionsToCreate: [], collectionsToDelete: [] };
2803
+ }
2804
+ }
2805
+ ({
2806
+ workspaceRoot: process.cwd()});
2807
+ function findLatestSnapshot(migrationsPath) {
2808
+ try {
2809
+ if (!fs5.existsSync(migrationsPath)) {
2810
+ return null;
2811
+ }
2812
+ const files = fs5.readdirSync(migrationsPath);
2813
+ const snapshotFiles = files.filter(
2814
+ (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
2815
+ );
2816
+ if (snapshotFiles.length === 0) {
2817
+ return null;
2818
+ }
2819
+ snapshotFiles.sort().reverse();
2820
+ const latestSnapshot = snapshotFiles[0];
2821
+ if (!latestSnapshot) {
2822
+ return null;
2823
+ }
2824
+ return path5.join(migrationsPath, latestSnapshot);
2825
+ } catch (error) {
2826
+ console.warn(`Error finding latest snapshot: ${error}`);
2827
+ return null;
2828
+ }
2829
+ }
2830
+ function applyMigrationOperations(snapshot, operations) {
2831
+ const updatedCollections = new Map(snapshot.collections);
2832
+ for (const collectionName of operations.collectionsToDelete) {
2833
+ updatedCollections.delete(collectionName);
2834
+ }
2835
+ for (const collection of operations.collectionsToCreate) {
2836
+ updatedCollections.set(collection.name, collection);
2837
+ }
2838
+ return {
2839
+ ...snapshot,
2840
+ collections: updatedCollections
2841
+ };
2842
+ }
2843
+ function loadSnapshotWithMigrations(config = {}) {
2844
+ const migrationsPath = config.migrationsPath;
2845
+ if (!migrationsPath) {
2846
+ return null;
2847
+ }
2848
+ if (fs5.existsSync(migrationsPath) && fs5.statSync(migrationsPath).isFile()) {
2849
+ try {
2850
+ const migrationContent = fs5.readFileSync(migrationsPath, "utf-8");
2851
+ return convertPocketBaseMigration(migrationContent);
2852
+ } catch (error) {
2853
+ console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2854
+ return null;
2855
+ }
2856
+ }
2857
+ const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2858
+ if (!latestSnapshotPath) {
2859
+ return null;
2860
+ }
2861
+ try {
2862
+ const migrationContent = fs5.readFileSync(latestSnapshotPath, "utf-8");
2863
+ let snapshot = convertPocketBaseMigration(migrationContent);
2864
+ const snapshotFilename = path5.basename(latestSnapshotPath);
2865
+ const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
2866
+ if (snapshotTimestamp) {
2867
+ const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
2868
+ for (const migrationFile of migrationFiles) {
2869
+ try {
2870
+ const migrationContent2 = fs5.readFileSync(migrationFile, "utf-8");
2871
+ const operations = parseMigrationOperations(migrationContent2);
2872
+ snapshot = applyMigrationOperations(snapshot, operations);
2873
+ } catch (error) {
2874
+ console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
2875
+ }
2876
+ }
2877
+ }
2878
+ return snapshot;
2879
+ } catch (error) {
2880
+ console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2881
+ return null;
2882
+ }
2883
+ }
2884
+
2544
2885
  // src/migration/validation.ts
2545
2886
  function detectCollectionDeletions(diff) {
2546
2887
  const changes = [];
@@ -2709,8 +3050,8 @@ var DEFAULT_CONFIG5 = {
2709
3050
  };
2710
3051
  function findConfigFile(directory) {
2711
3052
  for (const fileName of CONFIG_FILE_NAMES) {
2712
- const filePath = path4.join(directory, fileName);
2713
- if (fs4.existsSync(filePath)) {
3053
+ const filePath = path5.join(directory, fileName);
3054
+ if (fs5.existsSync(filePath)) {
2714
3055
  return filePath;
2715
3056
  }
2716
3057
  }
@@ -2718,7 +3059,7 @@ function findConfigFile(directory) {
2718
3059
  }
2719
3060
  function loadJsonConfig(configPath) {
2720
3061
  try {
2721
- const content = fs4.readFileSync(configPath, "utf-8");
3062
+ const content = fs5.readFileSync(configPath, "utf-8");
2722
3063
  return JSON.parse(content);
2723
3064
  } catch (error) {
2724
3065
  if (error instanceof SyntaxError) {
@@ -2747,10 +3088,10 @@ async function loadJsConfig(configPath) {
2747
3088
  }
2748
3089
  }
2749
3090
  async function loadConfigFile(configPath) {
2750
- if (!fs4.existsSync(configPath)) {
3091
+ if (!fs5.existsSync(configPath)) {
2751
3092
  return null;
2752
3093
  }
2753
- const ext = path4.extname(configPath).toLowerCase();
3094
+ const ext = path5.extname(configPath).toLowerCase();
2754
3095
  if (ext === ".json") {
2755
3096
  return loadJsonConfig(configPath);
2756
3097
  } else if (ext === ".js" || ext === ".mjs") {
@@ -2817,10 +3158,10 @@ function validateConfig(config, configPath) {
2817
3158
  }
2818
3159
  const cwd = process.cwd();
2819
3160
  const possiblePaths = [
2820
- path4.resolve(cwd, config.schema.directory),
2821
- path4.resolve(cwd, "shared", config.schema.directory)
3161
+ path5.resolve(cwd, config.schema.directory),
3162
+ path5.resolve(cwd, "shared", config.schema.directory)
2822
3163
  ];
2823
- const schemaDir = possiblePaths.find((p) => fs4.existsSync(p));
3164
+ const schemaDir = possiblePaths.find((p) => fs5.existsSync(p));
2824
3165
  if (!schemaDir) {
2825
3166
  throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
2826
3167
  "schema.directory"
@@ -2832,15 +3173,15 @@ async function loadConfig(options = {}) {
2832
3173
  let configFilePath;
2833
3174
  const cwd = process.cwd();
2834
3175
  if (options.config) {
2835
- const explicitPath = path4.resolve(cwd, options.config);
2836
- if (!fs4.existsSync(explicitPath)) {
3176
+ const explicitPath = path5.resolve(cwd, options.config);
3177
+ if (!fs5.existsSync(explicitPath)) {
2837
3178
  throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
2838
3179
  }
2839
3180
  configFilePath = explicitPath;
2840
3181
  } else {
2841
- const searchDirs = [cwd, path4.join(cwd, "shared")];
3182
+ const searchDirs = [cwd, path5.join(cwd, "shared")];
2842
3183
  for (const dir of searchDirs) {
2843
- if (fs4.existsSync(dir)) {
3184
+ if (fs5.existsSync(dir)) {
2844
3185
  const found = findConfigFile(dir);
2845
3186
  if (found) {
2846
3187
  configFilePath = found;
@@ -2869,18 +3210,18 @@ async function loadConfig(options = {}) {
2869
3210
  function getSchemaDirectory(config) {
2870
3211
  const cwd = process.cwd();
2871
3212
  const possiblePaths = [
2872
- path4.resolve(cwd, config.schema.directory),
2873
- path4.resolve(cwd, "shared", config.schema.directory)
3213
+ path5.resolve(cwd, config.schema.directory),
3214
+ path5.resolve(cwd, "shared", config.schema.directory)
2874
3215
  ];
2875
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
3216
+ return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
2876
3217
  }
2877
3218
  function getMigrationsDirectory(config) {
2878
3219
  const cwd = process.cwd();
2879
3220
  const possiblePaths = [
2880
- path4.resolve(cwd, config.migrations.directory),
2881
- path4.resolve(cwd, "shared", config.migrations.directory)
3221
+ path5.resolve(cwd, config.migrations.directory),
3222
+ path5.resolve(cwd, "shared", config.migrations.directory)
2882
3223
  ];
2883
- return possiblePaths.find((p) => fs4.existsSync(p)) || possiblePaths[0];
3224
+ return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
2884
3225
  }
2885
3226
  var currentVerbosity = "normal";
2886
3227
  function setVerbosity(level) {
@@ -3097,7 +3438,7 @@ async function executeGenerate(options) {
3097
3438
  const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
3098
3439
  logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
3099
3440
  logInfo("Loading previous snapshot...");
3100
- const previousSnapshot = loadSnapshotIfExists({
3441
+ const previousSnapshot = loadSnapshotWithMigrations({
3101
3442
  migrationsPath: migrationsDir,
3102
3443
  workspaceRoot: process.cwd()
3103
3444
  });
@@ -3124,7 +3465,7 @@ async function executeGenerate(options) {
3124
3465
  "Creating migration file...",
3125
3466
  () => Promise.resolve(generate(diff, migrationsDir))
3126
3467
  );
3127
- logSuccess(`Migration file created: ${path4.basename(migrationPath)}`);
3468
+ logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
3128
3469
  logSection("\u2705 Next Steps");
3129
3470
  console.log();
3130
3471
  console.log(" 1. Review the generated migration file:");
@@ -3302,7 +3643,7 @@ async function executeStatus(options) {
3302
3643
  const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
3303
3644
  logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
3304
3645
  logInfo("Loading previous snapshot...");
3305
- const previousSnapshot = loadSnapshotIfExists({
3646
+ const previousSnapshot = loadSnapshotWithMigrations({
3306
3647
  migrationsPath: migrationsDir,
3307
3648
  workspaceRoot: process.cwd()
3308
3649
  });