naro-cli 0.27.0 → 0.28.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.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type CacheTransport, type LocalBuildDependencies } from "@next-deploy/b
2
2
  import { type CliGitConnection } from "@next-deploy/sdk";
3
3
  import type { BuildObserver, DeploymentArtifact, DeploymentStatus, DeploymentStrategyRecord } from "./artifact-types";
4
4
  import { type GitInfo } from "./git-info";
5
+ import { type UserAdapterChoice } from "./user-adapter";
5
6
  import { type WorkingTreeCopy } from "./working-tree";
6
7
  /** The slice of LocalBuildExecutor the deploy flow needs — injectable in tests. */
7
8
  export interface DeployBuildExecutor {
@@ -14,6 +15,7 @@ export interface DeployBuildExecutor {
14
15
  env: Record<string, string>;
15
16
  strategy?: DeploymentStrategyRecord | null;
16
17
  vinextEnabled?: boolean;
18
+ userAdapter?: "replace" | "keep" | null;
17
19
  }, observer: BuildObserver, cache?: CacheTransport): Promise<{
18
20
  artifact: DeploymentArtifact;
19
21
  }>;
@@ -75,7 +77,7 @@ export interface DeployOptions {
75
77
  * enough to time some out when the whole suite ran at once.
76
78
  */
77
79
  pollIntervalMs?: number;
78
- /** Test seam: replaces the local OpenNext build. */
80
+ /** Test seam: replaces the local build. */
79
81
  executor?: DeployBuildExecutor;
80
82
  /** Test seam: replaces the `git` calls that read the repository. */
81
83
  gitInfo?: (dir: string) => Promise<GitInfo>;
@@ -84,6 +86,12 @@ export interface DeployOptions {
84
86
  * 대화형 CLI만 전달한다 — MCP·CI는 생략하고 안내만 받는다.
85
87
  */
86
88
  onMissingEnv?: (keys: string[]) => Promise<Record<string, string>>;
89
+ /**
90
+ * The answer to the adapter question (`--user-adapter`, the prompt, MCP's
91
+ * userAdapter), saved to the project once the deployment exists when it
92
+ * differs from the stored choice (spec 2026-11-07 S3).
93
+ */
94
+ userAdapter?: UserAdapterChoice | undefined;
87
95
  }
88
96
  export interface DeployResult {
89
97
  deploymentId: string;
@@ -105,7 +113,7 @@ export interface DeployResult {
105
113
  }
106
114
  /**
107
115
  * The full CLI deploy: authenticate, register the deployment with the
108
- * control plane, build locally (OpenNext for Next.js, the static pipeline
116
+ * control plane, build locally (vinext for Next.js, the static pipeline
109
117
  * for Vite/CRA/static sites), upload the artifact and poll until the
110
118
  * platform finishes publishing the worker.
111
119
  *
@@ -115,3 +123,4 @@ export declare function deployDirectory(sourceDir: string, options: DeployOption
115
123
  export { lintDatabase, type DatabaseLint } from "./commands/db-lint";
116
124
  export { readCapped } from "./functions/read-capped";
117
125
  export { assertFunctionsNode, deployFunctions, type DeployedFunction, type DeployFunctionsOptions, type DeployFunctionsResult, } from "./functions/deploy";
126
+ export { detectUserAdapter, parseUserAdapter, userAdapterUnreadable, type DetectedUserAdapter, type UserAdapterChoice, } from "./user-adapter";
package/dist/deploy.js CHANGED
@@ -3,26 +3,32 @@ import {
3
3
  assertFunctionsNode,
4
4
  deployDirectory,
5
5
  deployFunctions,
6
+ detectUserAdapter,
6
7
  eventsObserver,
7
8
  lintDatabase,
8
9
  localDependencies,
9
10
  parseDotenv,
11
+ parseUserAdapter,
10
12
  readCapped,
11
13
  readProjectEnv,
12
14
  slugify,
15
+ userAdapterUnreadable,
13
16
  workingTreeNotices
14
- } from "./chunk-UGJFIEH7.js";
17
+ } from "./chunk-UJGDKNDM.js";
15
18
  import "./chunk-K3NQKI34.js";
16
19
  export {
17
20
  assertFunctionsNode,
18
21
  deployDirectory,
19
22
  deployFunctions,
23
+ detectUserAdapter,
20
24
  eventsObserver,
21
25
  lintDatabase,
22
26
  localDependencies,
23
27
  parseDotenv,
28
+ parseUserAdapter,
24
29
  readCapped,
25
30
  readProjectEnv,
26
31
  slugify,
32
+ userAdapterUnreadable,
27
33
  workingTreeNotices
28
34
  };
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  STORAGE_OBJECTS_TABLE,
25
25
  STORAGE_SUBQUERIES_SQL,
26
26
  UNSHOWN_WARNING_LINE,
27
+ USER_ADAPTER_OPTIONS,
27
28
  appliedMigrations,
28
29
  applyMigrationFiles,
29
30
  assertFunctionsNode,
@@ -48,6 +49,7 @@ import {
48
49
  deployDirectory,
49
50
  deployFunctions,
50
51
  detectGitInfo,
52
+ detectUserAdapter,
51
53
  enableBackendCommand,
52
54
  ensureGitignoreHasNaro,
53
55
  envFlag,
@@ -81,6 +83,7 @@ import {
81
83
  parsePolicyNode,
82
84
  parsePolicyStatement,
83
85
  parseRedirectEntry,
86
+ parseUserAdapter,
84
87
  pendingMigrationNames,
85
88
  policiesForTypes,
86
89
  policiesLostByReset,
@@ -124,12 +127,16 @@ import {
124
127
  toNaroError,
125
128
  unreadablePolicyComment,
126
129
  usageMonthOf,
130
+ userAdapterQuestion,
131
+ userAdapterStored,
132
+ userAdapterUnasked,
133
+ userAdapterUnreadable,
127
134
  userForAccessToken,
128
135
  version,
129
136
  warningLines,
130
137
  withFunctionRetries,
131
138
  writeProjectLink
132
- } from "./chunk-UGJFIEH7.js";
139
+ } from "./chunk-UJGDKNDM.js";
133
140
  import "./chunk-K3NQKI34.js";
134
141
 
135
142
  // src/args.ts
@@ -257,6 +264,7 @@ var STRING_FLAGS = {
257
264
  "--build-command": "buildCommand",
258
265
  "--output-dir": "outputDir",
259
266
  "--vinext": "vinext",
267
+ "--user-adapter": "userAdapter",
260
268
  "--expires": "expires",
261
269
  "--verify-jwt": "verifyJwt",
262
270
  "--method": "method",
@@ -4207,6 +4215,7 @@ function deployTarget(flags) {
4207
4215
  async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDeploy }) {
4208
4216
  const { flags, output } = context;
4209
4217
  const target = deployTarget(flags);
4218
+ const flaggedAdapter = flaggedUserAdapter(flags);
4210
4219
  const link = flags.project ? { projectId: flags.project, teamSlug: void 0 } : readProjectLink(context.cwd);
4211
4220
  if (target.production && !flags.yes && !flags.json && isInteractive()) {
4212
4221
  const confirmed = await context.prompter.confirm("Deploy to production?");
@@ -4220,6 +4229,12 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
4220
4229
  const account = await accountLabel(context);
4221
4230
  output.log(` ${account} \u2192 ${team.slug}
4222
4231
  `);
4232
+ const userAdapter = await askUserAdapter(
4233
+ context,
4234
+ flaggedAdapter,
4235
+ link,
4236
+ canPrompt
4237
+ );
4223
4238
  const result = await deps.deploy(
4224
4239
  context.cwd,
4225
4240
  {
@@ -4246,7 +4261,8 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
4246
4261
  )
4247
4262
  },
4248
4263
  token: flags.token,
4249
- allowLogin: !flags.json && !flags.yes
4264
+ allowLogin: !flags.json && !flags.yes,
4265
+ ...userAdapter ? { userAdapter } : {}
4250
4266
  },
4251
4267
  (message) => output.log(message)
4252
4268
  );
@@ -4290,6 +4306,44 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
4290
4306
  ]
4291
4307
  );
4292
4308
  }
4309
+ function flaggedUserAdapter(flags) {
4310
+ if (flags.userAdapter === void 0) return void 0;
4311
+ const choice = parseUserAdapter(flags.userAdapter);
4312
+ if (!choice)
4313
+ throw new NaroError(
4314
+ "VALIDATION_FAILED",
4315
+ "--user-adapter takes replace or keep."
4316
+ );
4317
+ return choice;
4318
+ }
4319
+ async function askUserAdapter(context, flagged, link, canPrompt) {
4320
+ const { flags, output } = context;
4321
+ if (flagged) return flagged;
4322
+ const adapter = await detectUserAdapter(context.cwd, flags.root ?? null);
4323
+ if (!adapter) return void 0;
4324
+ let stored = null;
4325
+ if (link?.projectId) {
4326
+ try {
4327
+ stored = (await (await context.client()).projects.get(link.projectId)).userAdapter;
4328
+ } catch {
4329
+ output.log(userAdapterUnreadable(adapter));
4330
+ return void 0;
4331
+ }
4332
+ }
4333
+ if (stored) {
4334
+ output.log(userAdapterStored(adapter, stored));
4335
+ return void 0;
4336
+ }
4337
+ if (!canPrompt) {
4338
+ output.log(userAdapterUnasked(adapter));
4339
+ return void 0;
4340
+ }
4341
+ const index = await context.prompter.select(
4342
+ userAdapterQuestion(adapter),
4343
+ USER_ADAPTER_OPTIONS.map((option) => option.label)
4344
+ );
4345
+ return USER_ADAPTER_OPTIONS[index]?.value ?? "replace";
4346
+ }
4293
4347
  async function resolveDestination(context, link, canPrompt) {
4294
4348
  const client = await context.client();
4295
4349
  const teams = await client.teams.list();
@@ -11935,7 +11989,7 @@ async function runObservability(context) {
11935
11989
  }
11936
11990
 
11937
11991
  // src/commands/project-settings.ts
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]";
11992
+ var SET_USAGE2 = "Usage: naro project set [--subdomain <name>] [--framework <preset>] [--root <dir>] [--branch <branch>] [--build-command <script>] [--output-dir <dir>] [--vinext on|off] [--user-adapter replace|keep]";
11939
11993
  var GRANT_USAGE = "Usage: naro project access grant <user-id> --role <project_admin|project_developer|project_viewer>";
11940
11994
  function clearable(value) {
11941
11995
  return value === "" ? null : value;
@@ -11957,6 +12011,15 @@ function settingsFrom(context) {
11957
12011
  throw new NaroError("VALIDATION_FAILED", "--vinext takes on or off.");
11958
12012
  patch.vinextEnabled = flags.vinext === "on";
11959
12013
  }
12014
+ if (flags.userAdapter !== void 0) {
12015
+ const choice = parseUserAdapter(flags.userAdapter);
12016
+ if (!choice)
12017
+ throw new NaroError(
12018
+ "VALIDATION_FAILED",
12019
+ "--user-adapter takes replace or keep."
12020
+ );
12021
+ patch.userAdapter = choice;
12022
+ }
11960
12023
  if (Object.keys(patch).length === 0)
11961
12024
  throw new NaroError("VALIDATION_FAILED", SET_USAGE2);
11962
12025
  return patch;
@@ -14053,6 +14116,53 @@ function usageLines3(report, summary) {
14053
14116
  );
14054
14117
  return lines;
14055
14118
  }
14119
+ function tenths(value) {
14120
+ return Math.round(value * 10) / 10;
14121
+ }
14122
+ function cpuText(ms) {
14123
+ const whole = Math.round(ms);
14124
+ if (whole < 1e3) return `${String(whole)} ms`;
14125
+ const seconds = tenths(ms / 1e3);
14126
+ if (seconds < 60) return `${seconds.toFixed(1)} s`;
14127
+ const minutes = tenths(ms / 6e4);
14128
+ if (minutes < 60) return `${minutes.toFixed(1)} min`;
14129
+ return `${tenths(ms / 36e5).toFixed(1)} h`;
14130
+ }
14131
+ function environmentName(environment) {
14132
+ switch (environment.kind) {
14133
+ case "production":
14134
+ return "production";
14135
+ case "preview":
14136
+ return "preview";
14137
+ case "environment":
14138
+ return printable(environment.label);
14139
+ case "former":
14140
+ return `earlier name ${printable(environment.workerName)}`;
14141
+ }
14142
+ }
14143
+ function hostingLines(hosting) {
14144
+ if (!hosting || hosting.projects.length === 0) return [];
14145
+ const rows = [["CPU", "Invocations", "Project / environment"]];
14146
+ for (const project of hosting.projects) {
14147
+ rows.push([
14148
+ cpuText(project.cpuMs),
14149
+ formatCount(project.invocations),
14150
+ project.name === null ? `Not in this team ${printable(project.projectId)}` : printable(project.name)
14151
+ ]);
14152
+ for (const environment of project.environments)
14153
+ rows.push([
14154
+ cpuText(environment.cpuMs),
14155
+ formatCount(environment.invocations),
14156
+ ` ${environmentName(environment)}`
14157
+ ]);
14158
+ }
14159
+ return [
14160
+ "",
14161
+ "Compute (CPU) \xB7 observed, not billed \xB7 sampled estimate",
14162
+ ...table(rows),
14163
+ `Total: ${cpuText(hosting.totals.cpuMs)} of CPU over ${formatCount(hosting.totals.invocations)} invocations`
14164
+ ];
14165
+ }
14056
14166
 
14057
14167
  // src/commands/usage.ts
14058
14168
  async function runUsage(context) {
@@ -14060,12 +14170,26 @@ async function runUsage(context) {
14060
14170
  const teams = await client.teams.list();
14061
14171
  const team = await resolveTeam(context, teams, { intent: "read" });
14062
14172
  const month = context.flags.month;
14063
- const report = await client.teams.usage(
14064
- team.slug,
14065
- month !== void 0 ? { month } : {}
14066
- );
14173
+ const query = month !== void 0 ? { month } : {};
14174
+ const report = await client.teams.usage(team.slug, query);
14067
14175
  const summary = summarizeTeamUsage(report, usageMonthOf(/* @__PURE__ */ new Date()));
14068
- context.output.result({ ...report, summary }, usageLines3(report, summary));
14176
+ let hosting = null;
14177
+ let hostingError;
14178
+ try {
14179
+ hosting = await client.teams.usageHosting(team.slug, query);
14180
+ } catch (error) {
14181
+ hostingError = {
14182
+ code: error instanceof NaroError ? error.code : "UNKNOWN",
14183
+ message: error instanceof Error ? error.message : String(error)
14184
+ };
14185
+ context.output.warn(
14186
+ `Could not read compute usage: ${hostingError.message}`
14187
+ );
14188
+ }
14189
+ context.output.result(
14190
+ hostingError ? { ...report, summary, hosting, hostingError } : { ...report, summary, hosting },
14191
+ [...usageLines3(report, summary), ...hostingLines(hosting)]
14192
+ );
14069
14193
  }
14070
14194
 
14071
14195
  // src/router.ts
@@ -14098,6 +14222,7 @@ Usage:
14098
14222
  naro project delete Delete the project and everything under it: type its name to confirm
14099
14223
  naro project set [--subdomain <name>] [--framework <preset>] [--root <dir>] [--branch <branch>]
14100
14224
  [--build-command <script>] [--output-dir <dir>] [--vinext on|off]
14225
+ [--user-adapter replace|keep]
14101
14226
  Change the General settings; an empty value clears one. A new subdomain
14102
14227
  moves the project's address at once (asks first; --yes in CI)
14103
14228
  naro project check <subdomain> Whether a subdomain is free
@@ -14107,7 +14232,7 @@ Usage:
14107
14232
  Take a contributor's project role away (asks first)
14108
14233
  [--project <id> --yes] (in CI both; --yes alone is refused so the folder cannot choose the project)
14109
14234
  naro usage [--month YYYY-MM] [--team <slug>]
14110
- Team usage: requests, builds and custom domains against the plan, then by project
14235
+ Team usage: requests, builds and custom domains against the plan, then by project, then CPU by project and environment
14111
14236
  naro billing [--team <slug>] The team's plan, subscription and this month's overage; the plan changes in the dashboard
14112
14237
  naro dashboard [<capability>] The dashboard URL for what the CLI leaves there (payments, GitHub App, \u2026); no argument lists them
14113
14238
 
@@ -0,0 +1,32 @@
1
+ export type UserAdapterChoice = "replace" | "keep";
2
+ export interface DetectedUserAdapter {
3
+ /** The active next.config, relative to the app directory. */
4
+ file: string;
5
+ key: "adapterPath" | "experimental.adapterPath";
6
+ }
7
+ /** The next.config Next would load sets adapterPath, read the way Naro's builders read it. */
8
+ export declare function detectUserAdapter(directory: string, rootDirectory: string | null): Promise<DetectedUserAdapter | null>;
9
+ export declare function parseUserAdapter(value: unknown): UserAdapterChoice | null;
10
+ export declare const USER_ADAPTER_OPTIONS: ReadonlyArray<{
11
+ value: UserAdapterChoice;
12
+ label: string;
13
+ }>;
14
+ export declare function userAdapterQuestion(adapter: DetectedUserAdapter): string;
15
+ /** One line on what a choice means for this deploy (the local build has no Naro adapter, spec S2 D8). */
16
+ export declare function userAdapterOutcome(choice: UserAdapterChoice): string;
17
+ /** The line for a project that already chose. */
18
+ export declare function userAdapterStored(adapter: DetectedUserAdapter, choice: UserAdapterChoice): string;
19
+ /** The warning when nobody chose and nobody can be asked. */
20
+ export declare function userAdapterUnasked(adapter: DetectedUserAdapter): string;
21
+ /** Where each surface changes the stored choice. */
22
+ declare const CHOOSE_USER_ADAPTER: {
23
+ readonly cli: "naro project set --user-adapter replace|keep";
24
+ readonly mcp: "update_project with userAdapter replace|keep";
25
+ };
26
+ /**
27
+ * The warning when the project's stored choice could not be read. It may
28
+ * already say keep, so this line must not claim replace the way
29
+ * userAdapterUnasked does.
30
+ */
31
+ export declare function userAdapterUnreadable(adapter: DetectedUserAdapter, surface?: keyof typeof CHOOSE_USER_ADAPTER): string;
32
+ export {};
@@ -49,7 +49,7 @@ export interface WorkingTreeCopy {
49
49
  * edit to a committed `.env`, one hidden behind `skip-worktree`, one only
50
50
  * staged, and an untracked `.env.local` all stay on the machine. `next build`
51
51
  * reads those files on its own and inlines NEXT_PUBLIC_* into the bundle, and
52
- * OpenNext bundles every value they hold into the Worker, so a file copied
52
+ * the bundler inlines every value they hold into the Worker, so a file copied
53
53
  * here would ship values the developer declined to send. The env consent
54
54
  * prompt is the only way local values reach a build (known-issues #7,
55
55
  * CLI-DEPLOY-011).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naro-cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naro": "./dist/index.js"
@@ -23,7 +23,6 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@cloudflare/unenv-preset": "2.16.2",
26
- "@opennextjs/cloudflare": "1.20.12",
27
26
  "@supabase/supabase-js": "2.112.3",
28
27
  "@vercel/frameworks": "3.34.0",
29
28
  "esbuild": "0.28.1",
@@ -38,13 +37,13 @@
38
37
  "typescript": "7.0.2",
39
38
  "vitest": "3.2.7",
40
39
  "naro-js": "0.9.0",
41
- "@next-deploy/config": "0.1.0",
42
40
  "@next-deploy/build": "0.1.0",
43
- "@next-deploy/core": "0.1.0",
41
+ "@next-deploy/config": "0.1.0",
44
42
  "@next-deploy/data-core": "0.1.0",
45
- "@next-deploy/functions-bundler": "0.1.0",
46
43
  "@next-deploy/functions-runtime": "0.1.0",
47
- "@next-deploy/sdk": "0.1.0"
44
+ "@next-deploy/functions-bundler": "0.1.0",
45
+ "@next-deploy/sdk": "0.1.0",
46
+ "@next-deploy/core": "0.1.0"
48
47
  },
49
48
  "scripts": {
50
49
  "build": "tsup && pnpm build:types",