naro-cli 0.1.0 → 0.2.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.
@@ -7,9 +7,9 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/deploy.ts
9
9
  import { existsSync, readFileSync as readFileSync2 } from "fs";
10
- import { cp, mkdtemp as mkdtemp2, readFile as readFile5, rm as rm2 } from "fs/promises";
11
- import { tmpdir as tmpdir2 } from "os";
12
- import { basename, dirname, join as join8 } from "path";
10
+ import { cp, mkdtemp as mkdtemp2, readFile as readFile6, rm as rm2 } from "fs/promises";
11
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
12
+ import { basename, dirname, join as join9 } from "path";
13
13
  import { fileURLToPath } from "url";
14
14
 
15
15
  // ../../packages/build/src/cache.ts
@@ -110,7 +110,7 @@ async function saveCache(args) {
110
110
  // ../../packages/build/src/local-build-executor.ts
111
111
  import { mkdtemp, rm } from "fs/promises";
112
112
  import { tmpdir } from "os";
113
- import { join as join6 } from "path";
113
+ import { join as join7 } from "path";
114
114
 
115
115
  // ../../packages/core/src/errors.ts
116
116
  var DeploymentError = class extends Error {
@@ -14722,10 +14722,46 @@ async function detectPackageManager(root) {
14722
14722
  );
14723
14723
  }
14724
14724
 
14725
+ // ../../packages/build/src/proxy-to-middleware.ts
14726
+ import { access as access2, readFile as readFile3, rename, writeFile as writeFile2 } from "fs/promises";
14727
+ import { join as join3 } from "path";
14728
+ var EXTENSIONS = ["ts", "tsx", "js", "jsx"];
14729
+ var DIRECTORIES = ["", "src"];
14730
+ async function exists2(path) {
14731
+ try {
14732
+ await access2(path);
14733
+ return true;
14734
+ } catch {
14735
+ return false;
14736
+ }
14737
+ }
14738
+ function renameExport(source) {
14739
+ return source.replace(
14740
+ /(export\s+(?:default\s+)?(?:async\s+)?function\s+)proxy\b/g,
14741
+ "$1middleware"
14742
+ ).replace(/(export\s+const\s+)proxy\b/g, "$1middleware");
14743
+ }
14744
+ async function convertProxyToMiddleware(appRoot) {
14745
+ for (const dir of DIRECTORIES) {
14746
+ for (const ext of EXTENSIONS) {
14747
+ const rel = join3(dir, `proxy.${ext}`);
14748
+ const proxyPath = join3(appRoot, rel);
14749
+ if (!await exists2(proxyPath)) continue;
14750
+ const middlewarePath = join3(appRoot, dir, `middleware.${ext}`);
14751
+ if (await exists2(middlewarePath)) return { converted: false };
14752
+ const source = await readFile3(proxyPath, "utf8");
14753
+ await writeFile2(middlewarePath, renameExport(source));
14754
+ await rename(proxyPath, `${proxyPath}.converted`);
14755
+ return { converted: true, file: rel };
14756
+ }
14757
+ }
14758
+ return { converted: false };
14759
+ }
14760
+
14725
14761
  // ../../packages/build/src/platform-config.ts
14726
- import { access as access2, writeFile as writeFile2 } from "fs/promises";
14762
+ import { access as access3, writeFile as writeFile3 } from "fs/promises";
14727
14763
  import { createRequire } from "module";
14728
- import { join as join3 } from "path";
14764
+ import { join as join4 } from "path";
14729
14765
  var COMPATIBILITY_DATE = "2026-08-19";
14730
14766
  var NEXT_CONFIG_FILES = [
14731
14767
  "next.config.js",
@@ -14735,31 +14771,31 @@ var NEXT_CONFIG_FILES = [
14735
14771
  ];
14736
14772
  async function pathExists(path) {
14737
14773
  try {
14738
- await access2(path);
14774
+ await access3(path);
14739
14775
  return true;
14740
14776
  } catch {
14741
14777
  return false;
14742
14778
  }
14743
14779
  }
14744
14780
  async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
14745
- await writeFile2(path, contents, "utf8");
14781
+ await writeFile3(path, contents, "utf8");
14746
14782
  }, cloudflareModulePath = createRequire(import.meta.url).resolve(
14747
14783
  "@opennextjs/cloudflare"
14748
- ), exists2 = pathExists) {
14749
- const openNextConfigPath = join3(appRoot, ".next-deploy-open-next.config.mjs");
14750
- const wranglerConfigPath = join3(appRoot, ".next-deploy-wrangler.json");
14751
- const packageManagerConfigPath = join3(appRoot, ".next-deploy-user.npmrc");
14784
+ ), exists3 = pathExists) {
14785
+ const openNextConfigPath = join4(appRoot, ".next-deploy-open-next.config.mjs");
14786
+ const wranglerConfigPath = join4(appRoot, ".next-deploy-wrangler.json");
14787
+ const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
14752
14788
  const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
14753
14789
  let hasNextConfig = false;
14754
14790
  for (const name of NEXT_CONFIG_FILES) {
14755
- if (await exists2(join3(appRoot, name))) {
14791
+ if (await exists3(join4(appRoot, name))) {
14756
14792
  hasNextConfig = true;
14757
14793
  break;
14758
14794
  }
14759
14795
  }
14760
14796
  if (!hasNextConfig) {
14761
14797
  await writer(
14762
- join3(appRoot, "next.config.mjs"),
14798
+ join4(appRoot, "next.config.mjs"),
14763
14799
  'export default { output: "standalone" };\n'
14764
14800
  );
14765
14801
  }
@@ -14800,8 +14836,8 @@ export default defineCloudflareConfig({});
14800
14836
  }
14801
14837
 
14802
14838
  // ../../packages/build/src/open-next.ts
14803
- import { access as access3, readdir, readFile as readFile3 } from "fs/promises";
14804
- import { extname, isAbsolute, join as join4, relative, resolve, sep } from "path";
14839
+ import { access as access4, readdir, readFile as readFile4 } from "fs/promises";
14840
+ import { extname, isAbsolute, join as join5, relative, resolve, sep } from "path";
14805
14841
  function parseJsonc(source) {
14806
14842
  let output = "";
14807
14843
  let index = 0;
@@ -14896,7 +14932,7 @@ async function listFiles(root) {
14896
14932
  for (const entry of entries.sort(
14897
14933
  (left, right) => left.name.localeCompare(right.name)
14898
14934
  )) {
14899
- const path = join4(directory, entry.name);
14935
+ const path = join5(directory, entry.name);
14900
14936
  if (entry.isSymbolicLink()) {
14901
14937
  throw new DeploymentError(
14902
14938
  "ARTIFACT_INVALID",
@@ -14907,7 +14943,7 @@ async function listFiles(root) {
14907
14943
  else if (entry.isFile()) {
14908
14944
  files.push({
14909
14945
  name: relative(root, path).split(sep).join("/"),
14910
- data: await readFile3(path),
14946
+ data: await readFile4(path),
14911
14947
  contentType: contentType(path)
14912
14948
  });
14913
14949
  }
@@ -14926,13 +14962,13 @@ async function findWranglerConfig(root, configuredPath) {
14926
14962
  "Wrangler config escapes the application root."
14927
14963
  );
14928
14964
  }
14929
- await access3(resolved);
14965
+ await access4(resolved);
14930
14966
  return resolved;
14931
14967
  }
14932
14968
  for (const name of ["wrangler.jsonc", "wrangler.json"]) {
14933
- const path = join4(root, name);
14969
+ const path = join5(root, name);
14934
14970
  try {
14935
- await access3(path);
14971
+ await access4(path);
14936
14972
  return path;
14937
14973
  } catch {
14938
14974
  }
@@ -14946,7 +14982,7 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
14946
14982
  try {
14947
14983
  const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
14948
14984
  const metadata = parseJsonc(
14949
- await readFile3(configPath, "utf8")
14985
+ await readFile4(configPath, "utf8")
14950
14986
  );
14951
14987
  if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
14952
14988
  throw new DeploymentError(
@@ -14967,8 +15003,8 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
14967
15003
  "Worker entrypoint must be inside .open-next."
14968
15004
  );
14969
15005
  }
14970
- await access3(entrypointPath);
14971
- const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") : join4(outputRoot, "assets");
15006
+ await access4(entrypointPath);
15007
+ const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") : join5(outputRoot, "assets");
14972
15008
  const assetsRelation = relative(outputRoot, configuredAssets);
14973
15009
  if (assetsRelation === ".." || assetsRelation.startsWith(`..${sep}`) || isAbsolute(assetsRelation)) {
14974
15010
  throw new DeploymentError(
@@ -14976,10 +15012,10 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
14976
15012
  "Assets directory must be inside .open-next."
14977
15013
  );
14978
15014
  }
14979
- const bundleRoot = join4(outputRoot, "worker-bundle");
15015
+ const bundleRoot = join5(outputRoot, "worker-bundle");
14980
15016
  let bundleFiles = null;
14981
15017
  try {
14982
- await access3(bundleRoot);
15018
+ await access4(bundleRoot);
14983
15019
  bundleFiles = await listFiles(bundleRoot);
14984
15020
  } catch {
14985
15021
  }
@@ -15162,13 +15198,13 @@ function resolveAppRoot(checkout, configuredRoot) {
15162
15198
  }
15163
15199
 
15164
15200
  // ../../packages/build/src/next-project.ts
15165
- import { readFile as readFile4 } from "fs/promises";
15166
- import { join as join5 } from "path";
15201
+ import { readFile as readFile5 } from "fs/promises";
15202
+ import { join as join6 } from "path";
15167
15203
  async function validateNextProject(root) {
15168
15204
  let packageJson;
15169
15205
  try {
15170
15206
  packageJson = JSON.parse(
15171
- await readFile4(join5(root, "package.json"), "utf8")
15207
+ await readFile5(join6(root, "package.json"), "utf8")
15172
15208
  );
15173
15209
  } catch (error51) {
15174
15210
  throw new DeploymentError(
@@ -15197,7 +15233,7 @@ async function validateNextProject(root) {
15197
15233
 
15198
15234
  // ../../packages/build/src/local-build-executor.ts
15199
15235
  var defaultDependencies = {
15200
- createWorkspace: () => mkdtemp(join6(tmpdir(), "next-deploy-build-")),
15236
+ createWorkspace: () => mkdtemp(join7(tmpdir(), "next-deploy-build-")),
15201
15237
  removeWorkspace: (path) => rm(path, { recursive: true, force: true }),
15202
15238
  runCommand,
15203
15239
  detectPackageManager,
@@ -15207,7 +15243,8 @@ var defaultDependencies = {
15207
15243
  readOpenNextArtifact,
15208
15244
  openNextCommand: "opennextjs-cloudflare",
15209
15245
  wranglerCommand: "wrangler",
15210
- cacheFetch: (input, init) => fetch(input, init)
15246
+ cacheFetch: (input, init) => fetch(input, init),
15247
+ convertProxyToMiddleware
15211
15248
  };
15212
15249
  function sanitizeCacheKey(value) {
15213
15250
  return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 128);
@@ -15247,14 +15284,14 @@ var LocalBuildExecutor = class {
15247
15284
  }
15248
15285
  async build(input, observer, cache) {
15249
15286
  const workspace = await this.dependencies.createWorkspace();
15250
- const checkout = join6(workspace, "checkout");
15251
- const pmCacheDir = join6(workspace, ".pm-store");
15287
+ const checkout = join7(workspace, "checkout");
15288
+ const pmCacheDir = join7(workspace, ".pm-store");
15252
15289
  let cacheSeq = 0;
15253
15290
  const cachePorts = cache ? {
15254
15291
  transport: cache,
15255
15292
  fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
15256
15293
  runCommand: this.dependencies.runCommand,
15257
- tmpFile: (suffix) => join6(workspace, `cache-${String(cacheSeq++)}${suffix}`)
15294
+ tmpFile: (suffix) => join7(workspace, `cache-${String(cacheSeq++)}${suffix}`)
15258
15295
  } : null;
15259
15296
  try {
15260
15297
  await notifyStatus(observer, "CLONING");
@@ -15308,6 +15345,14 @@ var LocalBuildExecutor = class {
15308
15345
  "CLONING",
15309
15346
  `Validated Next.js ${project.nextVersion}`
15310
15347
  );
15348
+ const conversion = await (this.dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware)(appRoot);
15349
+ if (conversion.converted) {
15350
+ await observer.onLog({
15351
+ level: "WARN",
15352
+ stage: "CLONING",
15353
+ message: `Converted ${conversion.file ?? "proxy"} to edge middleware. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable.`
15354
+ });
15355
+ }
15311
15356
  const platformConfig = await this.dependencies.createPlatformBuildConfig(
15312
15357
  appRoot,
15313
15358
  input.deploymentId
@@ -15355,7 +15400,7 @@ var LocalBuildExecutor = class {
15355
15400
  observer
15356
15401
  );
15357
15402
  await notifyStatus(observer, "BUILDING");
15358
- const nextCacheDir = join6(appRoot, ".next", "cache");
15403
+ const nextCacheDir = join7(appRoot, ".next", "cache");
15359
15404
  const nextKey = sanitizeCacheKey(input.branch);
15360
15405
  if (cachePorts) {
15361
15406
  await tryRestore("next", nextKey, nextCacheDir, cachePorts, observer);
@@ -15407,7 +15452,7 @@ var LocalBuildExecutor = class {
15407
15452
  ],
15408
15453
  cwd: appRoot,
15409
15454
  env: {
15410
- WRANGLER_LOG_PATH: join6(appRoot, ".open-next", "wrangler.log")
15455
+ WRANGLER_LOG_PATH: join7(appRoot, ".open-next", "wrangler.log")
15411
15456
  },
15412
15457
  timeoutMs: 5 * 6e4,
15413
15458
  stage: "BUILDING",
@@ -15437,15 +15482,49 @@ var LocalBuildExecutor = class {
15437
15482
  }
15438
15483
  };
15439
15484
 
15485
+ // src/git-info.ts
15486
+ import { execFile } from "child_process";
15487
+ var SUPPORTED_REMOTE = [
15488
+ /^https:\/\/[^\s]+$/,
15489
+ /^ssh:\/\/[^\s]+$/,
15490
+ /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
15491
+ ];
15492
+ var defaultExec = (args, cwd) => new Promise((resolve3, reject) => {
15493
+ execFile("git", args, { cwd }, (error51, stdout) => {
15494
+ if (error51) reject(error51);
15495
+ else resolve3(stdout);
15496
+ });
15497
+ });
15498
+ async function tryExec(exec, args, cwd) {
15499
+ try {
15500
+ const out = (await exec(args, cwd)).trim();
15501
+ return out.length > 0 ? out : null;
15502
+ } catch {
15503
+ return null;
15504
+ }
15505
+ }
15506
+ async function detectGitInfo(dir, exec = defaultExec) {
15507
+ const remote = await tryExec(exec, ["remote", "get-url", "origin"], dir);
15508
+ const branch = await tryExec(exec, ["rev-parse", "--abbrev-ref", "HEAD"], dir);
15509
+ return {
15510
+ repositoryUrl: remote !== null && SUPPORTED_REMOTE.some((re) => re.test(remote)) ? remote : null,
15511
+ branch: branch !== null && branch !== "HEAD" ? branch : null
15512
+ };
15513
+ }
15514
+
15515
+ // src/login.ts
15516
+ import { spawn as spawn2 } from "child_process";
15517
+ import { homedir as homedir2 } from "os";
15518
+
15440
15519
  // src/session.ts
15441
15520
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
15442
15521
  import { homedir } from "os";
15443
- import { join as join7 } from "path";
15522
+ import { join as join8 } from "path";
15444
15523
  function credentialsDir(home = homedir()) {
15445
- return join7(home, ".naro");
15524
+ return join8(home, ".naro");
15446
15525
  }
15447
15526
  function credentialsFile(home = homedir()) {
15448
- return join7(credentialsDir(home), "credentials.json");
15527
+ return join8(credentialsDir(home), "credentials.json");
15449
15528
  }
15450
15529
  function saveSession(session, home = homedir()) {
15451
15530
  mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
@@ -15610,6 +15689,71 @@ async function login(deps) {
15610
15689
  }
15611
15690
  }
15612
15691
 
15692
+ // src/login.ts
15693
+ var stdio = {
15694
+ out: (m) => process.stdout.write(`${m}
15695
+ `),
15696
+ err: (m) => process.stderr.write(`${m}
15697
+ `)
15698
+ };
15699
+ function openBrowser(url2) {
15700
+ const platform = process.platform;
15701
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
15702
+ const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
15703
+ spawn2(command, args, { stdio: "ignore", detached: true }).unref();
15704
+ }
15705
+ async function runLogin(io = stdio) {
15706
+ io.out("Opening your browser to sign in with GitHub...");
15707
+ const session = await login({
15708
+ openBrowser: (url2) => {
15709
+ io.out(`If your browser did not open, visit:
15710
+ ${url2}`);
15711
+ openBrowser(url2);
15712
+ }
15713
+ });
15714
+ saveSession(session);
15715
+ io.out(`\u2705 Logged in as ${session.user.email ?? session.user.id}`);
15716
+ }
15717
+ async function runWhoami(io = stdio, home = homedir2(), refresh = refreshStoredSession) {
15718
+ let session = loadStoredSession(home);
15719
+ if (!session) {
15720
+ io.err("Not logged in. Run `naro login`.");
15721
+ process.exitCode = 1;
15722
+ return;
15723
+ }
15724
+ if (isExpired(session)) {
15725
+ try {
15726
+ session = await refresh(session.refresh_token);
15727
+ saveSession(session, home);
15728
+ } catch {
15729
+ io.err("Session expired. Run `naro login`.");
15730
+ process.exitCode = 1;
15731
+ return;
15732
+ }
15733
+ }
15734
+ const handle = session.user.github ? ` (${session.user.github})` : "";
15735
+ io.out(`Logged in as ${session.user.email ?? session.user.id}${handle}`);
15736
+ }
15737
+ async function defaultSignOut(session) {
15738
+ const client = createAuthClient();
15739
+ await client.auth.setSession({
15740
+ access_token: session.access_token,
15741
+ refresh_token: session.refresh_token
15742
+ });
15743
+ await client.auth.signOut();
15744
+ }
15745
+ async function runLogout(io = stdio, home = homedir2(), signOut = defaultSignOut) {
15746
+ const session = loadStoredSession(home);
15747
+ if (session) {
15748
+ try {
15749
+ await signOut(session);
15750
+ } catch {
15751
+ }
15752
+ }
15753
+ clearSession(home);
15754
+ io.out("Logged out.");
15755
+ }
15756
+
15613
15757
  // src/upload.ts
15614
15758
  function base(opts) {
15615
15759
  return `${opts.controlPlaneUrl.replace(/\/$/, "")}/api/internal/deployments/${opts.deploymentId}`;
@@ -15678,7 +15822,14 @@ async function startCliDeployment(accessToken, opts) {
15678
15822
  "Content-Type": "application/json",
15679
15823
  Authorization: `Bearer ${accessToken}`
15680
15824
  },
15681
- body: JSON.stringify({ name: opts.name, production: opts.production })
15825
+ body: JSON.stringify({
15826
+ name: opts.name,
15827
+ production: opts.production,
15828
+ ...opts.repositoryUrl != null && { repositoryUrl: opts.repositoryUrl },
15829
+ ...opts.branch != null && { branch: opts.branch },
15830
+ ...opts.rootDirectory != null && { rootDirectory: opts.rootDirectory },
15831
+ ...opts.env !== void 0 && { env: opts.env }
15832
+ })
15682
15833
  }
15683
15834
  );
15684
15835
  if (!response.ok) {
@@ -15693,13 +15844,26 @@ async function startCliDeployment(accessToken, opts) {
15693
15844
  function controlPlaneUrl(env = process.env) {
15694
15845
  return (env.NARO_CONTROL_PLANE_URL ?? "https://naro.sh").replace(/\/$/, "");
15695
15846
  }
15696
- async function requireSession() {
15697
- let session = loadStoredSession();
15698
- if (!session) throw new Error("Not logged in. Run `naro login`.");
15699
- if (isExpired(session)) {
15700
- session = await refreshStoredSession(session.refresh_token);
15701
- saveSession(session);
15847
+ async function requireSessionOrLogin(log2, home = homedir3(), loginFlow = () => login({
15848
+ openBrowser: (url2) => {
15849
+ log2(`If your browser did not open, visit:
15850
+ ${url2}`);
15851
+ openBrowser(url2);
15702
15852
  }
15853
+ })) {
15854
+ const stored = loadStoredSession(home);
15855
+ if (stored) {
15856
+ if (!isExpired(stored)) return stored;
15857
+ try {
15858
+ const refreshed = await refreshStoredSession(stored.refresh_token);
15859
+ saveSession(refreshed, home);
15860
+ return refreshed;
15861
+ } catch {
15862
+ }
15863
+ }
15864
+ log2("Not logged in \u2014 opening your browser to sign in with GitHub\u2026");
15865
+ const session = await loginFlow();
15866
+ saveSession(session, home);
15703
15867
  return session;
15704
15868
  }
15705
15869
  function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
@@ -15737,7 +15901,7 @@ function parseDotenv(text) {
15737
15901
  }
15738
15902
  function readProjectEnv(dir) {
15739
15903
  for (const name of [".env.local", ".env.production", ".env"]) {
15740
- const path = join8(dir, name);
15904
+ const path = join9(dir, name);
15741
15905
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
15742
15906
  }
15743
15907
  return {};
@@ -15749,7 +15913,7 @@ function slugify2(value) {
15749
15913
  async function projectName(sourceDir) {
15750
15914
  try {
15751
15915
  const pkg = JSON.parse(
15752
- await readFile5(join8(sourceDir, "package.json"), "utf8")
15916
+ await readFile6(join9(sourceDir, "package.json"), "utf8")
15753
15917
  );
15754
15918
  if (pkg.name) return pkg.name;
15755
15919
  } catch {
@@ -15760,7 +15924,7 @@ var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.tu
15760
15924
  function resolveBin(name) {
15761
15925
  let dir = dirname(fileURLToPath(import.meta.url));
15762
15926
  for (; ; ) {
15763
- const candidate = join8(dir, "node_modules", ".bin", name);
15927
+ const candidate = join9(dir, "node_modules", ".bin", name);
15764
15928
  if (existsSync(candidate)) return candidate;
15765
15929
  const parent = dirname(dir);
15766
15930
  if (parent === dir) return name;
@@ -15785,7 +15949,7 @@ async function detectPackageManagerLenient(root) {
15785
15949
  }
15786
15950
  function localDependencies(sourceDir) {
15787
15951
  return {
15788
- createWorkspace: () => mkdtemp2(join8(tmpdir2(), "naro-build-")),
15952
+ createWorkspace: () => mkdtemp2(join9(tmpdir2(), "naro-build-")),
15789
15953
  removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
15790
15954
  async runCommand(spec, observer) {
15791
15955
  if (spec.command === "git" && spec.args[0] === "clone") {
@@ -15813,15 +15977,26 @@ function localDependencies(sourceDir) {
15813
15977
  }
15814
15978
  async function deployDirectory(sourceDir, options, log2) {
15815
15979
  const cp2 = controlPlaneUrl();
15816
- const session = await requireSession();
15980
+ const session = await requireSessionOrLogin(log2);
15817
15981
  const name = slugify2(options.name ?? await projectName(sourceDir));
15818
15982
  const production = options.production ?? false;
15983
+ const git = await detectGitInfo(sourceDir);
15984
+ const branch = options.branch ?? git.branch;
15985
+ if (git.repositoryUrl) log2(`\u25B8 linking ${git.repositoryUrl}`);
15986
+ if (options.env && Object.keys(options.env).length > 0)
15987
+ log2(`\u25B8 syncing ${Object.keys(options.env).length} env var(s)`);
15819
15988
  log2("\u25B8 starting deployment");
15820
15989
  const ctx = await startCliDeployment(session.access_token, {
15821
15990
  name,
15822
15991
  production,
15823
- controlPlaneUrl: cp2
15992
+ controlPlaneUrl: cp2,
15993
+ repositoryUrl: git.repositoryUrl,
15994
+ branch,
15995
+ rootDirectory: options.rootDirectory,
15996
+ env: options.env
15824
15997
  });
15998
+ if (ctx.envSkipped?.length)
15999
+ log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
15825
16000
  const executor = new LocalBuildExecutor(localDependencies(sourceDir));
15826
16001
  const observer = eventsObserver(cp2, ctx.deploymentId, ctx.callbackToken);
15827
16002
  const result = await executor.build(
@@ -15847,15 +16022,12 @@ async function deployDirectory(sourceDir, options, log2) {
15847
16022
  }
15848
16023
 
15849
16024
  export {
15850
- saveSession,
15851
- loadStoredSession,
15852
- clearSession,
15853
- isExpired,
15854
- createAuthClient,
15855
- refreshStoredSession,
15856
- login,
16025
+ runLogin,
16026
+ runWhoami,
16027
+ runLogout,
15857
16028
  startCliDeployment,
15858
16029
  controlPlaneUrl,
16030
+ requireSessionOrLogin,
15859
16031
  eventsObserver,
15860
16032
  parseDotenv,
15861
16033
  readProjectEnv,
package/dist/deploy.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { BuildObserver } from "./artifact-types";
2
+ import { type StoredSession } from "./session";
2
3
  export interface CliDeploymentContext {
3
4
  deploymentId: string;
4
5
  projectId: string;
@@ -7,14 +8,20 @@ export interface CliDeploymentContext {
7
8
  rootDirectory: string | null;
8
9
  env: Record<string, string>;
9
10
  controlPlaneUrl: string;
11
+ envSkipped?: string[];
10
12
  }
11
13
  export declare function startCliDeployment(accessToken: string, opts: {
12
14
  name: string;
13
15
  production: boolean;
14
16
  controlPlaneUrl: string;
17
+ repositoryUrl?: string | null | undefined;
18
+ branch?: string | null | undefined;
19
+ rootDirectory?: string | null | undefined;
20
+ env?: Record<string, string> | undefined;
15
21
  fetch?: typeof fetch;
16
22
  }): Promise<CliDeploymentContext>;
17
23
  export declare function controlPlaneUrl(env?: NodeJS.ProcessEnv): string;
24
+ export declare function requireSessionOrLogin(log: (message: string) => void, home?: string, loginFlow?: () => Promise<StoredSession>): Promise<StoredSession>;
18
25
  export declare function eventsObserver(cp: string, deploymentId: string, token: string, fetchImpl?: typeof fetch): BuildObserver;
19
26
  export declare function parseDotenv(text: string): Record<string, string>;
20
27
  export declare function readProjectEnv(dir: string): Record<string, string>;
@@ -24,6 +31,7 @@ export interface DeployOptions {
24
31
  rootDirectory?: string | null;
25
32
  production?: boolean;
26
33
  env?: Record<string, string> | undefined;
34
+ branch?: string | undefined;
27
35
  }
28
36
  export interface DeployResult {
29
37
  workerName: string;
package/dist/deploy.js CHANGED
@@ -5,15 +5,17 @@ import {
5
5
  eventsObserver,
6
6
  parseDotenv,
7
7
  readProjectEnv,
8
+ requireSessionOrLogin,
8
9
  slugify,
9
10
  startCliDeployment
10
- } from "./chunk-63SYN66G.js";
11
+ } from "./chunk-UNCVFCTH.js";
11
12
  export {
12
13
  controlPlaneUrl,
13
14
  deployDirectory,
14
15
  eventsObserver,
15
16
  parseDotenv,
16
17
  readProjectEnv,
18
+ requireSessionOrLogin,
17
19
  slugify,
18
20
  startCliDeployment
19
21
  };
@@ -0,0 +1,7 @@
1
+ export interface GitInfo {
2
+ repositoryUrl: string | null;
3
+ branch: string | null;
4
+ }
5
+ export type GitExec = (args: string[], cwd: string) => Promise<string>;
6
+ /** Reads local git metadata for the deploy: origin remote URL + current branch. */
7
+ export declare function detectGitInfo(dir: string, exec?: GitExec): Promise<GitInfo>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface Flags {
2
2
  name?: string | undefined;
3
3
  root?: string | undefined;
4
+ branch?: string | undefined;
4
5
  production: boolean;
5
6
  }
6
7
  export declare function parseFlags(argv: string[]): Flags;
package/dist/index.js CHANGED
@@ -1,82 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- clearSession,
4
- createAuthClient,
5
3
  deployDirectory,
6
- isExpired,
7
- loadStoredSession,
8
- login,
9
4
  readProjectEnv,
10
- refreshStoredSession,
11
- saveSession
12
- } from "./chunk-63SYN66G.js";
13
-
14
- // src/login.ts
15
- import { spawn } from "child_process";
16
- import { homedir } from "os";
17
- var stdio = {
18
- out: (m) => process.stdout.write(`${m}
19
- `),
20
- err: (m) => process.stderr.write(`${m}
21
- `)
22
- };
23
- function openBrowser(url) {
24
- const platform = process.platform;
25
- const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
26
- const args = platform === "win32" ? ["/c", "start", "", url] : [url];
27
- spawn(command, args, { stdio: "ignore", detached: true }).unref();
28
- }
29
- async function runLogin(io = stdio) {
30
- io.out("Opening your browser to sign in with GitHub...");
31
- const session = await login({
32
- openBrowser: (url) => {
33
- io.out(`If your browser did not open, visit:
34
- ${url}`);
35
- openBrowser(url);
36
- }
37
- });
38
- saveSession(session);
39
- io.out(`\u2705 Logged in as ${session.user.email ?? session.user.id}`);
40
- }
41
- async function runWhoami(io = stdio, home = homedir(), refresh = refreshStoredSession) {
42
- let session = loadStoredSession(home);
43
- if (!session) {
44
- io.err("Not logged in. Run `naro login`.");
45
- process.exitCode = 1;
46
- return;
47
- }
48
- if (isExpired(session)) {
49
- try {
50
- session = await refresh(session.refresh_token);
51
- saveSession(session, home);
52
- } catch {
53
- io.err("Session expired. Run `naro login`.");
54
- process.exitCode = 1;
55
- return;
56
- }
57
- }
58
- const handle = session.user.github ? ` (${session.user.github})` : "";
59
- io.out(`Logged in as ${session.user.email ?? session.user.id}${handle}`);
60
- }
61
- async function defaultSignOut(session) {
62
- const client = createAuthClient();
63
- await client.auth.setSession({
64
- access_token: session.access_token,
65
- refresh_token: session.refresh_token
66
- });
67
- await client.auth.signOut();
68
- }
69
- async function runLogout(io = stdio, home = homedir(), signOut = defaultSignOut) {
70
- const session = loadStoredSession(home);
71
- if (session) {
72
- try {
73
- await signOut(session);
74
- } catch {
75
- }
76
- }
77
- clearSession(home);
78
- io.out("Logged out.");
79
- }
5
+ runLogin,
6
+ runLogout,
7
+ runWhoami
8
+ } from "./chunk-UNCVFCTH.js";
80
9
 
81
10
  // src/index.ts
82
11
  function parseFlags(argv) {
@@ -86,6 +15,7 @@ function parseFlags(argv) {
86
15
  if (arg === "--prod" || arg === "--production") flags.production = true;
87
16
  else if (arg === "--name") flags.name = argv[++i];
88
17
  else if (arg === "--root") flags.root = argv[++i];
18
+ else if (arg === "--branch") flags.branch = argv[++i];
89
19
  }
90
20
  return flags;
91
21
  }
@@ -95,15 +25,17 @@ Usage:
95
25
  naro login
96
26
  naro whoami
97
27
  naro logout
98
- naro deploy [--name <name>] [--root <dir>] [--prod]
28
+ naro deploy [--name <name>] [--root <dir>] [--branch <name>] [--prod]
99
29
 
100
30
  Options:
101
- --name Override the deployment name (defaults to package.json name)
102
- --root Sub-directory that contains the Next.js app (where package.json is)
103
- --prod Publish to production ({name}.naro.sh) instead of preview
31
+ --name Override the deployment name (defaults to package.json name)
32
+ --root Sub-directory that contains the Next.js app (where package.json is)
33
+ --branch Override the detected git branch
34
+ --prod Publish to production ({name}.naro.sh) instead of preview
104
35
 
105
36
  Credentials:
106
37
  Run \`naro login\` to authenticate via GitHub.
38
+ \`naro deploy\` signs you in automatically (opens your browser) when needed.
107
39
  Set NARO_CONTROL_PLANE_URL to use a custom control plane (defaults to https://naro.sh).
108
40
  `;
109
41
  async function main() {
@@ -134,6 +66,7 @@ ${HELP}`);
134
66
  {
135
67
  name: flags.name,
136
68
  rootDirectory: flags.root ?? null,
69
+ branch: flags.branch,
137
70
  production: flags.production,
138
71
  env
139
72
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naro-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naro": "./dist/index.js"