forked from wangziqi/gongxue-base
feat: harden pocketbase migration readiness checks
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export interface PocketBaseField {
|
||||
name: string;
|
||||
@@ -24,8 +25,20 @@ export interface PocketBaseCollection {
|
||||
deleteRule?: string;
|
||||
}
|
||||
|
||||
export function readPocketBaseSchema(schemaPath = process.env.PB_SCHEMA_PATH || '../../docs/pb_schema.json') {
|
||||
const absPath = path.resolve(process.cwd(), schemaPath);
|
||||
function defaultSchemaPath() {
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const candidates = [
|
||||
path.resolve(process.cwd(), process.env.PB_SCHEMA_PATH || ''),
|
||||
path.resolve(process.cwd(), '../../docs/pb_schema.json'),
|
||||
path.resolve(process.cwd(), 'docs/pb_schema.json'),
|
||||
path.resolve(moduleDir, '../../../docs/pb_schema.json'),
|
||||
].filter(candidate => candidate && candidate !== process.cwd());
|
||||
|
||||
return candidates.find(candidate => fs.existsSync(candidate)) || candidates[candidates.length - 1];
|
||||
}
|
||||
|
||||
export function readPocketBaseSchema(schemaPath = process.env.PB_SCHEMA_PATH || '') {
|
||||
const absPath = schemaPath ? path.resolve(process.cwd(), schemaPath) : defaultSchemaPath();
|
||||
const raw = JSON.parse(fs.readFileSync(absPath, 'utf8'));
|
||||
const collections = (Array.isArray(raw) ? raw : raw.collections || []) as PocketBaseCollection[];
|
||||
const byId = new Map(collections.map(c => [c.id, c]));
|
||||
|
||||
@@ -11,13 +11,14 @@ function writeJson(filePath, value) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
function runDryRun(exportDir) {
|
||||
const result = spawnSync(process.execPath, ['--import', 'tsx', dryRunScript, '--json'], {
|
||||
function runDryRun(exportDir, options = {}) {
|
||||
const result = spawnSync(process.execPath, ['--import', 'tsx', dryRunScript, '--json', ...(options.args || [])], {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PB_EXPORT_DIR: exportDir,
|
||||
...(options.env || {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -88,6 +89,22 @@ function createUnsafeExport() {
|
||||
return dir;
|
||||
}
|
||||
|
||||
function createWarningOnlyExport() {
|
||||
const dir = createSafeExport();
|
||||
writeJson(path.join(dir, 'unknown_business.json'), [
|
||||
{ id: 'x1', name: '尚未映射集合' },
|
||||
]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function createBrokenRelationExport() {
|
||||
const dir = createSafeExport();
|
||||
writeJson(path.join(dir, 'questions.json'), [
|
||||
{ id: 'q1', subjectId: 'missing-subject', categoryId: 'c1', type: 'choice', content: '题干', answer: 'A' },
|
||||
]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
const safeDir = createSafeExport();
|
||||
const safe = runDryRun(safeDir);
|
||||
assert.equal(safe.status, 0, `safe dry-run should pass: ${safe.stdout} ${safe.stderr}`);
|
||||
@@ -115,8 +132,29 @@ assert.ok(
|
||||
unsafe.payload.issues?.some(item => item.code === 'unsupported_collection'),
|
||||
'unsafe dry-run should warn about unsupported collections',
|
||||
);
|
||||
assert.ok(
|
||||
unsafe.payload.issues?.some(item => item.code === 'relation_target_export_missing' && item.collection === 'questions'),
|
||||
'unsafe dry-run should block when question relation target export is missing',
|
||||
);
|
||||
|
||||
const warningOnlyDir = createWarningOnlyExport();
|
||||
const warningOnly = runDryRun(warningOnlyDir);
|
||||
assert.equal(warningOnly.status, 0, 'warning-only dry-run should pass without fail-on-warnings');
|
||||
assert.ok(warningOnly.payload.summary?.warnings > 0, 'warning-only dry-run should report warnings');
|
||||
const warningAsFailure = runDryRun(warningOnlyDir, { args: ['--fail-on-warnings'] });
|
||||
assert.notEqual(warningAsFailure.status, 0, 'warning-only dry-run should fail with --fail-on-warnings');
|
||||
|
||||
const brokenRelationDir = createBrokenRelationExport();
|
||||
const brokenRelation = runDryRun(brokenRelationDir);
|
||||
assert.notEqual(brokenRelation.status, 0, 'broken relation dry-run should fail');
|
||||
assert.ok(
|
||||
brokenRelation.payload.issues?.some(item => item.code === 'relation_unresolved' && item.collection === 'questions'),
|
||||
'broken relation dry-run should detect unresolved question subject relation',
|
||||
);
|
||||
|
||||
fs.rmSync(safeDir, { recursive: true, force: true });
|
||||
fs.rmSync(unsafeDir, { recursive: true, force: true });
|
||||
fs.rmSync(warningOnlyDir, { recursive: true, force: true });
|
||||
fs.rmSync(brokenRelationDir, { recursive: true, force: true });
|
||||
|
||||
console.log('[PASS] PocketBase dry-run report');
|
||||
|
||||
Reference in New Issue
Block a user