gencow 0.1.192 → 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 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
- if (commands[cmd]) {
583
- Promise.resolve(commands[cmd](...args)).catch((e) => {
584
- error(e.message || String(e));
585
- process.exit(1);
586
- });
587
- } else {
588
- error(`Unknown command: ${cmd}`);
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
+ });
@@ -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";
@@ -74,11 +82,15 @@ export function addDeployAppToConfigSource(src, appName) {
74
82
  if (src.includes("deploy:")) return src;
75
83
 
76
84
  const deployBlock = ` deploy: {\n app: "${appName}",\n },\n`;
77
- const defineConfigSource = src.replace(/}\s*\)\s*;?\s*$/, `${deployBlock}});\n`);
78
- if (defineConfigSource !== src) return defineConfigSource;
85
+ const insertBeforeClosingObject = (pattern) => {
86
+ const match = src.match(pattern);
87
+ if (!match || match.index == null) return null;
88
+ const prefix = src.slice(0, match.index).trimEnd();
89
+ const separator = prefix.endsWith(",") || prefix.endsWith("{") ? "\n" : ",\n";
90
+ return `${prefix}${separator}${deployBlock}${src.slice(match.index)}`;
91
+ };
79
92
 
80
- const objectExportSource = src.replace(/}\s*;?\s*$/, `${deployBlock}};\n`);
81
- return objectExportSource;
93
+ return insertBeforeClosingObject(/\}\s*\)\s*;?\s*$/u) ?? insertBeforeClosingObject(/\}\s*;?\s*$/u) ?? src;
82
94
  }
83
95
 
84
96
  export { resolveCreatedAppId };
@@ -101,23 +113,52 @@ async function confirmDelete(name) {
101
113
  }
102
114
 
103
115
  export function createAppCommand({
116
+ appCreateTimeoutMs = resolveAppCreateTimeoutMs(process.env.GENCOW_APP_CREATE_TIMEOUT_MS),
117
+ clearTimeoutImpl = clearTimeout,
118
+ confirmDeleteImpl = confirmDelete,
119
+ createAbortControllerImpl = () => new AbortController(),
104
120
  cwdImpl = () => process.cwd(),
105
121
  loadConfig,
106
122
  errorImpl = error,
107
123
  infoImpl = info,
108
124
  loadCredsImpl = loadCreds,
109
125
  logImpl = log,
110
- processRef = process,
111
126
  requireCredsImpl = requireCreds,
112
127
  rpcMutationImpl = rpcMutation,
113
128
  rpcQueryImpl = rpcQuery,
114
129
  saveCredsImpl = saveCreds,
130
+ setTimeoutImpl = setTimeout,
115
131
  sleepImpl = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms)),
116
132
  deletePollAttempts = DEFAULT_DELETE_POLL_ATTEMPTS,
117
133
  successImpl = success,
118
134
  updateEnvLocalUrlImpl = updateEnvLocalUrl,
119
135
  warnImpl = warn,
120
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
+
121
162
  return async function app(subcmd, ...rest) {
122
163
  if (subcmd === "--help" || subcmd === "-h" || rest.includes("--help") || rest.includes("-h")) {
123
164
  logImpl(`\n${BOLD}${CYAN}gencow app${RESET} — App management\n`);
@@ -127,10 +168,10 @@ export function createAppCommand({
127
168
  logImpl(` ${CYAN}create${RESET} Create a new app`);
128
169
  logImpl(` ${CYAN}delete${RESET} Delete an app (confirmation required)`);
129
170
  logImpl(` ${CYAN}status${RESET} Show app status\n`);
130
- return;
171
+ return 0;
131
172
  }
132
173
 
133
- const creds = requireCredsImpl();
174
+ const creds = requireCredsImpl({ throwOnMissing: true });
134
175
 
135
176
  if (!subcmd || subcmd === "list") {
136
177
  logImpl(`\n${BOLD}${CYAN}Your Apps${RESET}\n`);
@@ -138,18 +179,16 @@ export function createAppCommand({
138
179
  const body = await res.json().catch(() => null);
139
180
  if (!res.ok) {
140
181
  errorImpl(appResponseError(body, invalidAppResponse("app list", res, "error")));
141
- processRef.exit(1);
142
- return;
182
+ return 1;
143
183
  }
144
184
  const apps = parseAppListResponse(body);
145
185
  if (!apps) {
146
186
  errorImpl(invalidAppResponse("app list", res, "success"));
147
- processRef.exit(1);
148
- return;
187
+ return 1;
149
188
  }
150
189
  if (!apps.length) {
151
190
  infoImpl("No apps yet. Run: gencow app create <name>");
152
- return;
191
+ return 0;
153
192
  }
154
193
 
155
194
  logImpl(` ${"NAME".padEnd(22)} ${"STATUS".padEnd(12)} ${"URL".padEnd(38)} DEPLOYED`);
@@ -163,29 +202,26 @@ export function createAppCommand({
163
202
  );
164
203
  }
165
204
  logImpl("");
166
- return;
205
+ return 0;
167
206
  }
168
207
 
169
208
  if (subcmd === "create") {
170
209
  const name = rest[0];
171
210
  if (!name) {
172
211
  errorImpl("Usage: gencow app create <name>");
173
- processRef.exit(1);
212
+ return 1;
174
213
  }
175
214
 
176
215
  logImpl(`\n${BOLD}${CYAN}Gencow App Create${RESET}\n`);
177
216
  infoImpl(`Creating app "${name}"...`);
178
- const res = await rpcMutationImpl(creds, "apps.create", { name });
179
- const data = await readJsonObjectResponse(res);
217
+ const { response: res, data } = await requestAppCreate(creds, name);
180
218
  if (!res.ok) {
181
219
  errorImpl(appResponseError(data, invalidAppResponse("app create", res, "error")));
182
- processRef.exit(1);
183
- return;
220
+ return 1;
184
221
  }
185
222
  if (!data) {
186
223
  errorImpl(invalidAppResponse("app create", res, "success"));
187
- processRef.exit(1);
188
- return;
224
+ return 1;
189
225
  }
190
226
 
191
227
  let createdApp;
@@ -193,8 +229,7 @@ export function createAppCommand({
193
229
  createdApp = resolveCreatedAppResponse(data, { platformUrl: creds.platformUrl });
194
230
  } catch (caught) {
195
231
  errorImpl(caught.message);
196
- processRef.exit(1);
197
- return;
232
+ return 1;
198
233
  }
199
234
  const { appId, appUrl } = createdApp;
200
235
 
@@ -255,7 +290,7 @@ ${dashboardLine}
255
290
 
256
291
  ${DIM}pnpm gencow dev — watch & auto-deploy to cloud + live logs${RESET}
257
292
  `);
258
- return;
293
+ return 0;
259
294
  }
260
295
 
261
296
  if (subcmd === "delete") {
@@ -266,15 +301,14 @@ ${dashboardLine}
266
301
  const name = rest.find((value) => !value.startsWith("-")) || loadCredsImpl()?.currentApp;
267
302
  if (!name || unsupportedOption) {
268
303
  errorImpl("Usage: gencow app delete <name> [--force]");
269
- processRef.exit(1);
270
- return;
304
+ return 1;
271
305
  }
272
306
 
273
307
  if (!force) {
274
- const confirmed = await confirmDelete(name);
308
+ const confirmed = await confirmDeleteImpl(name);
275
309
  if (!confirmed) {
276
310
  warnImpl("Cancelled — name did not match.");
277
- processRef.exit(0);
311
+ return 0;
278
312
  }
279
313
  }
280
314
 
@@ -300,8 +334,7 @@ ${dashboardLine}
300
334
  invalidAppResponse("app delete", delRes, delRes.ok ? "success" : "error"),
301
335
  ),
302
336
  );
303
- processRef.exit(1);
304
- return;
337
+ return 1;
305
338
  }
306
339
 
307
340
  const currentCreds = loadCredsImpl();
@@ -310,15 +343,14 @@ ${dashboardLine}
310
343
  }
311
344
 
312
345
  successImpl(`App "${name}" deleted`);
313
- return;
346
+ return 0;
314
347
  }
315
348
 
316
349
  if (subcmd === "status") {
317
350
  const name = rest[0] || loadCredsImpl()?.currentApp;
318
351
  if (!name) {
319
352
  errorImpl("Usage: gencow app status <name>");
320
- processRef.exit(1);
321
- return;
353
+ return 1;
322
354
  }
323
355
  const res = await rpcQueryImpl(creds, "apps.get", { name });
324
356
  const decoded = await readJsonValueResponse(res);
@@ -329,24 +361,20 @@ ${dashboardLine}
329
361
  ? appNotFoundDiagnostic()
330
362
  : appResponseError(body, invalidAppResponse("app status", res, "error")),
331
363
  );
332
- processRef.exit(1);
333
- return;
364
+ return 1;
334
365
  }
335
366
  if (!decoded.parsed) {
336
367
  errorImpl(invalidAppResponse("app status", res, "success"));
337
- processRef.exit(1);
338
- return;
368
+ return 1;
339
369
  }
340
370
  const lookup = parseAppLookupResponse(body, name);
341
371
  if (lookup.kind === "not_found") {
342
372
  errorImpl(appNotFoundDiagnostic());
343
- processRef.exit(1);
344
- return;
373
+ return 1;
345
374
  }
346
375
  if (lookup.kind === "invalid_response") {
347
376
  errorImpl(invalidAppResponse("app status", res, "success"));
348
- processRef.exit(1);
349
- return;
377
+ return 1;
350
378
  }
351
379
  const data = lookup.app;
352
380
  logImpl(`\n ${BOLD}${data.name}${RESET}`);
@@ -373,10 +401,11 @@ ${dashboardLine}
373
401
  );
374
402
  }
375
403
  logImpl("");
376
- return;
404
+ return 0;
377
405
  }
378
406
 
379
407
  errorImpl(`Unknown app subcommand: ${subcmd}`);
380
408
  logImpl(` Usage: gencow app [list|create|delete|status]`);
409
+ return 1;
381
410
  };
382
411
  }
@@ -110,6 +110,7 @@ export function validateServerRuntimeBundle(source, options = {}) {
110
110
  String.raw`(?:resolve|join|new\s+URL)\([^;\n]{0,640}(?:${repositorySourcePath}|["'](?:platform|server|route-gateway)["']\s*,\s*["']src["'])`,
111
111
  "iu",
112
112
  ),
113
+ /resolve\([^;\n]{0,320}\bfunctionsPath\b[^;\n]{0,320}["']\.\.\/src\/platform-api-surface\.ts["']/iu,
113
114
  ];
114
115
  const errors = repositorySourceImportPatterns.some((pattern) => pattern.test(source))
115
116
  ? [
@@ -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
+ }
@@ -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 ? deployPackageRuntime : createDeployPackageRuntimeForCwd(() => backendProjectRoot);
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
- return runStaticDeployFlow({
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`);
@@ -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("Platform credentials are required");
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) throw new PlatformCredentialsRequiredError();
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
  }
@@ -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 = staticBackend.detectedBackend && staticBackend.backendRoot ? staticBackend.backendRoot : cwd;
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 = staticBackend.detectedBackend && staticBackend.backendRoot ? staticBackend.backendRoot : cwd;
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 { appId, displayName: initialDisplayName, gencowJsonPath, prodAppId, projectRoot } = readStaticProjectContextImpl({
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
- return runStaticDeployRuntimeImpl({
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({ cwd, outputDir, mkdirSyncImpl = mkdirSync, resolvePathImpl = resolve, tarCreateImpl }) {
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") return download(options._[0], options);
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.192",
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",