gencow 0.1.231 → 0.1.232
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/core/index.js +1 -0
- package/lib/db-push-command.mjs +3 -1
- package/lib/deploy-package-runtime.mjs +10 -1
- package/lib/dev-cloud-command.mjs +37 -16
- package/lib/migration-bundle-manifest.mjs +40 -0
- package/lib/migration-client-diagnostic.mjs +6 -0
- package/lib/tenant-capability-compat.mjs +49 -0
- package/package.json +4 -4
- package/runtime/server.mjs +2092 -1676
- package/runtime/tooling.mjs +1 -0
- package/server/index.js.map +0 -7
package/core/index.js
CHANGED
|
@@ -3016,6 +3016,7 @@ var RESERVED_TENANT_RUNTIME_ENV_KEYS = [
|
|
|
3016
3016
|
"GENCOW_PLATFORM_DB",
|
|
3017
3017
|
"GENCOW_MANAGED_PUBLIC_ORIGINS",
|
|
3018
3018
|
"PLATFORM_OPENAI_KEY",
|
|
3019
|
+
"PLATFORM_OPENROUTER_KEY",
|
|
3019
3020
|
"PLATFORM_GOOGLE_KEY",
|
|
3020
3021
|
"PLATFORM_GOOGLE_CLIENT_ID",
|
|
3021
3022
|
"PLATFORM_GOOGLE_CLIENT_SECRET",
|
package/lib/db-push-command.mjs
CHANGED
|
@@ -445,7 +445,9 @@ function createDbMigrationCommand(deps, command) {
|
|
|
445
445
|
emitFailure(
|
|
446
446
|
error?.code === "MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED"
|
|
447
447
|
? buildLocalBundleIntegrityFailureDiagnostic(clientCorrelationId())
|
|
448
|
-
:
|
|
448
|
+
: error?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION"
|
|
449
|
+
? buildClientUserFixableDiagnostic(error.code, clientCorrelationId())
|
|
450
|
+
: buildClientBundleFailureDiagnostic(clientCorrelationId(), retryCommand),
|
|
449
451
|
);
|
|
450
452
|
return;
|
|
451
453
|
}
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./deploy-lockfile-selection.mjs";
|
|
43
43
|
import { detectProjectConfigFile, warnProjectConfigSelectionOnce } from "./project-config-selection.mjs";
|
|
44
44
|
import { writeCanonicalDeployBundle } from "./deploy-bundle-staging.mjs";
|
|
45
|
+
import { buildClientUserFixableDiagnostic } from "./migration-client-diagnostic.mjs";
|
|
45
46
|
|
|
46
47
|
async function loadProjectConfigDefault(options) {
|
|
47
48
|
const { loadConfig } = await import("./cli-project-runtime.mjs");
|
|
@@ -484,7 +485,15 @@ export function createDeployPackageRuntime({
|
|
|
484
485
|
writeFileSyncImpl,
|
|
485
486
|
}));
|
|
486
487
|
} catch (caught) {
|
|
487
|
-
|
|
488
|
+
if (caught?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION") {
|
|
489
|
+
for (const line of renderMigrationDiagnosticLines(
|
|
490
|
+
buildClientUserFixableDiagnostic(caught.code, `client-${Date.now()}`),
|
|
491
|
+
)) {
|
|
492
|
+
errorImpl(line);
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
errorImpl(`Packaging failed: ${caught.message}`);
|
|
496
|
+
}
|
|
488
497
|
exitImpl(1);
|
|
489
498
|
cleanupPreparedDeploySource();
|
|
490
499
|
return null;
|
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
import { emitMigrationAdvisories, emitMigrationResponsibilityNotice } from "./migration-advisory.mjs";
|
|
26
26
|
import { resolveCreatedAppResponse } from "./app-create-response.mjs";
|
|
27
27
|
import { readProjectDisplayName } from "./cli-project-runtime.mjs";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
getCliInvocationSelection,
|
|
30
|
+
resolveProjectMetadataPath,
|
|
31
|
+
resolveProjectSelection,
|
|
32
|
+
} from "./project-context.mjs";
|
|
33
|
+
import { buildClientUserFixableDiagnostic } from "./migration-client-diagnostic.mjs";
|
|
29
34
|
|
|
30
35
|
export function resolveDevCloudAppName({
|
|
31
36
|
cloudArgs,
|
|
@@ -264,20 +269,35 @@ export function createDevCloudCommand(deps) {
|
|
|
264
269
|
({ createDevCloudDeployBundle: createDevCloudDeployBundleImpl } =
|
|
265
270
|
await import("./dev-cloud-bundle.mjs"));
|
|
266
271
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
272
|
+
let bundle;
|
|
273
|
+
let auditMessage;
|
|
274
|
+
try {
|
|
275
|
+
({ bundle, auditMessage } = await createDevCloudDeployBundleImpl({
|
|
276
|
+
cwd: projectDir,
|
|
277
|
+
rootDir: functionsDir,
|
|
278
|
+
envFile: config.envFile,
|
|
279
|
+
cliVersion: deps.cliVersion ?? "0.0.0",
|
|
280
|
+
generatorVersion: migrationGeneratorVersion,
|
|
281
|
+
config,
|
|
282
|
+
existsSyncImpl: deps.existsSyncImpl,
|
|
283
|
+
mkdirSyncImpl: deps.mkdirSyncImpl,
|
|
284
|
+
readFileSyncImpl: deps.readFileSyncImpl,
|
|
285
|
+
resolvePathImpl: deps.resolvePathImpl,
|
|
286
|
+
warnImpl: deps.warnImpl,
|
|
287
|
+
writeFileSyncImpl: deps.writeFileSyncImpl,
|
|
288
|
+
}));
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION") {
|
|
291
|
+
for (const line of renderMigrationDiagnosticLines(
|
|
292
|
+
buildClientUserFixableDiagnostic(error.code, `client-${Date.now()}`),
|
|
293
|
+
)) {
|
|
294
|
+
deps.errorImpl(line);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
281
301
|
if (auditMessage) deps.logImpl(auditMessage);
|
|
282
302
|
const protocolHeaders = await buildDevCloudDeployProtocolHeaders({
|
|
283
303
|
bundle,
|
|
@@ -483,7 +503,8 @@ ${deps.BOLD}${deps.CYAN}🚀 Gencow Cloud Dev${deps.RESET}
|
|
|
483
503
|
const GENERATED_FILES = new Set(["api.ts", "README.md"]);
|
|
484
504
|
watchImpl(absoluteFunctions, { recursive: true }, (_eventType, filename) => {
|
|
485
505
|
if (!filename || filename.includes("node_modules")) return;
|
|
486
|
-
if (isServerCodegenWatchPath(filename, projectDir, config, { resolvePathImpl: deps.resolvePathImpl }))
|
|
506
|
+
if (isServerCodegenWatchPath(filename, projectDir, config, { resolvePathImpl: deps.resolvePathImpl }))
|
|
507
|
+
return;
|
|
487
508
|
const base = (filename.includes("/") ? filename.split("/").pop() : filename) ?? filename;
|
|
488
509
|
if (GENERATED_FILES.has(base)) return;
|
|
489
510
|
pendingFile = filename;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
MIGRATION_BUNDLE_PROTOCOL_VERSION,
|
|
8
8
|
parseMigrationFolderMillis,
|
|
9
9
|
} from "@gencow/migration-contract";
|
|
10
|
+
import { deriveTenantCapabilities, isAuthSchemaSourcePath } from "./tenant-capability-compat.mjs";
|
|
10
11
|
|
|
11
12
|
export { MIGRATION_BUNDLE_CAPABILITY_V1, MIGRATION_BUNDLE_MANIFEST_PATH, MIGRATION_BUNDLE_PROTOCOL_VERSION };
|
|
12
13
|
|
|
@@ -133,6 +134,37 @@ function bundleFileRows(backendRoot, directory = backendRoot) {
|
|
|
133
134
|
return rows;
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
function tenantCapabilitySources(backendRoot, directory = backendRoot) {
|
|
138
|
+
const sources = [];
|
|
139
|
+
for (const entry of readdirSync(directory).sort()) {
|
|
140
|
+
const path = resolve(directory, entry);
|
|
141
|
+
const stats = lstatSync(path);
|
|
142
|
+
if (stats.isDirectory()) {
|
|
143
|
+
sources.push(...tenantCapabilitySources(backendRoot, path));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const manifestPath = canonicalMigrationManifestPath(relative(backendRoot, path));
|
|
147
|
+
if (stats.isFile() && isAuthSchemaSourcePath(manifestPath)) {
|
|
148
|
+
sources.push({ path: manifestPath, source: readFileSync(path, "utf8") });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return sources;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function hasRateLimitCreationMigration(migrations) {
|
|
155
|
+
return migrations.some(({ path }) =>
|
|
156
|
+
/\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:["']?public["']?\.)?["']?rate_limit["']?\b/iu.test(
|
|
157
|
+
readFileSync(path, "utf8"),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function schemaChangeWithoutForwardMigrationError() {
|
|
163
|
+
const error = new Error("MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION");
|
|
164
|
+
error.code = "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION";
|
|
165
|
+
return error;
|
|
166
|
+
}
|
|
167
|
+
|
|
136
168
|
function schemaSourceRows(backendRoot, schemaSourcePaths) {
|
|
137
169
|
if (schemaSourcePaths === undefined) return null;
|
|
138
170
|
const rows = [];
|
|
@@ -183,6 +215,13 @@ export function unsignedMigrationBundleManifest({
|
|
|
183
215
|
sqlSha256: sha256(readFileSync(migration.path)),
|
|
184
216
|
requiredCapabilities: [MIGRATION_BUNDLE_CAPABILITY_V1],
|
|
185
217
|
}));
|
|
218
|
+
const requiredTenantCapabilities = deriveTenantCapabilities(tenantCapabilitySources(backendRoot));
|
|
219
|
+
if (
|
|
220
|
+
requiredTenantCapabilities.length > 0 &&
|
|
221
|
+
!hasRateLimitCreationMigration(migrationSourceFiles(backendRoot))
|
|
222
|
+
) {
|
|
223
|
+
throw schemaChangeWithoutForwardMigrationError();
|
|
224
|
+
}
|
|
186
225
|
return {
|
|
187
226
|
protocolVersion: MIGRATION_BUNDLE_PROTOCOL_VERSION,
|
|
188
227
|
generator: "drizzle-kit",
|
|
@@ -191,6 +230,7 @@ export function unsignedMigrationBundleManifest({
|
|
|
191
230
|
cliVersion,
|
|
192
231
|
...(sources === null ? {} : { schemaSources: sources }),
|
|
193
232
|
schemaSnapshotHash: sources === null ? sha256(readFileSync(schemaPath)) : sha256(JSON.stringify(sources)),
|
|
233
|
+
...(requiredTenantCapabilities.length > 0 ? { requiredTenantCapabilities } : {}),
|
|
194
234
|
migrations,
|
|
195
235
|
};
|
|
196
236
|
}
|
|
@@ -48,6 +48,12 @@ const USER_FIXABLE_FAILURES = {
|
|
|
48
48
|
detail:
|
|
49
49
|
"Fix schema imports, types, or Drizzle configuration before uploading any existing migration bundle.",
|
|
50
50
|
},
|
|
51
|
+
MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION: {
|
|
52
|
+
phase: "PREFLIGHT",
|
|
53
|
+
target: "PENDING_MIGRATION",
|
|
54
|
+
summary: "The durable Auth schema changed without a forward migration.",
|
|
55
|
+
detail: "Run gencow db:generate, review the rate_limit migration, then create a fresh migration bundle.",
|
|
56
|
+
},
|
|
51
57
|
MIGRATION_CHECK_CLOUD_ONLY: {
|
|
52
58
|
phase: "PREFLIGHT",
|
|
53
59
|
target: "PROJECT_CONFIG",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as migrationContract from "@gencow/migration-contract";
|
|
2
|
+
|
|
3
|
+
const AUTH_SCHEMA_FILE_PATTERN = /(?:^|\/)(?:schema-auth|auth-schema)(?:\.gen)?\.(?:c|m)?(?:j|t)s$/u;
|
|
4
|
+
const AUTH_RATE_LIMIT_CAPABILITY = "tenant.auth.rate_limit.v1";
|
|
5
|
+
|
|
6
|
+
function removeComments(source) {
|
|
7
|
+
return source.replace(/\/\*[\s\S]*?\*\//gu, " ").replace(/(^|[^:])\/\/.*$/gmu, "$1 ");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function fallbackIsAuthSchemaSourcePath(path) {
|
|
11
|
+
return AUTH_SCHEMA_FILE_PATTERN.test(String(path).replaceAll("\\", "/"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fallbackSourceDeclaresDurableAuthRateLimit(source) {
|
|
15
|
+
const normalized = removeComments(source);
|
|
16
|
+
return (
|
|
17
|
+
/\b(?:export\s+)?const\s+rateLimit\s*=\s*pgTable\s*\(\s*["']rate_limit["']/u.test(normalized) ||
|
|
18
|
+
/\b(?:export\s+)?const\s+rateLimits\s*=\s*pgTable\s*\(\s*["']rate_limit["']/u.test(normalized)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// A CLI can be linked against an older published migration-contract while the
|
|
23
|
+
// Platform has already learned a newer source shape (for example generated
|
|
24
|
+
// schema-auth.gen.ts). Capability detection must therefore be monotonic: an
|
|
25
|
+
// older contract may not make a bundle under-declare a capability that the
|
|
26
|
+
// Platform will enforce.
|
|
27
|
+
export function createTenantCapabilityCompatibility(contract = migrationContract) {
|
|
28
|
+
return {
|
|
29
|
+
isAuthSchemaSourcePath: (path) =>
|
|
30
|
+
Boolean(contract.isAuthSchemaSourcePath?.(path)) || fallbackIsAuthSchemaSourcePath(path),
|
|
31
|
+
deriveTenantCapabilities: (sources) => {
|
|
32
|
+
const contractCapabilities =
|
|
33
|
+
typeof contract.deriveTenantCapabilities === "function"
|
|
34
|
+
? contract.deriveTenantCapabilities(sources)
|
|
35
|
+
: [];
|
|
36
|
+
const fallbackCapabilities = sources.some(
|
|
37
|
+
(source) =>
|
|
38
|
+
fallbackIsAuthSchemaSourcePath(source.path) &&
|
|
39
|
+
fallbackSourceDeclaresDurableAuthRateLimit(source.source),
|
|
40
|
+
)
|
|
41
|
+
? [AUTH_RATE_LIMIT_CAPABILITY]
|
|
42
|
+
: [];
|
|
43
|
+
return [...new Set([...contractCapabilities, ...fallbackCapabilities])];
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const compatibility = createTenantCapabilityCompatibility();
|
|
49
|
+
export const { isAuthSchemaSourcePath, deriveTenantCapabilities } = compatibility;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gencow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.232",
|
|
4
4
|
"description": "Gencow — AI Backend Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dashboard/"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@gencow/migration-contract": "0.1.
|
|
23
|
+
"@gencow/migration-contract": "0.1.16",
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
25
25
|
"drizzle-kit": "1.0.0-rc.4",
|
|
26
26
|
"drizzle-orm": "1.0.0-rc.4",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"ai": "^6.0.233",
|
|
38
38
|
"@types/node": "^25.9.5",
|
|
39
39
|
"better-auth": "^1.6.23",
|
|
40
|
-
"@gencow/core": "0.1.44",
|
|
41
40
|
"@gencow/client": "0.2.7",
|
|
42
|
-
"@gencow/react": "0.2.7"
|
|
41
|
+
"@gencow/react": "0.2.7",
|
|
42
|
+
"@gencow/core": "0.1.44"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
|