@timurproko/a1 0.1.8-dev.820b4a4 → 0.1.8-dev.99e912e
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 +10 -3
- package/bin/cli.js +2 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/src/cli/dispatch.d.ts +2 -1
- package/dist/src/cli/dispatch.js +22 -3
- package/dist/src/foundation/release/index.d.ts +0 -1
- package/dist/src/foundation/release/index.js +0 -1
- package/dist/src/foundation/release/update.d.ts +2 -0
- package/dist/src/foundation/release/update.js +69 -10
- package/docs/ci-release-runbook.md +4 -2
- package/package.json +1 -1
- package/dist/src/foundation/release/development-preview-release.d.ts +0 -49
- package/dist/src/foundation/release/development-preview-release.js +0 -114
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
|
|
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
|
|
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-
|
|
8
|
+
"builtAt": "2026-08-24T07:36:29.445Z",
|
|
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-
|
|
8
|
+
"builtAt": "2026-08-24T07:36:31.707Z",
|
|
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-
|
|
8
|
+
"builtAt": "2026-08-24T07:36:58.529Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "c8491147088aa02f51443ecef1aa9f9e36914f9c899e221668bb3a36d9140941",
|
|
12
12
|
"size": 172544
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -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;
|
package/dist/src/cli/dispatch.js
CHANGED
|
@@ -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
|
|
40
|
-
return
|
|
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;
|
|
@@ -221,6 +221,64 @@ function createUpdateProgress(output, enabled) {
|
|
|
221
221
|
},
|
|
222
222
|
};
|
|
223
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
|
+
}
|
|
224
282
|
export async function runSelfUpdate(options) {
|
|
225
283
|
const fileSystem = options.fileSystem ?? defaultFileSystem;
|
|
226
284
|
const output = options.output ?? defaultOutput;
|
|
@@ -249,15 +307,16 @@ export async function runSelfUpdate(options) {
|
|
|
249
307
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`could not read its running package version: ${errorMessage(error)}`)}\n`);
|
|
250
308
|
return 1;
|
|
251
309
|
}
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
// No full stop after a version: it already ends in a dot-separated identifier,
|
|
318
|
+
// and a trailing one reads as part of the version rather than as punctuation.
|
|
319
|
+
output.stdout(`${PRODUCT_TEXT.commandName} update (${UPDATE_CHANNEL_LABELS[channel]}): ${runningVersion} → ${targetVersion}\n`);
|
|
261
320
|
const progress = createUpdateProgress(output, options.progress ?? (options.output === undefined && process.stdout.isTTY === true));
|
|
262
321
|
const rootLookup = await measure("global-root", async () => await runNpm(runner, ["root", "--global"], true, output, "resolve npm's global package root"));
|
|
263
322
|
if (rootLookup.result === null)
|
|
@@ -346,7 +405,7 @@ export async function runSelfUpdate(options) {
|
|
|
346
405
|
await transactionStore.clearCompleted();
|
|
347
406
|
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
348
407
|
progress.finish();
|
|
349
|
-
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}
|
|
408
|
+
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}\n`);
|
|
350
409
|
return 0;
|
|
351
410
|
}
|
|
352
411
|
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.
|
|
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
|
|
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,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
|
-
}
|