@akira-tl/forgerelay 1.2.3 → 1.2.5
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/CHANGELOG.md +27 -0
- package/README.md +19 -11
- package/capabilities/workspace/managed-worktrees/GUIDE.md +4 -2
- package/dist/cli/config/migrate.js +5 -2
- package/dist/cli/init/setup-config.js +0 -2
- package/dist/cli/setup-support.js +3 -2
- package/dist/mcp/server/core/schemas.js +6 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +4 -2
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +6 -1
- package/dist/mcp/server/workspace/runtime/workspace-open.js +19 -13
- package/dist/runtime/config/definition/general-config.js +4 -3
- package/dist/runtime/config/external-mcp-config.js +4 -3
- package/dist/runtime/config/user-config.js +4 -1
- package/dist/runtime/config/validation/ports.js +9 -0
- package/dist/runtime/logging/logger.js +19 -19
- package/dist/ui/.vite/manifest.json +276 -276
- package/dist/ui/activity-panel-app.html +2 -2
- package/dist/ui/assets/activity-panel-app-Dq2xLIrs.css +1 -0
- package/dist/ui/assets/{activity-panel-app-BeDiwd7D.js → activity-panel-app-hkM8rX3z.js} +1 -1
- package/dist/ui/assets/{angular-html-DuoCE7nn.js → angular-html-UD0GEFFl.js} +1 -1
- package/dist/ui/assets/{angular-ts-D5tGk0hL.js → angular-ts-B2awh-Q1.js} +1 -1
- package/dist/ui/assets/{apl-lciOTPbw.js → apl-CpbhnJc8.js} +1 -1
- package/dist/ui/assets/{astro-vJbqfNnJ.js → astro-CkVVuF5B.js} +1 -1
- package/dist/ui/assets/{blade-C7655Q4K.js → blade-czG58aop.js} +1 -1
- package/dist/ui/assets/{c-DdHp1Jrw.js → c-DSq6h06b.js} +1 -1
- package/dist/ui/assets/{cobol-DgvzQxZP.js → cobol-CYnmhT2E.js} +1 -1
- package/dist/ui/assets/{coffee-BIjVUOUd.js → coffee-ClLhNqd5.js} +1 -1
- package/dist/ui/assets/{cpp-CU8tGzvI.js → cpp-BZDC6U43.js} +1 -1
- package/dist/ui/assets/{crystal-CUQAwsg9.js → crystal-BN9jCJyU.js} +1 -1
- package/dist/ui/assets/{css-DtMTqD3F.js → css-8dMfiihi.js} +1 -1
- package/dist/ui/assets/{edge-DoCX9RnL.js → edge-DemPBRNz.js} +1 -1
- package/dist/ui/assets/{elixir-DJxV9_U6.js → elixir-bLdHW8dz.js} +1 -1
- package/dist/ui/assets/{elm-DIgWqK4r.js → elm-DlZPcXmu.js} +1 -1
- package/dist/ui/assets/{erb-BHxP0c_e.js → erb-wmTqbHwm.js} +1 -1
- package/dist/ui/assets/{git-rebase-HbI_Vo8d.js → git-rebase-BqrZJVYO.js} +1 -1
- package/dist/ui/assets/{glimmer-js-CthY4Ty_.js → glimmer-js-CkPzdNHI.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-B2-_A1Xz.js → glimmer-ts-Bd0vTdJC.js} +1 -1
- package/dist/ui/assets/{glsl-D9V9gq98.js → glsl-DKK0Kibz.js} +1 -1
- package/dist/ui/assets/{graphql-CddeAaS3.js → graphql-DmFr8ZvB.js} +1 -1
- package/dist/ui/assets/{hack-D_loGkOT.js → hack-BQcuk9lx.js} +1 -1
- package/dist/ui/assets/{haml-BoWDczd0.js → haml-Dbx0cG5B.js} +1 -1
- package/dist/ui/assets/{handlebars-ZoSOvw9y.js → handlebars-D0nD8yLi.js} +1 -1
- package/dist/ui/assets/{heavy-payload-DO34LwoZ.js → heavy-payload-CPOM8G8z.js} +3 -3
- package/dist/ui/assets/{html-CE-obXVo.js → html-9xkoIci1.js} +1 -1
- package/dist/ui/assets/{html-derivative-Cd-smAbW.js → html-derivative-Cg7n4YW0.js} +1 -1
- package/dist/ui/assets/{http-CZrkbrSl.js → http-Ckt_4_pP.js} +1 -1
- package/dist/ui/assets/{hurl-CxTuxBt1.js → hurl-BxMgqn9u.js} +1 -1
- package/dist/ui/assets/{java-BshLjCat.js → java-DxijgO-5.js} +1 -1
- package/dist/ui/assets/{javascript-D2pxQTiX.js → javascript-CusLzlRb.js} +1 -1
- package/dist/ui/assets/{jinja-kbfPP7-F.js → jinja-DBTRfImq.js} +1 -1
- package/dist/ui/assets/{jison-C2mt9l1M.js → jison-oCbVte0g.js} +1 -1
- package/dist/ui/assets/{json-DU_rg_AU.js → json-uq2yfoO2.js} +1 -1
- package/dist/ui/assets/{jsx-D_8aQMlx.js → jsx-B3AfJWyQ.js} +1 -1
- package/dist/ui/assets/{julia-BmefTaVV.js → julia-BFvq-UTm.js} +1 -1
- package/dist/ui/assets/{just-DXV8vk1S.js → just-Ck0W9Rcj.js} +1 -1
- package/dist/ui/assets/{latex-Cjm2MvGW.js → latex-BjKhTmaO.js} +1 -1
- package/dist/ui/assets/{liquid-DyQDlo0u.js → liquid-CsaE0q4b.js} +1 -1
- package/dist/ui/assets/{lua-B2A1vzzy.js → lua-D0MSBhv0.js} +1 -1
- package/dist/ui/assets/{marko-B9vOkXOU.js → marko-CTLxSPPr.js} +1 -1
- package/dist/ui/assets/{mdc-D42m6spU.js → mdc-BwDM_Bct.js} +1 -1
- package/dist/ui/assets/{nginx-TpfVxSUY.js → nginx-C5Q5rNaB.js} +1 -1
- package/dist/ui/assets/{nim-CJr5T1FK.js → nim-BjKHRt6k.js} +1 -1
- package/dist/ui/assets/{perl-cJZcsdm9.js → perl-BrABfniL.js} +1 -1
- package/dist/ui/assets/{php-CSZL5KRT.js → php-BWY__Khq.js} +1 -1
- package/dist/ui/assets/{pug-B1eMKzuA.js → pug-BD4PlwIK.js} +1 -1
- package/dist/ui/assets/{qml-Dl2_7zc9.js → qml-yQtdZNrK.js} +1 -1
- package/dist/ui/assets/{r-F0aaO9gG.js → r-D0tlixrf.js} +1 -1
- package/dist/ui/assets/{razor-BPnkSMCw.js → razor-CsmyuKJs.js} +1 -1
- package/dist/ui/assets/{regexp-BnGHMFbv.js → regexp-BXU4cTCD.js} +1 -1
- package/dist/ui/assets/{rst-B3CO7WYO.js → rst-CGlzAlrM.js} +1 -1
- package/dist/ui/assets/{ruby-B6VMzT1T.js → ruby-CRKDbfJj.js} +1 -1
- package/dist/ui/assets/{sas-Df2lHVuU.js → sas-CKDY6iPG.js} +1 -1
- package/dist/ui/assets/{scss-4XxI6sgu.js → scss-BCoXLSqD.js} +1 -1
- package/dist/ui/assets/{shellscript-DYASCN4r.js → shellscript-BVuRs_tF.js} +1 -1
- package/dist/ui/assets/{shellsession-Br6AGJO_.js → shellsession-alj1b7vN.js} +1 -1
- package/dist/ui/assets/{soy-p3ZNq1We.js → soy-BbQDrw9i.js} +1 -1
- package/dist/ui/assets/{sql-CAdv2OXL.js → sql-BdcqHaEA.js} +1 -1
- package/dist/ui/assets/{stata-Dx7yUbG_.js → stata-5IC5m_XB.js} +1 -1
- package/dist/ui/assets/{surrealql-6YsE8HQa.js → surrealql-BfVFLYzV.js} +1 -1
- package/dist/ui/assets/{svelte-YgzedwpD.js → svelte-B22aFi25.js} +1 -1
- package/dist/ui/assets/{templ-Bahqx3uD.js → templ-BRo7TcBX.js} +1 -1
- package/dist/ui/assets/{tex-o6fTQjkH.js → tex-CYhw-JbD.js} +1 -1
- package/dist/ui/assets/{ts-tags-BO-Co0Ju.js → ts-tags-C56aGOzq.js} +1 -1
- package/dist/ui/assets/{tsx-DtQe5AoH.js → tsx-IkFyQoro.js} +1 -1
- package/dist/ui/assets/{twig-BcUs6Kqc.js → twig-NkXaxf7l.js} +1 -1
- package/dist/ui/assets/{typescript-BPtM8h4u.js → typescript-AgbZakqy.js} +1 -1
- package/dist/ui/assets/{vue-5KkGUzJJ.js → vue-Cxz1Rk-f.js} +1 -1
- package/dist/ui/assets/{vue-html-Df_Xfsz9.js → vue-html-DWHLzP-w.js} +1 -1
- package/dist/ui/assets/{vue-vine-hUIe_u_Y.js → vue-vine-WR64TKte.js} +1 -1
- package/dist/ui/assets/{xml-C3Jy7as5.js → xml-CNdUn7jo.js} +1 -1
- package/dist/ui/assets/{xsl-BnF9OuFo.js → xsl-DLRRUZ98.js} +1 -1
- package/dist/ui/assets/{yaml-DyrFgCou.js → yaml-gltXViA8.js} +1 -1
- package/dist/workspace-colors.js +54 -0
- package/dist/workspaces/git/git-worktrees.js +53 -14
- package/dist/workspaces/inventory.js +2 -0
- package/dist/workspaces/presentation/workspace-presentation.js +13 -0
- package/dist/workspaces/relay/workspace-relay.js +3 -0
- package/dist/workspaces/sessions.js +28 -8
- package/dist/workspaces.js +9 -9
- package/docs/chatgpt-coding-workflow.md +16 -8
- package/docs/gotchas.md +2 -2
- package/package.json +2 -1
- package/schemas/v1/config.user.schema.json +2 -2
- package/scripts/ci/cmd-acceptance.mjs +3 -85
- package/scripts/ci/gates/acceptance-runtime.mjs +45 -0
- package/scripts/ci/gates/platform-tests.mjs +27 -17
- package/scripts/ci/gates/prepare-windows-shell-runtime.mjs +45 -0
- package/scripts/ci/gates/test-shard.mjs +29 -7
- package/scripts/ci/powershell51-acceptance.mjs +10 -91
- package/scripts/ci/pwsh-acceptance.mjs +7 -87
- package/scripts/ci/windows-product-acceptance.mjs +55 -0
- package/scripts/release/release-gate.test.mjs +121 -13
- package/scripts/release-parity.mjs +12 -1
- package/dist/ui/assets/activity-panel-app-DkfJzTZ3.css +0 -1
|
@@ -6,16 +6,13 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
-
import {
|
|
9
|
+
import { acceptanceRuntimeModuleUrl } from "./gates/acceptance-runtime.mjs";
|
|
10
10
|
|
|
11
11
|
if (process.platform !== "win32") {
|
|
12
12
|
console.log("cmd.exe packaged acceptance skipped outside Windows.");
|
|
13
13
|
process.exit(0);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const npmCli = process.env.npm_execpath;
|
|
17
|
-
if (!npmCli) throw new Error("cmd acceptance must run through npm so npm_execpath is available");
|
|
18
|
-
|
|
19
16
|
const root = await mkdtemp(join(tmpdir(), "forgerelay-cmd-acceptance-"));
|
|
20
17
|
try {
|
|
21
18
|
const cmd = resolveCmd();
|
|
@@ -27,7 +24,6 @@ try {
|
|
|
27
24
|
};
|
|
28
25
|
|
|
29
26
|
await exercisePtyLifecycle(runtime);
|
|
30
|
-
await exercisePackagedCmdShim(cmd);
|
|
31
27
|
|
|
32
28
|
console.log(`cmd.exe acceptance passed with ${cmd}.`);
|
|
33
29
|
} finally {
|
|
@@ -44,8 +40,8 @@ function resolveCmd() {
|
|
|
44
40
|
|
|
45
41
|
async function exercisePtyLifecycle(runtime) {
|
|
46
42
|
const [{ ProcessManager }, { BashOutputStore }] = await Promise.all([
|
|
47
|
-
import("
|
|
48
|
-
import("
|
|
43
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/mcp/process/process-sessions.js")),
|
|
44
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/activity/history/bash-output-store.js")),
|
|
49
45
|
]);
|
|
50
46
|
const durableStateDir = join(root, "durable-output-state");
|
|
51
47
|
const outputStore = new BashOutputStore(durableStateDir, {
|
|
@@ -180,84 +176,6 @@ async function exercisePtyLifecycle(runtime) {
|
|
|
180
176
|
}
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
async function exercisePackagedCmdShim(cmd) {
|
|
184
|
-
const artifactDir = join(root, "artifact");
|
|
185
|
-
const sharedPrefix = resolveAcceptancePrefix(process.cwd());
|
|
186
|
-
const prefix = sharedPrefix ?? join(root, "prefix");
|
|
187
|
-
const configDir = join(root, "config");
|
|
188
|
-
const stateDir = join(root, "state");
|
|
189
|
-
await Promise.all([
|
|
190
|
-
mkdir(artifactDir, { recursive: true }),
|
|
191
|
-
mkdir(prefix, { recursive: true }),
|
|
192
|
-
mkdir(configDir, { recursive: true }),
|
|
193
|
-
mkdir(stateDir, { recursive: true }),
|
|
194
|
-
]);
|
|
195
|
-
|
|
196
|
-
if (!sharedPrefix) {
|
|
197
|
-
const tarball = resolveAcceptanceTarball({ repoRoot: process.cwd(), artifactDir, npmCli });
|
|
198
|
-
runNpm(["install", "--global", "--prefix", prefix, tarball]);
|
|
199
|
-
} else {
|
|
200
|
-
console.log(`Using prepared acceptance install: ${prefix}`);
|
|
201
|
-
}
|
|
202
|
-
const shim = join(prefix, "forgerelay.cmd");
|
|
203
|
-
assert.ok(existsSync(shim), `npm did not create the cmd launcher shim: ${shim}`);
|
|
204
|
-
|
|
205
|
-
await writeFile(
|
|
206
|
-
join(configDir, "config.json"),
|
|
207
|
-
JSON.stringify({
|
|
208
|
-
host: "127.0.0.1",
|
|
209
|
-
port: 7678,
|
|
210
|
-
allowedRoots: [process.cwd()],
|
|
211
|
-
stateDir,
|
|
212
|
-
worktreeRoot: join(root, "worktrees"),
|
|
213
|
-
commandShell: {
|
|
214
|
-
mode: "follow-launcher",
|
|
215
|
-
family: "cmd",
|
|
216
|
-
executable: cmd,
|
|
217
|
-
},
|
|
218
|
-
shellInstructions: false,
|
|
219
|
-
}, null, 2),
|
|
220
|
-
"utf8",
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const launcherEnv = {
|
|
224
|
-
...process.env,
|
|
225
|
-
FORGERELAY_CONFIG_DIR: configDir,
|
|
226
|
-
FORGERELAY_OAUTH_OWNER_TOKEN: "cmd-acceptance-owner-token-that-is-long-enough",
|
|
227
|
-
};
|
|
228
|
-
delete launcherEnv.npm_lifecycle_event;
|
|
229
|
-
delete launcherEnv.FORGERELAY_COMMAND_SHELL;
|
|
230
|
-
|
|
231
|
-
const result = spawnSync(
|
|
232
|
-
cmd,
|
|
233
|
-
["/d", "/s", "/c", `"\"${shim}\" doctor"`],
|
|
234
|
-
{
|
|
235
|
-
cwd: process.cwd(),
|
|
236
|
-
env: launcherEnv,
|
|
237
|
-
encoding: "utf8",
|
|
238
|
-
windowsHide: true,
|
|
239
|
-
windowsVerbatimArguments: true,
|
|
240
|
-
},
|
|
241
|
-
);
|
|
242
|
-
if (result.error || result.status !== 0) {
|
|
243
|
-
throw new Error(`Packaged cmd launcher failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
244
|
-
}
|
|
245
|
-
assert.match(result.stdout ?? "", /Command shell: cmd \(.+; launcher\)/);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function runNpm(args) {
|
|
249
|
-
const result = spawnSync(process.execPath, [npmCli, ...args], {
|
|
250
|
-
cwd: process.cwd(),
|
|
251
|
-
env: process.env,
|
|
252
|
-
encoding: "utf8",
|
|
253
|
-
windowsHide: true,
|
|
254
|
-
});
|
|
255
|
-
if (result.error || result.status !== 0) {
|
|
256
|
-
throw new Error(`npm ${args.join(" ")} failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
257
|
-
}
|
|
258
|
-
return result;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
179
|
async function waitForFile(path, timeoutMs = 5_000) {
|
|
262
180
|
const deadline = Date.now() + timeoutMs;
|
|
263
181
|
while (Date.now() < deadline) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { resolveAcceptancePrefix } from "./acceptance-artifact.mjs";
|
|
5
|
+
|
|
6
|
+
const PACKAGE_PATH = ["@akira-tl", "forgerelay"];
|
|
7
|
+
|
|
8
|
+
export function resolveAcceptanceRuntimeRoot(repoRoot) {
|
|
9
|
+
const configuredRoot = process.env.FORGERELAY_ACCEPTANCE_RUNTIME_ROOT?.trim();
|
|
10
|
+
if (configuredRoot) {
|
|
11
|
+
const runtimeRoot = resolve(repoRoot, configuredRoot);
|
|
12
|
+
if (existsSync(join(runtimeRoot, "dist", "cli.js"))) return runtimeRoot;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const prefix = resolveAcceptancePrefix(repoRoot);
|
|
16
|
+
if (!prefix) {
|
|
17
|
+
if (configuredRoot) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Prepared acceptance runtime is missing dist/cli.js: ${resolve(repoRoot, configuredRoot)}. Run ci:prepare-windows-shell-runtime before shell acceptance.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return resolve(repoRoot);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const candidates = [
|
|
26
|
+
join(prefix, "node_modules", ...PACKAGE_PATH),
|
|
27
|
+
join(prefix, "lib", "node_modules", ...PACKAGE_PATH),
|
|
28
|
+
];
|
|
29
|
+
const installedRoot = candidates.find((candidate) => existsSync(join(candidate, "dist", "cli.js")));
|
|
30
|
+
if (!installedRoot) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Prepared acceptance install is missing dist/cli.js under npm prefix: ${prefix}. Run ci:prepare-windows-product before shell acceptance.`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return installedRoot;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function acceptanceRuntimeModuleUrl(repoRoot, relativePath) {
|
|
39
|
+
const runtimeRoot = resolveAcceptanceRuntimeRoot(repoRoot);
|
|
40
|
+
const path = join(runtimeRoot, relativePath);
|
|
41
|
+
if (!existsSync(path)) {
|
|
42
|
+
throw new Error(`Acceptance runtime module is missing: ${path}`);
|
|
43
|
+
}
|
|
44
|
+
return pathToFileURL(path).href;
|
|
45
|
+
}
|
|
@@ -5,24 +5,34 @@ import { spawnSync } from "node:child_process";
|
|
|
5
5
|
const npmCli = process.env.npm_execpath;
|
|
6
6
|
if (!npmCli) throw new Error("ci:platform-tests must run through npm so npm_execpath is available");
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
const groups = {
|
|
9
|
+
"runtime-process": [
|
|
10
|
+
"src/cli/mcp/diagnostics.test.ts",
|
|
11
|
+
"src/runtime/shell/command-shell-runtime.test.ts",
|
|
12
|
+
"src/runtime/state/lock/file-lock.test.ts",
|
|
13
|
+
"src/runtime/state/runtime-lease.test.ts",
|
|
14
|
+
"src/runtime/state/db/migrations.test.ts",
|
|
15
|
+
"src/mcp/process/process-platform.test.ts",
|
|
16
|
+
"src/mcp/process/process-sessions.test.ts",
|
|
17
|
+
],
|
|
18
|
+
"workspace-filesystem": [
|
|
19
|
+
"src/mcp/filesystem/roots.test.ts",
|
|
20
|
+
"src/mcp/filesystem/file-mutations.test.ts",
|
|
21
|
+
"src/workspaces/state/workspace-store.test.ts",
|
|
22
|
+
"src/workspaces.test.ts",
|
|
23
|
+
"src/workspaces/conversation-checkout.test.ts",
|
|
24
|
+
"src/workspaces/conversation-worktree.test.ts",
|
|
25
|
+
"src/workspaces/git/worktree-recovery.test.ts",
|
|
26
|
+
],
|
|
27
|
+
};
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
const shard = process.argv[2] ?? "all";
|
|
30
|
+
const tests = shard === "all" ? Object.values(groups).flat() : groups[shard];
|
|
31
|
+
if (!tests) {
|
|
32
|
+
throw new Error(`Unknown platform-test shard '${shard}'. Expected one of: all, ${Object.keys(groups).join(", ")}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`Platform-sensitive CI tests: ${process.platform}/${process.arch} shard=${shard} (${tests.length} files).`);
|
|
26
36
|
for (const path of tests) {
|
|
27
37
|
console.log(`\n== ${path} ==`);
|
|
28
38
|
const result = spawnSync(process.execPath, [npmCli, "exec", "--", "tsx", path], {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { existsSync, rmSync } from "node:fs";
|
|
6
|
+
import { mkdir } from "node:fs/promises";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { resolveAcceptanceTarball } from "./acceptance-artifact.mjs";
|
|
9
|
+
|
|
10
|
+
const npmCli = process.env.npm_execpath;
|
|
11
|
+
if (!npmCli) throw new Error("Windows shell runtime preparation must run through npm so npm_execpath is available");
|
|
12
|
+
|
|
13
|
+
const repoRoot = process.cwd();
|
|
14
|
+
const configuredRoot = process.env.FORGERELAY_ACCEPTANCE_RUNTIME_ROOT?.trim();
|
|
15
|
+
const runtimeRoot = resolve(repoRoot, configuredRoot || ".release-runtime");
|
|
16
|
+
rmSync(runtimeRoot, { recursive: true, force: true });
|
|
17
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const artifactDir = join(runtimeRoot, ".artifact-fallback");
|
|
20
|
+
await mkdir(artifactDir, { recursive: true });
|
|
21
|
+
const tarball = resolveAcceptanceTarball({ repoRoot, artifactDir, npmCli });
|
|
22
|
+
const tarExecutable = process.platform === "win32" ? "tar.exe" : "tar";
|
|
23
|
+
const unpack = spawnSync(
|
|
24
|
+
tarExecutable,
|
|
25
|
+
["-xzf", tarball, "-C", runtimeRoot, "--strip-components=1"],
|
|
26
|
+
{
|
|
27
|
+
cwd: repoRoot,
|
|
28
|
+
env: process.env,
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
shell: false,
|
|
32
|
+
timeout: 60_000,
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
if (unpack.error || unpack.status !== 0) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Acceptance runtime unpack failed: ${unpack.error?.message ?? unpack.stderr ?? unpack.stdout ?? unpack.status}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const expected of [join(runtimeRoot, "package.json"), join(runtimeRoot, "dist", "cli.js")]) {
|
|
42
|
+
assert.ok(existsSync(expected), `Unpacked acceptance runtime is missing: ${expected}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(`Prepared shell runtime directly from ${tarball} at ${runtimeRoot}.`);
|
|
@@ -8,7 +8,18 @@ const npmCli = process.env.npm_execpath;
|
|
|
8
8
|
if (!npmCli) throw new Error("ci:test-shard must run through npm so npm_execpath is available");
|
|
9
9
|
|
|
10
10
|
const shard = process.argv[2];
|
|
11
|
-
const allowed = new Set([
|
|
11
|
+
const allowed = new Set([
|
|
12
|
+
"runtime-config",
|
|
13
|
+
"workspace-relay-auth",
|
|
14
|
+
"workspace-relay",
|
|
15
|
+
"workspace-lifecycle",
|
|
16
|
+
"workspace-state",
|
|
17
|
+
"mcp-core",
|
|
18
|
+
"mcp-server-ui",
|
|
19
|
+
"lsp",
|
|
20
|
+
"subagent",
|
|
21
|
+
"ui-cli",
|
|
22
|
+
]);
|
|
12
23
|
if (!allowed.has(shard)) {
|
|
13
24
|
throw new Error(`Unknown CI test shard '${shard ?? ""}'. Expected one of: ${[...allowed].join(", ")}`);
|
|
14
25
|
}
|
|
@@ -26,18 +37,29 @@ for (const command of selected) runCommand(command);
|
|
|
26
37
|
console.log(`CI test shard ${shard} passed.`);
|
|
27
38
|
|
|
28
39
|
function classify(command) {
|
|
29
|
-
if (command === "npm run build:app") return "
|
|
40
|
+
if (command === "npm run build:app") return "mcp-server-ui";
|
|
30
41
|
if (command.includes("src/lsp/")) return "lsp";
|
|
42
|
+
if (command.includes("src/workspaces/relay/auth/")) return "workspace-relay-auth";
|
|
43
|
+
if (command.includes("src/workspaces/relay/tests/")) return "workspace-relay";
|
|
31
44
|
if (
|
|
32
|
-
command.includes("src/workspaces/")
|
|
45
|
+
command.includes("src/workspaces/conversation-")
|
|
46
|
+
|| command.includes("src/workspaces/git/")
|
|
33
47
|
|| command.includes("src/workspaces.test")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
) return "workspace-lifecycle";
|
|
49
|
+
if (command.includes("src/workspaces/") || command.includes("src/activity/")) {
|
|
50
|
+
return "workspace-state";
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
command.includes("src/mcp/server")
|
|
54
|
+
|| command.includes("src/mcp/panel/")
|
|
55
|
+
|| command.includes("src/mcp/process/server.test.ts")
|
|
56
|
+
) return "mcp-server-ui";
|
|
57
|
+
if (command.includes("src/mcp/")) return "mcp-core";
|
|
37
58
|
if (command.includes("src/runtime/") || command.startsWith("node --test scripts/")) {
|
|
38
59
|
return "runtime-config";
|
|
39
60
|
}
|
|
40
|
-
return "subagent
|
|
61
|
+
if (command.includes("src/subagents/")) return "subagent";
|
|
62
|
+
return "ui-cli";
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
function runCommand(command) {
|
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { mkdtemp,
|
|
5
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
-
import {
|
|
9
|
+
import { acceptanceRuntimeModuleUrl } from "./gates/acceptance-runtime.mjs";
|
|
10
10
|
|
|
11
11
|
if (process.platform !== "win32") {
|
|
12
12
|
console.log("Windows PowerShell 5.1 packaged acceptance skipped outside Windows.");
|
|
13
13
|
process.exit(0);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const npmCli = process.env.npm_execpath;
|
|
17
|
-
if (!npmCli) throw new Error("Windows PowerShell acceptance must run through npm so npm_execpath is available");
|
|
18
|
-
|
|
19
16
|
const root = await mkdtemp(join(tmpdir(), "forgerelay-powershell51-acceptance-"));
|
|
20
17
|
try {
|
|
21
18
|
const powershell = resolveWindowsPowerShell();
|
|
@@ -26,7 +23,9 @@ try {
|
|
|
26
23
|
assert.equal(major, 5, `Windows PowerShell acceptance requires major version 5; got ${version}`);
|
|
27
24
|
assert.equal(minor, 1, `Windows PowerShell acceptance requires version 5.1; got ${version}`);
|
|
28
25
|
|
|
29
|
-
const { resolveConfiguredCommandShellRuntime } = await import(
|
|
26
|
+
const { resolveConfiguredCommandShellRuntime } = await import(
|
|
27
|
+
acceptanceRuntimeModuleUrl(process.cwd(), "dist/runtime/shell/command-shell-runtime.js")
|
|
28
|
+
);
|
|
30
29
|
const resolvedRuntime = resolveConfiguredCommandShellRuntime({
|
|
31
30
|
mode: "pinned",
|
|
32
31
|
family: "powershell",
|
|
@@ -41,7 +40,6 @@ try {
|
|
|
41
40
|
|
|
42
41
|
await exerciseAgentRuntime(resolvedRuntime);
|
|
43
42
|
await exerciseHookRuntime(resolvedRuntime);
|
|
44
|
-
await exercisePackagedPowerShellShim(powershell, version);
|
|
45
43
|
|
|
46
44
|
console.log(`Windows PowerShell 5.1 acceptance passed with ${powershell} (${version}).`);
|
|
47
45
|
} finally {
|
|
@@ -77,8 +75,8 @@ function powerShellVersion(executable) {
|
|
|
77
75
|
|
|
78
76
|
async function exerciseAgentRuntime(runtime) {
|
|
79
77
|
const [{ ProcessManager }, { BashOutputStore }] = await Promise.all([
|
|
80
|
-
import("
|
|
81
|
-
import("
|
|
78
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/mcp/process/process-sessions.js")),
|
|
79
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/activity/history/bash-output-store.js")),
|
|
82
80
|
]);
|
|
83
81
|
const durableStateDir = join(root, "durable-output-state");
|
|
84
82
|
const outputStore = new BashOutputStore(durableStateDir, {
|
|
@@ -320,7 +318,9 @@ async function exercisePtyLifecycle(manager, outputStore, node) {
|
|
|
320
318
|
}
|
|
321
319
|
|
|
322
320
|
async function exerciseHookRuntime(runtime) {
|
|
323
|
-
const { HookRunner, parseHookConfig } = await import(
|
|
321
|
+
const { HookRunner, parseHookConfig } = await import(
|
|
322
|
+
acceptanceRuntimeModuleUrl(process.cwd(), "dist/mcp/hooks/hooks.js"),
|
|
323
|
+
);
|
|
324
324
|
const logging = {
|
|
325
325
|
level: "silent",
|
|
326
326
|
format: "json",
|
|
@@ -361,87 +361,6 @@ async function exerciseHookRuntime(runtime) {
|
|
|
361
361
|
);
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
async function exercisePackagedPowerShellShim(powershell, expectedVersion) {
|
|
365
|
-
const artifactDir = join(root, "artifact");
|
|
366
|
-
const sharedPrefix = resolveAcceptancePrefix(process.cwd());
|
|
367
|
-
const prefix = sharedPrefix ?? join(root, "prefix");
|
|
368
|
-
const configDir = join(root, "config");
|
|
369
|
-
const stateDir = join(root, "state");
|
|
370
|
-
await Promise.all([
|
|
371
|
-
mkdir(artifactDir, { recursive: true }),
|
|
372
|
-
mkdir(prefix, { recursive: true }),
|
|
373
|
-
mkdir(configDir, { recursive: true }),
|
|
374
|
-
mkdir(stateDir, { recursive: true }),
|
|
375
|
-
]);
|
|
376
|
-
|
|
377
|
-
if (!sharedPrefix) {
|
|
378
|
-
const tarball = resolveAcceptanceTarball({ repoRoot: process.cwd(), artifactDir, npmCli });
|
|
379
|
-
runNpm(["install", "--global", "--prefix", prefix, tarball]);
|
|
380
|
-
} else {
|
|
381
|
-
console.log(`Using prepared acceptance install: ${prefix}`);
|
|
382
|
-
}
|
|
383
|
-
const shim = join(prefix, "forgerelay.ps1");
|
|
384
|
-
assert.ok(existsSync(shim), `npm did not create the PowerShell launcher shim: ${shim}`);
|
|
385
|
-
|
|
386
|
-
await writeFile(
|
|
387
|
-
join(configDir, "config.json"),
|
|
388
|
-
JSON.stringify({
|
|
389
|
-
host: "127.0.0.1",
|
|
390
|
-
port: 7678,
|
|
391
|
-
allowedRoots: [process.cwd()],
|
|
392
|
-
stateDir,
|
|
393
|
-
worktreeRoot: join(root, "worktrees"),
|
|
394
|
-
commandShell: {
|
|
395
|
-
mode: "follow-launcher",
|
|
396
|
-
family: "powershell",
|
|
397
|
-
executable: powershell,
|
|
398
|
-
},
|
|
399
|
-
shellInstructions: false,
|
|
400
|
-
}, null, 2),
|
|
401
|
-
"utf8",
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
const launcherEnv = {
|
|
405
|
-
...process.env,
|
|
406
|
-
FORGERELAY_CONFIG_DIR: configDir,
|
|
407
|
-
FORGERELAY_OAUTH_OWNER_TOKEN: "powershell51-acceptance-owner-token-long-enough",
|
|
408
|
-
};
|
|
409
|
-
delete launcherEnv.npm_lifecycle_event;
|
|
410
|
-
delete launcherEnv.FORGERELAY_COMMAND_SHELL;
|
|
411
|
-
|
|
412
|
-
const result = spawnSync(
|
|
413
|
-
powershell,
|
|
414
|
-
["-NoLogo", "-NoProfile", "-NonInteractive", "-File", shim, "doctor"],
|
|
415
|
-
{
|
|
416
|
-
cwd: process.cwd(),
|
|
417
|
-
env: launcherEnv,
|
|
418
|
-
encoding: "utf8",
|
|
419
|
-
windowsHide: true,
|
|
420
|
-
},
|
|
421
|
-
);
|
|
422
|
-
if (result.error || result.status !== 0) {
|
|
423
|
-
throw new Error(`Packaged Windows PowerShell launcher failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
424
|
-
}
|
|
425
|
-
assert.match(
|
|
426
|
-
result.stdout ?? "",
|
|
427
|
-
new RegExp(`Command shell: powershell ${escapeRegExp(expectedVersion)} \\(.+; launcher\\)`),
|
|
428
|
-
);
|
|
429
|
-
assert.doesNotMatch(result.stdout ?? "", /Command shell: pwsh/);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function runNpm(args) {
|
|
433
|
-
const result = spawnSync(process.execPath, [npmCli, ...args], {
|
|
434
|
-
cwd: process.cwd(),
|
|
435
|
-
env: process.env,
|
|
436
|
-
encoding: "utf8",
|
|
437
|
-
windowsHide: true,
|
|
438
|
-
});
|
|
439
|
-
if (result.error || result.status !== 0) {
|
|
440
|
-
throw new Error(`npm ${args.join(" ")} failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
441
|
-
}
|
|
442
|
-
return result;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
364
|
async function waitForFile(path, timeoutMs = 5_000) {
|
|
446
365
|
const deadline = Date.now() + timeoutMs;
|
|
447
366
|
while (Date.now() < deadline) {
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
|
-
import {
|
|
5
|
-
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
6
5
|
import { tmpdir } from "node:os";
|
|
7
6
|
import { join } from "node:path";
|
|
8
7
|
import { spawnSync } from "node:child_process";
|
|
9
|
-
import {
|
|
8
|
+
import { acceptanceRuntimeModuleUrl } from "./gates/acceptance-runtime.mjs";
|
|
10
9
|
|
|
11
10
|
if (process.platform !== "win32") {
|
|
12
11
|
console.log("PowerShell 7 packaged acceptance skipped outside Windows.");
|
|
13
12
|
process.exit(0);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const npmCli = process.env.npm_execpath;
|
|
17
|
-
if (!npmCli) throw new Error("pwsh acceptance must run through npm so npm_execpath is available");
|
|
18
|
-
|
|
19
15
|
const root = await mkdtemp(join(tmpdir(), "forgerelay-pwsh-acceptance-"));
|
|
20
16
|
try {
|
|
21
17
|
const pwsh = resolvePowerShell7();
|
|
@@ -38,7 +34,6 @@ try {
|
|
|
38
34
|
|
|
39
35
|
await exerciseAgentRuntime(runtime);
|
|
40
36
|
await exerciseHookRuntime(runtime);
|
|
41
|
-
await exercisePackagedPowerShellShim(pwsh, version);
|
|
42
37
|
|
|
43
38
|
console.log(`PowerShell 7 acceptance passed with ${pwsh} (${version}).`);
|
|
44
39
|
} finally {
|
|
@@ -74,8 +69,8 @@ function powerShellVersion(executable) {
|
|
|
74
69
|
|
|
75
70
|
async function exerciseAgentRuntime(runtime) {
|
|
76
71
|
const [{ ProcessManager }, { BashOutputStore }] = await Promise.all([
|
|
77
|
-
import("
|
|
78
|
-
import("
|
|
72
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/mcp/process/process-sessions.js")),
|
|
73
|
+
import(acceptanceRuntimeModuleUrl(process.cwd(), "dist/activity/history/bash-output-store.js")),
|
|
79
74
|
]);
|
|
80
75
|
const durableStateDir = join(root, "durable-output-state");
|
|
81
76
|
const outputStore = new BashOutputStore(durableStateDir, {
|
|
@@ -301,7 +296,9 @@ async function exercisePtyLifecycle(manager, outputStore, node) {
|
|
|
301
296
|
}
|
|
302
297
|
|
|
303
298
|
async function exerciseHookRuntime(runtime) {
|
|
304
|
-
const { HookRunner, parseHookConfig } = await import(
|
|
299
|
+
const { HookRunner, parseHookConfig } = await import(
|
|
300
|
+
acceptanceRuntimeModuleUrl(process.cwd(), "dist/mcp/hooks/hooks.js"),
|
|
301
|
+
);
|
|
305
302
|
const logging = {
|
|
306
303
|
level: "silent",
|
|
307
304
|
format: "json",
|
|
@@ -337,83 +334,6 @@ async function exerciseHookRuntime(runtime) {
|
|
|
337
334
|
);
|
|
338
335
|
}
|
|
339
336
|
|
|
340
|
-
async function exercisePackagedPowerShellShim(pwsh, expectedVersion) {
|
|
341
|
-
const artifactDir = join(root, "artifact");
|
|
342
|
-
const sharedPrefix = resolveAcceptancePrefix(process.cwd());
|
|
343
|
-
const prefix = sharedPrefix ?? join(root, "prefix");
|
|
344
|
-
const configDir = join(root, "config");
|
|
345
|
-
const stateDir = join(root, "state");
|
|
346
|
-
await Promise.all([
|
|
347
|
-
mkdir(artifactDir, { recursive: true }),
|
|
348
|
-
mkdir(prefix, { recursive: true }),
|
|
349
|
-
mkdir(configDir, { recursive: true }),
|
|
350
|
-
mkdir(stateDir, { recursive: true }),
|
|
351
|
-
]);
|
|
352
|
-
|
|
353
|
-
if (!sharedPrefix) {
|
|
354
|
-
const tarball = resolveAcceptanceTarball({ repoRoot: process.cwd(), artifactDir, npmCli });
|
|
355
|
-
runNpm(["install", "--global", "--prefix", prefix, tarball]);
|
|
356
|
-
} else {
|
|
357
|
-
console.log(`Using prepared acceptance install: ${prefix}`);
|
|
358
|
-
}
|
|
359
|
-
const shim = join(prefix, "forgerelay.ps1");
|
|
360
|
-
assert.ok(existsSync(shim), `npm did not create the PowerShell launcher shim: ${shim}`);
|
|
361
|
-
|
|
362
|
-
await writeFile(
|
|
363
|
-
join(configDir, "config.json"),
|
|
364
|
-
JSON.stringify({
|
|
365
|
-
host: "127.0.0.1",
|
|
366
|
-
port: 7678,
|
|
367
|
-
allowedRoots: [process.cwd()],
|
|
368
|
-
stateDir,
|
|
369
|
-
worktreeRoot: join(root, "worktrees"),
|
|
370
|
-
commandShell: {
|
|
371
|
-
mode: "follow-launcher",
|
|
372
|
-
family: "pwsh",
|
|
373
|
-
executable: pwsh,
|
|
374
|
-
},
|
|
375
|
-
shellInstructions: false,
|
|
376
|
-
}, null, 2),
|
|
377
|
-
"utf8",
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
const launcherEnv = {
|
|
381
|
-
...process.env,
|
|
382
|
-
FORGERELAY_CONFIG_DIR: configDir,
|
|
383
|
-
FORGERELAY_OAUTH_OWNER_TOKEN: "pwsh-acceptance-owner-token-that-is-long-enough",
|
|
384
|
-
};
|
|
385
|
-
delete launcherEnv.npm_lifecycle_event;
|
|
386
|
-
delete launcherEnv.FORGERELAY_COMMAND_SHELL;
|
|
387
|
-
|
|
388
|
-
const result = spawnSync(
|
|
389
|
-
pwsh,
|
|
390
|
-
["-NoLogo", "-NoProfile", "-NonInteractive", "-File", shim, "doctor"],
|
|
391
|
-
{
|
|
392
|
-
cwd: process.cwd(),
|
|
393
|
-
env: launcherEnv,
|
|
394
|
-
encoding: "utf8",
|
|
395
|
-
windowsHide: true,
|
|
396
|
-
},
|
|
397
|
-
);
|
|
398
|
-
if (result.error || result.status !== 0) {
|
|
399
|
-
throw new Error(`Packaged PowerShell launcher failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
400
|
-
}
|
|
401
|
-
assert.match(result.stdout ?? "", new RegExp(`Command shell: pwsh ${escapeRegExp(expectedVersion)} \\(.+; launcher\\)`));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function runNpm(args) {
|
|
405
|
-
const result = spawnSync(process.execPath, [npmCli, ...args], {
|
|
406
|
-
cwd: process.cwd(),
|
|
407
|
-
env: process.env,
|
|
408
|
-
encoding: "utf8",
|
|
409
|
-
windowsHide: true,
|
|
410
|
-
});
|
|
411
|
-
if (result.error || result.status !== 0) {
|
|
412
|
-
throw new Error(`npm ${args.join(" ")} failed: ${result.error?.message ?? result.stderr ?? result.status}`);
|
|
413
|
-
}
|
|
414
|
-
return result;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
337
|
async function waitForFile(path, timeoutMs = 5_000) {
|
|
418
338
|
const deadline = Date.now() + timeoutMs;
|
|
419
339
|
while (Date.now() < deadline) {
|