quotacap 0.0.26 → 0.0.27

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.
@@ -4,7 +4,7 @@ import { ensureConfig, getDbPath } from "../config.js";
4
4
  import { createServiceClient } from "../runtime/client.js";
5
5
  import { VERSION } from "../version.js";
6
6
  import { isServiceManaged, runServiceCommand } from "../service/index.js";
7
- import { detectChannel, printUpdateFooter, readUpdateCache, refreshUpdateCache, updateFooter, } from "../runtime/updates.js";
7
+ import { detectChannel, printUpdateFooter, printVersionLine, readUpdateCache, refreshUpdateCache, updateFooter, } from "../runtime/updates.js";
8
8
  import { ClientError, OFFLINE_LABEL, emptySnapshot, offlineOnlyClient, resolveSnapshot } from "./snapshot-source.js";
9
9
  import { checkSkew, execSkewWarning, olderCliWarning, takeoverManaged, unmanagedSkewWarning, } from "./takeover.js";
10
10
  export function registerAdviseCommand(program, deps) {
@@ -67,6 +67,9 @@ export function registerAdviseCommand(program, deps) {
67
67
  ...takeoverOpts,
68
68
  });
69
69
  console.error(r.message);
70
+ // The takeover verified the daemon now reports the CLI version;
71
+ // keep the pre-check health from mislabeling the version line.
72
+ health.version = VERSION;
70
73
  }
71
74
  catch (e) {
72
75
  console.error(e instanceof Error ? e.message : String(e));
@@ -108,6 +111,7 @@ export function registerAdviseCommand(program, deps) {
108
111
  console.log(JSON.stringify(rec, null, 2));
109
112
  else {
110
113
  console.log(`${rec.use}: ${rec.reason}`);
114
+ printVersionLine(o, VERSION, health?.version);
111
115
  printUpdateFooter(o, footer);
112
116
  }
113
117
  return;
@@ -123,6 +127,7 @@ export function registerAdviseCommand(program, deps) {
123
127
  console.log(JSON.stringify(rec, null, 2));
124
128
  else {
125
129
  console.log(`${rec.use}: ${rec.reason}`);
130
+ printVersionLine(o, VERSION, health?.version);
126
131
  printUpdateFooter(o, footer);
127
132
  }
128
133
  });
@@ -6,7 +6,7 @@ import { assertValidSortKey } from "../format/rows.js";
6
6
  import { createServiceClient } from "../runtime/client.js";
7
7
  import { VERSION } from "../version.js";
8
8
  import { isServiceManaged, runServiceCommand } from "../service/index.js";
9
- import { detectChannel, printUpdateFooter, readUpdateCache, refreshUpdateCache, updateFooter, } from "../runtime/updates.js";
9
+ import { detectChannel, printUpdateFooter, printVersionLine, readUpdateCache, refreshUpdateCache, updateFooter, } from "../runtime/updates.js";
10
10
  import { ClientError, OFFLINE_LABEL, offlineOnlyClient, resolveSnapshot } from "./snapshot-source.js";
11
11
  import { checkSkew, execSkewWarning, olderCliWarning, takeoverManaged, unmanagedSkewWarning, } from "./takeover.js";
12
12
  const WIDE_COLUMNS = 100;
@@ -76,6 +76,9 @@ export function registerStatusCommand(program, deps) {
76
76
  ...takeoverOpts,
77
77
  });
78
78
  console.error(r.message);
79
+ // The takeover verified the daemon now reports the CLI version;
80
+ // keep the pre-check health from mislabeling the version line.
81
+ health.version = VERSION;
79
82
  }
80
83
  catch (e) {
81
84
  console.error(e instanceof Error ? e.message : String(e));
@@ -122,6 +125,7 @@ export function registerStatusCommand(program, deps) {
122
125
  return;
123
126
  }
124
127
  console.log(e.message);
128
+ printVersionLine(o, VERSION, health?.version);
125
129
  printUpdateFooter(o, footer);
126
130
  return;
127
131
  }
@@ -153,6 +157,7 @@ export function registerStatusCommand(program, deps) {
153
157
  if (issues)
154
158
  console.log("\n" + issues);
155
159
  }
160
+ printVersionLine(o, VERSION, health?.version);
156
161
  printUpdateFooter(o, footer);
157
162
  });
158
163
  }
@@ -119,7 +119,7 @@ export async function takeoverManaged(opts) {
119
119
  : "unknown";
120
120
  const execService = opts.execService ?? ((args, o) => runServiceCommand(args, o ?? {}, {}));
121
121
  const verb = opts.refreshRegistration ? "install" : "restart";
122
- const code = await execService([verb], opts.refreshRegistration ? { version: cliVersion } : undefined);
122
+ const code = await execService([verb], opts.refreshRegistration ? { version: cliVersion, quiet: true } : undefined);
123
123
  if (code !== 0) {
124
124
  throw new TakeoverError(`daemon upgrade failed: service ${verb} exited ${code}`);
125
125
  }
@@ -57,6 +57,10 @@ export declare function updateFooter(current: string, latest: string | null): st
57
57
  export declare function printUpdateFooter(o: {
58
58
  json?: boolean;
59
59
  }, footer: string | null): void;
60
+ export declare function printVersionLine(o: {
61
+ json?: boolean;
62
+ compact?: boolean;
63
+ }, cliVersion: string, daemonVersion: unknown): void;
60
64
  export declare function platformAssetName(platform?: NodeJS.Platform, arch?: string): string;
61
65
  export declare function verifyChecksum(data: Buffer, sumsText: string, name: string): boolean;
62
66
  export declare function defaultExtractTar(tarball: string, destDir: string): void;
@@ -333,6 +333,17 @@ export function printUpdateFooter(o, footer) {
333
333
  return;
334
334
  console.error(footer);
335
335
  }
336
+ // Version identity line, printed next to the update footer under the same
337
+ // stderr/TTY rules, plus never under --compact (prompt renderers must not
338
+ // see per-render noise). The daemon part appears only when the reachable
339
+ // daemon reports a version that differs from the CLI.
340
+ export function printVersionLine(o, cliVersion, daemonVersion) {
341
+ if (o.json || o.compact || !process.stderr.isTTY)
342
+ return;
343
+ const daemon = typeof daemonVersion === "string" && daemonVersion ? daemonVersion : null;
344
+ const suffix = daemon && daemon !== cliVersion ? ` · daemon ${daemon}` : "";
345
+ console.error(`quotacap ${cliVersion}${suffix}`);
346
+ }
336
347
  export function platformAssetName(platform = process.platform, arch = process.arch) {
337
348
  const os = platform === "darwin" ? "darwin" : platform === "linux" ? "linux" : null;
338
349
  const a = arch === "arm64" ? "arm64" : arch === "x64" ? "x64" : null;
@@ -75,8 +75,14 @@ export async function runServiceCommand(args, opts = {}, deps = {}) {
75
75
  case "install":
76
76
  // Internal: the post-update refresh passes the target version so
77
77
  // install compares and records it instead of the old in-process
78
- // VERSION. The `service install` CLI never sets opts.version.
79
- await backend.install({ ...deps, version: opts.version ?? deps.version });
78
+ // VERSION, plus quiet so a good update prints its own three lines
79
+ // instead of the install chatter. The `service install` CLI never
80
+ // sets opts.version or opts.quiet.
81
+ await backend.install({
82
+ ...deps,
83
+ version: opts.version ?? deps.version,
84
+ quiet: opts.quiet ?? deps.quiet,
85
+ });
80
86
  break;
81
87
  case "uninstall":
82
88
  await backend.uninstall(deps);
@@ -21,6 +21,7 @@ export interface ServiceDeps {
21
21
  sleep?: (ms: number) => Promise<void>;
22
22
  bootstrapTimeoutMs?: number;
23
23
  version?: string;
24
+ quiet?: boolean;
24
25
  }
25
26
  export declare function foregroundGuidance(verb: string, platform: string): string;
26
27
  export declare function defaultRunLaunchctl(args: string[]): string;
@@ -279,6 +279,8 @@ export async function install(deps = {}) {
279
279
  (deps.print ?? console.log)(foregroundGuidance("install", platform));
280
280
  return;
281
281
  }
282
+ if (deps.quiet)
283
+ deps = { ...deps, print: () => { } };
282
284
  const { home, uid, dataDir, print, run, lint, sleep, bootstrapTimeoutMs } = resolved(deps);
283
285
  const version = deps.version ?? VERSION;
284
286
  // Provision beside the data dir the service will use (identical to the
@@ -342,6 +344,14 @@ export async function install(deps = {}) {
342
344
  installedAt: (deps.now?.() ?? new Date()).toISOString(),
343
345
  }, dataDir);
344
346
  bootoutQuiet(run, uid, print);
347
+ // bootout is asynchronous: without this the replacement races the dying
348
+ // process for the port, the same race restart() guards against.
349
+ // deps.port keeps tests off the real daemon's port.
350
+ const upgradePort = deps.port ?? (await readConfig()).port;
351
+ const waitReleased = deps.waitReleased ?? defaultWaitReleased;
352
+ if (!(await waitReleased(upgradePort))) {
353
+ print(`port ${upgradePort} is still in use after stopping; starting anyway`);
354
+ }
345
355
  }
346
356
  else {
347
357
  writeServiceMetadata({
@@ -18,6 +18,7 @@ export interface SystemdDeps {
18
18
  error?: (msg: string) => void;
19
19
  now?: () => Date;
20
20
  version?: string;
21
+ quiet?: boolean;
21
22
  }
22
23
  export declare function formatSystemctlError(args: string[], detail: string): Error;
23
24
  export declare function defaultRunSystemctl(args: string[]): string;
@@ -106,6 +106,8 @@ export async function install(deps = {}) {
106
106
  (deps.print ?? console.log)(foregroundGuidance("install", platform));
107
107
  return;
108
108
  }
109
+ if (deps.quiet)
110
+ deps = { ...deps, print: () => { } };
109
111
  const { home, dataDir, print, run } = resolved(deps);
110
112
  const version = deps.version ?? VERSION;
111
113
  // Provision beside the data dir the service will use (identical to the
@@ -157,6 +159,14 @@ export async function install(deps = {}) {
157
159
  installedAt: (deps.now?.() ?? new Date()).toISOString(),
158
160
  }, dataDir);
159
161
  stopQuiet(run, print);
162
+ // systemctl stop can return before the port is released; same race as
163
+ // launchd. deps.port mirrors start(): tests must never probe the real
164
+ // daemon's port.
165
+ const upgradePort = deps.port ?? (await readConfig()).port;
166
+ const waitReleased = deps.waitReleased ?? defaultWaitReleased;
167
+ if (!(await waitReleased(upgradePort))) {
168
+ print(`port ${upgradePort} is still in use after stopping; starting anyway`);
169
+ }
160
170
  }
161
171
  else {
162
172
  writeServiceMetadata({
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // generated by scripts/build-embed.mjs — do not edit
2
- export const VERSION = "0.0.26";
2
+ export const VERSION = "0.0.27";
@@ -95,7 +95,7 @@ function App() {
95
95
  : null;
96
96
  const header = (_jsx(Header, { runtime: snapshot?.runtime ?? null, unreachable: error?.kind === "service-unavailable", refreshing: refreshing, onRefresh: () => void refresh(), onSettings: () => setSettingsOpen(true) }));
97
97
  if (snapshot && setupMode) {
98
- return (_jsxs(_Fragment, { children: [header, _jsx("main", { children: _jsx(Onboarding, { snapshot: snapshot, onFirstPoll: () => void refresh(), polling: refreshing, pollError: pollErrorText }) }), _jsx(SettingsDrawer, { open: settingsOpen, providers: snapshot.providers, onRefresh: () => void refresh(), refreshing: refreshing, onClose: () => setSettingsOpen(false) }), _jsx(SiteFooter, {})] }));
98
+ return (_jsxs(_Fragment, { children: [header, _jsx("main", { children: _jsx(Onboarding, { snapshot: snapshot, onFirstPoll: () => void refresh(), polling: refreshing, pollError: pollErrorText }) }), _jsx(SettingsDrawer, { open: settingsOpen, providers: snapshot.providers, onRefresh: () => void refresh(), refreshing: refreshing, onClose: () => setSettingsOpen(false) }), _jsx(SiteFooter, { version: snapshot.runtime.version })] }));
99
99
  }
100
100
  return (_jsxs(_Fragment, { children: [header, _jsxs("main", { children: [notice && (_jsx("div", { "data-testid": "refresh-notice", role: "status", children: notice })), loading && !snapshot && (_jsx("div", { "data-testid": "state-loading", "aria-busy": "true", "aria-label": "Loading quota state", children: [0, 1, 2].map((i) => (_jsx("div", { "aria-hidden": "true", className: "skel", style: {
101
101
  height: 64,
@@ -109,7 +109,7 @@ function App() {
109
109
  }, onViewAdvice: () => {
110
110
  setSelectedProvider(null);
111
111
  setAdviceOpen(true);
112
- } }))] }), _jsx(ProviderDrawer, { provider: snapshot?.providers.find((p) => p.id === selectedProvider) ?? null, asOf: snapshot?.asOf ?? "", recommendation: snapshot?.recommendation ?? null, onClose: () => setSelectedProvider(null), onRenamed: () => void load() }), snapshot && (_jsx(AdviceDrawer, { open: adviceOpen, recommendation: snapshot.recommendation, providers: snapshot.providers, asOf: snapshot.asOf, onClose: () => setAdviceOpen(false) })), _jsx(SettingsDrawer, { open: settingsOpen, providers: snapshot?.providers ?? [], onRefresh: () => void refresh(), refreshing: refreshing, onClose: () => setSettingsOpen(false) }), _jsx(SiteFooter, {})] }));
112
+ } }))] }), _jsx(ProviderDrawer, { provider: snapshot?.providers.find((p) => p.id === selectedProvider) ?? null, asOf: snapshot?.asOf ?? "", recommendation: snapshot?.recommendation ?? null, onClose: () => setSelectedProvider(null), onRenamed: () => void load() }), snapshot && (_jsx(AdviceDrawer, { open: adviceOpen, recommendation: snapshot.recommendation, providers: snapshot.providers, asOf: snapshot.asOf, onClose: () => setAdviceOpen(false) })), _jsx(SettingsDrawer, { open: settingsOpen, providers: snapshot?.providers ?? [], onRefresh: () => void refresh(), refreshing: refreshing, onClose: () => setSettingsOpen(false) }), _jsx(SiteFooter, { version: snapshot?.runtime.version })] }));
113
113
  }
114
114
  const el = typeof document === "undefined" ? null : document.getElementById("app");
115
115
  if (el)
@@ -1,3 +1,5 @@
1
1
  import React from "react";
2
2
  export declare function PacingLegend(): React.JSX.Element;
3
- export declare function SiteFooter(): React.JSX.Element;
3
+ export declare function SiteFooter({ version }: {
4
+ version?: string;
5
+ }): React.JSX.Element;
@@ -38,6 +38,7 @@ function servingHost() {
38
38
  return "127.0.0.1";
39
39
  }
40
40
  }
41
- export function SiteFooter() {
42
- return (_jsxs("footer", { className: "sitefoot", children: [_jsxs("div", { className: "sitefoot-start", children: [_jsxs("span", { children: ["QuotaCap \u00B7 local quota tracker \u00B7 bound to ", _jsx("code", { children: servingHost() })] }), _jsx(PacingLegend, {})] }), _jsx("span", { children: "MIT licence \u00B7 no telemetry \u00B7 history stays on this machine" })] }));
41
+ export function SiteFooter({ version }) {
42
+ const name = version ? `QuotaCap v${version}` : "QuotaCap";
43
+ return (_jsxs("footer", { className: "sitefoot", children: [_jsxs("div", { className: "sitefoot-start", children: [_jsxs("span", { children: [name, " \u00B7 local quota tracker \u00B7 bound to ", _jsx("code", { children: servingHost() })] }), _jsx(PacingLegend, {})] }), _jsx("span", { children: "MIT licence \u00B7 no telemetry \u00B7 history stays on this machine" })] }));
43
44
  }