diff --git a/docs/development.md b/docs/development.md index 9eed78a00..cd96239ba 100644 --- a/docs/development.md +++ b/docs/development.md @@ -242,6 +242,14 @@ commands use the same non-login Bash behavior on macOS/Linux, but preserve their existing `cmd.exe /c` string semantics on Windows. Service scripts are separate: they launch in a terminal and receive the service environment described below. +Because the shell differs per platform, a lifecycle command that must run +everywhere cannot use POSIX-only syntax — `VAR=1 cmd` env prefixes, `$VAR` +expansion, `cp`/`rm`, or a `./scripts/*.sh` entrypoint all fail under PowerShell, +and `bash` is not guaranteed to exist on Windows. Put that logic in a Node script +that reads what it needs from `process.env` and invoke it as +`node ./scripts/.mjs`. This repo's own setup does exactly that in +`scripts/seed-worktree-dev-state.mjs` and `scripts/seed-ios-native-cache.mjs`. + ```json { "worktree": { diff --git a/paseo.json b/paseo.json index ab984058e..076d1f2c5 100644 --- a/paseo.json +++ b/paseo.json @@ -3,10 +3,9 @@ "setup": [ "npm ci", "node ./scripts/seed-ios-native-cache.mjs", - "PASEO_DEV_MANAGED_HOME=1 PASEO_DEV_SEED_HOME=\"$PASEO_SOURCE_CHECKOUT_PATH/.dev/paseo-home\" PASEO_HOME=\"$PWD/.dev/paseo-home\" ./scripts/dev-home.sh", + "node ./scripts/seed-worktree-dev-state.mjs", "npm run build:server", - "npm run build --workspace=@getpaseo/expo-two-way-audio", - "cp \"$PASEO_SOURCE_CHECKOUT_PATH/packages/server/.env\" \"$PWD/packages/server/.env\"" + "npm run build --workspace=@getpaseo/expo-two-way-audio" ] }, "scripts": { diff --git a/scripts/seed-worktree-dev-state.mjs b/scripts/seed-worktree-dev-state.mjs new file mode 100644 index 000000000..d64578959 --- /dev/null +++ b/scripts/seed-worktree-dev-state.mjs @@ -0,0 +1,95 @@ +#!/usr/bin/env node +import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs"; +import { dirname, join } from "node:path"; + +// Worktree setup runs through a stable script shell: bash on macOS/Linux, but PowerShell +// on Windows. POSIX-only command strings (`VAR=1 cmd` env prefixes, `$VAR` expansion, `cp`, +// `./scripts/*.sh`) cannot express this step portably, so the seeding lives in Node — the +// one interpreter every Paseo checkout already depends on. + +const sourceRoot = process.env.PASEO_SOURCE_CHECKOUT_PATH; +const targetRoot = process.env.PASEO_WORKTREE_PATH || process.cwd(); + +if (!sourceRoot || sourceRoot === targetRoot) { + process.exit(0); +} + +seedPaseoHome(); +copyServerEnv(); + +function seedPaseoHome() { + const source = process.env.PASEO_DEV_SEED_HOME || join(sourceRoot, ".dev/paseo-home"); + const target = join(targetRoot, ".dev/paseo-home"); + + if (!existsSync(source)) { + console.log(` Seed: skipped (${source} missing)`); + return; + } + + if (source === target) { + console.log(" Seed: skipped (source is target)"); + return; + } + + if (process.env.PASEO_DEV_RESET_HOME === "1") { + rmSync(target, { recursive: true, force: true }); + } else if (hasEntries(target)) { + console.log(` Seed: skipped (${target} already has data)`); + return; + } + + mkdirSync(target, { recursive: true }); + console.log(` Seed: copying metadata from ${source}`); + copyJsonTree(join(source, "agents"), join(target, "agents")); + copyJsonTree(join(source, "projects"), join(target, "projects")); + + const config = join(source, "config.json"); + if (existsSync(config)) { + cpSync(config, join(target, "config.json")); + } + + console.log(` Seed: copied metadata from ${source}`); +} + +// Durable JSON metadata only. Runtime files (pid files, sockets, logs) must not be copied. +function copyJsonTree(source, target) { + if (!existsSync(source)) { + return; + } + + cpSync(source, target, { + recursive: true, + filter: (path) => isDirectory(path) || path.endsWith(".json"), + }); +} + +function copyServerEnv() { + const source = join(sourceRoot, "packages/server/.env"); + const target = join(targetRoot, "packages/server/.env"); + + // Untracked local config. A checkout without one is normal, so this is not a setup failure. + if (!existsSync(source)) { + console.log(` Env: skipped (${source} missing)`); + return; + } + + mkdirSync(dirname(target), { recursive: true }); + cpSync(source, target); + console.log(` Env: copied ${source}`); +} + +function hasEntries(path) { + try { + return readdirSync(path).length > 0; + } catch { + return false; + } +} + +function isDirectory(path) { + try { + return statSync(path).isDirectory(); + } catch { + return false; + } +}