@juspay/neurolink 12.44.5 → 12.44.7
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 +2 -2
- package/dist/browser/neurolink.min.js +272 -272
- package/dist/cli/commands/proxy.js +30 -13
- package/dist/cli/commands/proxyPolyfill.d.ts +4 -0
- package/dist/cli/commands/proxyPolyfill.js +55 -0
- package/dist/cli/parser.js +3 -1
- package/dist/cli/proxy-clients/registry.d.ts +2 -0
- package/dist/cli/proxy-clients/registry.js +4 -0
- package/dist/proxy/accountCooldown.js +5 -2
- package/dist/proxy/accountQuota.js +2 -0
- package/dist/proxy/codexFallback.js +3 -4
- package/dist/proxy/globalInstaller.d.ts +3 -1
- package/dist/proxy/globalInstaller.js +151 -4
- package/dist/proxy/proxyPackagePolyfills.d.ts +18 -0
- package/dist/proxy/proxyPackagePolyfills.js +334 -0
- package/dist/server/routes/claudeProxyRoutes.js +7 -2
- package/dist/server/routes/codexProxyRoutes.js +24 -8
- package/dist/types/proxy.d.ts +26 -0
- package/docs-site/static/search-index.json +4 -3
- package/package.json +5 -3
|
@@ -9,7 +9,7 @@ import chalk from "chalk";
|
|
|
9
9
|
import ora from "ora";
|
|
10
10
|
import { buildProxyHealthResponse, createProxyReadinessState, markProxyDrainingForUpdate, markProxyReady, resumeProxyConnections, waitForProxyReadiness, } from "../../proxy/proxyHealth.js";
|
|
11
11
|
import { logger } from "../../utils/logger.js";
|
|
12
|
-
import { applyAllClients, restoreAllClients, } from "../proxy-clients/registry.js";
|
|
12
|
+
import { applyAllClients, applyClientsOnProxyStart, restoreAllClients, } from "../proxy-clients/registry.js";
|
|
13
13
|
import { resolveProxyConfigPath } from "../../proxy/proxyConfig.js";
|
|
14
14
|
import { redactUrlsInText, sanitizeForLog, } from "../../utils/logSanitize.js";
|
|
15
15
|
import { raceWithAbort, withTimeout, } from "../../utils/async/withTimeout.js";
|
|
@@ -23,7 +23,7 @@ import { anthropicAccountKeysEqual, createAccountAllowlist, isAccountAllowed, LE
|
|
|
23
23
|
import { resolveProxyStatusAccountIdentity } from "../../proxy/codexAccountUsage.js";
|
|
24
24
|
import { beginProxyRequest, consumeInternalProxyRequest, releaseProxyRequestAccounting, withProxyFinalLogOwnership, getProxyActivitySnapshot, observeProxyFinalLog, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
|
|
25
25
|
import { configureProxyLifecycleLogger, flushProxyLifecycleEvents, persistProxyLifecycleAcceptance, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
|
|
26
|
-
import { describeInstallFailure, assertProxyServiceInstallIdle, probeProxyLaunchdPresence, inspectProxyPackage, isProxyServiceEnvironmentKey, parseProxyServiceInstallSettings, installStagedProxyPackage, readProxyPackageSelection, resolveProxyWorkerPackage, selectProxyPackage, writeProxyPackageLauncher, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../proxy/globalInstaller.js";
|
|
26
|
+
import { describeInstallFailure, assertProxyServiceInstallIdle, probeProxyLaunchdPresence, inspectProxyPackage, isProxyServiceEnvironmentKey, parseProxyServiceInstallSettings, installStagedProxyPackage, prepareProxyPackageUpgrade, readProxyPackageSelection, resolveProxyWorkerPackage, selectProxyPackage, writeProxyPackageLauncher, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../proxy/globalInstaller.js";
|
|
27
27
|
import { startUpdaterWorkerSupervisor } from "../../proxy/updaterSupervisor.js";
|
|
28
28
|
import { openProxyWorkerLog } from "../../proxy/workerLog.js";
|
|
29
29
|
import { startRollingProxyServer } from "../../proxy/rollingProxyServer.js";
|
|
@@ -2933,9 +2933,7 @@ async function startProxyRuntime(params) {
|
|
|
2933
2933
|
logger.always(` ${chalk.bold("Env File:")} ${chalk.cyan(params.loadedEnvFile)}`);
|
|
2934
2934
|
}
|
|
2935
2935
|
if (!isDev) {
|
|
2936
|
-
for (const result of await
|
|
2937
|
-
configPath: params.configPath,
|
|
2938
|
-
})) {
|
|
2936
|
+
for (const result of await applyClientsOnProxyStart(url, { configPath: params.configPath }, managedByLaunchd)) {
|
|
2939
2937
|
if (result.error) {
|
|
2940
2938
|
// Visible, not debug-level. A client whose config could not be written
|
|
2941
2939
|
// will keep talking to its own upstream, which looks like the proxy
|
|
@@ -2956,6 +2954,9 @@ async function startProxyRuntime(params) {
|
|
|
2956
2954
|
logger.always(chalk.dim(` Restart ${result.displayName} to connect through proxy`));
|
|
2957
2955
|
}
|
|
2958
2956
|
}
|
|
2957
|
+
if (managedByLaunchd) {
|
|
2958
|
+
logger.always(" Service startup preserves client settings; use 'neurolink proxy setup' to change client routing.");
|
|
2959
|
+
}
|
|
2959
2960
|
}
|
|
2960
2961
|
else {
|
|
2961
2962
|
logger.always(chalk.dim(" ⊘ Dev mode: skipping client auto-configuration"));
|
|
@@ -4270,20 +4271,36 @@ export const proxyGuardCommand = {
|
|
|
4270
4271
|
return;
|
|
4271
4272
|
}
|
|
4272
4273
|
try {
|
|
4273
|
-
//
|
|
4274
|
-
//
|
|
4275
|
-
previousPackage
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4274
|
+
// Keep the exact serving tree, including a same-version local
|
|
4275
|
+
// build. Restaging by version would discard its edits and rollback.
|
|
4276
|
+
if (previousPackage?.version !== runningVersion) {
|
|
4277
|
+
try {
|
|
4278
|
+
const original = inspectProxyPackage(process.argv[1]);
|
|
4279
|
+
if (original.version === runningVersion) {
|
|
4280
|
+
previousPackage = original;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
catch {
|
|
4284
|
+
// A mutable global entry may have disappeared since startup.
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (previousPackage?.version !== runningVersion) {
|
|
4288
|
+
previousPackage = await installStagedProxyPackage({
|
|
4289
|
+
version: runningVersion,
|
|
4290
|
+
packagesDir: PROXY_PACKAGES_DIR,
|
|
4291
|
+
installer,
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4280
4294
|
if (!isCurrentUpdateOwner()) {
|
|
4281
4295
|
return;
|
|
4282
4296
|
}
|
|
4283
|
-
candidatePackage = await
|
|
4297
|
+
candidatePackage = await prepareProxyPackageUpgrade({
|
|
4284
4298
|
version: result.latestVersion,
|
|
4285
4299
|
packagesDir: PROXY_PACKAGES_DIR,
|
|
4286
4300
|
installer,
|
|
4301
|
+
activePackage: previousPackage,
|
|
4302
|
+
isCurrentOwner: isCurrentUpdateAttemptOwner,
|
|
4303
|
+
onPolyfills: ({ applied, alreadyIncluded }) => logger.always(`[updater] local polyfills: ${applied.length} files applied, ${alreadyIncluded.length} already included in release`),
|
|
4287
4304
|
onProgress: ({ elapsedMs, outputBytes }) => logger.debug(`[updater] staged install progress elapsedMs=${elapsedMs} outputBytes=${outputBytes}`),
|
|
4288
4305
|
});
|
|
4289
4306
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs";
|
|
2
|
+
import type { ProxyPolyfillArgs } from "../../types/index.js";
|
|
3
|
+
/** Record the base of a locally patched package without changing running services. */
|
|
4
|
+
export declare const proxyPolyfillCommand: CommandModule<object, ProxyPolyfillArgs>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { inspectProxyPackage } from "../../proxy/globalInstaller.js";
|
|
2
|
+
import { captureProxyPackageBaseline } from "../../proxy/proxyPackagePolyfills.js";
|
|
3
|
+
/** Record the base of a locally patched package without changing running services. */
|
|
4
|
+
export const proxyPolyfillCommand = {
|
|
5
|
+
command: "polyfill <action>",
|
|
6
|
+
describe: "Preserve local proxy compatibility edits across staged upgrades",
|
|
7
|
+
builder: (yargs) => yargs
|
|
8
|
+
.positional("action", {
|
|
9
|
+
choices: ["capture"],
|
|
10
|
+
type: "string",
|
|
11
|
+
demandOption: true,
|
|
12
|
+
})
|
|
13
|
+
.option("base", {
|
|
14
|
+
type: "string",
|
|
15
|
+
demandOption: true,
|
|
16
|
+
description: "CLI entry script of the pristine package used for the local edit",
|
|
17
|
+
})
|
|
18
|
+
.option("patched", {
|
|
19
|
+
type: "string",
|
|
20
|
+
demandOption: true,
|
|
21
|
+
description: "CLI entry script of the locally patched package",
|
|
22
|
+
})
|
|
23
|
+
.option("format", {
|
|
24
|
+
choices: ["text", "json"],
|
|
25
|
+
type: "string",
|
|
26
|
+
default: "text",
|
|
27
|
+
})
|
|
28
|
+
.example("neurolink proxy polyfill capture --base /original/dist/cli/index.js --patched /local/dist/cli/index.js", "Record the exact original files so later upgrades can apply only the local diff"),
|
|
29
|
+
handler: async (argv) => {
|
|
30
|
+
try {
|
|
31
|
+
const base = inspectProxyPackage(argv.base);
|
|
32
|
+
const patched = inspectProxyPackage(argv.patched);
|
|
33
|
+
const files = captureProxyPackageBaseline(base, patched);
|
|
34
|
+
const result = {
|
|
35
|
+
captured: true,
|
|
36
|
+
baseVersion: base.version,
|
|
37
|
+
patchedVersion: patched.version,
|
|
38
|
+
files,
|
|
39
|
+
};
|
|
40
|
+
console.info(argv.format === "json"
|
|
41
|
+
? JSON.stringify(result)
|
|
42
|
+
: `Recorded ${files.length} local proxy polyfill file(s). Upgrades will apply remaining diff hunks, skip included fixes, and retain the current worker on conflicts.`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const message = error instanceof Error
|
|
46
|
+
? error.message
|
|
47
|
+
: "Proxy polyfill capture failed";
|
|
48
|
+
console.error(argv.format === "json"
|
|
49
|
+
? JSON.stringify({ captured: false, message })
|
|
50
|
+
: message);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=proxyPolyfill.js.map
|
package/dist/cli/parser.js
CHANGED
|
@@ -24,6 +24,7 @@ import { TelemetryCommandFactory } from "./commands/telemetry.js";
|
|
|
24
24
|
import { proxyStartCommand, proxyStatusCommand, proxyTelemetryCommand, proxySetupCommand, proxyGuardCommand, proxyInstallCommand, proxyUninstallCommand, } from "./commands/proxy.js";
|
|
25
25
|
import { proxyAnalyzeCommand } from "./commands/proxyAnalyze.js";
|
|
26
26
|
import { proxyRestartCommand } from "./commands/proxyRestart.js";
|
|
27
|
+
import { proxyPolyfillCommand } from "./commands/proxyPolyfill.js";
|
|
27
28
|
import { proxyShareCommand } from "./commands/proxyShare.js";
|
|
28
29
|
import { proxyPeerCommand } from "./commands/proxyPeer.js";
|
|
29
30
|
import { proxyExposeCommand } from "./commands/proxyExpose.js";
|
|
@@ -229,6 +230,7 @@ export function initializeCliParser() {
|
|
|
229
230
|
.command(proxyStartCommand)
|
|
230
231
|
.command(proxyStatusCommand)
|
|
231
232
|
.command(proxyRestartCommand)
|
|
233
|
+
.command(proxyPolyfillCommand)
|
|
232
234
|
.command(proxyShareCommand)
|
|
233
235
|
.command(proxyPeerCommand)
|
|
234
236
|
.command(proxyExposeCommand)
|
|
@@ -239,7 +241,7 @@ export function initializeCliParser() {
|
|
|
239
241
|
.command(proxyGuardCommand)
|
|
240
242
|
.command(proxyInstallCommand)
|
|
241
243
|
.command(proxyUninstallCommand)
|
|
242
|
-
.demandCommand(1, "Please specify a proxy subcommand: start, status, restart, share <create|provision|url|list|status|pause|resume|revoke|topup|set|link|rotate|level|note|notes|receipts|delete>, peer <add|request|sync|receipts|net|redeem|list|status|test|remove|pause|resume|set>, expose, analyze, replay <export|compare>, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
|
|
244
|
+
.demandCommand(1, "Please specify a proxy subcommand: start, status, restart, polyfill capture, share <create|provision|url|list|status|pause|resume|revoke|topup|set|link|rotate|level|note|notes|receipts|delete>, peer <add|request|sync|receipts|net|redeem|list|status|test|remove|pause|resume|set>, expose, analyze, replay <export|compare>, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
|
|
243
245
|
handler: () => { },
|
|
244
246
|
})
|
|
245
247
|
// Evaluate Command Group - Using EvaluateCommandFactory
|
|
@@ -15,5 +15,7 @@ export declare const PROXY_CLIENT_CONFIGURATORS: readonly CliProxyClientConfigur
|
|
|
15
15
|
* while the setup wizard prints a visible warning.
|
|
16
16
|
*/
|
|
17
17
|
export declare function applyAllClients(proxyBaseUrl: string, options?: CliProxyClientApplyOptions): Promise<CliProxyClientApplyResult[]>;
|
|
18
|
+
/** Service replacements own the server process, not the user's client routing. */
|
|
19
|
+
export declare function applyClientsOnProxyStart(proxyBaseUrl: string, options: CliProxyClientApplyOptions, managedByLaunchd: boolean): Promise<CliProxyClientApplyResult[]>;
|
|
18
20
|
/** Restore every client's previous configuration. See applyAllClients. */
|
|
19
21
|
export declare function restoreAllClients(proxyBaseUrl: string): Promise<CliProxyClientRestoreResult[]>;
|
|
@@ -77,6 +77,10 @@ export async function applyAllClients(proxyBaseUrl, options) {
|
|
|
77
77
|
}
|
|
78
78
|
return results;
|
|
79
79
|
}
|
|
80
|
+
/** Service replacements own the server process, not the user's client routing. */
|
|
81
|
+
export async function applyClientsOnProxyStart(proxyBaseUrl, options, managedByLaunchd) {
|
|
82
|
+
return managedByLaunchd ? [] : applyAllClients(proxyBaseUrl, options);
|
|
83
|
+
}
|
|
80
84
|
/** Restore every client's previous configuration. See applyAllClients. */
|
|
81
85
|
export async function restoreAllClients(proxyBaseUrl) {
|
|
82
86
|
const results = [];
|
|
@@ -98,7 +98,9 @@ export async function saveAccountCooldown(accountKey, coolingUntil, reason) {
|
|
|
98
98
|
await ensureAccountCooldownsLoaded();
|
|
99
99
|
// Another worker (a draining one during a rolling restart) may have written
|
|
100
100
|
// the file since this one loaded it, so the change folds onto the file as
|
|
101
|
-
// it is now rather than onto this worker's copy.
|
|
101
|
+
// it is now rather than onto this worker's copy. Best-effort: the mutex is
|
|
102
|
+
// per process, so two workers writing within the same few ms can still
|
|
103
|
+
// lose one update.
|
|
102
104
|
const onDisk = await readCooldownsFromDisk(memoryCache);
|
|
103
105
|
memoryCache = onDisk;
|
|
104
106
|
const current = onDisk[accountKey];
|
|
@@ -116,7 +118,8 @@ export async function clearAccountCooldown(accountKey, expectedCoolingUntil) {
|
|
|
116
118
|
await mutationMutex.runExclusive(async () => {
|
|
117
119
|
await ensureAccountCooldownsLoaded();
|
|
118
120
|
// Compare against the file, not this worker's copy: another worker may
|
|
119
|
-
// have extended this cooldown since, and that one must survive.
|
|
121
|
+
// have extended this cooldown since, and that one must survive. Same
|
|
122
|
+
// best-effort limit as saveAccountCooldown: no cross-process lock.
|
|
120
123
|
const onDisk = await readCooldownsFromDisk(memoryCache);
|
|
121
124
|
memoryCache = onDisk;
|
|
122
125
|
const current = onDisk[accountKey];
|
|
@@ -387,6 +387,8 @@ async function flushToDisk() {
|
|
|
387
387
|
// Another worker (a draining one during a rolling restart) may have
|
|
388
388
|
// written the file since this one loaded it: fold only this worker's
|
|
389
389
|
// changes onto the file as it is now, never rewrite it from this copy.
|
|
390
|
+
// Best-effort: the mutexes are per process, so two workers flushing
|
|
391
|
+
// within the same few ms can still lose one update.
|
|
390
392
|
const onDisk = await readQuotasFromDisk(filePath, fallback);
|
|
391
393
|
const merged = { ...onDisk };
|
|
392
394
|
for (const [key, quota] of Object.entries(flushing)) {
|
|
@@ -343,10 +343,9 @@ function codexSessionId(body) {
|
|
|
343
343
|
return undefined;
|
|
344
344
|
}
|
|
345
345
|
const fields = parsed;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
: undefined;
|
|
346
|
+
// `??` would let an empty or non-string session_id shadow a usable
|
|
347
|
+
// parent_session_id, so each field has to qualify on its own.
|
|
348
|
+
return [fields.session_id, fields.parent_session_id].find((value) => typeof value === "string" && value.length > 0);
|
|
350
349
|
}
|
|
351
350
|
/** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
|
|
352
351
|
export function convertClaudeRequestToCodex(body, model, reasoningEffort) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProxyServiceInstallSettings, ProxyPackageSelection, ProxyStagedInstallOptions, GlobalInstallerExecFile, GlobalInstallerKind, GlobalInstallerResolution, InstalledVersionValidation, ResolveGlobalInstallerOptions, ValidateInstalledVersionOptions } from "../types/index.js";
|
|
1
|
+
import type { ProxyServiceInstallSettings, ProxyPackageSelection, ProxyStagedInstallOptions, ProxyPackageUpgradeOptions, GlobalInstallerExecFile, GlobalInstallerKind, GlobalInstallerResolution, InstalledVersionValidation, ResolveGlobalInstallerOptions, ValidateInstalledVersionOptions } from "../types/index.js";
|
|
2
2
|
/** Resolve a package manager that can update the installation currently running. */
|
|
3
3
|
export declare function resolveGlobalInstaller(options?: ResolveGlobalInstallerOptions): GlobalInstallerResolution;
|
|
4
4
|
export declare function getGlobalInstallArgs(kind: GlobalInstallerKind, packageSpec: string): string[];
|
|
@@ -32,6 +32,8 @@ export declare function selectProxyPackage(packagesDir: string, selection: Proxy
|
|
|
32
32
|
export declare function writeProxyPackageLauncher(path: string, selection: ProxyPackageSelection, isCurrentOwner?: () => boolean): void;
|
|
33
33
|
/** Install in a private tree, then publish the immutable directory after validation. */
|
|
34
34
|
export declare function installStagedProxyPackage(options: ProxyStagedInstallOptions): Promise<ProxyPackageSelection>;
|
|
35
|
+
/** Preserve local runtime diff hunks before any launcher or live selection changes. */
|
|
36
|
+
export declare function prepareProxyPackageUpgrade(options: ProxyPackageUpgradeOptions): Promise<ProxyPackageSelection>;
|
|
35
37
|
/** Only operator runtime settings survive reinstall, never worker IPC identity. */
|
|
36
38
|
export declare function isProxyServiceEnvironmentKey(name: string): boolean;
|
|
37
39
|
/** Parse plutil JSON without exposing credentials or invoking the saved launcher. */
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execFileSync as nodeExecFileSync, spawn as nodeSpawn, } from "node:child_process";
|
|
2
2
|
import { accessSync, constants, existsSync, realpathSync, readFileSync, mkdirSync, writeFileSync, renameSync, chmodSync, rmSync, } from "node:fs";
|
|
3
|
-
import { mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { cp, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, resolve, relative, } from "node:path";
|
|
7
|
+
import { changedProxyRuntimeFiles, proxyPackageRoot, readProxyPackageBaseline, reconcileProxyPackagePolyfills, snapshotProxyPackage, writeProxyPackageBaseline, writeProxyPolyfilledFile, } from "./proxyPackagePolyfills.js";
|
|
7
8
|
function runText(execFileSync, bin, args) {
|
|
8
9
|
return String(execFileSync(bin, args, {
|
|
9
10
|
encoding: "utf8",
|
|
@@ -373,7 +374,10 @@ export function selectProxyPackage(packagesDir, selection, isCurrentOwner) {
|
|
|
373
374
|
const state = {
|
|
374
375
|
schemaVersion: 1,
|
|
375
376
|
active: selection,
|
|
376
|
-
previous: current.active &&
|
|
377
|
+
previous: current.active &&
|
|
378
|
+
(current.active.version !== selection.version ||
|
|
379
|
+
current.active.entryScript !== selection.entryScript ||
|
|
380
|
+
current.active.nodePath !== selection.nodePath)
|
|
377
381
|
? current.active
|
|
378
382
|
: current.previous,
|
|
379
383
|
};
|
|
@@ -539,7 +543,8 @@ export async function installStagedProxyPackage(options) {
|
|
|
539
543
|
}
|
|
540
544
|
});
|
|
541
545
|
});
|
|
542
|
-
validate(staging);
|
|
546
|
+
const stagedSelection = validate(staging);
|
|
547
|
+
writeProxyPackageBaseline(stagedSelection, snapshotProxyPackage(stagedSelection));
|
|
543
548
|
await rename(staging, destination);
|
|
544
549
|
return validate(destination);
|
|
545
550
|
}
|
|
@@ -547,6 +552,148 @@ export async function installStagedProxyPackage(options) {
|
|
|
547
552
|
await rm(staging, { recursive: true, force: true });
|
|
548
553
|
}
|
|
549
554
|
}
|
|
555
|
+
/** Registry comparison must never inherit an existing directory's dependencies. */
|
|
556
|
+
async function installFreshProxyRegistryPackage(options) {
|
|
557
|
+
if (!/^\d+\.\d+\.\d+$/.test(options.version)) {
|
|
558
|
+
throw new Error("Invalid proxy package version");
|
|
559
|
+
}
|
|
560
|
+
await mkdir(options.packagesDir, { recursive: true, mode: 0o700 });
|
|
561
|
+
const container = await mkdtemp(join(options.packagesDir, `.registry-${options.version}-`));
|
|
562
|
+
try {
|
|
563
|
+
const selection = await installStagedProxyPackage({
|
|
564
|
+
...options,
|
|
565
|
+
packagesDir: container,
|
|
566
|
+
});
|
|
567
|
+
return { selection, container };
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
await rm(container, { recursive: true, force: true });
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/** Preserve local runtime diff hunks before any launcher or live selection changes. */
|
|
575
|
+
export async function prepareProxyPackageUpgrade(options) {
|
|
576
|
+
const assertOwner = () => assertPackageSelectionOwner(options.isCurrentOwner);
|
|
577
|
+
assertOwner();
|
|
578
|
+
const active = options.activePackage;
|
|
579
|
+
let baseline = readProxyPackageBaseline(active);
|
|
580
|
+
if (!baseline) {
|
|
581
|
+
// Legacy packages need a clean registry comparison. Never invent a baseline
|
|
582
|
+
// from edited files: an unknown local build must remain serving until its
|
|
583
|
+
// operator records the actual base with `proxy polyfill capture`.
|
|
584
|
+
const original = await installFreshProxyRegistryPackage({
|
|
585
|
+
...options,
|
|
586
|
+
version: active.version,
|
|
587
|
+
});
|
|
588
|
+
let retainReference = false;
|
|
589
|
+
try {
|
|
590
|
+
assertOwner();
|
|
591
|
+
baseline = readProxyPackageBaseline(original.selection);
|
|
592
|
+
if (!baseline) {
|
|
593
|
+
throw new Error("Pristine registry package baseline is missing");
|
|
594
|
+
}
|
|
595
|
+
if (changedProxyRuntimeFiles(baseline, snapshotProxyPackage(active)).length) {
|
|
596
|
+
retainReference = true;
|
|
597
|
+
throw new Error(`Active proxy contains local changes without a recorded base; upgrade retained the serving package. Record its actual original package with 'proxy polyfill capture --base <original-entry> --patched <local-entry>'. Downloaded registry reference: ${original.selection.entryScript}`);
|
|
598
|
+
}
|
|
599
|
+
writeProxyPackageBaseline(active, baseline);
|
|
600
|
+
}
|
|
601
|
+
finally {
|
|
602
|
+
if (!retainReference) {
|
|
603
|
+
await rm(original.container, { recursive: true, force: true });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const local = snapshotProxyPackage(active);
|
|
608
|
+
// Also rejects unsupported local dependency/entrypoint changes.
|
|
609
|
+
changedProxyRuntimeFiles(baseline, local);
|
|
610
|
+
const fresh = await installFreshProxyRegistryPackage(options);
|
|
611
|
+
try {
|
|
612
|
+
const candidate = fresh.selection;
|
|
613
|
+
assertOwner();
|
|
614
|
+
const candidateBase = readProxyPackageBaseline(candidate);
|
|
615
|
+
if (!candidateBase ||
|
|
616
|
+
changedProxyRuntimeFiles(candidateBase, snapshotProxyPackage(candidate))
|
|
617
|
+
.length) {
|
|
618
|
+
throw new Error("Candidate proxy package has no trustworthy pristine baseline");
|
|
619
|
+
}
|
|
620
|
+
const { files, report } = reconcileProxyPackagePolyfills(baseline, local, candidateBase);
|
|
621
|
+
const assertLocalUnchanged = () => {
|
|
622
|
+
assertOwner();
|
|
623
|
+
if (JSON.stringify(snapshotProxyPackage(active)) !== JSON.stringify(local)) {
|
|
624
|
+
throw new Error("Active proxy files changed while preparing the upgrade; activation refused");
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
assertLocalUnchanged();
|
|
628
|
+
if (!report.applied.length) {
|
|
629
|
+
const destination = join(options.packagesDir, `${options.version}-release-${randomUUID()}`);
|
|
630
|
+
await rename(resolve(proxyPackageRoot(candidate.entryScript), "../../.."), destination);
|
|
631
|
+
const selected = inspectProxyPackage(join(destination, "node_modules", "@juspay", "neurolink", candidateBase.entryRelative));
|
|
632
|
+
assertOwner();
|
|
633
|
+
options.onPolyfills?.(report);
|
|
634
|
+
return selected;
|
|
635
|
+
}
|
|
636
|
+
const digest = createHash("sha256")
|
|
637
|
+
.update(JSON.stringify({ version: options.version, files }))
|
|
638
|
+
.digest("hex")
|
|
639
|
+
.slice(0, 16);
|
|
640
|
+
const destination = join(options.packagesDir, `${options.version}-polyfills-${digest}-${randomUUID()}`);
|
|
641
|
+
const candidateRoot = proxyPackageRoot(candidate.entryScript);
|
|
642
|
+
const expected = { ...candidateBase.files };
|
|
643
|
+
for (const [path, content] of Object.entries(files)) {
|
|
644
|
+
if (content === null) {
|
|
645
|
+
delete expected[path];
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
expected[path] = content;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const validateCopy = (root) => {
|
|
652
|
+
const selection = inspectProxyPackage(join(root, "node_modules", "@juspay", "neurolink", candidateBase.entryRelative));
|
|
653
|
+
const snapshot = snapshotProxyPackage(selection);
|
|
654
|
+
if (selection.version !== options.version ||
|
|
655
|
+
snapshot.manifest !== candidateBase.manifest ||
|
|
656
|
+
Object.keys({ ...snapshot.files, ...expected }).some((path) => snapshot.files[path] !== expected[path])) {
|
|
657
|
+
throw new Error("Polyfilled proxy package does not match its validated plan");
|
|
658
|
+
}
|
|
659
|
+
for (const path of report.applied) {
|
|
660
|
+
if (files[path] !== null && /\.[cm]?js$/.test(path)) {
|
|
661
|
+
(options.execFileSync ?? nodeExecFileSync)(selection.nodePath, ["--check", join(proxyPackageRoot(selection.entryScript), path)], { timeout: 10_000, stdio: "pipe" });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return selection;
|
|
665
|
+
};
|
|
666
|
+
const staging = await mkdtemp(join(options.packagesDir, `.polyfill-${options.version}-`));
|
|
667
|
+
try {
|
|
668
|
+
await cp(resolve(candidateRoot, "../../.."), staging, {
|
|
669
|
+
recursive: true,
|
|
670
|
+
mode: constants.COPYFILE_FICLONE,
|
|
671
|
+
verbatimSymlinks: true,
|
|
672
|
+
});
|
|
673
|
+
const root = join(staging, "node_modules", "@juspay", "neurolink");
|
|
674
|
+
for (const [path, content] of Object.entries(files)) {
|
|
675
|
+
if (content === null) {
|
|
676
|
+
await rm(join(root, path));
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
writeProxyPolyfilledFile(root, path, content);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
validateCopy(staging);
|
|
683
|
+
assertLocalUnchanged();
|
|
684
|
+
await rename(staging, destination);
|
|
685
|
+
const selection = validateCopy(destination);
|
|
686
|
+
options.onPolyfills?.(report);
|
|
687
|
+
return selection;
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
await rm(staging, { recursive: true, force: true });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
finally {
|
|
694
|
+
await rm(fresh.container, { recursive: true, force: true });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
550
697
|
/** Only operator runtime settings survive reinstall, never worker IPC identity. */
|
|
551
698
|
export function isProxyServiceEnvironmentKey(name) {
|
|
552
699
|
return ((name.startsWith("OTEL_") ||
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProxyPackageBaseline, ProxyPackagePolyfillReport, ProxyPackageSelection } from "../types/index.js";
|
|
2
|
+
/** Locate the package without importing its executable or provider graph. */
|
|
3
|
+
export declare function proxyPackageRoot(entryScript: string): string;
|
|
4
|
+
/** Bounded baseline of shipped proxy code; operator config and dependencies are excluded. */
|
|
5
|
+
export declare function snapshotProxyPackage(selection: ProxyPackageSelection): ProxyPackageBaseline;
|
|
6
|
+
/** Invalid or missing baseline must never be silently replaced with edited files. */
|
|
7
|
+
export declare function readProxyPackageBaseline(selection: ProxyPackageSelection): ProxyPackageBaseline | null;
|
|
8
|
+
export declare function writeProxyPackageBaseline(selection: ProxyPackageSelection, baseline: ProxyPackageBaseline): void;
|
|
9
|
+
/** Operator supplies the pristine base for a legacy local build; this does not activate it. */
|
|
10
|
+
export declare function captureProxyPackageBaseline(base: ProxyPackageSelection, patched: ProxyPackageSelection): string[];
|
|
11
|
+
export declare function changedProxyRuntimeFiles(base: ProxyPackageBaseline, current: ProxyPackageBaseline): string[];
|
|
12
|
+
/** Apply exact local hunks, leaving independent upstream changes intact. */
|
|
13
|
+
export declare function reconcileProxyPackagePolyfills(base: ProxyPackageBaseline, local: ProxyPackageBaseline, candidate: ProxyPackageBaseline): {
|
|
14
|
+
files: Record<string, string | null>;
|
|
15
|
+
report: ProxyPackagePolyfillReport;
|
|
16
|
+
};
|
|
17
|
+
/** Writes are confined to a new staged copy, never the serving package. */
|
|
18
|
+
export declare function writeProxyPolyfilledFile(root: string, path: string, content: string): void;
|