pocketbase-zod-schema 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/cli/index.cjs +406 -102
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +404 -100
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrate.cjs +409 -105
- package/dist/cli/migrate.cjs.map +1 -1
- package/dist/cli/migrate.js +404 -100
- package/dist/cli/migrate.js.map +1 -1
- package/dist/index.cjs +515 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +511 -158
- package/dist/index.js.map +1 -1
- package/dist/migration/diff.cjs +21 -3
- package/dist/migration/diff.cjs.map +1 -1
- package/dist/migration/diff.js +21 -3
- package/dist/migration/diff.js.map +1 -1
- package/dist/migration/index.cjs +457 -123
- package/dist/migration/index.cjs.map +1 -1
- package/dist/migration/index.d.cts +1 -1
- package/dist/migration/index.d.ts +1 -1
- package/dist/migration/index.js +456 -123
- package/dist/migration/index.js.map +1 -1
- package/dist/migration/snapshot.cjs +432 -118
- package/dist/migration/snapshot.cjs.map +1 -1
- package/dist/migration/snapshot.d.cts +34 -12
- package/dist/migration/snapshot.d.ts +34 -12
- package/dist/migration/snapshot.js +430 -117
- package/dist/migration/snapshot.js.map +1 -1
- package/dist/mutator.d.cts +3 -3
- package/dist/mutator.d.ts +3 -3
- package/dist/schema.cjs +34 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +33 -1
- package/dist/schema.js.map +1 -1
- package/dist/types.d.cts +5 -2
- package/dist/types.d.ts +5 -2
- package/dist/user-_AM523hb.d.cts +123 -0
- package/dist/user-_AM523hb.d.ts +123 -0
- package/package.json +2 -3
- package/dist/user-C39DQ40N.d.cts +0 -53
- package/dist/user-C39DQ40N.d.ts +0 -53
package/dist/cli/migrate.js
CHANGED
|
@@ -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
|
|
4
|
+
import * as fs5 from 'fs';
|
|
5
5
|
import { readFileSync } from 'fs';
|
|
6
|
-
import * as
|
|
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,
|
|
139
|
+
constructor(message, path7, operation, code, originalError) {
|
|
140
140
|
super(message);
|
|
141
141
|
this.name = "FileSystemError";
|
|
142
|
-
this.path =
|
|
142
|
+
this.path = path7;
|
|
143
143
|
this.operation = operation;
|
|
144
144
|
this.code = code;
|
|
145
145
|
this.originalError = originalError;
|
|
@@ -1080,20 +1080,20 @@ function mergeConfig(config) {
|
|
|
1080
1080
|
}
|
|
1081
1081
|
function resolveSchemaDir(config) {
|
|
1082
1082
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1083
|
-
if (
|
|
1083
|
+
if (path5.isAbsolute(config.schemaDir)) {
|
|
1084
1084
|
return config.schemaDir;
|
|
1085
1085
|
}
|
|
1086
|
-
return
|
|
1086
|
+
return path5.join(workspaceRoot, config.schemaDir);
|
|
1087
1087
|
}
|
|
1088
1088
|
function discoverSchemaFiles(config) {
|
|
1089
1089
|
const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
|
|
1090
1090
|
const mergedConfig = mergeConfig(normalizedConfig);
|
|
1091
1091
|
const schemaDir = resolveSchemaDir(normalizedConfig);
|
|
1092
1092
|
try {
|
|
1093
|
-
if (!
|
|
1093
|
+
if (!fs5.existsSync(schemaDir)) {
|
|
1094
1094
|
throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
|
|
1095
1095
|
}
|
|
1096
|
-
const files =
|
|
1096
|
+
const files = fs5.readdirSync(schemaDir);
|
|
1097
1097
|
const schemaFiles = files.filter((file) => {
|
|
1098
1098
|
const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
|
|
1099
1099
|
if (!hasValidExtension) return false;
|
|
@@ -1109,7 +1109,7 @@ function discoverSchemaFiles(config) {
|
|
|
1109
1109
|
});
|
|
1110
1110
|
return schemaFiles.map((file) => {
|
|
1111
1111
|
const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
|
|
1112
|
-
return
|
|
1112
|
+
return path5.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
|
|
1113
1113
|
});
|
|
1114
1114
|
} catch (error) {
|
|
1115
1115
|
if (error instanceof FileSystemError) {
|
|
@@ -1143,19 +1143,19 @@ async function importSchemaModule(filePath, config) {
|
|
|
1143
1143
|
let resolvedPath = null;
|
|
1144
1144
|
const jsPath = `${importPath}.js`;
|
|
1145
1145
|
const tsPath = `${importPath}.ts`;
|
|
1146
|
-
if (
|
|
1146
|
+
if (fs5.existsSync(jsPath)) {
|
|
1147
1147
|
resolvedPath = jsPath;
|
|
1148
|
-
} else if (
|
|
1148
|
+
} else if (fs5.existsSync(tsPath)) {
|
|
1149
1149
|
resolvedPath = tsPath;
|
|
1150
1150
|
} else {
|
|
1151
1151
|
resolvedPath = jsPath;
|
|
1152
1152
|
}
|
|
1153
|
-
const fileUrl = new URL(`file://${
|
|
1153
|
+
const fileUrl = new URL(`file://${path5.resolve(resolvedPath)}`);
|
|
1154
1154
|
const module = await import(fileUrl.href);
|
|
1155
1155
|
return module;
|
|
1156
1156
|
} catch (error) {
|
|
1157
1157
|
const tsPath = `${filePath}.ts`;
|
|
1158
|
-
const isTypeScriptFile =
|
|
1158
|
+
const isTypeScriptFile = fs5.existsSync(tsPath);
|
|
1159
1159
|
if (isTypeScriptFile) {
|
|
1160
1160
|
throw new SchemaParsingError(
|
|
1161
1161
|
`Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
|
|
@@ -1174,7 +1174,7 @@ Please either:
|
|
|
1174
1174
|
}
|
|
1175
1175
|
}
|
|
1176
1176
|
function getCollectionNameFromFile(filePath) {
|
|
1177
|
-
const filename =
|
|
1177
|
+
const filename = path5.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1178
1178
|
return toCollectionName(filename);
|
|
1179
1179
|
}
|
|
1180
1180
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
|
|
@@ -1339,7 +1339,7 @@ async function buildSchemaDefinition(config) {
|
|
|
1339
1339
|
importPath = normalizedConfig.pathTransformer(filePath);
|
|
1340
1340
|
} else if (mergedConfig.useCompiledFiles) {
|
|
1341
1341
|
const distPath = filePath.replace(/\/src\//, "/dist/");
|
|
1342
|
-
if (
|
|
1342
|
+
if (fs5.existsSync(`${distPath}.js`) || fs5.existsSync(`${distPath}.mjs`)) {
|
|
1343
1343
|
importPath = distPath;
|
|
1344
1344
|
} else {
|
|
1345
1345
|
importPath = filePath;
|
|
@@ -1516,6 +1516,9 @@ function compareFieldOptions(currentField, previousField) {
|
|
|
1516
1516
|
for (const key of allKeys) {
|
|
1517
1517
|
const currentValue = currentOptions[key];
|
|
1518
1518
|
const previousValue = previousOptions[key];
|
|
1519
|
+
if (currentValue === void 0 && previousValue === void 0) {
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1519
1522
|
if (!areValuesEqual(currentValue, previousValue)) {
|
|
1520
1523
|
changes.push({
|
|
1521
1524
|
property: `options.${key}`,
|
|
@@ -1536,11 +1539,26 @@ function compareRelationConfigurations(currentField, previousField) {
|
|
|
1536
1539
|
if (!currentRelation || !previousRelation) {
|
|
1537
1540
|
return changes;
|
|
1538
1541
|
}
|
|
1539
|
-
|
|
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) {
|
|
1540
1556
|
changes.push({
|
|
1541
1557
|
property: "relation.collection",
|
|
1542
|
-
oldValue:
|
|
1543
|
-
|
|
1558
|
+
oldValue: normalizedPrevious,
|
|
1559
|
+
// Use normalized value for clarity
|
|
1560
|
+
newValue: normalizedCurrent
|
|
1561
|
+
// Use normalized value for clarity
|
|
1544
1562
|
});
|
|
1545
1563
|
}
|
|
1546
1564
|
if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
|
|
@@ -1760,10 +1778,10 @@ function mergeConfig3(config) {
|
|
|
1760
1778
|
}
|
|
1761
1779
|
function resolveMigrationDir(config) {
|
|
1762
1780
|
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1763
|
-
if (
|
|
1781
|
+
if (path5.isAbsolute(config.migrationDir)) {
|
|
1764
1782
|
return config.migrationDir;
|
|
1765
1783
|
}
|
|
1766
|
-
return
|
|
1784
|
+
return path5.join(workspaceRoot, config.migrationDir);
|
|
1767
1785
|
}
|
|
1768
1786
|
function generateTimestamp(config) {
|
|
1769
1787
|
if (config?.timestampGenerator) {
|
|
@@ -1821,9 +1839,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
|
|
|
1821
1839
|
}
|
|
1822
1840
|
function writeMigrationFile(migrationDir, filename, content) {
|
|
1823
1841
|
try {
|
|
1824
|
-
if (!
|
|
1842
|
+
if (!fs5.existsSync(migrationDir)) {
|
|
1825
1843
|
try {
|
|
1826
|
-
|
|
1844
|
+
fs5.mkdirSync(migrationDir, { recursive: true });
|
|
1827
1845
|
} catch (error) {
|
|
1828
1846
|
const fsError = error;
|
|
1829
1847
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
@@ -1844,15 +1862,15 @@ function writeMigrationFile(migrationDir, filename, content) {
|
|
|
1844
1862
|
);
|
|
1845
1863
|
}
|
|
1846
1864
|
}
|
|
1847
|
-
const filePath =
|
|
1848
|
-
|
|
1865
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1866
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1849
1867
|
return filePath;
|
|
1850
1868
|
} catch (error) {
|
|
1851
1869
|
if (error instanceof FileSystemError) {
|
|
1852
1870
|
throw error;
|
|
1853
1871
|
}
|
|
1854
1872
|
const fsError = error;
|
|
1855
|
-
const filePath =
|
|
1873
|
+
const filePath = path5.join(migrationDir, filename);
|
|
1856
1874
|
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
1857
1875
|
throw new FileSystemError(
|
|
1858
1876
|
`Permission denied writing migration file. Check file and directory permissions.`,
|
|
@@ -2397,57 +2415,18 @@ function generate(diff, config) {
|
|
|
2397
2415
|
);
|
|
2398
2416
|
}
|
|
2399
2417
|
}
|
|
2418
|
+
|
|
2419
|
+
// src/migration/pocketbase-converter.ts
|
|
2400
2420
|
var SNAPSHOT_VERSION = "1.0.0";
|
|
2401
|
-
({
|
|
2402
|
-
|
|
2403
|
-
|
|
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;
|
|
2421
|
+
function resolveCollectionIdToName(collectionId) {
|
|
2422
|
+
if (collectionId === "_pb_users_auth_") {
|
|
2423
|
+
return "Users";
|
|
2424
2424
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
if (!migrationsPath) {
|
|
2429
|
-
return null;
|
|
2425
|
+
const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
2426
|
+
if (nameMatch) {
|
|
2427
|
+
return nameMatch[1];
|
|
2430
2428
|
}
|
|
2431
|
-
|
|
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
|
-
}
|
|
2439
|
-
}
|
|
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;
|
|
2429
|
+
return collectionId;
|
|
2451
2430
|
}
|
|
2452
2431
|
function convertPocketBaseCollection(pbCollection) {
|
|
2453
2432
|
const fields = [];
|
|
@@ -2466,17 +2445,28 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
2466
2445
|
type: pbField.type,
|
|
2467
2446
|
required: pbField.required || false
|
|
2468
2447
|
};
|
|
2469
|
-
|
|
2470
|
-
|
|
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
|
+
}
|
|
2471
2455
|
}
|
|
2472
2456
|
if (pbField.type === "relation") {
|
|
2457
|
+
const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
|
|
2458
|
+
const collectionName = resolveCollectionIdToName(collectionId);
|
|
2473
2459
|
field.relation = {
|
|
2474
|
-
collection:
|
|
2475
|
-
cascadeDelete: pbField.options?.cascadeDelete
|
|
2476
|
-
maxSelect: pbField.options?.maxSelect,
|
|
2477
|
-
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
|
|
2478
2464
|
};
|
|
2479
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) ;
|
|
2480
2470
|
fields.push(field);
|
|
2481
2471
|
}
|
|
2482
2472
|
}
|
|
@@ -2541,6 +2531,320 @@ function convertPocketBaseMigration(migrationContent) {
|
|
|
2541
2531
|
}
|
|
2542
2532
|
}
|
|
2543
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
|
+
|
|
2544
2848
|
// src/migration/validation.ts
|
|
2545
2849
|
function detectCollectionDeletions(diff) {
|
|
2546
2850
|
const changes = [];
|
|
@@ -2709,8 +3013,8 @@ var DEFAULT_CONFIG5 = {
|
|
|
2709
3013
|
};
|
|
2710
3014
|
function findConfigFile(directory) {
|
|
2711
3015
|
for (const fileName of CONFIG_FILE_NAMES) {
|
|
2712
|
-
const filePath =
|
|
2713
|
-
if (
|
|
3016
|
+
const filePath = path5.join(directory, fileName);
|
|
3017
|
+
if (fs5.existsSync(filePath)) {
|
|
2714
3018
|
return filePath;
|
|
2715
3019
|
}
|
|
2716
3020
|
}
|
|
@@ -2718,7 +3022,7 @@ function findConfigFile(directory) {
|
|
|
2718
3022
|
}
|
|
2719
3023
|
function loadJsonConfig(configPath) {
|
|
2720
3024
|
try {
|
|
2721
|
-
const content =
|
|
3025
|
+
const content = fs5.readFileSync(configPath, "utf-8");
|
|
2722
3026
|
return JSON.parse(content);
|
|
2723
3027
|
} catch (error) {
|
|
2724
3028
|
if (error instanceof SyntaxError) {
|
|
@@ -2747,10 +3051,10 @@ async function loadJsConfig(configPath) {
|
|
|
2747
3051
|
}
|
|
2748
3052
|
}
|
|
2749
3053
|
async function loadConfigFile(configPath) {
|
|
2750
|
-
if (!
|
|
3054
|
+
if (!fs5.existsSync(configPath)) {
|
|
2751
3055
|
return null;
|
|
2752
3056
|
}
|
|
2753
|
-
const ext =
|
|
3057
|
+
const ext = path5.extname(configPath).toLowerCase();
|
|
2754
3058
|
if (ext === ".json") {
|
|
2755
3059
|
return loadJsonConfig(configPath);
|
|
2756
3060
|
} else if (ext === ".js" || ext === ".mjs") {
|
|
@@ -2817,10 +3121,10 @@ function validateConfig(config, configPath) {
|
|
|
2817
3121
|
}
|
|
2818
3122
|
const cwd = process.cwd();
|
|
2819
3123
|
const possiblePaths = [
|
|
2820
|
-
|
|
2821
|
-
|
|
3124
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3125
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2822
3126
|
];
|
|
2823
|
-
const schemaDir = possiblePaths.find((p) =>
|
|
3127
|
+
const schemaDir = possiblePaths.find((p) => fs5.existsSync(p));
|
|
2824
3128
|
if (!schemaDir) {
|
|
2825
3129
|
throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
|
|
2826
3130
|
"schema.directory"
|
|
@@ -2832,15 +3136,15 @@ async function loadConfig(options = {}) {
|
|
|
2832
3136
|
let configFilePath;
|
|
2833
3137
|
const cwd = process.cwd();
|
|
2834
3138
|
if (options.config) {
|
|
2835
|
-
const explicitPath =
|
|
2836
|
-
if (!
|
|
3139
|
+
const explicitPath = path5.resolve(cwd, options.config);
|
|
3140
|
+
if (!fs5.existsSync(explicitPath)) {
|
|
2837
3141
|
throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
|
|
2838
3142
|
}
|
|
2839
3143
|
configFilePath = explicitPath;
|
|
2840
3144
|
} else {
|
|
2841
|
-
const searchDirs = [cwd,
|
|
3145
|
+
const searchDirs = [cwd, path5.join(cwd, "shared")];
|
|
2842
3146
|
for (const dir of searchDirs) {
|
|
2843
|
-
if (
|
|
3147
|
+
if (fs5.existsSync(dir)) {
|
|
2844
3148
|
const found = findConfigFile(dir);
|
|
2845
3149
|
if (found) {
|
|
2846
3150
|
configFilePath = found;
|
|
@@ -2869,18 +3173,18 @@ async function loadConfig(options = {}) {
|
|
|
2869
3173
|
function getSchemaDirectory(config) {
|
|
2870
3174
|
const cwd = process.cwd();
|
|
2871
3175
|
const possiblePaths = [
|
|
2872
|
-
|
|
2873
|
-
|
|
3176
|
+
path5.resolve(cwd, config.schema.directory),
|
|
3177
|
+
path5.resolve(cwd, "shared", config.schema.directory)
|
|
2874
3178
|
];
|
|
2875
|
-
return possiblePaths.find((p) =>
|
|
3179
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2876
3180
|
}
|
|
2877
3181
|
function getMigrationsDirectory(config) {
|
|
2878
3182
|
const cwd = process.cwd();
|
|
2879
3183
|
const possiblePaths = [
|
|
2880
|
-
|
|
2881
|
-
|
|
3184
|
+
path5.resolve(cwd, config.migrations.directory),
|
|
3185
|
+
path5.resolve(cwd, "shared", config.migrations.directory)
|
|
2882
3186
|
];
|
|
2883
|
-
return possiblePaths.find((p) =>
|
|
3187
|
+
return possiblePaths.find((p) => fs5.existsSync(p)) || possiblePaths[0];
|
|
2884
3188
|
}
|
|
2885
3189
|
var currentVerbosity = "normal";
|
|
2886
3190
|
function setVerbosity(level) {
|
|
@@ -3097,7 +3401,7 @@ async function executeGenerate(options) {
|
|
|
3097
3401
|
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
3098
3402
|
logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
|
|
3099
3403
|
logInfo("Loading previous snapshot...");
|
|
3100
|
-
const previousSnapshot =
|
|
3404
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
3101
3405
|
migrationsPath: migrationsDir,
|
|
3102
3406
|
workspaceRoot: process.cwd()
|
|
3103
3407
|
});
|
|
@@ -3124,7 +3428,7 @@ async function executeGenerate(options) {
|
|
|
3124
3428
|
"Creating migration file...",
|
|
3125
3429
|
() => Promise.resolve(generate(diff, migrationsDir))
|
|
3126
3430
|
);
|
|
3127
|
-
logSuccess(`Migration file created: ${
|
|
3431
|
+
logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
|
|
3128
3432
|
logSection("\u2705 Next Steps");
|
|
3129
3433
|
console.log();
|
|
3130
3434
|
console.log(" 1. Review the generated migration file:");
|
|
@@ -3302,7 +3606,7 @@ async function executeStatus(options) {
|
|
|
3302
3606
|
const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
|
|
3303
3607
|
logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
|
|
3304
3608
|
logInfo("Loading previous snapshot...");
|
|
3305
|
-
const previousSnapshot =
|
|
3609
|
+
const previousSnapshot = loadSnapshotWithMigrations({
|
|
3306
3610
|
migrationsPath: migrationsDir,
|
|
3307
3611
|
workspaceRoot: process.cwd()
|
|
3308
3612
|
});
|