@timurproko/a1 0.1.7 → 0.1.8-dev.3b54f21

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/README.md CHANGED
@@ -12,7 +12,7 @@ npm install --global @timurproko/a1@latest
12
12
  a1 # A1-owned UI and profile: ~/.a1/agent
13
13
  a1 version # show Installed, Release (latest), and Next versions
14
14
  a1 update # update to npm latest
15
- a1 update:next # update to npm next
15
+ a1 update:next # update to npm next (or a1 update:<commit> for a specific preview)
16
16
  ```
17
17
 
18
18
  Prerelease builds — what `a1 update:next` installs — add two development profiles
@@ -68,9 +68,16 @@ time and never committed, so previews cost no commits and need no command, and a
68
68
  installed preview says exactly which source produced it.
69
69
 
70
70
  ```sh
71
- a1 update:next # install the newest preview
71
+ a1 update:next # install the newest preview
72
+ a1 update:7eabe9e # install the preview built from that commit
72
73
  ```
73
74
 
75
+ Naming a commit is what the version suffix is for: read it off `a1 version`, a
76
+ pull request, or a commit list, and install exactly that build — you never need to
77
+ know which version it went out under. A commit that was never published is refused
78
+ rather than guessed at. A full preview version works in the same place, so a string
79
+ pasted back from `a1 version` is understood too.
80
+
74
81
  ### Stable — npm `latest`
75
82
 
76
83
  One command, from a clean `develop` that matches its remote:
@@ -86,7 +93,7 @@ It lands the version on `develop` through a pull request that merges itself.
86
93
  **Landing it is what publishes** — the workflow builds, validates the packed release
87
94
  on Windows, Linux, and macOS, publishes to npm `latest` with provenance, and only
88
95
  then writes the `v<version>` tag and records the GitHub Release. The command waits
89
- for that to succeed, then lands the next `-dev.0` version so previews resume
96
+ for that to succeed, then opens the next `-dev` line so previews resume
90
97
  immediately.
91
98
 
92
99
  ```sh
package/bin/cli.js CHANGED
@@ -22,9 +22,9 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
22
22
  const { runVersionStats } = await import("../dist/src/cli/index.js");
23
23
  return await runVersionStats({ packageRoot: fileURLToPath(packageRoot) });
24
24
  },
25
- update: async channel => {
25
+ update: async (channel, target) => {
26
26
  const { runSelfUpdate } = await import("../dist/src/foundation/release/index.js");
27
- return await runSelfUpdate({ packageRoot: fileURLToPath(packageRoot), channel });
27
+ return await runSelfUpdate({ packageRoot: fileURLToPath(packageRoot), channel, ...(target === undefined ? {} : { target }) });
28
28
  },
29
29
  packages: async request => {
30
30
  const [{ runPackageCommand }, { createPiPackagesPort }] = await Promise.all([
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-08-24T05:41:42.321Z",
8
+ "builtAt": "2026-08-24T07:13:11.159Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-24T05:41:49.189Z",
8
+ "builtAt": "2026-08-24T07:13:11.090Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "29e22fe29de2828982bc67ef418c4adcaab1490281477b7bea592ec6fe621bcd",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-24T05:42:24.069Z",
8
+ "builtAt": "2026-08-24T07:13:46.176Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "c0b203cd361d080f7e5a2e108d7c090a5ef94ab466631e1e01b6af93cd7ab226",
11
+ "sha256": "e9d61d614aa418c84bff93abb65342f0c05c1a670b77e22a88d01d2d61eba9b7",
12
12
  "size": 172544
13
13
  },
14
14
  "provenance": {
@@ -5,7 +5,7 @@ export type UpdateChannel = "stable" | "next";
5
5
  export interface CliHandlers {
6
6
  readonly launch: (intent: InteractiveLaunchIntent) => Promise<number>;
7
7
  readonly version: () => Promise<number>;
8
- readonly update: (channel: UpdateChannel) => Promise<number>;
8
+ readonly update: (channel: UpdateChannel, target?: string) => Promise<number>;
9
9
  readonly packages: (request: PackageCommandRequest) => Promise<number>;
10
10
  }
11
11
  export interface CliOutput {
@@ -21,6 +21,7 @@ export type CliCommand = {
21
21
  } | {
22
22
  readonly kind: "update";
23
23
  readonly channel: UpdateChannel;
24
+ readonly target?: string;
24
25
  } | {
25
26
  readonly kind: "packages";
26
27
  readonly request: PackageCommandRequest;
@@ -7,6 +7,7 @@ export function cliUsage(capabilities) {
7
7
  "version",
8
8
  "update [self|<source>|--extensions|--models]",
9
9
  "update:next",
10
+ "update:<commit>",
10
11
  "install <source>",
11
12
  "remove <source>",
12
13
  "list",
@@ -25,7 +26,7 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
25
26
  return await handlers.version();
26
27
  if (command.kind === "packages")
27
28
  return await handlers.packages(command.request);
28
- return await handlers.update(command.channel);
29
+ return await handlers.update(command.channel, command.target);
29
30
  }
30
31
  export function parseCliCommand(arguments_, capabilities) {
31
32
  if (arguments_.length === 0)
@@ -36,8 +37,8 @@ export function parseCliCommand(arguments_, capabilities) {
36
37
  }
37
38
  if (command === "version")
38
39
  return withoutArguments(rest, { kind: "version" });
39
- if (command === "update:next")
40
- return withoutArguments(rest, { kind: "update", channel: "next" });
40
+ if (command !== undefined && command.startsWith("update:"))
41
+ return parseColonUpdate(command.slice("update:".length), rest);
41
42
  if (command === "update")
42
43
  return parseUpdate(rest);
43
44
  if (command === "install" || command === "remove" || command === "uninstall") {
@@ -51,6 +52,24 @@ export function parseCliCommand(arguments_, capabilities) {
51
52
  return { kind: "error", message: `Bare ${PRODUCT_TEXT.commandName} is the ${PRODUCT_TEXT.displayName} agent experience; there is no agent subcommand.` };
52
53
  return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unknown command: ${command ?? ""}`) };
53
54
  }
55
+ /**
56
+ * What follows the colon says which build to move to. `next` is the newest
57
+ * preview; anything else names one outright, by the commit it was built from or by
58
+ * its full version — a preview is published as `<version>-dev.<commit>`, so the
59
+ * commit alone is enough to find it.
60
+ */
61
+ function parseColonUpdate(suffix, rest) {
62
+ if (rest.length > 0)
63
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("update takes what to move to after the colon, and nothing else.") };
64
+ if (suffix === "next")
65
+ return { kind: "update", channel: "next" };
66
+ if (suffix.length === 0)
67
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`update: needs a preview after the colon, as in ${PRODUCT_TEXT.commandName} update:next.`) };
68
+ if (!/^[0-9a-z][0-9a-z.+-]*$/i.test(suffix)) {
69
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic(`received an unusable preview: ${suffix}`) };
70
+ }
71
+ return { kind: "update", channel: "next", target: suffix };
72
+ }
54
73
  /**
55
74
  * `update` carries both meanings pinned Pi gives it: itself by default, and the
56
75
  * profile's packages when a target says so. Pi is refused as a target because A1
@@ -1,7 +1,6 @@
1
1
  export * from "./bootstrap.js";
2
2
  export * from "./cohort-selection.js";
3
3
  export * from "./cohort-state.js";
4
- export * from "./development-preview-release.js";
5
4
  export * from "./process-cleanup.js";
6
5
  export * from "./release.js";
7
6
  export * from "./release-gc.js";
@@ -1,7 +1,6 @@
1
1
  export * from "./bootstrap.js";
2
2
  export * from "./cohort-selection.js";
3
3
  export * from "./cohort-state.js";
4
- export * from "./development-preview-release.js";
5
4
  export * from "./process-cleanup.js";
6
5
  export * from "./release.js";
7
6
  export * from "./release-gc.js";
@@ -33,6 +33,8 @@ export interface UpdatePerformanceEvidence {
33
33
  export interface SelfUpdateOptions {
34
34
  packageRoot: string;
35
35
  channel?: UpdateChannel;
36
+ /** A specific preview to install, named by its commit or its full version. */
37
+ target?: string;
36
38
  environment?: NodeJS.ProcessEnv;
37
39
  fileSystem?: UpdateFileSystem;
38
40
  output?: UpdateOutput;
@@ -14,6 +14,13 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
14
14
  import { UpdateTransactionStore } from "./update-transaction.js";
15
15
  export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
16
16
  const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
17
+ /**
18
+ * What each channel is called when A1 says it out loud. The internal name stays
19
+ * `stable` because that is what the channel is, but what a reader is moving to is
20
+ * a release, and that is the word the repository, its tags, and its GitHub
21
+ * releases all use.
22
+ */
23
+ const UPDATE_CHANNEL_LABELS = { stable: "release", next: "next" };
17
24
  const defaultFileSystem = {
18
25
  async readFile(path) { return await readFile(path, "utf8"); },
19
26
  realpath,
@@ -192,12 +199,17 @@ function createUpdateProgress(output, enabled) {
192
199
  }, PROGRESS_TICK_MS);
193
200
  timer.unref?.();
194
201
  },
202
+ // The bar exists to say the update is still moving. Once it has finished
203
+ // there is a better line to occupy that row — the one naming what is now
204
+ // installed — so the bar gives the row back rather than leaving a full
205
+ // meter above a message that already implies it.
195
206
  finish() {
196
207
  stopCreep();
197
208
  if (!visible)
198
209
  return;
199
- output.stdout(`\r${renderProgressBar(100)}\n`);
210
+ output.stdout(`\r${" ".repeat(PROGRESS_BAR_WIDTH + 6)}\r`);
200
211
  visible = false;
212
+ shown = -1;
201
213
  },
202
214
  clear() {
203
215
  stopCreep();
@@ -209,6 +221,64 @@ function createUpdateProgress(output, enabled) {
209
221
  },
210
222
  };
211
223
  }
224
+ /** The newest version the channel points at, which is what an unqualified update takes. */
225
+ async function resolveChannelHead(runner, distTag, output) {
226
+ const lookup = await runNpm(runner, ["view", `${PRODUCT_PACKAGE}@${distTag}`, "version"], true, output, `query the npm ${distTag} channel`);
227
+ if (lookup.result === null)
228
+ return { version: null, exitCode: lookup.exitCode };
229
+ const version = validSemver(lookup.result.stdout.trim());
230
+ if (version === null) {
231
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed ${distTag} version from npm: ${JSON.stringify(lookup.result.stdout.trim())}.`)}\n`);
232
+ return { version: null, exitCode: 1 };
233
+ }
234
+ return { version, exitCode: 0 };
235
+ }
236
+ /**
237
+ * Resolve a preview the caller named.
238
+ *
239
+ * A preview is published as `<version>-dev.<commit>`, so its commit is enough to
240
+ * say which one is wanted — the version in front of it is not something anyone
241
+ * should have to remember. A full version is accepted too, for anyone reading one
242
+ * back from `a1 version` or a changelog.
243
+ *
244
+ * The published list is the authority: naming a commit that was never published,
245
+ * or one published more than once under different versions, is an error rather
246
+ * than a guess.
247
+ */
248
+ async function resolveRequestedPreview(runner, requested, output) {
249
+ const lookup = await runNpm(runner, ["view", PRODUCT_PACKAGE, "versions", "--json"], true, output, "list the published versions");
250
+ if (lookup.result === null)
251
+ return { version: null, exitCode: lookup.exitCode };
252
+ let published;
253
+ try {
254
+ published = JSON.parse(lookup.result.stdout.trim() || "[]");
255
+ }
256
+ catch {
257
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed version list from npm: ${JSON.stringify(lookup.result.stdout.trim())}.`)}\n`);
258
+ return { version: null, exitCode: 1 };
259
+ }
260
+ const versions = (Array.isArray(published) ? published : [published]).filter((value) => typeof value === "string");
261
+ const exact = versions.find(version => version === requested);
262
+ if (exact !== undefined) {
263
+ // Naming a release here would install it through the preview path, which is a
264
+ // different command with a different meaning. The commit form cannot express
265
+ // one, so only the fuller spelling of a preview reaches this.
266
+ if (!exact.includes("-dev.")) {
267
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`${exact} is a release, not a preview; run ${PRODUCT_TEXT.commandName} update to move to the current release.`)}\n`);
268
+ return { version: null, exitCode: 1 };
269
+ }
270
+ return { version: exact, exitCode: 0 };
271
+ }
272
+ const matches = versions.filter(version => version.endsWith(`-dev.${requested}`));
273
+ if (matches.length === 1)
274
+ return { version: matches[0], exitCode: 0 };
275
+ if (matches.length > 1) {
276
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`found more than one preview for ${requested}: ${matches.join(", ")}. Name the version instead.`)}\n`);
277
+ return { version: null, exitCode: 1 };
278
+ }
279
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`published no preview for ${requested}.`)}\n`);
280
+ return { version: null, exitCode: 1 };
281
+ }
212
282
  export async function runSelfUpdate(options) {
213
283
  const fileSystem = options.fileSystem ?? defaultFileSystem;
214
284
  const output = options.output ?? defaultOutput;
@@ -237,15 +307,14 @@ export async function runSelfUpdate(options) {
237
307
  output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
238
308
  return 1;
239
309
  }
240
- const targetLookup = await measure("target-resolution", async () => await runNpm(runner, ["view", `${PRODUCT_PACKAGE}@${distTag}`, "version"], true, output, `query the npm ${distTag} channel`));
241
- if (targetLookup.result === null)
242
- return targetLookup.exitCode;
243
- const targetVersion = validSemver(targetLookup.result.stdout.trim());
244
- if (targetVersion === null) {
245
- output.stderr(`${PRODUCT_TEXT.diagnostic(`received a malformed ${distTag} version from npm: ${JSON.stringify(targetLookup.result.stdout.trim())}.`)}\n`);
246
- return 1;
247
- }
248
- output.stdout(`${PRODUCT_TEXT.commandName} update (${channel}): ${runningVersion} → ${targetVersion}.\n`);
310
+ const requested = options.target?.trim();
311
+ const resolved = await measure("target-resolution", async () => requested === undefined || requested.length === 0
312
+ ? await resolveChannelHead(runner, distTag, output)
313
+ : await resolveRequestedPreview(runner, requested, output));
314
+ if (resolved.version === null)
315
+ return resolved.exitCode;
316
+ const targetVersion = resolved.version;
317
+ output.stdout(`${PRODUCT_TEXT.commandName} update (${UPDATE_CHANNEL_LABELS[channel]}): ${runningVersion} → ${targetVersion}.\n`);
249
318
  const progress = createUpdateProgress(output, options.progress ?? (options.output === undefined && process.stdout.isTTY === true));
250
319
  const rootLookup = await measure("global-root", async () => await runNpm(runner, ["root", "--global"], true, output, "resolve npm's global package root"));
251
320
  if (rootLookup.result === null)
@@ -334,7 +403,7 @@ export async function runSelfUpdate(options) {
334
403
  await transactionStore.clearCompleted();
335
404
  options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
336
405
  progress.finish();
337
- output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion} (${channel}).\n`);
406
+ output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}.\n`);
338
407
  return 0;
339
408
  }
340
409
  catch (error) {
@@ -37,7 +37,9 @@ The version is stamped at publish time — `<major.minor.patch>-dev.<short commi
37
37
  the base taken from whatever `package.json` declares and the suffix from the commit
38
38
  being published — and is never written back to the repository. An installed preview
39
39
  therefore names the exact source it came from, and rebuilding a commit produces the
40
- same version rather than a new one. `develop` therefore carries one open prerelease version between
40
+ same version rather than a new one. That suffix is also how a specific preview is
41
+ installed: `a1 update:<commit>` resolves it against the published list and
42
+ refuses a commit that was never published. `develop` therefore carries one open prerelease version between
41
43
  releases, and no commit is ever spent on a preview.
42
44
 
43
45
  One consequence worth knowing: a push that would republish an existing version
@@ -52,7 +54,7 @@ npm run release -- patch # or minor, major, or an exact x.y.z
52
54
  ```
53
55
 
54
56
  It lands `x.y.z` on `develop` through a pull request that merges itself, waits for
55
- that publication to succeed, and then lands `x.y.(z+1)-dev.0` so previews resume
57
+ that publication to succeed, and then lands `x.y.(z+1)-dev` so previews resume
56
58
  immediately. It publishes nothing itself and creates no tag.
57
59
 
58
60
  Landing the stable version is what publishes. The same pipeline sees a commit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.7",
3
+ "version": "0.1.8-dev.3b54f21",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",
@@ -1,49 +0,0 @@
1
- export declare const PREVIEW_RELEASE_SCHEMA: string;
2
- export interface DevelopmentPreviewCandidate {
3
- readonly version: string;
4
- readonly requiresVersionCommit: boolean;
5
- }
6
- export interface DevelopmentPreviewRegistryState {
7
- readonly published: boolean;
8
- readonly nextVersion: string | null;
9
- }
10
- export interface DevelopmentPreviewVerificationOptions {
11
- readonly attempts?: number;
12
- readonly delayMs?: number;
13
- readonly delay?: (milliseconds: number) => Promise<void>;
14
- }
15
- export interface DevelopmentPreviewPublishResult {
16
- readonly published: boolean;
17
- readonly recoveredPublishError: unknown | null;
18
- }
19
- export interface UncertifiedDevelopmentPreviewEvidenceInput {
20
- readonly packageName: string;
21
- readonly version: string;
22
- readonly commit: string;
23
- readonly tarball: string;
24
- readonly integrity: string;
25
- readonly shasum: string;
26
- readonly platform: NodeJS.Platform;
27
- readonly architecture: string;
28
- readonly recordedAt: string;
29
- }
30
- export interface UncertifiedDevelopmentPreviewEvidence extends UncertifiedDevelopmentPreviewEvidenceInput {
31
- readonly schema: typeof PREVIEW_RELEASE_SCHEMA;
32
- readonly channel: "next";
33
- readonly certificationStatus: "uncertified-development-preview";
34
- readonly terminalCapability: "owned-ui";
35
- readonly manualAcceptance: "accepted";
36
- readonly physicalHostCertification: "deferred";
37
- readonly crossPlatformCertification: "deferred";
38
- readonly stableReleaseEligible: false;
39
- }
40
- export declare function createUncertifiedDevelopmentPreviewEvidence(input: UncertifiedDevelopmentPreviewEvidenceInput): UncertifiedDevelopmentPreviewEvidence;
41
- export declare function requireManuallyAcceptedDevelopmentPreview(version: string, acceptedVersion: string): void;
42
- export declare function selectDevelopmentPreviewCandidate(currentVersion: string, publishedVersions: readonly string[]): DevelopmentPreviewCandidate;
43
- /**
44
- * Treats npm's process result as provisional: browser-auth completion can fail
45
- * after the immutable upload succeeds. Registry identity remains authoritative.
46
- */
47
- export declare function publishDevelopmentPreviewWithRecovery(publish: () => Promise<void>, verify: () => Promise<void>): Promise<DevelopmentPreviewPublishResult>;
48
- export declare function verifyDevelopmentPreviewRegistry(version: string, observe: () => Promise<DevelopmentPreviewRegistryState>, repairNextTag: () => Promise<void>, options?: DevelopmentPreviewVerificationOptions): Promise<void>;
49
- export declare function developmentPreviewTarballName(packageName: string, version: string): string;
@@ -1,114 +0,0 @@
1
- import { compare, inc, prerelease, valid } from "semver";
2
- import { PRODUCT_IDENTITY } from "../../product-identity.js";
3
- export const PREVIEW_RELEASE_SCHEMA = PRODUCT_IDENTITY.evidence.previewReleaseSchema;
4
- export function createUncertifiedDevelopmentPreviewEvidence(input) {
5
- const prereleaseParts = prerelease(input.version);
6
- if (valid(input.version) === null || prereleaseParts?.[0] !== "dev") {
7
- throw new Error(`uncertified preview requires a development prerelease: ${input.version}`);
8
- }
9
- return {
10
- schema: PREVIEW_RELEASE_SCHEMA,
11
- channel: "next",
12
- certificationStatus: "uncertified-development-preview",
13
- terminalCapability: "owned-ui",
14
- manualAcceptance: "accepted",
15
- physicalHostCertification: "deferred",
16
- crossPlatformCertification: "deferred",
17
- stableReleaseEligible: false,
18
- ...input,
19
- };
20
- }
21
- export function requireManuallyAcceptedDevelopmentPreview(version, acceptedVersion) {
22
- const acceptedPrerelease = prerelease(acceptedVersion);
23
- if (valid(acceptedVersion) === null || acceptedPrerelease?.[0] !== "dev") {
24
- throw new Error(`invalid manually accepted development preview: ${acceptedVersion}`);
25
- }
26
- if (version !== acceptedVersion) {
27
- throw new Error(`development preview ${version} has no exact manual acceptance; accepted version is ${acceptedVersion}`);
28
- }
29
- }
30
- export function selectDevelopmentPreviewCandidate(currentVersion, publishedVersions) {
31
- if (valid(currentVersion) === null)
32
- throw new Error(`invalid current package version: ${currentVersion}`);
33
- const published = publishedVersions.map(version => {
34
- if (valid(version) === null)
35
- throw new Error(`invalid published package version: ${version}`);
36
- return version;
37
- });
38
- const publishedSet = new Set(published);
39
- const highestPublished = published.reduce((highest, version) => highest === null || compare(version, highest) > 0 ? version : highest, null);
40
- const currentPrerelease = prerelease(currentVersion);
41
- const currentIsUnpublishedLeadingDev = currentPrerelease?.[0] === "dev"
42
- && !publishedSet.has(currentVersion)
43
- && (highestPublished === null || compare(currentVersion, highestPublished) > 0);
44
- if (currentIsUnpublishedLeadingDev)
45
- return { version: currentVersion, requiresVersionCommit: false };
46
- const base = highestPublished === null || compare(currentVersion, highestPublished) > 0
47
- ? currentVersion
48
- : highestPublished;
49
- const basePrerelease = prerelease(base);
50
- let candidate = basePrerelease?.[0] === "dev"
51
- ? inc(base, "prerelease", "dev")
52
- : inc(base, "prepatch", "dev");
53
- if (candidate === null)
54
- throw new Error(`could not increment development preview from ${base}`);
55
- while (publishedSet.has(candidate)) {
56
- candidate = inc(candidate, "prerelease", "dev");
57
- if (candidate === null)
58
- throw new Error(`could not increment development preview from ${base}`);
59
- }
60
- return { version: candidate, requiresVersionCommit: candidate !== currentVersion };
61
- }
62
- /**
63
- * Treats npm's process result as provisional: browser-auth completion can fail
64
- * after the immutable upload succeeds. Registry identity remains authoritative.
65
- */
66
- export async function publishDevelopmentPreviewWithRecovery(publish, verify) {
67
- let publishError = null;
68
- try {
69
- await publish();
70
- }
71
- catch (error) {
72
- publishError = error;
73
- }
74
- try {
75
- await verify();
76
- return { published: true, recoveredPublishError: publishError };
77
- }
78
- catch (verificationError) {
79
- if (publishError !== null)
80
- throw new AggregateError([publishError, verificationError], "npm publish failed and the exact version could not be verified in the registry");
81
- throw verificationError;
82
- }
83
- }
84
- export async function verifyDevelopmentPreviewRegistry(version, observe, repairNextTag, options = {}) {
85
- const attempts = options.attempts ?? 12;
86
- const delayMs = options.delayMs ?? 2_000;
87
- const delay = options.delay ?? (async (milliseconds) => await new Promise(resolvePromise => setTimeout(resolvePromise, milliseconds)));
88
- if (!Number.isInteger(attempts) || attempts < 1)
89
- throw new Error(`invalid registry verification attempts: ${attempts}`);
90
- let repaired = false;
91
- let last = { published: false, nextVersion: null };
92
- for (let attempt = 1; attempt <= attempts; attempt += 1) {
93
- last = await observe();
94
- if (last.published && last.nextVersion === version)
95
- return;
96
- if (last.published && !repaired) {
97
- await repairNextTag();
98
- repaired = true;
99
- }
100
- if (attempt < attempts)
101
- await delay(delayMs);
102
- }
103
- if (!last.published)
104
- throw new Error(`npm registry did not expose published version ${version} after ${attempts} attempts`);
105
- throw new Error(`npm next resolved ${last.nextVersion ?? "nothing"}; expected ${version} after ${attempts} attempts`);
106
- }
107
- export function developmentPreviewTarballName(packageName, version) {
108
- if (valid(version) === null)
109
- throw new Error(`invalid development preview version: ${version}`);
110
- const unscopedName = packageName.startsWith("@") ? packageName.slice(1).replace("/", "-") : packageName;
111
- if (!/^[a-z0-9._-]+$/i.test(unscopedName))
112
- throw new Error(`invalid package name: ${packageName}`);
113
- return `${unscopedName}-${version}.tgz`;
114
- }