gencow 0.1.193 → 0.1.194
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/bin/gencow.mjs +8 -10
- package/lib/app-command.mjs +62 -37
- package/lib/cli-command-runner.mjs +27 -0
- package/lib/deploy-runtime.mjs +6 -6
- package/lib/platform-client.mjs +14 -5
- package/lib/static-command.mjs +14 -7
- package/lib/template-marketplace-command.mjs +12 -8
- package/package.json +3 -3
- package/runtime/server.mjs +82 -18
package/bin/gencow.mjs
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
updateEnvLocalUrl,
|
|
39
39
|
} from "../lib/cli-project-runtime.mjs";
|
|
40
40
|
import { createCodegenCommand } from "../lib/codegen-command.mjs";
|
|
41
|
+
import { runCliCommand } from "../lib/cli-command-runner.mjs";
|
|
41
42
|
import { createDoctorCommand } from "../lib/doctor-command.mjs";
|
|
42
43
|
import { maybeCheckCliVersion } from "../lib/cli-version-check.mjs";
|
|
43
44
|
import { updateComponentReadme } from "../lib/component-readme.mjs";
|
|
@@ -579,13 +580,10 @@ maybeCheckCliVersion({
|
|
|
579
580
|
warnImpl: warn,
|
|
580
581
|
});
|
|
581
582
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
commands.help();
|
|
590
|
-
process.exit(1);
|
|
591
|
-
}
|
|
583
|
+
process.exitCode = await runCliCommand({
|
|
584
|
+
args,
|
|
585
|
+
command: cmd,
|
|
586
|
+
commands,
|
|
587
|
+
errorImpl: error,
|
|
588
|
+
helpImpl: commands.help,
|
|
589
|
+
});
|
package/lib/app-command.mjs
CHANGED
|
@@ -24,6 +24,14 @@ import { updateEnvLocalUrl } from "./cli-project-runtime.mjs";
|
|
|
24
24
|
// Covers the 60s runtime-observer restart grace plus multiple 5s reconciliation scans.
|
|
25
25
|
const DEFAULT_DELETE_POLL_ATTEMPTS = 75;
|
|
26
26
|
const DELETE_POLL_INTERVAL_MS = 1_000;
|
|
27
|
+
const DEFAULT_APP_CREATE_TIMEOUT_MS = 60_000;
|
|
28
|
+
|
|
29
|
+
function resolveAppCreateTimeoutMs(value) {
|
|
30
|
+
const parsed = Number(value);
|
|
31
|
+
return Number.isSafeInteger(parsed) && parsed >= 100 && parsed <= 600_000
|
|
32
|
+
? parsed
|
|
33
|
+
: DEFAULT_APP_CREATE_TIMEOUT_MS;
|
|
34
|
+
}
|
|
27
35
|
|
|
28
36
|
function appResponseStatus(response) {
|
|
29
37
|
return Number.isSafeInteger(response?.status) ? `HTTP ${response.status}` : "unknown HTTP status";
|
|
@@ -105,23 +113,52 @@ async function confirmDelete(name) {
|
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
export function createAppCommand({
|
|
116
|
+
appCreateTimeoutMs = resolveAppCreateTimeoutMs(process.env.GENCOW_APP_CREATE_TIMEOUT_MS),
|
|
117
|
+
clearTimeoutImpl = clearTimeout,
|
|
118
|
+
confirmDeleteImpl = confirmDelete,
|
|
119
|
+
createAbortControllerImpl = () => new AbortController(),
|
|
108
120
|
cwdImpl = () => process.cwd(),
|
|
109
121
|
loadConfig,
|
|
110
122
|
errorImpl = error,
|
|
111
123
|
infoImpl = info,
|
|
112
124
|
loadCredsImpl = loadCreds,
|
|
113
125
|
logImpl = log,
|
|
114
|
-
processRef = process,
|
|
115
126
|
requireCredsImpl = requireCreds,
|
|
116
127
|
rpcMutationImpl = rpcMutation,
|
|
117
128
|
rpcQueryImpl = rpcQuery,
|
|
118
129
|
saveCredsImpl = saveCreds,
|
|
130
|
+
setTimeoutImpl = setTimeout,
|
|
119
131
|
sleepImpl = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms)),
|
|
120
132
|
deletePollAttempts = DEFAULT_DELETE_POLL_ATTEMPTS,
|
|
121
133
|
successImpl = success,
|
|
122
134
|
updateEnvLocalUrlImpl = updateEnvLocalUrl,
|
|
123
135
|
warnImpl = warn,
|
|
124
136
|
}) {
|
|
137
|
+
const boundedAppCreateTimeoutMs = resolveAppCreateTimeoutMs(appCreateTimeoutMs);
|
|
138
|
+
|
|
139
|
+
async function requestAppCreate(creds, name) {
|
|
140
|
+
const controller = createAbortControllerImpl();
|
|
141
|
+
const timeoutError = new Error(
|
|
142
|
+
`App creation request timed out after ${boundedAppCreateTimeoutMs}ms. Please try again.`,
|
|
143
|
+
);
|
|
144
|
+
let timeoutHandle;
|
|
145
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
146
|
+
timeoutHandle = setTimeoutImpl(() => {
|
|
147
|
+
controller.abort(timeoutError);
|
|
148
|
+
reject(timeoutError);
|
|
149
|
+
}, boundedAppCreateTimeoutMs);
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
const request = (async () => {
|
|
153
|
+
const response = await rpcMutationImpl(creds, "apps.create", { name }, { signal: controller.signal });
|
|
154
|
+
return { response, data: await readJsonObjectResponse(response) };
|
|
155
|
+
})();
|
|
156
|
+
return await Promise.race([request, timeoutPromise]);
|
|
157
|
+
} finally {
|
|
158
|
+
clearTimeoutImpl(timeoutHandle);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
125
162
|
return async function app(subcmd, ...rest) {
|
|
126
163
|
if (subcmd === "--help" || subcmd === "-h" || rest.includes("--help") || rest.includes("-h")) {
|
|
127
164
|
logImpl(`\n${BOLD}${CYAN}gencow app${RESET} — App management\n`);
|
|
@@ -131,10 +168,10 @@ export function createAppCommand({
|
|
|
131
168
|
logImpl(` ${CYAN}create${RESET} Create a new app`);
|
|
132
169
|
logImpl(` ${CYAN}delete${RESET} Delete an app (confirmation required)`);
|
|
133
170
|
logImpl(` ${CYAN}status${RESET} Show app status\n`);
|
|
134
|
-
return;
|
|
171
|
+
return 0;
|
|
135
172
|
}
|
|
136
173
|
|
|
137
|
-
const creds = requireCredsImpl();
|
|
174
|
+
const creds = requireCredsImpl({ throwOnMissing: true });
|
|
138
175
|
|
|
139
176
|
if (!subcmd || subcmd === "list") {
|
|
140
177
|
logImpl(`\n${BOLD}${CYAN}Your Apps${RESET}\n`);
|
|
@@ -142,18 +179,16 @@ export function createAppCommand({
|
|
|
142
179
|
const body = await res.json().catch(() => null);
|
|
143
180
|
if (!res.ok) {
|
|
144
181
|
errorImpl(appResponseError(body, invalidAppResponse("app list", res, "error")));
|
|
145
|
-
|
|
146
|
-
return;
|
|
182
|
+
return 1;
|
|
147
183
|
}
|
|
148
184
|
const apps = parseAppListResponse(body);
|
|
149
185
|
if (!apps) {
|
|
150
186
|
errorImpl(invalidAppResponse("app list", res, "success"));
|
|
151
|
-
|
|
152
|
-
return;
|
|
187
|
+
return 1;
|
|
153
188
|
}
|
|
154
189
|
if (!apps.length) {
|
|
155
190
|
infoImpl("No apps yet. Run: gencow app create <name>");
|
|
156
|
-
return;
|
|
191
|
+
return 0;
|
|
157
192
|
}
|
|
158
193
|
|
|
159
194
|
logImpl(` ${"NAME".padEnd(22)} ${"STATUS".padEnd(12)} ${"URL".padEnd(38)} DEPLOYED`);
|
|
@@ -167,29 +202,26 @@ export function createAppCommand({
|
|
|
167
202
|
);
|
|
168
203
|
}
|
|
169
204
|
logImpl("");
|
|
170
|
-
return;
|
|
205
|
+
return 0;
|
|
171
206
|
}
|
|
172
207
|
|
|
173
208
|
if (subcmd === "create") {
|
|
174
209
|
const name = rest[0];
|
|
175
210
|
if (!name) {
|
|
176
211
|
errorImpl("Usage: gencow app create <name>");
|
|
177
|
-
|
|
212
|
+
return 1;
|
|
178
213
|
}
|
|
179
214
|
|
|
180
215
|
logImpl(`\n${BOLD}${CYAN}Gencow App Create${RESET}\n`);
|
|
181
216
|
infoImpl(`Creating app "${name}"...`);
|
|
182
|
-
const res = await
|
|
183
|
-
const data = await readJsonObjectResponse(res);
|
|
217
|
+
const { response: res, data } = await requestAppCreate(creds, name);
|
|
184
218
|
if (!res.ok) {
|
|
185
219
|
errorImpl(appResponseError(data, invalidAppResponse("app create", res, "error")));
|
|
186
|
-
|
|
187
|
-
return;
|
|
220
|
+
return 1;
|
|
188
221
|
}
|
|
189
222
|
if (!data) {
|
|
190
223
|
errorImpl(invalidAppResponse("app create", res, "success"));
|
|
191
|
-
|
|
192
|
-
return;
|
|
224
|
+
return 1;
|
|
193
225
|
}
|
|
194
226
|
|
|
195
227
|
let createdApp;
|
|
@@ -197,8 +229,7 @@ export function createAppCommand({
|
|
|
197
229
|
createdApp = resolveCreatedAppResponse(data, { platformUrl: creds.platformUrl });
|
|
198
230
|
} catch (caught) {
|
|
199
231
|
errorImpl(caught.message);
|
|
200
|
-
|
|
201
|
-
return;
|
|
232
|
+
return 1;
|
|
202
233
|
}
|
|
203
234
|
const { appId, appUrl } = createdApp;
|
|
204
235
|
|
|
@@ -259,7 +290,7 @@ ${dashboardLine}
|
|
|
259
290
|
|
|
260
291
|
${DIM}pnpm gencow dev — watch & auto-deploy to cloud + live logs${RESET}
|
|
261
292
|
`);
|
|
262
|
-
return;
|
|
293
|
+
return 0;
|
|
263
294
|
}
|
|
264
295
|
|
|
265
296
|
if (subcmd === "delete") {
|
|
@@ -270,15 +301,14 @@ ${dashboardLine}
|
|
|
270
301
|
const name = rest.find((value) => !value.startsWith("-")) || loadCredsImpl()?.currentApp;
|
|
271
302
|
if (!name || unsupportedOption) {
|
|
272
303
|
errorImpl("Usage: gencow app delete <name> [--force]");
|
|
273
|
-
|
|
274
|
-
return;
|
|
304
|
+
return 1;
|
|
275
305
|
}
|
|
276
306
|
|
|
277
307
|
if (!force) {
|
|
278
|
-
const confirmed = await
|
|
308
|
+
const confirmed = await confirmDeleteImpl(name);
|
|
279
309
|
if (!confirmed) {
|
|
280
310
|
warnImpl("Cancelled — name did not match.");
|
|
281
|
-
|
|
311
|
+
return 0;
|
|
282
312
|
}
|
|
283
313
|
}
|
|
284
314
|
|
|
@@ -304,8 +334,7 @@ ${dashboardLine}
|
|
|
304
334
|
invalidAppResponse("app delete", delRes, delRes.ok ? "success" : "error"),
|
|
305
335
|
),
|
|
306
336
|
);
|
|
307
|
-
|
|
308
|
-
return;
|
|
337
|
+
return 1;
|
|
309
338
|
}
|
|
310
339
|
|
|
311
340
|
const currentCreds = loadCredsImpl();
|
|
@@ -314,15 +343,14 @@ ${dashboardLine}
|
|
|
314
343
|
}
|
|
315
344
|
|
|
316
345
|
successImpl(`App "${name}" deleted`);
|
|
317
|
-
return;
|
|
346
|
+
return 0;
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
if (subcmd === "status") {
|
|
321
350
|
const name = rest[0] || loadCredsImpl()?.currentApp;
|
|
322
351
|
if (!name) {
|
|
323
352
|
errorImpl("Usage: gencow app status <name>");
|
|
324
|
-
|
|
325
|
-
return;
|
|
353
|
+
return 1;
|
|
326
354
|
}
|
|
327
355
|
const res = await rpcQueryImpl(creds, "apps.get", { name });
|
|
328
356
|
const decoded = await readJsonValueResponse(res);
|
|
@@ -333,24 +361,20 @@ ${dashboardLine}
|
|
|
333
361
|
? appNotFoundDiagnostic()
|
|
334
362
|
: appResponseError(body, invalidAppResponse("app status", res, "error")),
|
|
335
363
|
);
|
|
336
|
-
|
|
337
|
-
return;
|
|
364
|
+
return 1;
|
|
338
365
|
}
|
|
339
366
|
if (!decoded.parsed) {
|
|
340
367
|
errorImpl(invalidAppResponse("app status", res, "success"));
|
|
341
|
-
|
|
342
|
-
return;
|
|
368
|
+
return 1;
|
|
343
369
|
}
|
|
344
370
|
const lookup = parseAppLookupResponse(body, name);
|
|
345
371
|
if (lookup.kind === "not_found") {
|
|
346
372
|
errorImpl(appNotFoundDiagnostic());
|
|
347
|
-
|
|
348
|
-
return;
|
|
373
|
+
return 1;
|
|
349
374
|
}
|
|
350
375
|
if (lookup.kind === "invalid_response") {
|
|
351
376
|
errorImpl(invalidAppResponse("app status", res, "success"));
|
|
352
|
-
|
|
353
|
-
return;
|
|
377
|
+
return 1;
|
|
354
378
|
}
|
|
355
379
|
const data = lookup.app;
|
|
356
380
|
logImpl(`\n ${BOLD}${data.name}${RESET}`);
|
|
@@ -377,10 +401,11 @@ ${dashboardLine}
|
|
|
377
401
|
);
|
|
378
402
|
}
|
|
379
403
|
logImpl("");
|
|
380
|
-
return;
|
|
404
|
+
return 0;
|
|
381
405
|
}
|
|
382
406
|
|
|
383
407
|
errorImpl(`Unknown app subcommand: ${subcmd}`);
|
|
384
408
|
logImpl(` Usage: gencow app [list|create|delete|status]`);
|
|
409
|
+
return 1;
|
|
385
410
|
};
|
|
386
411
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function commandErrorMessage(caught) {
|
|
2
|
+
if (caught instanceof Error && caught.message) return caught.message;
|
|
3
|
+
return String(caught);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function normalizeCommandExitCode(exitCode) {
|
|
7
|
+
if (exitCode === undefined || exitCode === 0) return 0;
|
|
8
|
+
if (exitCode === 1) return 1;
|
|
9
|
+
throw new Error("CLI command returned an invalid exit code");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runCliCommand({ args, command, commands, errorImpl, helpImpl }) {
|
|
13
|
+
const handler = Object.hasOwn(commands, command) ? commands[command] : undefined;
|
|
14
|
+
if (typeof handler !== "function") {
|
|
15
|
+
errorImpl(`Unknown command: ${command}`);
|
|
16
|
+
helpImpl();
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const exitCode = await Reflect.apply(handler, commands, args);
|
|
22
|
+
return normalizeCommandExitCode(exitCode);
|
|
23
|
+
} catch (caught) {
|
|
24
|
+
errorImpl(commandErrorMessage(caught));
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/lib/deploy-runtime.mjs
CHANGED
|
@@ -468,10 +468,7 @@ export function createDeployCommand({
|
|
|
468
468
|
const declaredStaticClients = Array.isArray(workspaceConfig?.clients)
|
|
469
469
|
? workspaceConfig.clients.filter((client) => client?.static?.dir)
|
|
470
470
|
: [];
|
|
471
|
-
const clientSelection = selectClient(
|
|
472
|
-
declaredStaticClients,
|
|
473
|
-
cliSelection?.client ?? null,
|
|
474
|
-
);
|
|
471
|
+
const clientSelection = selectClient(declaredStaticClients, cliSelection?.client ?? null);
|
|
475
472
|
if (clientSelection.error) {
|
|
476
473
|
errorImpl(clientSelection.error);
|
|
477
474
|
exitImpl(1);
|
|
@@ -541,7 +538,9 @@ export function createDeployCommand({
|
|
|
541
538
|
resolvePathImpl,
|
|
542
539
|
}) || displayName;
|
|
543
540
|
const backendDeployRuntime =
|
|
544
|
-
backendProjectRoot === cwd
|
|
541
|
+
backendProjectRoot === cwd
|
|
542
|
+
? deployPackageRuntime
|
|
543
|
+
: createDeployPackageRuntimeForCwd(() => backendProjectRoot);
|
|
545
544
|
|
|
546
545
|
if (parsed.existingBundleMode && !appId) {
|
|
547
546
|
errorImpl("--existing-bundle requires an existing app target.");
|
|
@@ -607,7 +606,7 @@ export function createDeployCommand({
|
|
|
607
606
|
}
|
|
608
607
|
|
|
609
608
|
if (parsed.staticDeploy) {
|
|
610
|
-
|
|
609
|
+
await runStaticDeployFlow({
|
|
611
610
|
appId,
|
|
612
611
|
backendRoot: backendProjectRoot,
|
|
613
612
|
creds,
|
|
@@ -659,6 +658,7 @@ export function createDeployCommand({
|
|
|
659
658
|
})
|
|
660
659
|
: undefined,
|
|
661
660
|
});
|
|
661
|
+
return 0;
|
|
662
662
|
}
|
|
663
663
|
|
|
664
664
|
logImpl(`\n${BOLD}${CYAN}Gencow Deploy${RESET}\n`);
|
package/lib/platform-client.mjs
CHANGED
|
@@ -42,9 +42,15 @@ export function resolveCredsFromSources({ env = process.env, loadCredsImpl = loa
|
|
|
42
42
|
return creds;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export function formatPlatformCredentialsRequiredMessage({ isCi = Boolean(process.env.CI) } = {}) {
|
|
46
|
+
return isCi
|
|
47
|
+
? "CI environment detected but no credentials found. Set GENCOW_TOKEN in your CI/CD settings."
|
|
48
|
+
: "Not logged in. Run: gencow login";
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
export class PlatformCredentialsRequiredError extends Error {
|
|
46
|
-
constructor() {
|
|
47
|
-
super(
|
|
52
|
+
constructor(message = "Platform credentials are required") {
|
|
53
|
+
super(message);
|
|
48
54
|
this.name = "PlatformCredentialsRequiredError";
|
|
49
55
|
}
|
|
50
56
|
}
|
|
@@ -52,7 +58,9 @@ export class PlatformCredentialsRequiredError extends Error {
|
|
|
52
58
|
export function requireCreds({ throwOnMissing = false } = {}) {
|
|
53
59
|
const creds = resolveCredsFromSources();
|
|
54
60
|
if (!creds?.apiKey) {
|
|
55
|
-
if (throwOnMissing)
|
|
61
|
+
if (throwOnMissing) {
|
|
62
|
+
throw new PlatformCredentialsRequiredError(formatPlatformCredentialsRequiredMessage());
|
|
63
|
+
}
|
|
56
64
|
if (process.env.CI) {
|
|
57
65
|
error("CI environment detected but no credentials found.");
|
|
58
66
|
info(`Set ${CYAN}GENCOW_TOKEN${RESET} secret in your CI/CD settings.`);
|
|
@@ -86,10 +94,11 @@ export async function rpcQuery(creds, queryName, args = {}) {
|
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
export async function rpcMutation(creds, mutationName, args = {}) {
|
|
97
|
+
export async function rpcMutation(creds, mutationName, args = {}, requestOptions = {}) {
|
|
90
98
|
return platformFetch(creds, "/api/mutation", {
|
|
99
|
+
...requestOptions,
|
|
91
100
|
method: "POST",
|
|
92
|
-
headers: { "Content-Type": "application/json" },
|
|
101
|
+
headers: { ...requestOptions.headers, "Content-Type": "application/json" },
|
|
93
102
|
body: JSON.stringify({ name: mutationName, args }),
|
|
94
103
|
});
|
|
95
104
|
}
|
package/lib/static-command.mjs
CHANGED
|
@@ -66,11 +66,13 @@ export function readStaticProjectContext(options = {}) {
|
|
|
66
66
|
projectRoot = selection.projectDir;
|
|
67
67
|
} else {
|
|
68
68
|
const staticBackend = detectStaticDeployBackendImpl({ cwd, existsSyncImpl, resolvePathImpl });
|
|
69
|
-
projectRoot =
|
|
69
|
+
projectRoot =
|
|
70
|
+
staticBackend.detectedBackend && staticBackend.backendRoot ? staticBackend.backendRoot : cwd;
|
|
70
71
|
}
|
|
71
72
|
} catch {
|
|
72
73
|
const staticBackend = detectStaticDeployBackendImpl({ cwd, existsSyncImpl, resolvePathImpl });
|
|
73
|
-
projectRoot =
|
|
74
|
+
projectRoot =
|
|
75
|
+
staticBackend.detectedBackend && staticBackend.backendRoot ? staticBackend.backendRoot : cwd;
|
|
74
76
|
}
|
|
75
77
|
const gencowJsonPath = resolveProjectMetadataPath(projectRoot, resolvePathImpl);
|
|
76
78
|
let appId = explicitAppId;
|
|
@@ -201,15 +203,19 @@ export function createStaticCommand(options = {}) {
|
|
|
201
203
|
|
|
202
204
|
const parsed = parseStaticArgs(staticArgs);
|
|
203
205
|
const creds = requireCredsImpl();
|
|
204
|
-
const {
|
|
206
|
+
const {
|
|
207
|
+
appId,
|
|
208
|
+
displayName: initialDisplayName,
|
|
209
|
+
gencowJsonPath,
|
|
210
|
+
prodAppId,
|
|
211
|
+
projectRoot,
|
|
212
|
+
} = readStaticProjectContextImpl({
|
|
205
213
|
cwd: cwdImpl(),
|
|
206
214
|
explicitAppId: parsed.appId,
|
|
207
215
|
});
|
|
208
216
|
const processEnv = process.env;
|
|
209
217
|
const cliSelection = getCliInvocationSelection();
|
|
210
|
-
const config = loadConfigImpl
|
|
211
|
-
? await loadConfigImpl({ cwd: projectRoot, processEnv })
|
|
212
|
-
: null;
|
|
218
|
+
const config = loadConfigImpl ? await loadConfigImpl({ cwd: projectRoot, processEnv }) : null;
|
|
213
219
|
const displayName = config
|
|
214
220
|
? readProjectDisplayNameImpl({
|
|
215
221
|
config,
|
|
@@ -289,7 +295,7 @@ export function createStaticCommand(options = {}) {
|
|
|
289
295
|
);
|
|
290
296
|
}
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
await runStaticDeployRuntimeImpl({
|
|
293
299
|
creds,
|
|
294
300
|
appId: deployTarget,
|
|
295
301
|
displayName,
|
|
@@ -302,5 +308,6 @@ export function createStaticCommand(options = {}) {
|
|
|
302
308
|
releaseSourceRoot: projectRoot,
|
|
303
309
|
},
|
|
304
310
|
});
|
|
311
|
+
return 0;
|
|
305
312
|
};
|
|
306
313
|
}
|
|
@@ -53,7 +53,13 @@ async function createSourceBundle({ cwd, staticOutputDir, mkdirSyncImpl = mkdirS
|
|
|
53
53
|
return out;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
async function createStaticBundle({
|
|
56
|
+
async function createStaticBundle({
|
|
57
|
+
cwd,
|
|
58
|
+
outputDir,
|
|
59
|
+
mkdirSyncImpl = mkdirSync,
|
|
60
|
+
resolvePathImpl = resolve,
|
|
61
|
+
tarCreateImpl,
|
|
62
|
+
}) {
|
|
57
63
|
const out = resolve(cwd, ".gencow", "template-static.tar.gz");
|
|
58
64
|
mkdirSyncImpl(resolve(cwd, ".gencow"), { recursive: true });
|
|
59
65
|
await createTarGzipArchive({
|
|
@@ -65,12 +71,7 @@ async function createStaticBundle({ cwd, outputDir, mkdirSyncImpl = mkdirSync, r
|
|
|
65
71
|
return out;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
async function extractTemplateArchive({
|
|
69
|
-
archive,
|
|
70
|
-
targetDir,
|
|
71
|
-
mkdirSyncImpl = mkdirSync,
|
|
72
|
-
tarExtractImpl,
|
|
73
|
-
}) {
|
|
74
|
+
async function extractTemplateArchive({ archive, targetDir, mkdirSyncImpl = mkdirSync, tarExtractImpl }) {
|
|
74
75
|
mkdirSyncImpl(targetDir, { recursive: true });
|
|
75
76
|
const extractImpl = tarExtractImpl ?? (await import("tar")).extract;
|
|
76
77
|
await extractImpl({ file: archive, cwd: targetDir });
|
|
@@ -230,7 +231,10 @@ export function createTemplateMarketplaceCommand({
|
|
|
230
231
|
if (subcommand === "publish") return publish(options);
|
|
231
232
|
if (subcommand === "list") return list();
|
|
232
233
|
if (subcommand === "info") return infoCommand(options._[0]);
|
|
233
|
-
if (subcommand === "download")
|
|
234
|
+
if (subcommand === "download") {
|
|
235
|
+
await download(options._[0], options);
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
234
238
|
if (subcommand === "clone") {
|
|
235
239
|
const slug = options._[0];
|
|
236
240
|
const dir = options._[1] || slug;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gencow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.194",
|
|
4
4
|
"description": "Gencow — AI Backend Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^25.9.5",
|
|
35
35
|
"better-auth": "^1.6.23",
|
|
36
|
-
"@gencow/react": "0.2.6",
|
|
37
36
|
"@gencow/core": "0.1.42",
|
|
38
|
-
"@gencow/client": "0.2.6"
|
|
37
|
+
"@gencow/client": "0.2.6",
|
|
38
|
+
"@gencow/react": "0.2.6"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",
|
package/runtime/server.mjs
CHANGED
|
@@ -184101,6 +184101,15 @@ function resolveGenericDeploymentFailurePolicy(input) {
|
|
|
184101
184101
|
};
|
|
184102
184102
|
}
|
|
184103
184103
|
if (input.stage === "candidate") {
|
|
184104
|
+
if (code === "BACKEND_CAPABILITY_EMPTY") {
|
|
184105
|
+
return {
|
|
184106
|
+
status: 409,
|
|
184107
|
+
error: "Deploy failed: backend deploy did not expose any query, mutation, or procedure capability; use gencow static for frontend-only deployments",
|
|
184108
|
+
code,
|
|
184109
|
+
retryClass: "terminal-client",
|
|
184110
|
+
userAction: "EDIT_AND_CHECK"
|
|
184111
|
+
};
|
|
184112
|
+
}
|
|
184104
184113
|
if (code === "APP_DEPENDENCY_INSTALL_FAILED") {
|
|
184105
184114
|
return retry(
|
|
184106
184115
|
code,
|
|
@@ -184224,6 +184233,7 @@ var init_deployment_failure_contract = __esm({
|
|
|
184224
184233
|
"DEPLOYMENT_CONTROL_PLANE_",
|
|
184225
184234
|
"RUNTIME_COWBOX_",
|
|
184226
184235
|
"RUNTIME_ABSENCE_",
|
|
184236
|
+
"RUNTIME_DEPLOYMENT_",
|
|
184227
184237
|
"RUNTIME_GENERATION_",
|
|
184228
184238
|
"RUNTIME_OPERATION_",
|
|
184229
184239
|
"RUNTIME_OWNERSHIP_",
|
|
@@ -185899,6 +185909,27 @@ async function admitDeploymentExecutorRequest(input) {
|
|
|
185899
185909
|
throw new DeploymentExecutorAdmissionError("DEPLOYMENT_EXECUTOR_ADMISSION_UNAVAILABLE", 503);
|
|
185900
185910
|
}
|
|
185901
185911
|
}
|
|
185912
|
+
async function admitDeploymentExecutorRequestAfterActiveOperation(input, options = {}) {
|
|
185913
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
185914
|
+
const pollMs = options.pollMs ?? 500;
|
|
185915
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > 6e4 || !Number.isSafeInteger(pollMs) || pollMs < 1 || pollMs > 5e3) {
|
|
185916
|
+
throw new Error("DEPLOYMENT_EXECUTOR_BUSY_RETRY_CONFIG_INVALID");
|
|
185917
|
+
}
|
|
185918
|
+
const sleep6 = options.sleep ?? ((milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)));
|
|
185919
|
+
const now = options.now ?? Date.now;
|
|
185920
|
+
const admit = options.admit ?? admitDeploymentExecutorRequest;
|
|
185921
|
+
const deadlineMs = now() + timeoutMs;
|
|
185922
|
+
while (true) {
|
|
185923
|
+
try {
|
|
185924
|
+
return await admit(input);
|
|
185925
|
+
} catch (error51) {
|
|
185926
|
+
if (!(error51 instanceof DeploymentExecutorAdmissionError) || error51.code !== "DEPLOYMENT_EXECUTOR_APP_BUSY" || now() >= deadlineMs) {
|
|
185927
|
+
throw error51;
|
|
185928
|
+
}
|
|
185929
|
+
await sleep6(Math.min(pollMs, Math.max(1, deadlineMs - now())));
|
|
185930
|
+
}
|
|
185931
|
+
}
|
|
185932
|
+
}
|
|
185902
185933
|
function deploymentExecutorAdmissionPublicMessage(code) {
|
|
185903
185934
|
const messages = {
|
|
185904
185935
|
DEPLOYMENT_EXECUTOR_QUEUE_FULL: "Deployment executor queue capacity is exhausted",
|
|
@@ -185968,6 +185999,7 @@ function registerPlatformApiDeployRoutes({
|
|
|
185968
185999
|
acquireAdmissionLock: acquireAdmissionLock2,
|
|
185969
186000
|
maxQueuedOperations,
|
|
185970
186001
|
maxQueuedBytes,
|
|
186002
|
+
busyRetryTimeoutMs,
|
|
185971
186003
|
logger: logger3 = console
|
|
185972
186004
|
}) {
|
|
185973
186005
|
const deployApp = async (c) => {
|
|
@@ -186033,21 +186065,24 @@ function registerPlatformApiDeployRoutes({
|
|
|
186033
186065
|
for (const [header, value] of Object.entries(protocolHeaders)) c.header(header, value);
|
|
186034
186066
|
};
|
|
186035
186067
|
try {
|
|
186036
|
-
const admission = await
|
|
186037
|
-
|
|
186038
|
-
|
|
186039
|
-
|
|
186040
|
-
|
|
186041
|
-
|
|
186042
|
-
|
|
186043
|
-
|
|
186044
|
-
|
|
186045
|
-
|
|
186046
|
-
|
|
186047
|
-
|
|
186048
|
-
|
|
186049
|
-
|
|
186050
|
-
|
|
186068
|
+
const admission = await admitDeploymentExecutorRequestAfterActiveOperation(
|
|
186069
|
+
{
|
|
186070
|
+
db,
|
|
186071
|
+
app: appRow,
|
|
186072
|
+
userId: user2.id,
|
|
186073
|
+
environment,
|
|
186074
|
+
bundle: bundleBuffer,
|
|
186075
|
+
getHeader: (header) => c.req.header(header) || "",
|
|
186076
|
+
currentNodeId,
|
|
186077
|
+
currentSlotId,
|
|
186078
|
+
currentRevision,
|
|
186079
|
+
spoolRoot: executorSpoolRoot2,
|
|
186080
|
+
acquireAdmissionLock: acquireAdmissionLock2,
|
|
186081
|
+
maxQueuedOperations,
|
|
186082
|
+
maxQueuedBytes
|
|
186083
|
+
},
|
|
186084
|
+
{ timeoutMs: busyRetryTimeoutMs }
|
|
186085
|
+
);
|
|
186051
186086
|
protocolHeaders = admission.protocolHeaders;
|
|
186052
186087
|
applyProtocolHeaders();
|
|
186053
186088
|
const committedAttempt = admission.attempt;
|
|
@@ -411684,6 +411719,20 @@ function isPrepared(dataDir, source) {
|
|
|
411684
411719
|
return false;
|
|
411685
411720
|
}
|
|
411686
411721
|
}
|
|
411722
|
+
function loadVerifiedRuntimeLayout(dataDir, source) {
|
|
411723
|
+
assertVerifiedRuntimeExecutionSource(source);
|
|
411724
|
+
const metadata = readAppRuntimeMetadata(dataDir);
|
|
411725
|
+
if (!metadata || metadata.resolvedReleaseId !== source.releaseId || !isPrepared(dataDir, source)) {
|
|
411726
|
+
throw new Error("RUNTIME_CANDIDATE_LAYOUT_NOT_PREPARED");
|
|
411727
|
+
}
|
|
411728
|
+
return {
|
|
411729
|
+
...metadata,
|
|
411730
|
+
layout: source.layout,
|
|
411731
|
+
serverEntry: source.serverEntry,
|
|
411732
|
+
runtimeNodeModulesPath: getManagedRuntimeNodeModulesPath(dataDir),
|
|
411733
|
+
publishedReleaseDir: source.runtimeRoot
|
|
411734
|
+
};
|
|
411735
|
+
}
|
|
411687
411736
|
function prepareVerifiedRuntimeLayout(dataDir, source, runtimeCompatibility = source.releaseId) {
|
|
411688
411737
|
assertVerifiedRuntimeExecutionSource(source);
|
|
411689
411738
|
const metadata = {
|
|
@@ -419268,6 +419317,12 @@ async function createRuntimeControlPlaneProvisionerAdapter(input) {
|
|
|
419268
419317
|
});
|
|
419269
419318
|
preparedRuntimeLayouts.set(workspacePath, runtimeLayout);
|
|
419270
419319
|
},
|
|
419320
|
+
loadSealedWorkspace(workspacePath) {
|
|
419321
|
+
preparedRuntimeLayouts.set(
|
|
419322
|
+
workspacePath,
|
|
419323
|
+
loadVerifiedRuntimeLayout(workspacePath, runtimeSource)
|
|
419324
|
+
);
|
|
419325
|
+
},
|
|
419271
419326
|
async allocateCandidateTarget(workspacePath) {
|
|
419272
419327
|
void workspacePath;
|
|
419273
419328
|
return {
|
|
@@ -419306,6 +419361,7 @@ var init_runtime_control_plane_provisioner_adapter = __esm({
|
|
|
419306
419361
|
init_warm_pool_backend();
|
|
419307
419362
|
init_runtime_process_identity();
|
|
419308
419363
|
init_provisioner_database_preflight();
|
|
419364
|
+
init_provisioner_verified_runtime_layout();
|
|
419309
419365
|
}
|
|
419310
419366
|
});
|
|
419311
419367
|
|
|
@@ -420223,6 +420279,13 @@ async function probeSlotCapability(input) {
|
|
|
420223
420279
|
return "unavailable";
|
|
420224
420280
|
}
|
|
420225
420281
|
}
|
|
420282
|
+
async function probeCurrentSlotCapabilityWithRetry(input) {
|
|
420283
|
+
let capability2 = await probeSlotCapability(input);
|
|
420284
|
+
for (let attempt = 1; attempt < 3 && capability2 === "unavailable"; attempt += 1) {
|
|
420285
|
+
capability2 = await probeSlotCapability(input);
|
|
420286
|
+
}
|
|
420287
|
+
return capability2;
|
|
420288
|
+
}
|
|
420226
420289
|
async function convergeDeploymentRouteAcrossEligibleSlots(input) {
|
|
420227
420290
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
420228
420291
|
const env2 = input.env ?? process.env;
|
|
@@ -420231,13 +420294,14 @@ async function convergeDeploymentRouteAcrossEligibleSlots(input) {
|
|
|
420231
420294
|
const eligible = [];
|
|
420232
420295
|
let siblingBlocked = false;
|
|
420233
420296
|
for (const port of ports) {
|
|
420234
|
-
const
|
|
420297
|
+
const probeInput = {
|
|
420235
420298
|
fetchImpl,
|
|
420236
420299
|
port,
|
|
420237
420300
|
currentPort,
|
|
420238
420301
|
expectedNodeId: input.binding.nodeId,
|
|
420239
420302
|
env: env2
|
|
420240
|
-
}
|
|
420303
|
+
};
|
|
420304
|
+
const capability2 = port === currentPort ? await probeCurrentSlotCapabilityWithRetry(probeInput) : await probeSlotCapability(probeInput);
|
|
420241
420305
|
if (capability2 === "capable" || capability2 === "legacy") eligible.push(port);
|
|
420242
420306
|
if (port !== currentPort && capability2 === "blocked") siblingBlocked = true;
|
|
420243
420307
|
}
|
|
@@ -420534,7 +420598,7 @@ async function deployWithRuntimeControlPlane(input) {
|
|
|
420534
420598
|
}
|
|
420535
420599
|
);
|
|
420536
420600
|
await input.onExecutorPhase?.("runtime_preparing");
|
|
420537
|
-
|
|
420601
|
+
provisioner.loadSealedWorkspace(generation.workspacePath);
|
|
420538
420602
|
const identity2 = await prepareIdentity({
|
|
420539
420603
|
workspacePath: generation.workspacePath,
|
|
420540
420604
|
artifactRef: input.artifact.artifactRef,
|