gencow 0.1.194 → 0.1.196

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.
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
3
  import { resolve } from "path";
3
4
  import {
@@ -117,6 +118,7 @@ export function createAppCommand({
117
118
  clearTimeoutImpl = clearTimeout,
118
119
  confirmDeleteImpl = confirmDelete,
119
120
  createAbortControllerImpl = () => new AbortController(),
121
+ createDeleteRequestIdImpl = () => `app_delete_request_${randomUUID()}`,
120
122
  cwdImpl = () => process.cwd(),
121
123
  loadConfig,
122
124
  errorImpl = error,
@@ -313,7 +315,8 @@ ${dashboardLine}
313
315
  }
314
316
 
315
317
  infoImpl(`Deleting app "${name}"...`);
316
- const delRes = await rpcMutationImpl(creds, "apps.delete", { name });
318
+ const requestId = createDeleteRequestIdImpl();
319
+ const delRes = await rpcMutationImpl(creds, "apps.delete", { name, requestId });
317
320
  let delData = await readJsonObjectResponse(delRes);
318
321
  if (!isExactAppDeleteResponse(delData, name)) {
319
322
  const operationResult = await pollAppDeleteOperation({
@@ -1,6 +1,19 @@
1
1
  export const APP_DELETE_OPERATION_PATTERN =
2
2
  /^app_delete_(?:[a-f0-9]{32}|[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$/u;
3
3
 
4
+ const RETRY_CLASSES = new Set(["operator-action", "platform-action", "retryable-infra", "terminal-client"]);
5
+ const USER_ACTIONS = new Set([
6
+ "CONTACT_SUPPORT",
7
+ "EDIT_AND_CHECK",
8
+ "INSPECT_STATE",
9
+ "OPERATOR_ACTION",
10
+ "RETRY",
11
+ "UPGRADE_CLI",
12
+ "VIEW_DELETE_STATUS",
13
+ "VIEW_STATUS",
14
+ "WAIT",
15
+ ]);
16
+
4
17
  export function appResponseError(body, fallback) {
5
18
  if (!body || typeof body !== "object" || Array.isArray(body)) return fallback;
6
19
  const message = typeof body.error === "string" && body.error.trim() ? body.error.trim() : fallback;
@@ -20,5 +33,16 @@ export function appResponseError(body, fallback) {
20
33
  if (/^[a-z][a-z0-9_]{2,63}$/u.test(body.state ?? "")) {
21
34
  lines.push(`State: ${body.state}`);
22
35
  }
36
+ if (RETRY_CLASSES.has(body.retryClass) && USER_ACTIONS.has(body.userAction)) {
37
+ lines.push(`Retry class: ${body.retryClass}`);
38
+ lines.push(`Next action: ${body.userAction}`);
39
+ if (
40
+ Number.isSafeInteger(body.retryAfterMs) &&
41
+ body.retryAfterMs >= 0 &&
42
+ body.retryAfterMs <= 600_000
43
+ ) {
44
+ lines.push(`Retry after: ${body.retryAfterMs}ms`);
45
+ }
46
+ }
23
47
  return lines.join("\n");
24
48
  }
@@ -1,4 +1,59 @@
1
+ const SAFE_CORRELATION_ID = /^[A-Za-z0-9_-]{8,128}$/u;
2
+ const SAFE_PLATFORM_ERROR_DETAILS = new Map([
3
+ ["ACCOUNT_DELETED", "This account has been deleted."],
4
+ ["ACCOUNT_DELETION_PENDING", "This account is pending deletion."],
5
+ ["ACCOUNT_SUSPENDED", "This account is suspended."],
6
+ ["APP_RELEASE_CAPABILITY_UNAVAILABLE", "Release capability is temporarily unavailable."],
7
+ ["APP_RELEASE_POLICY_READ_UNAVAILABLE", "Release policy is temporarily unavailable."],
8
+ ]);
9
+
10
+ function safePlatformOrigin(value) {
11
+ try {
12
+ const url = new URL(value);
13
+ return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
1
19
  function commandErrorMessage(caught) {
20
+ if (caught instanceof Error && (caught.message === "APP_RELEASE_AUTH_REQUIRED" || caught.status === 401)) {
21
+ const lines = ["Login expired or invalid for this Platform."];
22
+ const platformOrigin = safePlatformOrigin(caught.platformUrl);
23
+ if (platformOrigin) lines.push(`Platform: ${platformOrigin}`);
24
+ const credentialEnvName =
25
+ caught.credentialSource === "environment" &&
26
+ (caught.credentialEnvName === "GENCOW_TOKEN" || caught.credentialEnvName === "GENCOW_DEPLOY_TOKEN")
27
+ ? caught.credentialEnvName
28
+ : null;
29
+ if (credentialEnvName) {
30
+ lines.push(`Credential source: ${credentialEnvName}`);
31
+ lines.push(
32
+ `Action: replace or unset ${credentialEnvName}; environment credentials override interactive login.`,
33
+ );
34
+ } else {
35
+ lines.push("Run: npx gencow@latest login");
36
+ }
37
+ if (typeof caught.correlationId === "string" && SAFE_CORRELATION_ID.test(caught.correlationId)) {
38
+ lines.push(`Correlation ID: ${caught.correlationId}`);
39
+ }
40
+ return lines.join("\n");
41
+ }
42
+ if (caught instanceof Error && caught.message) {
43
+ const platformOrigin = safePlatformOrigin(caught.platformUrl);
44
+ const correlationId =
45
+ typeof caught.correlationId === "string" && SAFE_CORRELATION_ID.test(caught.correlationId)
46
+ ? caught.correlationId
47
+ : null;
48
+ if (platformOrigin || correlationId || typeof caught.retryable === "boolean") {
49
+ const safeDetail = SAFE_PLATFORM_ERROR_DETAILS.get(caught.message);
50
+ const lines = [safeDetail ? `${caught.message}: ${safeDetail}` : caught.message];
51
+ if (platformOrigin) lines.push(`Platform: ${platformOrigin}`);
52
+ if (typeof caught.retryable === "boolean") lines.push(`Retryable: ${caught.retryable ? "yes" : "no"}`);
53
+ if (correlationId) lines.push(`Correlation ID: ${correlationId}`);
54
+ return lines.join("\n");
55
+ }
56
+ }
2
57
  if (caught instanceof Error && caught.message) return caught.message;
3
58
  return String(caught);
4
59
  }
@@ -544,6 +544,7 @@ export function buildDrizzleKitCommand(subcmd, options = {}) {
544
544
  dirnameImpl = dirname,
545
545
  existsSyncImpl = existsSync,
546
546
  processExecPath = process.execPath,
547
+ processVersions = process.versions,
547
548
  readFileSyncImpl = readFileSync,
548
549
  resolvePathImpl = resolve,
549
550
  } = options;
@@ -566,7 +567,10 @@ export function buildDrizzleKitCommand(subcmd, options = {}) {
566
567
  const invocation = buildDrizzleGeneratorInvocation({
567
568
  configPath: canonicalConfigPath,
568
569
  generatorBinPath: resolvedBin,
569
- processExecPath,
570
+ // drizzle-kit 1.x uses node:sqlite for migration metadata. The CLI itself
571
+ // may be launched by Bun through its shebang, but Bun does not implement
572
+ // node:sqlite, so run this Node-only tool with the Node executable on PATH.
573
+ processExecPath: typeof processVersions?.bun === "string" ? "node" : processExecPath,
570
574
  subcommand: subcmd,
571
575
  });
572
576
  return {
@@ -27,19 +27,29 @@ export function clearCreds() {
27
27
  }
28
28
 
29
29
  export function resolveCredsFromSources({ env = process.env, loadCredsImpl = loadCreds } = {}) {
30
- const envToken = env.GENCOW_TOKEN || env.GENCOW_DEPLOY_TOKEN;
30
+ const credentialEnvName = env.GENCOW_TOKEN
31
+ ? "GENCOW_TOKEN"
32
+ : env.GENCOW_DEPLOY_TOKEN
33
+ ? "GENCOW_DEPLOY_TOKEN"
34
+ : null;
35
+ const envToken = credentialEnvName ? env[credentialEnvName] : null;
31
36
  if (envToken) {
32
37
  const platformUrl = env.GENCOW_PLATFORM_URL || "https://gencow.app";
33
- return { apiKey: envToken, platformUrl: validatePlatformUrl(platformUrl) };
38
+ return {
39
+ apiKey: envToken,
40
+ platformUrl: validatePlatformUrl(platformUrl),
41
+ credentialSource: "environment",
42
+ credentialEnvName,
43
+ };
34
44
  }
35
45
 
36
46
  const creds = loadCredsImpl();
37
47
  if (!creds?.apiKey) return null;
38
48
  const platformUrl = env.GENCOW_PLATFORM_URL || creds.platformUrl;
39
49
  if (platformUrl) {
40
- return { ...creds, platformUrl: validatePlatformUrl(platformUrl) };
50
+ return { ...creds, platformUrl: validatePlatformUrl(platformUrl), credentialSource: "stored" };
41
51
  }
42
- return creds;
52
+ return { ...creds, credentialSource: "stored" };
43
53
  }
44
54
 
45
55
  export function formatPlatformCredentialsRequiredMessage({ isCi = Boolean(process.env.CI) } = {}) {
@@ -8,22 +8,87 @@ function releaseRequestId(explicit) {
8
8
  return explicit || `release_${randomUUID()}`;
9
9
  }
10
10
 
11
- async function responseError(response, fallback) {
12
- const body = await response.json().catch(() => ({}));
13
- const error = new Error(body.code || fallback);
14
- error.detail = body.error || response.statusText;
11
+ const SAFE_CORRELATION_ID = /^[A-Za-z0-9_-]{8,128}$/u;
12
+ const PUBLIC_RELEASE_ERROR_CODES = new Set([
13
+ "ACCOUNT_DELETED",
14
+ "ACCOUNT_DELETION_PENDING",
15
+ "ACCOUNT_SUSPENDED",
16
+ "APP_RELEASE_APP_NOT_FOUND",
17
+ "APP_RELEASE_ATTEMPT_NOT_FOUND",
18
+ "APP_RELEASE_ATTEMPT_TERMINAL",
19
+ "APP_RELEASE_AUTH_REQUIRED",
20
+ "APP_RELEASE_CAPABILITY_UNAVAILABLE",
21
+ "APP_RELEASE_COMPONENT_CONFLICT",
22
+ "APP_RELEASE_COMPONENT_INCOMPLETE",
23
+ "APP_RELEASE_COMPONENT_UNEXPECTED",
24
+ "APP_RELEASE_ENVIRONMENT_INVALID",
25
+ "APP_RELEASE_ENVIRONMENT_MISMATCH",
26
+ "APP_RELEASE_ENV_KEYS_MISSING",
27
+ "APP_RELEASE_IDEMPOTENCY_CONFLICT",
28
+ "APP_RELEASE_METADATA_INVALID",
29
+ "APP_RELEASE_POLICY_READ_UNAVAILABLE",
30
+ "APP_RELEASE_SOURCE_CONFLICT",
31
+ "APP_RELEASE_SOURCE_INVALID",
32
+ "APP_RELEASE_STATE_CONFLICT",
33
+ "APP_RELEASE_STORAGE_QUOTA_EXCEEDED",
34
+ "APP_RELEASE_VERSIONS_DISABLED",
35
+ "APP_SERVING_ROUTE_COMMIT_BLOCKED",
36
+ "APP_SERVING_ROUTE_COMMIT_PENDING",
37
+ "RELEASE_SOURCE_COMPONENT_MISMATCH",
38
+ ]);
39
+
40
+ function safePlatformOrigin(value) {
41
+ try {
42
+ const url = new URL(value);
43
+ return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function throwResponseError(response, fallback, body, creds = {}) {
50
+ const authRequired = response.status === 401;
51
+ const responseCode = PUBLIC_RELEASE_ERROR_CODES.has(body?.code) ? body.code : fallback;
52
+ const error = new Error(authRequired ? "APP_RELEASE_AUTH_REQUIRED" : responseCode);
15
53
  error.status = response.status;
54
+ const platformOrigin = safePlatformOrigin(creds.platformUrl);
55
+ if (platformOrigin) error.platformUrl = platformOrigin;
56
+ if (creds.credentialSource === "environment" || creds.credentialSource === "stored") {
57
+ error.credentialSource = creds.credentialSource;
58
+ }
59
+ if (creds.credentialEnvName === "GENCOW_TOKEN" || creds.credentialEnvName === "GENCOW_DEPLOY_TOKEN") {
60
+ error.credentialEnvName = creds.credentialEnvName;
61
+ }
62
+ if (typeof body.correlationId === "string" && SAFE_CORRELATION_ID.test(body.correlationId)) {
63
+ error.correlationId = body.correlationId;
64
+ }
65
+ error.retryable = authRequired
66
+ ? false
67
+ : typeof body.retryable === "boolean"
68
+ ? body.retryable
69
+ : response.status >= 500;
16
70
  throw error;
17
71
  }
18
72
 
73
+ async function responseError(response, fallback, creds, parsedBody) {
74
+ const body = parsedBody ?? ((await response.json().catch(() => ({}))) || {});
75
+ throwResponseError(response, fallback, body, creds);
76
+ }
77
+
19
78
  async function releaseCapability({ creds, appId, environment, platformFetchImpl }) {
20
79
  const response = await platformFetchImpl(
21
80
  creds,
22
81
  `/platform/apps/${appId}/release-capability?environment=${environment}`,
23
82
  { method: "GET" },
24
83
  );
25
- if (response.status === 404 || response.status === 405) return null;
26
- if (!response.ok) await responseError(response, "APP_RELEASE_CAPABILITY_FAILED");
84
+ if (response.status === 404 || response.status === 405) {
85
+ const body = (await response.json().catch(() => ({}))) || {};
86
+ if (typeof body?.code !== "string" || body.code.length === 0) return null;
87
+ await responseError(response, "APP_RELEASE_CAPABILITY_FAILED", creds, body);
88
+ }
89
+ if (!response.ok) {
90
+ await responseError(response, "APP_RELEASE_CAPABILITY_FAILED", creds);
91
+ }
27
92
  const body = await response.json().catch(() => ({}));
28
93
  if (body?.protocol !== "app-release-v1" || typeof body?.enabled !== "boolean") {
29
94
  throw new Error("APP_RELEASE_CAPABILITY_RESPONSE_INVALID");
@@ -66,7 +131,9 @@ export async function prepareReleaseAttempt({
66
131
  },
67
132
  body: sourcePackage.bundleBuffer,
68
133
  });
69
- if (!response.ok) await responseError(response, "APP_RELEASE_SOURCE_UPLOAD_FAILED");
134
+ if (!response.ok) {
135
+ await responseError(response, "APP_RELEASE_SOURCE_UPLOAD_FAILED", creds);
136
+ }
70
137
  const data = await response.json();
71
138
  if (!data.attemptId || data.state !== "source_stored") {
72
139
  throw new Error("APP_RELEASE_SOURCE_RESPONSE_INVALID");
@@ -97,14 +164,19 @@ export async function finalizeReleaseAttempt({
97
164
  body: JSON.stringify({ environment }),
98
165
  },
99
166
  );
100
- if (!response.ok) await responseError(response, "APP_RELEASE_FINALIZE_FAILED");
167
+ if (!response.ok) {
168
+ await responseError(response, "APP_RELEASE_FINALIZE_FAILED", creds);
169
+ }
101
170
  const data = await response.json();
102
171
  if (!data.release?.id || !Number.isSafeInteger(data.release.version)) {
103
172
  throw new Error("APP_RELEASE_FINALIZE_RESPONSE_INVALID");
104
173
  }
105
174
  return data.release;
106
175
  } catch (error) {
107
- const retryable = typeof error?.status !== "number" || error.status >= 500;
176
+ const retryable =
177
+ typeof error?.retryable === "boolean"
178
+ ? error.retryable
179
+ : typeof error?.status !== "number" || error.status >= 500;
108
180
  if (!retryable || attempt >= retryDelaysMs.length) throw error;
109
181
  await delayImpl(retryDelaysMs[attempt]);
110
182
  }
@@ -129,6 +201,8 @@ export async function failReleaseAttempt({
129
201
  body: JSON.stringify({ environment, failureStage, failureCode }),
130
202
  },
131
203
  );
132
- if (!response.ok) await responseError(response, "APP_RELEASE_FAIL_RECORD_FAILED");
204
+ if (!response.ok) {
205
+ await responseError(response, "APP_RELEASE_FAIL_RECORD_FAILED", creds);
206
+ }
133
207
  return await response.json();
134
208
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.194",
3
+ "version": "0.1.196",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,16 @@
19
19
  "scripts/",
20
20
  "dashboard/"
21
21
  ],
22
+ "scripts": {
23
+ "prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
24
+ "build": "node scripts/bundle-core.mjs && node scripts/bundle-internal.mjs && node scripts/bundle-server.mjs",
25
+ "build:runtime-bundles": "node scripts/bundle-core.mjs && node scripts/bundle-server.mjs",
26
+ "test:coverage": "node ../../scripts/run-package-coverage.mjs",
27
+ "coverage:compare": "node ../../scripts/compare-package-coverage.mjs",
28
+ "prepublishOnly": "npm run build && node scripts/pre-publish-check.mjs"
29
+ },
22
30
  "dependencies": {
31
+ "@gencow/migration-contract": "workspace:*",
23
32
  "@modelcontextprotocol/sdk": "^1.29.0",
24
33
  "drizzle-kit": "1.0.0-rc.4",
25
34
  "drizzle-orm": "1.0.0-rc.4",
@@ -27,21 +36,13 @@
27
36
  "open": "^10.2.0",
28
37
  "tar": "7.5.15",
29
38
  "ws": "^8.21.1",
30
- "zod": "^4.4.3",
31
- "@gencow/migration-contract": "0.1.12"
39
+ "zod": "^4.4.3"
32
40
  },
33
41
  "devDependencies": {
42
+ "@gencow/client": "workspace:*",
43
+ "@gencow/core": "workspace:*",
44
+ "@gencow/react": "workspace:*",
34
45
  "@types/node": "^25.9.5",
35
- "better-auth": "^1.6.23",
36
- "@gencow/core": "0.1.42",
37
- "@gencow/client": "0.2.6",
38
- "@gencow/react": "0.2.6"
39
- },
40
- "scripts": {
41
- "prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
42
- "build": "node scripts/bundle-core.mjs && node scripts/bundle-internal.mjs && node scripts/bundle-server.mjs",
43
- "build:runtime-bundles": "node scripts/bundle-core.mjs && node scripts/bundle-server.mjs",
44
- "test:coverage": "node ../../scripts/run-package-coverage.mjs",
45
- "coverage:compare": "node ../../scripts/compare-package-coverage.mjs"
46
+ "better-auth": "^1.6.23"
46
47
  }
47
- }
48
+ }