@git.zone/cli 6.10.0 → 6.12.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.
@@ -4,16 +4,15 @@ import {
4
4
  type IReleaseArtifact,
5
5
  } from "./classes.releasejournal.js";
6
6
  import { assertCredentialFreeGitPushDestination } from "./helpers.releasebranch.js";
7
-
8
- const supportedPnpmVersions = [
9
- "11.25.0",
10
- "12.1.0",
11
- "12.2.1",
12
- "12.3.0",
13
- "12.3.4",
14
- ] as const;
15
- const pnpmHelpRequirements = {
16
- "11.25.0": {
7
+ import {
8
+ getDefaultMinimumReleaseAgeMinutes,
9
+ packageSelectorMatches,
10
+ } from "../mod_tools/classes.packagemanager.js";
11
+
12
+ const stablePnpmVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
13
+ // Qualify the command contract, independently of the executable's version.
14
+ const pnpmHelpRequirements = [
15
+ {
17
16
  pack: ["--out <path>", "--json"],
18
17
  publish: [
19
18
  "publish [<tarball>|<dir>]",
@@ -24,33 +23,7 @@ const pnpmHelpRequirements = {
24
23
  "--access <public|restricted>",
25
24
  ],
26
25
  },
27
- "12.1.0": {
28
- pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
29
- publish: [
30
- "Usage: pnpm publish [OPTIONS] [PACKAGE]",
31
- "Tarball or directory to publish",
32
- "--no-git-checks",
33
- "--ignore-scripts",
34
- "--json",
35
- "--tag <TAG>",
36
- "--access <ACCESS>",
37
- "[possible values: public, restricted]",
38
- ],
39
- },
40
- "12.2.1": {
41
- pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
42
- publish: [
43
- "Usage: pnpm publish [OPTIONS] [PACKAGE]",
44
- "Tarball or directory to publish",
45
- "--no-git-checks",
46
- "--ignore-scripts",
47
- "--json",
48
- "--tag <TAG>",
49
- "--access <ACCESS>",
50
- "[possible values: public, restricted]",
51
- ],
52
- },
53
- "12.3.0": {
26
+ {
54
27
  pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
55
28
  publish: [
56
29
  "Usage: pnpm publish [OPTIONS] [PACKAGE]",
@@ -63,20 +36,7 @@ const pnpmHelpRequirements = {
63
36
  "[possible values: public, restricted]",
64
37
  ],
65
38
  },
66
- "12.3.4": {
67
- pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
68
- publish: [
69
- "Usage: pnpm publish [OPTIONS] [PACKAGE]",
70
- "Tarball or directory to publish",
71
- "--no-git-checks",
72
- "--ignore-scripts",
73
- "--json",
74
- "--tag <TAG>",
75
- "--access <ACCESS>",
76
- "[possible values: public, restricted]",
77
- ],
78
- },
79
- } as const;
39
+ ] as const;
80
40
  const maximumMetadataResponseBytes = 8 * 1024 * 1024;
81
41
  const capabilityCommandTimeoutMs = 30_000;
82
42
  const packCommandTimeoutMs = 10 * 60_000;
@@ -91,17 +51,14 @@ export function assertNpmPackageName(value: unknown): asserts value is string {
91
51
  }
92
52
 
93
53
  export interface IPnpmReleaseCapability {
94
- readonly version: (typeof supportedPnpmVersions)[number];
54
+ readonly version: string;
95
55
  }
96
56
 
97
57
  export const buildQualifiedPnpmArgs = (
98
58
  capabilityArg: IPnpmReleaseCapability | undefined,
99
59
  argsArg: readonly string[],
100
60
  ): string[] => {
101
- if (
102
- !capabilityArg ||
103
- !supportedPnpmVersions.includes(capabilityArg.version)
104
- ) {
61
+ if (!capabilityArg || !stablePnpmVersion.test(capabilityArg.version)) {
105
62
  throw new Error(
106
63
  "npm release commands require a qualified pnpm invocation.",
107
64
  );
@@ -165,15 +122,140 @@ const readPackageIdentity = async (
165
122
 
166
123
  const requireHelpFragments = (
167
124
  outputArg: string,
168
- fragmentsArg: readonly string[],
169
- commandArg: string,
125
+ commandArg: "pack" | "publish",
170
126
  ): void => {
171
- const missing = fragmentsArg.filter(
172
- (fragmentArg) => !outputArg.includes(fragmentArg),
127
+ const missing = pnpmHelpRequirements.map((requirements) =>
128
+ requirements[commandArg].filter(
129
+ (fragment) => !outputArg.includes(fragment),
130
+ ),
131
+ );
132
+ if (missing.every((fragments) => fragments.length > 0)) {
133
+ throw new Error(
134
+ `pnpm ${commandArg} is missing required exact-release capabilities: ${missing.map((fragments) => fragments.join(", ")).join("; or ")}.`,
135
+ );
136
+ }
137
+ };
138
+
139
+ const readPnpmJson = async (
140
+ shellArg: plugins.smartshell.Smartshell,
141
+ cwdArg: string,
142
+ capabilityArg: IPnpmReleaseCapability,
143
+ argsArg: string[],
144
+ labelArg: string,
145
+ ): Promise<unknown> => {
146
+ const result = await shellArg.execSpawn(
147
+ "pnpm",
148
+ buildQualifiedPnpmArgs(capabilityArg, [...argsArg, "--json"]),
149
+ {
150
+ cwd: cwdArg,
151
+ silent: true,
152
+ timeout: capabilityCommandTimeoutMs,
153
+ timeoutKillGraceMs: 5_000,
154
+ maxBuffer: maximumMetadataResponseBytes,
155
+ },
156
+ );
157
+ if (result.exitCode !== 0 || result.timedOut) {
158
+ throw new Error(`Unable to inspect pnpm ${labelArg}.`);
159
+ }
160
+ if (result.stdout.trim() === "undefined") return undefined;
161
+ try {
162
+ return JSON.parse(result.stdout);
163
+ } catch {
164
+ throw new Error(`Invalid pnpm ${labelArg} response.`);
165
+ }
166
+ };
167
+
168
+ const assertPnpmReleaseAge = async (
169
+ shellArg: plugins.smartshell.Smartshell,
170
+ cwdArg: string,
171
+ capabilityArg: IPnpmReleaseCapability,
172
+ ): Promise<void> => {
173
+ const configuredAge = await readPnpmJson(
174
+ shellArg,
175
+ cwdArg,
176
+ capabilityArg,
177
+ ["config", "get", "minimum-release-age"],
178
+ "minimum release age",
179
+ );
180
+ const age =
181
+ configuredAge === undefined || configuredAge === null
182
+ ? getDefaultMinimumReleaseAgeMinutes(capabilityArg.version)
183
+ : configuredAge;
184
+ if (
185
+ typeof age !== "number" ||
186
+ !Number.isSafeInteger(age) ||
187
+ age < 0 ||
188
+ age > Number.MAX_SAFE_INTEGER / 60_000
189
+ ) {
190
+ throw new Error(
191
+ "pnpm minimum-release-age must be a nonnegative integer number of minutes.",
192
+ );
193
+ }
194
+ if (age === 0) return;
195
+ const configuredExclusions = await readPnpmJson(
196
+ shellArg,
197
+ cwdArg,
198
+ capabilityArg,
199
+ ["config", "get", "minimum-release-age-exclude"],
200
+ "release-age exclusions",
173
201
  );
174
- if (missing.length > 0) {
202
+ const exclusions =
203
+ configuredExclusions === undefined || configuredExclusions === null
204
+ ? []
205
+ : configuredExclusions;
206
+ if (
207
+ !Array.isArray(exclusions) ||
208
+ exclusions.some((value) => typeof value !== "string" || !value.trim())
209
+ ) {
175
210
  throw new Error(
176
- `pnpm ${commandArg} is missing required exact-release capabilities: ${missing.join(", ")}.`,
211
+ "pnpm minimum-release-age-exclude must be an array of package selectors.",
212
+ );
213
+ }
214
+ if (
215
+ exclusions.some((selector) =>
216
+ packageSelectorMatches("pnpm", capabilityArg.version, selector),
217
+ )
218
+ )
219
+ return;
220
+
221
+ const times = await readPnpmJson(
222
+ shellArg,
223
+ cwdArg,
224
+ capabilityArg,
225
+ ["view", `pnpm@${capabilityArg.version}`, "time"],
226
+ "publication times",
227
+ );
228
+ if (!times || typeof times !== "object" || Array.isArray(times)) {
229
+ throw new Error("pnpm publication times must be an object.");
230
+ }
231
+ const publishedAt = Object.hasOwn(times, capabilityArg.version)
232
+ ? (times as Record<string, unknown>)[capabilityArg.version]
233
+ : undefined;
234
+ if (publishedAt === undefined) {
235
+ const ignoreMissingTime = await readPnpmJson(
236
+ shellArg,
237
+ cwdArg,
238
+ capabilityArg,
239
+ ["config", "get", "minimum-release-age-ignore-missing-time"],
240
+ "missing publication time policy",
241
+ );
242
+ if (
243
+ ignoreMissingTime === true ||
244
+ ignoreMissingTime === undefined ||
245
+ ignoreMissingTime === null
246
+ )
247
+ return;
248
+ throw new Error(
249
+ `The registry has no publication time for pnpm ${capabilityArg.version}; its minimum release age cannot be verified.`,
250
+ );
251
+ }
252
+ const timestamp =
253
+ typeof publishedAt === "string" ? Date.parse(publishedAt) : NaN;
254
+ if (!Number.isFinite(timestamp))
255
+ throw new Error("Invalid pnpm publication timestamp.");
256
+ if (timestamp > Date.now() - age * 60_000) {
257
+ throw new Error(
258
+ `pnpm ${capabilityArg.version} does not yet satisfy minimum-release-age (${age} minutes).`,
177
259
  );
178
260
  }
179
261
  };
@@ -189,16 +271,17 @@ export const assertPnpmReleaseCapability = async (
189
271
  timeoutKillGraceMs: 5_000,
190
272
  });
191
273
  const version = versionResult.stdout.trim();
192
- const supportedVersion = supportedPnpmVersions.find(
193
- (candidateArg) => candidateArg === version,
194
- );
195
- if (versionResult.exitCode !== 0 || !supportedVersion) {
274
+ if (
275
+ versionResult.exitCode !== 0 ||
276
+ versionResult.timedOut ||
277
+ !stablePnpmVersion.test(version)
278
+ ) {
196
279
  throw new Error(
197
- `Exact release packaging requires a verified pnpm version (${supportedPnpmVersions.join(", ")}); resolved ${version || "unknown"}.`,
280
+ "Exact release packaging requires a canonical stable pnpm version.",
198
281
  );
199
282
  }
200
- const requirements = pnpmHelpRequirements[supportedVersion];
201
- const capability = Object.freeze({ version: supportedVersion });
283
+ const capability = Object.freeze({ version });
284
+ await assertPnpmReleaseAge(smartshellArg, cwdArg, capability);
202
285
 
203
286
  const packHelp = await smartshellArg.execSpawn(
204
287
  "pnpm",
@@ -210,12 +293,12 @@ export const assertPnpmReleaseCapability = async (
210
293
  timeoutKillGraceMs: 5_000,
211
294
  },
212
295
  );
213
- if (packHelp.exitCode !== 0) {
296
+ if (packHelp.exitCode !== 0 || packHelp.timedOut) {
214
297
  throw new Error(
215
298
  "Unable to inspect the selected pnpm pack capabilities. Exact release commands require standalone pnpm with support; Corepack launchers are unsupported.",
216
299
  );
217
300
  }
218
- requireHelpFragments(packHelp.combinedOutput, requirements.pack, "pack");
301
+ requireHelpFragments(packHelp.combinedOutput, "pack");
219
302
 
220
303
  const publishHelp = await smartshellArg.execSpawn(
221
304
  "pnpm",
@@ -227,14 +310,10 @@ export const assertPnpmReleaseCapability = async (
227
310
  timeoutKillGraceMs: 5_000,
228
311
  },
229
312
  );
230
- if (publishHelp.exitCode !== 0) {
313
+ if (publishHelp.exitCode !== 0 || publishHelp.timedOut) {
231
314
  throw new Error("Unable to inspect pnpm publish capabilities.");
232
315
  }
233
- requireHelpFragments(
234
- publishHelp.combinedOutput,
235
- requirements.publish,
236
- "publish",
237
- );
316
+ requireHelpFragments(publishHelp.combinedOutput, "publish");
238
317
  return capability;
239
318
  };
240
319
 
@@ -17,7 +17,7 @@ const commandSummaries: ICommandHelpSummary[] = [
17
17
  {
18
18
  name: "commit",
19
19
  description:
20
- "Analyze changes and create semantic source commits",
20
+ "Create semantic source commits from AI or manual input",
21
21
  },
22
22
  { name: "release", description: "Create versioned releases from pending changelog entries" },
23
23
  { name: "format", description: "Plan or apply project formatting changes" },
@@ -992,7 +992,7 @@ function getLatestVersionFromMetadata(metadata: any): string | null {
992
992
  return typeof latest === "string" && latest.length > 0 ? latest : null;
993
993
  }
994
994
 
995
- function getDefaultMinimumReleaseAgeMinutes(currentPnpmVersion: string): number {
995
+ export function getDefaultMinimumReleaseAgeMinutes(currentPnpmVersion: string): number {
996
996
  const majorVersion = normalizeSemver(currentPnpmVersion)[0] || 0;
997
997
  return majorVersion >= 11 ? 1440 : 0;
998
998
  }
@@ -1086,7 +1086,7 @@ function normalizePnpmConfigListItem(rawItem: string): string {
1086
1086
  .trim();
1087
1087
  }
1088
1088
 
1089
- function packageSelectorMatches(
1089
+ export function packageSelectorMatches(
1090
1090
  packageName: string,
1091
1091
  version: string,
1092
1092
  pattern: string,