@arcote.tech/arc-cli 0.6.0 → 0.6.1

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/index.js CHANGED
@@ -27452,7 +27452,7 @@ function resolveWorkspace() {
27452
27452
  log2("Scanning workspaces...");
27453
27453
  const packages = discoverPackages(rootDir);
27454
27454
  ok(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
27455
- if (packages.length === 0) {
27455
+ if (packages.length === 0 && process.env.ARC_DEPLOY_API !== "1") {
27456
27456
  err("No workspace packages found.");
27457
27457
  process.exit(1);
27458
27458
  }
@@ -27821,7 +27821,7 @@ async function platformBuild(opts = {}) {
27821
27821
 
27822
27822
  // src/commands/platform-deploy.ts
27823
27823
  import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
27824
- import { join as join18 } from "path";
27824
+ import { dirname as dirname7, join as join18 } from "path";
27825
27825
 
27826
27826
  // src/deploy/bootstrap.ts
27827
27827
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
@@ -28510,17 +28510,19 @@ async function streamToString(stream2) {
28510
28510
  return new Response(stream2).text();
28511
28511
  }
28512
28512
  function baseSshArgs(target) {
28513
- const args = [
28513
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
28514
+ return [
28514
28515
  "-o",
28515
28516
  "BatchMode=yes",
28516
28517
  "-o",
28517
28518
  "StrictHostKeyChecking=accept-new",
28519
+ "-o",
28520
+ "IdentitiesOnly=yes",
28521
+ "-i",
28522
+ key,
28518
28523
  "-p",
28519
28524
  String(target.port)
28520
28525
  ];
28521
- if (target.sshKey)
28522
- args.push("-i", target.sshKey);
28523
- return args;
28524
28526
  }
28525
28527
  async function sshExec(target, cmd, opts = {}) {
28526
28528
  const args = [
@@ -28574,16 +28576,19 @@ async function waitForSsh(target, opts = {}) {
28574
28576
  throw new Error(`Timed out waiting for SSH on ${target.user}@${target.host}`);
28575
28577
  }
28576
28578
  async function scpUpload(target, localPath, remotePath) {
28579
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
28577
28580
  const args = [
28578
28581
  "-o",
28579
28582
  "BatchMode=yes",
28580
28583
  "-o",
28581
28584
  "StrictHostKeyChecking=accept-new",
28585
+ "-o",
28586
+ "IdentitiesOnly=yes",
28587
+ "-i",
28588
+ key,
28582
28589
  "-P",
28583
28590
  String(target.port)
28584
28591
  ];
28585
- if (target.sshKey)
28586
- args.push("-i", target.sshKey);
28587
28592
  args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
28588
28593
  const proc2 = spawn3({ cmd: ["scp", ...args], stderr: "pipe" });
28589
28594
  const [stderr, exitCode] = await Promise.all([
@@ -29666,13 +29671,23 @@ async function platformDeploy(envArg, options = {}) {
29666
29671
  }
29667
29672
  }
29668
29673
  function readCliVersion() {
29674
+ const candidates = [];
29675
+ const entry = process.argv[1];
29676
+ if (entry) {
29677
+ candidates.push(join18(dirname7(entry), "..", "package.json"));
29678
+ }
29669
29679
  try {
29670
- const pkgPath = join18(import.meta.dir, "..", "..", "package.json");
29671
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
29672
- return pkg.version ?? "unknown";
29673
- } catch {
29674
- return "unknown";
29680
+ candidates.push(join18(import.meta.dir, "..", "..", "package.json"));
29681
+ } catch {}
29682
+ for (const path4 of candidates) {
29683
+ try {
29684
+ const pkg = JSON.parse(readFileSync14(path4, "utf-8"));
29685
+ if (pkg.name === "@arcote.tech/arc-cli" && pkg.version) {
29686
+ return pkg.version;
29687
+ }
29688
+ } catch {}
29675
29689
  }
29690
+ return "unknown";
29676
29691
  }
29677
29692
  async function hashDeployConfig(rootDir) {
29678
29693
  const p2 = join18(rootDir, "deploy.arc.json");
@@ -31147,7 +31162,7 @@ import {
31147
31162
  rmSync as rmSync4,
31148
31163
  writeFileSync as writeFileSync15
31149
31164
  } from "fs";
31150
- import { dirname as dirname7, join as join19, normalize as normalize2, resolve } from "path";
31165
+ import { dirname as dirname8, join as join19, normalize as normalize2, resolve } from "path";
31151
31166
  function createDeployApiHandler(opts) {
31152
31167
  return async (req, url, ctx) => {
31153
31168
  const p3 = url.pathname;
@@ -31290,7 +31305,7 @@ function createDeployApiHandler(opts) {
31290
31305
  if (existsSync15(dst)) {
31291
31306
  rmSync4(dst, { recursive: true, force: true });
31292
31307
  }
31293
- mkdirSync14(dirname7(dst), { recursive: true });
31308
+ mkdirSync14(dirname8(dst), { recursive: true });
31294
31309
  cpSync(src2, dst, { recursive: true });
31295
31310
  }
31296
31311
  }
@@ -31328,7 +31343,7 @@ async function writeField(targetDir, name, file) {
31328
31343
  if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
31329
31344
  throw new Error(`Path traversal rejected: ${name}`);
31330
31345
  }
31331
- mkdirSync14(dirname7(full), { recursive: true });
31346
+ mkdirSync14(dirname8(full), { recursive: true });
31332
31347
  writeFileSync15(full, Buffer.from(await file.arrayBuffer()));
31333
31348
  }
31334
31349
  function sanitizeName2(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "CLI tool for Arc framework",
5
5
  "module": "index.ts",
6
6
  "main": "dist/index.js",
@@ -12,12 +12,12 @@
12
12
  "build": "bun build --target=bun ./src/index.ts --outdir=dist --external @arcote.tech/arc --external @arcote.tech/arc-ds --external @arcote.tech/arc-react --external @arcote.tech/platform && chmod +x dist/index.js"
13
13
  },
14
14
  "dependencies": {
15
- "@arcote.tech/arc": "^0.6.0",
16
- "@arcote.tech/arc-ds": "^0.6.0",
17
- "@arcote.tech/arc-react": "^0.6.0",
18
- "@arcote.tech/arc-host": "^0.6.0",
19
- "@arcote.tech/arc-adapter-db-sqlite": "^0.6.0",
20
- "@arcote.tech/platform": "^0.6.0",
15
+ "@arcote.tech/arc": "^0.6.1",
16
+ "@arcote.tech/arc-ds": "^0.6.1",
17
+ "@arcote.tech/arc-react": "^0.6.1",
18
+ "@arcote.tech/arc-host": "^0.6.1",
19
+ "@arcote.tech/arc-adapter-db-sqlite": "^0.6.1",
20
+ "@arcote.tech/platform": "^0.6.1",
21
21
  "@clack/prompts": "^0.9.0",
22
22
  "commander": "^11.1.0",
23
23
  "chokidar": "^3.5.3",
@@ -23,5 +23,36 @@ if [ ! -f "$CLI_BIN" ]; then
23
23
  bun add "@arcote.tech/arc-cli@${ARC_CLI_VERSION}"
24
24
  fi
25
25
 
26
+ # resolveWorkspace() in arc platform start exits hard if /app has no
27
+ # package.json. In runtime mode the workspace lives in /app/.arc/platform/
28
+ # but the CLI still walks up from cwd. Drop a stub manifest here so the
29
+ # walk-up resolves to /app/.arc/platform (or to a stable "no workspace"
30
+ # state in pre-deploy).
31
+ if [ ! -f /app/package.json ]; then
32
+ cat > /app/package.json <<'EOF'
33
+ {
34
+ "name": "arc-runtime",
35
+ "private": true,
36
+ "type": "module",
37
+ "workspaces": []
38
+ }
39
+ EOF
40
+ fi
41
+
42
+ # Make /app/.arc/platform the working directory — that's where deployed user
43
+ # code, deps and node_modules live (volume mount).
44
+ mkdir -p /app/.arc/platform
45
+ cd /app/.arc/platform
46
+ if [ ! -f package.json ]; then
47
+ cat > package.json <<'EOF'
48
+ {
49
+ "name": "arc-platform-runtime",
50
+ "private": true,
51
+ "type": "module",
52
+ "workspaces": []
53
+ }
54
+ EOF
55
+ fi
56
+
26
57
  echo "[entrypoint] starting arc platform (cli=${ARC_CLI_VERSION})"
27
58
  exec bun run "$CLI_BIN" platform start
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
2
+ import { dirname, join } from "path";
3
3
  import { bootstrap } from "../deploy/bootstrap";
4
4
  import {
5
5
  deployConfigExists,
@@ -125,13 +125,29 @@ export async function platformDeploy(
125
125
  // ---------------------------------------------------------------------------
126
126
 
127
127
  function readCliVersion(): string {
128
+ // import.meta.dir gets mangled by `bun build` — derive from process.argv[1]
129
+ // (the bundled dist/index.js path) which is stable across run modes.
130
+ const candidates: string[] = [];
131
+ const entry = process.argv[1];
132
+ if (entry) {
133
+ candidates.push(join(dirname(entry), "..", "package.json"));
134
+ }
128
135
  try {
129
- const pkgPath = join(import.meta.dir, "..", "..", "package.json");
130
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
131
- return pkg.version ?? "unknown";
136
+ candidates.push(join(import.meta.dir, "..", "..", "package.json"));
132
137
  } catch {
133
- return "unknown";
138
+ // import.meta.dir unavailable
139
+ }
140
+ for (const path of candidates) {
141
+ try {
142
+ const pkg = JSON.parse(readFileSync(path, "utf-8"));
143
+ if (pkg.name === "@arcote.tech/arc-cli" && pkg.version) {
144
+ return pkg.version as string;
145
+ }
146
+ } catch {
147
+ // Try next
148
+ }
134
149
  }
150
+ return "unknown";
135
151
  }
136
152
 
137
153
  async function hashDeployConfig(rootDir: string): Promise<string> {
package/src/deploy/ssh.ts CHANGED
@@ -21,16 +21,22 @@ export interface SshExecResult {
21
21
  }
22
22
 
23
23
  function baseSshArgs(target: DeployTarget): string[] {
24
- const args = [
24
+ // IdentitiesOnly=yes pins auth to the explicit key. Without it ssh-agent
25
+ // offers every loaded identity and trips MaxAuthTries on hardened sshd
26
+ // (ansible's sshd hardening + fail2ban lowers the threshold).
27
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
28
+ return [
25
29
  "-o",
26
30
  "BatchMode=yes",
27
31
  "-o",
28
32
  "StrictHostKeyChecking=accept-new",
33
+ "-o",
34
+ "IdentitiesOnly=yes",
35
+ "-i",
36
+ key,
29
37
  "-p",
30
38
  String(target.port),
31
39
  ];
32
- if (target.sshKey) args.push("-i", target.sshKey);
33
- return args;
34
40
  }
35
41
 
36
42
  /**
@@ -115,15 +121,19 @@ export async function scpUpload(
115
121
  localPath: string,
116
122
  remotePath: string,
117
123
  ): Promise<void> {
124
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
118
125
  const args = [
119
126
  "-o",
120
127
  "BatchMode=yes",
121
128
  "-o",
122
129
  "StrictHostKeyChecking=accept-new",
130
+ "-o",
131
+ "IdentitiesOnly=yes",
132
+ "-i",
133
+ key,
123
134
  "-P",
124
135
  String(target.port),
125
136
  ];
126
- if (target.sshKey) args.push("-i", target.sshKey);
127
137
  args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
128
138
 
129
139
  const proc = spawn({ cmd: ["scp", ...args], stderr: "pipe" });
@@ -97,7 +97,10 @@ export function resolveWorkspace(): WorkspaceInfo {
97
97
  `Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`,
98
98
  );
99
99
 
100
- if (packages.length === 0) {
100
+ // Empty package list is allowed in pre-deploy runtime mode (container with
101
+ // freshly-mounted volume, no user code pushed yet). The CLI entry that
102
+ // forbids this (arc platform build/dev) checks explicitly.
103
+ if (packages.length === 0 && process.env.ARC_DEPLOY_API !== "1") {
101
104
  err("No workspace packages found.");
102
105
  process.exit(1);
103
106
  }