@codeyam-editor/codeyam-editor 0.1.0-staging.36d3518 → 0.1.0-staging.3db0f53

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/npm/container.js CHANGED
@@ -8,7 +8,7 @@ const path = require("path");
8
8
  * inline monitoring UI at http://localhost:14199.
9
9
  */
10
10
 
11
- const { ensureServer, rootDir } = require("./utils");
11
+ const { ensureServer, rootDir, killChildProcess } = require("./utils");
12
12
  const { open } = require("./open");
13
13
 
14
14
  const PORT = 14199;
@@ -23,9 +23,13 @@ async function main() {
23
23
 
24
24
  process.on("SIGINT", () => {
25
25
  clearInterval(keepAlive);
26
- if (serverChild) serverChild.kill("SIGINT");
26
+ killChildProcess(serverChild);
27
27
  process.exit(0);
28
28
  });
29
29
  }
30
30
 
31
- main();
31
+ module.exports = { main };
32
+
33
+ if (require.main === module) {
34
+ main();
35
+ }
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Development CLI for running the codeyam-editor against any project.
5
+ *
6
+ * Two modes:
7
+ *
8
+ * Launcher — no subcommand (or --restart): builds the Rust binary and UI
9
+ * from the editor repo, starts the server, waits for readiness, opens browser.
10
+ * codeyam-editor-dev
11
+ * codeyam-editor-dev --restart
12
+ *
13
+ * Passthrough — any subcommand (start, init, editor, ...): rebuilds the
14
+ * Rust binary, then forwards args to it.
15
+ * codeyam-editor-dev editor step 1
16
+ * codeyam-editor-dev editor advance
17
+ */
18
+
19
+ const { execSync, spawn } = require("child_process");
20
+ const path = require("path");
21
+ const fs = require("fs");
22
+ const { ensureServer, restartServer, waitForPort, ensureBinary, uiDistDir, rootDir, killChildProcess } = require("./utils");
23
+ const { open } = require("./open");
24
+
25
+ // Tell the Rust binary to emit "codeyam-editor-dev" in all instructions,
26
+ // permissions, and installed files instead of "codeyam-editor".
27
+ process.env.CODEYAM_CLI = "codeyam-editor-dev";
28
+
29
+ const args = process.argv.slice(2);
30
+ const KNOWN_SUBCOMMANDS = ["start", "init", "editor"];
31
+
32
+ /**
33
+ * Check if the first non-flag argument is a known subcommand.
34
+ * Accepts an explicit argv array so the helper is testable without mutating
35
+ * process.argv (the module-level `args` const is the production default).
36
+ */
37
+ function hasSubcommand(argv = args) {
38
+ for (const arg of argv) {
39
+ if (arg.startsWith("-")) continue;
40
+ return KNOWN_SUBCOMMANDS.includes(arg);
41
+ }
42
+ return false;
43
+ }
44
+
45
+ /** Build the Rust binary from the editor repo. */
46
+ function buildBinary() {
47
+ console.error("Building Rust binary...");
48
+ try {
49
+ execSync("cargo build", { stdio: "inherit", cwd: rootDir });
50
+ } catch {
51
+ console.error("Failed to build Rust binary. Continuing with existing build (if any)...");
52
+ }
53
+ }
54
+
55
+ module.exports = { hasSubcommand, buildBinary };
56
+
57
+ if (require.main === module) {
58
+ // --- Passthrough mode: build binary, then forward to it ---
59
+ if (hasSubcommand()) {
60
+ buildBinary();
61
+ const binary = ensureBinary();
62
+ const child = spawn(binary, args, {
63
+ stdio: "inherit",
64
+ cwd: process.cwd(),
65
+ env: { ...process.env, CODEYAM_EDITOR_UI_DIR: uiDistDir() },
66
+ });
67
+ child.on("exit", (code) => {
68
+ process.exit(code ?? 1);
69
+ });
70
+ } else {
71
+ // --- Launcher mode: build everything, start server, open browser ---
72
+ const SERVER_PORT = 14199;
73
+ const shouldRestart = args.includes("--restart");
74
+
75
+ const readProjectPort = () => {
76
+ try {
77
+ const configPath = path.join(process.cwd(), ".codeyam", "editor.json");
78
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
79
+ return config.port || 3000;
80
+ } catch {
81
+ return 3000;
82
+ }
83
+ };
84
+
85
+ const launcherMain = async () => {
86
+ const projectDir = process.cwd();
87
+ console.error(`Editor repo: ${rootDir}`);
88
+ console.error(`Target project: ${projectDir}`);
89
+
90
+ buildBinary();
91
+
92
+ // Build the UI from the editor repo
93
+ console.error("Building UI...");
94
+ try {
95
+ execSync("npx vite build", { stdio: "inherit", cwd: path.join(rootDir, "ui") });
96
+ } catch {
97
+ console.error("Failed to build UI. Continuing with existing build (if any)...");
98
+ }
99
+
100
+ const serverChild = shouldRestart
101
+ ? await restartServer(SERVER_PORT)
102
+ : await ensureServer(SERVER_PORT);
103
+
104
+ const appPort = readProjectPort();
105
+ const viteInternalPort = appPort + 1;
106
+ console.error(`Waiting for dev server on port ${viteInternalPort}...`);
107
+ const viteReady = await waitForPort(viteInternalPort, 15000, 300);
108
+ if (!viteReady) {
109
+ console.error(`Warning: Dev server not ready on port ${viteInternalPort}. Live Preview may not work initially.`);
110
+ }
111
+
112
+ const url = `http://localhost:${SERVER_PORT}`;
113
+ console.error(`Editor UI: ${url}`);
114
+ console.error(`Live Preview: http://localhost:${appPort}`);
115
+ console.error(`Project: ${projectDir}`);
116
+ open(url);
117
+ const keepAlive = setInterval(() => {}, 60_000);
118
+
119
+ process.on("SIGINT", () => {
120
+ clearInterval(keepAlive);
121
+ killChildProcess(serverChild);
122
+ process.exit(0);
123
+ });
124
+ };
125
+
126
+ launcherMain();
127
+ }
128
+ }
package/npm/editor.js CHANGED
@@ -13,18 +13,125 @@
13
13
  */
14
14
 
15
15
  const { execSync } = require("child_process");
16
+ const fs = require("fs");
17
+ const os = require("os");
16
18
  const path = require("path");
17
- const { ensureServer, restartServer, waitForPort, rootDir } = require("./utils");
19
+ const { ensureServer, stopServer, isPortInUse, waitForPort, rootDir, killChildProcess } = require("./utils");
18
20
  const { open } = require("./open");
19
21
 
20
- const SERVER_PORT = 14199;
21
- const VITE_PORT = 5173;
22
+ const SERVER_PORT = parseInt(process.env.CODEYAM_EDITOR_PORT || "14199", 10);
23
+ const VITE_PORT = parseInt(process.env.PORT || "5173", 10);
22
24
  // The reverse proxy sits on VITE_PORT; the actual Vite dev server runs on
23
25
  // VITE_PORT + 1. We need to wait for the real Vite server, not the proxy.
24
26
  const VITE_INTERNAL_PORT = VITE_PORT + 1;
25
27
  const shouldRestart = process.argv.includes("--restart");
26
28
 
29
+ function buildBinary() {
30
+ console.error("Building Rust binary...");
31
+ try {
32
+ execSync("cargo build", { stdio: "inherit", cwd: rootDir });
33
+ return true;
34
+ } catch {
35
+ console.error("Failed to build Rust binary. Continuing with existing build (if any)...");
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // Resolve <CARGO_HOME>/bin (falling back to $HOME/.cargo/bin). Mirrors
41
+ // `installed_binary_path()` in crates/control-api/src/pty_broker_startup.rs
42
+ // — the Rust broker auto-spawn execs the binary at this path, so we MUST
43
+ // keep it in sync with the freshly-built debug binary or `npm run editor`
44
+ // will start a server whose PTY-broker auto-spawn execs a stale install.
45
+ function cargoBinDir() {
46
+ if (process.env.CARGO_HOME) return path.join(process.env.CARGO_HOME, "bin");
47
+ return path.join(os.homedir(), ".cargo", "bin");
48
+ }
49
+
50
+ // Install target/debug/codeyam-editor to <cargo_bin>/codeyam-editor
51
+ // atomically (write to tmp in same dir, then rename), and refresh the
52
+ // codeyam-editor-pty-broker hardlink so it points at the new inode.
53
+ //
54
+ // The PTY broker auto-spawn (control-api/src/pty_broker_startup.rs) always
55
+ // resolves the *installed* path, NOT current_exe() — that was a deliberate
56
+ // fix to prevent a fork-bomb where test binaries spawned themselves. So a
57
+ // fresh `target/debug/codeyam-editor` is not enough; the install path must
58
+ // be refreshed too. Without this, build tabs hang on "Reconnecting..."
59
+ // because the broker daemon errors out (`unrecognized subcommand`) before
60
+ // the UDS socket opens.
61
+ function installBinary() {
62
+ const srcPath = path.join(rootDir, "target", "debug", "codeyam-editor");
63
+ if (!fs.existsSync(srcPath)) {
64
+ console.error(`Skipping install: ${srcPath} not found.`);
65
+ return;
66
+ }
67
+ const binDir = cargoBinDir();
68
+ const dstPath = path.join(binDir, "codeyam-editor");
69
+ const linkPath = path.join(binDir, "codeyam-editor-pty-broker");
70
+ const tmpPath = path.join(binDir, `.codeyam-editor.tmp.${process.pid}`);
71
+ console.error(`Installing fresh binary to ${dstPath}...`);
72
+ try {
73
+ fs.mkdirSync(binDir, { recursive: true });
74
+ fs.copyFileSync(srcPath, tmpPath);
75
+ fs.chmodSync(tmpPath, 0o755);
76
+ fs.renameSync(tmpPath, dstPath);
77
+ try {
78
+ fs.unlinkSync(linkPath);
79
+ } catch (e) {
80
+ if (e.code !== "ENOENT") throw e;
81
+ }
82
+ fs.linkSync(dstPath, linkPath);
83
+ } catch (err) {
84
+ console.error(`Failed to install binary: ${err.message}`);
85
+ try { fs.unlinkSync(tmpPath); } catch {}
86
+ }
87
+ }
88
+
89
+ // Evict Rust build artifacts not touched in the last day so target/ doesn't
90
+ // grow unboundedly. Must run after the old server is stopped (it holds files
91
+ // in target/debug/ open) and before the new server starts. Non-fatal.
92
+ function sweepStaleArtifacts() {
93
+ console.error("Sweeping stale Rust build artifacts (idle >1 day)...");
94
+ try {
95
+ execSync("cargo sweep --time 1", { stdio: "inherit", cwd: rootDir });
96
+ } catch {
97
+ console.error("cargo-sweep failed (install with `cargo install cargo-sweep`). Continuing.");
98
+ }
99
+ }
100
+
27
101
  async function main() {
102
+ // Sync ports to editor.json so the Rust backend uses them
103
+ const editorJsonPath = path.join(rootDir, ".codeyam", "editor.json");
104
+ if (fs.existsSync(editorJsonPath)) {
105
+ const editorJson = JSON.parse(fs.readFileSync(editorJsonPath, "utf8"));
106
+ editorJson.port = VITE_PORT;
107
+ editorJson.startCommand = `cd ui && npx vite --port ${VITE_PORT}`;
108
+
109
+ const uiApp = editorJson.apps?.find(a => a.dir === "ui");
110
+ if (uiApp) {
111
+ uiApp.port = VITE_PORT;
112
+ uiApp.startCommand = `npx vite --port ${VITE_PORT}`;
113
+ }
114
+
115
+ if (editorJson.proxy) {
116
+ editorJson.proxy.controlPort = SERVER_PORT;
117
+ editorJson.proxy.httpPort = SERVER_PORT - 99;
118
+ editorJson.proxy.dbPort = SERVER_PORT - 98;
119
+ }
120
+ fs.writeFileSync(editorJsonPath, JSON.stringify(editorJson, null, 2));
121
+ }
122
+
123
+ // Rebuild the Rust binary so code changes are picked up on restart.
124
+ // This script is only invoked from a source checkout (via `npm run editor`),
125
+ // so Cargo.toml and the crates tree are always present.
126
+ const buildOk = buildBinary();
127
+ // Refresh <cargo_bin>/codeyam-editor (and the pty-broker hardlink) so
128
+ // the broker auto-spawn execs a binary that knows the `pty-broker
129
+ // daemon` subcommand. Skip if the build failed — installing a stale
130
+ // debug binary over a working install is worse than leaving it.
131
+ if (buildOk) {
132
+ installBinary();
133
+ }
134
+
28
135
  // Build the UI so the backend can serve it as static files
29
136
  console.error("Building UI...");
30
137
  try {
@@ -37,10 +144,17 @@ async function main() {
37
144
  }
38
145
 
39
146
  // Start the Rust backend — it serves ui/dist/ and launches the Vite dev
40
- // server (configured via startCommand in editor.json) for Live Preview HMR
41
- const serverChild = shouldRestart
42
- ? await restartServer(SERVER_PORT)
43
- : await ensureServer(SERVER_PORT);
147
+ // server (configured via startCommand in editor.json) for Live Preview HMR.
148
+ // On --restart: stop the existing server first, sweep stale artifacts while
149
+ // target/ is idle (no process holding files open), then start fresh.
150
+ if (shouldRestart && (await isPortInUse(SERVER_PORT))) {
151
+ console.error(`Stopping existing server on port ${SERVER_PORT}...`);
152
+ await stopServer(SERVER_PORT);
153
+ }
154
+ if (shouldRestart) {
155
+ sweepStaleArtifacts();
156
+ }
157
+ const serverChild = await ensureServer(SERVER_PORT);
44
158
 
45
159
  // Wait for the actual Vite dev server on the internal port (not the reverse
46
160
  // proxy on VITE_PORT, which starts immediately with the Rust binary).
@@ -59,9 +173,19 @@ async function main() {
59
173
 
60
174
  process.on("SIGINT", () => {
61
175
  clearInterval(keepAlive);
62
- if (serverChild) serverChild.kill("SIGINT");
176
+ killChildProcess(serverChild);
63
177
  process.exit(0);
64
178
  });
65
179
  }
66
180
 
67
- main();
181
+ module.exports = {
182
+ buildBinary,
183
+ cargoBinDir,
184
+ installBinary,
185
+ main,
186
+ sweepStaleArtifacts,
187
+ };
188
+
189
+ if (require.main === module) {
190
+ main();
191
+ }
@@ -5,11 +5,17 @@
5
5
  *
6
6
  * Downloads the pre-built platform binary and UI assets from GitHub Releases.
7
7
  * Skipped when running in local development (cargo builds from source instead).
8
+ *
9
+ * Cross-platform: uses Node-native `fetch` + the `tar` npm package to download
10
+ * and extract. Avoids depending on `curl` / `tar` system binaries — those exist
11
+ * on macOS and Linux but not on a default Windows install, and the previous
12
+ * `curl | tar xz` shell pipe was the original Windows-install blocker.
8
13
  */
9
14
 
10
15
  const fs = require("fs");
11
16
  const path = require("path");
12
- const { execSync } = require("child_process");
17
+ const { Readable } = require("stream");
18
+ const tar = require("tar");
13
19
 
14
20
  const rootDir = path.resolve(__dirname, "..");
15
21
 
@@ -17,8 +23,15 @@ const PLATFORM_MAP = {
17
23
  "darwin-arm64": "codeyam-editor-darwin-arm64",
18
24
  "darwin-x64": "codeyam-editor-darwin-x64",
19
25
  "linux-x64": "codeyam-editor-linux-x64",
26
+ "win32-x64": "codeyam-editor-win32-x64",
20
27
  };
21
28
 
29
+ function binaryName() {
30
+ return process.platform === "win32"
31
+ ? "codeyam-editor.exe"
32
+ : "codeyam-editor";
33
+ }
34
+
22
35
  function getArtifactName() {
23
36
  const key = `${process.platform}-${process.arch}`;
24
37
  const name = PLATFORM_MAP[key];
@@ -26,7 +39,10 @@ function getArtifactName() {
26
39
  console.error(
27
40
  `codeyam-editor: unsupported platform ${process.platform} ${process.arch}`
28
41
  );
29
- console.error("Supported: macOS (arm64, x64), Linux (x64)");
42
+ console.error("Supported: macOS (arm64, x64), Linux (x64), Windows (x64)");
43
+ console.error(
44
+ "You can build from source instead: cargo build --release"
45
+ );
30
46
  process.exit(0); // Don't fail the install — user may be building from source
31
47
  }
32
48
  return name;
@@ -47,9 +63,22 @@ function getReleaseTag() {
47
63
  return "latest";
48
64
  }
49
65
 
50
- function download() {
66
+ async function downloadAndExtract(url, dest) {
67
+ const res = await fetch(url);
68
+ if (!res.ok) {
69
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
70
+ }
71
+ await new Promise((resolve, reject) => {
72
+ Readable.fromWeb(res.body)
73
+ .pipe(tar.x({ cwd: dest }))
74
+ .on("finish", resolve)
75
+ .on("error", reject);
76
+ });
77
+ }
78
+
79
+ async function download() {
51
80
  // Skip if a binary already exists (e.g. local dev with cargo build)
52
- const binPath = path.join(rootDir, "bin", "codeyam-editor");
81
+ const binPath = path.join(rootDir, "bin", binaryName());
53
82
  if (fs.existsSync(binPath)) {
54
83
  return;
55
84
  }
@@ -71,13 +100,9 @@ function download() {
71
100
  fs.mkdirSync(uiDir, { recursive: true });
72
101
 
73
102
  try {
74
- // Download and extract in one step using curl + tar
75
- execSync(
76
- `curl -fsSL "${url}" | tar xz -C "${rootDir}"`,
77
- { stdio: ["ignore", "inherit", "inherit"], timeout: 120000 }
78
- );
103
+ await downloadAndExtract(url, rootDir);
79
104
 
80
- // Ensure binary is executable
105
+ // Ensure binary is executable. No-op on Windows but harmless.
81
106
  if (fs.existsSync(binPath)) {
82
107
  fs.chmodSync(binPath, 0o755);
83
108
  }
@@ -85,6 +110,7 @@ function download() {
85
110
  console.error("codeyam-editor installed successfully!");
86
111
  } catch (err) {
87
112
  console.error(`Failed to download codeyam-editor binary from ${url}`);
113
+ console.error(` Reason: ${err.message}`);
88
114
  console.error(
89
115
  "You can build from source instead: cargo build --release"
90
116
  );
@@ -92,4 +118,15 @@ function download() {
92
118
  }
93
119
  }
94
120
 
95
- download();
121
+ module.exports = {
122
+ PLATFORM_MAP,
123
+ binaryName,
124
+ getArtifactName,
125
+ getReleaseTag,
126
+ downloadAndExtract,
127
+ download,
128
+ };
129
+
130
+ if (require.main === module) {
131
+ download();
132
+ }