@wangyaoshen/remux 0.3.8-dev.29e114b
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/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/dependabot.yml +33 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/deploy.yml +65 -0
- package/.github/workflows/publish.yml +312 -0
- package/.github/workflows/release-please.yml +21 -0
- package/.gitmodules +3 -0
- package/.nvmrc +1 -0
- package/.release-please-manifest.json +3 -0
- package/CLAUDE.md +104 -0
- package/Dockerfile +23 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/apps/ios/Config/signing.xcconfig +4 -0
- package/apps/ios/Package.swift +26 -0
- package/apps/ios/Remux.xcodeproj/project.pbxproj +477 -0
- package/apps/ios/Remux.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
- package/apps/ios/Sources/Remux/Extensions/FaceIDManager.swift +29 -0
- package/apps/ios/Sources/Remux/Extensions/InspectCache.swift +66 -0
- package/apps/ios/Sources/Remux/MainTabView.swift +32 -0
- package/apps/ios/Sources/Remux/Remux.entitlements +8 -0
- package/apps/ios/Sources/Remux/RemuxiOSApp.swift +14 -0
- package/apps/ios/Sources/Remux/RootView.swift +130 -0
- package/apps/ios/Sources/Remux/Views/Control/ControlView.swift +102 -0
- package/apps/ios/Sources/Remux/Views/Inspect/InspectView.swift +98 -0
- package/apps/ios/Sources/Remux/Views/Live/LiveTerminalView.swift +132 -0
- package/apps/ios/Sources/Remux/Views/Now/NowView.swift +173 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/ManualConnectView.swift +55 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/OnboardingView.swift +70 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/QRScannerView.swift +92 -0
- package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +136 -0
- package/apps/macos/Package.swift +37 -0
- package/apps/macos/Resources/shell-integration/bash/bash-preexec.sh +382 -0
- package/apps/macos/Resources/shell-integration/bash/ghostty.bash +315 -0
- package/apps/macos/Resources/shell-integration/elvish/lib/ghostty-integration.elv +191 -0
- package/apps/macos/Resources/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +246 -0
- package/apps/macos/Resources/shell-integration/nushell/vendor/autoload/ghostty.nu +110 -0
- package/apps/macos/Resources/shell-integration/zsh/.zshenv +61 -0
- package/apps/macos/Resources/shell-integration/zsh/ghostty-integration +458 -0
- package/apps/macos/Resources/terminfo/67/ghostty +0 -0
- package/apps/macos/Resources/terminfo/78/xterm-ghostty +0 -0
- package/apps/macos/Sources/Remux/AppDelegate.swift +257 -0
- package/apps/macos/Sources/Remux/CrashReporter.swift +210 -0
- package/apps/macos/Sources/Remux/FinderIntegration.swift +117 -0
- package/apps/macos/Sources/Remux/GhosttyConfig.swift +311 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutAction.swift +115 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutSettingsView.swift +271 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/StoredShortcut.swift +149 -0
- package/apps/macos/Sources/Remux/MainContentView.swift +308 -0
- package/apps/macos/Sources/Remux/MenuBarManager.swift +275 -0
- package/apps/macos/Sources/Remux/NotificationManager.swift +145 -0
- package/apps/macos/Sources/Remux/PortScanner.swift +152 -0
- package/apps/macos/Sources/Remux/RemuxApp.swift +13 -0
- package/apps/macos/Sources/Remux/SSHDetector.swift +151 -0
- package/apps/macos/Sources/Remux/SessionPersistence.swift +226 -0
- package/apps/macos/Sources/Remux/SocketController.swift +258 -0
- package/apps/macos/Sources/Remux/UpdateChecker.swift +152 -0
- package/apps/macos/Sources/Remux/Views/CommandPalette.swift +198 -0
- package/apps/macos/Sources/Remux/Views/ConnectionView.swift +84 -0
- package/apps/macos/Sources/Remux/Views/InspectView.swift +127 -0
- package/apps/macos/Sources/Remux/Views/SettingsView.swift +77 -0
- package/apps/macos/Sources/Remux/Views/Sidebar/SidebarView.swift +410 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/BrowserPanel.swift +193 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/MarkdownPanel.swift +277 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/PanelProtocol.swift +14 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitNode.swift +149 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +234 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/TerminalPanel.swift +26 -0
- package/apps/macos/Sources/Remux/Views/TabBarView.swift +94 -0
- package/apps/macos/Sources/Remux/Views/Terminal/ClipboardHelper.swift +101 -0
- package/apps/macos/Sources/Remux/Views/Terminal/CopyModeOverlay.swift +325 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeTerminalView.swift +39 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +559 -0
- package/apps/macos/Sources/Remux/Views/Terminal/SurfaceSearchOverlay.swift +109 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +95 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalRelay.swift +117 -0
- package/build.mjs +33 -0
- package/native/android/DecodeGoldenPayloads.kt +487 -0
- package/native/android/ProtocolModels.kt +188 -0
- package/native/ios/DecodeGoldenPayloads.swift +711 -0
- package/native/ios/ProtocolModels.swift +200 -0
- package/package.json +45 -0
- package/packages/RemuxKit/Package.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Device/DeviceManager.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +206 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +108 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +395 -0
- package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +188 -0
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +142 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyBridge.swift +145 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyTerminalView.swift +35 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/Resources/ghostty-terminal.html +91 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +74 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +81 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +179 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +62 -0
- package/playwright.config.ts +17 -0
- package/pnpm-lock.yaml +1588 -0
- package/pty-daemon.js +303 -0
- package/release-please-config.json +14 -0
- package/scripts/auto-deploy.sh +46 -0
- package/scripts/build-dmg.sh +121 -0
- package/scripts/build-ghostty-kit.sh +43 -0
- package/scripts/check-active-terminology.mjs +132 -0
- package/scripts/setup-ci-secrets.sh +80 -0
- package/scripts/sync-ghostty-web.sh +28 -0
- package/scripts/upload-testflight.sh +100 -0
- package/server.js +7074 -0
- package/src/adapters/agent-events.ts +246 -0
- package/src/adapters/claude-code.ts +158 -0
- package/src/adapters/codex.ts +210 -0
- package/src/adapters/generic-shell.ts +58 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +99 -0
- package/src/adapters/types.ts +41 -0
- package/src/auth.ts +174 -0
- package/src/e2ee.ts +236 -0
- package/src/git-service.ts +168 -0
- package/src/message-buffer.ts +137 -0
- package/src/pty-daemon.ts +357 -0
- package/src/push.ts +127 -0
- package/src/renderers.ts +455 -0
- package/src/server.ts +2407 -0
- package/src/service.ts +226 -0
- package/src/session.ts +978 -0
- package/src/store.ts +1422 -0
- package/src/team.ts +123 -0
- package/src/tunnel.ts +126 -0
- package/src/types.d.ts +50 -0
- package/src/vt-tracker.ts +188 -0
- package/src/workspace-head.ts +144 -0
- package/src/workspace.ts +153 -0
- package/src/ws-handler.ts +1526 -0
- package/start.ps1 +83 -0
- package/tests/adapters.test.js +171 -0
- package/tests/auth.test.js +243 -0
- package/tests/codex-adapter.test.js +535 -0
- package/tests/durable-stream.test.js +153 -0
- package/tests/e2e/app.spec.js +530 -0
- package/tests/e2ee.test.js +325 -0
- package/tests/message-buffer.test.js +245 -0
- package/tests/message-routing.test.js +305 -0
- package/tests/pty-daemon.test.js +346 -0
- package/tests/push.test.js +281 -0
- package/tests/renderers.test.js +391 -0
- package/tests/search-shell.test.js +499 -0
- package/tests/server.test.js +882 -0
- package/tests/service.test.js +267 -0
- package/tests/store.test.js +369 -0
- package/tests/tunnel.test.js +67 -0
- package/tests/workspace-head.test.js +116 -0
- package/tests/workspace.test.js +417 -0
- package/tsconfig.backend.json +11 -0
- package/tsconfig.json +15 -0
- package/tui/client/client_test.go +125 -0
- package/tui/client/connection.go +342 -0
- package/tui/client/host_manager.go +141 -0
- package/tui/config/cache.go +81 -0
- package/tui/config/config.go +53 -0
- package/tui/config/config_test.go +89 -0
- package/tui/go.mod +32 -0
- package/tui/go.sum +50 -0
- package/tui/main.go +261 -0
- package/tui/tests/integration_test.go +283 -0
- package/tui/ui/model.go +310 -0
- package/vitest.config.js +10 -0
package/pty-daemon.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/pty-daemon.ts
|
|
10
|
+
import net from "net";
|
|
11
|
+
import pty from "node-pty";
|
|
12
|
+
import { parseArgs } from "util";
|
|
13
|
+
var TAG_PTY_OUTPUT = 1;
|
|
14
|
+
var TAG_CLIENT_INPUT = 2;
|
|
15
|
+
var TAG_RESIZE = 3;
|
|
16
|
+
var TAG_STATUS_REQ = 4;
|
|
17
|
+
var TAG_STATUS_RES = 5;
|
|
18
|
+
var TAG_SNAPSHOT_REQ = 6;
|
|
19
|
+
var TAG_SNAPSHOT_RES = 7;
|
|
20
|
+
var TAG_SCROLLBACK_REQ = 8;
|
|
21
|
+
var TAG_SCROLLBACK_RES = 9;
|
|
22
|
+
var TAG_SHUTDOWN = 255;
|
|
23
|
+
function encodeFrame(tag, payload) {
|
|
24
|
+
const data = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
25
|
+
const frame = Buffer.alloc(5 + data.length);
|
|
26
|
+
frame[0] = tag;
|
|
27
|
+
frame.writeUInt32BE(data.length, 1);
|
|
28
|
+
data.copy(frame, 5);
|
|
29
|
+
return frame;
|
|
30
|
+
}
|
|
31
|
+
var FrameParser = class {
|
|
32
|
+
buffer = Buffer.alloc(0);
|
|
33
|
+
onFrame;
|
|
34
|
+
constructor(onFrame) {
|
|
35
|
+
this.onFrame = onFrame;
|
|
36
|
+
}
|
|
37
|
+
feed(data) {
|
|
38
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
39
|
+
while (this.buffer.length >= 5) {
|
|
40
|
+
const tag = this.buffer[0];
|
|
41
|
+
const length = this.buffer.readUInt32BE(1);
|
|
42
|
+
if (this.buffer.length < 5 + length) break;
|
|
43
|
+
const payload = this.buffer.subarray(5, 5 + length);
|
|
44
|
+
this.buffer = this.buffer.subarray(5 + length);
|
|
45
|
+
this.onFrame(tag, payload);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var RingBuffer = class {
|
|
50
|
+
buf;
|
|
51
|
+
maxBytes;
|
|
52
|
+
writePos;
|
|
53
|
+
length;
|
|
54
|
+
constructor(maxBytes = 10 * 1024 * 1024) {
|
|
55
|
+
this.buf = Buffer.alloc(maxBytes);
|
|
56
|
+
this.maxBytes = maxBytes;
|
|
57
|
+
this.writePos = 0;
|
|
58
|
+
this.length = 0;
|
|
59
|
+
}
|
|
60
|
+
write(data) {
|
|
61
|
+
const bytes = typeof data === "string" ? Buffer.from(data) : data;
|
|
62
|
+
if (bytes.length >= this.maxBytes) {
|
|
63
|
+
bytes.copy(this.buf, 0, bytes.length - this.maxBytes);
|
|
64
|
+
this.writePos = 0;
|
|
65
|
+
this.length = this.maxBytes;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const space = this.maxBytes - this.writePos;
|
|
69
|
+
if (bytes.length <= space) {
|
|
70
|
+
bytes.copy(this.buf, this.writePos);
|
|
71
|
+
} else {
|
|
72
|
+
bytes.copy(this.buf, this.writePos, 0, space);
|
|
73
|
+
bytes.copy(this.buf, 0, space);
|
|
74
|
+
}
|
|
75
|
+
this.writePos = (this.writePos + bytes.length) % this.maxBytes;
|
|
76
|
+
this.length = Math.min(this.length + bytes.length, this.maxBytes);
|
|
77
|
+
}
|
|
78
|
+
read() {
|
|
79
|
+
if (this.length === 0) return Buffer.alloc(0);
|
|
80
|
+
if (this.length < this.maxBytes) {
|
|
81
|
+
return Buffer.from(this.buf.subarray(this.writePos - this.length, this.writePos));
|
|
82
|
+
}
|
|
83
|
+
return Buffer.concat([
|
|
84
|
+
this.buf.subarray(this.writePos),
|
|
85
|
+
this.buf.subarray(0, this.writePos)
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function parseCliArgs() {
|
|
90
|
+
const { values } = parseArgs({
|
|
91
|
+
options: {
|
|
92
|
+
socket: { type: "string" },
|
|
93
|
+
shell: { type: "string" },
|
|
94
|
+
cols: { type: "string" },
|
|
95
|
+
rows: { type: "string" },
|
|
96
|
+
cwd: { type: "string" },
|
|
97
|
+
"tab-id": { type: "string" }
|
|
98
|
+
},
|
|
99
|
+
strict: true
|
|
100
|
+
});
|
|
101
|
+
if (!values.socket || !values.shell) {
|
|
102
|
+
console.error("Usage: pty-daemon --socket <path> --shell <shell> [--cols N] [--rows N] [--cwd dir] [--tab-id id]");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
socket: values.socket,
|
|
107
|
+
shell: values.shell,
|
|
108
|
+
cols: parseInt(values.cols || "80", 10),
|
|
109
|
+
rows: parseInt(values.rows || "24", 10),
|
|
110
|
+
cwd: values.cwd || process.env.HOME || "/",
|
|
111
|
+
tabId: values["tab-id"] || "0"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function main() {
|
|
115
|
+
const args = parseCliArgs();
|
|
116
|
+
let seq = 0;
|
|
117
|
+
if (typeof process.disconnect === "function") {
|
|
118
|
+
try {
|
|
119
|
+
process.disconnect();
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const ptyProcess = pty.spawn(args.shell, [], {
|
|
124
|
+
name: "xterm-256color",
|
|
125
|
+
cols: args.cols,
|
|
126
|
+
rows: args.rows,
|
|
127
|
+
cwd: args.cwd,
|
|
128
|
+
env: {
|
|
129
|
+
...process.env,
|
|
130
|
+
TERM: "xterm-256color",
|
|
131
|
+
COLORTERM: "truecolor"
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
const scrollback = new RingBuffer();
|
|
135
|
+
const clients = /* @__PURE__ */ new Set();
|
|
136
|
+
let alive = true;
|
|
137
|
+
console.log(`[pty-daemon] started: pid=${ptyProcess.pid} socket=${args.socket} tab-id=${args.tabId}`);
|
|
138
|
+
ptyProcess.onData((data) => {
|
|
139
|
+
seq++;
|
|
140
|
+
scrollback.write(data);
|
|
141
|
+
const frame = encodeFrame(TAG_PTY_OUTPUT, data);
|
|
142
|
+
for (const client of clients) {
|
|
143
|
+
try {
|
|
144
|
+
client.write(frame);
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
150
|
+
alive = false;
|
|
151
|
+
console.log(`[pty-daemon] PTY exited: code=${exitCode} tab-id=${args.tabId}`);
|
|
152
|
+
const exitMsg = `\r
|
|
153
|
+
\x1B[33mShell exited (code: ${exitCode})\x1B[0m\r
|
|
154
|
+
`;
|
|
155
|
+
const frame = encodeFrame(TAG_PTY_OUTPUT, exitMsg);
|
|
156
|
+
for (const client of clients) {
|
|
157
|
+
try {
|
|
158
|
+
client.write(frame);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
for (const client of clients) {
|
|
164
|
+
try {
|
|
165
|
+
client.end();
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
cleanup();
|
|
170
|
+
}, 2e3);
|
|
171
|
+
});
|
|
172
|
+
const server = net.createServer((socket) => {
|
|
173
|
+
clients.add(socket);
|
|
174
|
+
console.log(`[pty-daemon] client connected (total: ${clients.size})`);
|
|
175
|
+
const parser = new FrameParser((tag, payload) => {
|
|
176
|
+
switch (tag) {
|
|
177
|
+
case TAG_CLIENT_INPUT:
|
|
178
|
+
if (alive) {
|
|
179
|
+
ptyProcess.write(payload.toString("utf8"));
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
case TAG_RESIZE: {
|
|
183
|
+
try {
|
|
184
|
+
const { cols, rows } = JSON.parse(payload.toString("utf8"));
|
|
185
|
+
if (alive && cols > 0 && rows > 0) {
|
|
186
|
+
ptyProcess.resize(
|
|
187
|
+
Math.max(1, Math.min(cols, 500)),
|
|
188
|
+
Math.max(1, Math.min(rows, 200))
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case TAG_STATUS_REQ: {
|
|
196
|
+
const status = JSON.stringify({
|
|
197
|
+
pid: ptyProcess.pid,
|
|
198
|
+
cols: args.cols,
|
|
199
|
+
rows: args.rows,
|
|
200
|
+
alive,
|
|
201
|
+
cwd: args.cwd,
|
|
202
|
+
tabId: args.tabId,
|
|
203
|
+
seq
|
|
204
|
+
});
|
|
205
|
+
socket.write(encodeFrame(TAG_STATUS_RES, status));
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case TAG_SNAPSHOT_REQ: {
|
|
209
|
+
const data = scrollback.read();
|
|
210
|
+
socket.write(encodeFrame(TAG_SNAPSHOT_RES, data));
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case TAG_SCROLLBACK_REQ: {
|
|
214
|
+
const data = scrollback.read();
|
|
215
|
+
socket.write(encodeFrame(TAG_SCROLLBACK_RES, data));
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case TAG_SHUTDOWN:
|
|
219
|
+
console.log(`[pty-daemon] shutdown requested`);
|
|
220
|
+
if (alive) {
|
|
221
|
+
try {
|
|
222
|
+
ptyProcess.kill();
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const c of clients) {
|
|
227
|
+
try {
|
|
228
|
+
c.end();
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
cleanup();
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
socket.on("data", (data) => {
|
|
237
|
+
parser.feed(Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
238
|
+
});
|
|
239
|
+
socket.on("close", () => {
|
|
240
|
+
clients.delete(socket);
|
|
241
|
+
console.log(`[pty-daemon] client disconnected (total: ${clients.size})`);
|
|
242
|
+
});
|
|
243
|
+
socket.on("error", (err) => {
|
|
244
|
+
console.error(`[pty-daemon] client socket error:`, err.message);
|
|
245
|
+
clients.delete(socket);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
try {
|
|
249
|
+
const fs = __require("fs");
|
|
250
|
+
if (fs.existsSync(args.socket)) {
|
|
251
|
+
fs.unlinkSync(args.socket);
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
server.listen(args.socket, () => {
|
|
256
|
+
console.log(`[pty-daemon] listening on ${args.socket}`);
|
|
257
|
+
});
|
|
258
|
+
server.on("error", (err) => {
|
|
259
|
+
console.error(`[pty-daemon] server error:`, err.message);
|
|
260
|
+
cleanup();
|
|
261
|
+
});
|
|
262
|
+
function cleanup() {
|
|
263
|
+
try {
|
|
264
|
+
const fs = __require("fs");
|
|
265
|
+
if (fs.existsSync(args.socket)) {
|
|
266
|
+
fs.unlinkSync(args.socket);
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
server.close();
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
process.on("SIGTERM", () => {
|
|
274
|
+
console.log(`[pty-daemon] SIGTERM received`);
|
|
275
|
+
if (alive) {
|
|
276
|
+
try {
|
|
277
|
+
ptyProcess.kill();
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
cleanup();
|
|
282
|
+
});
|
|
283
|
+
process.on("SIGINT", () => {
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
var isMainEntry = process.argv[1]?.includes("pty-daemon") || process.argv.includes("--socket");
|
|
287
|
+
if (isMainEntry) {
|
|
288
|
+
main();
|
|
289
|
+
}
|
|
290
|
+
export {
|
|
291
|
+
FrameParser,
|
|
292
|
+
TAG_CLIENT_INPUT,
|
|
293
|
+
TAG_PTY_OUTPUT,
|
|
294
|
+
TAG_RESIZE,
|
|
295
|
+
TAG_SCROLLBACK_REQ,
|
|
296
|
+
TAG_SCROLLBACK_RES,
|
|
297
|
+
TAG_SHUTDOWN,
|
|
298
|
+
TAG_SNAPSHOT_REQ,
|
|
299
|
+
TAG_SNAPSHOT_RES,
|
|
300
|
+
TAG_STATUS_REQ,
|
|
301
|
+
TAG_STATUS_RES,
|
|
302
|
+
encodeFrame
|
|
303
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packages": {
|
|
3
|
+
".": {
|
|
4
|
+
"release-type": "node",
|
|
5
|
+
"changelog-sections": [
|
|
6
|
+
{ "type": "feat", "section": "Features" },
|
|
7
|
+
{ "type": "fix", "section": "Bug Fixes" },
|
|
8
|
+
{ "type": "chore", "section": "Miscellaneous" },
|
|
9
|
+
{ "type": "refactor", "section": "Refactoring" },
|
|
10
|
+
{ "type": "docs", "section": "Documentation" }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto-deploy script for Remux runtime instances.
|
|
3
|
+
# Usage: auto-deploy.sh <branch> <worktree-path> <launchd-label>
|
|
4
|
+
#
|
|
5
|
+
# Checks if origin/<branch> has new commits, pulls, installs deps, and restarts.
|
|
6
|
+
# Designed to be called by launchd on a schedule (e.g. every 2 minutes).
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
BRANCH="${1:?Usage: auto-deploy.sh <branch> <worktree-path> <launchd-label>}"
|
|
11
|
+
WORKTREE="${2:?}"
|
|
12
|
+
LABEL="${3:?}"
|
|
13
|
+
LOG_PREFIX="[deploy:${BRANCH}]"
|
|
14
|
+
|
|
15
|
+
cd "$WORKTREE" || { echo "$LOG_PREFIX worktree not found: $WORKTREE"; exit 1; }
|
|
16
|
+
|
|
17
|
+
# Fetch latest from origin
|
|
18
|
+
git fetch origin "$BRANCH" --quiet 2>/dev/null || { echo "$LOG_PREFIX fetch failed"; exit 1; }
|
|
19
|
+
|
|
20
|
+
LOCAL=$(git rev-parse HEAD 2>/dev/null)
|
|
21
|
+
REMOTE=$(git rev-parse "origin/${BRANCH}" 2>/dev/null)
|
|
22
|
+
|
|
23
|
+
if [ "$LOCAL" = "$REMOTE" ]; then
|
|
24
|
+
# Already up to date
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
echo "$LOG_PREFIX updating ${LOCAL:0:7} → ${REMOTE:0:7}"
|
|
29
|
+
|
|
30
|
+
# Pull changes
|
|
31
|
+
git checkout --detach "origin/${BRANCH}" --quiet 2>/dev/null
|
|
32
|
+
|
|
33
|
+
# Install dependencies if lockfile changed
|
|
34
|
+
if ! git diff --quiet "$LOCAL" "$REMOTE" -- pnpm-lock.yaml 2>/dev/null; then
|
|
35
|
+
echo "$LOG_PREFIX pnpm-lock.yaml changed, installing deps"
|
|
36
|
+
/opt/homebrew/bin/pnpm install --frozen-lockfile --prefer-offline 2>/dev/null || true
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Fix node-pty spawn-helper permissions (needed after fresh install)
|
|
40
|
+
find node_modules -name "spawn-helper" -type f -exec chmod +x {} \; 2>/dev/null || true
|
|
41
|
+
|
|
42
|
+
# Restart the service
|
|
43
|
+
echo "$LOG_PREFIX restarting $LABEL"
|
|
44
|
+
/bin/launchctl kickstart -k "gui/501/${LABEL}" 2>/dev/null || true
|
|
45
|
+
|
|
46
|
+
echo "$LOG_PREFIX deployed ${REMOTE:0:7}"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build Remux.app and create a DMG installer.
|
|
3
|
+
# Usage: ./scripts/build-dmg.sh [--version X.Y.Z]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
8
|
+
APP_DIR="$ROOT_DIR/apps/macos"
|
|
9
|
+
BUILD_DIR="$ROOT_DIR/build"
|
|
10
|
+
|
|
11
|
+
# Read version from package.json or CLI arg
|
|
12
|
+
VERSION="${1:-}"
|
|
13
|
+
if [ -z "$VERSION" ]; then
|
|
14
|
+
VERSION=$(node -p "require('$ROOT_DIR/package.json').version" 2>/dev/null || echo "0.0.0")
|
|
15
|
+
fi
|
|
16
|
+
# Strip leading 'v' if present
|
|
17
|
+
VERSION="${VERSION#v}"
|
|
18
|
+
|
|
19
|
+
echo "Building Remux.app v${VERSION}..."
|
|
20
|
+
|
|
21
|
+
# Step 1: Build ghostty xcframework if missing
|
|
22
|
+
XCFW="$ROOT_DIR/vendor/ghostty/macos/GhosttyKit.xcframework"
|
|
23
|
+
if [ ! -d "$XCFW" ] || [ ! -f "$XCFW/Info.plist" ]; then
|
|
24
|
+
echo "GhosttyKit.xcframework not found. Building from source..."
|
|
25
|
+
cd "$ROOT_DIR/vendor/ghostty"
|
|
26
|
+
git submodule update --init 2>/dev/null || true
|
|
27
|
+
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
|
|
28
|
+
echo "GhosttyKit built."
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Step 2: Build macOS app (release)
|
|
32
|
+
cd "$APP_DIR"
|
|
33
|
+
swift build -c release 2>&1
|
|
34
|
+
|
|
35
|
+
BINARY=$(swift build -c release --show-bin-path)/Remux
|
|
36
|
+
if [ ! -f "$BINARY" ]; then
|
|
37
|
+
echo "Error: Binary not found at $BINARY"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Step 3: Create .app bundle
|
|
42
|
+
rm -rf "$BUILD_DIR/Remux.app"
|
|
43
|
+
mkdir -p "$BUILD_DIR/Remux.app/Contents/MacOS"
|
|
44
|
+
mkdir -p "$BUILD_DIR/Remux.app/Contents/Resources"
|
|
45
|
+
|
|
46
|
+
cp "$BINARY" "$BUILD_DIR/Remux.app/Contents/MacOS/Remux"
|
|
47
|
+
|
|
48
|
+
# Copy ghostty resources if available
|
|
49
|
+
if [ -d "$APP_DIR/Resources/terminfo" ]; then
|
|
50
|
+
cp -r "$APP_DIR/Resources/terminfo" "$BUILD_DIR/Remux.app/Contents/Resources/"
|
|
51
|
+
fi
|
|
52
|
+
if [ -d "$APP_DIR/Resources/shell-integration" ]; then
|
|
53
|
+
cp -r "$APP_DIR/Resources/shell-integration" "$BUILD_DIR/Remux.app/Contents/Resources/"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
cat > "$BUILD_DIR/Remux.app/Contents/Info.plist" << PLIST
|
|
57
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
58
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
59
|
+
<plist version="1.0">
|
|
60
|
+
<dict>
|
|
61
|
+
<key>CFBundleName</key>
|
|
62
|
+
<string>Remux</string>
|
|
63
|
+
<key>CFBundleDisplayName</key>
|
|
64
|
+
<string>Remux</string>
|
|
65
|
+
<key>CFBundleIdentifier</key>
|
|
66
|
+
<string>com.remux.desktop</string>
|
|
67
|
+
<key>CFBundleVersion</key>
|
|
68
|
+
<string>${VERSION}</string>
|
|
69
|
+
<key>CFBundleShortVersionString</key>
|
|
70
|
+
<string>${VERSION}</string>
|
|
71
|
+
<key>CFBundleExecutable</key>
|
|
72
|
+
<string>Remux</string>
|
|
73
|
+
<key>CFBundlePackageType</key>
|
|
74
|
+
<string>APPL</string>
|
|
75
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
76
|
+
<string>6.0</string>
|
|
77
|
+
<key>LSMinimumSystemVersion</key>
|
|
78
|
+
<string>14.0</string>
|
|
79
|
+
<key>NSHighResolutionCapable</key>
|
|
80
|
+
<true/>
|
|
81
|
+
<key>LSUIElement</key>
|
|
82
|
+
<false/>
|
|
83
|
+
<key>NSPrincipalClass</key>
|
|
84
|
+
<string>NSApplication</string>
|
|
85
|
+
</dict>
|
|
86
|
+
</plist>
|
|
87
|
+
PLIST
|
|
88
|
+
|
|
89
|
+
echo "✓ Remux.app created at $BUILD_DIR/Remux.app"
|
|
90
|
+
|
|
91
|
+
# Step 4: Create DMG
|
|
92
|
+
DMG_NAME="Remux-${VERSION}-arm64.dmg"
|
|
93
|
+
rm -f "$BUILD_DIR/$DMG_NAME"
|
|
94
|
+
|
|
95
|
+
if command -v create-dmg &> /dev/null; then
|
|
96
|
+
echo "Creating DMG with create-dmg..."
|
|
97
|
+
create-dmg \
|
|
98
|
+
--volname "Remux" \
|
|
99
|
+
--window-pos 200 120 \
|
|
100
|
+
--window-size 600 400 \
|
|
101
|
+
--icon-size 100 \
|
|
102
|
+
--icon "Remux.app" 150 190 \
|
|
103
|
+
--app-drop-link 450 190 \
|
|
104
|
+
"$BUILD_DIR/$DMG_NAME" \
|
|
105
|
+
"$BUILD_DIR/Remux.app" \
|
|
106
|
+
2>/dev/null || true
|
|
107
|
+
else
|
|
108
|
+
# Fallback: simple hdiutil DMG
|
|
109
|
+
echo "Creating DMG with hdiutil..."
|
|
110
|
+
hdiutil create -volname "Remux" \
|
|
111
|
+
-srcfolder "$BUILD_DIR/Remux.app" \
|
|
112
|
+
-ov -format UDZO \
|
|
113
|
+
"$BUILD_DIR/$DMG_NAME"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ -f "$BUILD_DIR/$DMG_NAME" ]; then
|
|
117
|
+
SIZE=$(du -h "$BUILD_DIR/$DMG_NAME" | cut -f1)
|
|
118
|
+
echo "✓ DMG created: $BUILD_DIR/$DMG_NAME ($SIZE)"
|
|
119
|
+
else
|
|
120
|
+
echo "⚠ DMG creation failed, but Remux.app is available"
|
|
121
|
+
fi
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build GhosttyKit.xcframework from the ghostty submodule.
|
|
3
|
+
# Prerequisites: zig (brew install zig), Xcode Command Line Tools
|
|
4
|
+
# Usage: ./scripts/build-ghostty-kit.sh [--debug]
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
GHOSTTY_DIR="$ROOT_DIR/vendor/ghostty"
|
|
11
|
+
|
|
12
|
+
if [ ! -f "$GHOSTTY_DIR/build.zig" ]; then
|
|
13
|
+
echo "Error: ghostty submodule not found at $GHOSTTY_DIR"
|
|
14
|
+
echo "Run: git submodule update --init vendor/ghostty"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if ! command -v zig &> /dev/null; then
|
|
19
|
+
echo "Error: zig not found. Install with: brew install zig"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
OPTIMIZE="ReleaseFast"
|
|
24
|
+
if [ "${1:-}" = "--debug" ]; then
|
|
25
|
+
OPTIMIZE="Debug"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
echo "Building GhosttyKit.xcframework (optimize=$OPTIMIZE)..."
|
|
29
|
+
cd "$GHOSTTY_DIR"
|
|
30
|
+
zig build -Demit-xcframework=true -Doptimize="$OPTIMIZE"
|
|
31
|
+
|
|
32
|
+
# Check output — ghostty emits to macos/GhosttyKit.xcframework
|
|
33
|
+
XCFW_PATH="$GHOSTTY_DIR/macos/GhosttyKit.xcframework"
|
|
34
|
+
if [ -d "$XCFW_PATH" ]; then
|
|
35
|
+
echo "Success: $XCFW_PATH"
|
|
36
|
+
echo "Size: $(du -sh "$XCFW_PATH" | cut -f1)"
|
|
37
|
+
else
|
|
38
|
+
echo "Error: xcframework not found at expected path"
|
|
39
|
+
echo "Checking macos/ and zig-out/ contents:"
|
|
40
|
+
ls -la "$GHOSTTY_DIR/macos/" 2>/dev/null || echo "macos/ not found"
|
|
41
|
+
ls -la "$GHOSTTY_DIR/zig-out/" 2>/dev/null || echo "zig-out/ not found"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const entrypoints = ["README.md", "AGENTS.md", "docs", "src", "tests", ".github"];
|
|
6
|
+
const ignoredDirNames = new Set(["node_modules", "dist", ".git", "coverage"]);
|
|
7
|
+
const allowedExtensions = new Set([".md", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".yml", ".yaml"]);
|
|
8
|
+
const allowedFiles = new Map([
|
|
9
|
+
["docs/ACTIVE_DOCS_INDEX.md", "active index must point to the archived runtime-v2 document set by its real path"],
|
|
10
|
+
["docs/adr/ADR_TERMINOLOGY.md", "terminology ADR must name the banned product term explicitly"],
|
|
11
|
+
["docs/remux-master-plan-2026-v2.md", "current planning document compares legacy assumptions against the current baseline"],
|
|
12
|
+
["docs/LEGACY_PLAN_GAPS.md", "legacy gap ledger must name the invalidated assumptions"],
|
|
13
|
+
["docs/TERMINOLOGY_AUDIT.md", "audit output must record the archived terms it scanned"],
|
|
14
|
+
]);
|
|
15
|
+
const archivedRuntimePatterns = [
|
|
16
|
+
{ label: "runtime-v2", regex: /\bruntime-v2\b/i },
|
|
17
|
+
{ label: "remuxd", regex: /\bremuxd\b/i },
|
|
18
|
+
{ label: "old runtime", regex: /\bold runtime\b/i },
|
|
19
|
+
{ label: "daemon", regex: /\bdaemon\b/i },
|
|
20
|
+
];
|
|
21
|
+
const productScrollPattern = /\bscroll\b/i;
|
|
22
|
+
const allowedScrollLinePatterns = [
|
|
23
|
+
/\bscrollback\b/i,
|
|
24
|
+
/\bscrollbar\b/i,
|
|
25
|
+
/\boverscroll\b/i,
|
|
26
|
+
/\bscrolling\b/i,
|
|
27
|
+
/\bscrollable\b/i,
|
|
28
|
+
/\bscrolltoline\b/i,
|
|
29
|
+
/\bxterm-scrollable-element\b/i,
|
|
30
|
+
/scroll-snap/i,
|
|
31
|
+
/-webkit-overflow-scrolling/i,
|
|
32
|
+
/addEventListener\(\s*["']scroll["']/,
|
|
33
|
+
/removeEventListener\(\s*["']scroll["']/,
|
|
34
|
+
/new Event\(\s*["']scroll["']/,
|
|
35
|
+
/\bscroll fixes\b/i,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
async function collectFiles(relativePath) {
|
|
39
|
+
const absolutePath = path.join(root, relativePath);
|
|
40
|
+
const stats = await readdir(absolutePath, { withFileTypes: true }).catch(() => null);
|
|
41
|
+
if (stats === null) {
|
|
42
|
+
return [relativePath];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const entry of stats) {
|
|
47
|
+
if (ignoredDirNames.has(entry.name)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const nextRelative = path.posix.join(relativePath, entry.name);
|
|
52
|
+
if (nextRelative.startsWith("docs/archive/") || nextRelative.startsWith("docs/assets/")) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
files.push(...(await collectFiles(nextRelative)));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (allowedExtensions.has(path.extname(entry.name))) {
|
|
62
|
+
files.push(nextRelative);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function main() {
|
|
70
|
+
const files = [];
|
|
71
|
+
for (const entrypoint of entrypoints) {
|
|
72
|
+
const absolute = path.join(root, entrypoint);
|
|
73
|
+
const isDirectory = await readdir(absolute, { withFileTypes: true }).then(() => true).catch(() => false);
|
|
74
|
+
if (isDirectory) {
|
|
75
|
+
files.push(...(await collectFiles(entrypoint)));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
files.push(entrypoint);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const violations = [];
|
|
83
|
+
for (const relativePath of files) {
|
|
84
|
+
if (allowedFiles.has(relativePath)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const source = await readFile(path.join(root, relativePath), "utf8");
|
|
89
|
+
const matchedLabels = archivedRuntimePatterns
|
|
90
|
+
.filter(({ regex }) => regex.test(source))
|
|
91
|
+
.map(({ label }) => label);
|
|
92
|
+
|
|
93
|
+
const lines = source.split("\n");
|
|
94
|
+
for (const [index, line] of lines.entries()) {
|
|
95
|
+
if (!productScrollPattern.test(line)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (allowedScrollLinePatterns.some((regex) => regex.test(line))) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
matchedLabels.push(`scroll(product-term)@L${index + 1}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (matchedLabels.length > 0) {
|
|
107
|
+
violations.push({ relativePath, matchedLabels });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (violations.length === 0) {
|
|
112
|
+
console.log("Terminology guard passed.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.error("Archived-runtime terminology found in active files:");
|
|
117
|
+
for (const violation of violations) {
|
|
118
|
+
console.error(`- ${violation.relativePath}: ${violation.matchedLabels.join(", ")}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.error("\nAllowed exceptions:");
|
|
122
|
+
for (const [relativePath, reason] of allowedFiles.entries()) {
|
|
123
|
+
console.error(`- ${relativePath}: ${reason}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main().catch((error) => {
|
|
130
|
+
console.error(error);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|