openplanr 1.22.0 → 1.24.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 +1423 -0
  2. package/dist/cli/commands/config.d.ts.map +1 -1
  3. package/dist/cli/commands/config.js +52 -0
  4. package/dist/cli/commands/config.js.map +1 -1
  5. package/dist/cli/commands/setup.d.ts.map +1 -1
  6. package/dist/cli/commands/setup.js +30 -0
  7. package/dist/cli/commands/setup.js.map +1 -1
  8. package/dist/cli/commands/upgrade.d.ts +9 -0
  9. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  10. package/dist/cli/commands/upgrade.js +149 -0
  11. package/dist/cli/commands/upgrade.js.map +1 -0
  12. package/dist/cli/index.js +28 -2
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/models/schema.d.ts +12 -0
  15. package/dist/models/schema.d.ts.map +1 -1
  16. package/dist/models/schema.js +9 -0
  17. package/dist/models/schema.js.map +1 -1
  18. package/dist/models/types.d.ts +8 -0
  19. package/dist/models/types.d.ts.map +1 -1
  20. package/dist/services/claude-plugin-service.d.ts +9 -0
  21. package/dist/services/claude-plugin-service.d.ts.map +1 -1
  22. package/dist/services/claude-plugin-service.js +22 -0
  23. package/dist/services/claude-plugin-service.js.map +1 -1
  24. package/dist/services/migration-registry.d.ts +94 -0
  25. package/dist/services/migration-registry.d.ts.map +1 -0
  26. package/dist/services/migration-registry.js +106 -0
  27. package/dist/services/migration-registry.js.map +1 -0
  28. package/dist/services/runtime-manager-service.d.ts +32 -0
  29. package/dist/services/runtime-manager-service.d.ts.map +1 -1
  30. package/dist/services/runtime-manager-service.js +122 -17
  31. package/dist/services/runtime-manager-service.js.map +1 -1
  32. package/dist/services/upgrade-offer-service.d.ts +89 -0
  33. package/dist/services/upgrade-offer-service.d.ts.map +1 -0
  34. package/dist/services/upgrade-offer-service.js +256 -0
  35. package/dist/services/upgrade-offer-service.js.map +1 -0
  36. package/dist/services/upgrade-service.d.ts +156 -0
  37. package/dist/services/upgrade-service.d.ts.map +1 -0
  38. package/dist/services/upgrade-service.js +510 -0
  39. package/dist/services/upgrade-service.js.map +1 -0
  40. package/package.json +4 -3
@@ -0,0 +1,256 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { display, logger } from '../utils/logger.js';
5
+ import { saveConfig } from './config-service.js';
6
+ import { isNonInteractive } from './interactive-state.js';
7
+ import { promptSelect } from './prompt-service.js';
8
+ import { runtimeRoot } from './runtime-manager-service.js';
9
+ import { executeCliHalfUpgrade, planCliUpgrade, reconcileInstalledTuple, } from './upgrade-service.js';
10
+ const DEFAULT_SNOOZE_STATE = {
11
+ neverAsk: false,
12
+ snoozeUntil: null,
13
+ snoozeStage: 0,
14
+ };
15
+ /**
16
+ * FR5 escalating backoff, keyed by the stage the user is snoozing *at*. The
17
+ * first "not now" (stage 0) waits a day; the next (stage 1) two days; every one
18
+ * after that (stage 2) a full week — so a prompt that was dismissed never
19
+ * reappears frequently enough to train a reflexive dismissal.
20
+ */
21
+ const BACKOFF_MS = {
22
+ 0: 24 * 60 * 60 * 1000,
23
+ 1: 48 * 60 * 60 * 1000,
24
+ 2: 7 * 24 * 60 * 60 * 1000,
25
+ };
26
+ /**
27
+ * FR6's "exact re-enable command" — stated verbatim both when the user chooses
28
+ * "never ask again" in the offer and when they set it via
29
+ * `planr config set-upgrade-policy --never-ask`, so a permanent opt-out is never
30
+ * a trap the user cannot find how to undo.
31
+ */
32
+ export const UPGRADE_REENABLE_COMMAND = 'planr config set-upgrade-policy --ask-again';
33
+ /** Where the machine-local snooze/never-ask state lives (reuses T-002's root). */
34
+ export function upgradeStatePath() {
35
+ return path.join(runtimeRoot(), 'upgrade-state.json');
36
+ }
37
+ /**
38
+ * Read the snooze state, failing open on *any* problem. A missing file is the
39
+ * common first-run case; a corrupt file is the crash-mid-write case the task's
40
+ * atomicity note calls out — both resolve to "no snooze recorded" so a bad local
41
+ * preference file can never throw out of every subsequent `preAction` hook.
42
+ */
43
+ export function readSnoozeState() {
44
+ const statePath = upgradeStatePath();
45
+ if (!existsSync(statePath))
46
+ return { ...DEFAULT_SNOOZE_STATE };
47
+ try {
48
+ const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
49
+ const stage = parsed.snoozeStage;
50
+ return {
51
+ neverAsk: parsed.neverAsk === true,
52
+ snoozeUntil: typeof parsed.snoozeUntil === 'string' ? parsed.snoozeUntil : null,
53
+ snoozeStage: stage === 1 || stage === 2 ? stage : 0,
54
+ };
55
+ }
56
+ catch {
57
+ return { ...DEFAULT_SNOOZE_STATE };
58
+ }
59
+ }
60
+ /** Persist the snooze state. Proportionate to a non-critical local file: a plain write. */
61
+ export async function writeSnoozeState(state) {
62
+ const statePath = upgradeStatePath();
63
+ await mkdir(path.dirname(statePath), { recursive: true });
64
+ await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
65
+ }
66
+ /**
67
+ * A test seam of the same shape as T-002's `OPENPLANR_ECOSYSTEM_SOURCE` and
68
+ * T-003's `OPENPLANR_NPM_BIN`: unset in production, it lets a hermetic
69
+ * subprocess integration test drive the offer path — which has no TTY — with a
70
+ * pre-selected choice instead of a live prompt.
71
+ */
72
+ export function injectedOfferChoice() {
73
+ const raw = process.env.OPENPLANR_UPGRADE_OFFER_CHOICE?.trim();
74
+ if (raw === 'upgrade-now' || raw === 'always' || raw === 'not-now' || raw === 'never')
75
+ return raw;
76
+ return null;
77
+ }
78
+ /**
79
+ * Whether the offer can be surfaced from `preAction`. In production this reduces
80
+ * exactly to `!isNonInteractive()` — the offer is shown only where a human can
81
+ * answer it. The injected-choice seam additionally makes the path reachable from
82
+ * a TTY-less test subprocess.
83
+ */
84
+ export function upgradeOfferReachable() {
85
+ return !isNonInteractive() || injectedOfferChoice() !== null;
86
+ }
87
+ /** Optional injected clock (ms since epoch), for a deterministic subprocess test. */
88
+ function injectedNow() {
89
+ const raw = process.env.OPENPLANR_UPGRADE_NOW?.trim();
90
+ if (!raw)
91
+ return undefined;
92
+ const value = Number(raw);
93
+ return Number.isFinite(value) ? value : undefined;
94
+ }
95
+ const OFFER_CHOICES = [
96
+ { name: '[u] Upgrade now', value: 'upgrade-now' },
97
+ { name: '[a] Always keep me current (upgrade automatically from now on)', value: 'always' },
98
+ { name: '[n] Not now (ask again later)', value: 'not-now' },
99
+ { name: '[x] Never ask again', value: 'never' },
100
+ ];
101
+ /**
102
+ * The default prompter: the injected-choice seam wins (test subprocess),
103
+ * otherwise the existing `promptSelect` helper renders the four-way menu with
104
+ * "not now" as the safe default.
105
+ */
106
+ async function defaultChooseAction(targetVersion) {
107
+ const injected = injectedOfferChoice();
108
+ if (injected !== null)
109
+ return injected;
110
+ return promptSelect(`OpenPlanr ${targetVersion} is available — how would you like to proceed?`, OFFER_CHOICES, 'not-now');
111
+ }
112
+ /** Human phrase for the backoff about to be applied at a given stage. */
113
+ function describeBackoff(stage) {
114
+ return stage === 0 ? 'in 24 hours' : stage === 1 ? 'in 48 hours' : 'in a week';
115
+ }
116
+ /**
117
+ * The entry point wired into `src/cli/index.ts`'s `preAction` hook. Fail-open at
118
+ * the top level: nothing about an upgrade offer may ever break the command the
119
+ * user actually ran, so every path below is wrapped and a thrown error degrades
120
+ * to "did not surface" rather than propagating out of the hook.
121
+ */
122
+ export async function maybeOfferUpgrade(projectDir, config, deps = {}) {
123
+ try {
124
+ return await runOffer(projectDir, config ?? null, deps);
125
+ }
126
+ catch {
127
+ return { surfaced: false, choice: null, reason: 'error' };
128
+ }
129
+ }
130
+ async function runOffer(projectDir, config, deps) {
131
+ const now = deps.now ?? injectedNow() ?? Date.now();
132
+ const reconcile = deps.reconcile ?? reconcileInstalledTuple;
133
+ const runUpgrade = deps.executeUpgrade ?? executeCliHalfUpgrade;
134
+ const chooseAction = deps.chooseAction ?? defaultChooseAction;
135
+ const state = readSnoozeState();
136
+ // (1) Permanent opt-out — honoured before anything else. Zero reconcile.
137
+ if (state.neverAsk)
138
+ return { surfaced: false, choice: null, reason: 'never-ask' };
139
+ // (2) Active snooze — the whole point of the backoff. This is the no-delay
140
+ // guarantee: a still-snoozed command touches no network and no reconcile.
141
+ if (state.snoozeUntil && now < Date.parse(state.snoozeUntil)) {
142
+ return { surfaced: false, choice: null, reason: 'snoozed' };
143
+ }
144
+ // (3) Team opt-out of the check itself — `update_check: false` skips reconcile
145
+ // entirely: no network, no prompt.
146
+ if (config?.upgrade?.updateCheck === false) {
147
+ return { surfaced: false, choice: null, reason: 'update-check-disabled' };
148
+ }
149
+ // Only now do we consult reconcile — bounded, cache-first, and offline-safe by
150
+ // T-002's construction. `unknown`/`aligned`/`incompatible` never offer here.
151
+ const reconciliation = await reconcile(projectDir, { now });
152
+ if (reconciliation.status !== 'upgrade-available') {
153
+ return { surfaced: false, choice: null, reason: 'not-upgrade-available' };
154
+ }
155
+ const plan = planCliUpgrade(reconciliation);
156
+ const targetVersion = plan.targetCliVersion ?? reconciliation.published?.cli.version ?? 'a newer version';
157
+ // (4) `auto_upgrade: true` — the team asked us to skip the prompt entirely.
158
+ if (config?.upgrade?.autoUpgrade === true) {
159
+ await performUpgrade(projectDir, plan, runUpgrade);
160
+ await writeSnoozeState({ ...DEFAULT_SNOOZE_STATE });
161
+ return { surfaced: false, choice: null, reason: 'auto-upgraded' };
162
+ }
163
+ // (5) The four-way offer.
164
+ logger.info(`An OpenPlanr upgrade is available (${reconciliation.installed.cli} → ${targetVersion}).`);
165
+ const choice = await chooseAction(targetVersion);
166
+ await applyChoice({ choice, projectDir, config, plan, now, state, runUpgrade });
167
+ return { surfaced: true, choice, reason: 'offered' };
168
+ }
169
+ async function applyChoice(input) {
170
+ const { choice, projectDir, config, plan, now, state, runUpgrade } = input;
171
+ switch (choice) {
172
+ case 'upgrade-now':
173
+ await performUpgrade(projectDir, plan, runUpgrade);
174
+ // A satisfied upgrade clears any prior snooze so future drift starts fresh.
175
+ await writeSnoozeState({ ...DEFAULT_SNOOZE_STATE });
176
+ return;
177
+ case 'always':
178
+ await enableAutoUpgrade(projectDir, config);
179
+ await performUpgrade(projectDir, plan, runUpgrade);
180
+ await writeSnoozeState({ ...DEFAULT_SNOOZE_STATE });
181
+ return;
182
+ case 'not-now':
183
+ await recordSnooze(state, now);
184
+ return;
185
+ case 'never':
186
+ await recordNeverAsk(state);
187
+ return;
188
+ }
189
+ }
190
+ /** FR5 escalating snooze: wait `BACKOFF_MS[stage]`, then advance the stage (capped at 2). */
191
+ async function recordSnooze(state, now) {
192
+ const stage = state.snoozeStage;
193
+ const snoozeUntil = new Date(now + BACKOFF_MS[stage]).toISOString();
194
+ const nextStage = stage < 2 ? (stage + 1) : 2;
195
+ await writeSnoozeState({ neverAsk: false, snoozeUntil, snoozeStage: nextStage });
196
+ logger.info(`Snoozed — I'll check for an upgrade again ${describeBackoff(stage)}.`);
197
+ }
198
+ /** FR6 permanent, reversible opt-out. States the exact re-enable command. */
199
+ async function recordNeverAsk(state) {
200
+ await writeSnoozeState({ ...state, neverAsk: true, snoozeUntil: null });
201
+ logger.info('You will not be asked about upgrades again on this machine.');
202
+ logger.info(`To re-enable the reminder, run: ${UPGRADE_REENABLE_COMMAND}`);
203
+ }
204
+ /** Persist the team-shared `auto_upgrade` preference into `config.json`. */
205
+ async function enableAutoUpgrade(projectDir, config) {
206
+ if (!config) {
207
+ logger.warn('No project config found, so auto-upgrade could not be saved for the team — upgrading now.');
208
+ return;
209
+ }
210
+ const updated = {
211
+ ...config,
212
+ upgrade: { ...config.upgrade, autoUpgrade: true },
213
+ };
214
+ await saveConfig(projectDir, updated);
215
+ logger.success('Auto-upgrade enabled — future upgrades will run without asking.');
216
+ }
217
+ /**
218
+ * Delegate the CLI-owned half to T-003's executor and render its result. This
219
+ * re-uses `executeCliHalfUpgrade` verbatim: the offer never re-implements the
220
+ * npm install, the verify-after-write, or the plugin-half prescription.
221
+ */
222
+ async function performUpgrade(projectDir, plan, runUpgrade) {
223
+ if (!plan.proceed || !plan.targetCliVersion) {
224
+ logger.warn(plan.reason);
225
+ return;
226
+ }
227
+ logger.info(`Upgrading the OpenPlanr CLI to ${plan.targetCliVersion}…`);
228
+ const result = await runUpgrade({ projectDir, targetCliVersion: plan.targetCliVersion });
229
+ renderUpgradeResult(result);
230
+ }
231
+ /** Mirror `planr upgrade apply`'s rendering so the inline offer reads identically. */
232
+ function renderUpgradeResult(result) {
233
+ if (result.ok) {
234
+ logger.success(`CLI upgraded to ${result.installedVersion}.`);
235
+ if (result.changelogBullets.length > 0) {
236
+ display.blank();
237
+ display.heading("What's new");
238
+ for (const bullet of result.changelogBullets)
239
+ display.bullet(bullet);
240
+ }
241
+ if (result.pluginHalfCommands.length > 0) {
242
+ display.blank();
243
+ display.heading('Plugin half — the CLI cannot install host plugins');
244
+ logger.info('Run these yourself, in order (the first refreshes the marketplace):');
245
+ result.pluginHalfCommands.forEach((command, index) => {
246
+ display.numbered(index + 1, command);
247
+ });
248
+ }
249
+ }
250
+ else {
251
+ logger.error(result.failure?.message ?? 'The upgrade did not complete.');
252
+ if (result.restoredTo)
253
+ logger.info(`Restored the previous version ${result.restoredTo}.`);
254
+ }
255
+ }
256
+ //# sourceMappingURL=upgrade-offer-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-offer-service.js","sourceRoot":"","sources":["../../src/services/upgrade-offer-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import { type ClaudeCommandRunner, type ClaudePluginOperation } from './claude-plugin-service.js';
2
+ /**
3
+ * One component of the published compatibility manifest (`ecosystem.json`'s
4
+ * `components.*`). Each artifact carries its own version plus the mutual
5
+ * compatibility range it requires of its sibling.
6
+ */
7
+ export interface EcosystemComponent {
8
+ version: string;
9
+ cliRange?: string;
10
+ pipelineRange?: string;
11
+ }
12
+ export interface EcosystemComponents {
13
+ cli: EcosystemComponent;
14
+ pipeline: EcosystemComponent;
15
+ skills: EcosystemComponent;
16
+ marketplace?: EcosystemComponent;
17
+ }
18
+ /**
19
+ * Where the compatibility manifest came from for this reconciliation.
20
+ *
21
+ * - `network` — freshly fetched and cached this run.
22
+ * - `cache` — a still-fresh cache (within the TTL); no network was touched.
23
+ * - `stale-cache` — the fetch failed, so a past cache was reused.
24
+ * - `unavailable` — neither a fetch nor any cache; the tuple cannot be judged.
25
+ */
26
+ export type EcosystemSource = 'network' | 'cache' | 'stale-cache' | 'unavailable';
27
+ export interface UpgradeReconciliation {
28
+ status: 'aligned' | 'upgrade-available' | 'incompatible' | 'unknown';
29
+ installed: {
30
+ cli: string;
31
+ skills: string | null;
32
+ pipeline: string | null;
33
+ };
34
+ published: EcosystemComponents | null;
35
+ ecosystemSource: EcosystemSource;
36
+ }
37
+ export interface ReconcileOptions {
38
+ /** Injectable `claude` runner; defaults to the real host command. */
39
+ claudeCommandRunner?: ClaudeCommandRunner;
40
+ /** Injectable fetch, for hermetic offline/hung-network tests. */
41
+ fetchImpl?: typeof fetch;
42
+ /** Clock override (ms since epoch), for deterministic TTL tests. */
43
+ now?: number;
44
+ /** Hard fetch timeout in ms; a hung network must never exceed this. */
45
+ timeoutMs?: number;
46
+ }
47
+ /**
48
+ * FR3: read the published compatibility manifest, compare it against the real
49
+ * installed tuple (this CLI's version, plus both host-plugin versions), and
50
+ * report whether the tuple is aligned, has an upgrade available, or is
51
+ * genuinely incompatible. The warn-vs-fail call is delegated to
52
+ * `classifyComponentDrift` so it is doctor's exact distinction, not a re-derivation.
53
+ */
54
+ export declare function reconcileInstalledTuple(_projectDir: string, options?: ReconcileOptions): Promise<UpgradeReconciliation>;
55
+ export interface NpmCommandResult {
56
+ status: number | null;
57
+ stdout: string;
58
+ stderr: string;
59
+ error?: Error;
60
+ }
61
+ export type NpmCommandRunner = (args: string[]) => NpmCommandResult;
62
+ export interface UpgradePlan {
63
+ proceed: boolean;
64
+ targetCliVersion: string | null;
65
+ reason: string;
66
+ }
67
+ /**
68
+ * FR4 ownership split, decided once so the CLI command stays thin: the npm half
69
+ * is executed only when the CLI itself can move forward — `upgrade-available`,
70
+ * or `incompatible` with the CLI genuinely behind the published version. An
71
+ * `incompatible` tuple whose CLI is not behind cannot be fixed by upgrading the
72
+ * CLI (the plugin half must move); apply prints the prescription instead of
73
+ * mutating. `aligned`/`unknown` never mutate.
74
+ */
75
+ export declare function planCliUpgrade(reconciliation: UpgradeReconciliation): UpgradePlan;
76
+ /**
77
+ * FR8 — "what's new, honestly." Return the changelog bullets between two
78
+ * `## <version>` headers: everything after `## <newVersion>` and before
79
+ * `## <oldVersion>` (the changelog is newest-first, so the new version sits
80
+ * above the old one). Only real list items in that window are returned, each
81
+ * verbatim from the file. When the window or its entries are missing — an
82
+ * unreleased target, a CHANGELOG the package does not ship, a shifted file, no
83
+ * bullets — the result is empty, and the caller says so rather than inventing a
84
+ * summary. If the old header is absent, the window is bounded at the next
85
+ * version header so a summary can never reach back past the target's section.
86
+ */
87
+ export declare function summarizeChangelogBetween(oldVersion: string, newVersion: string): string[];
88
+ /**
89
+ * FR4's manifest-refresh guarantee: the marketplace add/refresh command is
90
+ * printed FIRST — "without which the installer reinstalls the stale version." A
91
+ * stable sort keeps every other operation in the order
92
+ * `inspectClaudePluginIntegration` produced, and each is rendered by
93
+ * `formatClaudePluginOperationCommand` (the same argv an apply would run), so
94
+ * the prescription can never drift from what would actually execute. This is a
95
+ * pure formatter — it prints the plugin half, it never runs it.
96
+ */
97
+ export declare function prescribePluginHalfCommands(operations: ClaudePluginOperation[]): string[];
98
+ /** One migration's outcome as the injected registry runner reports it (T-006). */
99
+ export interface MigrationRunResult {
100
+ id: string;
101
+ applied: boolean;
102
+ alreadyApplied: boolean;
103
+ failure?: string;
104
+ }
105
+ /**
106
+ * FR7's migration registry, injected rather than imported so this service owns
107
+ * only the call site and result field (the registry lives in
108
+ * `migration-registry.ts`). Called with the pre-upgrade and verified
109
+ * post-upgrade versions so a migration runs only when the upgrade crosses its
110
+ * version. `runPendingMigrations` satisfies this shape structurally.
111
+ */
112
+ export type MigrationRunner = (fromVersion: string, toVersion: string, ctx: {
113
+ projectDir: string;
114
+ }) => Promise<MigrationRunResult[]>;
115
+ export interface ExecuteCliHalfUpgradeInput {
116
+ projectDir: string;
117
+ targetCliVersion: string;
118
+ /** Injectable npm runner; defaults to the real (or `OPENPLANR_NPM_BIN`) npm. */
119
+ npmCommandRunner?: NpmCommandRunner;
120
+ /** Injectable `claude` runner for the prescription's inspection (hermetic tests). */
121
+ claudeCommandRunner?: ClaudeCommandRunner;
122
+ /**
123
+ * FR7 migration runner, run after the CLI half verifies. Omitted (no runner)
124
+ * means no migrations are attempted; the `apply` command injects the real
125
+ * registry's `runPendingMigrations`.
126
+ */
127
+ migrationRunner?: MigrationRunner;
128
+ }
129
+ export interface ExecuteCliHalfUpgradeResult {
130
+ ok: boolean;
131
+ cliUpgraded: boolean;
132
+ installedVersion: string;
133
+ restoredTo?: string;
134
+ changelogBullets: string[];
135
+ pluginHalfCommands: string[];
136
+ /** Per-migration results for every registered migration this upgrade crossed. */
137
+ migrations: MigrationRunResult[];
138
+ failure?: {
139
+ step: 'npm-install' | 'verify' | 'changelog' | 'migration';
140
+ message: string;
141
+ };
142
+ }
143
+ /**
144
+ * FR4/FR8/FR9. Execute the one half the CLI owns — a global npm install — and
145
+ * prescribe (never execute) the plugin half.
146
+ *
147
+ * FR9's atomicity is enforced by verify-after-write: the previously installed
148
+ * version is captured *before* any mutation as the restorable backup, and after
149
+ * a zero-exit install the on-disk version is re-read. A clean exit that did not
150
+ * land the target is the decisive case the spec names — it triggers an automatic
151
+ * reinstall of the captured previous version and reports exactly what was
152
+ * restored, so a partially-upgraded install can never report success. `ok` is
153
+ * `false` whenever an owned step fails, even when npm itself exited zero.
154
+ */
155
+ export declare function executeCliHalfUpgrade(input: ExecuteCliHalfUpgradeInput): Promise<ExecuteCliHalfUpgradeResult>;
156
+ //# sourceMappingURL=upgrade-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-service.d.ts","sourceRoot":"","sources":["../../src/services/upgrade-service.ts"],"names":[],"mappings":"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"}