@aion0/forge 0.4.1 → 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 +7 -3
- package/bin/forge-server.mjs +0 -17
- package/lib/help-docs/10-troubleshooting.md +7 -0
- package/lib/logger.ts +11 -6
- package/lib/terminal-standalone.ts +30 -4
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,11 +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.4.
|
|
5
|
+
## Changes since v0.4.1
|
|
6
6
|
|
|
7
7
|
### Bug Fixes
|
|
8
8
|
- fix: prevent double console.log wrapping in production mode
|
|
9
|
+
- fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
|
|
9
10
|
|
|
11
|
+
### Documentation
|
|
12
|
+
- fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.1...v0.4.2
|
package/bin/forge-server.mjs
CHANGED
|
@@ -72,23 +72,6 @@ const LOG_FILE = join(DATA_DIR, 'forge.log');
|
|
|
72
72
|
|
|
73
73
|
process.chdir(ROOT);
|
|
74
74
|
|
|
75
|
-
// ── Init logger (timestamps + file output) ──
|
|
76
|
-
try {
|
|
77
|
-
const { initLogger } = await import('../lib/logger.ts');
|
|
78
|
-
initLogger();
|
|
79
|
-
} catch {
|
|
80
|
-
// logger.ts is TypeScript, may not load directly in .mjs — fallback inline
|
|
81
|
-
const _key = Symbol.for('forge-logger-init');
|
|
82
|
-
if (!globalThis[_key]) {
|
|
83
|
-
globalThis[_key] = true;
|
|
84
|
-
const _origLog = console.log, _origErr = console.error, _origWarn = console.warn;
|
|
85
|
-
const _ts = () => new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
86
|
-
console.log = (...a) => _origLog(`[${_ts()}]`, ...a);
|
|
87
|
-
console.error = (...a) => _origErr(`[${_ts()}]`, ...a);
|
|
88
|
-
console.warn = (...a) => _origWarn(`[${_ts()}]`, ...a);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
75
|
// ── Migrate old layout (~/.forge/*) to new (~/.forge/data/*) ──
|
|
93
76
|
if (!getArg('--dir')) {
|
|
94
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
|
@@ -15,13 +15,18 @@ export function initLogger() {
|
|
|
15
15
|
(globalThis as any)[loggerKey] = true;
|
|
16
16
|
|
|
17
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';
|
|
18
21
|
let logFile: string | null = null;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
}
|
|
25
30
|
|
|
26
31
|
const origLog = console.log;
|
|
27
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`);
|
package/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|