@homespunapps/cli 1.6.75 → 1.6.77

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.
@@ -274,6 +274,44 @@ function inlineExplicitAsset(asset) {
274
274
  ...(mime !== undefined ? { mime } : {}),
275
275
  };
276
276
  }
277
+ /**
278
+ * Report connections the manifest references that the app does not have.
279
+ *
280
+ * Deploying a manifest DECLARES a connection name; it never creates the
281
+ * connection, because its allowed host is the exfiltration defence and only an
282
+ * owner may bind one. Without this the gap is invisible until someone uses the
283
+ * feature and gets "no connection named X on this app", which reads like a bug
284
+ * in the app rather than setup that was never done.
285
+ *
286
+ * STDERR, never stdout: stdout carries the JSON a caller pipes into `jq`.
287
+ *
288
+ * The printed command is a SUGGESTION built from the host the relay read out
289
+ * of the manifest. It is shown with its provenance so the reader checks it
290
+ * rather than pasting blind, and `--header-value -` makes the CLI prompt for
291
+ * the credential instead of putting it in shell history.
292
+ */
293
+ function reportMissingConnections(missing, appRef) {
294
+ if (!missing || missing.length === 0)
295
+ return;
296
+ for (const m of missing) {
297
+ warn(`connection '${m.name}' is declared in the manifest but does not exist on this app; every call through it will fail`);
298
+ if (m.suggested_host) {
299
+ process.stderr.write(` the manifest points it at ${m.suggested_host}. If that is right:\n` +
300
+ ` homespun connections create --app ${appRef} --name ${m.name} \\\n` +
301
+ ` --allowed-host ${m.suggested_host} \\\n` +
302
+ ` --kind static --header-name Authorization --header-value -\n`);
303
+ }
304
+ else {
305
+ // Rules disagreeing on a host, or resolving it from a setting at
306
+ // delivery time. Printing one of several hosts would suggest a binding
307
+ // that silently fails for the others, so name the host yourself.
308
+ process.stderr.write(` its rules do not agree on a single host, so choose the destination yourself:\n` +
309
+ ` homespun connections create --app ${appRef} --name ${m.name} \\\n` +
310
+ ` --allowed-host <host> \\\n` +
311
+ ` --kind static --header-name Authorization --header-value -\n`);
312
+ }
313
+ }
314
+ }
277
315
  /** Split explicit assets by the presign threshold. A pure size check, no I/O. */
278
316
  function partitionExplicitAssets(assets) {
279
317
  const small = [];
@@ -472,6 +510,15 @@ export async function runDeploy(args) {
472
510
  ...(force ? { force } : {}),
473
511
  });
474
512
  printJson(result);
513
+ reportMissingConnections(result.missing_connections, appId ?? "<app>");
514
+ // A check exists to be a gate, so it must not report success on a
515
+ // manifest whose connections do not exist: every call through them
516
+ // fails at runtime. Gated on the STRUCTURED field, never on the warning
517
+ // prose, because a grep that stops matching fails open and a gate that
518
+ // fails open is worse than no gate.
519
+ if ((result.missing_connections?.length ?? 0) > 0) {
520
+ process.exitCode = 1;
521
+ }
475
522
  }
476
523
  catch (e) {
477
524
  failFromError(e);
@@ -529,6 +576,9 @@ export async function runDeploy(args) {
529
576
  ...(redeployed.breaks ? { breaks: redeployed.breaks } : {}),
530
577
  ...(redeployed.warnings ? { warnings: redeployed.warnings } : {}),
531
578
  });
579
+ // A brand-new app has no connections at all, so this names every one the
580
+ // manifest declares. That is the moment the advice is most useful.
581
+ reportMissingConnections(redeployed.missing_connections, app.slug);
532
582
  }
533
583
  catch (e) {
534
584
  failFromError(e);
@@ -577,6 +627,7 @@ export async function runDeploy(args) {
577
627
  ...(redeployed.breaks ? { breaks: redeployed.breaks } : {}),
578
628
  ...(redeployed.warnings ? { warnings: redeployed.warnings } : {}),
579
629
  });
630
+ reportMissingConnections(redeployed.missing_connections, app.slug);
580
631
  }
581
632
  catch (e) {
582
633
  failFromError(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homespunapps/cli",
3
- "version": "1.6.75",
3
+ "version": "1.6.77",
4
4
  "description": "Command-line client for the Homespun relay: deploy a real multi-user web app from your agent, then keep reading and writing its data.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "test:unit": "vitest run"
37
37
  },
38
38
  "dependencies": {
39
- "@homespunapps/core": "^1.6.75",
39
+ "@homespunapps/core": "^1.6.77",
40
40
  "qrcode-terminal": "^0.12.0"
41
41
  },
42
42
  "devDependencies": {