naro-cli 0.25.0 → 0.27.0

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/deploy.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  readProjectEnv,
12
12
  slugify,
13
13
  workingTreeNotices
14
- } from "./chunk-X5TUBMEU.js";
14
+ } from "./chunk-UGJFIEH7.js";
15
15
  import "./chunk-K3NQKI34.js";
16
16
  export {
17
17
  assertFunctionsNode,
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  BACKEND_STEPS,
5
5
  BOOKMARK_FORMAT_MESSAGE,
6
6
  BUNDLE_MESSAGES,
7
+ CAPABILITIES,
7
8
  DATA_ENV_MESSAGES,
8
9
  FUNCTION_INVOKE_MAX_RESPONSE_BYTES,
9
10
  FUNCTION_INVOKE_METHODS,
@@ -36,6 +37,9 @@ import {
36
37
  controlPlaneUrl,
37
38
  createAuthClient,
38
39
  createPrompter,
40
+ dashboardCapability,
41
+ dashboardLink,
42
+ dashboardNeeds,
39
43
  dataClient,
40
44
  dataEnvironment,
41
45
  dataError,
@@ -125,7 +129,7 @@ import {
125
129
  warningLines,
126
130
  withFunctionRetries,
127
131
  writeProjectLink
128
- } from "./chunk-X5TUBMEU.js";
132
+ } from "./chunk-UGJFIEH7.js";
129
133
  import "./chunk-K3NQKI34.js";
130
134
 
131
135
  // src/args.ts
@@ -4754,7 +4758,7 @@ async function zoneNamed(context, usage) {
4754
4758
  if (!zone)
4755
4759
  throw new NaroError(
4756
4760
  "NOT_FOUND",
4757
- `${domain} is not on naro DNS for team ${team}. \`naro dns\` lists the zones.`
4761
+ `${domain} is not on Naro DNS for team ${team}. \`naro dns\` lists the zones.`
4758
4762
  );
4759
4763
  return { client, team, zone };
4760
4764
  }
@@ -4769,7 +4773,7 @@ async function runDns(context) {
4769
4773
  const { client, team } = await teamAndClient(context);
4770
4774
  const state = await client.dns.state(team);
4771
4775
  context.output.result({ team, ...state }, [
4772
- `naro DNS for ${team}${state.enabled ? "" : " (not enabled on this server)"}:`,
4776
+ `Naro DNS for ${team}${state.enabled ? "" : " (not enabled on this server)"}:`,
4773
4777
  ...state.zones.length === 0 ? [" No zones. `naro dns add <domain>` starts a move."] : state.zones.flatMap((zone) => zoneLines(zone).map((l) => ` ${l}`))
4774
4778
  ]);
4775
4779
  }
@@ -4778,7 +4782,7 @@ async function runDnsAdd(context) {
4778
4782
  const { client, team } = await teamAndClient(context);
4779
4783
  const zone = await client.dns.addZone(team, domain);
4780
4784
  context.output.result({ team, zone }, [
4781
- `\u2705 Started moving ${zone.name} to naro DNS`,
4785
+ `\u2705 Started moving ${zone.name} to Naro DNS`,
4782
4786
  ` Set these name servers at your registrar: ${zone.nameServers.join(", ")}`,
4783
4787
  " `naro dns mail` shows the mail records to keep, `naro dns check` asks whether the move is done."
4784
4788
  ]);
@@ -4790,11 +4794,11 @@ async function runDnsRemove(context) {
4790
4794
  );
4791
4795
  await confirmOrAbort(
4792
4796
  context,
4793
- `Remove ${zone.name} from naro DNS (${zone.status})?`
4797
+ `Remove ${zone.name} from Naro DNS (${zone.status})?`
4794
4798
  );
4795
4799
  await client.dns.removeZone(team, zone.id);
4796
4800
  context.output.result({ team, removed: zone.name }, [
4797
- `\u2705 Removed ${zone.name} from naro DNS`
4801
+ `\u2705 Removed ${zone.name} from Naro DNS`
4798
4802
  ]);
4799
4803
  }
4800
4804
  async function runDnsCheck(context) {
@@ -5269,6 +5273,15 @@ async function accessJwtFor(config, jwtStore) {
5269
5273
  }
5270
5274
  return { error: invalidSession() };
5271
5275
  }
5276
+ async function freshAccessJwt(config) {
5277
+ const jwtStore = config.accessJwt;
5278
+ if (jwtStore === void 0 || config.service) return;
5279
+ const jwt = await jwtStore.get();
5280
+ if (jwt === null) return;
5281
+ const expiry = localExpiry(config, jwt);
5282
+ if (expiry === null || expiry - ACCESS_JWT_MARGIN_MS > Date.now()) return;
5283
+ await accessJwtFor(config, jwtStore);
5284
+ }
5272
5285
  async function bearerFor(config, path, withToken) {
5273
5286
  if (!withToken) return { token: null, session: null };
5274
5287
  const store = storeFor(config, path);
@@ -6205,6 +6218,7 @@ var AuthClient = class {
6205
6218
  const renewed = tokenFrom(result.answer);
6206
6219
  if (renewed !== null && token !== null && renewed !== token)
6207
6220
  await tokens.replaceIf(token, renewed);
6221
+ await freshAccessJwt(this.#http);
6208
6222
  return { data: { session: body.session, user }, error: null };
6209
6223
  }
6210
6224
  /** `{ data: { user } }`; `user` is null when signed out. */
@@ -9002,7 +9016,7 @@ var RealtimeChannel = class {
9002
9016
  payload: isRecord(payload.payload) ? payload.payload : {}
9003
9017
  };
9004
9018
  for (const binding of this.#bindings)
9005
- if (binding.type === "broadcast" && binding.event === event)
9019
+ if (binding.type === "broadcast" && (binding.event === event || binding.event === "*"))
9006
9020
  safely(() => binding.callback(message));
9007
9021
  }
9008
9022
  #system(payload) {
@@ -11823,6 +11837,47 @@ async function runFunctionsInvoke(context) {
11823
11837
  );
11824
11838
  }
11825
11839
 
11840
+ // src/commands/dashboard.ts
11841
+ var WITH_PAGE = CAPABILITIES.filter(
11842
+ (capability) => typeof capability.dashboard === "string"
11843
+ );
11844
+ async function runDashboard(context) {
11845
+ const [id] = context.positionals;
11846
+ if (id === void 0) {
11847
+ const rows = WITH_PAGE.map((capability2) => ({
11848
+ capability: capability2.id,
11849
+ title: capability2.title,
11850
+ cli: capability2.cli.kind === "built" ? capability2.cli.commands ?? [] : []
11851
+ }));
11852
+ const width = Math.max(...rows.map((row) => row.capability.length));
11853
+ context.output.result({ capabilities: rows }, [
11854
+ "Pages in the dashboard (naro dashboard <capability> prints the URL):",
11855
+ ...rows.map(
11856
+ (row) => ` ${row.capability.padEnd(width)} ${row.title}${row.cli.length === 0 ? " (dashboard only)" : ""}`
11857
+ )
11858
+ ]);
11859
+ return;
11860
+ }
11861
+ const capability = dashboardCapability(id);
11862
+ const needs = dashboardNeeds(capability.dashboard);
11863
+ const client = await context.client();
11864
+ const target = needs.project ? { projectId: await context.resolveProjectId() } : needs.team ? {
11865
+ teamSlug: (await resolveTeam(context, await client.teams.list(), {
11866
+ intent: "read"
11867
+ })).slug
11868
+ } : {};
11869
+ const link = await dashboardLink(client, capability, target, {
11870
+ baseUrl: controlPlaneUrl(),
11871
+ client: "cli"
11872
+ });
11873
+ context.output.result({ link }, [
11874
+ link.url,
11875
+ ` ${link.title}`,
11876
+ ...link.reason ? [` Not in the CLI: ${link.reason.why}`] : [],
11877
+ ...link.cli.length > 0 ? [` Or: ${link.cli.join(", ")}`] : []
11878
+ ]);
11879
+ }
11880
+
11826
11881
  // src/commands/readouts.ts
11827
11882
  function money(cents) {
11828
11883
  return `$${(cents / 100).toFixed(2)}`;
@@ -11841,7 +11896,9 @@ function billingLines(billing) {
11841
11896
  ...overage ? [
11842
11897
  ` overage ${overage.month}: ${money(overage.totalCents)} (requests ${money(overage.requestsCents)}, builds ${money(overage.buildsCents)}, domains ${money(overage.domainsCents)})`
11843
11898
  ] : [],
11844
- ...billing.canManage ? [" Change the plan in the dashboard: Settings \u2192 Billing."] : []
11899
+ ...billing.canManage ? [
11900
+ " Change the plan in the dashboard: naro dashboard billing-checkout prints the page."
11901
+ ] : []
11845
11902
  ];
11846
11903
  }
11847
11904
  async function runBilling(context) {
@@ -11877,51 +11934,6 @@ async function runObservability(context) {
11877
11934
  context.output.result({ projectId, report }, observabilityLines(report));
11878
11935
  }
11879
11936
 
11880
- // src/commands/supabase.ts
11881
- async function runSupabase(context) {
11882
- const projectId = await context.resolveProjectId();
11883
- const client = await context.client();
11884
- const links = await client.supabase.links(projectId);
11885
- context.output.result({ projectId, links }, [
11886
- `Supabase links (${String(links.length)}):`,
11887
- ...links.length === 0 ? [
11888
- " None. Connect a Supabase project in the dashboard, under the project's Settings."
11889
- ] : links.map(
11890
- (link) => ` ${link.target.padEnd(12)} ${link.supabaseName} (${link.supabaseRef}), ${link.keyKind} keys${link.includeSecret ? " + secret" : ""}`
11891
- )
11892
- ]);
11893
- }
11894
- async function runSupabaseDisconnect(context) {
11895
- const [given] = context.positionals;
11896
- const target = given === "all" ? void 0 : given;
11897
- const projectId = await context.resolveProjectId();
11898
- const client = await context.client();
11899
- await confirmOrAbort(
11900
- context,
11901
- target === void 0 ? "Disconnect Supabase from every target your role may change? The env vars it set are deleted, and the next deploy goes without them." : `Disconnect Supabase from ${target}? The env vars it set there are deleted, and the next deploy goes without them.`
11902
- );
11903
- const { removed } = await client.supabase.disconnect(
11904
- projectId,
11905
- target === void 0 ? {} : { target }
11906
- );
11907
- let remaining = [];
11908
- if (target === void 0) {
11909
- try {
11910
- remaining = (await client.supabase.links(projectId)).map(
11911
- (link) => link.target
11912
- );
11913
- } catch {
11914
- remaining = null;
11915
- }
11916
- }
11917
- context.output.result({ projectId, removed, remaining }, [
11918
- `\u2705 Removed ${String(removed)} Supabase link(s)`,
11919
- ...remaining === null ? ["\u26A0 Could not read which links are left; run `naro supabase` to check."] : remaining.length > 0 ? [
11920
- `\u26A0 Still connected: ${remaining.join(", ")} \u2014 your role cannot change those.`
11921
- ] : []
11922
- ]);
11923
- }
11924
-
11925
11937
  // src/commands/project-settings.ts
11926
11938
  var SET_USAGE2 = "Usage: naro project set [--subdomain <name>] [--framework <preset>] [--root <dir>] [--branch <branch>] [--build-command <script>] [--output-dir <dir>] [--vinext on|off]";
11927
11939
  var GRANT_USAGE = "Usage: naro project access grant <user-id> --role <project_admin|project_developer|project_viewer>";
@@ -12176,61 +12188,6 @@ async function runFlagRemove(context) {
12176
12188
  `\u2705 Removed flag ${flag.name}`
12177
12189
  ]);
12178
12190
  }
12179
- function bindingLine(binding) {
12180
- return `${binding.bindingName.padEnd(16)} ${binding.type.padEnd(3)} ${binding.resourceName}${binding.provisioned ? "" : " (not provisioned)"}${binding.managed ? " (managed by naro)" : ""}`;
12181
- }
12182
- async function runBindings(context) {
12183
- const { client, projectId } = await projectAndClient(context);
12184
- const bindings = await client.bindings.list(projectId);
12185
- context.output.result({ projectId, bindings }, [
12186
- `Bindings (${String(bindings.length)}):`,
12187
- ...bindings.length === 0 ? [
12188
- " None. `naro bindings add --type <r2|kv|d1> --name <BINDING>` creates one."
12189
- ] : bindings.map((binding) => ` ${bindingLine(binding)}`)
12190
- ]);
12191
- }
12192
- async function runBindingAdd(context) {
12193
- const { type, name } = context.flags;
12194
- if (!type || !name)
12195
- throw new NaroError(
12196
- "VALIDATION_FAILED",
12197
- "Usage: naro bindings add --type <r2|kv|d1> --name <BINDING>"
12198
- );
12199
- const { client, projectId } = await projectAndClient(context);
12200
- const binding = await client.bindings.add(projectId, type, name);
12201
- context.output.result({ projectId, binding }, [
12202
- `\u2705 Added ${bindingLine(binding)}`,
12203
- " The worker sees it from the next deploy."
12204
- ]);
12205
- }
12206
- async function runBindingRemove(context) {
12207
- const [name] = context.positionalOrThrow(
12208
- 1,
12209
- "Usage: naro bindings remove <BINDING>"
12210
- );
12211
- const { client, projectId } = await projectAndClient(context);
12212
- const binding = (await client.bindings.list(projectId)).find(
12213
- (candidate) => candidate.bindingName === name
12214
- );
12215
- if (!binding)
12216
- throw new NaroError(
12217
- "NOT_FOUND",
12218
- `No binding named ${name}. \`naro bindings\` lists them.`
12219
- );
12220
- if (binding.managed)
12221
- throw new NaroError(
12222
- "STORAGE_MANAGED",
12223
- "The Naro database is removed when the project is deleted. Set the project's database to None to unbind it."
12224
- );
12225
- await confirmOrAbort(
12226
- context,
12227
- `Remove the binding ${binding.bindingName}? This deletes the ${binding.type} resource ${binding.resourceName} and everything in it.`
12228
- );
12229
- await client.bindings.remove(projectId, binding.id);
12230
- context.output.result({ projectId, removed: binding.bindingName }, [
12231
- `\u2705 Removed ${binding.bindingName} and its ${binding.type} resource`
12232
- ]);
12233
- }
12234
12191
 
12235
12192
  // src/commands/teams.ts
12236
12193
  async function teamScope(context) {
@@ -14152,6 +14109,7 @@ Usage:
14152
14109
  naro usage [--month YYYY-MM] [--team <slug>]
14153
14110
  Team usage: requests, builds and custom domains against the plan, then by project
14154
14111
  naro billing [--team <slug>] The team's plan, subscription and this month's overage; the plan changes in the dashboard
14112
+ naro dashboard [<capability>] The dashboard URL for what the CLI leaves there (payments, GitHub App, \u2026); no argument lists them
14155
14113
 
14156
14114
  naro deploy [--prod] Build locally and deploy (preview by default)
14157
14115
  [--root <dir>] [--name <name>] --root points at the app in a monorepo
@@ -14192,8 +14150,6 @@ Usage:
14192
14150
  naro env import <file> [target] Upload a dotenv file's variables (asks first; --yes in CI)
14193
14151
  naro env pull [file] Write env vars to .env.local
14194
14152
  target is production, preview, all or an environment name; add and import use all when none is given
14195
- naro supabase [list] The project's Supabase links: target, Supabase project and keys (connect in the dashboard)
14196
- naro supabase disconnect [target] Remove a target's link, or (no target or all) every link your role may change, with its env vars (asks first)
14197
14153
 
14198
14154
  naro environments List the project's environments: address, branch, protection, data, and how many the plan allows
14199
14155
  naro environments create <slug> [--branch <b>] [--copy-env preview|production|none] [--database schema|empty]
@@ -14211,9 +14167,9 @@ Usage:
14211
14167
  naro domains verify [host] [--wait] Re-check DNS and print what is missing
14212
14168
  exits non-zero while a domain is not live yet
14213
14169
 
14214
- naro dns [list] [--team <slug>] naro DNS: the team's zones, their status and name servers
14215
- naro dns add <domain> Start moving a domain onto naro DNS; prints the name servers to set
14216
- naro dns remove <domain> Cancel the move, or take the domain off naro DNS (asks first; --yes in CI)
14170
+ naro dns [list] [--team <slug>] Naro DNS: the team's zones, their status and name servers
14171
+ naro dns add <domain> Start moving a domain onto Naro DNS; prints the name servers to set
14172
+ naro dns remove <domain> Cancel the move, or take the domain off Naro DNS (asks first; --yes in CI)
14217
14173
  naro dns check <domain> Read the zone's status and ask Cloudflare whether the move is done
14218
14174
  naro dns mail <domain> The mail records (MX, SPF, DMARC, DKIM) to keep before the move
14219
14175
  naro dns scan <domain> The records Cloudflare found in the current DNS
@@ -14238,10 +14194,6 @@ Usage:
14238
14194
  naro flags on <name> Turn a flag on; the live app follows within about a minute
14239
14195
  naro flags off <name> Turn a flag off
14240
14196
  naro flags remove <name> Remove a flag; code still reading it sees it off (asks first)
14241
- naro bindings [list] The R2, KV and D1 resources bound to the worker
14242
- naro bindings add --type <r2|kv|d1> --name <BINDING>
14243
- Create a resource and bind it as env.<BINDING> from the next deploy
14244
- naro bindings remove <BINDING> Delete the resource and everything in it (asks first; --yes in CI)
14245
14197
 
14246
14198
  The data commands below (db, migration, gen, backend, policy, storage, realtime) take --env <name>:
14247
14199
  a named environment's data instead of production's, which preview shares
@@ -14829,10 +14781,8 @@ function routeCommand(parsed) {
14829
14781
  // Spec 2026-10-18-usage-cli-mcp §2.
14830
14782
  case "usage":
14831
14783
  return positionals.length === 0 ? { handler: runUsage, positionals: [] } : null;
14832
- case "supabase":
14833
- if (sub === void 0 || sub === "list")
14834
- return positionals.length <= 1 ? { handler: runSupabase, positionals: [] } : null;
14835
- return sub === "disconnect" && positionals.length <= 2 ? { handler: runSupabaseDisconnect, positionals: positionals.slice(1) } : null;
14784
+ case "dashboard":
14785
+ return positionals.length <= 1 ? { handler: runDashboard, positionals } : null;
14836
14786
  case "billing":
14837
14787
  return positionals.length === 0 ? { handler: runBilling, positionals: [] } : null;
14838
14788
  case "observability":
@@ -15029,16 +14979,6 @@ function routeCommand(parsed) {
15029
14979
  const command2 = commands[sub];
15030
14980
  return command2 ? { handler: command2, positionals: positionals.slice(1) } : null;
15031
14981
  }
15032
- case "bindings": {
15033
- if (sub === void 0 || sub === "list")
15034
- return positionals.length <= 1 ? { handler: runBindings, positionals: [] } : null;
15035
- const commands = {
15036
- add: runBindingAdd,
15037
- remove: runBindingRemove
15038
- };
15039
- const command2 = commands[sub];
15040
- return command2 ? { handler: command2, positionals: positionals.slice(1) } : null;
15041
- }
15042
14982
  case "dns": {
15043
14983
  if (sub === void 0 || sub === "list")
15044
14984
  return positionals.length <= 1 ? { handler: runDns, positionals: [] } : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naro-cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naro": "./dist/index.js"
@@ -37,14 +37,14 @@
37
37
  "tsx": "4.23.12",
38
38
  "typescript": "7.0.2",
39
39
  "vitest": "3.2.7",
40
- "naro-js": "0.7.1",
41
- "@next-deploy/build": "0.1.0",
40
+ "naro-js": "0.9.0",
42
41
  "@next-deploy/config": "0.1.0",
42
+ "@next-deploy/build": "0.1.0",
43
+ "@next-deploy/core": "0.1.0",
44
+ "@next-deploy/data-core": "0.1.0",
43
45
  "@next-deploy/functions-bundler": "0.1.0",
44
46
  "@next-deploy/functions-runtime": "0.1.0",
45
- "@next-deploy/sdk": "0.1.0",
46
- "@next-deploy/core": "0.1.0",
47
- "@next-deploy/data-core": "0.1.0"
47
+ "@next-deploy/sdk": "0.1.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsup && pnpm build:types",