gencow 0.1.179 → 0.1.181
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/lib/backend-bundle-file-plan.mjs +67 -0
- package/lib/db-command.mjs +2 -0
- package/lib/db-push-command.mjs +7 -1
- package/lib/deploy-package-runtime.mjs +19 -7
- package/lib/dev-cloud-bundle.mjs +10 -4
- package/lib/migration-bundle-manifest.mjs +2 -1
- package/lib/migration-bundle-roundtrip.mjs +55 -0
- package/lib/migration-bundle-staging.mjs +13 -2
- package/lib/migration-client-diagnostic.mjs +22 -0
- package/lib/project-migration-manifest.mjs +26 -17
- package/package.json +4 -4
- package/server/index.js.map +0 -7
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { shouldIncludeTarEntry } from "./tar-archive.mjs";
|
|
6
|
+
|
|
7
|
+
function sha256(value) {
|
|
8
|
+
return createHash("sha256").update(value).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function canonicalPath(value) {
|
|
12
|
+
return value.replaceAll("\\", "/").normalize("NFC");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectFiles(backendRoot, archiveRoot, excludes, directory = backendRoot) {
|
|
16
|
+
const files = [];
|
|
17
|
+
for (const entry of readdirSync(directory).sort()) {
|
|
18
|
+
const path = resolve(directory, entry);
|
|
19
|
+
const stats = lstatSync(path);
|
|
20
|
+
if (stats.isDirectory()) {
|
|
21
|
+
files.push(...collectFiles(backendRoot, archiveRoot, excludes, path));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
25
|
+
throw new Error(`Bundle entry ${JSON.stringify(entry)} must be a regular file`);
|
|
26
|
+
}
|
|
27
|
+
const relativePath = canonicalPath(relative(backendRoot, path));
|
|
28
|
+
const archivePath = canonicalPath(`${archiveRoot}/${relativePath}`);
|
|
29
|
+
if (!shouldIncludeTarEntry(archivePath, excludes)) continue;
|
|
30
|
+
const bytes = readFileSync(path);
|
|
31
|
+
files.push({ relativePath, archivePath, size: bytes.length, sha256: sha256(bytes) });
|
|
32
|
+
}
|
|
33
|
+
return files;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertUniqueCanonicalPaths(files) {
|
|
37
|
+
const exact = new Set();
|
|
38
|
+
const folded = new Set();
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const caseFolded = file.relativePath.toLocaleLowerCase("en-US");
|
|
41
|
+
if (exact.has(file.relativePath) || folded.has(caseFolded)) {
|
|
42
|
+
throw new Error("Backend bundle paths must be unique without Unicode or case-fold collisions");
|
|
43
|
+
}
|
|
44
|
+
exact.add(file.relativePath);
|
|
45
|
+
folded.add(caseFolded);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildBackendBundleFilePlan({ backendRoot, archiveRoot = "gencow", excludes = [] }) {
|
|
50
|
+
const normalizedArchiveRoot = canonicalPath(archiveRoot).replace(/^\.\/+|\/+$/gu, "");
|
|
51
|
+
if (!normalizedArchiveRoot || normalizedArchiveRoot.includes("..")) {
|
|
52
|
+
throw new Error("Backend bundle archive root is invalid");
|
|
53
|
+
}
|
|
54
|
+
const files = collectFiles(backendRoot, normalizedArchiveRoot, excludes);
|
|
55
|
+
assertUniqueCanonicalPaths(files);
|
|
56
|
+
const bundleFileRows = files.map((file) => [file.relativePath, file.size, file.sha256]);
|
|
57
|
+
const archiveEntries = files.map((file) => file.archivePath);
|
|
58
|
+
const migrationsRoot = resolve(backendRoot, "migrations");
|
|
59
|
+
if (existsSync(migrationsRoot) && !files.some((file) => file.relativePath.startsWith("migrations/"))) {
|
|
60
|
+
archiveEntries.push(`${normalizedArchiveRoot}/migrations/`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
archiveEntries,
|
|
64
|
+
bundleFileRows,
|
|
65
|
+
bundleSha256: sha256(JSON.stringify(bundleFileRows)),
|
|
66
|
+
};
|
|
67
|
+
}
|
package/lib/db-command.mjs
CHANGED
|
@@ -173,6 +173,7 @@ export function createDbCommands({
|
|
|
173
173
|
statSyncImpl = statSync,
|
|
174
174
|
successImpl = success,
|
|
175
175
|
tarCreateImpl,
|
|
176
|
+
verifyMigrationBundleArchiveRoundTripImpl,
|
|
176
177
|
unlinkSyncImpl = unlinkSync,
|
|
177
178
|
cpSyncImpl = cpSync,
|
|
178
179
|
dirnameImpl = dirname,
|
|
@@ -210,6 +211,7 @@ export function createDbCommands({
|
|
|
210
211
|
runInServer,
|
|
211
212
|
successImpl,
|
|
212
213
|
tarCreateImpl,
|
|
214
|
+
verifyMigrationBundleArchiveRoundTripImpl,
|
|
213
215
|
tmpdirImpl,
|
|
214
216
|
unlinkSyncImpl,
|
|
215
217
|
warnImpl,
|
package/lib/db-push-command.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
buildAuthSchemaProvenanceDiagnostic,
|
|
19
19
|
buildClientBundleFailureDiagnostic,
|
|
20
|
+
buildLocalBundleIntegrityFailureDiagnostic,
|
|
20
21
|
buildClientPreflightFailureDiagnostic,
|
|
21
22
|
buildClientUserFixableDiagnostic,
|
|
22
23
|
} from "./migration-client-diagnostic.mjs";
|
|
@@ -350,11 +351,16 @@ function createDbMigrationCommand(deps, command) {
|
|
|
350
351
|
resolvePathImpl: deps.resolvePathImpl,
|
|
351
352
|
rmSyncImpl: deps.rmSyncImpl,
|
|
352
353
|
tarCreateImpl: deps.tarCreateImpl,
|
|
354
|
+
verifyArchiveImpl: deps.verifyMigrationBundleArchiveRoundTripImpl,
|
|
353
355
|
tmpdirImpl: deps.tmpdirImpl,
|
|
354
356
|
writeFileSyncImpl: deps.writeFileSyncImpl,
|
|
355
357
|
});
|
|
356
358
|
} catch (error) {
|
|
357
|
-
emitFailure(
|
|
359
|
+
emitFailure(
|
|
360
|
+
error?.code === "MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED"
|
|
361
|
+
? buildLocalBundleIntegrityFailureDiagnostic(clientCorrelationId())
|
|
362
|
+
: buildClientBundleFailureDiagnostic(clientCorrelationId(), retryCommand),
|
|
363
|
+
);
|
|
358
364
|
return;
|
|
359
365
|
}
|
|
360
366
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "./project-migration-manifest.mjs";
|
|
23
23
|
import { hasWorkspaceSourceRuntime } from "./runtime-mode.mjs";
|
|
24
24
|
import { createTarGzipArchive } from "./tar-archive.mjs";
|
|
25
|
+
import { verifyMigrationBundleArchiveRoundTrip } from "./migration-bundle-roundtrip.mjs";
|
|
25
26
|
import { assertToolingRuntimeCapabilities, loadToolingRuntime } from "./tooling-runtime.mjs";
|
|
26
27
|
import {
|
|
27
28
|
extractActionableMigrationDiagnostic,
|
|
@@ -179,12 +180,14 @@ export function createDeployPackageRuntime({
|
|
|
179
180
|
const cwd = cwdImpl();
|
|
180
181
|
const { genEnv } = await loadMigrationGenerationContext(cwd);
|
|
181
182
|
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
if (
|
|
184
|
+
!hasProjectDatabaseSchemaSource({
|
|
185
|
+
cwd,
|
|
186
|
+
config: migrationProjectConfig,
|
|
187
|
+
existsSyncImpl,
|
|
188
|
+
resolvePathImpl,
|
|
189
|
+
})
|
|
190
|
+
) {
|
|
188
191
|
logImpl(`${DIM} ok No schema source — migration generation not required${RESET}`);
|
|
189
192
|
return SUPPORTED_DRIZZLE_KIT_GENERATOR_VERSION;
|
|
190
193
|
}
|
|
@@ -412,12 +415,13 @@ export function createDeployPackageRuntime({
|
|
|
412
415
|
/* ignore and keep safe defaults */
|
|
413
416
|
}
|
|
414
417
|
}
|
|
415
|
-
const filesToPack = ["
|
|
418
|
+
const filesToPack = ["package.json"];
|
|
416
419
|
let migrationManifestStage;
|
|
417
420
|
try {
|
|
418
421
|
migrationManifestStage = stageProjectMigrationManifest({
|
|
419
422
|
cwd,
|
|
420
423
|
config: migrationProjectConfig,
|
|
424
|
+
backendExcludes: envExcludes,
|
|
421
425
|
cliVersion,
|
|
422
426
|
generatorVersion,
|
|
423
427
|
existsSyncImpl,
|
|
@@ -427,6 +431,7 @@ export function createDeployPackageRuntime({
|
|
|
427
431
|
rmSyncImpl,
|
|
428
432
|
writeFileSyncImpl,
|
|
429
433
|
});
|
|
434
|
+
filesToPack.push(...migrationManifestStage.backendArchiveEntries);
|
|
430
435
|
if (migrationManifestStage.relativePath) filesToPack.push(migrationManifestStage.relativePath);
|
|
431
436
|
} catch (caught) {
|
|
432
437
|
errorImpl(`Migration bundle manifest generation failed: ${caught.message}`);
|
|
@@ -515,6 +520,13 @@ export function createDeployPackageRuntime({
|
|
|
515
520
|
|
|
516
521
|
const bundleBuffer = readFileSyncImpl(tmpBundle);
|
|
517
522
|
const bundleSize = statSyncImpl(tmpBundle).size;
|
|
523
|
+
try {
|
|
524
|
+
await verifyMigrationBundleArchiveRoundTrip({ bundleBuffer });
|
|
525
|
+
} catch (caught) {
|
|
526
|
+
errorImpl(`Migration bundle round-trip verification failed: ${caught.message}`);
|
|
527
|
+
exitImpl(1);
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
518
530
|
let deployClaim;
|
|
519
531
|
try {
|
|
520
532
|
deployClaim = await buildDeployClaimFromArchiveImpl({
|
package/lib/dev-cloud-bundle.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
formatDependencyVersionAuditError,
|
|
14
14
|
} from "./deploy-auditor.mjs";
|
|
15
15
|
import { shouldIncludeTarEntry } from "./tar-archive.mjs";
|
|
16
|
+
import { verifyMigrationBundleArchiveRoundTrip } from "./migration-bundle-roundtrip.mjs";
|
|
16
17
|
|
|
17
18
|
const OPTIONAL_BUNDLE_FILES = [
|
|
18
19
|
"gencow.config.js",
|
|
@@ -119,8 +120,7 @@ export async function createDevCloudDeployBundle({
|
|
|
119
120
|
writeFileSyncImpl = writeFileSync,
|
|
120
121
|
} = {}) {
|
|
121
122
|
const bundleRootDir = rootDir ?? functionsDir;
|
|
122
|
-
if (!cwd || !bundleRootDir)
|
|
123
|
-
throw new Error("cwd and rootDir are required to create a dev cloud bundle");
|
|
123
|
+
if (!cwd || !bundleRootDir) throw new Error("cwd and rootDir are required to create a dev cloud bundle");
|
|
124
124
|
|
|
125
125
|
const functionsSource = resolvePathImpl(cwd, bundleRootDir);
|
|
126
126
|
if (!existsSyncImpl(functionsSource)) throw new Error(`Backend root dir not found: ${functionsSource}`);
|
|
@@ -143,7 +143,7 @@ export async function createDevCloudDeployBundle({
|
|
|
143
143
|
mkdirSyncImpl(stagingDir, { recursive: true });
|
|
144
144
|
|
|
145
145
|
try {
|
|
146
|
-
const filesToPack = [
|
|
146
|
+
const filesToPack = ["package.json", DEPLOY_DEPENDENCY_AUDIT_MANIFEST];
|
|
147
147
|
const envExcludes = buildBackendEnvExcludePatterns({
|
|
148
148
|
rootDir: `./${bundleRootDir.replace(/^\.\/+/, "").replace(/[\\/]+$/, "")}`,
|
|
149
149
|
...(envFile ? { envFile } : {}),
|
|
@@ -156,7 +156,10 @@ export async function createDevCloudDeployBundle({
|
|
|
156
156
|
mkdirSyncImpl(dirname(manifestPath), { recursive: true });
|
|
157
157
|
writeFileSyncImpl(manifestPath, JSON.stringify(dependencyVersionResult.manifest, null, 2));
|
|
158
158
|
|
|
159
|
-
const cronManifest = await writeCronManifestForProjectImpl({
|
|
159
|
+
const cronManifest = await writeCronManifestForProjectImpl({
|
|
160
|
+
cwd: stagingDir,
|
|
161
|
+
functionsDir: bundleRootDir,
|
|
162
|
+
});
|
|
160
163
|
if (cronManifest) filesToPack.push(CRON_MANIFEST_RELATIVE_PATH);
|
|
161
164
|
|
|
162
165
|
const filteredPackage = buildFilteredPackageJson(projectPkg, auditResult?.runtimeDeps);
|
|
@@ -174,6 +177,7 @@ export async function createDevCloudDeployBundle({
|
|
|
174
177
|
const migrationManifestStage = stageProjectMigrationManifest({
|
|
175
178
|
cwd: stagingDir,
|
|
176
179
|
backendDir: bundleRootDir,
|
|
180
|
+
backendExcludes: envExcludes,
|
|
177
181
|
config,
|
|
178
182
|
cliVersion,
|
|
179
183
|
generatorVersion,
|
|
@@ -184,6 +188,7 @@ export async function createDevCloudDeployBundle({
|
|
|
184
188
|
rmSyncImpl,
|
|
185
189
|
writeFileSyncImpl,
|
|
186
190
|
});
|
|
191
|
+
filesToPack.push(...migrationManifestStage.backendArchiveEntries);
|
|
187
192
|
if (migrationManifestStage.relativePath) filesToPack.push(migrationManifestStage.relativePath);
|
|
188
193
|
|
|
189
194
|
try {
|
|
@@ -193,6 +198,7 @@ export async function createDevCloudDeployBundle({
|
|
|
193
198
|
tarCreateImpl,
|
|
194
199
|
excludes: envExcludes,
|
|
195
200
|
});
|
|
201
|
+
await verifyMigrationBundleArchiveRoundTrip({ bundleBuffer: bundle, backendDir: bundleRootDir });
|
|
196
202
|
return {
|
|
197
203
|
bundle,
|
|
198
204
|
auditMessage,
|
|
@@ -179,5 +179,6 @@ export function unsignedMigrationBundleManifest({
|
|
|
179
179
|
|
|
180
180
|
export function buildMigrationBundleManifest(params) {
|
|
181
181
|
const unsigned = unsignedMigrationBundleManifest(params);
|
|
182
|
-
|
|
182
|
+
const rows = params.bundleFilePlan?.bundleFileRows ?? bundleFileRows(params.backendRoot);
|
|
183
|
+
return { ...unsigned, bundleSha256: sha256(JSON.stringify(rows)) };
|
|
183
184
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MIGRATION_BUNDLE_MANIFEST_PATH,
|
|
7
|
+
buildMigrationBundleManifest,
|
|
8
|
+
} from "./migration-bundle-manifest.mjs";
|
|
9
|
+
|
|
10
|
+
export const MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED = "MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED";
|
|
11
|
+
|
|
12
|
+
function localIntegrityError(cause) {
|
|
13
|
+
const error = new Error(MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED, cause ? { cause } : undefined);
|
|
14
|
+
error.code = MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED;
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function verifyMigrationBundleArchiveRoundTrip({
|
|
19
|
+
archivePath,
|
|
20
|
+
bundleBuffer,
|
|
21
|
+
backendDir = "gencow",
|
|
22
|
+
}) {
|
|
23
|
+
const verificationRoot = mkdtempSync(join(tmpdir(), "gencow-migration-roundtrip-"));
|
|
24
|
+
try {
|
|
25
|
+
mkdirSync(verificationRoot, { recursive: true });
|
|
26
|
+
const verificationArchive = archivePath ?? resolve(verificationRoot, "bundle.tar.gz");
|
|
27
|
+
if (!archivePath) writeFileSync(verificationArchive, bundleBuffer, { mode: 0o600 });
|
|
28
|
+
const { extract } = await import("tar");
|
|
29
|
+
await extract({ cwd: verificationRoot, file: verificationArchive });
|
|
30
|
+
const manifestPath = resolve(verificationRoot, MIGRATION_BUNDLE_MANIFEST_PATH);
|
|
31
|
+
const backendRoot = resolve(verificationRoot, backendDir);
|
|
32
|
+
if (!existsSync(manifestPath) || !existsSync(backendRoot)) {
|
|
33
|
+
throw localIntegrityError();
|
|
34
|
+
}
|
|
35
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
36
|
+
const recomputed = buildMigrationBundleManifest({
|
|
37
|
+
backendRoot,
|
|
38
|
+
cliVersion: manifest.cliVersion,
|
|
39
|
+
generatorVersion: manifest.generatorVersion,
|
|
40
|
+
sourceMode: manifest.sourceMode,
|
|
41
|
+
schemaSourcePaths: manifest.schemaSources?.map((source) => resolve(backendRoot, source.path)),
|
|
42
|
+
});
|
|
43
|
+
if (JSON.stringify(recomputed) !== JSON.stringify(manifest)) {
|
|
44
|
+
throw localIntegrityError();
|
|
45
|
+
}
|
|
46
|
+
return manifest;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error?.code === MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
throw localIntegrityError(error);
|
|
52
|
+
} finally {
|
|
53
|
+
rmSync(verificationRoot, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
buildMigrationBundleManifest,
|
|
7
7
|
} from "./migration-bundle-manifest.mjs";
|
|
8
8
|
import { buildBackendEnvExcludePatterns } from "./backend-env-resolver.mjs";
|
|
9
|
+
import { buildBackendBundleFilePlan } from "./backend-bundle-file-plan.mjs";
|
|
9
10
|
import { createTarGzipArchive } from "./tar-archive.mjs";
|
|
11
|
+
import { verifyMigrationBundleArchiveRoundTrip } from "./migration-bundle-roundtrip.mjs";
|
|
10
12
|
|
|
11
13
|
export async function stageMigrationBundleArchive({
|
|
12
14
|
backendRoot,
|
|
@@ -21,6 +23,7 @@ export async function stageMigrationBundleArchive({
|
|
|
21
23
|
resolvePathImpl = resolve,
|
|
22
24
|
rmSyncImpl = rmSync,
|
|
23
25
|
tarCreateImpl,
|
|
26
|
+
verifyArchiveImpl = verifyMigrationBundleArchiveRoundTrip,
|
|
24
27
|
tmpdirImpl = tmpdir,
|
|
25
28
|
writeFileSyncImpl = writeFileSync,
|
|
26
29
|
}) {
|
|
@@ -34,6 +37,12 @@ export async function stageMigrationBundleArchive({
|
|
|
34
37
|
cpSyncImpl(backendRoot, stagedBackendRoot, { recursive: true, errorOnExist: true });
|
|
35
38
|
mkdirSyncImpl(resolvePathImpl(stagedBackendRoot, "migrations"), { recursive: true });
|
|
36
39
|
mkdirSyncImpl(metadataRoot, { recursive: true });
|
|
40
|
+
const excludes = buildBackendEnvExcludePatterns(config);
|
|
41
|
+
const bundleFilePlan = buildBackendBundleFilePlan({
|
|
42
|
+
backendRoot: stagedBackendRoot,
|
|
43
|
+
archiveRoot: "gencow",
|
|
44
|
+
excludes,
|
|
45
|
+
});
|
|
37
46
|
const manifest = buildMigrationBundleManifest({
|
|
38
47
|
backendRoot: stagedBackendRoot,
|
|
39
48
|
cliVersion,
|
|
@@ -42,15 +51,17 @@ export async function stageMigrationBundleArchive({
|
|
|
42
51
|
schemaSourcePaths: schemaSourcePaths?.map((sourcePath) =>
|
|
43
52
|
resolvePathImpl(stagedBackendRoot, relative(backendRoot, sourcePath)),
|
|
44
53
|
),
|
|
54
|
+
bundleFilePlan,
|
|
45
55
|
});
|
|
46
56
|
writeFileSyncImpl(manifestPath, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
|
|
47
57
|
await createTarGzipArchive({
|
|
48
58
|
cwd: workspaceRoot,
|
|
49
59
|
file: archivePath,
|
|
50
|
-
entries: [
|
|
51
|
-
excludes
|
|
60
|
+
entries: [...bundleFilePlan.archiveEntries, MIGRATION_BUNDLE_MANIFEST_PATH],
|
|
61
|
+
excludes,
|
|
52
62
|
tarCreateImpl,
|
|
53
63
|
});
|
|
64
|
+
await verifyArchiveImpl({ archivePath });
|
|
54
65
|
return { archivePath, cleanup, manifest, workspaceRoot };
|
|
55
66
|
} catch (error) {
|
|
56
67
|
cleanup();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MIGRATION_CAPABILITY_MANIFEST_V1,
|
|
3
|
+
platformActionDiagnostic,
|
|
3
4
|
retryableInfraDiagnostic,
|
|
4
5
|
userFixableDiagnostic,
|
|
5
6
|
} from "@gencow/migration-contract";
|
|
@@ -139,6 +140,27 @@ export function buildClientBundleFailureDiagnostic(correlationId, retryCommand)
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
export function buildLocalBundleIntegrityFailureDiagnostic(correlationId) {
|
|
144
|
+
return platformActionDiagnostic({
|
|
145
|
+
code: "MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED",
|
|
146
|
+
phase: "PREFLIGHT",
|
|
147
|
+
databaseState: "UNCHANGED",
|
|
148
|
+
correlationId,
|
|
149
|
+
retryCondition: "Install a patched CLI, then create a fresh migration bundle.",
|
|
150
|
+
reason: {
|
|
151
|
+
code: "migration_bundle_local_integrity_failed",
|
|
152
|
+
summary: "The local archive did not match its migration manifest.",
|
|
153
|
+
detail: "No remote request was sent and the cloud database was not changed.",
|
|
154
|
+
},
|
|
155
|
+
suggestedFixes: [],
|
|
156
|
+
nextAction: "UPDATE_CLI",
|
|
157
|
+
docsUrl: MIGRATION_CAPABILITY_MANIFEST_V1.docsUrl,
|
|
158
|
+
supportedCliRange: MIGRATION_CAPABILITY_MANIFEST_V1.supportedCliRange,
|
|
159
|
+
forbiddenActions: [...FORBIDDEN_ACTIONS],
|
|
160
|
+
redaction: { serverReturnedRawSql: false, localExcerpt: "NONE" },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
142
164
|
export function buildClientPreflightFailureDiagnostic(correlationId) {
|
|
143
165
|
return {
|
|
144
166
|
...buildClientBundleFailureDiagnostic(correlationId),
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
MIGRATION_BUNDLE_MANIFEST_PATH,
|
|
6
6
|
buildMigrationBundleManifest,
|
|
7
7
|
} from "./migration-bundle-manifest.mjs";
|
|
8
|
+
import { buildBackendEnvExcludePatterns } from "./backend-env-resolver.mjs";
|
|
9
|
+
import { buildBackendBundleFilePlan } from "./backend-bundle-file-plan.mjs";
|
|
8
10
|
import { resolveConfiguredSchemaPaths, resolveDrizzleSchemaPaths } from "./cli-schema-paths.mjs";
|
|
9
11
|
|
|
10
12
|
export const PROJECT_AUXILIARY_SCHEMA_PATHS = [
|
|
@@ -18,11 +20,7 @@ export const PROJECT_AUXILIARY_SCHEMA_PATHS = [
|
|
|
18
20
|
|
|
19
21
|
function declaredConfiguredSchemaPaths({ cwd, backendDir, config, resolvePathImpl }) {
|
|
20
22
|
if (!config?.schema) return [];
|
|
21
|
-
return resolveConfiguredSchemaPaths(
|
|
22
|
-
cwd,
|
|
23
|
-
resolvePathImpl(cwd, config.rootDir ?? backendDir),
|
|
24
|
-
config.schema,
|
|
25
|
-
);
|
|
23
|
+
return resolveConfiguredSchemaPaths(cwd, resolvePathImpl(cwd, config.rootDir ?? backendDir), config.schema);
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
function projectSchemaSourcePaths({
|
|
@@ -38,10 +36,11 @@ function projectSchemaSourcePaths({
|
|
|
38
36
|
const missing = declared.filter((pathValue) => !existsSyncImpl(pathValue));
|
|
39
37
|
if (missing.length > 0) throw new Error("Configured schema source is missing from the project");
|
|
40
38
|
}
|
|
41
|
-
return resolveDrizzleSchemaPaths(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
return resolveDrizzleSchemaPaths(config ?? { rootDir: backendDir }, {
|
|
40
|
+
cwd,
|
|
41
|
+
existsSyncImpl,
|
|
42
|
+
resolvePathImpl,
|
|
43
|
+
}).filter((pathValue) => existsSyncImpl(pathValue));
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export function hasProjectDatabaseSchemaSource({
|
|
@@ -52,14 +51,16 @@ export function hasProjectDatabaseSchemaSource({
|
|
|
52
51
|
resolvePathImpl = resolve,
|
|
53
52
|
}) {
|
|
54
53
|
if (declaredConfiguredSchemaPaths({ cwd, backendDir, config, resolvePathImpl }).length > 0) return true;
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
return (
|
|
55
|
+
projectSchemaSourcePaths({
|
|
56
|
+
cwd,
|
|
57
|
+
backendDir,
|
|
58
|
+
config,
|
|
59
|
+
existsSyncImpl,
|
|
60
|
+
resolvePathImpl,
|
|
61
|
+
requireDeclaredSources: false,
|
|
62
|
+
}).length > 0
|
|
63
|
+
);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
export function stageProjectMigrationManifest({
|
|
@@ -67,6 +68,7 @@ export function stageProjectMigrationManifest({
|
|
|
67
68
|
cliVersion,
|
|
68
69
|
generatorVersion,
|
|
69
70
|
backendDir = "gencow",
|
|
71
|
+
backendExcludes,
|
|
70
72
|
config,
|
|
71
73
|
existsSyncImpl = existsSync,
|
|
72
74
|
mkdirSyncImpl = mkdirSync,
|
|
@@ -84,6 +86,11 @@ export function stageProjectMigrationManifest({
|
|
|
84
86
|
resolvePathImpl,
|
|
85
87
|
requireDeclaredSources: true,
|
|
86
88
|
});
|
|
89
|
+
const bundleFilePlan = buildBackendBundleFilePlan({
|
|
90
|
+
backendRoot,
|
|
91
|
+
archiveRoot: backendDir,
|
|
92
|
+
excludes: backendExcludes ?? buildBackendEnvExcludePatterns(config),
|
|
93
|
+
});
|
|
87
94
|
|
|
88
95
|
const manifestPath = resolvePathImpl(cwd, MIGRATION_BUNDLE_MANIFEST_PATH);
|
|
89
96
|
const previous = existsSyncImpl(manifestPath) ? readFileSyncImpl(manifestPath) : null;
|
|
@@ -93,6 +100,7 @@ export function stageProjectMigrationManifest({
|
|
|
93
100
|
generatorVersion,
|
|
94
101
|
sourceMode: "GENERATED",
|
|
95
102
|
schemaSourcePaths,
|
|
103
|
+
bundleFilePlan,
|
|
96
104
|
});
|
|
97
105
|
mkdirSyncImpl(dirname(manifestPath), { recursive: true });
|
|
98
106
|
writeFileSyncImpl(manifestPath, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
|
|
@@ -101,6 +109,7 @@ export function stageProjectMigrationManifest({
|
|
|
101
109
|
return {
|
|
102
110
|
relativePath: MIGRATION_BUNDLE_MANIFEST_PATH,
|
|
103
111
|
manifest,
|
|
112
|
+
backendArchiveEntries: bundleFilePlan.archiveEntries,
|
|
104
113
|
cleanup() {
|
|
105
114
|
if (cleaned) return;
|
|
106
115
|
cleaned = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gencow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.181",
|
|
4
4
|
"description": "Gencow — AI Backend Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"tar": "^7.5.21",
|
|
28
28
|
"ws": "^8.21.1",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@gencow/migration-contract": "0.1.
|
|
30
|
+
"@gencow/migration-contract": "0.1.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.5",
|
|
34
34
|
"better-auth": "^1.6.23",
|
|
35
35
|
"@gencow/client": "0.2.6",
|
|
36
|
-
"@gencow/
|
|
37
|
-
"@gencow/
|
|
36
|
+
"@gencow/react": "0.2.6",
|
|
37
|
+
"@gencow/core": "0.1.42"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
|