@aion0/forge 0.4.0 → 0.4.2
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/RELEASE_NOTES.md +10 -3
- package/bin/forge-server.mjs +0 -9
- package/lib/help-docs/10-troubleshooting.md +7 -0
- package/lib/logger.ts +15 -9
- package/lib/terminal-standalone.ts +30 -4
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
# Forge v0.4.
|
|
1
|
+
# Forge v0.4.2
|
|
2
2
|
|
|
3
3
|
Released: 2026-03-21
|
|
4
4
|
|
|
5
|
-
## Changes since v0.
|
|
5
|
+
## Changes since v0.4.1
|
|
6
6
|
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
- fix: prevent double console.log wrapping in production mode
|
|
9
|
+
- fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
### Documentation
|
|
12
|
+
- fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.1...v0.4.2
|
package/bin/forge-server.mjs
CHANGED
|
@@ -72,15 +72,6 @@ const LOG_FILE = join(DATA_DIR, 'forge.log');
|
|
|
72
72
|
|
|
73
73
|
process.chdir(ROOT);
|
|
74
74
|
|
|
75
|
-
// ── Add timestamps to all console output ──
|
|
76
|
-
const origLog = console.log;
|
|
77
|
-
const origError = console.error;
|
|
78
|
-
const origWarn = console.warn;
|
|
79
|
-
const ts = () => new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
80
|
-
console.log = (...args) => origLog(`[${ts()}]`, ...args);
|
|
81
|
-
console.error = (...args) => origError(`[${ts()}]`, ...args);
|
|
82
|
-
console.warn = (...args) => origWarn(`[${ts()}]`, ...args);
|
|
83
|
-
|
|
84
75
|
// ── Migrate old layout (~/.forge/*) to new (~/.forge/data/*) ──
|
|
85
76
|
if (!getArg('--dir')) {
|
|
86
77
|
const oldSettings = join(homedir(), '.forge', 'settings.yaml');
|
|
@@ -61,6 +61,13 @@ rm -rf .next
|
|
|
61
61
|
pnpm build # or forge server rebuild
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
### npm install fails with ENOTEMPTY
|
|
65
|
+
Previous install was interrupted. Clean up and retry:
|
|
66
|
+
```bash
|
|
67
|
+
rm -rf $(npm root -g)/@aion0/forge $(npm root -g)/@aion0/.forge-*
|
|
68
|
+
npm install -g @aion0/forge
|
|
69
|
+
```
|
|
70
|
+
|
|
64
71
|
## Logs
|
|
65
72
|
|
|
66
73
|
- Background server: `~/.forge/data/forge.log`
|
package/lib/logger.ts
CHANGED
|
@@ -7,20 +7,26 @@
|
|
|
7
7
|
import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Use globalThis to prevent double-init across forge-server.mjs and init.ts
|
|
11
|
+
const loggerKey = Symbol.for('forge-logger-init');
|
|
11
12
|
|
|
12
13
|
export function initLogger() {
|
|
13
|
-
if (
|
|
14
|
-
|
|
14
|
+
if ((globalThis as any)[loggerKey]) return;
|
|
15
|
+
(globalThis as any)[loggerKey] = true;
|
|
15
16
|
|
|
16
17
|
// Determine log file path
|
|
18
|
+
// In production mode (FORGE_EXTERNAL_SERVICES=1), stdout is already redirected to forge.log
|
|
19
|
+
// by forge-server.mjs, so we only need to write to file in dev mode
|
|
20
|
+
const isProduction = process.env.FORGE_EXTERNAL_SERVICES === '1';
|
|
17
21
|
let logFile: string | null = null;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if (!isProduction) {
|
|
23
|
+
try {
|
|
24
|
+
const { getDataDir } = require('./dirs');
|
|
25
|
+
const dataDir = getDataDir();
|
|
26
|
+
if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
|
|
27
|
+
logFile = join(dataDir, 'forge.log');
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
const origLog = console.log;
|
|
26
32
|
const origError = console.error;
|
|
@@ -149,10 +149,36 @@ function createTmuxSession(cols: number, rows: number): string {
|
|
|
149
149
|
|
|
150
150
|
const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
151
151
|
const name = `${SESSION_PREFIX}${id}`;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
try {
|
|
153
|
+
execSync(`${TMUX} new-session -d -s ${name} -x ${cols} -y ${rows}`, {
|
|
154
|
+
cwd: getDefaultCwd(),
|
|
155
|
+
env: { ...process.env, TERM: 'xterm-256color' },
|
|
156
|
+
});
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
const msg = e.stderr?.toString() || e.message || '';
|
|
159
|
+
if (msg.includes('posix_spawn') || msg.includes('fork failed') || msg.includes('No such file')) {
|
|
160
|
+
// PTY exhausted — aggressive cleanup: kill ALL idle sessions
|
|
161
|
+
console.error(`[terminal] PTY exhausted, cleaning up all idle sessions...`);
|
|
162
|
+
const all = listTmuxSessions();
|
|
163
|
+
for (const s of all) {
|
|
164
|
+
if (!s.attached) {
|
|
165
|
+
killTmuxSession(s.name);
|
|
166
|
+
console.log(`[terminal] Killed idle session: ${s.name}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Retry once
|
|
170
|
+
try {
|
|
171
|
+
execSync(`${TMUX} new-session -d -s ${name} -x ${cols} -y ${rows}`, {
|
|
172
|
+
cwd: getDefaultCwd(),
|
|
173
|
+
env: { ...process.env, TERM: 'xterm-256color' },
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
throw new Error('Failed to create terminal session. PTY devices exhausted. Run: sudo sysctl kern.tty.ptmx_max=2048');
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
throw e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
156
182
|
// Enable mouse scrolling and set large scrollback buffer
|
|
157
183
|
try {
|
|
158
184
|
execSync(`${TMUX} set-option -t ${name} mouse on 2>/dev/null`);
|