heyspex 0.2.0 → 0.2.1
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/.claude-plugin/marketplace.json +14 -0
- package/bin/hs.mjs +163 -11
- package/dist/cli/hs-check.mjs +88 -46
- package/dist/cli/hs-claim.mjs +84 -39
- package/dist/cli/hs-connect.mjs +583 -129
- package/dist/cli/hs-defer.mjs +251 -30
- package/dist/cli/hs-dev.mjs +20 -8
- package/dist/cli/hs-env.mjs +62 -34
- package/dist/cli/hs-index-rebuild.mjs +288 -83
- package/dist/cli/hs-index-reconcile.mjs +397 -111
- package/dist/cli/hs-init.mjs +2143 -91
- package/dist/cli/hs-install.mjs +628 -106
- package/dist/cli/hs-join.mjs +686 -236
- package/dist/cli/hs-vault-pull.mjs +3189 -179
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +396 -178
- package/dist/cli/hs-workspace.mjs +289 -151
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
- package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
- package/docs/copilot-plugin-install.md +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +20 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/run-hook.mjs +20 -2
- package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
- package/plugins/heyspex/hooks/hooks.json +94 -37
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
- package/scripts/claude-hooks/_run.mjs +316 -23
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
- package/scripts/claude-hooks/session-end.mjs +2 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
- package/scripts/cursor-hooks/manifest.mjs +46 -0
- package/scripts/heyspex-mode.mjs +20 -0
- package/scripts/hs-mode.mjs +1 -1
- package/scripts/hs-view.mjs +180 -17
- package/scripts/install-claude-code-hooks.mjs +3 -1
- package/scripts/lib/console-url-resolver.mjs +3 -2
- package/scripts/lib/customer-kit.mjs +327 -10
- package/scripts/lib/dev-mode.mjs +7 -6
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-log.mjs +2 -1
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -356
- package/scripts/lib/window-placement.mjs +109 -0
- package/spexharness/src/adapters/claude-code.mjs +11 -3
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/intent-classifier.mjs +4 -2
- package/spexharness/src/intent-directive.mjs +626 -541
- package/spexharness/src/rule-table.mjs +31 -1
- package/vendor/heyspex-mcp/account-switch.mjs +6 -5
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
- package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
- package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
- package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
- package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
- package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
- package/vendor/heyspex-mcp/cli.mjs +7 -1
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
- package/vendor/heyspex-mcp/console-transport.mjs +7 -1
- package/vendor/heyspex-mcp/credentials.mjs +9 -8
- package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
- package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
- package/vendor/heyspex-mcp/http-transport.mjs +2 -0
- package/vendor/heyspex-mcp/list-tools.mjs +1 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/plan-push.mjs +3 -2
- package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
- package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
- package/vendor/heyspex-mcp/promote-research.mjs +6 -0
- package/vendor/heyspex-mcp/read-path.mjs +162 -6
- package/vendor/heyspex-mcp/request-context.mjs +25 -1
- package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/session-owner.mjs +3 -2
- package/vendor/heyspex-mcp/session-tools.mjs +10 -0
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
- package/vendor/heyspex-mcp/split-research.mjs +3 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
- package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
- package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
- package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
- package/vendor/heyspex-mcp/vault-io.mjs +720 -724
- package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
- package/vendor/heyspex-mcp/whoami.mjs +2 -1
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -18,6 +18,10 @@ var __esm = (fn, res) => function __init() {
|
|
|
18
18
|
var __commonJS = (cb, mod) => function __require2() {
|
|
19
19
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
20
20
|
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
21
25
|
var __copyProps = (to, from, except, desc2) => {
|
|
22
26
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
27
|
for (let key of __getOwnPropNames(from))
|
|
@@ -59,11 +63,26 @@ var init_hs_command = __esm({
|
|
|
59
63
|
}
|
|
60
64
|
});
|
|
61
65
|
|
|
66
|
+
// scripts/lib/heyspex-home.mjs
|
|
67
|
+
import { homedir } from "node:os";
|
|
68
|
+
function heyspexHome(env = process.env) {
|
|
69
|
+
const override = env?.HEYSPEX_HOME;
|
|
70
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
71
|
+
return override.trim();
|
|
72
|
+
}
|
|
73
|
+
return homedir();
|
|
74
|
+
}
|
|
75
|
+
var init_heyspex_home = __esm({
|
|
76
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
62
81
|
// vendor/heyspex-mcp/credentials.mjs
|
|
63
82
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
64
|
-
import os, { homedir } from "node:os";
|
|
83
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
65
84
|
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
66
|
-
function credentialsFilePath(homeDir =
|
|
85
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
67
86
|
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
68
87
|
}
|
|
69
88
|
function parseMcpToken(raw) {
|
|
@@ -159,7 +178,7 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
159
178
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
160
179
|
return value || null;
|
|
161
180
|
}
|
|
162
|
-
function readCurrentAccountId({ homeDir =
|
|
181
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
|
|
163
182
|
const read = readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
164
183
|
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
165
184
|
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
@@ -172,6 +191,7 @@ var MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT_ID, ACCOUNT_FI
|
|
|
172
191
|
var init_credentials = __esm({
|
|
173
192
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
174
193
|
"use strict";
|
|
194
|
+
init_heyspex_home();
|
|
175
195
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
176
196
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
177
197
|
LEGACY_ACCOUNT_ID = "legacy";
|
|
@@ -183,9 +203,9 @@ var init_credentials = __esm({
|
|
|
183
203
|
|
|
184
204
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
185
205
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
186
|
-
import { homedir as
|
|
206
|
+
import { homedir as homedir3 } from "node:os";
|
|
187
207
|
import { join as join3 } from "node:path";
|
|
188
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
208
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
|
|
189
209
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
190
210
|
if (fromEnv) return fromEnv;
|
|
191
211
|
return readCurrentAccountId({ homeDir, readFile });
|
|
@@ -193,6 +213,7 @@ function resolveSessionOwnerUncached({ env = process.env, homeDir = homedir2(),
|
|
|
193
213
|
var init_session_owner = __esm({
|
|
194
214
|
"vendor/heyspex-mcp/session-owner.mjs"() {
|
|
195
215
|
"use strict";
|
|
216
|
+
init_heyspex_home();
|
|
196
217
|
init_credentials();
|
|
197
218
|
}
|
|
198
219
|
});
|
|
@@ -227,20 +248,47 @@ var init_vault_less_endpoints = __esm({
|
|
|
227
248
|
}
|
|
228
249
|
});
|
|
229
250
|
|
|
251
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
252
|
+
import { isAbsolute, resolve as resolve2, sep } from "node:path";
|
|
253
|
+
var init_shared_path_helpers = __esm({
|
|
254
|
+
"scripts/lib/shared-path-helpers.mjs"() {
|
|
255
|
+
"use strict";
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
260
|
+
import {
|
|
261
|
+
readdirSync,
|
|
262
|
+
readFileSync as readFileSync3,
|
|
263
|
+
writeFileSync as writeFileSync2,
|
|
264
|
+
mkdirSync as mkdirSync2,
|
|
265
|
+
existsSync as existsSync2,
|
|
266
|
+
renameSync as renameSync2,
|
|
267
|
+
rmSync
|
|
268
|
+
} from "node:fs";
|
|
269
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
270
|
+
var init_write_outbox = __esm({
|
|
271
|
+
"vendor/heyspex-mcp/write-outbox.mjs"() {
|
|
272
|
+
"use strict";
|
|
273
|
+
init_shared_path_helpers();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
230
277
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
231
|
-
import { existsSync as
|
|
232
|
-
import { homedir as
|
|
233
|
-
import { dirname as dirname3, join as
|
|
278
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
279
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
280
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
234
281
|
var init_vault_resolve = __esm({
|
|
235
282
|
"vendor/heyspex-mcp/vault-resolve.mjs"() {
|
|
236
283
|
"use strict";
|
|
284
|
+
init_heyspex_home();
|
|
237
285
|
}
|
|
238
286
|
});
|
|
239
287
|
|
|
240
288
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
241
|
-
import { readFileSync as
|
|
242
|
-
import { homedir as
|
|
243
|
-
import { join as
|
|
289
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
290
|
+
import { homedir as homedir5 } from "node:os";
|
|
291
|
+
import { join as join6 } from "node:path";
|
|
244
292
|
function isCloudVaultId(value) {
|
|
245
293
|
return typeof value === "string" && CLOUD_VAULT_ID_RE.test(value.trim());
|
|
246
294
|
}
|
|
@@ -248,16 +296,33 @@ var CLOUD_VAULT_ID_RE, SCOPE_REFUSAL_CODES;
|
|
|
248
296
|
var init_console_fetch = __esm({
|
|
249
297
|
"vendor/heyspex-mcp/console-fetch.mjs"() {
|
|
250
298
|
"use strict";
|
|
299
|
+
init_heyspex_home();
|
|
251
300
|
init_credentials();
|
|
252
301
|
init_session_owner();
|
|
253
302
|
init_vault_less_endpoints();
|
|
254
303
|
init_hs_command();
|
|
304
|
+
init_write_outbox();
|
|
255
305
|
init_vault_resolve();
|
|
256
306
|
CLOUD_VAULT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
257
307
|
SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
258
308
|
}
|
|
259
309
|
});
|
|
260
310
|
|
|
311
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
312
|
+
import { homedir as homedir6 } from "node:os";
|
|
313
|
+
function heyspexHome2(env = process.env) {
|
|
314
|
+
const override = env?.HEYSPEX_HOME;
|
|
315
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
316
|
+
return override.trim();
|
|
317
|
+
}
|
|
318
|
+
return homedir6();
|
|
319
|
+
}
|
|
320
|
+
var init_heyspex_home2 = __esm({
|
|
321
|
+
"spex-console/src/lib/heyspex-home.ts"() {
|
|
322
|
+
"use strict";
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
261
326
|
// spex-console/src/lib/vault-registry/types.ts
|
|
262
327
|
function emptyVaultRegistry() {
|
|
263
328
|
return {
|
|
@@ -278,14 +343,13 @@ var init_types = __esm({
|
|
|
278
343
|
});
|
|
279
344
|
|
|
280
345
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
281
|
-
import { existsSync as
|
|
282
|
-
import {
|
|
283
|
-
import { dirname as dirname4, join as join6, resolve as resolve3 } from "node:path";
|
|
346
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
347
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
284
348
|
function vaultRegistryDir(opts = {}) {
|
|
285
|
-
return opts.registryDir ??
|
|
349
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
286
350
|
}
|
|
287
351
|
function vaultRegistryFilePath(opts = {}) {
|
|
288
|
-
return
|
|
352
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
289
353
|
}
|
|
290
354
|
function isNonEmptyString(v) {
|
|
291
355
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -386,9 +450,9 @@ function parseVaultRegistryFile(json) {
|
|
|
386
450
|
}
|
|
387
451
|
function readRegistryJson(opts) {
|
|
388
452
|
const filePath = vaultRegistryFilePath(opts);
|
|
389
|
-
if (!
|
|
453
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
390
454
|
try {
|
|
391
|
-
const raw =
|
|
455
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
392
456
|
const json = JSON.parse(raw);
|
|
393
457
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
394
458
|
return { kind: "ok", raw, json };
|
|
@@ -417,29 +481,32 @@ function loadVaultRegistry(opts = {}) {
|
|
|
417
481
|
var init_registry_file = __esm({
|
|
418
482
|
"spex-console/src/lib/vault-registry/registry-file.ts"() {
|
|
419
483
|
"use strict";
|
|
484
|
+
init_heyspex_home2();
|
|
420
485
|
init_types();
|
|
421
486
|
init_types();
|
|
422
487
|
}
|
|
423
488
|
});
|
|
424
489
|
|
|
425
490
|
// scripts/lib/dev-mode.mjs
|
|
426
|
-
import { mkdirSync as
|
|
427
|
-
import { homedir as
|
|
428
|
-
import { join as
|
|
491
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
492
|
+
import { homedir as homedir7 } from "node:os";
|
|
493
|
+
import { join as join8 } from "node:path";
|
|
429
494
|
var init_dev_mode = __esm({
|
|
430
495
|
"scripts/lib/dev-mode.mjs"() {
|
|
431
496
|
"use strict";
|
|
497
|
+
init_heyspex_home();
|
|
432
498
|
init_credentials();
|
|
433
499
|
}
|
|
434
500
|
});
|
|
435
501
|
|
|
436
502
|
// scripts/lib/console-url-resolver.mjs
|
|
437
|
-
import { readFileSync as
|
|
438
|
-
import { homedir as
|
|
439
|
-
import { join as
|
|
503
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
504
|
+
import { homedir as homedir8 } from "node:os";
|
|
505
|
+
import { join as join9 } from "node:path";
|
|
440
506
|
var init_console_url_resolver = __esm({
|
|
441
507
|
"scripts/lib/console-url-resolver.mjs"() {
|
|
442
508
|
"use strict";
|
|
509
|
+
init_heyspex_home();
|
|
443
510
|
init_dev_mode();
|
|
444
511
|
}
|
|
445
512
|
});
|
|
@@ -482,13 +549,12 @@ var init_default_workspace = __esm({
|
|
|
482
549
|
});
|
|
483
550
|
|
|
484
551
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
485
|
-
import { readFileSync as
|
|
486
|
-
import os2 from "node:os";
|
|
552
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
487
553
|
function resolveConfiguredAccountId(opts = {}) {
|
|
488
554
|
return resolveSessionOwnerUncached({
|
|
489
555
|
env: opts.env ?? process.env,
|
|
490
|
-
homeDir: opts.homeDir ??
|
|
491
|
-
readFile: opts.readFile ?? ((p) =>
|
|
556
|
+
homeDir: opts.homeDir ?? heyspexHome2(),
|
|
557
|
+
readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
|
|
492
558
|
});
|
|
493
559
|
}
|
|
494
560
|
function resolveCloudAccountIdOrRefuse(opts = {}) {
|
|
@@ -521,6 +587,7 @@ var CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE;
|
|
|
521
587
|
var init_vault_workspace_shared = __esm({
|
|
522
588
|
"spex-console/src/lib/cli/vault-workspace-shared.ts"() {
|
|
523
589
|
"use strict";
|
|
590
|
+
init_heyspex_home2();
|
|
524
591
|
init_session_owner();
|
|
525
592
|
init_default_workspace();
|
|
526
593
|
CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE = "refusing -- no signed-in account (no HS_ACCOUNT_ID env var, and no account_id: in ~/.heyspex/config.yaml). Run `hs login` first; nothing was written.";
|
|
@@ -974,12 +1041,12 @@ var require_stringify = __commonJS({
|
|
|
974
1041
|
throw new TypeError('expected "' + language + '.stringify" to be a function');
|
|
975
1042
|
}
|
|
976
1043
|
data = Object.assign({}, file.data, data);
|
|
977
|
-
const
|
|
1044
|
+
const open2 = opts.delimiters[0];
|
|
978
1045
|
const close = opts.delimiters[1];
|
|
979
1046
|
const matter4 = engine.stringify(data, options2).trim();
|
|
980
1047
|
let buf = "";
|
|
981
1048
|
if (matter4 !== "{}") {
|
|
982
|
-
buf = newline(
|
|
1049
|
+
buf = newline(open2) + newline(matter4) + newline(close);
|
|
983
1050
|
}
|
|
984
1051
|
if (typeof file.excerpt === "string" && file.excerpt !== "") {
|
|
985
1052
|
if (str2.indexOf(file.excerpt.trim()) === -1) {
|
|
@@ -1007,11 +1074,11 @@ var require_excerpt = __commonJS({
|
|
|
1007
1074
|
if (typeof opts.excerpt === "function") {
|
|
1008
1075
|
return opts.excerpt(file, opts);
|
|
1009
1076
|
}
|
|
1010
|
-
const
|
|
1011
|
-
if (
|
|
1077
|
+
const sep2 = file.data.excerpt_separator || opts.excerpt_separator;
|
|
1078
|
+
if (sep2 == null && (opts.excerpt === false || opts.excerpt == null)) {
|
|
1012
1079
|
return file;
|
|
1013
1080
|
}
|
|
1014
|
-
const delimiter2 = typeof opts.excerpt === "string" ? opts.excerpt :
|
|
1081
|
+
const delimiter2 = typeof opts.excerpt === "string" ? opts.excerpt : sep2 || opts.delimiters[0];
|
|
1015
1082
|
const idx = file.content.indexOf(delimiter2);
|
|
1016
1083
|
if (idx !== -1) {
|
|
1017
1084
|
file.excerpt = file.content.slice(0, idx);
|
|
@@ -1076,7 +1143,7 @@ var require_parse = __commonJS({
|
|
|
1076
1143
|
var require_gray_matter = __commonJS({
|
|
1077
1144
|
"node_modules/gray-matter/index.js"(exports2, module2) {
|
|
1078
1145
|
"use strict";
|
|
1079
|
-
var
|
|
1146
|
+
var fs12 = __require("fs");
|
|
1080
1147
|
var sections = require_section_matter();
|
|
1081
1148
|
var defaults = require_defaults();
|
|
1082
1149
|
var stringify = require_stringify();
|
|
@@ -1103,18 +1170,18 @@ var require_gray_matter = __commonJS({
|
|
|
1103
1170
|
}
|
|
1104
1171
|
function parseMatter(file, options2) {
|
|
1105
1172
|
const opts = defaults(options2);
|
|
1106
|
-
const
|
|
1173
|
+
const open2 = opts.delimiters[0];
|
|
1107
1174
|
const close = "\n" + opts.delimiters[1];
|
|
1108
1175
|
let str2 = file.content;
|
|
1109
1176
|
if (opts.language) {
|
|
1110
1177
|
file.language = opts.language;
|
|
1111
1178
|
}
|
|
1112
|
-
const openLen =
|
|
1113
|
-
if (!utils.startsWith(str2,
|
|
1179
|
+
const openLen = open2.length;
|
|
1180
|
+
if (!utils.startsWith(str2, open2, openLen)) {
|
|
1114
1181
|
excerpt(file, opts);
|
|
1115
1182
|
return file;
|
|
1116
1183
|
}
|
|
1117
|
-
if (str2.charAt(openLen) ===
|
|
1184
|
+
if (str2.charAt(openLen) === open2.slice(-1)) {
|
|
1118
1185
|
return file;
|
|
1119
1186
|
}
|
|
1120
1187
|
str2 = str2.slice(openLen);
|
|
@@ -1160,7 +1227,7 @@ var require_gray_matter = __commonJS({
|
|
|
1160
1227
|
return stringify(file, data, options2);
|
|
1161
1228
|
};
|
|
1162
1229
|
matter4.read = function(filepath, options2) {
|
|
1163
|
-
const str2 =
|
|
1230
|
+
const str2 = fs12.readFileSync(filepath, "utf8");
|
|
1164
1231
|
const file = matter4(str2, options2);
|
|
1165
1232
|
file.path = filepath;
|
|
1166
1233
|
return file;
|
|
@@ -1170,9 +1237,9 @@ var require_gray_matter = __commonJS({
|
|
|
1170
1237
|
};
|
|
1171
1238
|
matter4.language = function(str2, options2) {
|
|
1172
1239
|
const opts = defaults(options2);
|
|
1173
|
-
const
|
|
1240
|
+
const open2 = opts.delimiters[0];
|
|
1174
1241
|
if (matter4.test(str2)) {
|
|
1175
|
-
str2 = str2.slice(
|
|
1242
|
+
str2 = str2.slice(open2.length);
|
|
1176
1243
|
}
|
|
1177
1244
|
const language = str2.slice(0, str2.search(/\r?\n/));
|
|
1178
1245
|
return {
|
|
@@ -1214,7 +1281,7 @@ var init_diagnose_skeleton = __esm({
|
|
|
1214
1281
|
});
|
|
1215
1282
|
|
|
1216
1283
|
// dist/cli/._sql-orm-inert.cli-shim.mjs
|
|
1217
|
-
var inert, customType, sql, and, eq, isNull, inArray, bigint, bigserial, boolean, foreignKey, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex;
|
|
1284
|
+
var inert, customType, sql, and, eq, isNull, desc, gt, inArray, bigint, bigserial, boolean, foreignKey, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex;
|
|
1218
1285
|
var init_sql_orm_inert_cli_shim = __esm({
|
|
1219
1286
|
"dist/cli/._sql-orm-inert.cli-shim.mjs"() {
|
|
1220
1287
|
"use strict";
|
|
@@ -1237,6 +1304,8 @@ var init_sql_orm_inert_cli_shim = __esm({
|
|
|
1237
1304
|
and = inert;
|
|
1238
1305
|
eq = inert;
|
|
1239
1306
|
isNull = inert;
|
|
1307
|
+
desc = inert;
|
|
1308
|
+
gt = inert;
|
|
1240
1309
|
inArray = inert;
|
|
1241
1310
|
bigint = inert;
|
|
1242
1311
|
bigserial = inert;
|
|
@@ -1265,7 +1334,7 @@ var init_interfaces = __esm({
|
|
|
1265
1334
|
});
|
|
1266
1335
|
|
|
1267
1336
|
// spex-console/src/lib/db/schema/postgres.ts
|
|
1268
|
-
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements;
|
|
1337
|
+
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
|
|
1269
1338
|
var init_postgres = __esm({
|
|
1270
1339
|
"spex-console/src/lib/db/schema/postgres.ts"() {
|
|
1271
1340
|
"use strict";
|
|
@@ -1707,7 +1776,13 @@ var init_postgres = __esm({
|
|
|
1707
1776
|
"priority_change",
|
|
1708
1777
|
"parent_change",
|
|
1709
1778
|
"claim_acquired",
|
|
1710
|
-
"claim_released"
|
|
1779
|
+
"claim_released",
|
|
1780
|
+
// Collaborator write audit (HS-FWB6, migration 0077).
|
|
1781
|
+
"spec_created",
|
|
1782
|
+
"spec_edited",
|
|
1783
|
+
"spec_deleted",
|
|
1784
|
+
"capture_written",
|
|
1785
|
+
"wip_recorded"
|
|
1711
1786
|
]);
|
|
1712
1787
|
focusPin = pgTable(
|
|
1713
1788
|
"focus_pin",
|
|
@@ -2900,18 +2975,50 @@ var init_postgres = __esm({
|
|
|
2900
2975
|
// `owner/name`
|
|
2901
2976
|
branch: text("branch").notNull(),
|
|
2902
2977
|
commitSha: text("commit_sha").notNull(),
|
|
2903
|
-
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`)
|
|
2978
|
+
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2979
|
+
// The pull request that carries this settlement
|
|
2980
|
+
// ([[HS-9D25-CWPX-EPIC-branch-lifecycle-open-a-pull-request]]). NULL until
|
|
2981
|
+
// the branch has gone quiet and a PR was opened or found; a write made
|
|
2982
|
+
// after its PR merged stays NULL, which is what triggers the next PR.
|
|
2983
|
+
pullRequestNumber: integer("pull_request_number"),
|
|
2984
|
+
pullRequestUrl: text("pull_request_url")
|
|
2904
2985
|
},
|
|
2905
2986
|
(table) => [
|
|
2906
2987
|
// "latest settlement for this spec" — the read path's only query.
|
|
2907
|
-
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc())
|
|
2988
|
+
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc()),
|
|
2989
|
+
// The PR sweep's only query: branches with settlements not yet in a PR.
|
|
2990
|
+
index("spec_settlements_unproposed_idx").on(table.vaultId, table.repo, table.branch).where(sql`${table.pullRequestNumber} IS NULL`)
|
|
2991
|
+
]
|
|
2992
|
+
);
|
|
2993
|
+
sessionBranchDeclarations = pgTable(
|
|
2994
|
+
"session_branch_declarations",
|
|
2995
|
+
{
|
|
2996
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
2997
|
+
vaultId: text("vault_id").notNull().default(DEFAULT_VAULT_ID),
|
|
2998
|
+
actorKey: text("actor_key").notNull(),
|
|
2999
|
+
sessionId: text("session_id").notNull(),
|
|
3000
|
+
branch: text("branch").notNull(),
|
|
3001
|
+
declaredAt: timestamp("declared_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
3002
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
3003
|
+
},
|
|
3004
|
+
(table) => [
|
|
3005
|
+
uniqueIndex("session_branch_declarations_uq").on(
|
|
3006
|
+
table.vaultId,
|
|
3007
|
+
table.actorKey,
|
|
3008
|
+
table.sessionId
|
|
3009
|
+
),
|
|
3010
|
+
index("session_branch_declarations_lookup_idx").on(
|
|
3011
|
+
table.vaultId,
|
|
3012
|
+
table.actorKey,
|
|
3013
|
+
table.declaredAt.desc()
|
|
3014
|
+
)
|
|
2908
3015
|
]
|
|
2909
3016
|
);
|
|
2910
3017
|
}
|
|
2911
3018
|
});
|
|
2912
3019
|
|
|
2913
3020
|
// spex-console/src/lib/api-key-service.ts
|
|
2914
|
-
import { createHash, randomBytes, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
3021
|
+
import { createHash as createHash2, randomBytes, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
2915
3022
|
var DEFAULT_ROTATION_GRACE_WINDOW_MS;
|
|
2916
3023
|
var init_api_key_service = __esm({
|
|
2917
3024
|
"spex-console/src/lib/api-key-service.ts"() {
|
|
@@ -3070,7 +3177,7 @@ var init_satellite = __esm({
|
|
|
3070
3177
|
});
|
|
3071
3178
|
|
|
3072
3179
|
// spex-console/src/lib/vault-resolve/index.ts
|
|
3073
|
-
import { existsSync as
|
|
3180
|
+
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
3074
3181
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3075
3182
|
import path4 from "node:path";
|
|
3076
3183
|
var init_vault_resolve2 = __esm({
|
|
@@ -3109,7 +3216,7 @@ var init_resolve_status = __esm({
|
|
|
3109
3216
|
});
|
|
3110
3217
|
|
|
3111
3218
|
// spex-console/src/lib/vault-resolve/active.ts
|
|
3112
|
-
import { existsSync as
|
|
3219
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
3113
3220
|
import path5 from "node:path";
|
|
3114
3221
|
function fallbackSpecsDir() {
|
|
3115
3222
|
const override = process.env.HEYSPEX_VAULT_DIR;
|
|
@@ -3142,7 +3249,7 @@ var init_active = __esm({
|
|
|
3142
3249
|
});
|
|
3143
3250
|
|
|
3144
3251
|
// spex-console/src/lib/control-plane/statusline-focus-cache.ts
|
|
3145
|
-
import
|
|
3252
|
+
import fs4 from "node:fs";
|
|
3146
3253
|
import path6 from "node:path";
|
|
3147
3254
|
var init_statusline_focus_cache = __esm({
|
|
3148
3255
|
"spex-console/src/lib/control-plane/statusline-focus-cache.ts"() {
|
|
@@ -3462,8 +3569,8 @@ function singleFlightDebounce(fn, delayMs) {
|
|
|
3462
3569
|
})();
|
|
3463
3570
|
}
|
|
3464
3571
|
return function debounced() {
|
|
3465
|
-
return new Promise((
|
|
3466
|
-
const resolver = { resolve:
|
|
3572
|
+
return new Promise((resolve7, reject) => {
|
|
3573
|
+
const resolver = { resolve: resolve7, reject };
|
|
3467
3574
|
if (inFlight) {
|
|
3468
3575
|
if (queuedResolvers) {
|
|
3469
3576
|
queuedResolvers.push(resolver);
|
|
@@ -3756,6 +3863,55 @@ var init_branch_policy = __esm({
|
|
|
3756
3863
|
}
|
|
3757
3864
|
});
|
|
3758
3865
|
|
|
3866
|
+
// spex-console/src/lib/git-export/branch-resolver.ts
|
|
3867
|
+
function isValidSessionBranch(name, defaultBranch) {
|
|
3868
|
+
const b = name.trim();
|
|
3869
|
+
if (!b) return { ok: false, reason: "branch name is empty" };
|
|
3870
|
+
if (b === defaultBranch) {
|
|
3871
|
+
return { ok: false, reason: `\`${b}\` is the default branch; the drain never writes it` };
|
|
3872
|
+
}
|
|
3873
|
+
if (PROTECTED_SESSION_BRANCHES.includes(b)) {
|
|
3874
|
+
return { ok: false, reason: `\`${b}\` is a protected ref` };
|
|
3875
|
+
}
|
|
3876
|
+
const hasControlChar = [...b].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
|
|
3877
|
+
if (hasControlChar || /[\s~^:?*[\\]/.test(b)) {
|
|
3878
|
+
return { ok: false, reason: "branch name contains characters git refs do not allow" };
|
|
3879
|
+
}
|
|
3880
|
+
if (b.includes("..") || b.includes("@{") || b.includes("//")) {
|
|
3881
|
+
return { ok: false, reason: "branch name contains a forbidden sequence (.. @{ //)" };
|
|
3882
|
+
}
|
|
3883
|
+
if (b.startsWith("/") || b.endsWith("/") || b.startsWith("-") || b.endsWith(".") || b === "@") {
|
|
3884
|
+
return { ok: false, reason: "branch name has a forbidden leading or trailing character" };
|
|
3885
|
+
}
|
|
3886
|
+
if (b.split("/").some((seg) => seg.startsWith(".") || seg.endsWith(".lock"))) {
|
|
3887
|
+
return {
|
|
3888
|
+
ok: false,
|
|
3889
|
+
reason: "branch name has a path segment starting with . or ending in .lock"
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
return { ok: true };
|
|
3893
|
+
}
|
|
3894
|
+
function resolveSessionTargetBranch(input) {
|
|
3895
|
+
const dated = resolveTargetBranch({
|
|
3896
|
+
user: input.user,
|
|
3897
|
+
date: input.date,
|
|
3898
|
+
defaultBranch: input.defaultBranch
|
|
3899
|
+
});
|
|
3900
|
+
const declared = input.sessionBranch?.trim();
|
|
3901
|
+
if (!declared) return { branch: dated, source: "dated" };
|
|
3902
|
+
const check = isValidSessionBranch(declared, input.defaultBranch);
|
|
3903
|
+
if (!check.ok) return { branch: dated, source: "dated", reason: check.reason };
|
|
3904
|
+
return { branch: declared, source: "session" };
|
|
3905
|
+
}
|
|
3906
|
+
var PROTECTED_SESSION_BRANCHES;
|
|
3907
|
+
var init_branch_resolver = __esm({
|
|
3908
|
+
"spex-console/src/lib/git-export/branch-resolver.ts"() {
|
|
3909
|
+
"use strict";
|
|
3910
|
+
init_branch_policy();
|
|
3911
|
+
PROTECTED_SESSION_BRANCHES = ["main", "master", "HEAD", "trunk"];
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3914
|
+
|
|
3759
3915
|
// spex-console/src/lib/git-export/config.ts
|
|
3760
3916
|
function parseGithubRemote(githubRemote) {
|
|
3761
3917
|
const match = githubRemote.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
@@ -3820,6 +3976,9 @@ ${fm}
|
|
|
3820
3976
|
---
|
|
3821
3977
|
${body}`;
|
|
3822
3978
|
}
|
|
3979
|
+
function parseSpecMarkdown(raw) {
|
|
3980
|
+
return (0, import_gray_matter.default)(raw);
|
|
3981
|
+
}
|
|
3823
3982
|
var import_gray_matter, YAML_STRINGIFY_OPTIONS;
|
|
3824
3983
|
var init_vault_frontmatter = __esm({
|
|
3825
3984
|
"spex-console/src/lib/vault-frontmatter.ts"() {
|
|
@@ -3843,10 +4002,10 @@ function trashPathFor(relPath, now) {
|
|
|
3843
4002
|
const normalized = relPath.split("\\").join("/");
|
|
3844
4003
|
const slashIdx = normalized.lastIndexOf("/");
|
|
3845
4004
|
const folder = slashIdx === -1 ? "" : normalized.slice(0, slashIdx);
|
|
3846
|
-
const
|
|
4005
|
+
const basename4 = slashIdx === -1 ? normalized : normalized.slice(slashIdx + 1);
|
|
3847
4006
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
3848
4007
|
const trashDir = folder ? `${folder}/.trash` : ".trash";
|
|
3849
|
-
return `${trashDir}/${stamp}-${
|
|
4008
|
+
return `${trashDir}/${stamp}-${basename4}`;
|
|
3850
4009
|
}
|
|
3851
4010
|
function materializeSpecMarkdown(bundle) {
|
|
3852
4011
|
return stringifySpecMarkdown(bundle.bodyText, bundle.frontmatter);
|
|
@@ -4155,6 +4314,39 @@ var init_github_client = __esm({
|
|
|
4155
4314
|
}
|
|
4156
4315
|
});
|
|
4157
4316
|
|
|
4317
|
+
// spex-console/src/lib/git-export/session-branch-store.ts
|
|
4318
|
+
async function readActiveDeclaration(db, vaultId, actorKey, now) {
|
|
4319
|
+
const rows = await db.select().from(sessionBranchDeclarations).where(
|
|
4320
|
+
and(
|
|
4321
|
+
eq(sessionBranchDeclarations.vaultId, vaultId),
|
|
4322
|
+
eq(sessionBranchDeclarations.actorKey, actorKey),
|
|
4323
|
+
gt(sessionBranchDeclarations.expiresAt, now)
|
|
4324
|
+
)
|
|
4325
|
+
).orderBy(desc(sessionBranchDeclarations.declaredAt)).limit(1);
|
|
4326
|
+
const row = rows[0];
|
|
4327
|
+
if (!row) return null;
|
|
4328
|
+
return {
|
|
4329
|
+
vaultId: row.vaultId,
|
|
4330
|
+
actorKey: row.actorKey,
|
|
4331
|
+
sessionId: row.sessionId,
|
|
4332
|
+
branch: row.branch,
|
|
4333
|
+
declaredAt: row.declaredAt.toISOString(),
|
|
4334
|
+
expiresAt: row.expiresAt.toISOString()
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
async function readActiveSessionBranch(db, vaultId, actorKey, now) {
|
|
4338
|
+
return (await readActiveDeclaration(db, vaultId, actorKey, now))?.branch ?? null;
|
|
4339
|
+
}
|
|
4340
|
+
var SESSION_BRANCH_TTL_MS;
|
|
4341
|
+
var init_session_branch_store = __esm({
|
|
4342
|
+
"spex-console/src/lib/git-export/session-branch-store.ts"() {
|
|
4343
|
+
"use strict";
|
|
4344
|
+
init_sql_orm_inert_cli_shim();
|
|
4345
|
+
init_postgres();
|
|
4346
|
+
SESSION_BRANCH_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
4347
|
+
}
|
|
4348
|
+
});
|
|
4349
|
+
|
|
4158
4350
|
// spex-console/src/lib/git-export/settlement-store.ts
|
|
4159
4351
|
async function recordSettlement(tx, input) {
|
|
4160
4352
|
await tx.insert(specSettlements).values({
|
|
@@ -4385,6 +4577,7 @@ async function processVault(db, vaultId, env, now, deps, batchLimit, jobTimeoutM
|
|
|
4385
4577
|
result: { status: "error", message: target.message }
|
|
4386
4578
|
};
|
|
4387
4579
|
}
|
|
4580
|
+
const lookupSessionBranch = deps.lookupSessionBranch ?? ((vId, actor) => readActiveSessionBranch(tx, vId, actor, now()));
|
|
4388
4581
|
const { config, tokenSource, installationId } = target;
|
|
4389
4582
|
const provider = createPatCommitProvider(() => config.token);
|
|
4390
4583
|
let pendingSettlement = null;
|
|
@@ -4398,11 +4591,18 @@ async function processVault(db, vaultId, env, now, deps, batchLimit, jobTimeoutM
|
|
|
4398
4591
|
fetchBundles: (specIds) => fetchDrainBundles(tx, vaultId, specIds),
|
|
4399
4592
|
resolveActorIdentity,
|
|
4400
4593
|
commit: async (plan, message, coAuthor) => {
|
|
4401
|
-
|
|
4594
|
+
let sessionBranch = null;
|
|
4595
|
+
try {
|
|
4596
|
+
sessionBranch = await lookupSessionBranch(vaultId, coAuthor.name);
|
|
4597
|
+
} catch {
|
|
4598
|
+
sessionBranch = null;
|
|
4599
|
+
}
|
|
4600
|
+
const targetBranch = resolveSessionTargetBranch({
|
|
4402
4601
|
user: coAuthor.name,
|
|
4403
4602
|
date: now(),
|
|
4404
|
-
defaultBranch: config.branch
|
|
4405
|
-
|
|
4603
|
+
defaultBranch: config.branch,
|
|
4604
|
+
sessionBranch
|
|
4605
|
+
}).branch;
|
|
4406
4606
|
const outcome = await commitPlanToGitHub(
|
|
4407
4607
|
provider,
|
|
4408
4608
|
config,
|
|
@@ -4543,12 +4743,13 @@ var init_drain_runner = __esm({
|
|
|
4543
4743
|
init_postgres();
|
|
4544
4744
|
init_github_app();
|
|
4545
4745
|
init_advisory_lock();
|
|
4546
|
-
|
|
4746
|
+
init_branch_resolver();
|
|
4547
4747
|
init_config();
|
|
4548
4748
|
init_drain();
|
|
4549
4749
|
init_drain_skip();
|
|
4550
4750
|
init_export_status();
|
|
4551
4751
|
init_github_client();
|
|
4752
|
+
init_session_branch_store();
|
|
4552
4753
|
init_settlement_store();
|
|
4553
4754
|
BOT_NAME = "heyspex-bot";
|
|
4554
4755
|
BOT_EMAIL = "bot@heyspex.app";
|
|
@@ -4806,7 +5007,7 @@ var init_diagnose_spawned = __esm({
|
|
|
4806
5007
|
});
|
|
4807
5008
|
|
|
4808
5009
|
// spex-console/src/lib/guardrail-naming.ts
|
|
4809
|
-
import
|
|
5010
|
+
import fs5 from "node:fs";
|
|
4810
5011
|
import path7 from "node:path";
|
|
4811
5012
|
var init_guardrail_naming = __esm({
|
|
4812
5013
|
"spex-console/src/lib/guardrail-naming.ts"() {
|
|
@@ -4815,8 +5016,8 @@ var init_guardrail_naming = __esm({
|
|
|
4815
5016
|
});
|
|
4816
5017
|
|
|
4817
5018
|
// spex-console/src/lib/indexer/parser.ts
|
|
4818
|
-
import { createHash as
|
|
4819
|
-
import { promises as
|
|
5019
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5020
|
+
import { promises as fs6 } from "node:fs";
|
|
4820
5021
|
import path8 from "node:path";
|
|
4821
5022
|
var import_gray_matter2;
|
|
4822
5023
|
var init_parser = __esm({
|
|
@@ -4923,7 +5124,7 @@ var init_scoped_context = __esm({
|
|
|
4923
5124
|
|
|
4924
5125
|
// spex-console/src/lib/indexer/indexer-service.ts
|
|
4925
5126
|
import { spawnSync } from "node:child_process";
|
|
4926
|
-
import { promises as
|
|
5127
|
+
import { promises as fs7 } from "node:fs";
|
|
4927
5128
|
import path9 from "node:path";
|
|
4928
5129
|
var init_indexer_service = __esm({
|
|
4929
5130
|
"spex-console/src/lib/indexer/indexer-service.ts"() {
|
|
@@ -4942,7 +5143,7 @@ var init_indexer_service = __esm({
|
|
|
4942
5143
|
});
|
|
4943
5144
|
|
|
4944
5145
|
// spex-console/src/lib/indexer/outbox-index-feeder.ts
|
|
4945
|
-
import { promises as
|
|
5146
|
+
import { promises as fs8 } from "node:fs";
|
|
4946
5147
|
import path10 from "node:path";
|
|
4947
5148
|
var init_outbox_index_feeder = __esm({
|
|
4948
5149
|
"spex-console/src/lib/indexer/outbox-index-feeder.ts"() {
|
|
@@ -5322,8 +5523,8 @@ var init_search_body_hit = __esm({
|
|
|
5322
5523
|
|
|
5323
5524
|
// spex-console/src/lib/vault.ts
|
|
5324
5525
|
import { spawn } from "node:child_process";
|
|
5325
|
-
import { readdirSync, readFileSync as
|
|
5326
|
-
import { promises as
|
|
5526
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync } from "node:fs";
|
|
5527
|
+
import { promises as fs9 } from "node:fs";
|
|
5327
5528
|
import path13 from "node:path";
|
|
5328
5529
|
import yaml3 from "js-yaml";
|
|
5329
5530
|
function normalizeDateField(value) {
|
|
@@ -5365,18 +5566,18 @@ function extractBodyLinks(body) {
|
|
|
5365
5566
|
return links;
|
|
5366
5567
|
}
|
|
5367
5568
|
function toSummary(absPath, root, fm, body) {
|
|
5368
|
-
const
|
|
5569
|
+
const basename4 = path13.basename(absPath).replace(/\.md$/i, "");
|
|
5369
5570
|
const relPath = path13.relative(root, absPath).split(path13.sep).join("/");
|
|
5370
5571
|
return {
|
|
5371
|
-
id: typeof fm.id === "string" && fm.id ? fm.id :
|
|
5372
|
-
name: typeof fm.name === "string" && fm.name ? fm.name :
|
|
5572
|
+
id: typeof fm.id === "string" && fm.id ? fm.id : basename4,
|
|
5573
|
+
name: typeof fm.name === "string" && fm.name ? fm.name : basename4,
|
|
5373
5574
|
type: typeof fm.type === "string" ? fm.type : "unknown",
|
|
5374
5575
|
status: typeof fm.status === "string" ? fm.status : "unknown",
|
|
5375
5576
|
effort: typeof fm.effort === "number" ? fm.effort : null,
|
|
5376
5577
|
owner: typeof fm.owner === "string" ? fm.owner : null,
|
|
5377
5578
|
tags: Array.isArray(fm.tags) ? fm.tags.map(String) : [],
|
|
5378
5579
|
archived: fm.archived === true,
|
|
5379
|
-
basename,
|
|
5580
|
+
basename: basename4,
|
|
5380
5581
|
relPath,
|
|
5381
5582
|
links: extractBodyLinks(body),
|
|
5382
5583
|
created: normalizeDateField(fm.created),
|
|
@@ -5462,9 +5663,9 @@ function invalidateVaultCacheForFile(absPath) {
|
|
|
5462
5663
|
else evictProcessVaultHotRows();
|
|
5463
5664
|
}
|
|
5464
5665
|
function specIdFromAbsPath(absPath) {
|
|
5465
|
-
const
|
|
5466
|
-
if (!
|
|
5467
|
-
const parsed = parseSpecId(
|
|
5666
|
+
const basename4 = path13.basename(absPath).replace(/\.md$/i, "");
|
|
5667
|
+
if (!basename4) return null;
|
|
5668
|
+
const parsed = parseSpecId(basename4);
|
|
5468
5669
|
if (parsed?.era === 3 && parsed.fileKey) return `HS-${parsed.fileKey.toUpperCase()}`;
|
|
5469
5670
|
return null;
|
|
5470
5671
|
}
|
|
@@ -5555,8 +5756,8 @@ async function writeSpecBytesToVault(relPath, content) {
|
|
|
5555
5756
|
try {
|
|
5556
5757
|
const absPath = path13.join(vaultRoot(), relPath);
|
|
5557
5758
|
const dir = path13.dirname(absPath);
|
|
5558
|
-
await
|
|
5559
|
-
await
|
|
5759
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
5760
|
+
await fs9.writeFile(absPath, content, "utf8");
|
|
5560
5761
|
} catch (err) {
|
|
5561
5762
|
const message = err instanceof Error ? err.message : String(err);
|
|
5562
5763
|
if (isLocalVaultMode()) {
|
|
@@ -5568,7 +5769,7 @@ async function writeSpecBytesToVault(relPath, content) {
|
|
|
5568
5769
|
async function parseLoadedSpec(absPath, root) {
|
|
5569
5770
|
let raw;
|
|
5570
5771
|
try {
|
|
5571
|
-
raw = await
|
|
5772
|
+
raw = await fs9.readFile(absPath, "utf8");
|
|
5572
5773
|
} catch {
|
|
5573
5774
|
return null;
|
|
5574
5775
|
}
|
|
@@ -5718,45 +5919,1761 @@ var init_vault = __esm({
|
|
|
5718
5919
|
}
|
|
5719
5920
|
});
|
|
5720
5921
|
|
|
5721
|
-
//
|
|
5722
|
-
import {
|
|
5723
|
-
import
|
|
5724
|
-
import
|
|
5922
|
+
// node_modules/readdirp/esm/index.js
|
|
5923
|
+
import { stat, lstat, readdir, realpath } from "node:fs/promises";
|
|
5924
|
+
import { Readable } from "node:stream";
|
|
5925
|
+
import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "node:path";
|
|
5926
|
+
function readdirp(root, options2 = {}) {
|
|
5927
|
+
let type = options2.entryType || options2.type;
|
|
5928
|
+
if (type === "both")
|
|
5929
|
+
type = EntryTypes.FILE_DIR_TYPE;
|
|
5930
|
+
if (type)
|
|
5931
|
+
options2.type = type;
|
|
5932
|
+
if (!root) {
|
|
5933
|
+
throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
|
|
5934
|
+
} else if (typeof root !== "string") {
|
|
5935
|
+
throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
|
|
5936
|
+
} else if (type && !ALL_TYPES.includes(type)) {
|
|
5937
|
+
throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
|
|
5938
|
+
}
|
|
5939
|
+
options2.root = root;
|
|
5940
|
+
return new ReaddirpStream(options2);
|
|
5941
|
+
}
|
|
5942
|
+
var EntryTypes, defaultOptions, RECURSIVE_ERROR_CODE, NORMAL_FLOW_ERRORS, ALL_TYPES, DIR_TYPES, FILE_TYPES, isNormalFlowError, wantBigintFsStats, emptyFn, normalizeFilter, ReaddirpStream;
|
|
5943
|
+
var init_esm = __esm({
|
|
5944
|
+
"node_modules/readdirp/esm/index.js"() {
|
|
5945
|
+
"use strict";
|
|
5946
|
+
EntryTypes = {
|
|
5947
|
+
FILE_TYPE: "files",
|
|
5948
|
+
DIR_TYPE: "directories",
|
|
5949
|
+
FILE_DIR_TYPE: "files_directories",
|
|
5950
|
+
EVERYTHING_TYPE: "all"
|
|
5951
|
+
};
|
|
5952
|
+
defaultOptions = {
|
|
5953
|
+
root: ".",
|
|
5954
|
+
fileFilter: (_entryInfo) => true,
|
|
5955
|
+
directoryFilter: (_entryInfo) => true,
|
|
5956
|
+
type: EntryTypes.FILE_TYPE,
|
|
5957
|
+
lstat: false,
|
|
5958
|
+
depth: 2147483648,
|
|
5959
|
+
alwaysStat: false,
|
|
5960
|
+
highWaterMark: 4096
|
|
5961
|
+
};
|
|
5962
|
+
Object.freeze(defaultOptions);
|
|
5963
|
+
RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
|
|
5964
|
+
NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
|
|
5965
|
+
ALL_TYPES = [
|
|
5966
|
+
EntryTypes.DIR_TYPE,
|
|
5967
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
5968
|
+
EntryTypes.FILE_DIR_TYPE,
|
|
5969
|
+
EntryTypes.FILE_TYPE
|
|
5970
|
+
];
|
|
5971
|
+
DIR_TYPES = /* @__PURE__ */ new Set([
|
|
5972
|
+
EntryTypes.DIR_TYPE,
|
|
5973
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
5974
|
+
EntryTypes.FILE_DIR_TYPE
|
|
5975
|
+
]);
|
|
5976
|
+
FILE_TYPES = /* @__PURE__ */ new Set([
|
|
5977
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
5978
|
+
EntryTypes.FILE_DIR_TYPE,
|
|
5979
|
+
EntryTypes.FILE_TYPE
|
|
5980
|
+
]);
|
|
5981
|
+
isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
|
|
5982
|
+
wantBigintFsStats = process.platform === "win32";
|
|
5983
|
+
emptyFn = (_entryInfo) => true;
|
|
5984
|
+
normalizeFilter = (filter) => {
|
|
5985
|
+
if (filter === void 0)
|
|
5986
|
+
return emptyFn;
|
|
5987
|
+
if (typeof filter === "function")
|
|
5988
|
+
return filter;
|
|
5989
|
+
if (typeof filter === "string") {
|
|
5990
|
+
const fl = filter.trim();
|
|
5991
|
+
return (entry) => entry.basename === fl;
|
|
5992
|
+
}
|
|
5993
|
+
if (Array.isArray(filter)) {
|
|
5994
|
+
const trItems = filter.map((item) => item.trim());
|
|
5995
|
+
return (entry) => trItems.some((f) => entry.basename === f);
|
|
5996
|
+
}
|
|
5997
|
+
return emptyFn;
|
|
5998
|
+
};
|
|
5999
|
+
ReaddirpStream = class extends Readable {
|
|
6000
|
+
constructor(options2 = {}) {
|
|
6001
|
+
super({
|
|
6002
|
+
objectMode: true,
|
|
6003
|
+
autoDestroy: true,
|
|
6004
|
+
highWaterMark: options2.highWaterMark
|
|
6005
|
+
});
|
|
6006
|
+
const opts = { ...defaultOptions, ...options2 };
|
|
6007
|
+
const { root, type } = opts;
|
|
6008
|
+
this._fileFilter = normalizeFilter(opts.fileFilter);
|
|
6009
|
+
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
6010
|
+
const statMethod = opts.lstat ? lstat : stat;
|
|
6011
|
+
if (wantBigintFsStats) {
|
|
6012
|
+
this._stat = (path15) => statMethod(path15, { bigint: true });
|
|
6013
|
+
} else {
|
|
6014
|
+
this._stat = statMethod;
|
|
6015
|
+
}
|
|
6016
|
+
this._maxDepth = opts.depth ?? defaultOptions.depth;
|
|
6017
|
+
this._wantsDir = type ? DIR_TYPES.has(type) : false;
|
|
6018
|
+
this._wantsFile = type ? FILE_TYPES.has(type) : false;
|
|
6019
|
+
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
|
|
6020
|
+
this._root = presolve(root);
|
|
6021
|
+
this._isDirent = !opts.alwaysStat;
|
|
6022
|
+
this._statsProp = this._isDirent ? "dirent" : "stats";
|
|
6023
|
+
this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
|
|
6024
|
+
this.parents = [this._exploreDir(root, 1)];
|
|
6025
|
+
this.reading = false;
|
|
6026
|
+
this.parent = void 0;
|
|
6027
|
+
}
|
|
6028
|
+
async _read(batch) {
|
|
6029
|
+
if (this.reading)
|
|
6030
|
+
return;
|
|
6031
|
+
this.reading = true;
|
|
6032
|
+
try {
|
|
6033
|
+
while (!this.destroyed && batch > 0) {
|
|
6034
|
+
const par = this.parent;
|
|
6035
|
+
const fil = par && par.files;
|
|
6036
|
+
if (fil && fil.length > 0) {
|
|
6037
|
+
const { path: path15, depth } = par;
|
|
6038
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path15));
|
|
6039
|
+
const awaited = await Promise.all(slice);
|
|
6040
|
+
for (const entry of awaited) {
|
|
6041
|
+
if (!entry)
|
|
6042
|
+
continue;
|
|
6043
|
+
if (this.destroyed)
|
|
6044
|
+
return;
|
|
6045
|
+
const entryType = await this._getEntryType(entry);
|
|
6046
|
+
if (entryType === "directory" && this._directoryFilter(entry)) {
|
|
6047
|
+
if (depth <= this._maxDepth) {
|
|
6048
|
+
this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
|
|
6049
|
+
}
|
|
6050
|
+
if (this._wantsDir) {
|
|
6051
|
+
this.push(entry);
|
|
6052
|
+
batch--;
|
|
6053
|
+
}
|
|
6054
|
+
} else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
|
|
6055
|
+
if (this._wantsFile) {
|
|
6056
|
+
this.push(entry);
|
|
6057
|
+
batch--;
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
} else {
|
|
6062
|
+
const parent = this.parents.pop();
|
|
6063
|
+
if (!parent) {
|
|
6064
|
+
this.push(null);
|
|
6065
|
+
break;
|
|
6066
|
+
}
|
|
6067
|
+
this.parent = await parent;
|
|
6068
|
+
if (this.destroyed)
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
} catch (error) {
|
|
6073
|
+
this.destroy(error);
|
|
6074
|
+
} finally {
|
|
6075
|
+
this.reading = false;
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6078
|
+
async _exploreDir(path15, depth) {
|
|
6079
|
+
let files;
|
|
6080
|
+
try {
|
|
6081
|
+
files = await readdir(path15, this._rdOptions);
|
|
6082
|
+
} catch (error) {
|
|
6083
|
+
this._onError(error);
|
|
6084
|
+
}
|
|
6085
|
+
return { files, depth, path: path15 };
|
|
6086
|
+
}
|
|
6087
|
+
async _formatEntry(dirent, path15) {
|
|
6088
|
+
let entry;
|
|
6089
|
+
const basename4 = this._isDirent ? dirent.name : dirent;
|
|
6090
|
+
try {
|
|
6091
|
+
const fullPath = presolve(pjoin(path15, basename4));
|
|
6092
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
|
|
6093
|
+
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
6094
|
+
} catch (err) {
|
|
6095
|
+
this._onError(err);
|
|
6096
|
+
return;
|
|
6097
|
+
}
|
|
6098
|
+
return entry;
|
|
6099
|
+
}
|
|
6100
|
+
_onError(err) {
|
|
6101
|
+
if (isNormalFlowError(err) && !this.destroyed) {
|
|
6102
|
+
this.emit("warn", err);
|
|
6103
|
+
} else {
|
|
6104
|
+
this.destroy(err);
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
async _getEntryType(entry) {
|
|
6108
|
+
if (!entry && this._statsProp in entry) {
|
|
6109
|
+
return "";
|
|
6110
|
+
}
|
|
6111
|
+
const stats = entry[this._statsProp];
|
|
6112
|
+
if (stats.isFile())
|
|
6113
|
+
return "file";
|
|
6114
|
+
if (stats.isDirectory())
|
|
6115
|
+
return "directory";
|
|
6116
|
+
if (stats && stats.isSymbolicLink()) {
|
|
6117
|
+
const full = entry.fullPath;
|
|
6118
|
+
try {
|
|
6119
|
+
const entryRealPath = await realpath(full);
|
|
6120
|
+
const entryRealPathStats = await lstat(entryRealPath);
|
|
6121
|
+
if (entryRealPathStats.isFile()) {
|
|
6122
|
+
return "file";
|
|
6123
|
+
}
|
|
6124
|
+
if (entryRealPathStats.isDirectory()) {
|
|
6125
|
+
const len = entryRealPath.length;
|
|
6126
|
+
if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
|
|
6127
|
+
const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
|
|
6128
|
+
recursiveError.code = RECURSIVE_ERROR_CODE;
|
|
6129
|
+
return this._onError(recursiveError);
|
|
6130
|
+
}
|
|
6131
|
+
return "directory";
|
|
6132
|
+
}
|
|
6133
|
+
} catch (error) {
|
|
6134
|
+
this._onError(error);
|
|
6135
|
+
return "";
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
_includeAsFile(entry) {
|
|
6140
|
+
const stats = entry && entry[this._statsProp];
|
|
6141
|
+
return stats && this._wantsEverything && !stats.isDirectory();
|
|
6142
|
+
}
|
|
6143
|
+
};
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
5725
6146
|
|
|
5726
|
-
//
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
if (scope === "none") {
|
|
5738
|
-
if (!isVaultLessEndpoint(method, path15)) {
|
|
5739
|
-
throw new Error(
|
|
5740
|
-
`controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path15} is vault-scoped and must name { accountId, vaultId }.`
|
|
5741
|
-
);
|
|
6147
|
+
// node_modules/chokidar/esm/handler.js
|
|
6148
|
+
import { watchFile, unwatchFile, watch as fs_watch } from "fs";
|
|
6149
|
+
import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
|
|
6150
|
+
import * as sysPath from "path";
|
|
6151
|
+
import { type as osType } from "os";
|
|
6152
|
+
function createFsWatchInstance(path15, options2, listener, errHandler, emitRaw) {
|
|
6153
|
+
const handleEvent = (rawEvent, evPath) => {
|
|
6154
|
+
listener(path15);
|
|
6155
|
+
emitRaw(rawEvent, evPath, { watchedPath: path15 });
|
|
6156
|
+
if (evPath && path15 !== evPath) {
|
|
6157
|
+
fsWatchBroadcast(sysPath.resolve(path15, evPath), KEY_LISTENERS, sysPath.join(path15, evPath));
|
|
5742
6158
|
}
|
|
5743
|
-
return {};
|
|
5744
|
-
}
|
|
5745
|
-
return { [VAULT_ID_HEADER]: scope.vaultId, [ACCOUNT_ID_HEADER]: scope.accountId };
|
|
5746
|
-
}
|
|
5747
|
-
function cloudRequestScope(scope) {
|
|
5748
|
-
const named = { accountId: scope.accountId, vaultId: scope.vaultId };
|
|
5749
|
-
return {
|
|
5750
|
-
...named,
|
|
5751
|
-
headersFor: (method, path15) => isVaultLessEndpoint(method, path15) ? {} : scopeHeadersFor(named, method, path15)
|
|
5752
6159
|
};
|
|
6160
|
+
try {
|
|
6161
|
+
return fs_watch(path15, {
|
|
6162
|
+
persistent: options2.persistent
|
|
6163
|
+
}, handleEvent);
|
|
6164
|
+
} catch (error) {
|
|
6165
|
+
errHandler(error);
|
|
6166
|
+
return void 0;
|
|
6167
|
+
}
|
|
5753
6168
|
}
|
|
6169
|
+
var STR_DATA, STR_END, STR_CLOSE, EMPTY_FN, pl, isWindows, isMacos, isLinux, isFreeBSD, isIBMi, EVENTS, EV, THROTTLE_MODE_WATCH, statMethods, KEY_LISTENERS, KEY_ERR, KEY_RAW, HANDLER_KEYS, binaryExtensions, isBinaryPath, foreach, addAndConvert, clearItem, delFromSet, isEmptySet, FsWatchInstances, fsWatchBroadcast, setFsWatchListener, FsWatchFileInstances, setFsWatchFileListener, NodeFsHandler;
|
|
6170
|
+
var init_handler = __esm({
|
|
6171
|
+
"node_modules/chokidar/esm/handler.js"() {
|
|
6172
|
+
"use strict";
|
|
6173
|
+
STR_DATA = "data";
|
|
6174
|
+
STR_END = "end";
|
|
6175
|
+
STR_CLOSE = "close";
|
|
6176
|
+
EMPTY_FN = () => {
|
|
6177
|
+
};
|
|
6178
|
+
pl = process.platform;
|
|
6179
|
+
isWindows = pl === "win32";
|
|
6180
|
+
isMacos = pl === "darwin";
|
|
6181
|
+
isLinux = pl === "linux";
|
|
6182
|
+
isFreeBSD = pl === "freebsd";
|
|
6183
|
+
isIBMi = osType() === "OS400";
|
|
6184
|
+
EVENTS = {
|
|
6185
|
+
ALL: "all",
|
|
6186
|
+
READY: "ready",
|
|
6187
|
+
ADD: "add",
|
|
6188
|
+
CHANGE: "change",
|
|
6189
|
+
ADD_DIR: "addDir",
|
|
6190
|
+
UNLINK: "unlink",
|
|
6191
|
+
UNLINK_DIR: "unlinkDir",
|
|
6192
|
+
RAW: "raw",
|
|
6193
|
+
ERROR: "error"
|
|
6194
|
+
};
|
|
6195
|
+
EV = EVENTS;
|
|
6196
|
+
THROTTLE_MODE_WATCH = "watch";
|
|
6197
|
+
statMethods = { lstat: lstat2, stat: stat2 };
|
|
6198
|
+
KEY_LISTENERS = "listeners";
|
|
6199
|
+
KEY_ERR = "errHandlers";
|
|
6200
|
+
KEY_RAW = "rawEmitters";
|
|
6201
|
+
HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
|
|
6202
|
+
binaryExtensions = /* @__PURE__ */ new Set([
|
|
6203
|
+
"3dm",
|
|
6204
|
+
"3ds",
|
|
6205
|
+
"3g2",
|
|
6206
|
+
"3gp",
|
|
6207
|
+
"7z",
|
|
6208
|
+
"a",
|
|
6209
|
+
"aac",
|
|
6210
|
+
"adp",
|
|
6211
|
+
"afdesign",
|
|
6212
|
+
"afphoto",
|
|
6213
|
+
"afpub",
|
|
6214
|
+
"ai",
|
|
6215
|
+
"aif",
|
|
6216
|
+
"aiff",
|
|
6217
|
+
"alz",
|
|
6218
|
+
"ape",
|
|
6219
|
+
"apk",
|
|
6220
|
+
"appimage",
|
|
6221
|
+
"ar",
|
|
6222
|
+
"arj",
|
|
6223
|
+
"asf",
|
|
6224
|
+
"au",
|
|
6225
|
+
"avi",
|
|
6226
|
+
"bak",
|
|
6227
|
+
"baml",
|
|
6228
|
+
"bh",
|
|
6229
|
+
"bin",
|
|
6230
|
+
"bk",
|
|
6231
|
+
"bmp",
|
|
6232
|
+
"btif",
|
|
6233
|
+
"bz2",
|
|
6234
|
+
"bzip2",
|
|
6235
|
+
"cab",
|
|
6236
|
+
"caf",
|
|
6237
|
+
"cgm",
|
|
6238
|
+
"class",
|
|
6239
|
+
"cmx",
|
|
6240
|
+
"cpio",
|
|
6241
|
+
"cr2",
|
|
6242
|
+
"cur",
|
|
6243
|
+
"dat",
|
|
6244
|
+
"dcm",
|
|
6245
|
+
"deb",
|
|
6246
|
+
"dex",
|
|
6247
|
+
"djvu",
|
|
6248
|
+
"dll",
|
|
6249
|
+
"dmg",
|
|
6250
|
+
"dng",
|
|
6251
|
+
"doc",
|
|
6252
|
+
"docm",
|
|
6253
|
+
"docx",
|
|
6254
|
+
"dot",
|
|
6255
|
+
"dotm",
|
|
6256
|
+
"dra",
|
|
6257
|
+
"DS_Store",
|
|
6258
|
+
"dsk",
|
|
6259
|
+
"dts",
|
|
6260
|
+
"dtshd",
|
|
6261
|
+
"dvb",
|
|
6262
|
+
"dwg",
|
|
6263
|
+
"dxf",
|
|
6264
|
+
"ecelp4800",
|
|
6265
|
+
"ecelp7470",
|
|
6266
|
+
"ecelp9600",
|
|
6267
|
+
"egg",
|
|
6268
|
+
"eol",
|
|
6269
|
+
"eot",
|
|
6270
|
+
"epub",
|
|
6271
|
+
"exe",
|
|
6272
|
+
"f4v",
|
|
6273
|
+
"fbs",
|
|
6274
|
+
"fh",
|
|
6275
|
+
"fla",
|
|
6276
|
+
"flac",
|
|
6277
|
+
"flatpak",
|
|
6278
|
+
"fli",
|
|
6279
|
+
"flv",
|
|
6280
|
+
"fpx",
|
|
6281
|
+
"fst",
|
|
6282
|
+
"fvt",
|
|
6283
|
+
"g3",
|
|
6284
|
+
"gh",
|
|
6285
|
+
"gif",
|
|
6286
|
+
"graffle",
|
|
6287
|
+
"gz",
|
|
6288
|
+
"gzip",
|
|
6289
|
+
"h261",
|
|
6290
|
+
"h263",
|
|
6291
|
+
"h264",
|
|
6292
|
+
"icns",
|
|
6293
|
+
"ico",
|
|
6294
|
+
"ief",
|
|
6295
|
+
"img",
|
|
6296
|
+
"ipa",
|
|
6297
|
+
"iso",
|
|
6298
|
+
"jar",
|
|
6299
|
+
"jpeg",
|
|
6300
|
+
"jpg",
|
|
6301
|
+
"jpgv",
|
|
6302
|
+
"jpm",
|
|
6303
|
+
"jxr",
|
|
6304
|
+
"key",
|
|
6305
|
+
"ktx",
|
|
6306
|
+
"lha",
|
|
6307
|
+
"lib",
|
|
6308
|
+
"lvp",
|
|
6309
|
+
"lz",
|
|
6310
|
+
"lzh",
|
|
6311
|
+
"lzma",
|
|
6312
|
+
"lzo",
|
|
6313
|
+
"m3u",
|
|
6314
|
+
"m4a",
|
|
6315
|
+
"m4v",
|
|
6316
|
+
"mar",
|
|
6317
|
+
"mdi",
|
|
6318
|
+
"mht",
|
|
6319
|
+
"mid",
|
|
6320
|
+
"midi",
|
|
6321
|
+
"mj2",
|
|
6322
|
+
"mka",
|
|
6323
|
+
"mkv",
|
|
6324
|
+
"mmr",
|
|
6325
|
+
"mng",
|
|
6326
|
+
"mobi",
|
|
6327
|
+
"mov",
|
|
6328
|
+
"movie",
|
|
6329
|
+
"mp3",
|
|
6330
|
+
"mp4",
|
|
6331
|
+
"mp4a",
|
|
6332
|
+
"mpeg",
|
|
6333
|
+
"mpg",
|
|
6334
|
+
"mpga",
|
|
6335
|
+
"mxu",
|
|
6336
|
+
"nef",
|
|
6337
|
+
"npx",
|
|
6338
|
+
"numbers",
|
|
6339
|
+
"nupkg",
|
|
6340
|
+
"o",
|
|
6341
|
+
"odp",
|
|
6342
|
+
"ods",
|
|
6343
|
+
"odt",
|
|
6344
|
+
"oga",
|
|
6345
|
+
"ogg",
|
|
6346
|
+
"ogv",
|
|
6347
|
+
"otf",
|
|
6348
|
+
"ott",
|
|
6349
|
+
"pages",
|
|
6350
|
+
"pbm",
|
|
6351
|
+
"pcx",
|
|
6352
|
+
"pdb",
|
|
6353
|
+
"pdf",
|
|
6354
|
+
"pea",
|
|
6355
|
+
"pgm",
|
|
6356
|
+
"pic",
|
|
6357
|
+
"png",
|
|
6358
|
+
"pnm",
|
|
6359
|
+
"pot",
|
|
6360
|
+
"potm",
|
|
6361
|
+
"potx",
|
|
6362
|
+
"ppa",
|
|
6363
|
+
"ppam",
|
|
6364
|
+
"ppm",
|
|
6365
|
+
"pps",
|
|
6366
|
+
"ppsm",
|
|
6367
|
+
"ppsx",
|
|
6368
|
+
"ppt",
|
|
6369
|
+
"pptm",
|
|
6370
|
+
"pptx",
|
|
6371
|
+
"psd",
|
|
6372
|
+
"pya",
|
|
6373
|
+
"pyc",
|
|
6374
|
+
"pyo",
|
|
6375
|
+
"pyv",
|
|
6376
|
+
"qt",
|
|
6377
|
+
"rar",
|
|
6378
|
+
"ras",
|
|
6379
|
+
"raw",
|
|
6380
|
+
"resources",
|
|
6381
|
+
"rgb",
|
|
6382
|
+
"rip",
|
|
6383
|
+
"rlc",
|
|
6384
|
+
"rmf",
|
|
6385
|
+
"rmvb",
|
|
6386
|
+
"rpm",
|
|
6387
|
+
"rtf",
|
|
6388
|
+
"rz",
|
|
6389
|
+
"s3m",
|
|
6390
|
+
"s7z",
|
|
6391
|
+
"scpt",
|
|
6392
|
+
"sgi",
|
|
6393
|
+
"shar",
|
|
6394
|
+
"snap",
|
|
6395
|
+
"sil",
|
|
6396
|
+
"sketch",
|
|
6397
|
+
"slk",
|
|
6398
|
+
"smv",
|
|
6399
|
+
"snk",
|
|
6400
|
+
"so",
|
|
6401
|
+
"stl",
|
|
6402
|
+
"suo",
|
|
6403
|
+
"sub",
|
|
6404
|
+
"swf",
|
|
6405
|
+
"tar",
|
|
6406
|
+
"tbz",
|
|
6407
|
+
"tbz2",
|
|
6408
|
+
"tga",
|
|
6409
|
+
"tgz",
|
|
6410
|
+
"thmx",
|
|
6411
|
+
"tif",
|
|
6412
|
+
"tiff",
|
|
6413
|
+
"tlz",
|
|
6414
|
+
"ttc",
|
|
6415
|
+
"ttf",
|
|
6416
|
+
"txz",
|
|
6417
|
+
"udf",
|
|
6418
|
+
"uvh",
|
|
6419
|
+
"uvi",
|
|
6420
|
+
"uvm",
|
|
6421
|
+
"uvp",
|
|
6422
|
+
"uvs",
|
|
6423
|
+
"uvu",
|
|
6424
|
+
"viv",
|
|
6425
|
+
"vob",
|
|
6426
|
+
"war",
|
|
6427
|
+
"wav",
|
|
6428
|
+
"wax",
|
|
6429
|
+
"wbmp",
|
|
6430
|
+
"wdp",
|
|
6431
|
+
"weba",
|
|
6432
|
+
"webm",
|
|
6433
|
+
"webp",
|
|
6434
|
+
"whl",
|
|
6435
|
+
"wim",
|
|
6436
|
+
"wm",
|
|
6437
|
+
"wma",
|
|
6438
|
+
"wmv",
|
|
6439
|
+
"wmx",
|
|
6440
|
+
"woff",
|
|
6441
|
+
"woff2",
|
|
6442
|
+
"wrm",
|
|
6443
|
+
"wvx",
|
|
6444
|
+
"xbm",
|
|
6445
|
+
"xif",
|
|
6446
|
+
"xla",
|
|
6447
|
+
"xlam",
|
|
6448
|
+
"xls",
|
|
6449
|
+
"xlsb",
|
|
6450
|
+
"xlsm",
|
|
6451
|
+
"xlsx",
|
|
6452
|
+
"xlt",
|
|
6453
|
+
"xltm",
|
|
6454
|
+
"xltx",
|
|
6455
|
+
"xm",
|
|
6456
|
+
"xmind",
|
|
6457
|
+
"xpi",
|
|
6458
|
+
"xpm",
|
|
6459
|
+
"xwd",
|
|
6460
|
+
"xz",
|
|
6461
|
+
"z",
|
|
6462
|
+
"zip",
|
|
6463
|
+
"zipx"
|
|
6464
|
+
]);
|
|
6465
|
+
isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
|
|
6466
|
+
foreach = (val, fn) => {
|
|
6467
|
+
if (val instanceof Set) {
|
|
6468
|
+
val.forEach(fn);
|
|
6469
|
+
} else {
|
|
6470
|
+
fn(val);
|
|
6471
|
+
}
|
|
6472
|
+
};
|
|
6473
|
+
addAndConvert = (main2, prop, item) => {
|
|
6474
|
+
let container = main2[prop];
|
|
6475
|
+
if (!(container instanceof Set)) {
|
|
6476
|
+
main2[prop] = container = /* @__PURE__ */ new Set([container]);
|
|
6477
|
+
}
|
|
6478
|
+
container.add(item);
|
|
6479
|
+
};
|
|
6480
|
+
clearItem = (cont) => (key) => {
|
|
6481
|
+
const set = cont[key];
|
|
6482
|
+
if (set instanceof Set) {
|
|
6483
|
+
set.clear();
|
|
6484
|
+
} else {
|
|
6485
|
+
delete cont[key];
|
|
6486
|
+
}
|
|
6487
|
+
};
|
|
6488
|
+
delFromSet = (main2, prop, item) => {
|
|
6489
|
+
const container = main2[prop];
|
|
6490
|
+
if (container instanceof Set) {
|
|
6491
|
+
container.delete(item);
|
|
6492
|
+
} else if (container === item) {
|
|
6493
|
+
delete main2[prop];
|
|
6494
|
+
}
|
|
6495
|
+
};
|
|
6496
|
+
isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
6497
|
+
FsWatchInstances = /* @__PURE__ */ new Map();
|
|
6498
|
+
fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
6499
|
+
const cont = FsWatchInstances.get(fullPath);
|
|
6500
|
+
if (!cont)
|
|
6501
|
+
return;
|
|
6502
|
+
foreach(cont[listenerType], (listener) => {
|
|
6503
|
+
listener(val1, val2, val3);
|
|
6504
|
+
});
|
|
6505
|
+
};
|
|
6506
|
+
setFsWatchListener = (path15, fullPath, options2, handlers) => {
|
|
6507
|
+
const { listener, errHandler, rawEmitter } = handlers;
|
|
6508
|
+
let cont = FsWatchInstances.get(fullPath);
|
|
6509
|
+
let watcher;
|
|
6510
|
+
if (!options2.persistent) {
|
|
6511
|
+
watcher = createFsWatchInstance(path15, options2, listener, errHandler, rawEmitter);
|
|
6512
|
+
if (!watcher)
|
|
6513
|
+
return;
|
|
6514
|
+
return watcher.close.bind(watcher);
|
|
6515
|
+
}
|
|
6516
|
+
if (cont) {
|
|
6517
|
+
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
6518
|
+
addAndConvert(cont, KEY_ERR, errHandler);
|
|
6519
|
+
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
6520
|
+
} else {
|
|
6521
|
+
watcher = createFsWatchInstance(
|
|
6522
|
+
path15,
|
|
6523
|
+
options2,
|
|
6524
|
+
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
6525
|
+
errHandler,
|
|
6526
|
+
// no need to use broadcast here
|
|
6527
|
+
fsWatchBroadcast.bind(null, fullPath, KEY_RAW)
|
|
6528
|
+
);
|
|
6529
|
+
if (!watcher)
|
|
6530
|
+
return;
|
|
6531
|
+
watcher.on(EV.ERROR, async (error) => {
|
|
6532
|
+
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
|
|
6533
|
+
if (cont)
|
|
6534
|
+
cont.watcherUnusable = true;
|
|
6535
|
+
if (isWindows && error.code === "EPERM") {
|
|
6536
|
+
try {
|
|
6537
|
+
const fd = await open(path15, "r");
|
|
6538
|
+
await fd.close();
|
|
6539
|
+
broadcastErr(error);
|
|
6540
|
+
} catch (err) {
|
|
6541
|
+
}
|
|
6542
|
+
} else {
|
|
6543
|
+
broadcastErr(error);
|
|
6544
|
+
}
|
|
6545
|
+
});
|
|
6546
|
+
cont = {
|
|
6547
|
+
listeners: listener,
|
|
6548
|
+
errHandlers: errHandler,
|
|
6549
|
+
rawEmitters: rawEmitter,
|
|
6550
|
+
watcher
|
|
6551
|
+
};
|
|
6552
|
+
FsWatchInstances.set(fullPath, cont);
|
|
6553
|
+
}
|
|
6554
|
+
return () => {
|
|
6555
|
+
delFromSet(cont, KEY_LISTENERS, listener);
|
|
6556
|
+
delFromSet(cont, KEY_ERR, errHandler);
|
|
6557
|
+
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
6558
|
+
if (isEmptySet(cont.listeners)) {
|
|
6559
|
+
cont.watcher.close();
|
|
6560
|
+
FsWatchInstances.delete(fullPath);
|
|
6561
|
+
HANDLER_KEYS.forEach(clearItem(cont));
|
|
6562
|
+
cont.watcher = void 0;
|
|
6563
|
+
Object.freeze(cont);
|
|
6564
|
+
}
|
|
6565
|
+
};
|
|
6566
|
+
};
|
|
6567
|
+
FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
6568
|
+
setFsWatchFileListener = (path15, fullPath, options2, handlers) => {
|
|
6569
|
+
const { listener, rawEmitter } = handlers;
|
|
6570
|
+
let cont = FsWatchFileInstances.get(fullPath);
|
|
6571
|
+
const copts = cont && cont.options;
|
|
6572
|
+
if (copts && (copts.persistent < options2.persistent || copts.interval > options2.interval)) {
|
|
6573
|
+
unwatchFile(fullPath);
|
|
6574
|
+
cont = void 0;
|
|
6575
|
+
}
|
|
6576
|
+
if (cont) {
|
|
6577
|
+
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
6578
|
+
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
6579
|
+
} else {
|
|
6580
|
+
cont = {
|
|
6581
|
+
listeners: listener,
|
|
6582
|
+
rawEmitters: rawEmitter,
|
|
6583
|
+
options: options2,
|
|
6584
|
+
watcher: watchFile(fullPath, options2, (curr, prev) => {
|
|
6585
|
+
foreach(cont.rawEmitters, (rawEmitter2) => {
|
|
6586
|
+
rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
|
|
6587
|
+
});
|
|
6588
|
+
const currmtime = curr.mtimeMs;
|
|
6589
|
+
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
6590
|
+
foreach(cont.listeners, (listener2) => listener2(path15, curr));
|
|
6591
|
+
}
|
|
6592
|
+
})
|
|
6593
|
+
};
|
|
6594
|
+
FsWatchFileInstances.set(fullPath, cont);
|
|
6595
|
+
}
|
|
6596
|
+
return () => {
|
|
6597
|
+
delFromSet(cont, KEY_LISTENERS, listener);
|
|
6598
|
+
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
6599
|
+
if (isEmptySet(cont.listeners)) {
|
|
6600
|
+
FsWatchFileInstances.delete(fullPath);
|
|
6601
|
+
unwatchFile(fullPath);
|
|
6602
|
+
cont.options = cont.watcher = void 0;
|
|
6603
|
+
Object.freeze(cont);
|
|
6604
|
+
}
|
|
6605
|
+
};
|
|
6606
|
+
};
|
|
6607
|
+
NodeFsHandler = class {
|
|
6608
|
+
constructor(fsW) {
|
|
6609
|
+
this.fsw = fsW;
|
|
6610
|
+
this._boundHandleError = (error) => fsW._handleError(error);
|
|
6611
|
+
}
|
|
6612
|
+
/**
|
|
6613
|
+
* Watch file for changes with fs_watchFile or fs_watch.
|
|
6614
|
+
* @param path to file or dir
|
|
6615
|
+
* @param listener on fs change
|
|
6616
|
+
* @returns closer for the watcher instance
|
|
6617
|
+
*/
|
|
6618
|
+
_watchWithNodeFs(path15, listener) {
|
|
6619
|
+
const opts = this.fsw.options;
|
|
6620
|
+
const directory = sysPath.dirname(path15);
|
|
6621
|
+
const basename4 = sysPath.basename(path15);
|
|
6622
|
+
const parent = this.fsw._getWatchedDir(directory);
|
|
6623
|
+
parent.add(basename4);
|
|
6624
|
+
const absolutePath = sysPath.resolve(path15);
|
|
6625
|
+
const options2 = {
|
|
6626
|
+
persistent: opts.persistent
|
|
6627
|
+
};
|
|
6628
|
+
if (!listener)
|
|
6629
|
+
listener = EMPTY_FN;
|
|
6630
|
+
let closer;
|
|
6631
|
+
if (opts.usePolling) {
|
|
6632
|
+
const enableBin = opts.interval !== opts.binaryInterval;
|
|
6633
|
+
options2.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
|
|
6634
|
+
closer = setFsWatchFileListener(path15, absolutePath, options2, {
|
|
6635
|
+
listener,
|
|
6636
|
+
rawEmitter: this.fsw._emitRaw
|
|
6637
|
+
});
|
|
6638
|
+
} else {
|
|
6639
|
+
closer = setFsWatchListener(path15, absolutePath, options2, {
|
|
6640
|
+
listener,
|
|
6641
|
+
errHandler: this._boundHandleError,
|
|
6642
|
+
rawEmitter: this.fsw._emitRaw
|
|
6643
|
+
});
|
|
6644
|
+
}
|
|
6645
|
+
return closer;
|
|
6646
|
+
}
|
|
6647
|
+
/**
|
|
6648
|
+
* Watch a file and emit add event if warranted.
|
|
6649
|
+
* @returns closer for the watcher instance
|
|
6650
|
+
*/
|
|
6651
|
+
_handleFile(file, stats, initialAdd) {
|
|
6652
|
+
if (this.fsw.closed) {
|
|
6653
|
+
return;
|
|
6654
|
+
}
|
|
6655
|
+
const dirname7 = sysPath.dirname(file);
|
|
6656
|
+
const basename4 = sysPath.basename(file);
|
|
6657
|
+
const parent = this.fsw._getWatchedDir(dirname7);
|
|
6658
|
+
let prevStats = stats;
|
|
6659
|
+
if (parent.has(basename4))
|
|
6660
|
+
return;
|
|
6661
|
+
const listener = async (path15, newStats) => {
|
|
6662
|
+
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
6663
|
+
return;
|
|
6664
|
+
if (!newStats || newStats.mtimeMs === 0) {
|
|
6665
|
+
try {
|
|
6666
|
+
const newStats2 = await stat2(file);
|
|
6667
|
+
if (this.fsw.closed)
|
|
6668
|
+
return;
|
|
6669
|
+
const at = newStats2.atimeMs;
|
|
6670
|
+
const mt = newStats2.mtimeMs;
|
|
6671
|
+
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
6672
|
+
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
6673
|
+
}
|
|
6674
|
+
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
6675
|
+
this.fsw._closeFile(path15);
|
|
6676
|
+
prevStats = newStats2;
|
|
6677
|
+
const closer2 = this._watchWithNodeFs(file, listener);
|
|
6678
|
+
if (closer2)
|
|
6679
|
+
this.fsw._addPathCloser(path15, closer2);
|
|
6680
|
+
} else {
|
|
6681
|
+
prevStats = newStats2;
|
|
6682
|
+
}
|
|
6683
|
+
} catch (error) {
|
|
6684
|
+
this.fsw._remove(dirname7, basename4);
|
|
6685
|
+
}
|
|
6686
|
+
} else if (parent.has(basename4)) {
|
|
6687
|
+
const at = newStats.atimeMs;
|
|
6688
|
+
const mt = newStats.mtimeMs;
|
|
6689
|
+
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
6690
|
+
this.fsw._emit(EV.CHANGE, file, newStats);
|
|
6691
|
+
}
|
|
6692
|
+
prevStats = newStats;
|
|
6693
|
+
}
|
|
6694
|
+
};
|
|
6695
|
+
const closer = this._watchWithNodeFs(file, listener);
|
|
6696
|
+
if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
|
|
6697
|
+
if (!this.fsw._throttle(EV.ADD, file, 0))
|
|
6698
|
+
return;
|
|
6699
|
+
this.fsw._emit(EV.ADD, file, stats);
|
|
6700
|
+
}
|
|
6701
|
+
return closer;
|
|
6702
|
+
}
|
|
6703
|
+
/**
|
|
6704
|
+
* Handle symlinks encountered while reading a dir.
|
|
6705
|
+
* @param entry returned by readdirp
|
|
6706
|
+
* @param directory path of dir being read
|
|
6707
|
+
* @param path of this item
|
|
6708
|
+
* @param item basename of this item
|
|
6709
|
+
* @returns true if no more processing is needed for this entry.
|
|
6710
|
+
*/
|
|
6711
|
+
async _handleSymlink(entry, directory, path15, item) {
|
|
6712
|
+
if (this.fsw.closed) {
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
const full = entry.fullPath;
|
|
6716
|
+
const dir = this.fsw._getWatchedDir(directory);
|
|
6717
|
+
if (!this.fsw.options.followSymlinks) {
|
|
6718
|
+
this.fsw._incrReadyCount();
|
|
6719
|
+
let linkPath;
|
|
6720
|
+
try {
|
|
6721
|
+
linkPath = await fsrealpath(path15);
|
|
6722
|
+
} catch (e) {
|
|
6723
|
+
this.fsw._emitReady();
|
|
6724
|
+
return true;
|
|
6725
|
+
}
|
|
6726
|
+
if (this.fsw.closed)
|
|
6727
|
+
return;
|
|
6728
|
+
if (dir.has(item)) {
|
|
6729
|
+
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
6730
|
+
this.fsw._symlinkPaths.set(full, linkPath);
|
|
6731
|
+
this.fsw._emit(EV.CHANGE, path15, entry.stats);
|
|
6732
|
+
}
|
|
6733
|
+
} else {
|
|
6734
|
+
dir.add(item);
|
|
6735
|
+
this.fsw._symlinkPaths.set(full, linkPath);
|
|
6736
|
+
this.fsw._emit(EV.ADD, path15, entry.stats);
|
|
6737
|
+
}
|
|
6738
|
+
this.fsw._emitReady();
|
|
6739
|
+
return true;
|
|
6740
|
+
}
|
|
6741
|
+
if (this.fsw._symlinkPaths.has(full)) {
|
|
6742
|
+
return true;
|
|
6743
|
+
}
|
|
6744
|
+
this.fsw._symlinkPaths.set(full, true);
|
|
6745
|
+
}
|
|
6746
|
+
_handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
|
|
6747
|
+
directory = sysPath.join(directory, "");
|
|
6748
|
+
throttler = this.fsw._throttle("readdir", directory, 1e3);
|
|
6749
|
+
if (!throttler)
|
|
6750
|
+
return;
|
|
6751
|
+
const previous = this.fsw._getWatchedDir(wh.path);
|
|
6752
|
+
const current = /* @__PURE__ */ new Set();
|
|
6753
|
+
let stream = this.fsw._readdirp(directory, {
|
|
6754
|
+
fileFilter: (entry) => wh.filterPath(entry),
|
|
6755
|
+
directoryFilter: (entry) => wh.filterDir(entry)
|
|
6756
|
+
});
|
|
6757
|
+
if (!stream)
|
|
6758
|
+
return;
|
|
6759
|
+
stream.on(STR_DATA, async (entry) => {
|
|
6760
|
+
if (this.fsw.closed) {
|
|
6761
|
+
stream = void 0;
|
|
6762
|
+
return;
|
|
6763
|
+
}
|
|
6764
|
+
const item = entry.path;
|
|
6765
|
+
let path15 = sysPath.join(directory, item);
|
|
6766
|
+
current.add(item);
|
|
6767
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path15, item)) {
|
|
6768
|
+
return;
|
|
6769
|
+
}
|
|
6770
|
+
if (this.fsw.closed) {
|
|
6771
|
+
stream = void 0;
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
if (item === target || !target && !previous.has(item)) {
|
|
6775
|
+
this.fsw._incrReadyCount();
|
|
6776
|
+
path15 = sysPath.join(dir, sysPath.relative(dir, path15));
|
|
6777
|
+
this._addToNodeFs(path15, initialAdd, wh, depth + 1);
|
|
6778
|
+
}
|
|
6779
|
+
}).on(EV.ERROR, this._boundHandleError);
|
|
6780
|
+
return new Promise((resolve7, reject) => {
|
|
6781
|
+
if (!stream)
|
|
6782
|
+
return reject();
|
|
6783
|
+
stream.once(STR_END, () => {
|
|
6784
|
+
if (this.fsw.closed) {
|
|
6785
|
+
stream = void 0;
|
|
6786
|
+
return;
|
|
6787
|
+
}
|
|
6788
|
+
const wasThrottled = throttler ? throttler.clear() : false;
|
|
6789
|
+
resolve7(void 0);
|
|
6790
|
+
previous.getChildren().filter((item) => {
|
|
6791
|
+
return item !== directory && !current.has(item);
|
|
6792
|
+
}).forEach((item) => {
|
|
6793
|
+
this.fsw._remove(directory, item);
|
|
6794
|
+
});
|
|
6795
|
+
stream = void 0;
|
|
6796
|
+
if (wasThrottled)
|
|
6797
|
+
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
6798
|
+
});
|
|
6799
|
+
});
|
|
6800
|
+
}
|
|
6801
|
+
/**
|
|
6802
|
+
* Read directory to add / remove files from `@watched` list and re-read it on change.
|
|
6803
|
+
* @param dir fs path
|
|
6804
|
+
* @param stats
|
|
6805
|
+
* @param initialAdd
|
|
6806
|
+
* @param depth relative to user-supplied path
|
|
6807
|
+
* @param target child path targeted for watch
|
|
6808
|
+
* @param wh Common watch helpers for this path
|
|
6809
|
+
* @param realpath
|
|
6810
|
+
* @returns closer for the watcher instance.
|
|
6811
|
+
*/
|
|
6812
|
+
async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
|
|
6813
|
+
const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
|
|
6814
|
+
const tracked = parentDir.has(sysPath.basename(dir));
|
|
6815
|
+
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
|
|
6816
|
+
this.fsw._emit(EV.ADD_DIR, dir, stats);
|
|
6817
|
+
}
|
|
6818
|
+
parentDir.add(sysPath.basename(dir));
|
|
6819
|
+
this.fsw._getWatchedDir(dir);
|
|
6820
|
+
let throttler;
|
|
6821
|
+
let closer;
|
|
6822
|
+
const oDepth = this.fsw.options.depth;
|
|
6823
|
+
if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
|
|
6824
|
+
if (!target) {
|
|
6825
|
+
await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
|
|
6826
|
+
if (this.fsw.closed)
|
|
6827
|
+
return;
|
|
6828
|
+
}
|
|
6829
|
+
closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
|
|
6830
|
+
if (stats2 && stats2.mtimeMs === 0)
|
|
6831
|
+
return;
|
|
6832
|
+
this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
|
|
6833
|
+
});
|
|
6834
|
+
}
|
|
6835
|
+
return closer;
|
|
6836
|
+
}
|
|
6837
|
+
/**
|
|
6838
|
+
* Handle added file, directory, or glob pattern.
|
|
6839
|
+
* Delegates call to _handleFile / _handleDir after checks.
|
|
6840
|
+
* @param path to file or ir
|
|
6841
|
+
* @param initialAdd was the file added at watch instantiation?
|
|
6842
|
+
* @param priorWh depth relative to user-supplied path
|
|
6843
|
+
* @param depth Child path actually targeted for watch
|
|
6844
|
+
* @param target Child path actually targeted for watch
|
|
6845
|
+
*/
|
|
6846
|
+
async _addToNodeFs(path15, initialAdd, priorWh, depth, target) {
|
|
6847
|
+
const ready = this.fsw._emitReady;
|
|
6848
|
+
if (this.fsw._isIgnored(path15) || this.fsw.closed) {
|
|
6849
|
+
ready();
|
|
6850
|
+
return false;
|
|
6851
|
+
}
|
|
6852
|
+
const wh = this.fsw._getWatchHelpers(path15);
|
|
6853
|
+
if (priorWh) {
|
|
6854
|
+
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
6855
|
+
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
6856
|
+
}
|
|
6857
|
+
try {
|
|
6858
|
+
const stats = await statMethods[wh.statMethod](wh.watchPath);
|
|
6859
|
+
if (this.fsw.closed)
|
|
6860
|
+
return;
|
|
6861
|
+
if (this.fsw._isIgnored(wh.watchPath, stats)) {
|
|
6862
|
+
ready();
|
|
6863
|
+
return false;
|
|
6864
|
+
}
|
|
6865
|
+
const follow = this.fsw.options.followSymlinks;
|
|
6866
|
+
let closer;
|
|
6867
|
+
if (stats.isDirectory()) {
|
|
6868
|
+
const absPath = sysPath.resolve(path15);
|
|
6869
|
+
const targetPath = follow ? await fsrealpath(path15) : path15;
|
|
6870
|
+
if (this.fsw.closed)
|
|
6871
|
+
return;
|
|
6872
|
+
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
6873
|
+
if (this.fsw.closed)
|
|
6874
|
+
return;
|
|
6875
|
+
if (absPath !== targetPath && targetPath !== void 0) {
|
|
6876
|
+
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
6877
|
+
}
|
|
6878
|
+
} else if (stats.isSymbolicLink()) {
|
|
6879
|
+
const targetPath = follow ? await fsrealpath(path15) : path15;
|
|
6880
|
+
if (this.fsw.closed)
|
|
6881
|
+
return;
|
|
6882
|
+
const parent = sysPath.dirname(wh.watchPath);
|
|
6883
|
+
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
6884
|
+
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
6885
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path15, wh, targetPath);
|
|
6886
|
+
if (this.fsw.closed)
|
|
6887
|
+
return;
|
|
6888
|
+
if (targetPath !== void 0) {
|
|
6889
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path15), targetPath);
|
|
6890
|
+
}
|
|
6891
|
+
} else {
|
|
6892
|
+
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
6893
|
+
}
|
|
6894
|
+
ready();
|
|
6895
|
+
if (closer)
|
|
6896
|
+
this.fsw._addPathCloser(path15, closer);
|
|
6897
|
+
return false;
|
|
6898
|
+
} catch (error) {
|
|
6899
|
+
if (this.fsw._handleError(error)) {
|
|
6900
|
+
ready();
|
|
6901
|
+
return path15;
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
});
|
|
5754
6908
|
|
|
5755
|
-
//
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
6909
|
+
// node_modules/chokidar/esm/index.js
|
|
6910
|
+
var esm_exports = {};
|
|
6911
|
+
__export(esm_exports, {
|
|
6912
|
+
FSWatcher: () => FSWatcher,
|
|
6913
|
+
WatchHelper: () => WatchHelper,
|
|
6914
|
+
default: () => esm_default,
|
|
6915
|
+
watch: () => watch
|
|
6916
|
+
});
|
|
6917
|
+
import { stat as statcb } from "fs";
|
|
6918
|
+
import { stat as stat3, readdir as readdir2 } from "fs/promises";
|
|
6919
|
+
import { EventEmitter } from "events";
|
|
6920
|
+
import * as sysPath2 from "path";
|
|
6921
|
+
function arrify(item) {
|
|
6922
|
+
return Array.isArray(item) ? item : [item];
|
|
6923
|
+
}
|
|
6924
|
+
function createPattern(matcher) {
|
|
6925
|
+
if (typeof matcher === "function")
|
|
6926
|
+
return matcher;
|
|
6927
|
+
if (typeof matcher === "string")
|
|
6928
|
+
return (string) => matcher === string;
|
|
6929
|
+
if (matcher instanceof RegExp)
|
|
6930
|
+
return (string) => matcher.test(string);
|
|
6931
|
+
if (typeof matcher === "object" && matcher !== null) {
|
|
6932
|
+
return (string) => {
|
|
6933
|
+
if (matcher.path === string)
|
|
6934
|
+
return true;
|
|
6935
|
+
if (matcher.recursive) {
|
|
6936
|
+
const relative3 = sysPath2.relative(matcher.path, string);
|
|
6937
|
+
if (!relative3) {
|
|
6938
|
+
return false;
|
|
6939
|
+
}
|
|
6940
|
+
return !relative3.startsWith("..") && !sysPath2.isAbsolute(relative3);
|
|
6941
|
+
}
|
|
6942
|
+
return false;
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6945
|
+
return () => false;
|
|
6946
|
+
}
|
|
6947
|
+
function normalizePath(path15) {
|
|
6948
|
+
if (typeof path15 !== "string")
|
|
6949
|
+
throw new Error("string expected");
|
|
6950
|
+
path15 = sysPath2.normalize(path15);
|
|
6951
|
+
path15 = path15.replace(/\\/g, "/");
|
|
6952
|
+
let prepend = false;
|
|
6953
|
+
if (path15.startsWith("//"))
|
|
6954
|
+
prepend = true;
|
|
6955
|
+
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
6956
|
+
while (path15.match(DOUBLE_SLASH_RE2))
|
|
6957
|
+
path15 = path15.replace(DOUBLE_SLASH_RE2, "/");
|
|
6958
|
+
if (prepend)
|
|
6959
|
+
path15 = "/" + path15;
|
|
6960
|
+
return path15;
|
|
6961
|
+
}
|
|
6962
|
+
function matchPatterns(patterns, testString, stats) {
|
|
6963
|
+
const path15 = normalizePath(testString);
|
|
6964
|
+
for (let index2 = 0; index2 < patterns.length; index2++) {
|
|
6965
|
+
const pattern = patterns[index2];
|
|
6966
|
+
if (pattern(path15, stats)) {
|
|
6967
|
+
return true;
|
|
6968
|
+
}
|
|
6969
|
+
}
|
|
6970
|
+
return false;
|
|
6971
|
+
}
|
|
6972
|
+
function anymatch(matchers, testString) {
|
|
6973
|
+
if (matchers == null) {
|
|
6974
|
+
throw new TypeError("anymatch: specify first argument");
|
|
6975
|
+
}
|
|
6976
|
+
const matchersArray = arrify(matchers);
|
|
6977
|
+
const patterns = matchersArray.map((matcher) => createPattern(matcher));
|
|
6978
|
+
if (testString == null) {
|
|
6979
|
+
return (testString2, stats) => {
|
|
6980
|
+
return matchPatterns(patterns, testString2, stats);
|
|
6981
|
+
};
|
|
6982
|
+
}
|
|
6983
|
+
return matchPatterns(patterns, testString);
|
|
6984
|
+
}
|
|
6985
|
+
function watch(paths, options2 = {}) {
|
|
6986
|
+
const watcher = new FSWatcher(options2);
|
|
6987
|
+
watcher.add(paths);
|
|
6988
|
+
return watcher;
|
|
6989
|
+
}
|
|
6990
|
+
var SLASH, SLASH_SLASH, ONE_DOT, TWO_DOTS, STRING_TYPE, BACK_SLASH_RE, DOUBLE_SLASH_RE, DOT_RE, REPLACER_RE, isMatcherObject, unifyPaths, toUnix, normalizePathToUnix, normalizeIgnored, getAbsolutePath, EMPTY_SET, DirEntry, STAT_METHOD_F, STAT_METHOD_L, WatchHelper, FSWatcher, esm_default;
|
|
6991
|
+
var init_esm2 = __esm({
|
|
6992
|
+
"node_modules/chokidar/esm/index.js"() {
|
|
6993
|
+
"use strict";
|
|
6994
|
+
init_esm();
|
|
6995
|
+
init_handler();
|
|
6996
|
+
SLASH = "/";
|
|
6997
|
+
SLASH_SLASH = "//";
|
|
6998
|
+
ONE_DOT = ".";
|
|
6999
|
+
TWO_DOTS = "..";
|
|
7000
|
+
STRING_TYPE = "string";
|
|
7001
|
+
BACK_SLASH_RE = /\\/g;
|
|
7002
|
+
DOUBLE_SLASH_RE = /\/\//;
|
|
7003
|
+
DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
|
|
7004
|
+
REPLACER_RE = /^\.[/\\]/;
|
|
7005
|
+
isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
|
|
7006
|
+
unifyPaths = (paths_) => {
|
|
7007
|
+
const paths = arrify(paths_).flat();
|
|
7008
|
+
if (!paths.every((p) => typeof p === STRING_TYPE)) {
|
|
7009
|
+
throw new TypeError(`Non-string provided as watch path: ${paths}`);
|
|
7010
|
+
}
|
|
7011
|
+
return paths.map(normalizePathToUnix);
|
|
7012
|
+
};
|
|
7013
|
+
toUnix = (string) => {
|
|
7014
|
+
let str2 = string.replace(BACK_SLASH_RE, SLASH);
|
|
7015
|
+
let prepend = false;
|
|
7016
|
+
if (str2.startsWith(SLASH_SLASH)) {
|
|
7017
|
+
prepend = true;
|
|
7018
|
+
}
|
|
7019
|
+
while (str2.match(DOUBLE_SLASH_RE)) {
|
|
7020
|
+
str2 = str2.replace(DOUBLE_SLASH_RE, SLASH);
|
|
7021
|
+
}
|
|
7022
|
+
if (prepend) {
|
|
7023
|
+
str2 = SLASH + str2;
|
|
7024
|
+
}
|
|
7025
|
+
return str2;
|
|
7026
|
+
};
|
|
7027
|
+
normalizePathToUnix = (path15) => toUnix(sysPath2.normalize(toUnix(path15)));
|
|
7028
|
+
normalizeIgnored = (cwd = "") => (path15) => {
|
|
7029
|
+
if (typeof path15 === "string") {
|
|
7030
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path15) ? path15 : sysPath2.join(cwd, path15));
|
|
7031
|
+
} else {
|
|
7032
|
+
return path15;
|
|
7033
|
+
}
|
|
7034
|
+
};
|
|
7035
|
+
getAbsolutePath = (path15, cwd) => {
|
|
7036
|
+
if (sysPath2.isAbsolute(path15)) {
|
|
7037
|
+
return path15;
|
|
7038
|
+
}
|
|
7039
|
+
return sysPath2.join(cwd, path15);
|
|
7040
|
+
};
|
|
7041
|
+
EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
7042
|
+
DirEntry = class {
|
|
7043
|
+
constructor(dir, removeWatcher) {
|
|
7044
|
+
this.path = dir;
|
|
7045
|
+
this._removeWatcher = removeWatcher;
|
|
7046
|
+
this.items = /* @__PURE__ */ new Set();
|
|
7047
|
+
}
|
|
7048
|
+
add(item) {
|
|
7049
|
+
const { items } = this;
|
|
7050
|
+
if (!items)
|
|
7051
|
+
return;
|
|
7052
|
+
if (item !== ONE_DOT && item !== TWO_DOTS)
|
|
7053
|
+
items.add(item);
|
|
7054
|
+
}
|
|
7055
|
+
async remove(item) {
|
|
7056
|
+
const { items } = this;
|
|
7057
|
+
if (!items)
|
|
7058
|
+
return;
|
|
7059
|
+
items.delete(item);
|
|
7060
|
+
if (items.size > 0)
|
|
7061
|
+
return;
|
|
7062
|
+
const dir = this.path;
|
|
7063
|
+
try {
|
|
7064
|
+
await readdir2(dir);
|
|
7065
|
+
} catch (err) {
|
|
7066
|
+
if (this._removeWatcher) {
|
|
7067
|
+
this._removeWatcher(sysPath2.dirname(dir), sysPath2.basename(dir));
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
has(item) {
|
|
7072
|
+
const { items } = this;
|
|
7073
|
+
if (!items)
|
|
7074
|
+
return;
|
|
7075
|
+
return items.has(item);
|
|
7076
|
+
}
|
|
7077
|
+
getChildren() {
|
|
7078
|
+
const { items } = this;
|
|
7079
|
+
if (!items)
|
|
7080
|
+
return [];
|
|
7081
|
+
return [...items.values()];
|
|
7082
|
+
}
|
|
7083
|
+
dispose() {
|
|
7084
|
+
this.items.clear();
|
|
7085
|
+
this.path = "";
|
|
7086
|
+
this._removeWatcher = EMPTY_FN;
|
|
7087
|
+
this.items = EMPTY_SET;
|
|
7088
|
+
Object.freeze(this);
|
|
7089
|
+
}
|
|
7090
|
+
};
|
|
7091
|
+
STAT_METHOD_F = "stat";
|
|
7092
|
+
STAT_METHOD_L = "lstat";
|
|
7093
|
+
WatchHelper = class {
|
|
7094
|
+
constructor(path15, follow, fsw) {
|
|
7095
|
+
this.fsw = fsw;
|
|
7096
|
+
const watchPath = path15;
|
|
7097
|
+
this.path = path15 = path15.replace(REPLACER_RE, "");
|
|
7098
|
+
this.watchPath = watchPath;
|
|
7099
|
+
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
7100
|
+
this.dirParts = [];
|
|
7101
|
+
this.dirParts.forEach((parts) => {
|
|
7102
|
+
if (parts.length > 1)
|
|
7103
|
+
parts.pop();
|
|
7104
|
+
});
|
|
7105
|
+
this.followSymlinks = follow;
|
|
7106
|
+
this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
|
|
7107
|
+
}
|
|
7108
|
+
entryPath(entry) {
|
|
7109
|
+
return sysPath2.join(this.watchPath, sysPath2.relative(this.watchPath, entry.fullPath));
|
|
7110
|
+
}
|
|
7111
|
+
filterPath(entry) {
|
|
7112
|
+
const { stats } = entry;
|
|
7113
|
+
if (stats && stats.isSymbolicLink())
|
|
7114
|
+
return this.filterDir(entry);
|
|
7115
|
+
const resolvedPath = this.entryPath(entry);
|
|
7116
|
+
return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
|
|
7117
|
+
}
|
|
7118
|
+
filterDir(entry) {
|
|
7119
|
+
return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
|
|
7120
|
+
}
|
|
7121
|
+
};
|
|
7122
|
+
FSWatcher = class extends EventEmitter {
|
|
7123
|
+
// Not indenting methods for history sake; for now.
|
|
7124
|
+
constructor(_opts = {}) {
|
|
7125
|
+
super();
|
|
7126
|
+
this.closed = false;
|
|
7127
|
+
this._closers = /* @__PURE__ */ new Map();
|
|
7128
|
+
this._ignoredPaths = /* @__PURE__ */ new Set();
|
|
7129
|
+
this._throttled = /* @__PURE__ */ new Map();
|
|
7130
|
+
this._streams = /* @__PURE__ */ new Set();
|
|
7131
|
+
this._symlinkPaths = /* @__PURE__ */ new Map();
|
|
7132
|
+
this._watched = /* @__PURE__ */ new Map();
|
|
7133
|
+
this._pendingWrites = /* @__PURE__ */ new Map();
|
|
7134
|
+
this._pendingUnlinks = /* @__PURE__ */ new Map();
|
|
7135
|
+
this._readyCount = 0;
|
|
7136
|
+
this._readyEmitted = false;
|
|
7137
|
+
const awf = _opts.awaitWriteFinish;
|
|
7138
|
+
const DEF_AWF = { stabilityThreshold: 2e3, pollInterval: 100 };
|
|
7139
|
+
const opts = {
|
|
7140
|
+
// Defaults
|
|
7141
|
+
persistent: true,
|
|
7142
|
+
ignoreInitial: false,
|
|
7143
|
+
ignorePermissionErrors: false,
|
|
7144
|
+
interval: 100,
|
|
7145
|
+
binaryInterval: 300,
|
|
7146
|
+
followSymlinks: true,
|
|
7147
|
+
usePolling: false,
|
|
7148
|
+
// useAsync: false,
|
|
7149
|
+
atomic: true,
|
|
7150
|
+
// NOTE: overwritten later (depends on usePolling)
|
|
7151
|
+
..._opts,
|
|
7152
|
+
// Change format
|
|
7153
|
+
ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
|
|
7154
|
+
awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
|
|
7155
|
+
};
|
|
7156
|
+
if (isIBMi)
|
|
7157
|
+
opts.usePolling = true;
|
|
7158
|
+
if (opts.atomic === void 0)
|
|
7159
|
+
opts.atomic = !opts.usePolling;
|
|
7160
|
+
const envPoll = process.env.CHOKIDAR_USEPOLLING;
|
|
7161
|
+
if (envPoll !== void 0) {
|
|
7162
|
+
const envLower = envPoll.toLowerCase();
|
|
7163
|
+
if (envLower === "false" || envLower === "0")
|
|
7164
|
+
opts.usePolling = false;
|
|
7165
|
+
else if (envLower === "true" || envLower === "1")
|
|
7166
|
+
opts.usePolling = true;
|
|
7167
|
+
else
|
|
7168
|
+
opts.usePolling = !!envLower;
|
|
7169
|
+
}
|
|
7170
|
+
const envInterval = process.env.CHOKIDAR_INTERVAL;
|
|
7171
|
+
if (envInterval)
|
|
7172
|
+
opts.interval = Number.parseInt(envInterval, 10);
|
|
7173
|
+
let readyCalls = 0;
|
|
7174
|
+
this._emitReady = () => {
|
|
7175
|
+
readyCalls++;
|
|
7176
|
+
if (readyCalls >= this._readyCount) {
|
|
7177
|
+
this._emitReady = EMPTY_FN;
|
|
7178
|
+
this._readyEmitted = true;
|
|
7179
|
+
process.nextTick(() => this.emit(EVENTS.READY));
|
|
7180
|
+
}
|
|
7181
|
+
};
|
|
7182
|
+
this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
|
|
7183
|
+
this._boundRemove = this._remove.bind(this);
|
|
7184
|
+
this.options = opts;
|
|
7185
|
+
this._nodeFsHandler = new NodeFsHandler(this);
|
|
7186
|
+
Object.freeze(opts);
|
|
7187
|
+
}
|
|
7188
|
+
_addIgnoredPath(matcher) {
|
|
7189
|
+
if (isMatcherObject(matcher)) {
|
|
7190
|
+
for (const ignored of this._ignoredPaths) {
|
|
7191
|
+
if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
|
|
7192
|
+
return;
|
|
7193
|
+
}
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
this._ignoredPaths.add(matcher);
|
|
7197
|
+
}
|
|
7198
|
+
_removeIgnoredPath(matcher) {
|
|
7199
|
+
this._ignoredPaths.delete(matcher);
|
|
7200
|
+
if (typeof matcher === "string") {
|
|
7201
|
+
for (const ignored of this._ignoredPaths) {
|
|
7202
|
+
if (isMatcherObject(ignored) && ignored.path === matcher) {
|
|
7203
|
+
this._ignoredPaths.delete(ignored);
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
// Public methods
|
|
7209
|
+
/**
|
|
7210
|
+
* Adds paths to be watched on an existing FSWatcher instance.
|
|
7211
|
+
* @param paths_ file or file list. Other arguments are unused
|
|
7212
|
+
*/
|
|
7213
|
+
add(paths_, _origAdd, _internal) {
|
|
7214
|
+
const { cwd } = this.options;
|
|
7215
|
+
this.closed = false;
|
|
7216
|
+
this._closePromise = void 0;
|
|
7217
|
+
let paths = unifyPaths(paths_);
|
|
7218
|
+
if (cwd) {
|
|
7219
|
+
paths = paths.map((path15) => {
|
|
7220
|
+
const absPath = getAbsolutePath(path15, cwd);
|
|
7221
|
+
return absPath;
|
|
7222
|
+
});
|
|
7223
|
+
}
|
|
7224
|
+
paths.forEach((path15) => {
|
|
7225
|
+
this._removeIgnoredPath(path15);
|
|
7226
|
+
});
|
|
7227
|
+
this._userIgnored = void 0;
|
|
7228
|
+
if (!this._readyCount)
|
|
7229
|
+
this._readyCount = 0;
|
|
7230
|
+
this._readyCount += paths.length;
|
|
7231
|
+
Promise.all(paths.map(async (path15) => {
|
|
7232
|
+
const res = await this._nodeFsHandler._addToNodeFs(path15, !_internal, void 0, 0, _origAdd);
|
|
7233
|
+
if (res)
|
|
7234
|
+
this._emitReady();
|
|
7235
|
+
return res;
|
|
7236
|
+
})).then((results) => {
|
|
7237
|
+
if (this.closed)
|
|
7238
|
+
return;
|
|
7239
|
+
results.forEach((item) => {
|
|
7240
|
+
if (item)
|
|
7241
|
+
this.add(sysPath2.dirname(item), sysPath2.basename(_origAdd || item));
|
|
7242
|
+
});
|
|
7243
|
+
});
|
|
7244
|
+
return this;
|
|
7245
|
+
}
|
|
7246
|
+
/**
|
|
7247
|
+
* Close watchers or start ignoring events from specified paths.
|
|
7248
|
+
*/
|
|
7249
|
+
unwatch(paths_) {
|
|
7250
|
+
if (this.closed)
|
|
7251
|
+
return this;
|
|
7252
|
+
const paths = unifyPaths(paths_);
|
|
7253
|
+
const { cwd } = this.options;
|
|
7254
|
+
paths.forEach((path15) => {
|
|
7255
|
+
if (!sysPath2.isAbsolute(path15) && !this._closers.has(path15)) {
|
|
7256
|
+
if (cwd)
|
|
7257
|
+
path15 = sysPath2.join(cwd, path15);
|
|
7258
|
+
path15 = sysPath2.resolve(path15);
|
|
7259
|
+
}
|
|
7260
|
+
this._closePath(path15);
|
|
7261
|
+
this._addIgnoredPath(path15);
|
|
7262
|
+
if (this._watched.has(path15)) {
|
|
7263
|
+
this._addIgnoredPath({
|
|
7264
|
+
path: path15,
|
|
7265
|
+
recursive: true
|
|
7266
|
+
});
|
|
7267
|
+
}
|
|
7268
|
+
this._userIgnored = void 0;
|
|
7269
|
+
});
|
|
7270
|
+
return this;
|
|
7271
|
+
}
|
|
7272
|
+
/**
|
|
7273
|
+
* Close watchers and remove all listeners from watched paths.
|
|
7274
|
+
*/
|
|
7275
|
+
close() {
|
|
7276
|
+
if (this._closePromise) {
|
|
7277
|
+
return this._closePromise;
|
|
7278
|
+
}
|
|
7279
|
+
this.closed = true;
|
|
7280
|
+
this.removeAllListeners();
|
|
7281
|
+
const closers = [];
|
|
7282
|
+
this._closers.forEach((closerList) => closerList.forEach((closer) => {
|
|
7283
|
+
const promise = closer();
|
|
7284
|
+
if (promise instanceof Promise)
|
|
7285
|
+
closers.push(promise);
|
|
7286
|
+
}));
|
|
7287
|
+
this._streams.forEach((stream) => stream.destroy());
|
|
7288
|
+
this._userIgnored = void 0;
|
|
7289
|
+
this._readyCount = 0;
|
|
7290
|
+
this._readyEmitted = false;
|
|
7291
|
+
this._watched.forEach((dirent) => dirent.dispose());
|
|
7292
|
+
this._closers.clear();
|
|
7293
|
+
this._watched.clear();
|
|
7294
|
+
this._streams.clear();
|
|
7295
|
+
this._symlinkPaths.clear();
|
|
7296
|
+
this._throttled.clear();
|
|
7297
|
+
this._closePromise = closers.length ? Promise.all(closers).then(() => void 0) : Promise.resolve();
|
|
7298
|
+
return this._closePromise;
|
|
7299
|
+
}
|
|
7300
|
+
/**
|
|
7301
|
+
* Expose list of watched paths
|
|
7302
|
+
* @returns for chaining
|
|
7303
|
+
*/
|
|
7304
|
+
getWatched() {
|
|
7305
|
+
const watchList = {};
|
|
7306
|
+
this._watched.forEach((entry, dir) => {
|
|
7307
|
+
const key = this.options.cwd ? sysPath2.relative(this.options.cwd, dir) : dir;
|
|
7308
|
+
const index2 = key || ONE_DOT;
|
|
7309
|
+
watchList[index2] = entry.getChildren().sort();
|
|
7310
|
+
});
|
|
7311
|
+
return watchList;
|
|
7312
|
+
}
|
|
7313
|
+
emitWithAll(event, args) {
|
|
7314
|
+
this.emit(event, ...args);
|
|
7315
|
+
if (event !== EVENTS.ERROR)
|
|
7316
|
+
this.emit(EVENTS.ALL, event, ...args);
|
|
7317
|
+
}
|
|
7318
|
+
// Common helpers
|
|
7319
|
+
// --------------
|
|
7320
|
+
/**
|
|
7321
|
+
* Normalize and emit events.
|
|
7322
|
+
* Calling _emit DOES NOT MEAN emit() would be called!
|
|
7323
|
+
* @param event Type of event
|
|
7324
|
+
* @param path File or directory path
|
|
7325
|
+
* @param stats arguments to be passed with event
|
|
7326
|
+
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
7327
|
+
*/
|
|
7328
|
+
async _emit(event, path15, stats) {
|
|
7329
|
+
if (this.closed)
|
|
7330
|
+
return;
|
|
7331
|
+
const opts = this.options;
|
|
7332
|
+
if (isWindows)
|
|
7333
|
+
path15 = sysPath2.normalize(path15);
|
|
7334
|
+
if (opts.cwd)
|
|
7335
|
+
path15 = sysPath2.relative(opts.cwd, path15);
|
|
7336
|
+
const args = [path15];
|
|
7337
|
+
if (stats != null)
|
|
7338
|
+
args.push(stats);
|
|
7339
|
+
const awf = opts.awaitWriteFinish;
|
|
7340
|
+
let pw;
|
|
7341
|
+
if (awf && (pw = this._pendingWrites.get(path15))) {
|
|
7342
|
+
pw.lastChange = /* @__PURE__ */ new Date();
|
|
7343
|
+
return this;
|
|
7344
|
+
}
|
|
7345
|
+
if (opts.atomic) {
|
|
7346
|
+
if (event === EVENTS.UNLINK) {
|
|
7347
|
+
this._pendingUnlinks.set(path15, [event, ...args]);
|
|
7348
|
+
setTimeout(() => {
|
|
7349
|
+
this._pendingUnlinks.forEach((entry, path16) => {
|
|
7350
|
+
this.emit(...entry);
|
|
7351
|
+
this.emit(EVENTS.ALL, ...entry);
|
|
7352
|
+
this._pendingUnlinks.delete(path16);
|
|
7353
|
+
});
|
|
7354
|
+
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
7355
|
+
return this;
|
|
7356
|
+
}
|
|
7357
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path15)) {
|
|
7358
|
+
event = EVENTS.CHANGE;
|
|
7359
|
+
this._pendingUnlinks.delete(path15);
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
7362
|
+
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
7363
|
+
const awfEmit = (err, stats2) => {
|
|
7364
|
+
if (err) {
|
|
7365
|
+
event = EVENTS.ERROR;
|
|
7366
|
+
args[0] = err;
|
|
7367
|
+
this.emitWithAll(event, args);
|
|
7368
|
+
} else if (stats2) {
|
|
7369
|
+
if (args.length > 1) {
|
|
7370
|
+
args[1] = stats2;
|
|
7371
|
+
} else {
|
|
7372
|
+
args.push(stats2);
|
|
7373
|
+
}
|
|
7374
|
+
this.emitWithAll(event, args);
|
|
7375
|
+
}
|
|
7376
|
+
};
|
|
7377
|
+
this._awaitWriteFinish(path15, awf.stabilityThreshold, event, awfEmit);
|
|
7378
|
+
return this;
|
|
7379
|
+
}
|
|
7380
|
+
if (event === EVENTS.CHANGE) {
|
|
7381
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path15, 50);
|
|
7382
|
+
if (isThrottled)
|
|
7383
|
+
return this;
|
|
7384
|
+
}
|
|
7385
|
+
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
7386
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path15) : path15;
|
|
7387
|
+
let stats2;
|
|
7388
|
+
try {
|
|
7389
|
+
stats2 = await stat3(fullPath);
|
|
7390
|
+
} catch (err) {
|
|
7391
|
+
}
|
|
7392
|
+
if (!stats2 || this.closed)
|
|
7393
|
+
return;
|
|
7394
|
+
args.push(stats2);
|
|
7395
|
+
}
|
|
7396
|
+
this.emitWithAll(event, args);
|
|
7397
|
+
return this;
|
|
7398
|
+
}
|
|
7399
|
+
/**
|
|
7400
|
+
* Common handler for errors
|
|
7401
|
+
* @returns The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
7402
|
+
*/
|
|
7403
|
+
_handleError(error) {
|
|
7404
|
+
const code = error && error.code;
|
|
7405
|
+
if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
|
|
7406
|
+
this.emit(EVENTS.ERROR, error);
|
|
7407
|
+
}
|
|
7408
|
+
return error || this.closed;
|
|
7409
|
+
}
|
|
7410
|
+
/**
|
|
7411
|
+
* Helper utility for throttling
|
|
7412
|
+
* @param actionType type being throttled
|
|
7413
|
+
* @param path being acted upon
|
|
7414
|
+
* @param timeout duration of time to suppress duplicate actions
|
|
7415
|
+
* @returns tracking object or false if action should be suppressed
|
|
7416
|
+
*/
|
|
7417
|
+
_throttle(actionType, path15, timeout) {
|
|
7418
|
+
if (!this._throttled.has(actionType)) {
|
|
7419
|
+
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
7420
|
+
}
|
|
7421
|
+
const action = this._throttled.get(actionType);
|
|
7422
|
+
if (!action)
|
|
7423
|
+
throw new Error("invalid throttle");
|
|
7424
|
+
const actionPath = action.get(path15);
|
|
7425
|
+
if (actionPath) {
|
|
7426
|
+
actionPath.count++;
|
|
7427
|
+
return false;
|
|
7428
|
+
}
|
|
7429
|
+
let timeoutObject;
|
|
7430
|
+
const clear = () => {
|
|
7431
|
+
const item = action.get(path15);
|
|
7432
|
+
const count2 = item ? item.count : 0;
|
|
7433
|
+
action.delete(path15);
|
|
7434
|
+
clearTimeout(timeoutObject);
|
|
7435
|
+
if (item)
|
|
7436
|
+
clearTimeout(item.timeoutObject);
|
|
7437
|
+
return count2;
|
|
7438
|
+
};
|
|
7439
|
+
timeoutObject = setTimeout(clear, timeout);
|
|
7440
|
+
const thr = { timeoutObject, clear, count: 0 };
|
|
7441
|
+
action.set(path15, thr);
|
|
7442
|
+
return thr;
|
|
7443
|
+
}
|
|
7444
|
+
_incrReadyCount() {
|
|
7445
|
+
return this._readyCount++;
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Awaits write operation to finish.
|
|
7449
|
+
* Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
|
|
7450
|
+
* @param path being acted upon
|
|
7451
|
+
* @param threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
|
|
7452
|
+
* @param event
|
|
7453
|
+
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
7454
|
+
*/
|
|
7455
|
+
_awaitWriteFinish(path15, threshold, event, awfEmit) {
|
|
7456
|
+
const awf = this.options.awaitWriteFinish;
|
|
7457
|
+
if (typeof awf !== "object")
|
|
7458
|
+
return;
|
|
7459
|
+
const pollInterval = awf.pollInterval;
|
|
7460
|
+
let timeoutHandler;
|
|
7461
|
+
let fullPath = path15;
|
|
7462
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path15)) {
|
|
7463
|
+
fullPath = sysPath2.join(this.options.cwd, path15);
|
|
7464
|
+
}
|
|
7465
|
+
const now = /* @__PURE__ */ new Date();
|
|
7466
|
+
const writes = this._pendingWrites;
|
|
7467
|
+
function awaitWriteFinishFn(prevStat) {
|
|
7468
|
+
statcb(fullPath, (err, curStat) => {
|
|
7469
|
+
if (err || !writes.has(path15)) {
|
|
7470
|
+
if (err && err.code !== "ENOENT")
|
|
7471
|
+
awfEmit(err);
|
|
7472
|
+
return;
|
|
7473
|
+
}
|
|
7474
|
+
const now2 = Number(/* @__PURE__ */ new Date());
|
|
7475
|
+
if (prevStat && curStat.size !== prevStat.size) {
|
|
7476
|
+
writes.get(path15).lastChange = now2;
|
|
7477
|
+
}
|
|
7478
|
+
const pw = writes.get(path15);
|
|
7479
|
+
const df = now2 - pw.lastChange;
|
|
7480
|
+
if (df >= threshold) {
|
|
7481
|
+
writes.delete(path15);
|
|
7482
|
+
awfEmit(void 0, curStat);
|
|
7483
|
+
} else {
|
|
7484
|
+
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
7485
|
+
}
|
|
7486
|
+
});
|
|
7487
|
+
}
|
|
7488
|
+
if (!writes.has(path15)) {
|
|
7489
|
+
writes.set(path15, {
|
|
7490
|
+
lastChange: now,
|
|
7491
|
+
cancelWait: () => {
|
|
7492
|
+
writes.delete(path15);
|
|
7493
|
+
clearTimeout(timeoutHandler);
|
|
7494
|
+
return event;
|
|
7495
|
+
}
|
|
7496
|
+
});
|
|
7497
|
+
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
/**
|
|
7501
|
+
* Determines whether user has asked to ignore this path.
|
|
7502
|
+
*/
|
|
7503
|
+
_isIgnored(path15, stats) {
|
|
7504
|
+
if (this.options.atomic && DOT_RE.test(path15))
|
|
7505
|
+
return true;
|
|
7506
|
+
if (!this._userIgnored) {
|
|
7507
|
+
const { cwd } = this.options;
|
|
7508
|
+
const ign = this.options.ignored;
|
|
7509
|
+
const ignored = (ign || []).map(normalizeIgnored(cwd));
|
|
7510
|
+
const ignoredPaths = [...this._ignoredPaths];
|
|
7511
|
+
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
7512
|
+
this._userIgnored = anymatch(list, void 0);
|
|
7513
|
+
}
|
|
7514
|
+
return this._userIgnored(path15, stats);
|
|
7515
|
+
}
|
|
7516
|
+
_isntIgnored(path15, stat4) {
|
|
7517
|
+
return !this._isIgnored(path15, stat4);
|
|
7518
|
+
}
|
|
7519
|
+
/**
|
|
7520
|
+
* Provides a set of common helpers and properties relating to symlink handling.
|
|
7521
|
+
* @param path file or directory pattern being watched
|
|
7522
|
+
*/
|
|
7523
|
+
_getWatchHelpers(path15) {
|
|
7524
|
+
return new WatchHelper(path15, this.options.followSymlinks, this);
|
|
7525
|
+
}
|
|
7526
|
+
// Directory helpers
|
|
7527
|
+
// -----------------
|
|
7528
|
+
/**
|
|
7529
|
+
* Provides directory tracking objects
|
|
7530
|
+
* @param directory path of the directory
|
|
7531
|
+
*/
|
|
7532
|
+
_getWatchedDir(directory) {
|
|
7533
|
+
const dir = sysPath2.resolve(directory);
|
|
7534
|
+
if (!this._watched.has(dir))
|
|
7535
|
+
this._watched.set(dir, new DirEntry(dir, this._boundRemove));
|
|
7536
|
+
return this._watched.get(dir);
|
|
7537
|
+
}
|
|
7538
|
+
// File helpers
|
|
7539
|
+
// ------------
|
|
7540
|
+
/**
|
|
7541
|
+
* Check for read permissions: https://stackoverflow.com/a/11781404/1358405
|
|
7542
|
+
*/
|
|
7543
|
+
_hasReadPermissions(stats) {
|
|
7544
|
+
if (this.options.ignorePermissionErrors)
|
|
7545
|
+
return true;
|
|
7546
|
+
return Boolean(Number(stats.mode) & 256);
|
|
7547
|
+
}
|
|
7548
|
+
/**
|
|
7549
|
+
* Handles emitting unlink events for
|
|
7550
|
+
* files and directories, and via recursion, for
|
|
7551
|
+
* files and directories within directories that are unlinked
|
|
7552
|
+
* @param directory within which the following item is located
|
|
7553
|
+
* @param item base path of item/directory
|
|
7554
|
+
*/
|
|
7555
|
+
_remove(directory, item, isDirectory) {
|
|
7556
|
+
const path15 = sysPath2.join(directory, item);
|
|
7557
|
+
const fullPath = sysPath2.resolve(path15);
|
|
7558
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path15) || this._watched.has(fullPath);
|
|
7559
|
+
if (!this._throttle("remove", path15, 100))
|
|
7560
|
+
return;
|
|
7561
|
+
if (!isDirectory && this._watched.size === 1) {
|
|
7562
|
+
this.add(directory, item, true);
|
|
7563
|
+
}
|
|
7564
|
+
const wp = this._getWatchedDir(path15);
|
|
7565
|
+
const nestedDirectoryChildren = wp.getChildren();
|
|
7566
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path15, nested));
|
|
7567
|
+
const parent = this._getWatchedDir(directory);
|
|
7568
|
+
const wasTracked = parent.has(item);
|
|
7569
|
+
parent.remove(item);
|
|
7570
|
+
if (this._symlinkPaths.has(fullPath)) {
|
|
7571
|
+
this._symlinkPaths.delete(fullPath);
|
|
7572
|
+
}
|
|
7573
|
+
let relPath = path15;
|
|
7574
|
+
if (this.options.cwd)
|
|
7575
|
+
relPath = sysPath2.relative(this.options.cwd, path15);
|
|
7576
|
+
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
7577
|
+
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
7578
|
+
if (event === EVENTS.ADD)
|
|
7579
|
+
return;
|
|
7580
|
+
}
|
|
7581
|
+
this._watched.delete(path15);
|
|
7582
|
+
this._watched.delete(fullPath);
|
|
7583
|
+
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
7584
|
+
if (wasTracked && !this._isIgnored(path15))
|
|
7585
|
+
this._emit(eventName, path15);
|
|
7586
|
+
this._closePath(path15);
|
|
7587
|
+
}
|
|
7588
|
+
/**
|
|
7589
|
+
* Closes all watchers for a path
|
|
7590
|
+
*/
|
|
7591
|
+
_closePath(path15) {
|
|
7592
|
+
this._closeFile(path15);
|
|
7593
|
+
const dir = sysPath2.dirname(path15);
|
|
7594
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path15));
|
|
7595
|
+
}
|
|
7596
|
+
/**
|
|
7597
|
+
* Closes only file-specific watchers
|
|
7598
|
+
*/
|
|
7599
|
+
_closeFile(path15) {
|
|
7600
|
+
const closers = this._closers.get(path15);
|
|
7601
|
+
if (!closers)
|
|
7602
|
+
return;
|
|
7603
|
+
closers.forEach((closer) => closer());
|
|
7604
|
+
this._closers.delete(path15);
|
|
7605
|
+
}
|
|
7606
|
+
_addPathCloser(path15, closer) {
|
|
7607
|
+
if (!closer)
|
|
7608
|
+
return;
|
|
7609
|
+
let list = this._closers.get(path15);
|
|
7610
|
+
if (!list) {
|
|
7611
|
+
list = [];
|
|
7612
|
+
this._closers.set(path15, list);
|
|
7613
|
+
}
|
|
7614
|
+
list.push(closer);
|
|
7615
|
+
}
|
|
7616
|
+
_readdirp(root, opts) {
|
|
7617
|
+
if (this.closed)
|
|
7618
|
+
return;
|
|
7619
|
+
const options2 = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
7620
|
+
let stream = readdirp(root, options2);
|
|
7621
|
+
this._streams.add(stream);
|
|
7622
|
+
stream.once(STR_CLOSE, () => {
|
|
7623
|
+
stream = void 0;
|
|
7624
|
+
});
|
|
7625
|
+
stream.once(STR_END, () => {
|
|
7626
|
+
if (stream) {
|
|
7627
|
+
this._streams.delete(stream);
|
|
7628
|
+
stream = void 0;
|
|
7629
|
+
}
|
|
7630
|
+
});
|
|
7631
|
+
return stream;
|
|
7632
|
+
}
|
|
7633
|
+
};
|
|
7634
|
+
esm_default = { watch, FSWatcher };
|
|
7635
|
+
}
|
|
7636
|
+
});
|
|
7637
|
+
|
|
7638
|
+
// spex-console/bin/hs-vault-pull.ts
|
|
7639
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
7640
|
+
import path14 from "node:path";
|
|
7641
|
+
import process4 from "node:process";
|
|
7642
|
+
|
|
7643
|
+
// spex-console/src/lib/cli/control-shared.ts
|
|
7644
|
+
init_hs_command();
|
|
7645
|
+
init_console_fetch();
|
|
7646
|
+
init_credentials();
|
|
7647
|
+
init_session_owner();
|
|
7648
|
+
init_vault_less_endpoints();
|
|
7649
|
+
init_registry_file();
|
|
7650
|
+
import path from "node:path";
|
|
7651
|
+
var VAULT_ID_HEADER = "x-heyspex-vault-id";
|
|
7652
|
+
var ACCOUNT_ID_HEADER = "x-heyspex-account-id";
|
|
7653
|
+
function scopeHeadersFor(scope, method, path15) {
|
|
7654
|
+
if (scope === "none") {
|
|
7655
|
+
if (!isVaultLessEndpoint(method, path15)) {
|
|
7656
|
+
throw new Error(
|
|
7657
|
+
`controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path15} is vault-scoped and must name { accountId, vaultId }.`
|
|
7658
|
+
);
|
|
7659
|
+
}
|
|
7660
|
+
return {};
|
|
7661
|
+
}
|
|
7662
|
+
return { [VAULT_ID_HEADER]: scope.vaultId, [ACCOUNT_ID_HEADER]: scope.accountId };
|
|
7663
|
+
}
|
|
7664
|
+
function cloudRequestScope(scope) {
|
|
7665
|
+
const named = { accountId: scope.accountId, vaultId: scope.vaultId };
|
|
7666
|
+
return {
|
|
7667
|
+
...named,
|
|
7668
|
+
headersFor: (method, path15) => isVaultLessEndpoint(method, path15) ? {} : scopeHeadersFor(named, method, path15)
|
|
7669
|
+
};
|
|
7670
|
+
}
|
|
7671
|
+
|
|
7672
|
+
// spex-console/src/lib/cli/init.ts
|
|
7673
|
+
init_console_url_resolver();
|
|
7674
|
+
import { randomUUID } from "node:crypto";
|
|
7675
|
+
function parseVaultId(content) {
|
|
7676
|
+
const m = content.match(/^vault_id:\s*(.+?)\s*$/m);
|
|
5760
7677
|
if (!m) return null;
|
|
5761
7678
|
const raw = m[1].trim();
|
|
5762
7679
|
const unquoted = raw.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
@@ -5822,6 +7739,7 @@ var PULL_HELP = [
|
|
|
5822
7739
|
"Usage:",
|
|
5823
7740
|
" hs pull [--apply] [--only <id|basename>[,...]] [--overwrite]",
|
|
5824
7741
|
" [--from <console-url>] [--detail] [--json]",
|
|
7742
|
+
" hs pull --apply --watch [--interval <seconds>] [--push [--force]] [--from <console-url>]",
|
|
5825
7743
|
"",
|
|
5826
7744
|
"Options:",
|
|
5827
7745
|
" --apply Actually write. WITHOUT it, hs pull only REPORTS a plan.",
|
|
@@ -5831,6 +7749,16 @@ var PULL_HELP = [
|
|
|
5831
7749
|
" --from <url> Source console (default: the cloud console, app.heyspex.com).",
|
|
5832
7750
|
" --detail List every skipped spec instead of a bounded sample.",
|
|
5833
7751
|
" --json Emit the raw result object.",
|
|
7752
|
+
" --watch Keep running: pull now, then every --interval seconds,",
|
|
7753
|
+
" backing off while idle. Requires --apply. One line per",
|
|
7754
|
+
" tick that wrote something, silence otherwise. This is",
|
|
7755
|
+
" what the Companion runs (SpexRelay's cloud \u2192 local leg).",
|
|
7756
|
+
" --interval <s> Base tick for --watch (default 60).",
|
|
7757
|
+
" --push With --watch: also run the disk -> cloud leg. File edits under",
|
|
7758
|
+
" specs/ are pushed (1 s debounce, never status, never creating",
|
|
7759
|
+
" specs, never over a newer cloud copy). A file with no sync",
|
|
7760
|
+
" record is only SEEDED, never pushed. See `hs push`.",
|
|
7761
|
+
" --force With --push: let a disk edit overwrite a newer cloud copy.",
|
|
5834
7762
|
"",
|
|
5835
7763
|
"Which vault: --vault <id> (global flag) > this repo's heyspex.yaml vault_id >",
|
|
5836
7764
|
"the account's active vault read from the console. The registry.json pointer",
|
|
@@ -5843,11 +7771,18 @@ var PULL_HELP = [
|
|
|
5843
7771
|
" 2 Usage error",
|
|
5844
7772
|
" 11 No vault could be resolved, or --apply would write another vault into this repo",
|
|
5845
7773
|
"",
|
|
5846
|
-
"
|
|
5847
|
-
"
|
|
7774
|
+
"Without --push: cloud \u2192 disk only; never deletes. A spec already on disk is",
|
|
7775
|
+
"skipped and reported, except one the last sync wrote and nobody edited since",
|
|
7776
|
+
"(sync ledger), which fast-forwards to a newer cloud copy.",
|
|
5848
7777
|
"Fixes the custody gap in [[HS-OQNH-21DO-DIAG-cloud-written-specs-are-invisible-to]]."
|
|
5849
7778
|
].join("\n");
|
|
5850
|
-
var
|
|
7779
|
+
var DEFAULT_WATCH_INTERVAL_SEC = 60;
|
|
7780
|
+
var FLAGS_WITH_VALUES = /* @__PURE__ */ new Set(["--only", "--from", "--interval"]);
|
|
7781
|
+
function parseIntervalSec(value) {
|
|
7782
|
+
if (!/^\d+$/.test(value)) return null;
|
|
7783
|
+
const n = Number(value);
|
|
7784
|
+
return n >= 1 ? n : null;
|
|
7785
|
+
}
|
|
5851
7786
|
function parsePullArgs(argv) {
|
|
5852
7787
|
const tokens = argv[0] === "pull" ? argv.slice(1) : [...argv];
|
|
5853
7788
|
if (tokens.includes("--help") || tokens.includes("-h")) return { help: true };
|
|
@@ -5857,7 +7792,27 @@ function parsePullArgs(argv) {
|
|
|
5857
7792
|
detail: false,
|
|
5858
7793
|
json: false,
|
|
5859
7794
|
only: [],
|
|
5860
|
-
from: null
|
|
7795
|
+
from: null,
|
|
7796
|
+
watch: false,
|
|
7797
|
+
intervalSec: DEFAULT_WATCH_INTERVAL_SEC,
|
|
7798
|
+
push: false,
|
|
7799
|
+
force: false
|
|
7800
|
+
};
|
|
7801
|
+
const applyValued = (flag, value) => {
|
|
7802
|
+
if (flag === "--only") {
|
|
7803
|
+
args.only.push(
|
|
7804
|
+
...value.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
7805
|
+
);
|
|
7806
|
+
return null;
|
|
7807
|
+
}
|
|
7808
|
+
if (flag === "--interval") {
|
|
7809
|
+
const n = parseIntervalSec(value);
|
|
7810
|
+
if (n === null) return `--interval must be a whole number of seconds >= 1 (got \`${value}\`)`;
|
|
7811
|
+
args.intervalSec = n;
|
|
7812
|
+
return null;
|
|
7813
|
+
}
|
|
7814
|
+
args.from = value;
|
|
7815
|
+
return null;
|
|
5861
7816
|
};
|
|
5862
7817
|
for (let i = 0; i < tokens.length; i++) {
|
|
5863
7818
|
const tok = tokens[i];
|
|
@@ -5867,13 +7822,8 @@ function parsePullArgs(argv) {
|
|
|
5867
7822
|
return { error: { message: `${tok} requires a value`, code: PULL_EXIT.USAGE } };
|
|
5868
7823
|
}
|
|
5869
7824
|
i++;
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
...value.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
5873
|
-
);
|
|
5874
|
-
} else {
|
|
5875
|
-
args.from = value.trim();
|
|
5876
|
-
}
|
|
7825
|
+
const problem = applyValued(tok, value.trim());
|
|
7826
|
+
if (problem) return { error: { message: problem, code: PULL_EXIT.USAGE } };
|
|
5877
7827
|
continue;
|
|
5878
7828
|
}
|
|
5879
7829
|
const eq2 = tok.indexOf("=");
|
|
@@ -5883,13 +7833,8 @@ function parsePullArgs(argv) {
|
|
|
5883
7833
|
if (value.length === 0) {
|
|
5884
7834
|
return { error: { message: `${flag} requires a value`, code: PULL_EXIT.USAGE } };
|
|
5885
7835
|
}
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
...value.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
|
|
5889
|
-
);
|
|
5890
|
-
} else {
|
|
5891
|
-
args.from = value;
|
|
5892
|
-
}
|
|
7836
|
+
const problem = applyValued(flag, value);
|
|
7837
|
+
if (problem) return { error: { message: problem, code: PULL_EXIT.USAGE } };
|
|
5893
7838
|
continue;
|
|
5894
7839
|
}
|
|
5895
7840
|
switch (tok) {
|
|
@@ -5905,10 +7850,31 @@ function parsePullArgs(argv) {
|
|
|
5905
7850
|
case "--json":
|
|
5906
7851
|
args.json = true;
|
|
5907
7852
|
break;
|
|
7853
|
+
case "--watch":
|
|
7854
|
+
args.watch = true;
|
|
7855
|
+
break;
|
|
7856
|
+
case "--push":
|
|
7857
|
+
args.push = true;
|
|
7858
|
+
break;
|
|
7859
|
+
case "--force":
|
|
7860
|
+
args.force = true;
|
|
7861
|
+
break;
|
|
5908
7862
|
default:
|
|
5909
7863
|
return { error: { message: `unknown option \`${tok}\``, code: PULL_EXIT.USAGE } };
|
|
5910
7864
|
}
|
|
5911
7865
|
}
|
|
7866
|
+
if (args.watch && !args.apply) {
|
|
7867
|
+
return { error: { message: "--watch requires --apply", code: PULL_EXIT.USAGE } };
|
|
7868
|
+
}
|
|
7869
|
+
if (args.push && !args.watch) {
|
|
7870
|
+
return { error: { message: "--push requires --watch --apply", code: PULL_EXIT.USAGE } };
|
|
7871
|
+
}
|
|
7872
|
+
if (args.force && !args.push) {
|
|
7873
|
+
return { error: { message: "--force requires --push", code: PULL_EXIT.USAGE } };
|
|
7874
|
+
}
|
|
7875
|
+
if (args.watch && args.json) {
|
|
7876
|
+
return { error: { message: "--watch cannot be combined with --json", code: PULL_EXIT.USAGE } };
|
|
7877
|
+
}
|
|
5912
7878
|
return { args };
|
|
5913
7879
|
}
|
|
5914
7880
|
|
|
@@ -5950,7 +7916,7 @@ function resolveSyncVaultTarget(input) {
|
|
|
5950
7916
|
detail: `heyspex.yaml vault_id${drift}`
|
|
5951
7917
|
};
|
|
5952
7918
|
}
|
|
5953
|
-
if (input.pointer.status === "read" && input.pointer.vaultId) {
|
|
7919
|
+
if (input.allowPointer && input.pointer.status === "read" && input.pointer.vaultId) {
|
|
5954
7920
|
return {
|
|
5955
7921
|
ok: true,
|
|
5956
7922
|
vaultId: input.pointer.vaultId,
|
|
@@ -5958,9 +7924,9 @@ function resolveSyncVaultTarget(input) {
|
|
|
5958
7924
|
detail: "this account's active vault, read from the console (heyspex.yaml names none)"
|
|
5959
7925
|
};
|
|
5960
7926
|
}
|
|
5961
|
-
const why = input.pointer.status === "unreadable" ? `the console could not answer for the active vault (${input.pointer.detail})` : "the console reports no active vault for this account";
|
|
7927
|
+
const why = !input.allowPointer ? "the account-wide active vault is not a binding and was not consulted (set HEYSPEX_ALLOW_POINTER_VAULT=1 to follow it on purpose)" : input.pointer.status === "unreadable" ? `the console could not answer for the active vault (${input.pointer.detail})` : "the console reports no active vault for this account";
|
|
5962
7928
|
return refuse(
|
|
5963
|
-
`cannot tell which vault to ${verb === "pull" ? "pull into" : verb === "index reconcile" ? "reconcile" : "rebuild"}: heyspex.yaml names no vault_id and ${why}. Nothing was changed. The registry.json pointer copy was deliberately not used (it can be stale and target the wrong vault). Name the vault: \`hs --vault <id> ${verb}\`; check this repo's wiring with \`hs connect --check\`; or
|
|
7929
|
+
`cannot tell which vault to ${verb === "pull" ? "pull into" : verb === "push" ? "push from" : verb === "import" ? "import into" : verb === "index reconcile" ? "reconcile" : "rebuild"}: heyspex.yaml names no vault_id and ${why}. Nothing was changed. The registry.json pointer copy was deliberately not used (it can be stale and target the wrong vault). Name the vault: \`hs --vault <id> ${verb}\`; check this repo's wiring with \`hs connect --check\`; or bind this repo with a heyspex.yaml vault_id (a committed satellite marker is portable).`
|
|
5964
7930
|
);
|
|
5965
7931
|
}
|
|
5966
7932
|
async function resolveSyncVaultTargetFromEnv(deps) {
|
|
@@ -5982,8 +7948,11 @@ async function resolveSyncVaultTargetFromEnv(deps) {
|
|
|
5982
7948
|
} catch {
|
|
5983
7949
|
registryRowId = null;
|
|
5984
7950
|
}
|
|
7951
|
+
const allowPointer = ["1", "true", "yes"].includes(
|
|
7952
|
+
(deps.env.HEYSPEX_ALLOW_POINTER_VAULT ?? "").trim().toLowerCase()
|
|
7953
|
+
);
|
|
5985
7954
|
let pointer = { status: "unreadable", detail: "not read" };
|
|
5986
|
-
if (!override && !markerVaultId) {
|
|
7955
|
+
if (!override && !markerVaultId && allowPointer) {
|
|
5987
7956
|
const http = {
|
|
5988
7957
|
fetchImpl: deps.fetchImpl ?? fetch,
|
|
5989
7958
|
consoleUrl: deps.consoleUrl,
|
|
@@ -6005,13 +7974,157 @@ async function resolveSyncVaultTargetFromEnv(deps) {
|
|
|
6005
7974
|
markerVaultId,
|
|
6006
7975
|
registryRowId,
|
|
6007
7976
|
pointer,
|
|
6008
|
-
verb: deps.verb
|
|
7977
|
+
verb: deps.verb,
|
|
7978
|
+
allowPointer
|
|
6009
7979
|
});
|
|
6010
7980
|
}
|
|
6011
7981
|
|
|
6012
7982
|
// spex-console/bin/hs-vault-pull.ts
|
|
6013
7983
|
init_vault_workspace_shared();
|
|
6014
7984
|
|
|
7985
|
+
// spex-console/src/lib/vault-sync/dirty.ts
|
|
7986
|
+
import { promises as fs3, watch as fsWatch } from "node:fs";
|
|
7987
|
+
import nodePath2 from "node:path";
|
|
7988
|
+
|
|
7989
|
+
// spex-console/src/lib/vault-sync/ledger.ts
|
|
7990
|
+
import { createHash } from "node:crypto";
|
|
7991
|
+
import { promises as fs2 } from "node:fs";
|
|
7992
|
+
import nodePath from "node:path";
|
|
7993
|
+
var VAULT_RUNTIME_DIR = ".heyspex";
|
|
7994
|
+
var LEDGER_FILE = "vault-sync-ledger.json";
|
|
7995
|
+
function ledgerPath(vaultRoot2) {
|
|
7996
|
+
return nodePath.join(vaultRoot2, VAULT_RUNTIME_DIR, LEDGER_FILE);
|
|
7997
|
+
}
|
|
7998
|
+
function hashContent(content) {
|
|
7999
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
8000
|
+
}
|
|
8001
|
+
function parseLedger(raw) {
|
|
8002
|
+
let json;
|
|
8003
|
+
try {
|
|
8004
|
+
json = JSON.parse(raw);
|
|
8005
|
+
} catch {
|
|
8006
|
+
return {};
|
|
8007
|
+
}
|
|
8008
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) return {};
|
|
8009
|
+
const out = {};
|
|
8010
|
+
for (const [k, v] of Object.entries(json)) {
|
|
8011
|
+
if (!v || typeof v !== "object") continue;
|
|
8012
|
+
const e = v;
|
|
8013
|
+
if (typeof e.hash !== "string" || e.hash.length === 0) continue;
|
|
8014
|
+
out[k] = {
|
|
8015
|
+
hash: e.hash,
|
|
8016
|
+
remoteUpdatedAt: typeof e.remoteUpdatedAt === "string" ? e.remoteUpdatedAt : null,
|
|
8017
|
+
at: typeof e.at === "string" ? e.at : "",
|
|
8018
|
+
by: e.by === "push" ? "push" : "pull"
|
|
8019
|
+
};
|
|
8020
|
+
}
|
|
8021
|
+
return out;
|
|
8022
|
+
}
|
|
8023
|
+
function createLedgerStore(vaultRoot2, { now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
8024
|
+
const file = ledgerPath(vaultRoot2);
|
|
8025
|
+
const pending = /* @__PURE__ */ new Map();
|
|
8026
|
+
let chain = Promise.resolve();
|
|
8027
|
+
async function readDisk() {
|
|
8028
|
+
try {
|
|
8029
|
+
return parseLedger(await fs2.readFile(file, "utf8"));
|
|
8030
|
+
} catch {
|
|
8031
|
+
return {};
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
async function writeAtomic(ledger) {
|
|
8035
|
+
await fs2.mkdir(nodePath.dirname(file), { recursive: true });
|
|
8036
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
8037
|
+
await fs2.writeFile(tmp, `${JSON.stringify(ledger)}
|
|
8038
|
+
`, "utf8");
|
|
8039
|
+
let lastErr;
|
|
8040
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
8041
|
+
try {
|
|
8042
|
+
await fs2.rename(tmp, file);
|
|
8043
|
+
return;
|
|
8044
|
+
} catch (err) {
|
|
8045
|
+
lastErr = err;
|
|
8046
|
+
await new Promise((r) => setTimeout(r, 25 * (attempt + 1)));
|
|
8047
|
+
}
|
|
8048
|
+
}
|
|
8049
|
+
await fs2.rm(tmp, { force: true }).catch(() => {
|
|
8050
|
+
});
|
|
8051
|
+
throw lastErr;
|
|
8052
|
+
}
|
|
8053
|
+
return {
|
|
8054
|
+
async snapshot() {
|
|
8055
|
+
const disk = await readDisk();
|
|
8056
|
+
for (const [k, v] of pending) disk[k] = v;
|
|
8057
|
+
return disk;
|
|
8058
|
+
},
|
|
8059
|
+
record(relPath, entry) {
|
|
8060
|
+
pending.set(relPath, { ...entry, at: entry.at ?? now().toISOString() });
|
|
8061
|
+
},
|
|
8062
|
+
flush() {
|
|
8063
|
+
chain = chain.then(async () => {
|
|
8064
|
+
if (pending.size === 0) return;
|
|
8065
|
+
const batch = new Map(pending);
|
|
8066
|
+
const merged = await readDisk();
|
|
8067
|
+
for (const [k, v] of batch) merged[k] = v;
|
|
8068
|
+
await writeAtomic(merged);
|
|
8069
|
+
for (const [k, v] of batch) if (pending.get(k) === v) pending.delete(k);
|
|
8070
|
+
});
|
|
8071
|
+
return chain;
|
|
8072
|
+
}
|
|
8073
|
+
};
|
|
8074
|
+
}
|
|
8075
|
+
|
|
8076
|
+
// spex-console/src/lib/vault-sync/dirty.ts
|
|
8077
|
+
var DIRTY_FILE = "vault-dirty";
|
|
8078
|
+
function dirtyMarkerPath(vaultRoot2) {
|
|
8079
|
+
return nodePath2.join(vaultRoot2, VAULT_RUNTIME_DIR, DIRTY_FILE);
|
|
8080
|
+
}
|
|
8081
|
+
async function writeDirtyMarker(vaultRoot2, id, now = () => /* @__PURE__ */ new Date()) {
|
|
8082
|
+
const file = dirtyMarkerPath(vaultRoot2);
|
|
8083
|
+
await fs3.mkdir(nodePath2.dirname(file), { recursive: true });
|
|
8084
|
+
await fs3.writeFile(file, JSON.stringify({ id, at: now().toISOString() }), "utf8");
|
|
8085
|
+
}
|
|
8086
|
+
function parseDirtyMarker(raw) {
|
|
8087
|
+
try {
|
|
8088
|
+
const j = JSON.parse(raw);
|
|
8089
|
+
return typeof j.id === "string" && j.id.trim() ? { id: j.id.trim(), at: typeof j.at === "string" ? j.at : "" } : null;
|
|
8090
|
+
} catch {
|
|
8091
|
+
return null;
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
function watchDirtyMarker(vaultRoot2, onDirty, { debounceMs = 100 } = {}) {
|
|
8095
|
+
const file = dirtyMarkerPath(vaultRoot2);
|
|
8096
|
+
let timer = null;
|
|
8097
|
+
let closed = false;
|
|
8098
|
+
let watcher = null;
|
|
8099
|
+
try {
|
|
8100
|
+
void fs3.mkdir(nodePath2.dirname(file), { recursive: true }).then(() => {
|
|
8101
|
+
if (closed) return;
|
|
8102
|
+
try {
|
|
8103
|
+
watcher = fsWatch(nodePath2.dirname(file), (_evt, name) => {
|
|
8104
|
+
if (name && String(name) !== DIRTY_FILE) return;
|
|
8105
|
+
if (timer) clearTimeout(timer);
|
|
8106
|
+
timer = setTimeout(async () => {
|
|
8107
|
+
try {
|
|
8108
|
+
const m = parseDirtyMarker(await fs3.readFile(file, "utf8"));
|
|
8109
|
+
if (m) onDirty(m.id);
|
|
8110
|
+
} catch {
|
|
8111
|
+
}
|
|
8112
|
+
}, debounceMs);
|
|
8113
|
+
});
|
|
8114
|
+
watcher.on("error", () => {
|
|
8115
|
+
});
|
|
8116
|
+
} catch {
|
|
8117
|
+
}
|
|
8118
|
+
});
|
|
8119
|
+
} catch {
|
|
8120
|
+
}
|
|
8121
|
+
return () => {
|
|
8122
|
+
closed = true;
|
|
8123
|
+
if (timer) clearTimeout(timer);
|
|
8124
|
+
watcher?.close();
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
8127
|
+
|
|
6015
8128
|
// spex-console/src/lib/vault-sync/pull.ts
|
|
6016
8129
|
function normalizeSpecRelPath(raw) {
|
|
6017
8130
|
if (typeof raw !== "string") return null;
|
|
@@ -6029,6 +8142,10 @@ function normalizeSpecRelPath(raw) {
|
|
|
6029
8142
|
if (!/\.md$/i.test(p)) return null;
|
|
6030
8143
|
return p;
|
|
6031
8144
|
}
|
|
8145
|
+
function hasDotSegment(relPath) {
|
|
8146
|
+
const segments = relPath.split("/");
|
|
8147
|
+
return segments.slice(0, -1).some((s) => s.startsWith("."));
|
|
8148
|
+
}
|
|
6032
8149
|
function basenameOfRelPath(relPath) {
|
|
6033
8150
|
const last = relPath.split("/").pop() ?? relPath;
|
|
6034
8151
|
return last.replace(/\.md$/i, "");
|
|
@@ -6040,11 +8157,11 @@ function selectRemoteRefs(remote, only) {
|
|
|
6040
8157
|
const selected = [];
|
|
6041
8158
|
for (const ref of remote) {
|
|
6042
8159
|
const id = (ref.id ?? "").toLowerCase();
|
|
6043
|
-
const
|
|
8160
|
+
const basename4 = (ref.basename ?? "").toLowerCase();
|
|
6044
8161
|
let hit = null;
|
|
6045
8162
|
for (const sel of selectors) {
|
|
6046
8163
|
const s = sel.toLowerCase();
|
|
6047
|
-
if (id === s ||
|
|
8164
|
+
if (id === s || basename4 === s || id.startsWith(`${s}-`) || basename4.startsWith(`${s}-`)) {
|
|
6048
8165
|
hit = sel;
|
|
6049
8166
|
break;
|
|
6050
8167
|
}
|
|
@@ -6056,6 +8173,18 @@ function selectRemoteRefs(remote, only) {
|
|
|
6056
8173
|
}
|
|
6057
8174
|
return { selected, unmatched: selectors.filter((s) => !matched.has(s)) };
|
|
6058
8175
|
}
|
|
8176
|
+
function instantMs(value) {
|
|
8177
|
+
return typeof value === "string" && value ? Date.parse(value) : Number.NaN;
|
|
8178
|
+
}
|
|
8179
|
+
function canFastForward(ref, relPath, local) {
|
|
8180
|
+
const entry = local.ledger?.[relPath];
|
|
8181
|
+
const disk = local.hashes?.get(relPath);
|
|
8182
|
+
if (!entry || !disk || disk !== entry.hash) return null;
|
|
8183
|
+
const remote = instantMs(ref.updatedAt);
|
|
8184
|
+
const seen = instantMs(entry.remoteUpdatedAt);
|
|
8185
|
+
if (Number.isNaN(remote) || Number.isNaN(seen) || remote <= seen) return null;
|
|
8186
|
+
return entry.hash;
|
|
8187
|
+
}
|
|
6059
8188
|
function decidePullPlan({
|
|
6060
8189
|
remote,
|
|
6061
8190
|
local,
|
|
@@ -6071,11 +8200,11 @@ function decidePullPlan({
|
|
|
6071
8200
|
if (!localByBasename.has(base)) localByBasename.set(base, norm);
|
|
6072
8201
|
}
|
|
6073
8202
|
const pull = [];
|
|
6074
|
-
const
|
|
8203
|
+
const skip2 = [];
|
|
6075
8204
|
for (const ref of remote) {
|
|
6076
8205
|
const relPath = normalizeSpecRelPath(ref.relPath ?? null);
|
|
6077
8206
|
if (!relPath) {
|
|
6078
|
-
|
|
8207
|
+
skip2.push({
|
|
6079
8208
|
id: ref.id,
|
|
6080
8209
|
relPath: null,
|
|
6081
8210
|
reason: "unsafe-path",
|
|
@@ -6083,18 +8212,43 @@ function decidePullPlan({
|
|
|
6083
8212
|
});
|
|
6084
8213
|
continue;
|
|
6085
8214
|
}
|
|
6086
|
-
const
|
|
8215
|
+
const basename4 = basenameOfRelPath(relPath);
|
|
8216
|
+
if (hasDotSegment(relPath)) {
|
|
8217
|
+
skip2.push({
|
|
8218
|
+
id: ref.id,
|
|
8219
|
+
relPath,
|
|
8220
|
+
reason: "trashed-path",
|
|
8221
|
+
detail: "soft-deleted on the remote \u2014 the pull never resurrects a .trash/ row"
|
|
8222
|
+
});
|
|
8223
|
+
continue;
|
|
8224
|
+
}
|
|
6087
8225
|
if (localRelPaths.has(relPath)) {
|
|
8226
|
+
const ffHash = overwrite ? null : canFastForward(ref, relPath, local);
|
|
6088
8227
|
if (overwrite) {
|
|
6089
|
-
pull.push({
|
|
8228
|
+
pull.push({
|
|
8229
|
+
id: ref.id,
|
|
8230
|
+
relPath,
|
|
8231
|
+
basename: basename4,
|
|
8232
|
+
overwrites: true,
|
|
8233
|
+
remoteUpdatedAt: ref.updatedAt ?? void 0
|
|
8234
|
+
});
|
|
8235
|
+
} else if (ffHash) {
|
|
8236
|
+
pull.push({
|
|
8237
|
+
id: ref.id,
|
|
8238
|
+
relPath,
|
|
8239
|
+
basename: basename4,
|
|
8240
|
+
overwrites: true,
|
|
8241
|
+
remoteUpdatedAt: ref.updatedAt ?? void 0,
|
|
8242
|
+
fastForwardFromHash: ffHash
|
|
8243
|
+
});
|
|
6090
8244
|
} else {
|
|
6091
|
-
|
|
8245
|
+
skip2.push({ id: ref.id, relPath, reason: "present-on-disk" });
|
|
6092
8246
|
}
|
|
6093
8247
|
continue;
|
|
6094
8248
|
}
|
|
6095
|
-
const elsewhere = localByBasename.get(
|
|
8249
|
+
const elsewhere = localByBasename.get(basename4.toLowerCase());
|
|
6096
8250
|
if (elsewhere) {
|
|
6097
|
-
|
|
8251
|
+
skip2.push({
|
|
6098
8252
|
id: ref.id,
|
|
6099
8253
|
relPath,
|
|
6100
8254
|
reason: "basename-exists-elsewhere",
|
|
@@ -6102,19 +8256,54 @@ function decidePullPlan({
|
|
|
6102
8256
|
});
|
|
6103
8257
|
continue;
|
|
6104
8258
|
}
|
|
6105
|
-
pull.push({
|
|
8259
|
+
pull.push({
|
|
8260
|
+
id: ref.id,
|
|
8261
|
+
relPath,
|
|
8262
|
+
basename: basename4,
|
|
8263
|
+
overwrites: false,
|
|
8264
|
+
remoteUpdatedAt: ref.updatedAt ?? void 0
|
|
8265
|
+
});
|
|
6106
8266
|
}
|
|
6107
|
-
return { pull, skip };
|
|
8267
|
+
return { pull, skip: skip2 };
|
|
6108
8268
|
}
|
|
6109
8269
|
async function runVaultPull(deps, options2 = {}) {
|
|
6110
8270
|
const dryRun = options2.dryRun !== false;
|
|
6111
8271
|
const overwrite = options2.overwrite === true;
|
|
6112
|
-
|
|
6113
|
-
|
|
8272
|
+
let remote;
|
|
8273
|
+
let selected;
|
|
8274
|
+
let unmatched;
|
|
8275
|
+
if (options2.direct && deps.getRemoteRef && options2.only && options2.only.length > 0) {
|
|
8276
|
+
selected = [];
|
|
8277
|
+
unmatched = [];
|
|
8278
|
+
for (const sel of options2.only) {
|
|
8279
|
+
const ref = await deps.getRemoteRef(sel);
|
|
8280
|
+
if (ref) selected.push(ref);
|
|
8281
|
+
else unmatched.push(sel);
|
|
8282
|
+
}
|
|
8283
|
+
remote = selected;
|
|
8284
|
+
} else {
|
|
8285
|
+
remote = await deps.listRemote();
|
|
8286
|
+
({ selected, unmatched } = selectRemoteRefs(remote, options2.only));
|
|
8287
|
+
}
|
|
6114
8288
|
const localRelPaths = await deps.listLocal();
|
|
8289
|
+
const ledger = deps.readLedger && !overwrite ? await deps.readLedger() : void 0;
|
|
8290
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
8291
|
+
if (ledger && deps.hashLocal) {
|
|
8292
|
+
const onDisk = new Set(localRelPaths.map((p) => normalizeSpecRelPath(p) ?? p));
|
|
8293
|
+
for (const ref of selected) {
|
|
8294
|
+
const rel = normalizeSpecRelPath(ref.relPath ?? null);
|
|
8295
|
+
if (!rel || !onDisk.has(rel)) continue;
|
|
8296
|
+
const entry = ledger[rel];
|
|
8297
|
+
const rem = instantMs(ref.updatedAt);
|
|
8298
|
+
const seen = instantMs(entry?.remoteUpdatedAt);
|
|
8299
|
+
if (!entry || Number.isNaN(rem) || Number.isNaN(seen) || rem <= seen) continue;
|
|
8300
|
+
const h = await deps.hashLocal(rel);
|
|
8301
|
+
if (h) hashes.set(rel, h);
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
6115
8304
|
const plan = decidePullPlan({
|
|
6116
8305
|
remote: selected,
|
|
6117
|
-
local: { relPaths: localRelPaths },
|
|
8306
|
+
local: { relPaths: localRelPaths, ledger, hashes },
|
|
6118
8307
|
overwrite
|
|
6119
8308
|
});
|
|
6120
8309
|
const written = [];
|
|
@@ -6132,7 +8321,19 @@ async function runVaultPull(deps, options2 = {}) {
|
|
|
6132
8321
|
continue;
|
|
6133
8322
|
}
|
|
6134
8323
|
const bytes = deps.serialize(content.body, content.frontmatter);
|
|
6135
|
-
|
|
8324
|
+
if (item.fastForwardFromHash && deps.hashLocal) {
|
|
8325
|
+
const nowHash = await deps.hashLocal(item.relPath);
|
|
8326
|
+
if (nowHash !== item.fastForwardFromHash) {
|
|
8327
|
+
plan.skip.push({
|
|
8328
|
+
id: item.id,
|
|
8329
|
+
relPath: item.relPath,
|
|
8330
|
+
reason: "present-on-disk",
|
|
8331
|
+
detail: "edited locally while the pull ran - not overwritten"
|
|
8332
|
+
});
|
|
8333
|
+
continue;
|
|
8334
|
+
}
|
|
8335
|
+
}
|
|
8336
|
+
await deps.writeSpec(item.relPath, bytes, { overwrite: item.overwrites });
|
|
6136
8337
|
written.push({
|
|
6137
8338
|
id: item.id,
|
|
6138
8339
|
relPath: item.relPath,
|
|
@@ -6141,7 +8342,11 @@ async function runVaultPull(deps, options2 = {}) {
|
|
|
6141
8342
|
});
|
|
6142
8343
|
if (deps.afterWrite) {
|
|
6143
8344
|
try {
|
|
6144
|
-
await deps.afterWrite(item.relPath
|
|
8345
|
+
await deps.afterWrite(item.relPath, {
|
|
8346
|
+
id: item.id,
|
|
8347
|
+
content: bytes,
|
|
8348
|
+
remoteUpdatedAt: item.remoteUpdatedAt ?? null
|
|
8349
|
+
});
|
|
6145
8350
|
} catch {
|
|
6146
8351
|
}
|
|
6147
8352
|
}
|
|
@@ -6154,6 +8359,12 @@ async function runVaultPull(deps, options2 = {}) {
|
|
|
6154
8359
|
}
|
|
6155
8360
|
}
|
|
6156
8361
|
}
|
|
8362
|
+
if (!dryRun && deps.finish) {
|
|
8363
|
+
try {
|
|
8364
|
+
await deps.finish();
|
|
8365
|
+
} catch {
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
6157
8368
|
return {
|
|
6158
8369
|
dryRun,
|
|
6159
8370
|
remoteCount: remote.length,
|
|
@@ -6165,9 +8376,10 @@ async function runVaultPull(deps, options2 = {}) {
|
|
|
6165
8376
|
};
|
|
6166
8377
|
}
|
|
6167
8378
|
var SKIP_LABELS = {
|
|
6168
|
-
"present-on-disk": "already on disk (use --overwrite to replace)",
|
|
8379
|
+
"present-on-disk": "already on disk (use --overwrite to replace; files unmodified since the last sync fast-forward on their own)",
|
|
6169
8380
|
"basename-exists-elsewhere": "same basename lives at another path \u2014 not touched",
|
|
6170
|
-
"unsafe-path": "remote row has no safe vault-relative .md path"
|
|
8381
|
+
"unsafe-path": "remote row has no safe vault-relative .md path",
|
|
8382
|
+
"trashed-path": "soft-deleted on the remote (.trash/) \u2014 never resurrected"
|
|
6171
8383
|
};
|
|
6172
8384
|
function formatPullSummary(result, { detail = false, sampleSize = 5 } = {}) {
|
|
6173
8385
|
const lines = [];
|
|
@@ -6222,23 +8434,22 @@ function formatPullSummary(result, { detail = false, sampleSize = 5 } = {}) {
|
|
|
6222
8434
|
}
|
|
6223
8435
|
|
|
6224
8436
|
// spex-console/src/lib/vault-sync/pull-io.ts
|
|
6225
|
-
import { promises as
|
|
6226
|
-
import
|
|
6227
|
-
import nodePath from "node:path";
|
|
8437
|
+
import { promises as fs10 } from "node:fs";
|
|
8438
|
+
import nodePath3 from "node:path";
|
|
6228
8439
|
|
|
6229
8440
|
// spex-console/src/lib/dev-mode.ts
|
|
8441
|
+
init_heyspex_home2();
|
|
6230
8442
|
init_credentials();
|
|
6231
|
-
import { mkdirSync as
|
|
6232
|
-
import {
|
|
6233
|
-
import { join as join9 } from "node:path";
|
|
8443
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8444
|
+
import { join as join10 } from "node:path";
|
|
6234
8445
|
var CLOUD_CONSOLE_URL2 = "https://app.heyspex.com";
|
|
6235
|
-
var defaultReadFile = (p) =>
|
|
8446
|
+
var defaultReadFile = (p) => readFileSync10(p, "utf8");
|
|
6236
8447
|
function readCredentialsMcpToken({
|
|
6237
|
-
homeDir =
|
|
8448
|
+
homeDir = heyspexHome2(),
|
|
6238
8449
|
readFile = defaultReadFile
|
|
6239
8450
|
} = {}) {
|
|
6240
8451
|
try {
|
|
6241
|
-
const raw = readFile(
|
|
8452
|
+
const raw = readFile(join10(homeDir, ".heyspex", "credentials.yaml"));
|
|
6242
8453
|
const match = raw.match(/^mcp_token:\s*["']?([^"'\r\n#]+)/m);
|
|
6243
8454
|
return match?.[1]?.trim() || null;
|
|
6244
8455
|
} catch {
|
|
@@ -6247,6 +8458,7 @@ function readCredentialsMcpToken({
|
|
|
6247
8458
|
}
|
|
6248
8459
|
|
|
6249
8460
|
// spex-console/src/lib/vault-sync/pull-io.ts
|
|
8461
|
+
init_heyspex_home2();
|
|
6250
8462
|
init_vault();
|
|
6251
8463
|
init_vault_frontmatter();
|
|
6252
8464
|
var LIST_FIELDS = "id,basename,relPath,type,status,updated,updatedAt";
|
|
@@ -6262,7 +8474,7 @@ var VaultPullSourceError = class extends Error {
|
|
|
6262
8474
|
};
|
|
6263
8475
|
function resolvePullToken({
|
|
6264
8476
|
env = process.env,
|
|
6265
|
-
homeDir =
|
|
8477
|
+
homeDir = heyspexHome2()
|
|
6266
8478
|
} = {}) {
|
|
6267
8479
|
const fromEnv = env.HEYSPEX_MCP_TOKEN?.trim();
|
|
6268
8480
|
if (fromEnv) return fromEnv;
|
|
@@ -6361,22 +8573,68 @@ async function fetchRemoteSpecContent(http, item) {
|
|
|
6361
8573
|
}
|
|
6362
8574
|
return { body: spec.body, frontmatter };
|
|
6363
8575
|
}
|
|
8576
|
+
function remoteStamp(spec) {
|
|
8577
|
+
if (!spec) return null;
|
|
8578
|
+
const fm = spec.frontmatter;
|
|
8579
|
+
for (const candidate of [fm?.updated, spec.updatedAt, spec.updated]) {
|
|
8580
|
+
const raw = candidate instanceof Date ? candidate.toISOString() : candidate;
|
|
8581
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
8582
|
+
const ms = Date.parse(raw);
|
|
8583
|
+
if (!Number.isNaN(ms)) return /T/.test(raw) ? new Date(ms).toISOString() : raw.trim();
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
return null;
|
|
8587
|
+
}
|
|
8588
|
+
async function fetchRemoteRefAndContent(http, selector) {
|
|
8589
|
+
const { status, json, text: text2 } = await getJson(http, `api/specs/${encodeURIComponent(selector)}`);
|
|
8590
|
+
if (status === 404) return null;
|
|
8591
|
+
if (status !== 200) {
|
|
8592
|
+
throw new VaultPullSourceError(
|
|
8593
|
+
`GET /api/specs/${selector} returned ${status}: ${text2.slice(0, 200)}`,
|
|
8594
|
+
status
|
|
8595
|
+
);
|
|
8596
|
+
}
|
|
8597
|
+
const spec = json?.spec;
|
|
8598
|
+
const fm = spec?.frontmatter && typeof spec.frontmatter === "object" ? spec.frontmatter : null;
|
|
8599
|
+
if (!spec || typeof spec.body !== "string" || !fm || Object.keys(fm).length === 0) {
|
|
8600
|
+
throw new VaultPullSourceError(`GET /api/specs/${selector} returned no usable spec`, status);
|
|
8601
|
+
}
|
|
8602
|
+
return {
|
|
8603
|
+
ref: {
|
|
8604
|
+
id: String(spec.id ?? fm.id ?? ""),
|
|
8605
|
+
basename: typeof spec.basename === "string" ? spec.basename : null,
|
|
8606
|
+
relPath: typeof spec.relPath === "string" ? spec.relPath : null,
|
|
8607
|
+
type: typeof spec.type === "string" ? spec.type : null,
|
|
8608
|
+
status: typeof spec.status === "string" ? spec.status : null,
|
|
8609
|
+
updated: typeof spec.updated === "string" ? spec.updated : null,
|
|
8610
|
+
updatedAt: remoteStamp(spec)
|
|
8611
|
+
},
|
|
8612
|
+
content: { body: spec.body, frontmatter: fm }
|
|
8613
|
+
};
|
|
8614
|
+
}
|
|
8615
|
+
async function hashFile(abs) {
|
|
8616
|
+
try {
|
|
8617
|
+
return hashContent(await fs10.readFile(abs, "utf8"));
|
|
8618
|
+
} catch {
|
|
8619
|
+
return null;
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
6364
8622
|
async function listLocalSpecRelPaths(root = vaultRoot()) {
|
|
6365
8623
|
const out = [];
|
|
6366
8624
|
async function walk(dir) {
|
|
6367
8625
|
let entries;
|
|
6368
8626
|
try {
|
|
6369
|
-
entries = await
|
|
8627
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
6370
8628
|
} catch {
|
|
6371
8629
|
return;
|
|
6372
8630
|
}
|
|
6373
8631
|
for (const entry of entries) {
|
|
6374
|
-
const full =
|
|
8632
|
+
const full = nodePath3.join(dir, entry.name);
|
|
6375
8633
|
if (entry.isDirectory()) {
|
|
6376
8634
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
6377
8635
|
await walk(full);
|
|
6378
8636
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
6379
|
-
out.push(
|
|
8637
|
+
out.push(nodePath3.relative(root, full).split(nodePath3.sep).join("/"));
|
|
6380
8638
|
}
|
|
6381
8639
|
}
|
|
6382
8640
|
}
|
|
@@ -6388,21 +8646,659 @@ function createVaultPullDeps({
|
|
|
6388
8646
|
token,
|
|
6389
8647
|
fetchImpl,
|
|
6390
8648
|
localRoot,
|
|
6391
|
-
scopeHeaders
|
|
8649
|
+
scopeHeaders,
|
|
8650
|
+
ledger,
|
|
8651
|
+
onLocalWrite
|
|
6392
8652
|
}) {
|
|
6393
8653
|
const http = { baseUrl, token, fetchImpl, scopeHeaders };
|
|
8654
|
+
const prefetched = /* @__PURE__ */ new Map();
|
|
6394
8655
|
return {
|
|
6395
8656
|
listRemote: () => listRemoteSpecs(http),
|
|
6396
|
-
|
|
8657
|
+
getRemoteRef: async (selector) => {
|
|
8658
|
+
const hit = await fetchRemoteRefAndContent(http, selector);
|
|
8659
|
+
if (!hit) return null;
|
|
8660
|
+
if (hit.ref.basename) prefetched.set(hit.ref.basename, hit.content);
|
|
8661
|
+
return hit.ref;
|
|
8662
|
+
},
|
|
8663
|
+
fetchRemoteSpec: async (item) => {
|
|
8664
|
+
const cached = prefetched.get(item.basename);
|
|
8665
|
+
if (cached) {
|
|
8666
|
+
prefetched.delete(item.basename);
|
|
8667
|
+
return cached;
|
|
8668
|
+
}
|
|
8669
|
+
return fetchRemoteSpecContent(http, item);
|
|
8670
|
+
},
|
|
8671
|
+
readLedger: ledger ? () => ledger.snapshot() : void 0,
|
|
8672
|
+
hashLocal: (relPath) => hashFile(nodePath3.join(localRoot ?? vaultRoot(), relPath)),
|
|
8673
|
+
finish: ledger ? () => ledger.flush() : void 0,
|
|
6397
8674
|
listLocal: () => listLocalSpecRelPaths(localRoot ?? vaultRoot()),
|
|
6398
8675
|
serialize: stringifySpecMarkdown,
|
|
6399
|
-
writeSpec: localRoot ? async (relPath, content) => {
|
|
6400
|
-
const abs =
|
|
6401
|
-
await
|
|
6402
|
-
|
|
8676
|
+
writeSpec: localRoot ? async (relPath, content, opts) => {
|
|
8677
|
+
const abs = nodePath3.join(localRoot, relPath);
|
|
8678
|
+
await fs10.mkdir(nodePath3.dirname(abs), { recursive: true });
|
|
8679
|
+
onLocalWrite?.(relPath);
|
|
8680
|
+
await fs10.writeFile(abs, content, {
|
|
8681
|
+
encoding: "utf8",
|
|
8682
|
+
flag: opts?.overwrite === true ? "w" : "wx"
|
|
8683
|
+
});
|
|
6403
8684
|
} : (relPath, content) => writeSpecBytesToVault(relPath, content),
|
|
6404
|
-
afterWrite: (relPath) =>
|
|
8685
|
+
afterWrite: async (relPath, info) => {
|
|
8686
|
+
if (ledger && info) {
|
|
8687
|
+
ledger.record(relPath, {
|
|
8688
|
+
hash: hashContent(info.content),
|
|
8689
|
+
remoteUpdatedAt: info.remoteUpdatedAt,
|
|
8690
|
+
by: "pull"
|
|
8691
|
+
});
|
|
8692
|
+
}
|
|
8693
|
+
await updateVaultCacheEntry(nodePath3.join(localRoot ?? vaultRoot(), relPath));
|
|
8694
|
+
}
|
|
8695
|
+
};
|
|
8696
|
+
}
|
|
8697
|
+
|
|
8698
|
+
// spex-console/src/lib/vault-sync/pull-watch.ts
|
|
8699
|
+
var DEFAULT_WATCH_OPTIONS = {
|
|
8700
|
+
baseIntervalMs: 6e4,
|
|
8701
|
+
// 120 s: a cloud-side console edit lands within ~2 minutes worst case. A tick
|
|
8702
|
+
// is ~14 list GETs for ~7,000 specs (500 rows/page) + one GET per changed
|
|
8703
|
+
// spec, so an idle vault costs ~7 req/min at the ceiling - well inside the
|
|
8704
|
+
// list route's budget. Local writes use the dirty-marker fast path instead.
|
|
8705
|
+
maxIntervalMs: 12e4,
|
|
8706
|
+
backoffFactor: 1.5
|
|
8707
|
+
};
|
|
8708
|
+
function nextWatchDelay(previousMs, outcome, opts = DEFAULT_WATCH_OPTIONS) {
|
|
8709
|
+
if (outcome === "wrote") return opts.baseIntervalMs;
|
|
8710
|
+
const prev = previousMs ?? opts.baseIntervalMs;
|
|
8711
|
+
return Math.min(opts.maxIntervalMs, Math.round(prev * opts.backoffFactor));
|
|
8712
|
+
}
|
|
8713
|
+
function classifyTick(result) {
|
|
8714
|
+
if (result.written.length > 0) return "wrote";
|
|
8715
|
+
if (result.failed.length > 0) return "error";
|
|
8716
|
+
return "idle";
|
|
8717
|
+
}
|
|
8718
|
+
function formatWatchTick(result, sourceLabel) {
|
|
8719
|
+
const lines = [];
|
|
8720
|
+
if (result.written.length > 0) {
|
|
8721
|
+
lines.push({
|
|
8722
|
+
level: "info",
|
|
8723
|
+
line: `relay: pulled ${result.written.length} spec(s) from ${sourceLabel}`
|
|
8724
|
+
});
|
|
8725
|
+
for (const w of result.written) {
|
|
8726
|
+
lines.push({ level: "info", line: ` ${w.overwrote ? "~" : "+"} ${w.relPath} (${w.id})` });
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
for (const f of result.failed) {
|
|
8730
|
+
lines.push({
|
|
8731
|
+
level: "warn",
|
|
8732
|
+
line: `relay: failed to pull ${f.relPath} (${f.id}) \u2014 ${f.error}`
|
|
8733
|
+
});
|
|
8734
|
+
}
|
|
8735
|
+
return lines;
|
|
8736
|
+
}
|
|
8737
|
+
async function runPullWatch(deps, opts = DEFAULT_WATCH_OPTIONS) {
|
|
8738
|
+
const summary = { ticks: 0, written: 0, failed: 0, errors: 0 };
|
|
8739
|
+
let delay = null;
|
|
8740
|
+
while (!deps.signal.aborted) {
|
|
8741
|
+
const dirty = deps.takeDirty?.() ?? [];
|
|
8742
|
+
if (dirty.length > 0 && deps.pullDirty && delay !== null) {
|
|
8743
|
+
try {
|
|
8744
|
+
const result = await deps.pullDirty(dirty);
|
|
8745
|
+
summary.written += result.written.length;
|
|
8746
|
+
summary.failed += result.failed.length;
|
|
8747
|
+
for (const { level, line } of formatWatchTick(result, deps.sourceLabel)) {
|
|
8748
|
+
deps.log(level, line);
|
|
8749
|
+
}
|
|
8750
|
+
} catch (err) {
|
|
8751
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8752
|
+
deps.log("warn", `relay: refresh of ${dirty.join(", ")} failed - ${message}`);
|
|
8753
|
+
}
|
|
8754
|
+
if (deps.signal.aborted) break;
|
|
8755
|
+
await deps.sleep(delay, deps.signal);
|
|
8756
|
+
continue;
|
|
8757
|
+
}
|
|
8758
|
+
summary.ticks += 1;
|
|
8759
|
+
let outcome;
|
|
8760
|
+
try {
|
|
8761
|
+
const result = await deps.pullOnce();
|
|
8762
|
+
summary.written += result.written.length;
|
|
8763
|
+
summary.failed += result.failed.length;
|
|
8764
|
+
for (const { level, line } of formatWatchTick(result, deps.sourceLabel)) {
|
|
8765
|
+
deps.log(level, line);
|
|
8766
|
+
}
|
|
8767
|
+
outcome = classifyTick(result);
|
|
8768
|
+
} catch (err) {
|
|
8769
|
+
summary.errors += 1;
|
|
8770
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8771
|
+
deps.log("warn", `relay: pull from ${deps.sourceLabel} failed \u2014 ${message}`);
|
|
8772
|
+
outcome = "error";
|
|
8773
|
+
}
|
|
8774
|
+
if (deps.signal.aborted) break;
|
|
8775
|
+
delay = nextWatchDelay(delay, outcome, opts);
|
|
8776
|
+
await deps.sleep(delay, deps.signal);
|
|
8777
|
+
}
|
|
8778
|
+
return summary;
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
// spex-console/src/lib/vault-sync/push.ts
|
|
8782
|
+
init_vault_frontmatter();
|
|
8783
|
+
var skip = (reason, detail) => ({
|
|
8784
|
+
reason,
|
|
8785
|
+
push: false,
|
|
8786
|
+
...detail ? { detail } : {}
|
|
8787
|
+
});
|
|
8788
|
+
function readSpecIdentity(content) {
|
|
8789
|
+
try {
|
|
8790
|
+
const data = parseSpecMarkdown(content).data ?? {};
|
|
8791
|
+
const id = typeof data.id === "string" ? data.id.trim() : "";
|
|
8792
|
+
const type = typeof data.type === "string" ? data.type.trim() : "";
|
|
8793
|
+
return id && type ? { id, type } : null;
|
|
8794
|
+
} catch {
|
|
8795
|
+
return null;
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
function precheckPush(local, ledger, explicit = false) {
|
|
8799
|
+
const rel = normalizeSpecRelPath(local.relPath);
|
|
8800
|
+
if (!rel) return skip("unsafe-path", local.relPath);
|
|
8801
|
+
if (hasDotSegment(rel))
|
|
8802
|
+
return skip("trashed-path", "files under a dot-folder (.trash/) never sync");
|
|
8803
|
+
if (!explicit && ledger && ledger.hash === local.hash)
|
|
8804
|
+
return skip("unchanged", "disk matches the last sync");
|
|
8805
|
+
if (!readSpecIdentity(local.content)) {
|
|
8806
|
+
return skip("no-id", "no id:/type: in the frontmatter - not a spec the cloud can address");
|
|
8807
|
+
}
|
|
8808
|
+
return null;
|
|
8809
|
+
}
|
|
8810
|
+
function decidePushPlan({
|
|
8811
|
+
local,
|
|
8812
|
+
ledger,
|
|
8813
|
+
remote,
|
|
8814
|
+
force = false,
|
|
8815
|
+
allowUnledgered = false
|
|
8816
|
+
}) {
|
|
8817
|
+
const entry = isLedgerEntry(ledger) ? ledger : ledger?.[local.relPath];
|
|
8818
|
+
const pre = precheckPush(local, entry, allowUnledgered);
|
|
8819
|
+
if (pre) return pre;
|
|
8820
|
+
if (!remote)
|
|
8821
|
+
return skip("new-on-disk", "no remote row - hs never creates cloud specs from a disk file");
|
|
8822
|
+
if (!entry && !allowUnledgered) {
|
|
8823
|
+
return skip("seeded", "no sync record yet - recorded as the baseline, nothing sent");
|
|
8824
|
+
}
|
|
8825
|
+
if (remote.bytes === local.content) {
|
|
8826
|
+
return skip("identical", "disk already equals the remote serialization");
|
|
8827
|
+
}
|
|
8828
|
+
const remoteMs = instantMs(remote.updatedAt);
|
|
8829
|
+
let newer = false;
|
|
8830
|
+
let why = "";
|
|
8831
|
+
if (entry) {
|
|
8832
|
+
const seen = instantMs(entry.remoteUpdatedAt);
|
|
8833
|
+
newer = !Number.isNaN(remoteMs) && !Number.isNaN(seen) && remoteMs > seen;
|
|
8834
|
+
why = `remote updated ${remote.updatedAt} > last synced ${entry.remoteUpdatedAt}`;
|
|
8835
|
+
} else if (!Number.isNaN(remoteMs)) {
|
|
8836
|
+
newer = local.mtimeMs === void 0 || local.mtimeMs < remoteMs;
|
|
8837
|
+
why = `no sync record; remote updated ${remote.updatedAt} is not older than the disk file`;
|
|
8838
|
+
}
|
|
8839
|
+
if (newer && !force) return skip("remote-newer", why);
|
|
8840
|
+
return { reason: "push", push: true, ...newer ? { forced: true, detail: why } : {} };
|
|
8841
|
+
}
|
|
8842
|
+
function isLedgerEntry(v) {
|
|
8843
|
+
return !!v && typeof v === "object" && typeof v.hash === "string";
|
|
8844
|
+
}
|
|
8845
|
+
function jsonSafe(value) {
|
|
8846
|
+
if (value instanceof Date) {
|
|
8847
|
+
const iso = value.toISOString();
|
|
8848
|
+
return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso;
|
|
8849
|
+
}
|
|
8850
|
+
if (Array.isArray(value)) return value.map(jsonSafe);
|
|
8851
|
+
if (value && typeof value === "object") {
|
|
8852
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, jsonSafe(v)]));
|
|
8853
|
+
}
|
|
8854
|
+
return value;
|
|
8855
|
+
}
|
|
8856
|
+
function buildPushPayload(content) {
|
|
8857
|
+
const parsed = parseSpecMarkdown(content);
|
|
8858
|
+
const frontmatter = jsonSafe({ ...parsed.data ?? {} });
|
|
8859
|
+
delete frontmatter.status;
|
|
8860
|
+
return { id: String(frontmatter.id ?? ""), frontmatter, body: parsed.content };
|
|
8861
|
+
}
|
|
8862
|
+
function matchesOnly(relPath, id, only) {
|
|
8863
|
+
const base = basenameOfRelPath(relPath).toLowerCase();
|
|
8864
|
+
const lid = (id ?? "").toLowerCase();
|
|
8865
|
+
return only.some((sel) => {
|
|
8866
|
+
const s = sel.toLowerCase();
|
|
8867
|
+
return base === s || lid === s || base.startsWith(`${s}-`) || lid.startsWith(`${s}-`);
|
|
8868
|
+
});
|
|
8869
|
+
}
|
|
8870
|
+
async function runVaultPush(deps, options2 = {}) {
|
|
8871
|
+
const dryRun = options2.dryRun !== false;
|
|
8872
|
+
const only = (options2.only ?? []).map((s) => s.trim()).filter(Boolean);
|
|
8873
|
+
const allowUnledgered = options2.allowUnledgered === true || only.length > 0;
|
|
8874
|
+
const result = {
|
|
8875
|
+
dryRun,
|
|
8876
|
+
scanned: 0,
|
|
8877
|
+
pushed: [],
|
|
8878
|
+
skipped: [],
|
|
8879
|
+
conflicts: [],
|
|
8880
|
+
warnings: [],
|
|
8881
|
+
failed: [],
|
|
8882
|
+
unmatchedSelectors: [],
|
|
8883
|
+
deferred: []
|
|
8884
|
+
};
|
|
8885
|
+
const ledger = await deps.readLedger();
|
|
8886
|
+
let relPaths = options2.paths ? [...options2.paths] : await deps.listLocal();
|
|
8887
|
+
relPaths = relPaths.map((p) => normalizeSpecRelPath(p) ?? p);
|
|
8888
|
+
let remoteIndex = null;
|
|
8889
|
+
if (deps.listRemote) {
|
|
8890
|
+
remoteIndex = /* @__PURE__ */ new Map();
|
|
8891
|
+
for (const r of await deps.listRemote()) {
|
|
8892
|
+
if (r.basename) remoteIndex.set(r.basename.toLowerCase(), r.updatedAt ?? null);
|
|
8893
|
+
}
|
|
8894
|
+
}
|
|
8895
|
+
const matchedSelectors = /* @__PURE__ */ new Set();
|
|
8896
|
+
let puts = 0;
|
|
8897
|
+
for (const relPath of relPaths) {
|
|
8898
|
+
const local = await deps.readLocal(relPath);
|
|
8899
|
+
if (!local) continue;
|
|
8900
|
+
const identity = readSpecIdentity(local.content);
|
|
8901
|
+
if (only.length > 0) {
|
|
8902
|
+
if (!matchesOnly(relPath, identity?.id ?? null, only)) continue;
|
|
8903
|
+
for (const sel of only) {
|
|
8904
|
+
if (matchesOnly(relPath, identity?.id ?? null, [sel])) matchedSelectors.add(sel);
|
|
8905
|
+
}
|
|
8906
|
+
}
|
|
8907
|
+
result.scanned += 1;
|
|
8908
|
+
const entry = ledger[relPath];
|
|
8909
|
+
const pl2 = {
|
|
8910
|
+
relPath,
|
|
8911
|
+
content: local.content,
|
|
8912
|
+
hash: local.hash,
|
|
8913
|
+
mtimeMs: local.mtimeMs
|
|
8914
|
+
};
|
|
8915
|
+
const item = (reason, detail) => ({
|
|
8916
|
+
relPath,
|
|
8917
|
+
id: identity?.id ?? null,
|
|
8918
|
+
reason,
|
|
8919
|
+
...detail ? { detail } : {}
|
|
8920
|
+
});
|
|
8921
|
+
const pre = precheckPush(pl2, entry, allowUnledgered);
|
|
8922
|
+
if (pre) {
|
|
8923
|
+
result.skipped.push(item(pre.reason, pre.detail));
|
|
8924
|
+
continue;
|
|
8925
|
+
}
|
|
8926
|
+
const basename4 = basenameOfRelPath(relPath);
|
|
8927
|
+
if (remoteIndex) {
|
|
8928
|
+
const stamp = remoteIndex.get(basename4.toLowerCase());
|
|
8929
|
+
if (stamp === void 0) {
|
|
8930
|
+
result.skipped.push(
|
|
8931
|
+
item("new-on-disk", "no remote row - hs never creates cloud specs from a disk file")
|
|
8932
|
+
);
|
|
8933
|
+
continue;
|
|
8934
|
+
}
|
|
8935
|
+
if (!entry && !allowUnledgered) {
|
|
8936
|
+
if (!dryRun) {
|
|
8937
|
+
deps.recordLedger(relPath, { hash: local.hash, remoteUpdatedAt: stamp, by: "push" });
|
|
8938
|
+
}
|
|
8939
|
+
result.skipped.push(
|
|
8940
|
+
item("seeded", "no sync record yet - recorded as the baseline, nothing sent")
|
|
8941
|
+
);
|
|
8942
|
+
continue;
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
let remote;
|
|
8946
|
+
try {
|
|
8947
|
+
remote = await deps.fetchRemote(basename4);
|
|
8948
|
+
} catch (err) {
|
|
8949
|
+
result.failed.push({
|
|
8950
|
+
relPath,
|
|
8951
|
+
id: identity?.id ?? null,
|
|
8952
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8953
|
+
});
|
|
8954
|
+
continue;
|
|
8955
|
+
}
|
|
8956
|
+
const decision = decidePushPlan({
|
|
8957
|
+
local: pl2,
|
|
8958
|
+
ledger: entry,
|
|
8959
|
+
remote: remote ? { updatedAt: remote.updatedAt, bytes: deps.serialize(remote.body, remote.frontmatter) } : null,
|
|
8960
|
+
force: options2.force === true,
|
|
8961
|
+
allowUnledgered
|
|
8962
|
+
});
|
|
8963
|
+
if (decision.reason === "seeded" && remote && !dryRun) {
|
|
8964
|
+
deps.recordLedger(relPath, {
|
|
8965
|
+
hash: local.hash,
|
|
8966
|
+
remoteUpdatedAt: remote.updatedAt,
|
|
8967
|
+
by: "push"
|
|
8968
|
+
});
|
|
8969
|
+
}
|
|
8970
|
+
if (decision.reason === "identical" && remote && !dryRun) {
|
|
8971
|
+
deps.recordLedger(relPath, {
|
|
8972
|
+
hash: local.hash,
|
|
8973
|
+
remoteUpdatedAt: remote.updatedAt,
|
|
8974
|
+
by: "push"
|
|
8975
|
+
});
|
|
8976
|
+
}
|
|
8977
|
+
if (!decision.push) {
|
|
8978
|
+
(decision.reason === "remote-newer" ? result.conflicts : result.skipped).push(
|
|
8979
|
+
item(decision.reason, decision.detail)
|
|
8980
|
+
);
|
|
8981
|
+
continue;
|
|
8982
|
+
}
|
|
8983
|
+
if (dryRun) {
|
|
8984
|
+
result.pushed.push(item("push", decision.detail));
|
|
8985
|
+
continue;
|
|
8986
|
+
}
|
|
8987
|
+
if (options2.maxPuts !== void 0 && puts >= options2.maxPuts) {
|
|
8988
|
+
result.deferred.push(item("push", "rate cap for this run"));
|
|
8989
|
+
continue;
|
|
8990
|
+
}
|
|
8991
|
+
const payload = buildPushPayload(local.content);
|
|
8992
|
+
puts += 1;
|
|
8993
|
+
let put;
|
|
8994
|
+
try {
|
|
8995
|
+
put = await deps.putRemote(basename4, {
|
|
8996
|
+
frontmatter: payload.frontmatter,
|
|
8997
|
+
body: payload.body
|
|
8998
|
+
});
|
|
8999
|
+
} catch (err) {
|
|
9000
|
+
result.failed.push({
|
|
9001
|
+
relPath,
|
|
9002
|
+
id: payload.id,
|
|
9003
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9004
|
+
});
|
|
9005
|
+
continue;
|
|
9006
|
+
}
|
|
9007
|
+
if (!put.ok) {
|
|
9008
|
+
result.failed.push({
|
|
9009
|
+
relPath,
|
|
9010
|
+
id: payload.id,
|
|
9011
|
+
error: put.error ?? `PUT returned ${put.status}`
|
|
9012
|
+
});
|
|
9013
|
+
if (put.status === 429) {
|
|
9014
|
+
result.retryAfterSec = put.retryAfterSec ?? 60;
|
|
9015
|
+
break;
|
|
9016
|
+
}
|
|
9017
|
+
continue;
|
|
9018
|
+
}
|
|
9019
|
+
if (put.statusIgnored) {
|
|
9020
|
+
result.warnings.push(
|
|
9021
|
+
`${relPath}: status is owned by PATCH /api/specs/${payload.id}/status - the disk status was not pushed`
|
|
9022
|
+
);
|
|
9023
|
+
}
|
|
9024
|
+
deps.recordLedger(relPath, {
|
|
9025
|
+
hash: local.hash,
|
|
9026
|
+
remoteUpdatedAt: put.updatedAt ?? null,
|
|
9027
|
+
by: "push"
|
|
9028
|
+
});
|
|
9029
|
+
result.pushed.push(item("push", decision.detail));
|
|
9030
|
+
try {
|
|
9031
|
+
await deps.markDirty?.(payload.id);
|
|
9032
|
+
} catch {
|
|
9033
|
+
}
|
|
9034
|
+
}
|
|
9035
|
+
result.unmatchedSelectors = only.filter((s) => !matchedSelectors.has(s));
|
|
9036
|
+
try {
|
|
9037
|
+
await deps.flushLedger();
|
|
9038
|
+
} catch {
|
|
9039
|
+
}
|
|
9040
|
+
return result;
|
|
9041
|
+
}
|
|
9042
|
+
|
|
9043
|
+
// spex-console/src/lib/vault-sync/push-io.ts
|
|
9044
|
+
import { promises as fs11 } from "node:fs";
|
|
9045
|
+
import nodePath4 from "node:path";
|
|
9046
|
+
init_vault_frontmatter();
|
|
9047
|
+
async function putRemoteSpec(http, id, payload) {
|
|
9048
|
+
const { baseUrl, token, fetchImpl = fetch } = http;
|
|
9049
|
+
const path15 = `/api/specs/${encodeURIComponent(id)}`;
|
|
9050
|
+
const scope = http.accountId && http.vaultId ? cloudRequestScope({ accountId: http.accountId, vaultId: http.vaultId }).headersFor(
|
|
9051
|
+
"PUT",
|
|
9052
|
+
"/api/specs/:id"
|
|
9053
|
+
) : http.scopeHeaders ?? {};
|
|
9054
|
+
const headers = {
|
|
9055
|
+
Accept: "application/json",
|
|
9056
|
+
"Content-Type": "application/json",
|
|
9057
|
+
...scope
|
|
9058
|
+
};
|
|
9059
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
9060
|
+
const res = await fetchImpl(`${baseUrl}${path15}`, {
|
|
9061
|
+
method: "PUT",
|
|
9062
|
+
headers,
|
|
9063
|
+
body: JSON.stringify({ frontmatter: payload.frontmatter, body: payload.body })
|
|
9064
|
+
});
|
|
9065
|
+
const text2 = await res.text();
|
|
9066
|
+
let json = null;
|
|
9067
|
+
try {
|
|
9068
|
+
json = JSON.parse(text2);
|
|
9069
|
+
} catch {
|
|
9070
|
+
}
|
|
9071
|
+
if (res.status !== 200) {
|
|
9072
|
+
const retry = Number(res.headers.get("retry-after"));
|
|
9073
|
+
return {
|
|
9074
|
+
ok: false,
|
|
9075
|
+
status: res.status,
|
|
9076
|
+
error: `PUT ${path15} returned ${res.status}: ${text2.slice(0, 200)}`,
|
|
9077
|
+
...res.status === 429 && Number.isFinite(retry) && retry > 0 ? { retryAfterSec: retry } : {}
|
|
9078
|
+
};
|
|
9079
|
+
}
|
|
9080
|
+
const spec = json?.spec ?? null;
|
|
9081
|
+
return {
|
|
9082
|
+
ok: true,
|
|
9083
|
+
status: 200,
|
|
9084
|
+
updatedAt: remoteStamp(spec),
|
|
9085
|
+
statusIgnored: json?.status_ignored === true
|
|
9086
|
+
};
|
|
9087
|
+
}
|
|
9088
|
+
async function fetchPushRemote(http, basename4) {
|
|
9089
|
+
const { status, json, text: text2 } = await getJson(http, `api/specs/${encodeURIComponent(basename4)}`);
|
|
9090
|
+
if (status === 404) return null;
|
|
9091
|
+
if (status !== 200) {
|
|
9092
|
+
throw new VaultPullSourceError(
|
|
9093
|
+
`GET /api/specs/${basename4} returned ${status}: ${text2.slice(0, 200)}`,
|
|
9094
|
+
status
|
|
9095
|
+
);
|
|
9096
|
+
}
|
|
9097
|
+
const spec = json?.spec;
|
|
9098
|
+
const fm = spec?.frontmatter && typeof spec.frontmatter === "object" ? spec.frontmatter : null;
|
|
9099
|
+
if (!spec || typeof spec.body !== "string" || !fm) {
|
|
9100
|
+
throw new VaultPullSourceError(`GET /api/specs/${basename4} returned no usable spec`, status);
|
|
9101
|
+
}
|
|
9102
|
+
return {
|
|
9103
|
+
id: String(spec.id ?? fm.id ?? ""),
|
|
9104
|
+
updatedAt: remoteStamp(spec),
|
|
9105
|
+
body: spec.body,
|
|
9106
|
+
frontmatter: fm
|
|
9107
|
+
};
|
|
9108
|
+
}
|
|
9109
|
+
function createVaultPushDeps({
|
|
9110
|
+
baseUrl,
|
|
9111
|
+
token,
|
|
9112
|
+
fetchImpl,
|
|
9113
|
+
vaultRoot: vaultRoot2,
|
|
9114
|
+
scopeHeaders,
|
|
9115
|
+
accountId,
|
|
9116
|
+
vaultId,
|
|
9117
|
+
ledger = createLedgerStore(vaultRoot2),
|
|
9118
|
+
withListIndex = false,
|
|
9119
|
+
markDirty
|
|
9120
|
+
}) {
|
|
9121
|
+
const http = {
|
|
9122
|
+
baseUrl,
|
|
9123
|
+
token,
|
|
9124
|
+
fetchImpl,
|
|
9125
|
+
scopeHeaders,
|
|
9126
|
+
accountId,
|
|
9127
|
+
vaultId
|
|
9128
|
+
};
|
|
9129
|
+
const specsRoot = nodePath4.join(vaultRoot2, "specs");
|
|
9130
|
+
return {
|
|
9131
|
+
listLocal: () => listLocalSpecRelPaths(specsRoot),
|
|
9132
|
+
readLocal: async (relPath) => {
|
|
9133
|
+
const abs = nodePath4.join(specsRoot, relPath);
|
|
9134
|
+
try {
|
|
9135
|
+
const [content, stat4] = await Promise.all([fs11.readFile(abs, "utf8"), fs11.stat(abs)]);
|
|
9136
|
+
return { content, hash: hashContent(content), mtimeMs: stat4.mtimeMs };
|
|
9137
|
+
} catch {
|
|
9138
|
+
return null;
|
|
9139
|
+
}
|
|
9140
|
+
},
|
|
9141
|
+
readLedger: () => ledger.snapshot(),
|
|
9142
|
+
fetchRemote: (basename4) => fetchPushRemote(http, basename4),
|
|
9143
|
+
listRemote: withListIndex ? () => listRemoteSpecs(http) : void 0,
|
|
9144
|
+
serialize: stringifySpecMarkdown,
|
|
9145
|
+
putRemote: (basename4, payload) => putRemoteSpec(http, basename4, payload),
|
|
9146
|
+
recordLedger: (relPath, entry) => ledger.record(relPath, entry),
|
|
9147
|
+
flushLedger: () => ledger.flush(),
|
|
9148
|
+
markDirty
|
|
9149
|
+
};
|
|
9150
|
+
}
|
|
9151
|
+
|
|
9152
|
+
// spex-console/src/lib/vault-sync/push-watch.ts
|
|
9153
|
+
var realClock = {
|
|
9154
|
+
now: () => Date.now(),
|
|
9155
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
9156
|
+
clearTimeout: (h) => clearTimeout(h)
|
|
9157
|
+
};
|
|
9158
|
+
var DEFAULT_PUSH_OPTIONS = {
|
|
9159
|
+
debounceMs: 1e3,
|
|
9160
|
+
recentPullWriteMs: 2e3,
|
|
9161
|
+
maxPutsPerWindow: 20,
|
|
9162
|
+
windowMs: 36e5
|
|
9163
|
+
};
|
|
9164
|
+
function formatPushTick(result) {
|
|
9165
|
+
const out = [];
|
|
9166
|
+
for (const p of result.pushed) {
|
|
9167
|
+
out.push({ level: "info", line: `relay: pushed ${p.relPath} (${p.id ?? "?"}) to the cloud` });
|
|
9168
|
+
}
|
|
9169
|
+
for (const c of result.conflicts) {
|
|
9170
|
+
const ref = c.id ?? c.relPath;
|
|
9171
|
+
out.push({
|
|
9172
|
+
level: "warn",
|
|
9173
|
+
line: `relay: conflict ${c.relPath} (${c.id ?? "?"}) - the cloud copy changed since the last sync; kept both. hs pull --overwrite --only ${ref} | hs push --force --only ${ref} --apply`
|
|
9174
|
+
});
|
|
9175
|
+
}
|
|
9176
|
+
for (const f of result.failed) {
|
|
9177
|
+
out.push({
|
|
9178
|
+
level: "warn",
|
|
9179
|
+
line: `relay: push failed ${f.relPath} (${f.id ?? "?"}) - ${f.error}`
|
|
9180
|
+
});
|
|
9181
|
+
}
|
|
9182
|
+
for (const w of result.warnings) out.push({ level: "warn", line: `relay: push warning ${w}` });
|
|
9183
|
+
return out;
|
|
9184
|
+
}
|
|
9185
|
+
function createPushScheduler(deps, opts = DEFAULT_PUSH_OPTIONS) {
|
|
9186
|
+
const { clock } = deps;
|
|
9187
|
+
const timers = /* @__PURE__ */ new Map();
|
|
9188
|
+
const ready = /* @__PURE__ */ new Set();
|
|
9189
|
+
const pullWrites = /* @__PURE__ */ new Map();
|
|
9190
|
+
const putTimes = [];
|
|
9191
|
+
let running = null;
|
|
9192
|
+
let retryHandle = null;
|
|
9193
|
+
let blockedUntil = 0;
|
|
9194
|
+
let disposed = false;
|
|
9195
|
+
const budget = () => {
|
|
9196
|
+
const cutoff = clock.now() - opts.windowMs;
|
|
9197
|
+
while (putTimes.length > 0 && putTimes[0] <= cutoff) putTimes.shift();
|
|
9198
|
+
return Math.max(0, opts.maxPutsPerWindow - putTimes.length);
|
|
9199
|
+
};
|
|
9200
|
+
function scheduleRetry(ms) {
|
|
9201
|
+
if (retryHandle || disposed) return;
|
|
9202
|
+
retryHandle = clock.setTimeout(() => {
|
|
9203
|
+
retryHandle = null;
|
|
9204
|
+
void drain();
|
|
9205
|
+
}, ms);
|
|
9206
|
+
}
|
|
9207
|
+
async function runDrain() {
|
|
9208
|
+
while (ready.size > 0 && !disposed) {
|
|
9209
|
+
const wait = blockedUntil - clock.now();
|
|
9210
|
+
if (wait > 0) return scheduleRetry(wait);
|
|
9211
|
+
const room = budget();
|
|
9212
|
+
if (room === 0) {
|
|
9213
|
+
deps.log(
|
|
9214
|
+
"warn",
|
|
9215
|
+
`relay: push deferred - hourly write cap (${opts.maxPutsPerWindow}) reached; ${ready.size} pending`
|
|
9216
|
+
);
|
|
9217
|
+
return scheduleRetry(Math.max(1e3, putTimes[0] + opts.windowMs - clock.now()));
|
|
9218
|
+
}
|
|
9219
|
+
const batch = [...ready];
|
|
9220
|
+
ready.clear();
|
|
9221
|
+
let result;
|
|
9222
|
+
try {
|
|
9223
|
+
result = await deps.pushPaths(batch, room);
|
|
9224
|
+
} catch (err) {
|
|
9225
|
+
deps.log(
|
|
9226
|
+
"warn",
|
|
9227
|
+
`relay: push failed - ${err instanceof Error ? err.message : String(err)}`
|
|
9228
|
+
);
|
|
9229
|
+
continue;
|
|
9230
|
+
}
|
|
9231
|
+
for (const { level, line } of formatPushTick(result)) deps.log(level, line);
|
|
9232
|
+
for (const p of result.pushed) if (!result.dryRun) putTimes.push(clock.now());
|
|
9233
|
+
for (const d of result.deferred) ready.add(d.relPath);
|
|
9234
|
+
if (result.retryAfterSec) {
|
|
9235
|
+
blockedUntil = clock.now() + result.retryAfterSec * 1e3;
|
|
9236
|
+
for (const f of result.failed) ready.add(f.relPath);
|
|
9237
|
+
}
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
function drain() {
|
|
9241
|
+
if (!running) {
|
|
9242
|
+
running = runDrain().finally(() => {
|
|
9243
|
+
running = null;
|
|
9244
|
+
if (ready.size > 0 && !disposed && blockedUntil <= clock.now() && budget() > 0)
|
|
9245
|
+
void drain();
|
|
9246
|
+
});
|
|
9247
|
+
}
|
|
9248
|
+
return running;
|
|
9249
|
+
}
|
|
9250
|
+
return {
|
|
9251
|
+
notify(relPath) {
|
|
9252
|
+
if (disposed) return;
|
|
9253
|
+
const wrote = pullWrites.get(relPath);
|
|
9254
|
+
if (wrote !== void 0 && clock.now() - wrote < opts.recentPullWriteMs) return;
|
|
9255
|
+
const existing = timers.get(relPath);
|
|
9256
|
+
if (existing) clock.clearTimeout(existing);
|
|
9257
|
+
timers.set(
|
|
9258
|
+
relPath,
|
|
9259
|
+
clock.setTimeout(() => {
|
|
9260
|
+
timers.delete(relPath);
|
|
9261
|
+
ready.add(relPath);
|
|
9262
|
+
void drain();
|
|
9263
|
+
}, opts.debounceMs)
|
|
9264
|
+
);
|
|
9265
|
+
},
|
|
9266
|
+
notePullWrite(relPath) {
|
|
9267
|
+
pullWrites.set(relPath, clock.now());
|
|
9268
|
+
const pending = timers.get(relPath);
|
|
9269
|
+
if (pending) {
|
|
9270
|
+
clock.clearTimeout(pending);
|
|
9271
|
+
timers.delete(relPath);
|
|
9272
|
+
}
|
|
9273
|
+
},
|
|
9274
|
+
async idle() {
|
|
9275
|
+
while (running) await running;
|
|
9276
|
+
},
|
|
9277
|
+
dispose() {
|
|
9278
|
+
disposed = true;
|
|
9279
|
+
for (const h of timers.values()) clock.clearTimeout(h);
|
|
9280
|
+
timers.clear();
|
|
9281
|
+
if (retryHandle) clock.clearTimeout(retryHandle);
|
|
9282
|
+
}
|
|
9283
|
+
};
|
|
9284
|
+
}
|
|
9285
|
+
async function startPushFileWatch(specsRoot, scheduler) {
|
|
9286
|
+
const chokidar = await Promise.resolve().then(() => (init_esm2(), esm_exports));
|
|
9287
|
+
const nodePath5 = await import("node:path");
|
|
9288
|
+
const watcher = chokidar.watch(specsRoot, {
|
|
9289
|
+
ignoreInitial: true,
|
|
9290
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
|
|
9291
|
+
ignored: (p) => {
|
|
9292
|
+
const rel = nodePath5.relative(specsRoot, p).split(nodePath5.sep);
|
|
9293
|
+
return rel.some((seg) => seg.startsWith(".") && seg !== "");
|
|
9294
|
+
}
|
|
9295
|
+
});
|
|
9296
|
+
const onFile = (abs) => {
|
|
9297
|
+
if (!/\.md$/i.test(abs)) return;
|
|
9298
|
+
scheduler.notify(nodePath5.relative(specsRoot, abs).split(nodePath5.sep).join("/"));
|
|
6405
9299
|
};
|
|
9300
|
+
watcher.on("add", onFile).on("change", onFile);
|
|
9301
|
+
return () => watcher.close();
|
|
6406
9302
|
}
|
|
6407
9303
|
|
|
6408
9304
|
// spex-console/bin/run-entry.ts
|
|
@@ -6434,9 +9330,25 @@ function runEntry(name, main2, options2 = {}) {
|
|
|
6434
9330
|
}
|
|
6435
9331
|
|
|
6436
9332
|
// spex-console/bin/hs-vault-pull.ts
|
|
9333
|
+
function makeSleeper() {
|
|
9334
|
+
let wakeNow = null;
|
|
9335
|
+
const sleep = (ms, signal) => new Promise((resolve7) => {
|
|
9336
|
+
if (signal.aborted) return resolve7();
|
|
9337
|
+
const done = () => {
|
|
9338
|
+
clearTimeout(timer);
|
|
9339
|
+
signal.removeEventListener("abort", done);
|
|
9340
|
+
wakeNow = null;
|
|
9341
|
+
resolve7();
|
|
9342
|
+
};
|
|
9343
|
+
const timer = setTimeout(done, ms);
|
|
9344
|
+
wakeNow = done;
|
|
9345
|
+
signal.addEventListener("abort", done, { once: true });
|
|
9346
|
+
});
|
|
9347
|
+
return { sleep, wake: () => wakeNow?.() };
|
|
9348
|
+
}
|
|
6437
9349
|
function readMarkerVaultId(vaultRoot2) {
|
|
6438
9350
|
try {
|
|
6439
|
-
return parseVaultId(
|
|
9351
|
+
return parseVaultId(readFileSync13(path14.join(vaultRoot2, "heyspex.yaml"), "utf8"));
|
|
6440
9352
|
} catch {
|
|
6441
9353
|
return null;
|
|
6442
9354
|
}
|
|
@@ -6513,20 +9425,118 @@ ${PULL_HELP}
|
|
|
6513
9425
|
accountId: account.accountId,
|
|
6514
9426
|
vaultId: target.vaultId
|
|
6515
9427
|
}).headersFor("GET", "/api/specs");
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
9428
|
+
const ledger = createLedgerStore(vaultRoot2);
|
|
9429
|
+
let pushScheduler = null;
|
|
9430
|
+
const deps = createVaultPullDeps({
|
|
9431
|
+
baseUrl,
|
|
9432
|
+
token,
|
|
9433
|
+
scopeHeaders,
|
|
9434
|
+
localRoot: path14.join(vaultRoot2, "specs"),
|
|
9435
|
+
ledger,
|
|
9436
|
+
onLocalWrite: (relPath) => pushScheduler?.notePullWrite(relPath)
|
|
9437
|
+
});
|
|
9438
|
+
if (args.watch) {
|
|
9439
|
+
const controller = new AbortController();
|
|
9440
|
+
const stop = () => controller.abort();
|
|
9441
|
+
process4.once("SIGINT", stop);
|
|
9442
|
+
process4.once("SIGTERM", stop);
|
|
9443
|
+
process4.stdout.write(
|
|
9444
|
+
`vault: ${target.vaultId} (source: ${target.source} \u2014 ${target.detail})
|
|
9445
|
+
source: ${baseUrl}
|
|
9446
|
+
watch: every ${args.intervalSec}s while active, backing off to ${Math.round(Math.max(DEFAULT_WATCH_OPTIONS.maxIntervalMs, args.intervalSec * 1e3) / 1e3)}s while idle
|
|
9447
|
+
`
|
|
9448
|
+
);
|
|
9449
|
+
const log = (level, line) => (level === "warn" ? process4.stderr : process4.stdout).write(`${line}
|
|
9450
|
+
`);
|
|
9451
|
+
const { sleep, wake } = makeSleeper();
|
|
9452
|
+
const dirtyIds = /* @__PURE__ */ new Set();
|
|
9453
|
+
const closeDirty = watchDirtyMarker(vaultRoot2, (id) => {
|
|
9454
|
+
dirtyIds.add(id);
|
|
9455
|
+
wake();
|
|
9456
|
+
});
|
|
9457
|
+
process4.stdout.write(`dirty-trigger: ${dirtyMarkerPath(vaultRoot2)}
|
|
9458
|
+
`);
|
|
9459
|
+
let closePushWatch = null;
|
|
9460
|
+
if (args.push) {
|
|
9461
|
+
const common = {
|
|
6519
9462
|
baseUrl,
|
|
6520
9463
|
token,
|
|
9464
|
+
vaultRoot: vaultRoot2,
|
|
6521
9465
|
scopeHeaders,
|
|
6522
|
-
|
|
6523
|
-
|
|
9466
|
+
accountId: account.accountId,
|
|
9467
|
+
vaultId: target.vaultId,
|
|
9468
|
+
ledger,
|
|
9469
|
+
markDirty: (id) => writeDirtyMarker(vaultRoot2, id)
|
|
9470
|
+
};
|
|
9471
|
+
const eventDeps = createVaultPushDeps(common);
|
|
9472
|
+
const sweepDeps = createVaultPushDeps({ ...common, withListIndex: true });
|
|
9473
|
+
const cap = DEFAULT_PUSH_OPTIONS.maxPutsPerWindow;
|
|
9474
|
+
pushScheduler = createPushScheduler({
|
|
9475
|
+
clock: realClock,
|
|
9476
|
+
log,
|
|
9477
|
+
pushPaths: (paths, maxPuts) => runVaultPush(eventDeps, {
|
|
9478
|
+
dryRun: false,
|
|
9479
|
+
force: args.force,
|
|
9480
|
+
paths,
|
|
9481
|
+
maxPuts
|
|
9482
|
+
})
|
|
9483
|
+
});
|
|
9484
|
+
try {
|
|
9485
|
+
const sweep = await runVaultPush(sweepDeps, {
|
|
9486
|
+
dryRun: false,
|
|
9487
|
+
force: args.force,
|
|
9488
|
+
maxPuts: cap
|
|
9489
|
+
});
|
|
9490
|
+
const seeded = sweep.skipped.filter((i) => i.reason === "seeded").length;
|
|
9491
|
+
for (const { level, line } of formatPushTick(sweep)) log(level, line);
|
|
9492
|
+
process4.stdout.write(
|
|
9493
|
+
`push: ready \u2014 ${seeded} spec(s) newly baselined, ${sweep.pushed.length} pushed, ${sweep.conflicts.length} conflict(s); watching specs/ (cap ${cap} writes/hour)
|
|
9494
|
+
`
|
|
9495
|
+
);
|
|
9496
|
+
} catch (err) {
|
|
9497
|
+
log(
|
|
9498
|
+
"warn",
|
|
9499
|
+
`relay: push startup sweep failed \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
9500
|
+
);
|
|
9501
|
+
}
|
|
9502
|
+
closePushWatch = await startPushFileWatch(path14.join(vaultRoot2, "specs"), pushScheduler);
|
|
9503
|
+
}
|
|
9504
|
+
const summary = await runPullWatch(
|
|
9505
|
+
{
|
|
9506
|
+
pullOnce: () => runVaultPull(deps, { dryRun: false, overwrite: args.overwrite, only: args.only }),
|
|
9507
|
+
sleep,
|
|
9508
|
+
log,
|
|
9509
|
+
signal: controller.signal,
|
|
9510
|
+
sourceLabel: baseUrl,
|
|
9511
|
+
takeDirty: () => {
|
|
9512
|
+
const ids = [...dirtyIds];
|
|
9513
|
+
dirtyIds.clear();
|
|
9514
|
+
return ids;
|
|
9515
|
+
},
|
|
9516
|
+
pullDirty: (ids) => runVaultPull(deps, { dryRun: false, only: ids, direct: true })
|
|
9517
|
+
},
|
|
6524
9518
|
{
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
9519
|
+
...DEFAULT_WATCH_OPTIONS,
|
|
9520
|
+
baseIntervalMs: args.intervalSec * 1e3,
|
|
9521
|
+
maxIntervalMs: Math.max(DEFAULT_WATCH_OPTIONS.maxIntervalMs, args.intervalSec * 1e3)
|
|
6528
9522
|
}
|
|
6529
9523
|
);
|
|
9524
|
+
closeDirty();
|
|
9525
|
+
pushScheduler?.dispose();
|
|
9526
|
+
await closePushWatch?.();
|
|
9527
|
+
process4.stdout.write(
|
|
9528
|
+
`watch stopped after ${summary.ticks} tick(s): ${summary.written} pulled, ${summary.failed} failed
|
|
9529
|
+
`
|
|
9530
|
+
);
|
|
9531
|
+
process4.exitCode = PULL_EXIT.OK;
|
|
9532
|
+
return;
|
|
9533
|
+
}
|
|
9534
|
+
try {
|
|
9535
|
+
const result = await runVaultPull(deps, {
|
|
9536
|
+
dryRun: !args.apply,
|
|
9537
|
+
overwrite: args.overwrite,
|
|
9538
|
+
only: args.only
|
|
9539
|
+
});
|
|
6530
9540
|
if (args.json) {
|
|
6531
9541
|
process4.stdout.write(
|
|
6532
9542
|
`${JSON.stringify(
|