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.
- package/lib/app-command.mjs +4 -1
- package/lib/app-response-error.mjs +24 -0
- package/lib/cli-command-runner.mjs +55 -0
- package/lib/cli-project-runtime.mjs +5 -1
- package/lib/platform-client.mjs +14 -4
- package/lib/release-client.mjs +84 -10
- package/package.json +16 -15
- package/runtime/server.mjs +2047 -1309
- package/server/index.js.map +7 -0
package/lib/app-command.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 {
|
package/lib/platform-client.mjs
CHANGED
|
@@ -27,19 +27,29 @@ export function clearCreds() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function resolveCredsFromSources({ env = process.env, loadCredsImpl = loadCreds } = {}) {
|
|
30
|
-
const
|
|
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 {
|
|
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) } = {}) {
|
package/lib/release-client.mjs
CHANGED
|
@@ -8,22 +8,87 @@ function releaseRequestId(explicit) {
|
|
|
8
8
|
return explicit || `release_${randomUUID()}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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)
|
|
26
|
-
|
|
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)
|
|
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)
|
|
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 =
|
|
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)
|
|
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.
|
|
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
|
+
}
|