@minhspark/codex-mcp-bridge 1.19.6 → 1.20.0
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/.codex-plugin/plugin.json +26 -0
- package/.mcp.json +7 -0
- package/CHANGELOG.md +16 -0
- package/README.md +43 -1
- package/assets/plugin-icon.svg +5 -0
- package/mcp.json +9 -0
- package/package.json +10 -3
- package/plugin.json +33 -0
- package/scripts/install-native-relay.mjs +1 -1
- package/scripts/publish-repo-analytics.mjs +74 -26
- package/scripts/sync-version.mjs +7 -0
- package/skills/codex-bridge/SKILL.md +19 -0
- package/src/claude-bridge.mjs +54 -17
- package/src/claude-composer-submit.mjs +102 -0
- package/src/claude-composer-submit.ps1 +211 -0
- package/src/claude-session-creation.mjs +53 -7
- package/src/claude-session-router.mjs +31 -0
- package/src/index.mjs +1 -1
- package/src/native-relay-companion.mjs +1 -1
- package/src/platform.mjs +5 -3
- package/src/runtime-state.mjs +1 -1
- package/src/sites-connector.mjs +410 -0
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
const REQUEST_ID = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32,128})$/i;
|
|
5
|
-
const PENDING = new Set(["awaiting_user", "launch_uncertain", "ambiguous", "awaiting_project_confirmation"]);
|
|
5
|
+
const PENDING = new Set(["awaiting_user", "awaiting_session", "submission_uncertain", "launch_uncertain", "ambiguous", "awaiting_project_confirmation"]);
|
|
6
6
|
const STATUSES = new Set([...PENDING, "created", "abandoned"]);
|
|
7
7
|
const MAX_REQUESTS = 32;
|
|
8
8
|
const MAX_TASKS = 8192;
|
|
@@ -35,6 +35,8 @@ function augmentedPrompt(prompt, requestId) {
|
|
|
35
35
|
|
|
36
36
|
function matchesInitialMessage(text, expected) {
|
|
37
37
|
if (text === expected) return true;
|
|
38
|
+
const normalized = (value) => typeof value === "string" ? value.replace(/\r\n/g, "\n").split("\n").filter((line) => line !== "").join("\n") : null;
|
|
39
|
+
if (normalized(text) === normalized(expected)) return true;
|
|
38
40
|
if (typeof text !== "string" || !/^<system-reminder>\r?\nThe user started this session without choosing a project folder,/.test(text)) return false;
|
|
39
41
|
const closing = text.indexOf("</system-reminder>");
|
|
40
42
|
if (closing < 0 || closing > 32768) return false;
|
|
@@ -42,7 +44,7 @@ function matchesInitialMessage(text, expected) {
|
|
|
42
44
|
if (body.includes("<system-reminder") || body.includes("</system-reminder")) return false;
|
|
43
45
|
const suffix = text.slice(closing + "</system-reminder>".length);
|
|
44
46
|
const separator = /^\r?\n\r?\n/.exec(suffix);
|
|
45
|
-
return separator !== null && suffix.slice(separator[0].length) === expected;
|
|
47
|
+
return separator !== null && normalized(suffix.slice(separator[0].length)) === normalized(expected);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function receipt(request) {
|
|
@@ -50,7 +52,7 @@ function receipt(request) {
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export class ClaudeSessionCreation {
|
|
53
|
-
constructor({ open, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath = (directory) => {
|
|
55
|
+
constructor({ open, submit, trust, settle = async () => {}, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath = (directory) => {
|
|
54
56
|
const canonical = fs.realpathSync.native(directory);
|
|
55
57
|
if (!fs.statSync(canonical).isDirectory()) throw new Error("Not a directory");
|
|
56
58
|
return canonical;
|
|
@@ -58,7 +60,7 @@ export class ClaudeSessionCreation {
|
|
|
58
60
|
for (const [name, dependency] of Object.entries({ open, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath })) {
|
|
59
61
|
if (typeof dependency !== "function") throw new TypeError(`${name} must be a function`);
|
|
60
62
|
}
|
|
61
|
-
Object.assign(this, { open, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath, platform });
|
|
63
|
+
Object.assign(this, { open, submit, trust, settle, listTasks, listSessions, readContext, readTranscript, readAccount, assertProcess, realpath, platform });
|
|
62
64
|
this.requests = new Map();
|
|
63
65
|
this.queue = Promise.resolve();
|
|
64
66
|
}
|
|
@@ -89,7 +91,7 @@ export class ClaudeSessionCreation {
|
|
|
89
91
|
return pending;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
async startLocked({ requestId, cwd, prompt, account, senderThreadId, beforeOpen }) {
|
|
94
|
+
async startLocked({ requestId, cwd, prompt, account, senderThreadId, beforeOpen, autoSubmit = false, trustProject = false }) {
|
|
93
95
|
if (!REQUEST_ID.test(requestId ?? "") || typeof requestId !== "string") fail("CLAUDE_CREATION_REQUEST_INVALID", "requestId must be a UUID or 32–128 hexadecimal characters.");
|
|
94
96
|
if (!string(senderThreadId, 256)) fail("CLAUDE_CREATION_SENDER_INVALID", "A verified sender thread identity is required.");
|
|
95
97
|
const identity = await this.checkAccount(account);
|
|
@@ -100,11 +102,17 @@ export class ClaudeSessionCreation {
|
|
|
100
102
|
if (!sameAccount(identity, prior.account) || prior.senderThreadId !== senderThreadId || !this.sameCwd(prior.cwd, canonical) || prior.prompt !== prompt) {
|
|
101
103
|
fail("CLAUDE_CREATION_REQUEST_CONFLICT", "This requestId is already bound to a different creation payload.");
|
|
102
104
|
}
|
|
105
|
+
if (autoSubmit && !prior.submissionAttempted && ["awaiting_user", "launch_uncertain"].includes(prior.status)) {
|
|
106
|
+
const observed = await this.inspect(requestId, { account, senderThreadId });
|
|
107
|
+
if (observed.promptSubmitted) return observed;
|
|
108
|
+
return this.completeRequest(prior, { account, senderThreadId, beforeOpen, trustProject });
|
|
109
|
+
}
|
|
103
110
|
return receipt(prior);
|
|
104
111
|
}
|
|
105
112
|
if (this.requests.size >= MAX_REQUESTS) fail("CLAUDE_CREATION_LIMIT", "The creation receipt limit has been reached; existing requests remain available for inspection.");
|
|
106
113
|
for (const existing of this.requests.values()) {
|
|
107
|
-
|
|
114
|
+
const composerReleased = existing.status === "awaiting_project_confirmation" && existing.submissionObserved === true;
|
|
115
|
+
if (sameAccount(existing.account, identity) && PENDING.has(existing.status) && !composerReleased) fail("CLAUDE_CREATION_PENDING", "A creation request is already pending for this account; inspect or explicitly abandon that request before opening another project in the shared composer.");
|
|
108
116
|
}
|
|
109
117
|
const tasks = await this.listTasks(account);
|
|
110
118
|
if (!Array.isArray(tasks) || tasks.length > MAX_TASKS || tasks.some((task) => !string(task?.taskId, 256) || (task.fileTaskId !== undefined && task.fileTaskId !== task.taskId))) fail("CLAUDE_CREATION_TASKS_INVALID", "The existing native task baseline cannot be verified.");
|
|
@@ -114,6 +122,8 @@ export class ClaudeSessionCreation {
|
|
|
114
122
|
try { if (this.sameCwd(await this.canonical(task.cwd), canonical)) existingProject = true; } catch {}
|
|
115
123
|
}
|
|
116
124
|
if (!existingProject) fail("CLAUDE_CREATION_PROJECT_NOT_FOUND", "The canonical directory must already belong to a nonarchived Claude Desktop task.");
|
|
125
|
+
if (autoSubmit && typeof this.submit !== "function") fail("CLAUDE_CREATION_SUBMIT_UNAVAILABLE", "Native composer submission is not available.");
|
|
126
|
+
if (trustProject && typeof this.trust !== "function") fail("CLAUDE_CREATION_TRUST_UNAVAILABLE", "Authorized project trust preparation is not available.");
|
|
117
127
|
const url = `claude://code/new?q=${encodeURIComponent(initialMessage)}&folder=${encodeURIComponent(canonical)}`;
|
|
118
128
|
if (url.length > 30000) fail("CLAUDE_CREATION_URL_TOO_LONG", "The encoded Claude Desktop link exceeds 30000 characters.");
|
|
119
129
|
await this.checkAccount(account, identity);
|
|
@@ -125,14 +135,49 @@ export class ClaudeSessionCreation {
|
|
|
125
135
|
this.requests.set(requestId, request);
|
|
126
136
|
try {
|
|
127
137
|
await this.open(url);
|
|
138
|
+
if (autoSubmit) await this.settle();
|
|
128
139
|
request.status = "awaiting_user";
|
|
129
140
|
request.reason = "Claude Desktop was asked to prefill a new session. Confirm the folder if prompted and press Send; no prompt has been submitted by the bridge.";
|
|
130
141
|
} catch {
|
|
131
142
|
request.reason = "The Desktop launcher failed or returned an uncertain outcome. This request will not be launched again; inspect it to check whether the session was created.";
|
|
132
143
|
}
|
|
144
|
+
if (request.status === "awaiting_user" && autoSubmit) {
|
|
145
|
+
return this.completeRequest(request, { account, senderThreadId, beforeOpen, trustProject });
|
|
146
|
+
}
|
|
133
147
|
return receipt(request);
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
async completeRequest(request, { account, senderThreadId, beforeOpen, trustProject }) {
|
|
151
|
+
if (typeof this.submit !== "function") fail("CLAUDE_CREATION_SUBMIT_UNAVAILABLE", "Native composer submission is not available.");
|
|
152
|
+
if (trustProject && typeof this.trust !== "function") fail("CLAUDE_CREATION_TRUST_UNAVAILABLE", "Authorized project trust preparation is not available.");
|
|
153
|
+
await this.checkAccount(account, request.account);
|
|
154
|
+
if (beforeOpen) await beforeOpen();
|
|
155
|
+
if (trustProject) {
|
|
156
|
+
const trusted = await this.trust({ cwd: request.cwd, prompt: request.initialMessage });
|
|
157
|
+
if (!["trusted", "absent"].includes(trusted?.status)) {
|
|
158
|
+
request.status = "awaiting_user";
|
|
159
|
+
request.reason = trusted?.reason ?? "Native workspace trust is not confirmed; inspect the exact project composer.";
|
|
160
|
+
return receipt(request);
|
|
161
|
+
}
|
|
162
|
+
if (trusted.status === "trusted") await this.settle();
|
|
163
|
+
}
|
|
164
|
+
await this.checkAccount(account, request.account);
|
|
165
|
+
if (beforeOpen) await beforeOpen();
|
|
166
|
+
request.submissionAttempted = true;
|
|
167
|
+
request.status = "submission_uncertain";
|
|
168
|
+
request.reason = "Native submission was attempted; inspect this request before retrying. The bridge will not press Send again.";
|
|
169
|
+
try {
|
|
170
|
+
const submission = await this.submit({ prompt: request.initialMessage, cwd: request.cwd, beforeSubmit: async () => {
|
|
171
|
+
await this.checkAccount(account, request.account);
|
|
172
|
+
if (beforeOpen) await beforeOpen();
|
|
173
|
+
} });
|
|
174
|
+
if (submission?.status === "blocked" && submission.submissionAttempted === false) delete request.submissionAttempted;
|
|
175
|
+
request.status = submission?.status === "submitted" ? "awaiting_session" : submission?.status === "blocked" ? "awaiting_user" : "submission_uncertain";
|
|
176
|
+
request.reason = submission?.reason ?? "Inspect the original native composer and creation receipt; submission has not been verified.";
|
|
177
|
+
} catch {}
|
|
178
|
+
return this.inspect(request.requestId, { account, senderThreadId });
|
|
179
|
+
}
|
|
180
|
+
|
|
136
181
|
async inspect(requestId, { account, senderThreadId }) {
|
|
137
182
|
const request = this.requests.get(requestId);
|
|
138
183
|
if (!request) fail("CLAUDE_CREATION_REQUEST_NOT_FOUND", "No retained creation request has this requestId.");
|
|
@@ -206,10 +251,11 @@ export class ClaudeSessionCreation {
|
|
|
206
251
|
const hasSession = ["created", "awaiting_project_confirmation"].includes(request.status) || request.sessionId !== undefined;
|
|
207
252
|
if (hasSession && (!string(request.sessionId, 256) || !string(request.taskId, 256) || request.baseline.includes(request.taskId) || (request.title !== null && !string(request.title, 4096)))) fail("CLAUDE_CREATION_STATE_INVALID", "A submitted creation receipt is invalid.");
|
|
208
253
|
if (request.submissionObserved !== undefined && typeof request.submissionObserved !== "boolean") fail("CLAUDE_CREATION_STATE_INVALID", "A retained submission marker is invalid.");
|
|
254
|
+
if (request.submissionAttempted !== undefined && typeof request.submissionAttempted !== "boolean") fail("CLAUDE_CREATION_STATE_INVALID", "A retained submission attempt is invalid.");
|
|
209
255
|
if ((request.status === "awaiting_project_confirmation" || request.observedCwd !== undefined) && (!string(request.observedCwd, 8192) || !paths.isAbsolute(request.observedCwd))) fail("CLAUDE_CREATION_STATE_INVALID", "A retained observed directory is invalid.");
|
|
210
256
|
if (request.status === "awaiting_project_confirmation" && (request.submissionObserved !== true || this.sameCwd(request.cwd, request.observedCwd))) fail("CLAUDE_CREATION_STATE_INVALID", "A pending project confirmation is invalid.");
|
|
211
257
|
if (request.ambiguitySeen !== undefined && typeof request.ambiguitySeen !== "boolean") fail("CLAUDE_CREATION_STATE_INVALID", "A retained ambiguity marker is invalid.");
|
|
212
|
-
restored.set(request.requestId, structuredClone({ requestId: request.requestId, cwd: request.cwd, prompt: request.prompt, initialMessage: request.initialMessage, account: { fingerprint: request.account.fingerprint, root: request.account.root }, senderThreadId: request.senderThreadId, baseline: request.baseline, status: request.status, reason: request.reason, ...(request.ambiguitySeen || request.status === "ambiguous" ? { ambiguitySeen: true } : {}), ...(request.submissionObserved ? { submissionObserved: true } : {}), ...(request.observedCwd ? { observedCwd: request.observedCwd } : {}), ...(hasSession ? { sessionId: request.sessionId, taskId: request.taskId, title: request.title } : {}) }));
|
|
258
|
+
restored.set(request.requestId, structuredClone({ requestId: request.requestId, cwd: request.cwd, prompt: request.prompt, initialMessage: request.initialMessage, account: { fingerprint: request.account.fingerprint, root: request.account.root }, senderThreadId: request.senderThreadId, baseline: request.baseline, status: request.status, reason: request.reason, ...(request.submissionAttempted ? { submissionAttempted: true } : {}), ...(request.ambiguitySeen || request.status === "ambiguous" ? { ambiguitySeen: true } : {}), ...(request.submissionObserved ? { submissionObserved: true } : {}), ...(request.observedCwd ? { observedCwd: request.observedCwd } : {}), ...(hasSession ? { sessionId: request.sessionId, taskId: request.taskId, title: request.title } : {}) }));
|
|
213
259
|
}
|
|
214
260
|
this.requests = restored;
|
|
215
261
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertClaudeSessionCwd } from "./peer-protocol.mjs";
|
|
2
2
|
import { preflightFailure } from "./recipient-preflight.mjs";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
4
|
|
|
4
5
|
function assertCwd(session, expectedCwd) {
|
|
5
6
|
try {
|
|
@@ -23,6 +24,36 @@ function uniqueLiveSessions(sessions) {
|
|
|
23
24
|
return [...unique.values()];
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export async function recoverClaudeDesktopSession(args, { listTasks, listSessions, open, beforeOpen, sleep = delay, timeoutMs = 15000 }) {
|
|
28
|
+
try { return { session: resolveClaudeDesktopSession(args) }; }
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.preflight?.code !== "CLAUDE_SESSION_NOT_FOUND" || args.target !== "auto" || !args.expectedTaskId) throw error;
|
|
31
|
+
}
|
|
32
|
+
const assertTask = async () => {
|
|
33
|
+
await beforeOpen();
|
|
34
|
+
const tasks = await listTasks();
|
|
35
|
+
if (!Array.isArray(tasks) || tasks.length > 8192) throw preflightFailure("CLAUDE_DESKTOP_TASK_UNVERIFIED", "Saved Desktop tasks cannot be verified.");
|
|
36
|
+
const matches = tasks.filter((task) => task.taskId === args.expectedTaskId);
|
|
37
|
+
if (matches.length !== 1 || matches[0].fileTaskId !== matches[0].taskId || matches[0].isArchived !== false) {
|
|
38
|
+
throw preflightFailure("CLAUDE_DESKTOP_TASK_UNVERIFIED", "The exact saved Desktop task is missing, archived, or ambiguous; no replacement was opened.");
|
|
39
|
+
}
|
|
40
|
+
assertCwd(matches[0], args.expectedCwd);
|
|
41
|
+
return matches[0];
|
|
42
|
+
};
|
|
43
|
+
const task = await assertTask();
|
|
44
|
+
try { await open(task.taskId); }
|
|
45
|
+
catch { return { recovery: { status: "launch_uncertain", sent: false, taskId: task.taskId, cwd: args.expectedCwd, reason: "The exact Desktop task could not be confirmed open. Inspect this task before retrying; no message was sent." } }; }
|
|
46
|
+
const deadline = Date.now() + timeoutMs;
|
|
47
|
+
for (;;) {
|
|
48
|
+
await assertTask();
|
|
49
|
+
try { return { session: resolveClaudeDesktopSession({ ...args, sessions: await listSessions() }) }; }
|
|
50
|
+
catch (error) { if (error.preflight?.code !== "CLAUDE_SESSION_NOT_FOUND") throw error; }
|
|
51
|
+
if (Date.now() >= deadline) break;
|
|
52
|
+
await sleep(Math.min(250, Math.max(0, deadline - Date.now())));
|
|
53
|
+
}
|
|
54
|
+
return { recovery: { status: "awaiting_session", sent: false, taskId: task.taskId, cwd: args.expectedCwd, reason: "The exact saved task was opened in Claude Desktop and is reconnecting. Retry target auto with this expectedTaskId; no message has been sent." } };
|
|
55
|
+
}
|
|
56
|
+
|
|
26
57
|
export function sameClaudeDesktopRecipient(selected, current) {
|
|
27
58
|
return Boolean(
|
|
28
59
|
selected && current &&
|
package/src/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ import { AGENT_PROMPT_GUIDANCE, PROMPT_FIELD_HINT } from "./prompt-guidance.mjs"
|
|
|
35
35
|
|
|
36
36
|
exitForVersionRequest(import.meta.url);
|
|
37
37
|
|
|
38
|
-
const VERSION = "1.
|
|
38
|
+
const VERSION = "1.20.0";
|
|
39
39
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
40
40
|
const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
|
|
41
41
|
|
|
@@ -34,7 +34,7 @@ import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
|
|
|
34
34
|
|
|
35
35
|
exitForVersionRequest(import.meta.url);
|
|
36
36
|
|
|
37
|
-
const VERSION = "1.
|
|
37
|
+
const VERSION = "1.20.0";
|
|
38
38
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
39
39
|
|
|
40
40
|
function errorResponse(code, message, sent) {
|
package/src/platform.mjs
CHANGED
|
@@ -471,14 +471,16 @@ export async function openThreadInCodexApp(threadId, { activate = true } = {}) {
|
|
|
471
471
|
|
|
472
472
|
export async function openClaudeCodeComposer(url, { platform = process.platform, env = process.env, run = execFileAsync } = {}) {
|
|
473
473
|
const parsed = new URL(url);
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
const keys = parsed.pathname === "/continue" ? ["session"] : ["q", "folder"];
|
|
475
|
+
if (parsed.protocol !== "claude:" || parsed.hostname !== "code" || !["/new", "/continue"].includes(parsed.pathname) || parsed.username || parsed.password || parsed.port || parsed.hash
|
|
476
|
+
|| [...parsed.searchParams.keys()].some((key) => !keys.includes(key) || parsed.searchParams.getAll(key).length !== 1)
|
|
477
|
+
|| (parsed.pathname === "/continue" && !/^local_[0-9a-f-]{36}$/i.test(parsed.searchParams.get("session") ?? ""))) {
|
|
476
478
|
throw new Error("Invalid Claude Desktop Code creation link");
|
|
477
479
|
}
|
|
478
480
|
if (platform === "win32") {
|
|
479
481
|
const shell = path.win32.join(env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
480
482
|
const encodedUrl = Buffer.from(url, "utf8").toString("base64");
|
|
481
|
-
const script = `$ErrorActionPreference='Stop'; $creationUrl=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedUrl}')); Start-Process -FilePath $creationUrl -ErrorAction Stop`;
|
|
483
|
+
const script = String.raw`$ErrorActionPreference='Stop'; $creationUrl=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedUrl}')); try { Start-Process -FilePath $creationUrl -ErrorAction Stop } catch { $desktopPaths=@(Get-Process claude -ErrorAction SilentlyContinue | ForEach-Object { $_.Path } | Where-Object { $_ -match '\\(?:AnthropicClaude\\app-[^\\]+|WindowsApps\\Claude_[^\\]+\\app)\\claude\.exe$' -and (Test-Path -LiteralPath $_) } | Select-Object -Unique); if ($desktopPaths.Count -eq 0 -and $env:LOCALAPPDATA) { $desktopPaths=@(Get-ChildItem -LiteralPath (Join-Path $env:LOCALAPPDATA 'AnthropicClaude') -Directory -Filter 'app-*' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | ForEach-Object { Join-Path $_.FullName 'claude.exe' } | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1) }; if ($desktopPaths.Count -ne 1) { throw }; Start-Process -FilePath $desktopPaths[0] -ArgumentList ('"'+$creationUrl+'"') -WindowStyle Hidden -ErrorAction Stop }`;
|
|
482
484
|
await run(shell, ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 10000 });
|
|
483
485
|
return;
|
|
484
486
|
}
|
package/src/runtime-state.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
export function createRuntimeState({ directory = path.dirname(fileURLToPath(import.meta.url)), configuration = () => null } = {}) {
|
|
7
7
|
const fingerprint = () => {
|
|
8
8
|
const hash = createHash("sha256");
|
|
9
|
-
for (const file of ["../package.json", ...readdirSync(directory).filter((file) =>
|
|
9
|
+
for (const file of ["../package.json", ...readdirSync(directory).filter((file) => /\.(?:mjs|ps1)$/.test(file)).sort()]) {
|
|
10
10
|
hash.update(file).update("\0").update(readFileSync(path.join(directory, file))).update("\0");
|
|
11
11
|
}
|
|
12
12
|
return hash.digest("hex");
|