create-pmos 0.6.2 → 0.6.3
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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/workspace/bin/pmos.js +14 -25
- package/workspace/doctor.js +42 -38
- package/workspace/server.js +45 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
package/workspace/bin/pmos.js
CHANGED
|
@@ -74,20 +74,23 @@ function openBrowser(url) {
|
|
|
74
74
|
try { spawn(cmd[0], cmd[1], { detached: true, stdio: 'ignore' }).unref(); } catch {}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// node-pty compiles pty.node (require loads it) and, on macOS only, a separate spawn-helper
|
|
78
|
-
// binary (exec'd at spawn — Linux uses forkpty directly and never builds it). A partial macOS
|
|
79
|
-
// build with pty.node but no spawn-helper loads fine yet dies with 'posix_spawnp failed' on every
|
|
80
|
-
// session — the #1 tenant failure, common on a brand-new/non-LTS Node. So "installed" is not
|
|
81
|
-
// "works": verify the artifacts the running platform actually needs, not just the package folder.
|
|
82
|
-
function nodePtyBuildOk() {
|
|
83
|
-
const rel = path.join(WS, 'node_modules', 'node-pty', 'build', 'Release');
|
|
84
|
-
if (!fs.existsSync(path.join(rel, 'pty.node'))) return false;
|
|
85
|
-
return process.platform !== 'darwin' || fs.existsSync(path.join(rel, 'spawn-helper'));
|
|
86
|
-
}
|
|
87
77
|
function npm(args) {
|
|
88
78
|
return spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', args,
|
|
89
79
|
{ cwd: WS, stdio: 'inherit', shell: process.platform === 'win32' }).status === 0;
|
|
90
80
|
}
|
|
81
|
+
// node-pty exec()s a prebuilt macOS `spawn-helper` to open each PTY; the prebuild-extraction path
|
|
82
|
+
// can drop its executable bit, and then EVERY session dies with 'posix_spawnp failed' — the #1
|
|
83
|
+
// field failure. Restore +x wherever node-pty keeps the helper (prebuilds/<plat>-<arch>/ or a
|
|
84
|
+
// from-source build/Release/) before the server starts. Idempotent; no-op off macOS.
|
|
85
|
+
function fixSpawnHelperPerms() {
|
|
86
|
+
if (process.platform === 'win32') return;
|
|
87
|
+
const root = path.join(WS, 'node_modules', 'node-pty');
|
|
88
|
+
const targets = [path.join(root, 'build', 'Release', 'spawn-helper')];
|
|
89
|
+
try { const pb = path.join(root, 'prebuilds'); for (const d of fs.readdirSync(pb)) targets.push(path.join(pb, d, 'spawn-helper')); } catch {}
|
|
90
|
+
for (const f of targets) {
|
|
91
|
+
try { const st = fs.statSync(f); if (!(st.mode & 0o111)) fs.chmodSync(f, st.mode | 0o755); } catch {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
91
94
|
function ensureInstalled() {
|
|
92
95
|
if (!fs.existsSync(path.join(WS, 'node_modules', 'node-pty'))) {
|
|
93
96
|
say('First run — installing the workspace (one-time, ~20s)…');
|
|
@@ -96,21 +99,7 @@ function ensureInstalled() {
|
|
|
96
99
|
return false;
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
|
-
//
|
|
100
|
-
// compiles from source) BEFORE the server tries to spawn a session and dies with posix_spawnp.
|
|
101
|
-
if (!nodePtyBuildOk()) {
|
|
102
|
-
say('node-pty is present but its native build is incomplete — rebuilding from source…');
|
|
103
|
-
npm(['rebuild', 'node-pty']);
|
|
104
|
-
}
|
|
105
|
-
if (!nodePtyBuildOk()) {
|
|
106
|
-
const toolchain = process.platform === 'darwin' ? 'xcode-select --install'
|
|
107
|
-
: process.platform === 'win32' ? 'Visual Studio Build Tools (C++)'
|
|
108
|
-
: 'apt install build-essential python3';
|
|
109
|
-
say('✗ node-pty could not be built on this machine. Run `pmos doctor` for the full diagnosis.');
|
|
110
|
-
say(` Most often: install a C/C++ toolchain (${toolchain}),`);
|
|
111
|
-
say(` or switch to Node 22 LTS (you are on ${process.version}; a very new/non-LTS Node may not build node-pty).`);
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
102
|
+
fixSpawnHelperPerms(); // the #1 field failure: a non-executable prebuilt spawn-helper → posix_spawnp
|
|
114
103
|
return true;
|
|
115
104
|
}
|
|
116
105
|
|
package/workspace/doctor.js
CHANGED
|
@@ -31,54 +31,58 @@ function binaryArch(file) {
|
|
|
31
31
|
console.log(`pmos workspace doctor — node ${process.version} (${process.platform}/${process.arch})`);
|
|
32
32
|
console.log(` install dir: ${WSDIR}\n`);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
// node-pty` (or a clean reinstall) already builds from source; the old `--build-from-source`
|
|
36
|
-
// npm flag is unnecessary and newer npm rejects it as unknown config. These are the canonical
|
|
37
|
-
// fixes, dir-qualified so they are copy-pasteable from anywhere.
|
|
38
|
-
const TOOLCHAIN = process.platform === 'darwin' ? 'xcode-select --install'
|
|
39
|
-
: process.platform === 'win32' ? 'Visual Studio Build Tools (C++)'
|
|
40
|
-
: 'apt install build-essential python3';
|
|
41
|
-
const REINSTALL = `cd "${WSDIR}" && rm -rf node_modules && npm install (recompiles node-pty from source; needs a C/C++ toolchain: ${TOOLCHAIN})`;
|
|
42
|
-
const REINSTALL_LTS = `${REINSTALL}\n If it still fails on Node ${process.version}, that Node may be too new/non-LTS for node-pty to build — switch to Node 22 LTS and retry.`;
|
|
34
|
+
const REINSTALL = `cd "${WSDIR}" && rm -rf node_modules && npm install (reinstalls node-pty for this platform)`;
|
|
43
35
|
|
|
44
36
|
// 1. Node version
|
|
45
37
|
const major = Number(process.versions.node.split('.')[0]);
|
|
46
|
-
if (major
|
|
47
|
-
else
|
|
48
|
-
else ok(`Node ${process.version} (>= 18)`);
|
|
38
|
+
if (major >= 18) ok(`Node ${process.version} (>= 18)`);
|
|
39
|
+
else bad(`Node ${process.version} is too old`, 'install Node 18+ (arm64-native on Apple Silicon)');
|
|
49
40
|
|
|
50
41
|
// 2. node-pty loads
|
|
51
42
|
let pty = null;
|
|
52
43
|
try { pty = require('node-pty'); ok('node-pty native module loads'); }
|
|
53
|
-
catch (e) {
|
|
54
|
-
bad(`node-pty failed to load: ${e.message.split('\n')[0]}`, REINSTALL_LTS);
|
|
55
|
-
}
|
|
44
|
+
catch (e) { bad(`node-pty failed to load: ${e.message.split('\n')[0]}`, REINSTALL); }
|
|
56
45
|
|
|
57
|
-
// 3.
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const got = info && info.arch;
|
|
74
|
-
const
|
|
46
|
+
// 3. native binaries usable — and the #1 field failure, FIXED here. node-pty loads pty.node
|
|
47
|
+
// (from prebuilds/<plat>-<arch>/ or a from-source build/Release/) and, on macOS, exec()s a
|
|
48
|
+
// prebuilt spawn-helper to open each PTY. The prebuild-extraction path can drop that helper's
|
|
49
|
+
// executable bit, so it is present yet every session dies with 'posix_spawnp failed'. This
|
|
50
|
+
// check verifies pty.node's arch and, on macOS, that spawn-helper exists and is +x —
|
|
51
|
+
// RESTORING the bit rather than only reporting it. (Linux uses forkpty directly, no helper.)
|
|
52
|
+
let nproot; try { nproot = path.dirname(require.resolve('node-pty/package.json')); }
|
|
53
|
+
catch { nproot = path.join(__dirname, 'node_modules', 'node-pty'); }
|
|
54
|
+
const findBin = (name) => [
|
|
55
|
+
path.join(nproot, 'build', 'Release', name),
|
|
56
|
+
path.join(nproot, 'prebuilds', `${process.platform}-${process.arch}`, name),
|
|
57
|
+
].find((f) => fs.existsSync(f));
|
|
58
|
+
const ptyNode = findBin('pty.node');
|
|
59
|
+
let bin3 = true; const note3 = [];
|
|
60
|
+
if (!ptyNode) { bin3 = false; note3.push('pty.node missing (install incomplete)'); }
|
|
61
|
+
else {
|
|
62
|
+
const info = binaryArch(ptyNode); const want = process.arch; const got = info && info.arch;
|
|
63
|
+
const archOk = !info || info.arch === 'universal' || info.x === 'ELF' || info.x === 'PE' ||
|
|
75
64
|
(want === 'arm64' && got === 'arm64') || (want === 'x64' && got === 'x86_64');
|
|
76
|
-
if (
|
|
77
|
-
else
|
|
78
|
-
|
|
65
|
+
if (!archOk) { bin3 = false; note3.push(`ARCH MISMATCH (Node ${want} vs binary ${got})`); }
|
|
66
|
+
else if (got && got !== 'universal' && info.x === 'Mach-O') note3.push(got);
|
|
67
|
+
}
|
|
68
|
+
if (bin3 && process.platform === 'darwin') {
|
|
69
|
+
const helper = findBin('spawn-helper');
|
|
70
|
+
if (!helper) { bin3 = false; note3.push('spawn-helper missing'); }
|
|
71
|
+
else {
|
|
72
|
+
const st = fs.statSync(helper);
|
|
73
|
+
if (!(st.mode & 0o111)) {
|
|
74
|
+
try { fs.chmodSync(helper, st.mode | 0o755); note3.push('spawn-helper +x RESTORED — this was the posix_spawnp cause'); }
|
|
75
|
+
catch { bin3 = false; note3.push('spawn-helper not executable'); }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
79
78
|
}
|
|
79
|
+
if (bin3) ok('node-pty native binaries usable' + (note3.length ? ` (${note3.join('; ')})` : ''));
|
|
80
|
+
else bad('node-pty native binaries unusable' + (note3.length ? ` — ${note3.join('; ')}` : ''),
|
|
81
|
+
note3.some((n) => /MISMATCH/.test(n)) ? `install a ${process.arch}-native Node, then: ${REINSTALL}`
|
|
82
|
+
: note3.some((n) => /not executable/.test(n)) ? `chmod +x "${findBin('spawn-helper')}"`
|
|
83
|
+
: REINSTALL);
|
|
80
84
|
|
|
81
|
-
// 4. a real PTY spawns and echoes
|
|
85
|
+
// 4. a real PTY spawns and echoes (after the perms fix above this should pass)
|
|
82
86
|
if (pty) {
|
|
83
87
|
const sh = process.platform === 'win32' ? (process.env.COMSPEC || 'powershell.exe') : '/bin/sh';
|
|
84
88
|
try {
|
|
@@ -94,7 +98,7 @@ function binaryArch(file) {
|
|
|
94
98
|
} catch (e) {
|
|
95
99
|
bad(`PTY spawn failed: ${e.message}`,
|
|
96
100
|
/posix_spawnp/.test(e.message)
|
|
97
|
-
? `
|
|
101
|
+
? `on macOS this is node-pty's spawn-helper lacking +x (check 3 restores it — re-run \`npm run doctor\`); otherwise a wrong-arch binary — ${REINSTALL}`
|
|
98
102
|
: process.platform === 'win32' ? 'native Windows needs WSL for a real PTY — run the workspace inside WSL'
|
|
99
103
|
: 'check that /bin/sh exists and is executable');
|
|
100
104
|
}
|
package/workspace/server.js
CHANGED
|
@@ -36,12 +36,30 @@ try {
|
|
|
36
36
|
} catch (e) {
|
|
37
37
|
console.error('✗ node-pty failed to load — the native module is missing or built for the wrong platform.');
|
|
38
38
|
console.error(` ${e.message.split('\n')[0]}`);
|
|
39
|
-
console.error(' Fix: npm run doctor
|
|
40
|
-
console.error(' rm -rf node_modules && npm install
|
|
41
|
-
console.error(` node-pty compiles a native addon — needs a C/C++ toolchain (macOS: xcode-select --install; Debian/Ubuntu: apt install build-essential python3) and a supported Node LTS. You are on ${process.version}; if it is a very new or non-LTS release the build may fail — try Node 22 LTS.`);
|
|
39
|
+
console.error(' Fix: npm run doctor (diagnoses the exact cause)');
|
|
40
|
+
console.error(' rm -rf node_modules && npm install (reinstalls node-pty for this platform)');
|
|
42
41
|
process.exit(1);
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
// node-pty ships a prebuilt macOS `spawn-helper` binary that it exec()s to open each PTY. The
|
|
45
|
+
// prebuild-extraction path can drop its executable bit, and then EVERY session dies with
|
|
46
|
+
// `posix_spawnp failed` — the #1 field failure (it looks like a broken build but the binary is
|
|
47
|
+
// fine, just not +x). Restore the bit wherever node-pty keeps the helper (prebuilds/<plat>-<arch>/
|
|
48
|
+
// or a from-source build/Release/) BEFORE we spawn anything. Cheap, idempotent, no-op off macOS.
|
|
49
|
+
function ensureSpawnHelperExecutable() {
|
|
50
|
+
if (process.platform === 'win32') return;
|
|
51
|
+
try {
|
|
52
|
+
const root = path.dirname(require.resolve('node-pty/package.json'));
|
|
53
|
+
const targets = [path.join(root, 'build', 'Release', 'spawn-helper')];
|
|
54
|
+
const pb = path.join(root, 'prebuilds');
|
|
55
|
+
try { for (const d of fs.readdirSync(pb)) targets.push(path.join(pb, d, 'spawn-helper')); } catch {}
|
|
56
|
+
for (const f of targets) {
|
|
57
|
+
try { const st = fs.statSync(f); if (!(st.mode & 0o111)) fs.chmodSync(f, st.mode | 0o755); } catch {}
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
ensureSpawnHelperExecutable();
|
|
62
|
+
|
|
45
63
|
const { checkSurface } = require('./lib/surface-contract');
|
|
46
64
|
const scheduleLogic = require('./lib/schedule-logic');
|
|
47
65
|
const harness = require('./lib/harness-parse');
|
|
@@ -819,20 +837,18 @@ function appendBuf(s, data) {
|
|
|
819
837
|
s.parsed = null; // buffer moved; the cached parse is stale
|
|
820
838
|
}
|
|
821
839
|
|
|
822
|
-
// Platform-correct guidance when a spawn fails. `posix_spawnp failed`
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
// newer npm rejects it as an unknown config).
|
|
840
|
+
// Platform-correct guidance when a spawn fails. On macOS `posix_spawnp failed` is almost always
|
|
841
|
+
// node-pty's prebuilt `spawn-helper` missing its executable bit (prebuild extraction drops it) —
|
|
842
|
+
// the binary is fine, just not +x. The server auto-restores it at startup and `npm run doctor`
|
|
843
|
+
// re-applies + verifies the fix; a wrong-arch binary (x64 Node on Apple Silicon) is the rarer
|
|
844
|
+
// alternative, cured by a clean reinstall.
|
|
828
845
|
function spawnFailureAdvice(err, cmd, isShell) {
|
|
829
846
|
const msg = String(err && err.message || err);
|
|
830
847
|
if (/posix_spawnp|EFAULT|ENOEXEC|wrong architecture|not valid/i.test(msg)) {
|
|
831
|
-
return `could not spawn '${cmd}' (${msg}).
|
|
832
|
-
`
|
|
833
|
-
`
|
|
834
|
-
`
|
|
835
|
-
`fail — try Node 22 LTS.`;
|
|
848
|
+
return `could not spawn '${cmd}' (${msg}). On macOS this is almost always node-pty's prebuilt ` +
|
|
849
|
+
`spawn-helper lacking its executable bit. Fix: run \`pmos doctor\` (it restores +x and verifies), ` +
|
|
850
|
+
`then ↻ respawn. If it persists, the binary is wrong-arch — \`rm -rf node_modules && npm install\` ` +
|
|
851
|
+
`in the workspace install dir.`;
|
|
836
852
|
}
|
|
837
853
|
if (process.platform === 'win32') {
|
|
838
854
|
return `could not start '${cmd}': ${msg}. Native Windows needs WSL for a real PTY shell — ` +
|
|
@@ -905,8 +921,21 @@ function ensureTmuxSession(name, cmd, workdir, cols, rows) {
|
|
|
905
921
|
function attachPty(s) {
|
|
906
922
|
const name = tmuxName(s.id);
|
|
907
923
|
const startedMs = Date.now();
|
|
908
|
-
|
|
909
|
-
|
|
924
|
+
let p;
|
|
925
|
+
try {
|
|
926
|
+
p = pty.spawn(TMUX, ['-L', TMUX_SOCK, 'attach-session', '-t', name],
|
|
927
|
+
{ name: 'xterm-256color', cols: s.cols || 80, rows: s.rows || 24, cwd: s.cwd, env: sessionEnv() });
|
|
928
|
+
} catch (e) {
|
|
929
|
+
// A broken node-pty (e.g. a non-executable spawn-helper) throws synchronously here. This must
|
|
930
|
+
// NOT propagate: on the startup first-shell it would crash the whole server and blank the
|
|
931
|
+
// entire UI ("no terminal painted"). Degrade to an honest exited pane carrying the fix, exactly
|
|
932
|
+
// like the direct-PTY path does — the server stays up and the session shows why + a respawn.
|
|
933
|
+
s.pty = null; s.state = 'exited';
|
|
934
|
+
s.note = spawnFailureAdvice(e, s.agent || 'session', false);
|
|
935
|
+
appendBuf(s, `\x1b[38;5;203m✗ ${s.note}\x1b[0m\r\n\x1b[38;5;244mFix the above, then ↻ respawn.\x1b[0m\r\n`);
|
|
936
|
+
persist(s); pushSessions();
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
910
939
|
s.pty = p; s.tmux = name; s.state = 'working';
|
|
911
940
|
// Start the quiet clock at ATTACH, not at first byte: a session that spawns and immediately
|
|
912
941
|
// hangs should go quiet on schedule rather than read 'working' forever. On a restore this
|