void 0.10.7 → 0.10.8

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/dist/cli/cli.mjs CHANGED
@@ -1967,7 +1967,7 @@ else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, p
1967
1967
  else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-DjKE2-A-.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
1968
1968
  else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-CO5XAS9t.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
1969
1969
  else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-Cf79J4aw.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
1970
- else if (command === "github") runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (root, parsed) => import("../github-cmd-DnqxyOsb.mjs").then(({ runGithubCommand }) => runGithubCommand(root, parsed)));
1970
+ else if (command === "github") runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (root, parsed) => import("../github-cmd-B6OX9c7d.mjs").then(({ runGithubCommand }) => runGithubCommand(root, parsed)));
1971
1971
  else if (command === "build") runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (root, parsed) => import("../build-cmd-Br8qL0rA.mjs").then(({ runBuildCommand }) => runBuildCommand(root, parsed)));
1972
1972
  else if (command === "prepare") {
1973
1973
  const root = process.cwd();
@@ -2103,7 +2103,7 @@ else if (command === "-v" || command === "--version") {
2103
2103
  case "project": return runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (r, p) => import("../project-cmd-DnU7u9QF.mjs").then(({ runProjectCommand }) => runProjectCommand(r, p)));
2104
2104
  case "secret": return runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (r, p) => import("../secret-u7FRvg8d.mjs").then(({ runSecretCommand }) => runSecretCommand(r, p)));
2105
2105
  case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-_luIsM_B.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
2106
- case "github": return runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (r, p) => import("../github-cmd-DnqxyOsb.mjs").then(({ runGithubCommand }) => runGithubCommand(r, p)));
2106
+ case "github": return runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (r, p) => import("../github-cmd-B6OX9c7d.mjs").then(({ runGithubCommand }) => runGithubCommand(r, p)));
2107
2107
  case "build": return runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (r, p) => import("../build-cmd-Br8qL0rA.mjs").then(({ runBuildCommand }) => runBuildCommand(r, p)));
2108
2108
  case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-DjKE2-A-.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
2109
2109
  case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-CO5XAS9t.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
@@ -187,35 +187,38 @@ async function runGithubLink(root, client) {
187
187
  else log.success("GitHub account linked.");
188
188
  }
189
189
  /**
190
- * Discover and join the shared GitHub App installations owned by the
191
- * organizations the logged-in GitHub identity belongs to. Drives the same
192
- * 3-endpoint localhost + PKCE handshake as {@link runGithubLink}, but against
193
- * the platform's JOIN routes: membership upsert is idempotent, so unlike link
194
- * there is no 409/already-linked conflict to map. The only special error is the
195
- * fail-closed 503 raised when org-installation sharing is not configured on the
196
- * instance.
190
+ * Browser round-trip (localhost + PKCE) that joins the caller to their
191
+ * organization's shared GitHub App installations. Returns the number of
192
+ * installations joined. Throws PlatformApiError — including the 503
193
+ * `github_oauth_not_configured` — so callers own the user-facing handling
194
+ * (`runGithubJoin` prints and exits; the connect auto-join fallback falls
195
+ * through to its install-first error).
197
196
  */
198
- async function runGithubJoin(root, client) {
197
+ async function performGithubJoin(client) {
199
198
  const verifier = createCodeVerifier();
200
199
  const challenge = createCodeChallenge(verifier);
201
200
  const state = randomBytes(32).toString("hex");
201
+ const { count } = await awaitBrowserCallback({
202
+ state,
203
+ buildAuthUrl: async (callback) => {
204
+ callback.searchParams.set("state", state);
205
+ const { url } = await client.getGithubJoinUrl(callback.href, challenge);
206
+ return url;
207
+ },
208
+ describeError: (p) => p.get("error") ? "GitHub authorization failed." : null,
209
+ onUrl: (url) => log.message(`Open this URL to join your organization's shared GitHub App installations:\n${import_picocolors.default.cyan(url)}`),
210
+ finalize: async (params) => {
211
+ const joinCode = params.get("join_code");
212
+ if (!joinCode) throw new Error("github: join callback did not return a join code.");
213
+ return client.completeGithubJoin(joinCode, verifier);
214
+ }
215
+ });
216
+ return count;
217
+ }
218
+ async function runGithubJoin(root, client) {
202
219
  let count;
203
220
  try {
204
- ({count} = await awaitBrowserCallback({
205
- state,
206
- buildAuthUrl: async (callback) => {
207
- callback.searchParams.set("state", state);
208
- const { url } = await client.getGithubJoinUrl(callback.href, challenge);
209
- return url;
210
- },
211
- describeError: (p) => p.get("error") ? "GitHub authorization failed." : null,
212
- onUrl: (url) => log.message(`Open this URL to join your organization's shared GitHub App installations:\n${import_picocolors.default.cyan(url)}`),
213
- finalize: async (params) => {
214
- const joinCode = params.get("join_code");
215
- if (!joinCode) throw new Error("github: join callback did not return a join code.");
216
- return client.completeGithubJoin(joinCode, verifier);
217
- }
218
- }));
221
+ count = await performGithubJoin(client);
219
222
  } catch (err) {
220
223
  if (err instanceof PlatformApiError && err.status === 503 && err.code === "github_oauth_not_configured") {
221
224
  log.error("Organization installation sharing is not enabled on this Void instance.");
@@ -281,6 +284,26 @@ async function runGithubConnectAuthorize(client, installationId, repoFullName) {
281
284
  codeVerifier: verifier
282
285
  };
283
286
  }
287
+ /**
288
+ * Free-text repository prompt used when the installation's repository list is
289
+ * not enumerable client-side: `repository_selection === 'all'`, or a shared
290
+ * org installation whose list the server redacts for non-owner members. The
291
+ * server re-verifies actual access on connect, so this is DX, not security.
292
+ */
293
+ async function promptRepoFullName(root) {
294
+ const gitDefault = parseGithubRepoFullName(collectGitDeployMetadata(root, null).repositoryUrl);
295
+ const input = await text({
296
+ message: "Repository (owner/repo):",
297
+ placeholder: gitDefault ?? "owner/repo",
298
+ initialValue: gitDefault ?? "",
299
+ validate: (value) => /^[^/\s]+\/[^/\s]+$/.test((value ?? "").trim()) ? void 0 : "Enter the repository as owner/repo."
300
+ });
301
+ if (isCancel(input)) {
302
+ cancel("Cancelled.");
303
+ process.exit(0);
304
+ }
305
+ return input.trim();
306
+ }
284
307
  async function runConnect(root, client, args) {
285
308
  const isTTY = process.stdin.isTTY === true;
286
309
  let projectId;
@@ -318,14 +341,36 @@ async function runConnect(root, client, args) {
318
341
  projectId = picked;
319
342
  }
320
343
  }
321
- const installations = (await client.listGithubInstallations()).filter((i) => i.status === "active");
344
+ const listActiveInstallations = async () => (await client.listGithubInstallations()).filter((i) => i.status === "active");
345
+ let autoJoinAttempted = false;
346
+ const attemptAutoJoin = async () => {
347
+ autoJoinAttempted = true;
348
+ try {
349
+ return await performGithubJoin(client);
350
+ } catch (err) {
351
+ if (err instanceof PlatformApiError && err.status === 503 && err.code === "github_oauth_not_configured") return 0;
352
+ throw err;
353
+ }
354
+ };
355
+ let installations = await listActiveInstallations();
356
+ if (installations.length === 0 && isTTY) {
357
+ log.info("No GitHub App installation is linked to your account yet — checking whether your organization already installed the Void GitHub App…");
358
+ if (await attemptAutoJoin() > 0) installations = await listActiveInstallations();
359
+ }
322
360
  if (installations.length === 0) {
323
- log.error("No active GitHub App installation. Run `void github install` first.");
361
+ log.error(isTTY ? "No GitHub App installation found for your account or your organizations. Run `void github install` first." : "No active GitHub App installation. Run `void github install` first, or `void github join` if your organization already installed the Void GitHub App.");
324
362
  process.exit(1);
325
363
  }
326
364
  let installation;
327
365
  if (args.installationId) {
328
- const found = installations.find((i) => i.installation_id === args.installationId);
366
+ let found = installations.find((i) => i.installation_id === args.installationId);
367
+ if (!found && isTTY && !autoJoinAttempted) {
368
+ log.info(`Installation '${args.installationId}' is not linked to your account yet — checking whether your organization shares it…`);
369
+ if (await attemptAutoJoin() > 0) {
370
+ installations = await listActiveInstallations();
371
+ found = installations.find((i) => i.installation_id === args.installationId);
372
+ }
373
+ }
329
374
  if (!found) {
330
375
  log.error(`connect: No active installation found with id '${args.installationId}'.`);
331
376
  process.exit(1);
@@ -352,8 +397,11 @@ async function runConnect(root, client, args) {
352
397
  let repoFullName;
353
398
  if (args.repo) repoFullName = args.repo;
354
399
  else if (installation.repository_selection === "selected") if (installation.repositories.length === 0) {
355
- log.error("connect: The installation has no accessible repositories. Update the GitHub App install to grant access.");
356
- process.exit(1);
400
+ if (!isTTY) {
401
+ log.error("connect: --repo <owner/repo> is required — this installation does not expose a repository list to your account.");
402
+ process.exit(1);
403
+ }
404
+ repoFullName = await promptRepoFullName(root);
357
405
  } else if (installation.repositories.length === 1) repoFullName = installation.repositories[0].full_name;
358
406
  else if (!isTTY) {
359
407
  log.error("connect: Multiple repositories available. Pass --repo <owner/repo>.");
@@ -377,21 +425,7 @@ async function runConnect(root, client, args) {
377
425
  log.error("connect: --repo <owner/repo> is required in non-interactive mode for an all-repositories installation.");
378
426
  process.exit(1);
379
427
  }
380
- const gitDefault = parseGithubRepoFullName(collectGitDeployMetadata(root, null).repositoryUrl);
381
- const input = await text({
382
- message: "Repository (owner/repo):",
383
- placeholder: gitDefault ?? "owner/repo",
384
- initialValue: gitDefault ?? ""
385
- });
386
- if (isCancel(input)) {
387
- cancel("Cancelled.");
388
- process.exit(0);
389
- }
390
- repoFullName = input.trim();
391
- if (!repoFullName) {
392
- log.error("connect: Repository is required.");
393
- process.exit(1);
394
- }
428
+ repoFullName = await promptRepoFullName(root);
395
429
  }
396
430
  const branchDefault = args.branch ?? collectGitDeployMetadata(root, null).gitRef ?? "main";
397
431
  let branch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "void",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -353,7 +353,7 @@
353
353
  "valibot": ">=1.0.0-beta.7",
354
354
  "vite": "^8.0.0",
355
355
  "zod": "^3.25.0 || ^4.0.0",
356
- "@void/md": "0.10.7"
356
+ "@void/md": "0.10.8"
357
357
  },
358
358
  "peerDependenciesMeta": {
359
359
  "@void/md": {
@@ -609,6 +609,8 @@ void github join
609
609
 
610
610
  Join the GitHub App installations your organization already has. If a teammate installed the Void GitHub App on a shared GitHub organization, run `void github join` to gain access to those installations without re-installing. Void opens your browser to authorize (a localhost + PKCE handshake, the same mechanics as `void github link`), confirms with GitHub which installations you can manage, and records your membership. Afterwards `void github installations` lists them and `void github connect` can connect your own projects to their repositories.
611
611
 
612
+ In an interactive terminal you rarely need to run this yourself — `void github connect` runs the same join automatically when no active installations are linked to your account. Running `void github join` yourself matters mainly for non-interactive use (without a TTY, `void github connect` never opens a browser), or to link installations ahead of time.
613
+
612
614
  Requires an authenticated CLI (`void auth login` first) and organization-installation sharing enabled on your Void instance; when it is not enabled the command fails closed with a clear message. You can only join installations your GitHub authorization actually returns — you cannot name or join one you cannot access on GitHub.
613
615
 
614
616
  ### `void github connect`
@@ -619,6 +621,8 @@ void github connect [project] [options]
619
621
 
620
622
  Connect a GitHub repository to a Void project for automatic deploys. On every push to the configured branch, Void builds and deploys your project automatically.
621
623
 
624
+ Interactively (TTY), if your account has no active installations linked, `void github connect` first runs the same browser authorize as `void github join` automatically — if a teammate already installed the Void GitHub App on your organization, you join it on the spot and the connect continues. Only when no shared installation is found does it ask you to run `void github install`. The same recovery runs when `--installation <id>` names an installation your account is not linked to yet. Without a TTY, connect never opens a browser: it fails closed and tells you to run `void github install`, or `void github join` if your organization already installed the App.
625
+
622
626
  **Options**
623
627
 
624
628
  | Flag | Description |
@@ -636,7 +640,7 @@ Interactively (TTY), after resolving the repository and branch, `void github con
636
640
 
637
641
  **Non-interactive use (CI)**
638
642
 
639
- When stdin is not a TTY, `void github connect` never prompts — it fails closed and names any flag it needs. `--branch` is always required. `--project` must be resolvable (positional / `--project` / `VOID_PROJECT` / linked `.void/project.json`). `--installation` is required only when your account has more than one installation; otherwise the sole installation is used. `--repo` is required when the installation grants access to all repos or to more than one selected repo; when it grants exactly one repo that repo is used automatically. Use `void github installations` to discover the `installation_id`.
643
+ When stdin is not a TTY, `void github connect` never prompts — it fails closed and names any flag it needs. `--branch` is always required. `--project` must be resolvable (positional / `--project` / `VOID_PROJECT` / linked `.void/project.json`). `--installation` is required only when your account has more than one installation; otherwise the sole installation is used. `--repo` is required when the installation grants access to all repos or to more than one selected repo; when it grants exactly one repo that repo is used automatically. `--repo` is also required when the installation does not expose a repository list to your account (shared org installations hide it). Use `void github installations` to discover the `installation_id`.
640
644
 
641
645
  ```
642
646
  void github connect my-app \
@@ -649,7 +653,7 @@ void github connect my-app \
649
653
 
650
654
  **Connecting as an organization member**
651
655
 
652
- If you are a member of a GitHub organization but not the person who installed the App, `void github connect` first confirms that you personally have access to the specific repository. When it detects this, it opens your browser once to authorize access to that repo on GitHub (a localhost + PKCE handshake), then completes the connection automatically — no extra flags. Run `void github join` first so the installation appears in `void github installations`. You can only connect repositories you can access on GitHub; one you cannot see is refused with a clear message.
656
+ If you are a member of a GitHub organization but not the person who installed the App, `void github connect` first confirms that you personally have access to the specific repository. Interactively (TTY), when it detects this it opens your browser once to authorize access to that repo on GitHub (a localhost + PKCE handshake), then completes the connection automatically — no extra flags. Without a TTY, this per-repo authorization never opens a browser: connect fails closed with an error explaining that the installation requires per-repo authorization, which needs an interactive browser sign-in, and telling you to run `void github connect` locally to authorize, then retry. Interactively, connect joins the shared installation automatically when your account has no active installations linked, so running `void github join` first is optional — useful mainly to see the installation in `void github installations` beforehand. You can only connect repositories you can access on GitHub; one you cannot see is refused with a clear message.
653
657
 
654
658
  ### `void github update`
655
659