@timurproko/a1-install 0.2.6 → 0.2.7-dev.1222

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/bin/a1-install.js CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { spawn } from "node:child_process";
4
4
  import { access, lstat, readFile, realpath } from "node:fs/promises";
5
- import { lstatSync, realpathSync } from "node:fs";
5
+ import { lstatSync, realpathSync, readFileSync, statSync } from "node:fs";
6
+ import { homedir } from "node:os";
6
7
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
7
8
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { SHARED_PROGRESS_ACCENT_ANSI } from "./progress-palette.js";
9
+ import { SHARED_PROGRESS_ACCENT_ANSI, SHARED_PROGRESS_PALETTE } from "./progress-palette.js";
9
10
 
10
11
  export { SHARED_PROGRESS_ACCENT_ANSI };
11
12
 
@@ -26,6 +27,9 @@ const STABLE_VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$
26
27
  const DEVELOPMENT_VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)-dev\.[1-9]\d*$/u;
27
28
  const PREVIEW_NUMBER_PATTERN = /^[1-9]\d*$/u;
28
29
  const VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-dev\.[1-9]\d*)?$/u;
30
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
31
+ const PACKAGE_DOCUMENT_TIMEOUT_MS = 30_000;
32
+ const MAX_PACKAGE_DOCUMENT_BYTES = 32 * 1024 * 1024;
29
33
 
30
34
  class InstallationFailure extends Error {
31
35
  constructor(reason, details = "", exitCode = 1) {
@@ -93,12 +97,34 @@ export function installerHelp() {
93
97
  ].join("\n");
94
98
  }
95
99
 
96
- export function renderProgressBar(percent) {
100
+ // Invariant: the bootstrap cannot import the main package's settings manager or its dependencies.
101
+ // Mirror only its bounded, read-only global A1 accent resolution before acquisition.
102
+ export function resolveInstallerAccentColor(environment = process.env, home = homedir) {
103
+ try {
104
+ const selectedHome = environment.A1_PROFILE_HOME ?? home();
105
+ if (!isAbsolute(selectedHome) || selectedHome.includes("\0")) return "blue";
106
+ const file = join(selectedHome, ".a1", "agent", "settings.json");
107
+ if (statSync(file).size > 256 * 1024) return "blue";
108
+ const raw = readFileSync(file, "utf8");
109
+ if (Buffer.byteLength(raw, "utf8") > 256 * 1024) return "blue";
110
+ const root = JSON.parse(raw.replace(/^\uFEFF/u, ""));
111
+ const settings = root && typeof root === "object" && !Array.isArray(root)
112
+ ? Object.getOwnPropertyDescriptor(root, "a1")?.value : undefined;
113
+ const value = settings && typeof settings === "object" && !Array.isArray(settings)
114
+ ? settings.accentColor : undefined;
115
+ if (value === "default") return "purple";
116
+ return typeof value === "string" && Object.hasOwn(SHARED_PROGRESS_PALETTE, value) ? value : "blue";
117
+ } catch {
118
+ return "blue";
119
+ }
120
+ }
121
+
122
+ export function renderProgressBar(percent, accent = SHARED_PROGRESS_ACCENT_ANSI) {
97
123
  const bounded = Math.min(100, Math.max(0, Math.round(percent)));
98
124
  const filled = Math.round((bounded / 100) * PROGRESS_WIDTH);
99
125
  const gray = "\u001b[38;2;128;128;128m";
100
126
  const track = "\u001b[38;2;102;102;102m";
101
- return `${SHARED_PROGRESS_ACCENT_ANSI}${"━".repeat(filled)}${track}${"─".repeat(PROGRESS_WIDTH - filled)}${gray} ${bounded}%\u001b[39m`;
127
+ return `${accent}${"━".repeat(filled)}${track}${"─".repeat(PROGRESS_WIDTH - filled)}${gray} ${bounded}%\u001b[39m`;
102
128
  }
103
129
 
104
130
  export function classifyProgressLine(line, fallback = "Installing") {
@@ -150,6 +176,7 @@ class ProgressDisplay {
150
176
  this.timer = null;
151
177
  this.visible = false;
152
178
  this.maxWidth = PROGRESS_WIDTH + 6;
179
+ this.accent = options.accent ?? SHARED_PROGRESS_ACCENT_ANSI;
153
180
  }
154
181
 
155
182
  set(percent, _label, creepTo = percent) {
@@ -172,7 +199,7 @@ class ProgressDisplay {
172
199
  this.shown = rounded;
173
200
  if (!this.visible) this.output.write("\u001b[?25l");
174
201
  this.visible = true;
175
- this.output.write(`\r${renderProgressBar(rounded)}\u001b[K`);
202
+ this.output.write(`\r${renderProgressBar(rounded, this.accent)}\u001b[K`);
176
203
  }
177
204
 
178
205
  finish() {
@@ -279,16 +306,49 @@ async function runChild(command, args, options) {
279
306
 
280
307
  function targetSpec(target) {
281
308
  if (target.kind === "develop") return `${APPLICATION_PACKAGE}@next`;
309
+ if (target.kind === "preview") return `${APPLICATION_PACKAGE}@${target.requested}`;
282
310
  return `${APPLICATION_PACKAGE}@latest`;
283
311
  }
284
312
 
313
+ /** A numbered preview needs the registry's manifests; a full version resolves through npm directly. */
314
+ function previewNeedsPackageDocument(target) {
315
+ return target.kind === "preview" && PREVIEW_NUMBER_PATTERN.test(target.requested);
316
+ }
317
+
285
318
  function resolutionArguments(target) {
286
- if (target.kind === "preview") {
287
- return ["view", APPLICATION_PACKAGE, "versions", "--json", "--loglevel=verbose", "--no-fund", "--no-audit"];
288
- }
289
319
  return ["view", targetSpec(target), "version", "--json", "--loglevel=verbose", "--no-fund", "--no-audit"];
290
320
  }
291
321
 
322
+ function packageDocumentUrl(environment) {
323
+ const registry = (environment.npm_config_registry ?? "").trim() || DEFAULT_REGISTRY;
324
+ return `${registry.replace(/\/+$/u, "")}/${encodeURIComponent(APPLICATION_PACKAGE)}`;
325
+ }
326
+
327
+ /**
328
+ * The registry's full package document, whose per-version manifests record the pull request a
329
+ * preview was published from; the version list and the abbreviated install document do not.
330
+ */
331
+ async function fetchPackageDocument(fetchImpl, environment) {
332
+ let response;
333
+ try {
334
+ response = await fetchImpl(packageDocumentUrl(environment), {
335
+ headers: { accept: "application/json" },
336
+ signal: AbortSignal.timeout(PACKAGE_DOCUMENT_TIMEOUT_MS),
337
+ });
338
+ } catch (error) {
339
+ throw new InstallationFailure("could not resolve the selected release", String(error));
340
+ }
341
+ if (!response.ok) throw new InstallationFailure("could not resolve the selected release", `registry responded with status ${response.status}`);
342
+ const text = await response.text();
343
+ if (text.length > MAX_PACKAGE_DOCUMENT_BYTES) throw new InstallationFailure("could not resolve the selected release", "registry package document is oversized");
344
+ return text;
345
+ }
346
+
347
+ function recordedPullRequest(manifest) {
348
+ const value = manifest?.publication?.pullRequest;
349
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
350
+ }
351
+
292
352
  function updateArguments(target, targetVersion) {
293
353
  if (target.kind === "develop") return ["update", "--develop"];
294
354
  if (target.kind === "preview") return ["update", "--develop", targetVersion];
@@ -313,17 +373,32 @@ function parseResolvedVersion(stdout, target) {
313
373
  return value;
314
374
  }
315
375
 
316
- export function resolvePublishedPreview(stdout, requested) {
376
+ /**
377
+ * The preview a `--develop` value names among the published versions: an exact full version, or a
378
+ * number that a manifest records as its pull request or that trails a `-dev.<number>` version
379
+ * published before pull requests were recorded. `source` is the registry's package document; a
380
+ * bare JSON list of versions is accepted as a document without records.
381
+ */
382
+ export function resolvePublishedPreview(source, requested) {
317
383
  let published;
318
- try { published = JSON.parse(String(stdout).trim() || "[]"); }
319
- catch { throw new InstallationFailure("could not resolve the selected release", String(stdout)); }
320
- const versions = (Array.isArray(published) ? published : [published])
321
- .filter(value => typeof value === "string" && DEVELOPMENT_VERSION_PATTERN.test(value));
384
+ try { published = JSON.parse(String(source).trim() || "[]"); }
385
+ catch { throw new InstallationFailure("could not resolve the selected release", String(source)); }
386
+ const manifests = Array.isArray(published)
387
+ ? Object.fromEntries(published.filter(value => typeof value === "string").map(version => [version, {}]))
388
+ : published?.versions;
389
+ if (typeof manifests !== "object" || manifests === null) throw new InstallationFailure("could not resolve the selected release", String(source));
390
+ const previews = Object.entries(manifests)
391
+ .filter(([version]) => DEVELOPMENT_VERSION_PATTERN.test(version))
392
+ .map(([version, manifest]) => ({ version, pullRequest: recordedPullRequest(manifest) }));
393
+ const versions = previews.map(preview => preview.version);
322
394
  if (DEVELOPMENT_VERSION_PATTERN.test(requested)) {
323
395
  if (versions.includes(requested)) return requested;
324
396
  throw new InstallationFailure("the selected release was not found", `published no preview ${requested}`);
325
397
  }
326
- const matches = versions.filter(version => version.endsWith(`-dev.${requested}`));
398
+ const number = Number(requested);
399
+ const matches = previews
400
+ .filter(preview => preview.pullRequest === number || preview.version.endsWith(`-dev.${requested}`))
401
+ .map(preview => preview.version);
327
402
  if (matches.length === 1) return matches[0];
328
403
  const detail = matches.length === 0
329
404
  ? `published no preview for ${requested}`
@@ -500,7 +575,19 @@ export async function runInstaller(argv, options = {}) {
500
575
  throw new InstallationFailure("Node.js 22.19.0 or newer is required");
501
576
  }
502
577
 
503
- const progress = options.progress ?? new ProgressDisplay(stdout, options.tty ?? stdout.isTTY === true, options.progressOptions);
578
+ const accentColor = resolveInstallerAccentColor(environment);
579
+ const background = Number.parseInt(String(environment.COLORFGBG ?? "").split(";").at(-1) ?? "", 10);
580
+ const appearance = Number.isInteger(background) && (background === 7 || background === 15 || background >= 231) ? "light" : "dark";
581
+ const program = (environment.TERM_PROGRAM ?? "").toLowerCase();
582
+ const known = ["kitty", "ghostty", "wezterm", "rex", "warpterminal", "iterm.app", "alacritty", "vscode", "zed"];
583
+ const trueColor = environment.PI_TRUE_COLOR === "1" || (environment.PI_TRUE_COLOR !== "0" && (
584
+ /^(truecolor|24bit)$/iu.test(environment.COLORTERM ?? "")
585
+ || (environment.TERM ?? "").endsWith("-direct") || process.platform === "win32"
586
+ || environment.WT_SESSION !== undefined || known.includes(program)
587
+ ));
588
+ const mode = trueColor ? "truecolor" : "256color";
589
+ const accent = SHARED_PROGRESS_PALETTE[accentColor][appearance][mode].accent;
590
+ const progress = options.progress ?? new ProgressDisplay(stdout, options.tty ?? stdout.isTTY === true, { ...options.progressOptions, accent });
504
591
  const runner = options.runner ?? createDefaultProcessRunner(environment);
505
592
  let activeChild = null;
506
593
  let killTimer = null;
@@ -532,16 +619,23 @@ export async function runInstaller(argv, options = {}) {
532
619
  try {
533
620
  progress.set(3, "Preparing", 10);
534
621
  let phase = "Resolving version";
535
- const resolveResult = await runner.npm(resolutionArguments(parsed.target), {
536
- onChild: setActiveChild,
537
- onStderrLine: line => { phase = classifyProgressLine(line, phase); progress.set(10, phase, 18); },
538
- });
539
- setActiveChild(null);
540
- if (cancelled) throw new InstallationCancelled();
541
- if (resolveResult.code !== 0) throw new InstallationFailure(conciseFailure("resolve", resolveResult.stderr), resolveResult.stderr);
542
- const targetVersion = parsed.target.kind === "preview"
543
- ? resolvePublishedPreview(resolveResult.stdout, parsed.target.requested)
544
- : parseResolvedVersion(resolveResult.stdout, parsed.target);
622
+ let targetVersion;
623
+ if (previewNeedsPackageDocument(parsed.target)) {
624
+ // Rationale: a numbered preview is found by the pull request its published manifest records,
625
+ // which only the registry's full package document carries.
626
+ const document = await fetchPackageDocument(options.fetch ?? globalThis.fetch, environment);
627
+ if (cancelled) throw new InstallationCancelled();
628
+ targetVersion = resolvePublishedPreview(document, parsed.target.requested);
629
+ } else {
630
+ const resolveResult = await runner.npm(resolutionArguments(parsed.target), {
631
+ onChild: setActiveChild,
632
+ onStderrLine: line => { phase = classifyProgressLine(line, phase); progress.set(10, phase, 18); },
633
+ });
634
+ setActiveChild(null);
635
+ if (cancelled) throw new InstallationCancelled();
636
+ if (resolveResult.code !== 0) throw new InstallationFailure(conciseFailure("resolve", resolveResult.stderr), resolveResult.stderr);
637
+ targetVersion = parseResolvedVersion(resolveResult.stdout, parsed.target);
638
+ }
545
639
  progress.set(18, "Resolving packages", 24);
546
640
 
547
641
  const rootResult = await runner.npm(["root", "--global", "--loglevel=error", "--no-fund", "--no-audit"], { onChild: setActiveChild });
@@ -1,5 +1,139 @@
1
1
  /**
2
- * Generated by scripts/pi/sync-progress-palette.ts from @earendil-works/pi-coding-agent 1.1.0
3
- * (MIT), commit abe508e1b89912adde45528136c3221eb69acdd7. Do not edit directly.
2
+ * Generated by scripts/pi/sync-progress-palette.ts from A1's named accent projection
3
+ * over pinned Pi 1.4.0 (MIT), commit 4a080252f06caf63a525d7873c5d94d3c23887e0. Do not edit directly.
4
4
  */
5
- export const SHARED_PROGRESS_ACCENT_ANSI = "\u001b[38;2;167;152;215m";
5
+ export const SHARED_PROGRESS_PALETTE = Object.freeze({
6
+ "blue": {
7
+ "dark": {
8
+ "truecolor": {
9
+ "accent": "\u001b[38;2;130;160;241m",
10
+ "border": "\u001b[38;2;116;102;197m"
11
+ },
12
+ "256color": {
13
+ "accent": "\u001b[38;5;111m",
14
+ "border": "\u001b[38;5;98m"
15
+ }
16
+ },
17
+ "light": {
18
+ "truecolor": {
19
+ "accent": "\u001b[38;2;63;96;226m",
20
+ "border": "\u001b[38;2;83;67;154m"
21
+ },
22
+ "256color": {
23
+ "accent": "\u001b[38;5;62m",
24
+ "border": "\u001b[38;5;60m"
25
+ }
26
+ }
27
+ },
28
+ "purple": {
29
+ "dark": {
30
+ "truecolor": {
31
+ "accent": "\u001b[38;2;167;152;215m",
32
+ "border": "\u001b[38;2;105;115;167m"
33
+ },
34
+ "256color": {
35
+ "accent": "\u001b[38;5;140m",
36
+ "border": "\u001b[38;5;61m"
37
+ }
38
+ },
39
+ "light": {
40
+ "truecolor": {
41
+ "accent": "\u001b[38;2;116;89;180m",
42
+ "border": "\u001b[38;2;68;77;130m"
43
+ },
44
+ "256color": {
45
+ "accent": "\u001b[38;5;97m",
46
+ "border": "\u001b[38;5;60m"
47
+ }
48
+ }
49
+ },
50
+ "cyan": {
51
+ "dark": {
52
+ "truecolor": {
53
+ "accent": "\u001b[38;2;93;179;186m",
54
+ "border": "\u001b[38;2;74;128;146m"
55
+ },
56
+ "256color": {
57
+ "accent": "\u001b[38;5;73m",
58
+ "border": "\u001b[38;5;66m"
59
+ }
60
+ },
61
+ "light": {
62
+ "truecolor": {
63
+ "accent": "\u001b[38;2;40;122;129m",
64
+ "border": "\u001b[38;2;40;89;106m"
65
+ },
66
+ "256color": {
67
+ "accent": "\u001b[38;5;30m",
68
+ "border": "\u001b[38;5;23m"
69
+ }
70
+ }
71
+ },
72
+ "green": {
73
+ "dark": {
74
+ "truecolor": {
75
+ "accent": "\u001b[38;2;104;183;141m",
76
+ "border": "\u001b[38;2;75;132;120m"
77
+ },
78
+ "256color": {
79
+ "accent": "\u001b[38;5;72m",
80
+ "border": "\u001b[38;5;66m"
81
+ }
82
+ },
83
+ "light": {
84
+ "truecolor": {
85
+ "accent": "\u001b[38;2;51;126;88m",
86
+ "border": "\u001b[38;2;41;93;82m"
87
+ },
88
+ "256color": {
89
+ "accent": "\u001b[38;5;65m",
90
+ "border": "\u001b[38;5;23m"
91
+ }
92
+ }
93
+ },
94
+ "orange": {
95
+ "dark": {
96
+ "truecolor": {
97
+ "accent": "\u001b[38;2;231;135;83m",
98
+ "border": "\u001b[38;2;178;89;79m"
99
+ },
100
+ "256color": {
101
+ "accent": "\u001b[38;5;173m",
102
+ "border": "\u001b[38;5;131m"
103
+ }
104
+ },
105
+ "light": {
106
+ "truecolor": {
107
+ "accent": "\u001b[38;2;174;80;16m",
108
+ "border": "\u001b[38;2;133;57;49m"
109
+ },
110
+ "256color": {
111
+ "accent": "\u001b[38;5;130m",
112
+ "border": "\u001b[38;5;95m"
113
+ }
114
+ }
115
+ },
116
+ "pink": {
117
+ "dark": {
118
+ "truecolor": {
119
+ "accent": "\u001b[38;2;223;122;199m",
120
+ "border": "\u001b[38;2;150;91;171m"
121
+ },
122
+ "256color": {
123
+ "accent": "\u001b[38;5;176m",
124
+ "border": "\u001b[38;5;97m"
125
+ }
126
+ },
127
+ "light": {
128
+ "truecolor": {
129
+ "accent": "\u001b[38;2;178;56;153m",
130
+ "border": "\u001b[38;2;114;61;131m"
131
+ },
132
+ "256color": {
133
+ "accent": "\u001b[38;5;132m",
134
+ "border": "\u001b[38;5;60m"
135
+ }
136
+ }
137
+ }
138
+ });
139
+ export const SHARED_PROGRESS_ACCENT_ANSI = SHARED_PROGRESS_PALETTE.blue.dark.truecolor.accent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1-install",
3
- "version": "0.2.6",
3
+ "version": "0.2.7-dev.1222",
4
4
  "description": "Dependency-free installer for a1",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,5 +23,9 @@
23
23
  "url": "https://github.com/timurproko/a1"
24
24
  },
25
25
  "author": "Timur Prokopiev",
26
- "license": "MIT"
26
+ "license": "MIT",
27
+ "publication": {
28
+ "source": "0ec0a4fd915c5e452ca135159dcb58e810dea96f",
29
+ "pullRequest": 847
30
+ }
27
31
  }