@indigoai-us/hq-cli 5.291.0 → 5.292.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +27 -8
  2. package/assets/scaffold/core/scripts/handoff-finalize.sh +18 -0
  3. package/assets/scaffold/core/scripts/handoff-post.sh +71 -6
  4. package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
  5. package/assets/scaffold/core/scripts/hq-delegate-grant.sh +226 -0
  6. package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
  7. package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
  8. package/assets/scaffold/core/scripts/hq-delegate-verify.sh +213 -0
  9. package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
  10. package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
  11. package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
  12. package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
  13. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +830 -0
  14. package/dist/command-catalog.generated.d.ts +106 -1
  15. package/dist/command-catalog.generated.js +132 -1
  16. package/dist/commands/core-delegate-w1b.d.ts +8 -0
  17. package/dist/commands/core-delegate-w1b.js +1049 -0
  18. package/dist/commands/core-delegate.d.ts +28 -0
  19. package/dist/commands/core-delegate.js +15 -13
  20. package/dist/commands/core-handoff.js +152 -23
  21. package/dist/commands/core-jobs-native.d.ts +15 -0
  22. package/dist/commands/core-jobs-native.js +709 -2
  23. package/dist/commands/core-ontology.d.ts +22 -4
  24. package/dist/commands/core-ontology.js +90 -8
  25. package/dist/commands/core.js +10 -0
  26. package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
  27. package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
  28. package/dist/lib/checkpoint-ontology/entity-file.d.ts +6 -3
  29. package/dist/lib/checkpoint-ontology/entity-file.js +12 -6
  30. package/dist/lib/checkpoint-ontology/merge-copies.d.ts +27 -8
  31. package/dist/lib/checkpoint-ontology/merge-copies.js +64 -97
  32. package/dist/lib/daemon/flag.d.ts +2 -0
  33. package/dist/lib/daemon/flag.js +2 -0
  34. package/dist/utils/scaffold-sibling-references.js +4 -4
  35. package/dist/utils/self-update.d.ts +6 -0
  36. package/dist/utils/self-update.js +28 -6
  37. package/dist/utils/staged-install.d.ts +34 -0
  38. package/dist/utils/staged-install.js +296 -0
  39. package/package.json +1 -1
  40. package/scripts/mesh-daemon-if-stale.mjs +4 -0
@@ -27,16 +27,25 @@
27
27
  *
28
28
  * A signal whose `entity_refs` names a copy holds that copy: signals are
29
29
  * server-owned, so the ref cannot be re-pointed from here. With `rewriteRefs`
30
- * (flag `checkpoint.ontology.merge-rewrite-refs`) signal refs, invoice and
31
- * bill links, source and knowledge links are re-pointed at the survivor, the
32
- * survivor receives the copy's `external_ids` and `web_domains`, and only a
33
- * link from another copy or an external id conflict holds a pair.
30
+ * (flag `checkpoint.ontology.merge-rewrite-refs`) invoice and bill links and
31
+ * knowledge links are re-pointed at the survivor and the survivor receives
32
+ * the copy's `external_ids` and `web_domains`. A signal ref or a link from a
33
+ * source still holds the pair (`server_owned_refs`): the sync client cannot
34
+ * push those files. A link from another copy or an external id conflict also
35
+ * holds a pair.
34
36
  *
35
37
  * With `pairKind: "variants"` (flag `checkpoint.ontology.merge-variants`) the
36
38
  * pairs are two-file duplicate groups whose canonical names differ only in
37
39
  * case, spacing or punctuation; the survivor is the spaced name, then the
38
40
  * higher `signal_count`, then path order. The copy's canonical name becomes
39
41
  * an alias of the survivor. Merge and delete work as for numbered copies.
42
+ *
43
+ * With `pairKind: "listed"` (flag `checkpoint.ontology.merge-listed-pairs`)
44
+ * the pairs are exactly the `only` entries, reviewed by a person: no finder
45
+ * runs, so the names may differ. Each entry must name two entities of one
46
+ * type; a copy listed twice, or listed both as a copy and as an original, is
47
+ * skipped as `listed_conflict`. Holds, merge and delete work as for numbered
48
+ * copies.
40
49
  */
41
50
  import * as fs from "node:fs";
42
51
  import * as path from "node:path";
@@ -53,6 +62,8 @@ export const MERGE_COPIES_FLAG = "checkpoint.ontology.merge-copies";
53
62
  * names differ only in spacing, case or punctuation (CO-65).
54
63
  */
55
64
  export const MERGE_VARIANTS_FLAG = "checkpoint.ontology.merge-variants";
65
+ /** `merge-copies --listed`: merge exactly the reviewed `--only` pairs (CO-74). */
66
+ export const MERGE_LISTED_PAIRS_FLAG = "checkpoint.ontology.merge-listed-pairs";
56
67
  /**
57
68
  * `merge-copies --rewrite-refs`: re-point signal `entity_refs`, invoice and
58
69
  * bill links, source and knowledge links from a copy to its survivor, and
@@ -219,6 +230,36 @@ export function findVariantPairs(index) {
219
230
  pairs.sort((a, b) => a.copy.localeCompare(b.copy));
220
231
  return { pairs, skipped };
221
232
  }
233
+ /**
234
+ * The reviewed pairs themselves, for `pairKind: "listed"`. An entry is kept
235
+ * when both files are indexed entities of one writable type. Read-only.
236
+ */
237
+ export function findListedPairs(index, only) {
238
+ const pairs = [];
239
+ const skipped = [];
240
+ const copyCount = new Map();
241
+ for (const entry of only)
242
+ copyCount.set(entry.copy, (copyCount.get(entry.copy) ?? 0) + 1);
243
+ const originals = new Set(only.map((entry) => entry.original));
244
+ for (const entry of only) {
245
+ const copy = index.byRel.get(entry.copy);
246
+ const original = index.byRel.get(entry.original);
247
+ if (copyCount.get(entry.copy) > 1 || originals.has(entry.copy) || entry.copy === entry.original) {
248
+ skipped.push({ copy: entry.copy, reason: "listed_conflict" });
249
+ }
250
+ else if (!copy || !original) {
251
+ skipped.push({ copy: entry.copy, reason: index.invalid.has(entry.original) ? "original_invalid" : "no_original" });
252
+ }
253
+ else if (copy.type !== original.type || !isWritableEntityType(copy.type)) {
254
+ skipped.push({ copy: entry.copy, reason: "type_differs" });
255
+ }
256
+ else {
257
+ pairs.push({ type: copy.type, copy: entry.copy, original: entry.original });
258
+ }
259
+ }
260
+ pairs.sort((a, b) => a.copy.localeCompare(b.copy));
261
+ return { pairs, skipped };
262
+ }
222
263
  /** Resolve a relative link in `fromRel` to a company-relative path, or null. */
223
264
  function resolveLink(fromRel, href) {
224
265
  if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("#") || href.startsWith("/"))
@@ -525,76 +566,13 @@ function referencesCopy(fromRel, content, copy) {
525
566
  }
526
567
  return false;
527
568
  }
528
- function entityRefOf(rel) {
529
- const [, , type, file] = rel.split("/");
530
- return `${type}/${file.slice(0, -3)}`;
531
- }
532
- /** Re-point block-style `entity_refs` items from a copy to its survivor. */
533
- function rewriteSignalRefs(content, copyRef, originalRef) {
534
- const front = frontLines(content);
535
- if (!front)
536
- return content;
537
- const index = front.lines.findIndex((line) => /^entity_refs:\s*$/.test(line));
538
- if (index === -1) {
539
- const inline = front.lines.some((line) => /^entity_refs:\s*\S/.test(line));
540
- if (inline && content.includes(copyRef))
541
- return "signal_ref_format";
542
- return content;
543
- }
544
- let end = index + 1;
545
- // A block sequence may sit at the key's own indent: `entity_refs:\n- company/x`.
546
- while (end < front.lines.length && (/^\s/.test(front.lines[end]) || /^- /.test(front.lines[end])))
547
- end += 1;
548
- const itemRe = /^(\s*)- (["']?)([^"'\n]+?)\2\s*$/;
549
- const refs = [];
550
- for (let i = index + 1; i < end; i += 1) {
551
- const match = itemRe.exec(front.lines[i]);
552
- if (!match)
553
- continue;
554
- const hadMd = match[3].endsWith(".md");
555
- refs.push({
556
- index: i,
557
- value: hadMd ? match[3].slice(0, -3) : match[3],
558
- hadMd,
559
- indent: match[1],
560
- quote: match[2],
561
- });
562
- }
563
- const copyItems = refs.filter((item) => item.value === copyRef);
564
- if (copyItems.length === 0)
565
- return content;
566
- const lines = [...front.lines];
567
- const remove = new Set();
568
- let seenOriginal = refs.some((item) => item.value === originalRef);
569
- for (const item of copyItems) {
570
- if (seenOriginal) {
571
- remove.add(item.index);
572
- }
573
- else {
574
- const value = item.hadMd ? `${originalRef}.md` : originalRef;
575
- lines[item.index] = `${item.indent}- ${item.quote}${value}${item.quote}`;
576
- seenOriginal = true;
577
- }
578
- }
579
- const next = withFrontLines(content, front, lines.filter((_, i) => !remove.has(i)), fileNewline(content));
580
- const oldParsed = parseEntityFile(content);
581
- const newParsed = parseEntityFile(next);
582
- if (!oldParsed || !newParsed || oldParsed.body !== newParsed.body)
583
- return "signal_ref_format";
584
- const keys = new Set([...Object.keys(oldParsed.frontmatter), ...Object.keys(newParsed.frontmatter)]);
585
- for (const key of keys) {
586
- if (key === "entity_refs")
587
- continue;
588
- if (!isDeepStrictEqual(oldParsed.frontmatter[key], newParsed.frontmatter[key]))
589
- return "signal_ref_format";
590
- }
591
- const newRefs = newParsed.frontmatter.entity_refs;
592
- if (!Array.isArray(newRefs) || !newRefs.every((ref) => typeof ref === "string"))
593
- return "signal_ref_format";
594
- const normalized = newRefs.map((ref) => (ref.endsWith(".md") ? ref.slice(0, -3) : ref));
595
- if (!normalized.includes(originalRef) || normalized.includes(copyRef))
596
- return "signal_ref_format";
597
- return next;
569
+ /**
570
+ * Signals and sources are written by the server pipeline and the sync
571
+ * client never pushes them, so a rewrite made here would not reach the
572
+ * vault (hq-cloud cloud-authoritative.ts).
573
+ */
574
+ function isServerOwnedRel(rel) {
575
+ return rel.startsWith("signals/") || rel.startsWith("sources/");
598
576
  }
599
577
  const REF_KEYS = new Set(["system", "namespace", "objectType", "recordId"]);
600
578
  function refKey(ref) {
@@ -871,6 +849,7 @@ function plan(company, index, pairs, allPairs, now, rewriteRefs, resolutions = n
871
849
  const unwritable = rewriteRefs
872
850
  ? inbound.filter((rel) => copies.has(rel))
873
851
  : inbound.filter((rel) => !rel.startsWith("ontology/entities/") || !isWritableEntityType(rel.split("/")[2]) || copies.has(rel));
852
+ const serverOwned = rewriteRefs ? [...refs, ...inbound.filter(isServerOwnedRel)] : [];
874
853
  if (typeof merged === "string") {
875
854
  entry.held = "merge_invalid";
876
855
  entry.detail = merged;
@@ -879,6 +858,10 @@ function plan(company, index, pairs, allPairs, now, rewriteRefs, resolutions = n
879
858
  entry.held = "external_id_conflict";
880
859
  entry.detail = identity.conflicts.join(", ");
881
860
  }
861
+ else if (serverOwned.length > 0) {
862
+ entry.held = "server_owned_refs";
863
+ entry.detail = [...new Set(serverOwned)].sort().join(", ");
864
+ }
882
865
  else if (!rewriteRefs && refs.length > 0) {
883
866
  entry.held = "signal_refs";
884
867
  }
@@ -960,7 +943,13 @@ export async function mergeCopies(options) {
960
943
  const { hqRoot, company, mode, now } = options;
961
944
  const index = buildEntityIndex(company.dir);
962
945
  const pairKind = options.pairKind ?? "numbered";
963
- const found = pairKind === "variants" ? findVariantPairs(index) : findCopyPairs(index);
946
+ if (pairKind === "listed" && !options.only)
947
+ throw new Error("merge-copies: listed pairs need an only list");
948
+ const found = pairKind === "listed"
949
+ ? findListedPairs(index, options.only)
950
+ : pairKind === "variants"
951
+ ? findVariantPairs(index)
952
+ : findCopyPairs(index);
964
953
  const listed = options.only
965
954
  ? found.pairs.filter((pair) => options.only.some((entry) => entry.copy === pair.copy && entry.original === pair.original))
966
955
  : found.pairs;
@@ -1060,28 +1049,6 @@ export async function mergeCopies(options) {
1060
1049
  report.errors.push({ path: rel, error: error.name });
1061
1050
  }
1062
1051
  }
1063
- if (!rewriteRefs)
1064
- continue;
1065
- const copyRef = entityRefOf(entry.copy);
1066
- const originalRef = entityRefOf(entry.original);
1067
- for (const rel of entry.signal_refs) {
1068
- try {
1069
- const content = read(company, rel);
1070
- const next = rewriteSignalRefs(content, copyRef, originalRef);
1071
- if (next === "signal_ref_format") {
1072
- report.errors.push({ path: rel, error: "signal_ref_format" });
1073
- continue;
1074
- }
1075
- if (next === content)
1076
- continue;
1077
- safeWriteFile(hqRoot, [company.dir], path.join(company.dir, rel), next);
1078
- if (!report.rewritten.includes(rel))
1079
- report.rewritten.push(rel);
1080
- }
1081
- catch (error) {
1082
- report.errors.push({ path: rel, error: error.name });
1083
- }
1084
- }
1085
1052
  }
1086
1053
  }
1087
1054
  if (mode === "delete") {
@@ -6,6 +6,8 @@
6
6
  import { type FlagClientOptions, type FlagResolution } from "@indigoai-us/hq-flags-client";
7
7
  /** Outposts hand running services to the next daemon on a CLI update instead of restarting them all. */
8
8
  export declare const SUPERVISOR_HANDOFF_FLAG = "daemon.supervisor-handoff";
9
+ /** Self-updates build the new CLI beside the running one and swap it in, so `hq` never disappears mid-update. */
10
+ export declare const STAGED_SELF_UPDATE_FLAG = "cli.staged-self-update";
9
11
  export interface DaemonFlagClient {
10
12
  ready(): Promise<void>;
11
13
  explain(flagKey: string, options?: {
@@ -9,6 +9,8 @@ import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
9
9
  import { resolveProcessFlagCompanyContext } from "../flag-registry.js";
10
10
  /** Outposts hand running services to the next daemon on a CLI update instead of restarting them all. */
11
11
  export const SUPERVISOR_HANDOFF_FLAG = "daemon.supervisor-handoff";
12
+ /** Self-updates build the new CLI beside the running one and swap it in, so `hq` never disappears mid-update. */
13
+ export const STAGED_SELF_UPDATE_FLAG = "cli.staged-self-update";
12
14
  const READ_TIMEOUT_MS = 5_000;
13
15
  export async function readDaemonFlag(flagKey, options = {}) {
14
16
  const env = options.env ?? process.env;
@@ -42,7 +42,7 @@ function shellLocationAliases(contents) {
42
42
  const scriptRoot = SCRIPT_ROOT_TOKENS.exec(value);
43
43
  if (scriptRoot) {
44
44
  const suffix = value.slice(scriptRoot.index + scriptRoot[0].length)
45
- .match(/^\/([^"'`\s;|&)]+)/)?.[1] ?? ".";
45
+ .match(/^\/([^"'`\s;|&)}]+)/)?.[1] ?? ".";
46
46
  aliases.set(name, { kind: "asset", baseDirectory: path.posix.normalize(suffix) });
47
47
  continue;
48
48
  }
@@ -116,15 +116,15 @@ export function scanScaffoldSiblingReferences(scaffoldRoot) {
116
116
  (/\b(?:source|exec|bash|sh|node)\b/.test(line) || /^\s*\.\s+/.test(line)));
117
117
  for (const line of commandLines) {
118
118
  SCRIPT_ROOT_TOKENS.lastIndex = 0;
119
- for (const match of line.matchAll(/(?:\$\{SCRIPT_DIR\}|\$SCRIPT_DIR)\/([^"'`\s;|&)]+)/g)) {
119
+ for (const match of line.matchAll(/(?:\$\{SCRIPT_DIR\}|\$SCRIPT_DIR)\/([^"'`\s;|&)}]+)/g)) {
120
120
  recordRelative(sourceFile, match[1]);
121
121
  }
122
- const dirnamePath = new RegExp(String.raw `\$\(\s*dirname\s+["']?${BASH_SOURCE_TOKEN}["']?\s*\)(\/[^"'\x60\s;|&)]+)`, "g");
122
+ const dirnamePath = new RegExp(String.raw `\$\(\s*dirname\s+["']?${BASH_SOURCE_TOKEN}["']?\s*\)(\/[^"'\x60\s;|&)}]+)`, "g");
123
123
  for (const match of line.matchAll(dirnamePath))
124
124
  recordRelative(sourceFile, match[1]);
125
125
  for (const [alias, location] of aliases) {
126
126
  const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
- const aliasPath = new RegExp(String.raw `(?:\$\{${escapedAlias}\}|\$${escapedAlias})\/([^"'\x60\s;|&)]+)`, "g");
127
+ const aliasPath = new RegExp(String.raw `(?:\$\{${escapedAlias}\}|\$${escapedAlias})\/([^"'\x60\s;|&)}]+)`, "g");
128
128
  for (const match of line.matchAll(aliasPath)) {
129
129
  if (location.kind === "hq-root") {
130
130
  const reference = match[0];
@@ -161,6 +161,12 @@ export interface SelfUpdateDeps {
161
161
  interactive?: boolean;
162
162
  /** Best-effort daemon kick after the CLI install converges (tests). */
163
163
  restartDaemon?: () => RestartDaemonResult | void;
164
+ /**
165
+ * Whether `cli.staged-self-update` is on: build the new version beside the
166
+ * running package and swap it in, instead of moving the running package
167
+ * aside for the whole install. Read only when an npm global install is due.
168
+ */
169
+ stagedSelfUpdate?: () => Promise<boolean>;
164
170
  }
165
171
  /**
166
172
  * Startup path: the running CLI is behind npm `latest`, so update in place and
@@ -64,6 +64,7 @@ import semver from "semver";
64
64
  import chalk from "chalk";
65
65
  import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
66
66
  import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, derivePnpmHome, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, isPrefixWritable, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, } from "./version-gate.js";
67
+ import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
67
68
  import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
68
69
  import { markLatestIneffective } from "./version-check.js";
69
70
  import { restartDaemonServiceIfInstalled } from "../lib/mesh/live/daemon/install.js";
@@ -414,12 +415,15 @@ async function updateAndReexec(argv, flavor, known, deps) {
414
415
  console.error(chalk.dim(`hq-cli ${latest} is available, but this copy is a local dependency (${install.packageRoot}) — update the project that owns it.`));
415
416
  return { action: "skipped", latest };
416
417
  }
418
+ const staged = canStageInstall(install) && (await readStagedSelfUpdateFlag(deps, env));
417
419
  const releaseLock = flavor.lock ? (deps.acquireLock ?? acquireUpdateLock)() : () => { };
418
420
  if (!releaseLock)
419
421
  return { action: "skipped", latest };
420
422
  const renamePath = deps.renamePath ?? fs.renameSync;
421
- const installSnapshot = snapshotInstall(install, renamePath);
422
- if (!installSnapshot) {
423
+ // The staged install keeps the running package in place until the swap and
424
+ // restores it itself, so it takes no snapshot.
425
+ const installSnapshot = staged ? null : snapshotInstall(install, renamePath);
426
+ if (!staged && !installSnapshot) {
423
427
  console.error(chalk.dim(" No backup could be made for this hq-cli install; proceeding without rollback protection."));
424
428
  }
425
429
  let result;
@@ -440,10 +444,15 @@ async function updateAndReexec(argv, flavor, known, deps) {
440
444
  // Forward env only when PNPM_HOME had to be injected, so the common path
441
445
  // spawns with the inherited environment and keeps the two-argument runner
442
446
  // call it has always made.
443
- result =
444
- updateEnv === undefined
445
- ? runner(plan.cmd, plan.args)
446
- : runner(plan.cmd, plan.args, updateEnv);
447
+ if (staged) {
448
+ result = stagedNpmInstall(install, latestValid, { run: runner, env, rename: renamePath });
449
+ }
450
+ else if (updateEnv === undefined) {
451
+ result = runner(plan.cmd, plan.args);
452
+ }
453
+ else {
454
+ result = runner(plan.cmd, plan.args, updateEnv);
455
+ }
447
456
  if (!result.ok && installSnapshot) {
448
457
  restoredAfterFailure = restoreInstall(installSnapshot, renamePath);
449
458
  if (restoredAfterFailure)
@@ -537,6 +546,19 @@ async function updateAndReexec(argv, flavor, known, deps) {
537
546
  console.error(chalk.dim(`✓ hq-cli updated to ${latest}.`));
538
547
  return { action: "reexec", reexecStatus: status, latest };
539
548
  }
549
+ async function readStagedSelfUpdateFlag(deps, env) {
550
+ try {
551
+ if (deps.stagedSelfUpdate)
552
+ return await deps.stagedSelfUpdate();
553
+ // Loaded here so the startup path pays for the flag client only when an
554
+ // update is actually due.
555
+ const { readDaemonFlag, STAGED_SELF_UPDATE_FLAG } = await import("../lib/daemon/flag.js");
556
+ return await readDaemonFlag(STAGED_SELF_UPDATE_FLAG, { env });
557
+ }
558
+ catch {
559
+ return false;
560
+ }
561
+ }
540
562
  /**
541
563
  * Startup path: the running CLI is behind npm `latest`, so update in place and
542
564
  * re-run the user's command on the new version. `latest` is normally the cached
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Staged npm self-update: install the new hq-cli beside the running package,
3
+ * check that it starts, then swap it in with two renames.
4
+ *
5
+ * The in-place update moves the live package aside for the whole
6
+ * `npm install -g`, so anything that starts `hq` during the install finds no
7
+ * CLI on disk ("hq: command not found", MODULE_NOT_FOUND; outposts primary and
8
+ * 2, 5.290.0 -> 5.291.0). Here the live package stays in place until the new
9
+ * one is complete and has answered `--version`.
10
+ *
11
+ * Only a plain npm global layout is staged: `<prefix>/lib/node_modules/<pkg>`
12
+ * as a real directory, with bin links in `<prefix>/bin`. pnpm, Bun, Windows
13
+ * and linked checkouts keep the in-place update.
14
+ */
15
+ import * as fs from "node:fs";
16
+ import { type RunningInstall, type UpdateResult } from "./version-gate.js";
17
+ /** Set for the staging install; hq-cli's own postinstall skips itself on it. */
18
+ export declare const STAGED_INSTALL_ENV = "HQ_STAGED_INSTALL";
19
+ export interface StagedInstallDeps {
20
+ /** Runs the package manager. Defaults to {@link runUpdateCommand}. */
21
+ run?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
22
+ env?: NodeJS.ProcessEnv;
23
+ /** The version the staged package's `hq --version` reports, or null. */
24
+ probeVersion?: (packageRoot: string) => string | null;
25
+ /** The package's postinstall, run from the live path after the swap. */
26
+ runPostinstall?: (packageRoot: string) => void;
27
+ rename?: typeof fs.renameSync;
28
+ /** Removes the staging and backup directories. */
29
+ remove?: (target: string) => void;
30
+ platform?: NodeJS.Platform;
31
+ }
32
+ export declare function canStageInstall(install: RunningInstall, platform?: NodeJS.Platform): boolean;
33
+ export declare function stagedNpmInstall(install: RunningInstall, version: string, deps?: StagedInstallDeps): UpdateResult;
34
+ //# sourceMappingURL=staged-install.d.ts.map