@indigoai-us/hq-cli 5.234.1 → 5.234.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.234.2] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The flag-registry worker now uses source embedded in the CLI, so package
|
|
10
|
+
updates can no longer cause startup failures by removing its script.
|
|
11
|
+
|
|
5
12
|
## [5.234.1] — 2026-09-27
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -12,9 +12,8 @@ import { readMachineCompanyBinding } from "./work-context/company.js";
|
|
|
12
12
|
import { readSessionState } from "./work-context/state.js";
|
|
13
13
|
import { workContextRoot } from "./work-context/paths.js";
|
|
14
14
|
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
15
|
-
import {
|
|
15
|
+
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
16
16
|
import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
|
|
17
|
-
import { FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, flagRegistryWorkerModeCacheFailureContext, flagRegistryStateDir, flagRegistryWorkerModeCachePath, readCachedFlagRegistryWorkerMode, writeCachedFlagRegistryWorkerMode, } from "./flag-registry-worker-mode-cache.js";
|
|
18
17
|
import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
|
|
19
18
|
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
20
19
|
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
@@ -101,86 +100,6 @@ function processSnapshotReader(companyIdentifiers) {
|
|
|
101
100
|
},
|
|
102
101
|
};
|
|
103
102
|
}
|
|
104
|
-
function personSubjectForWorkerMode(idToken) {
|
|
105
|
-
if (typeof idToken !== "string")
|
|
106
|
-
return null;
|
|
107
|
-
const payload = idToken.split(".")[1];
|
|
108
|
-
if (!payload)
|
|
109
|
-
return null;
|
|
110
|
-
try {
|
|
111
|
-
const subject = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).sub;
|
|
112
|
-
return typeof subject === "string" && subject.trim() ? subject : null;
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
Sentry.captureMessage("Could not scope cached flag-registry worker mode because the cached ID token payload is invalid", { level: "warning", tags: { component: "flag-registry-worker-mode-cache" } });
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/** Scope a persisted rollout value to the same identity that fetched it. */
|
|
120
|
-
function workerModeCacheScopes(company) {
|
|
121
|
-
const tokens = loadCachedTokens();
|
|
122
|
-
const personSubject = tokens ? personSubjectForWorkerMode(tokens.idToken) : null;
|
|
123
|
-
if (tokens && !personSubject)
|
|
124
|
-
return null;
|
|
125
|
-
return [
|
|
126
|
-
...(company.companyUid ? [`company:${company.companyUid}`] : []),
|
|
127
|
-
...(company.companySlug ? [`company-slug:${company.companySlug}`] : []),
|
|
128
|
-
...(personSubject ? [`person:${personSubject}`] : []),
|
|
129
|
-
];
|
|
130
|
-
}
|
|
131
|
-
function selfContainedWorkerEnabled(company) {
|
|
132
|
-
const localOverride = readLocalOverride(FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, process.env);
|
|
133
|
-
if (localOverride !== null)
|
|
134
|
-
return localOverride;
|
|
135
|
-
const scopes = workerModeCacheScopes(company);
|
|
136
|
-
if (!scopes)
|
|
137
|
-
return false;
|
|
138
|
-
const filePath = flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes);
|
|
139
|
-
try {
|
|
140
|
-
return readCachedFlagRegistryWorkerMode(filePath)?.value ?? false;
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
const failure = flagRegistryWorkerModeCacheFailureContext(error, "read", filePath);
|
|
144
|
-
Sentry.captureException(failure.exception, {
|
|
145
|
-
tags: {
|
|
146
|
-
component: "flag-registry-worker-mode-cache",
|
|
147
|
-
worker: "flag-registry",
|
|
148
|
-
...failure.tags,
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function cacheWorkerModeFromSnapshot(snapshot, company) {
|
|
155
|
-
// A developer's local hq-flags override is process-local and must not become
|
|
156
|
-
// the value selected by a future process.
|
|
157
|
-
if (readLocalOverride(FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, process.env) !== null)
|
|
158
|
-
return;
|
|
159
|
-
const scopes = workerModeCacheScopes(company);
|
|
160
|
-
if (!scopes)
|
|
161
|
-
return;
|
|
162
|
-
const decision = resolveFlag({
|
|
163
|
-
flagKey: FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG,
|
|
164
|
-
env: {},
|
|
165
|
-
snapshot,
|
|
166
|
-
companyIdentifiers: [company.companyUid, company.companySlug].filter((value) => Boolean(value)),
|
|
167
|
-
lookup: { fallback: false },
|
|
168
|
-
});
|
|
169
|
-
try {
|
|
170
|
-
writeCachedFlagRegistryWorkerMode(flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes), { version: snapshot.version, value: decision.value });
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
const filePath = flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes);
|
|
174
|
-
const failure = flagRegistryWorkerModeCacheFailureContext(error, "write", filePath);
|
|
175
|
-
Sentry.captureException(failure.exception, {
|
|
176
|
-
tags: {
|
|
177
|
-
component: "flag-registry-worker-mode-cache",
|
|
178
|
-
worker: "flag-registry",
|
|
179
|
-
...failure.tags,
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
103
|
/**
|
|
185
104
|
* Begin one registry refresh without awaiting it or retaining the CLI process.
|
|
186
105
|
*
|
|
@@ -199,42 +118,38 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
|
199
118
|
const companyIdentifiers = [companyUid, company.companySlug].filter((value) => Boolean(value));
|
|
200
119
|
processReader = processSnapshotReader(companyIdentifiers);
|
|
201
120
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
121
|
+
const embeddedSource = dependencies.workerSource ?? FLAG_REGISTRY_WORKER_SOURCE;
|
|
122
|
+
if (embeddedSource.length === 0) {
|
|
123
|
+
(dependencies.captureWorkerFailure ?? ((error, tags) => {
|
|
124
|
+
Sentry.captureException(error, { tags });
|
|
125
|
+
}))(new Error("Self-contained flag-registry worker source is unavailable"), {
|
|
126
|
+
component: "flag-registry-worker-startup",
|
|
127
|
+
worker: "flag-registry",
|
|
128
|
+
flag_worker_failure: "embedded_source_unavailable",
|
|
129
|
+
});
|
|
130
|
+
return processReader;
|
|
131
|
+
}
|
|
132
|
+
// This source is embedded at build time, so it remains available while an
|
|
133
|
+
// updater replaces the installed package tree.
|
|
134
|
+
const workerOptions = {
|
|
135
|
+
workerData: {
|
|
204
136
|
endpoint,
|
|
205
137
|
...(companyUid ? { companyUid } : {}),
|
|
206
138
|
...(company.companySlug ? { companyIdentifiers: [company.companySlug] } : {}),
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// build time. It is already resident before company, identity, and cache
|
|
211
|
-
// reads can yield to an updater replacing this package tree.
|
|
212
|
-
const embeddedSource = dependencies.workerSource ?? FLAG_REGISTRY_WORKER_SOURCE;
|
|
213
|
-
if (embeddedSource.length === 0) {
|
|
214
|
-
(dependencies.captureWorkerFailure ?? ((error, tags) => {
|
|
215
|
-
Sentry.captureException(error, { tags });
|
|
216
|
-
}))(new Error("Self-contained flag-registry worker source is unavailable"), {
|
|
217
|
-
component: "flag-registry-worker-startup",
|
|
218
|
-
worker: "flag-registry",
|
|
219
|
-
flag_worker_failure: "embedded_source_unavailable",
|
|
220
|
-
});
|
|
221
|
-
return processReader;
|
|
222
|
-
}
|
|
223
|
-
workerSource = embeddedSource;
|
|
224
|
-
workerOptions = { ...workerOptions, eval: true };
|
|
225
|
-
}
|
|
139
|
+
},
|
|
140
|
+
eval: true,
|
|
141
|
+
};
|
|
226
142
|
// The SDK's child-process integration captures every Worker error unless
|
|
227
143
|
// its process-wide captureWorkerErrors option is disabled. Scope only this
|
|
228
144
|
// worker so the reporting boundary can classify its startup errors without
|
|
229
145
|
// changing capture for unrelated workers.
|
|
230
146
|
const worker = Sentry.withScope((scope) => {
|
|
231
147
|
scope.setTag("worker", "flag-registry");
|
|
232
|
-
return (dependencies.createWorker ?? ((filename, options) => new Worker(filename, options)))(
|
|
148
|
+
return (dependencies.createWorker ?? ((filename, options) => new Worker(filename, options)))(embeddedSource, workerOptions);
|
|
233
149
|
});
|
|
234
150
|
worker.on("message", (value) => {
|
|
235
151
|
if (isFlagSnapshot(value)) {
|
|
236
152
|
heldSnapshot = value;
|
|
237
|
-
cacheWorkerModeFromSnapshot(value, company);
|
|
238
153
|
}
|
|
239
154
|
});
|
|
240
155
|
// A failed background refresh must stay invisible to an offline CLI.
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
|
|
2
|
-
interface CachedWorkerMode {
|
|
3
|
-
version: number;
|
|
4
|
-
value: boolean;
|
|
5
|
-
}
|
|
6
|
-
export type FlagRegistryWorkerModeCacheOperation = "read" | "write";
|
|
7
|
-
export type FlagRegistryWorkerModeCacheStep = "mkdir" | "write_temp" | "link" | "unlink" | "prune" | "readdir" | "read";
|
|
8
|
-
/** Build bounded filesystem diagnostics without exposing the state directory. */
|
|
9
|
-
export declare function flagRegistryWorkerModeCacheFailureContext(error: unknown, operation: FlagRegistryWorkerModeCacheOperation, filePath: string, env?: NodeJS.ProcessEnv): {
|
|
10
|
-
exception: unknown;
|
|
11
|
-
tags: Record<string, string>;
|
|
12
|
-
};
|
|
13
|
-
/** Keep the last server snapshot decision for this one startup-mode flag. */
|
|
14
|
-
export declare function flagRegistryWorkerModeCachePath(stateDir: string, scopeIdentifiers: readonly string[]): string;
|
|
15
|
-
/** A missing, invalid, or inaccessible cache never enables the rollout. */
|
|
16
|
-
export declare function readCachedFlagRegistryWorkerMode(filePath: string): CachedWorkerMode | null;
|
|
17
|
-
export declare function writeCachedFlagRegistryWorkerMode(filePath: string, snapshot: CachedWorkerMode): void;
|
|
18
|
-
export declare function flagRegistryStateDir(env?: NodeJS.ProcessEnv): string;
|
|
19
|
-
export {};
|
|
20
|
-
//# sourceMappingURL=flag-registry-worker-mode-cache.d.ts.map
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import * as fs from "node:fs";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
export const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
|
|
7
|
-
class FlagRegistryWorkerModeCacheStepError extends Error {
|
|
8
|
-
cacheStep;
|
|
9
|
-
originalError;
|
|
10
|
-
constructor(cacheStep, originalError) {
|
|
11
|
-
super(`Flag registry worker-mode cache failed at ${cacheStep}`);
|
|
12
|
-
this.cacheStep = cacheStep;
|
|
13
|
-
this.originalError = originalError;
|
|
14
|
-
this.name = "FlagRegistryWorkerModeCacheStepError";
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
const KNOWN_ERRNOS = new Set([
|
|
18
|
-
"EACCES",
|
|
19
|
-
"EBUSY",
|
|
20
|
-
"EEXIST",
|
|
21
|
-
"EIO",
|
|
22
|
-
"EISDIR",
|
|
23
|
-
"EMFILE",
|
|
24
|
-
"ENFILE",
|
|
25
|
-
"ENOENT",
|
|
26
|
-
"ENOSPC",
|
|
27
|
-
"ENOTDIR",
|
|
28
|
-
"EPERM",
|
|
29
|
-
"EROFS",
|
|
30
|
-
]);
|
|
31
|
-
function withCacheFsStep(cacheStep, operation) {
|
|
32
|
-
try {
|
|
33
|
-
return operation();
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (error instanceof FlagRegistryWorkerModeCacheStepError)
|
|
37
|
-
throw error;
|
|
38
|
-
throw new FlagRegistryWorkerModeCacheStepError(cacheStep, error);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function originalCacheError(error) {
|
|
42
|
-
return error instanceof FlagRegistryWorkerModeCacheStepError
|
|
43
|
-
? error.originalError
|
|
44
|
-
: error;
|
|
45
|
-
}
|
|
46
|
-
function cacheErrorCode(error) {
|
|
47
|
-
const original = originalCacheError(error);
|
|
48
|
-
return typeof original === "object" && original !== null && "code" in original
|
|
49
|
-
? original.code
|
|
50
|
-
: undefined;
|
|
51
|
-
}
|
|
52
|
-
function directoryHasUchg(directory) {
|
|
53
|
-
if (process.platform !== "darwin")
|
|
54
|
-
return "unknown";
|
|
55
|
-
try {
|
|
56
|
-
const rawFlags = execFileSync("/usr/bin/stat", ["-f", "%f", directory], { timeout: 1000 }).toString("utf8").trim();
|
|
57
|
-
if (!/^\d+$/.test(rawFlags))
|
|
58
|
-
return "unknown";
|
|
59
|
-
const flags = Number(rawFlags);
|
|
60
|
-
if (!Number.isSafeInteger(flags))
|
|
61
|
-
return "unknown";
|
|
62
|
-
return String((flags & 0x00000002) !== 0 || (flags & 0x00020000) !== 0);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
// This bounded diagnostic is best-effort; preserve unknown and the original cache error.
|
|
66
|
-
return "unknown";
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/** Build bounded filesystem diagnostics without exposing the state directory. */
|
|
70
|
-
export function flagRegistryWorkerModeCacheFailureContext(error, operation, filePath, env = process.env) {
|
|
71
|
-
const cacheError = error instanceof FlagRegistryWorkerModeCacheStepError
|
|
72
|
-
? error
|
|
73
|
-
: undefined;
|
|
74
|
-
const directory = path.dirname(filePath);
|
|
75
|
-
let dirExists = "unknown";
|
|
76
|
-
let stats;
|
|
77
|
-
try {
|
|
78
|
-
stats = fs.statSync(directory);
|
|
79
|
-
dirExists = "true";
|
|
80
|
-
}
|
|
81
|
-
catch (metadataError) {
|
|
82
|
-
if (cacheErrorCode(metadataError) === "ENOENT")
|
|
83
|
-
dirExists = "false";
|
|
84
|
-
// Keep inaccessible metadata unknown; diagnostics must not hide the cache error.
|
|
85
|
-
}
|
|
86
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
87
|
-
const ownerMatches = typeof uid === "number" && typeof stats?.uid === "number"
|
|
88
|
-
? String(stats.uid === uid)
|
|
89
|
-
: "unknown";
|
|
90
|
-
const dirMode = typeof stats?.mode === "number"
|
|
91
|
-
? (stats.mode & 0o777).toString(8).padStart(3, "0")
|
|
92
|
-
: "unknown";
|
|
93
|
-
const dirHasUchg = directoryHasUchg(directory);
|
|
94
|
-
const rawErrno = cacheErrorCode(error);
|
|
95
|
-
const errno = typeof rawErrno === "string" && KNOWN_ERRNOS.has(rawErrno)
|
|
96
|
-
? rawErrno
|
|
97
|
-
: "UNKNOWN";
|
|
98
|
-
const stateDirSource = env.HQ_STATE_DIR?.trim() ? "env" : "home";
|
|
99
|
-
return {
|
|
100
|
-
exception: originalCacheError(error),
|
|
101
|
-
tags: {
|
|
102
|
-
cache_op: operation,
|
|
103
|
-
cache_step: cacheError?.cacheStep ?? (operation === "read" ? "read" : "write_temp"),
|
|
104
|
-
errno,
|
|
105
|
-
state_dir_source: stateDirSource,
|
|
106
|
-
dir_exists: dirExists,
|
|
107
|
-
dir_owner_matches_uid: ownerMatches,
|
|
108
|
-
dir_mode: dirMode,
|
|
109
|
-
dir_has_uchg: dirHasUchg,
|
|
110
|
-
cache_path_kind: "<stateDir>/flags/<basename>",
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
/** Keep the last server snapshot decision for this one startup-mode flag. */
|
|
115
|
-
export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
|
|
116
|
-
const basename = scopeIdentifiers.length === 0
|
|
117
|
-
? "worker-mode-default.json"
|
|
118
|
-
: `worker-mode-${createHash("sha256")
|
|
119
|
-
.update([...scopeIdentifiers].sort().join("\0"))
|
|
120
|
-
.digest("hex")
|
|
121
|
-
.slice(0, 24)}.json`;
|
|
122
|
-
return path.join(stateDir, "flags", basename);
|
|
123
|
-
}
|
|
124
|
-
/** A missing, invalid, or inaccessible cache never enables the rollout. */
|
|
125
|
-
export function readCachedFlagRegistryWorkerMode(filePath) {
|
|
126
|
-
let latest = readCacheRecordIfPresent(filePath);
|
|
127
|
-
const directory = path.dirname(filePath);
|
|
128
|
-
if (!withCacheFsStep("read", () => fs.existsSync(directory)))
|
|
129
|
-
return latest;
|
|
130
|
-
const basename = path.basename(filePath);
|
|
131
|
-
const versionedPrefix = `${basename}.v`;
|
|
132
|
-
for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
|
|
133
|
-
if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
|
|
134
|
-
continue;
|
|
135
|
-
const versionedPath = path.join(directory, entry);
|
|
136
|
-
const record = readCacheRecordIfPresent(versionedPath);
|
|
137
|
-
if (record)
|
|
138
|
-
latest = newerCacheRecord(latest, record);
|
|
139
|
-
}
|
|
140
|
-
return latest;
|
|
141
|
-
}
|
|
142
|
-
export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
|
|
143
|
-
withCacheFsStep("mkdir", () => fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 }));
|
|
144
|
-
const existing = readCachedFlagRegistryWorkerMode(filePath);
|
|
145
|
-
if (existing && snapshot.version < existing.version)
|
|
146
|
-
return;
|
|
147
|
-
// Each registry version gets its own immutable destination. Concurrent
|
|
148
|
-
// processes can publish v7 and v8 in either order without an older value
|
|
149
|
-
// replacing the newer record.
|
|
150
|
-
const versionedPath = `${filePath}.v${snapshot.version}.json`;
|
|
151
|
-
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
152
|
-
try {
|
|
153
|
-
withCacheFsStep("write_temp", () => fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
|
|
154
|
-
encoding: "utf8",
|
|
155
|
-
mode: 0o600,
|
|
156
|
-
flag: "wx",
|
|
157
|
-
}));
|
|
158
|
-
try {
|
|
159
|
-
withCacheFsStep("link", () => fs.linkSync(tempPath, versionedPath));
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
if (!isAlreadyExistsError(error))
|
|
163
|
-
throw error;
|
|
164
|
-
const existingVersion = readCacheRecord(versionedPath);
|
|
165
|
-
if (existingVersion.value !== snapshot.value) {
|
|
166
|
-
throw new FlagRegistryWorkerModeCacheStepError("link", new Error("Conflicting flag-registry worker cache values share a version"));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
if (withCacheFsStep("unlink", () => fs.existsSync(tempPath))) {
|
|
172
|
-
withCacheFsStep("unlink", () => fs.unlinkSync(tempPath));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
pruneOlderCacheRecords(filePath);
|
|
176
|
-
}
|
|
177
|
-
export function flagRegistryStateDir(env = process.env) {
|
|
178
|
-
const override = env.HQ_STATE_DIR?.trim();
|
|
179
|
-
return override ? override : path.join(os.homedir(), ".hq");
|
|
180
|
-
}
|
|
181
|
-
function isCachedWorkerMode(value) {
|
|
182
|
-
return typeof value === "object" && value !== null &&
|
|
183
|
-
"version" in value && typeof value.version === "number" &&
|
|
184
|
-
Number.isSafeInteger(value.version) && value.version >= 0 &&
|
|
185
|
-
"value" in value && typeof value.value === "boolean";
|
|
186
|
-
}
|
|
187
|
-
function readCacheRecord(filePath) {
|
|
188
|
-
const contents = withCacheFsStep("read", () => fs.readFileSync(filePath, "utf8"));
|
|
189
|
-
const value = JSON.parse(contents);
|
|
190
|
-
if (!isCachedWorkerMode(value)) {
|
|
191
|
-
throw new Error("Cached flag-registry worker mode has an invalid shape");
|
|
192
|
-
}
|
|
193
|
-
return value;
|
|
194
|
-
}
|
|
195
|
-
function newerCacheRecord(current, candidate) {
|
|
196
|
-
if (!current || candidate.version > current.version)
|
|
197
|
-
return candidate;
|
|
198
|
-
if (candidate.version === current.version && candidate.value !== current.value) {
|
|
199
|
-
throw new Error("Conflicting flag-registry worker cache values share a version");
|
|
200
|
-
}
|
|
201
|
-
return current;
|
|
202
|
-
}
|
|
203
|
-
function pruneOlderCacheRecords(filePath) {
|
|
204
|
-
const latest = readCachedFlagRegistryWorkerMode(filePath);
|
|
205
|
-
if (!latest)
|
|
206
|
-
return;
|
|
207
|
-
const directory = path.dirname(filePath);
|
|
208
|
-
const basename = path.basename(filePath);
|
|
209
|
-
const versionedPrefix = `${basename}.v`;
|
|
210
|
-
for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
|
|
211
|
-
if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
|
|
212
|
-
continue;
|
|
213
|
-
const recordPath = path.join(directory, entry);
|
|
214
|
-
const record = readCacheRecordIfPresent(recordPath);
|
|
215
|
-
if (record && record.version < latest.version) {
|
|
216
|
-
try {
|
|
217
|
-
withCacheFsStep("prune", () => fs.unlinkSync(recordPath));
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (!isNotFoundError(error))
|
|
221
|
-
throw error;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (withCacheFsStep("prune", () => fs.existsSync(filePath))) {
|
|
226
|
-
const legacyRecord = readCacheRecordIfPresent(filePath);
|
|
227
|
-
if (legacyRecord && legacyRecord.version < latest.version) {
|
|
228
|
-
try {
|
|
229
|
-
withCacheFsStep("prune", () => fs.unlinkSync(filePath));
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
if (!isNotFoundError(error))
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function readCacheRecordIfPresent(filePath) {
|
|
239
|
-
try {
|
|
240
|
-
return readCacheRecord(filePath);
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
if (isNotFoundError(error))
|
|
244
|
-
return null;
|
|
245
|
-
throw error;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function isAlreadyExistsError(error) {
|
|
249
|
-
return cacheErrorCode(error) === "EEXIST";
|
|
250
|
-
}
|
|
251
|
-
function isNotFoundError(error) {
|
|
252
|
-
return cacheErrorCode(error) === "ENOENT";
|
|
253
|
-
}
|
|
254
|
-
//# sourceMappingURL=flag-registry-worker-mode-cache.js.map
|