@rehearsal-db/core 0.1.0-rc.7 → 0.1.0-rc.9

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
@@ -3,6 +3,40 @@
3
3
  All notable Rehearsal package changes will be documented here. The format follows Keep
4
4
  a Changelog, and versions will follow Semantic Versioning after the package exists.
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0-rc.9] - 2026-10-17
9
+
10
+ ### Fixed
11
+
12
+ - Wrong or stale source-access approval and review-required privacy drafts report
13
+ actionable configuration refusals instead of internal failures. Source access,
14
+ privacy review and baseline-preservation requirements are unchanged.
15
+
16
+ ## [0.1.0-rc.8] - 2026-10-17
17
+
18
+ ### Added
19
+
20
+ - Optional interactive update reminders, with explicit network consent, daily
21
+ opening-session checks, defer/skip/disable choices and exact-version guidance.
22
+ Machine commands remain network-free, custom installations stay manual-only,
23
+ and no update installs automatically.
24
+
25
+ ### Fixed
26
+
27
+ - Invalid positive-integer configuration settings and rejected configuration proposals
28
+ report configuration failures rather than baseline or internal failures. Review
29
+ remains non-writing, and unexpected faults retain their original failure category.
30
+
31
+ ### Documentation
32
+
33
+ - Separate released VSIX installation, dashboard access, and updates from contributor
34
+ setup and separately approved commercial launch requirements.
35
+ - Clarify that configured database and Storage data remain persistent and that no
36
+ separate temporary Docker-helper ownership or cleanup capability exists today.
37
+ - Explain profile startup and resource measurements without equating cached-image
38
+ container checks with a cold VM or whole-machine memory benchmark.
39
+
6
40
  ## [0.1.0-rc.7] - 2026-10-15
7
41
 
8
42
  ### Fixed
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "0.1.0-rc.7",
3
+ "version": "0.1.0-rc.9",
4
4
  "private": false,
5
5
  "description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
6
6
  "repository": {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Validate and normalize the complete reviewed privacy policy version 2.
3
3
  */
4
+ import { RehearsalError } from "../shared/diagnostics.mjs";
4
5
  import { validatePrivacyDependencyGraph } from "./privacy_dependency_graph.mjs";
5
6
  import { validatePrivacyRecipe } from "./privacy_recipe_validation.mjs";
6
7
  import { ACTIONS, BINDING_NAME, DEFAULT_PATH_MAXIMUM_BYTES, DEFAULT_PATH_MAXIMUM_SEGMENTS, ENVIRONMENT_KEY, HEX_64, assertKnownKeys as knownKeys, isPrivacyObject as isObject, validateIdentifier as identifier, validateNonEmpty as nonEmpty, validatePositiveInteger as positiveInteger, } from "./privacy_validation_shared.mjs";
@@ -19,7 +20,18 @@ export const validateExecutablePrivacyPolicy = (policy) => {
19
20
  throw new Error("Executable privacy policies must use policyVersion 2.");
20
21
  }
21
22
  if (policy.draft === true) {
22
- throw new Error("The privacy policy is still a REVIEW REQUIRED draft.");
23
+ throw new RehearsalError({
24
+ category: "configuration_invalid",
25
+ code: "CONFIGURATION_INVALID",
26
+ message: "The privacy policy is still a REVIEW REQUIRED draft.",
27
+ expected: "A complete, explicitly reviewed and approved privacy policy.",
28
+ actual: "REVIEW REQUIRED draft.",
29
+ refused: "This draft policy was not accepted for source export or baseline activation.",
30
+ suggestions: [
31
+ "Review every exported table, column, recipe and source-data boundary, and obtain explicit privacy approval before completing this draft.",
32
+ "Do not remove the draft marker merely to bypass review. After approval, rerun rehearsal baseline refresh.",
33
+ ],
34
+ });
23
35
  }
24
36
  if (typeof policy.migrationCutoff !== "string" ||
25
37
  !/^\d{14}$/u.test(policy.migrationCutoff)) {
@@ -4,6 +4,7 @@
4
4
  import { access, readFile } from "node:fs/promises";
5
5
  import { join, relative } from "node:path";
6
6
  import * as prompts from "@clack/prompts";
7
+ import { createGuidedUpdates } from "./guided_updates.mjs";
7
8
  import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, } from "../project/configuration.mjs";
8
9
  import { validateRuntimeSanitizationPolicy } from "../baseline/sanitization_policy.mjs";
9
10
  import { inspectRehearsalNodeRuntime, planRehearsalSetup, summarizeRehearsalSetup, } from "../project/setup.mjs";
@@ -280,7 +281,7 @@ const reviewPolicyInteractively = async ({ state, flags, }) => {
280
281
  }
281
282
  return true;
282
283
  };
283
- const guidedOptions = (state, hasLastDetails) => {
284
+ const guidedOptions = (state, hasLastDetails, showUpdates) => {
284
285
  const options = [];
285
286
  if (!state.node.supported) {
286
287
  options.push({
@@ -370,7 +371,9 @@ const guidedOptions = (state, hasLastDetails) => {
370
371
  label: "Get help",
371
372
  hint: "Create a safe, copy-ready support report",
372
373
  command: "support",
373
- }, { label: "Show all commands", command: "help" }, { label: "Exit", command: "exit" });
374
+ }, { label: "Show all commands", command: "help" }, ...(showUpdates
375
+ ? [{ label: "Check for updates", command: "check-updates" }]
376
+ : []), { label: "Exit", command: "exit" });
374
377
  return options;
375
378
  };
376
379
  const showGuidedStatus = ({ state, options, flags, }) => {
@@ -575,153 +578,165 @@ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot,
575
578
  return "home";
576
579
  }
577
580
  };
578
- export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }) => async ({ flags, planOptions, }) => {
579
- const state = await inspectGuidedProject({
580
- projectRoot,
581
- planOptions,
582
- topologyCandidateSummary,
583
- });
584
- const lastDetails = getLastGuidedDetails();
585
- const options = guidedOptions(state, Boolean(lastDetails));
586
- showGuidedStatus({ state, options, flags });
587
- const selected = await promptForChoice({
588
- message: "What would you like to do?",
589
- options,
590
- flags,
591
- optionsAlreadyShown: !useStyledPrompts(flags),
592
- });
593
- if (!selected || selected.command === "exit")
594
- return null;
595
- if (selected.command === "node-help") {
596
- console.log([
597
- "",
598
- terminalStyle(flags, "1", "SUPPORTED NODE.JS REQUIRED"),
599
- `This shell is using Node.js ${state.node.version}. Rehearsal has not changed your project.`,
600
- `Supported releases: ${REHEARSAL_NODE_LABEL}.`,
601
- "",
602
- "Recommended with nvm:",
603
- " nvm install --lts",
604
- " nvm use --lts",
605
- "",
606
- "Then reinstall Rehearsal in your scratch project and run npx rehearsal again.",
607
- ].join("\n"));
608
- return "home";
609
- }
610
- if (selected.command === "policy-review") {
611
- try {
612
- await reviewPolicyInteractively({ state, flags });
581
+ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }) => {
582
+ const updates = createGuidedUpdates({ projectRoot });
583
+ return async ({ flags, planOptions, }) => {
584
+ if (await updates(flags))
585
+ return null;
586
+ const state = await inspectGuidedProject({
587
+ projectRoot,
588
+ planOptions,
589
+ topologyCandidateSummary,
590
+ });
591
+ const lastDetails = getLastGuidedDetails();
592
+ const options = guidedOptions(state, Boolean(lastDetails), !flags.json &&
593
+ Boolean(process.stdin.isTTY && process.stdout.isTTY) &&
594
+ !process.env.CI &&
595
+ !process.env.CODEX_CI &&
596
+ process.env.REHEARSAL_UPDATE_CHECK !== "off");
597
+ showGuidedStatus({ state, options, flags });
598
+ const selected = await promptForChoice({
599
+ message: "What would you like to do?",
600
+ options,
601
+ flags,
602
+ optionsAlreadyShown: !useStyledPrompts(flags),
603
+ });
604
+ if (!selected || selected.command === "exit")
605
+ return null;
606
+ if (selected.command === "check-updates") {
607
+ return (await updates(flags, true)) ? null : "home";
608
+ }
609
+ if (selected.command === "node-help") {
610
+ console.log([
611
+ "",
612
+ terminalStyle(flags, "1", "SUPPORTED NODE.JS REQUIRED"),
613
+ `This shell is using Node.js ${state.node.version}. Rehearsal has not changed your project.`,
614
+ `Supported releases: ${REHEARSAL_NODE_LABEL}.`,
615
+ "",
616
+ "Recommended with nvm:",
617
+ " nvm install --lts",
618
+ " nvm use --lts",
619
+ "",
620
+ "Then reinstall Rehearsal in your scratch project and run npx rehearsal again.",
621
+ ].join("\n"));
622
+ return "home";
613
623
  }
614
- catch (error) {
615
- if (errorMessage(error) !== "Policy review was cancelled.") {
616
- throw error;
624
+ if (selected.command === "policy-review") {
625
+ try {
626
+ await reviewPolicyInteractively({ state, flags });
617
627
  }
628
+ catch (error) {
629
+ if (errorMessage(error) !== "Policy review was cancelled.") {
630
+ throw error;
631
+ }
632
+ if (useStyledPrompts(flags)) {
633
+ prompts.cancel("Policy review cancelled; the draft was not changed.");
634
+ }
635
+ else {
636
+ console.log("Policy review cancelled; the draft was not changed.");
637
+ }
638
+ }
639
+ return "home";
640
+ }
641
+ if (selected.command === "last-details") {
618
642
  if (useStyledPrompts(flags)) {
619
- prompts.cancel("Policy review cancelled; the draft was not changed.");
643
+ prompts.note(lastDetails, "Technical details");
620
644
  }
621
645
  else {
622
- console.log("Policy review cancelled; the draft was not changed.");
646
+ console.log(`\nTECHNICAL DETAILS\n${lastDetails}`);
623
647
  }
648
+ return "home";
624
649
  }
625
- return "home";
626
- }
627
- if (selected.command === "last-details") {
628
- if (useStyledPrompts(flags)) {
629
- prompts.note(lastDetails, "Technical details");
650
+ if (selected.command === "refresh-guide") {
651
+ const { plan } = await buildRefreshWorkflowPlan(planOptions);
652
+ console.log(`\n${renderRefreshWorkflow({ mode: "preview", plan })}\n`);
653
+ const accepted = await promptForConfirmation("Create this replacement, discard current runtime edits, and remove exactly the listed old copies after success?", flags);
654
+ if (!accepted)
655
+ return "home";
656
+ flags.refreshConfirmation = plan.digest;
657
+ return "refresh";
630
658
  }
631
- else {
632
- console.log(`\nTECHNICAL DETAILS\n${lastDetails}`);
659
+ if (selected.command === "manage-runtime") {
660
+ const runtimeAction = await promptForChoice({
661
+ message: "Manage the disposable local runtime",
662
+ flags,
663
+ options: [
664
+ { label: "Show status", command: "status" },
665
+ { label: "Verify runtime", command: "verify" },
666
+ { label: "Reset to the immutable baseline", command: "reset" },
667
+ { label: "Stop runtime", command: "stop" },
668
+ { label: "Discard runtime", command: "discard" },
669
+ { label: "Back", command: "exit" },
670
+ ],
671
+ });
672
+ if (!runtimeAction || runtimeAction.command === "exit")
673
+ return "home";
674
+ if (["reset", "discard"].includes(runtimeAction.command)) {
675
+ const accepted = await promptForConfirmation(runtimeAction.command === "reset"
676
+ ? "Reset the disposable runtime and remove its current changes?"
677
+ : "Discard the disposable runtime? The immutable baseline will remain.", flags);
678
+ if (!accepted)
679
+ return "home";
680
+ }
681
+ return runtimeAction.command;
633
682
  }
634
- return "home";
635
- }
636
- if (selected.command === "refresh-guide") {
637
- const { plan } = await buildRefreshWorkflowPlan(planOptions);
638
- console.log(`\n${renderRefreshWorkflow({ mode: "preview", plan })}\n`);
639
- const accepted = await promptForConfirmation("Create this replacement, discard current runtime edits, and remove exactly the listed old copies after success?", flags);
640
- if (!accepted)
641
- return "home";
642
- flags.refreshConfirmation = plan.digest;
643
- return "refresh";
644
- }
645
- if (selected.command === "manage-runtime") {
646
- const runtimeAction = await promptForChoice({
647
- message: "Manage the disposable local runtime",
683
+ if (selected.command === "cleanup-guide") {
684
+ return guideCleanup({
685
+ flags,
686
+ planOptions,
687
+ planRuntimeStackCleanup,
688
+ session,
689
+ });
690
+ }
691
+ if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
692
+ return guideBaseline({
693
+ state,
694
+ selected,
695
+ flags,
696
+ planOptions,
697
+ projectRoot,
698
+ session,
699
+ });
700
+ }
701
+ if (selected.command !== "setup-write")
702
+ return selected.command;
703
+ const targetChoice = await promptForChoice({
704
+ message: "Which database should Rehearsal use?",
648
705
  flags,
649
- options: [
650
- { label: "Show status", command: "status" },
651
- { label: "Verify runtime", command: "verify" },
652
- { label: "Reset to the immutable baseline", command: "reset" },
653
- { label: "Stop runtime", command: "stop" },
654
- { label: "Discard runtime", command: "discard" },
655
- { label: "Back", command: "exit" },
656
- ],
706
+ options: state.target === "supabase"
707
+ ? [
708
+ { label: "Supabase", command: "supabase" },
709
+ { label: "PostgreSQL", command: "postgresql" },
710
+ ]
711
+ : [
712
+ { label: "PostgreSQL", command: "postgresql" },
713
+ { label: "Supabase", command: "supabase" },
714
+ ],
657
715
  });
658
- if (!runtimeAction || runtimeAction.command === "exit")
716
+ if (!targetChoice)
659
717
  return "home";
660
- if (["reset", "discard"].includes(runtimeAction.command)) {
661
- const accepted = await promptForConfirmation(runtimeAction.command === "reset"
662
- ? "Reset the disposable runtime and remove its current changes?"
663
- : "Discard the disposable runtime? The immutable baseline will remain.", flags);
664
- if (!accepted)
665
- return "home";
718
+ if (targetChoice.command !== "supabase" &&
719
+ targetChoice.command !== "postgresql") {
720
+ throw new Error("Unsupported guided runtime target.");
666
721
  }
667
- return runtimeAction.command;
668
- }
669
- if (selected.command === "cleanup-guide") {
670
- return guideCleanup({
671
- flags,
672
- planOptions,
673
- planRuntimeStackCleanup,
674
- session,
675
- });
676
- }
677
- if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
678
- return guideBaseline({
679
- state,
680
- selected,
681
- flags,
682
- planOptions,
722
+ const target = targetChoice.command;
723
+ flags.target = target;
724
+ const setupPlan = await planRehearsalSetup({
683
725
  projectRoot,
684
- session,
726
+ target,
685
727
  });
686
- }
687
- if (selected.command !== "setup-write")
688
- return selected.command;
689
- const targetChoice = await promptForChoice({
690
- message: "Which database should Rehearsal use?",
691
- flags,
692
- options: state.target === "supabase"
693
- ? [
694
- { label: "Supabase", command: "supabase" },
695
- { label: "PostgreSQL", command: "postgresql" },
696
- ]
697
- : [
698
- { label: "PostgreSQL", command: "postgresql" },
699
- { label: "Supabase", command: "supabase" },
700
- ],
701
- });
702
- if (!targetChoice)
703
- return "home";
704
- if (targetChoice.command !== "supabase" &&
705
- targetChoice.command !== "postgresql") {
706
- throw new Error("Unsupported guided runtime target.");
707
- }
708
- const target = targetChoice.command;
709
- flags.target = target;
710
- const setupPlan = await planRehearsalSetup({
711
- projectRoot,
712
- target,
713
- });
714
- const preview = {
715
- ...summarizeRehearsalSetup(setupPlan, { mode: "preview" }),
716
- nextAction: "Review this plan. No files are written unless you approve below.",
728
+ const preview = {
729
+ ...summarizeRehearsalSetup(setupPlan, { mode: "preview" }),
730
+ nextAction: "Review this plan. No files are written unless you approve below.",
731
+ };
732
+ console.log(`\n${renderSetup(preview)}\n`);
733
+ const accepted = await promptForConfirmation("Create these project-local files?", flags);
734
+ if (!accepted) {
735
+ console.log("No changes made.");
736
+ return "home";
737
+ }
738
+ session.setupPlan = setupPlan;
739
+ flags.write = true;
740
+ return "setup";
717
741
  };
718
- console.log(`\n${renderSetup(preview)}\n`);
719
- const accepted = await promptForConfirmation("Create these project-local files?", flags);
720
- if (!accepted) {
721
- console.log("No changes made.");
722
- return "home";
723
- }
724
- session.setupPlan = setupPlan;
725
- flags.write = true;
726
- return "setup";
727
742
  };
@@ -0,0 +1,14 @@
1
+ /** Consent and reminders belong to the interactive guide, never machine commands. */
2
+ import type { TerminalFlags } from "./terminal.mjs";
3
+ import { promptForChoice } from "./terminal.mjs";
4
+ export declare function createGuidedUpdates({ projectRoot, installed, directory: configuredDirectory, fetchImplementation, now, environment, terminal, choose, write, }: {
5
+ projectRoot: string;
6
+ installed?: string;
7
+ directory?: string;
8
+ fetchImplementation?: typeof fetch;
9
+ now?: () => number;
10
+ environment?: NodeJS.ProcessEnv;
11
+ terminal?: () => boolean;
12
+ choose?: typeof promptForChoice;
13
+ write?: (text: string) => void;
14
+ }): (flags: TerminalFlags, manual?: boolean) => Promise<boolean>;
@@ -0,0 +1,148 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { promptForChoice } from "./terminal.mjs";
3
+ import { REHEARSAL_VERSION } from "../shared/diagnostics.mjs";
4
+ import { UPDATE_DAY, checkCoreUpdate, coreUpdateCommand, defaultUpdateDirectory, readUpdatePreference, saveUpdatePreference, } from "./update_advisory.mjs";
5
+ export function createGuidedUpdates({ projectRoot, installed = REHEARSAL_VERSION, directory: configuredDirectory, fetchImplementation = fetch, now = Date.now, environment = process.env, terminal = () => Boolean(process.stdin.isTTY && process.stdout.isTTY), choose = promptForChoice, write = (text) => console.log(text), }) {
6
+ let opened = false;
7
+ const directory = configuredDirectory ??
8
+ environment.REHEARSAL_UPDATE_STATE_DIR ??
9
+ defaultUpdateDirectory();
10
+ const select = async (input) => {
11
+ try {
12
+ return await choose(input);
13
+ }
14
+ catch (error) {
15
+ // Node's plain readline prompt rejects on a terminal Ctrl+C. Treat only
16
+ // that observed cancellation as an exit; unexpected prompt faults remain errors.
17
+ if (error instanceof Error &&
18
+ error.name === "AbortError" &&
19
+ "code" in error &&
20
+ error.code === "ABORT_ERR" &&
21
+ error.message === "Aborted with Ctrl+C")
22
+ return undefined;
23
+ throw error;
24
+ }
25
+ };
26
+ return async (flags, manual = false) => {
27
+ if (flags.json || !terminal() || environment.CI || environment.CODEX_CI) {
28
+ return false;
29
+ }
30
+ if (environment.REHEARSAL_UPDATE_CHECK === "off") {
31
+ if (manual)
32
+ write("Update checks are disabled in this session. No network request was made.");
33
+ return false;
34
+ }
35
+ if (!manual && opened)
36
+ return false;
37
+ opened = true;
38
+ if (!isAbsolute(directory) ||
39
+ directory.length > 4096 ||
40
+ /[\x00-\x1f\x7f]/u.test(directory)) {
41
+ write("Update preference location is invalid. No update check was made; local work can continue.");
42
+ return false;
43
+ }
44
+ const time = now();
45
+ const preference = await readUpdatePreference(directory, time);
46
+ let expectedPreference = { ...preference };
47
+ let explicitConsent = false;
48
+ const persist = async () => {
49
+ const saved = await saveUpdatePreference(directory, preference, expectedPreference);
50
+ if (!saved)
51
+ write("Update preferences changed or could not be saved. Automatic checks stop here; use Check for updates for an explicit check.");
52
+ else
53
+ expectedPreference = { ...preference };
54
+ return saved;
55
+ };
56
+ if (!manual &&
57
+ (preference.permission === "disabled" ||
58
+ (preference.lastCheckAt !== null &&
59
+ time - preference.lastCheckAt < UPDATE_DAY) ||
60
+ (preference.lastPromptAt !== null &&
61
+ time - preference.lastPromptAt < UPDATE_DAY)))
62
+ return false;
63
+ if (manual || preference.permission !== "enabled") {
64
+ const consent = await select({
65
+ message: "Check npm for Rehearsal updates?",
66
+ flags,
67
+ options: [
68
+ { label: "Not now", command: "later" },
69
+ {
70
+ label: "Check once",
71
+ command: "once",
72
+ hint: "Contacts registry.npmjs.org; no project data or credentials are sent",
73
+ },
74
+ {
75
+ label: "Check and remind me daily",
76
+ command: "enable",
77
+ hint: "Only when opening this interactive guide",
78
+ },
79
+ { label: "Disable reminders", command: "disable" },
80
+ ],
81
+ });
82
+ preference.lastPromptAt = time;
83
+ if (!consent)
84
+ return true;
85
+ if (consent.command === "later") {
86
+ await persist();
87
+ return false;
88
+ }
89
+ if (consent.command === "disable") {
90
+ preference.permission = "disabled";
91
+ await persist();
92
+ return false;
93
+ }
94
+ if (!["once", "enable"].includes(consent.command ?? ""))
95
+ return false;
96
+ explicitConsent = true;
97
+ if (consent.command === "enable")
98
+ preference.permission = "enabled";
99
+ }
100
+ preference.lastCheckAt = time;
101
+ if (!(await persist()) && !explicitConsent)
102
+ return false;
103
+ write("Checking the published Rehearsal release…");
104
+ const release = await checkCoreUpdate({ installed, fetchImplementation });
105
+ if (release.state === "unavailable") {
106
+ write("Update information is unavailable. Continue normally; Check for updates can retry later.");
107
+ return false;
108
+ }
109
+ if (release.state !== "available") {
110
+ if (manual)
111
+ write(`Installed core ${installed}; published ${release.channel} release ${release.version}. ${release.state === "ahead" ? "Your installation is newer; it will not be downgraded." : "No newer release is available on this channel."}`);
112
+ return false;
113
+ }
114
+ if (!manual && preference.skippedVersion === release.version)
115
+ return false;
116
+ const command = await coreUpdateCommand(projectRoot, installed, release.version);
117
+ if (!command) {
118
+ write(`Published ${release.channel} release: ${release.version}. This update requires manual compatibility or installation-origin review; preserve reviewed artifacts and follow your project's update instructions.`);
119
+ return false;
120
+ }
121
+ const decision = await select({
122
+ message: `Core update available: ${installed} → ${release.version} (${release.channel})`,
123
+ flags,
124
+ options: [
125
+ { label: "Not now", command: "later" },
126
+ {
127
+ label: "Show update command and exit",
128
+ command: "update",
129
+ hint: "Review compatibility; nothing installs automatically",
130
+ },
131
+ { label: "Skip this release", command: "skip" },
132
+ { label: "Disable reminders", command: "disable" },
133
+ ],
134
+ });
135
+ if (!decision)
136
+ return true;
137
+ preference.lastPromptAt = time;
138
+ if (decision?.command === "skip")
139
+ preference.skippedVersion = release.version;
140
+ if (decision?.command === "disable")
141
+ preference.permission = "disabled";
142
+ await persist();
143
+ if (decision?.command !== "update")
144
+ return false;
145
+ write(`\nCore ${installed} → ${release.version} (${release.channel})\nReview release compatibility: https://github.com/Ddupasquier/rehearsal-db/releases/tag/v${release.version}\nAfter this guide exits, run:\n ${command}\nThis pins the reviewed version and changes the project manifest, lockfile and dependencies. Rehearsal has not installed anything. Review the dependency diff afterward.\n`);
146
+ return true;
147
+ };
148
+ }
@@ -0,0 +1,29 @@
1
+ export declare const UPDATE_DAY = 86400000;
2
+ type Channel = "stable" | "preview";
3
+ export interface UpdatePreference {
4
+ schemaVersion: 1;
5
+ permission: "ask" | "enabled" | "disabled";
6
+ lastCheckAt: number | null;
7
+ lastPromptAt: number | null;
8
+ skippedVersion: string | null;
9
+ }
10
+ export declare const emptyUpdatePreference: () => UpdatePreference;
11
+ export declare function compareUpdateVersions(left: string, right: string): number | null;
12
+ export type CoreUpdate = {
13
+ state: "available" | "current" | "ahead";
14
+ version: string;
15
+ channel: Channel;
16
+ } | {
17
+ state: "unavailable";
18
+ channel: Channel;
19
+ };
20
+ export declare function checkCoreUpdate({ installed, fetchImplementation, timeoutMs, }: {
21
+ installed: string;
22
+ fetchImplementation?: typeof fetch;
23
+ timeoutMs?: number;
24
+ }): Promise<CoreUpdate>;
25
+ export declare const defaultUpdateDirectory: () => string;
26
+ export declare function readUpdatePreference(directory: string, now: number): Promise<UpdatePreference>;
27
+ export declare function saveUpdatePreference(directory: string, value: UpdatePreference, expected?: UpdatePreference): Promise<boolean>;
28
+ export declare function coreUpdateCommand(project: string, installed: string, available: string): Promise<string | null>;
29
+ export {};