package-registry-manager 0.25.0 → 0.26.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.
- package/README.md +5 -0
- package/package.json +3 -3
- package/src/audit-config.mjs +96 -0
- package/src/bootstrap-recovery.mjs +163 -0
- package/src/cpp-distribution.mjs +164 -0
- package/src/credential-cycle.mjs +8 -3
- package/src/credential-requirements.mjs +105 -0
- package/src/default-browser.mjs +1 -1
- package/src/default-scope.mjs +1 -1
- package/src/discovery.mjs +289 -24
- package/src/distribution-channel.mjs +87 -0
- package/src/exec.mjs +20 -0
- package/src/extra-flows.mjs +122 -2
- package/src/flows.mjs +19 -3
- package/src/generated-artifact.mjs +48 -0
- package/src/github.mjs +4 -0
- package/src/identity-conflicts.mjs +130 -0
- package/src/manifest-proposal.mjs +1 -1
- package/src/model.mjs +6 -0
- package/src/npm-release.mjs +68 -0
- package/src/nuget-ci.mjs +238 -0
- package/src/nuget-migration.mjs +275 -0
- package/src/nuget-page.mjs +100 -0
- package/src/nuget-policy.mjs +198 -0
- package/src/nuget-publish.py +145 -0
- package/src/nuget-setup.mjs +169 -0
- package/src/nuget-workflow.mjs +54 -0
- package/src/organization-scan.mjs +41 -2
- package/src/pack-review.mjs +122 -0
- package/src/package-coverage.mjs +33 -9
- package/src/plan.mjs +35 -9
- package/src/prerequisites.mjs +1 -1
- package/src/profile.mjs +1 -1
- package/src/publication-state.mjs +299 -0
- package/src/publishers.mjs +78 -12
- package/src/publishing-policy.mjs +7 -1
- package/src/publishing-state.mjs +74 -1
- package/src/python.mjs +1 -1
- package/src/registry-state.mjs +118 -3
- package/src/release-runs.mjs +3 -4
- package/src/setup-batch.mjs +48 -2
- package/src/setup.mjs +82 -120
- package/src/sign-in-import.mjs +132 -40
- package/src/skips.mjs +9 -1
- package/src/source-code.mjs +1 -1
- package/src/tokens.mjs +32 -4
- package/src/workflow-migration.mjs +15 -2
- package/src/workflow-proposal.mjs +6 -2
package/src/discovery.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { credentialRequirements } from "./credential-requirements.mjs";
|
|
2
|
+
import { auditConfig, applyAuditConfig } from "./audit-config.mjs";
|
|
3
|
+
import { cppDistribution } from "./cpp-distribution.mjs";
|
|
4
|
+
import { markIdentityConflicts } from "./identity-conflicts.mjs";
|
|
1
5
|
import { readFile, readdir, realpath } from "node:fs/promises";
|
|
2
6
|
import path from "node:path";
|
|
3
7
|
|
|
@@ -6,6 +10,7 @@ import { packageWorkflows, auditWrappers } from "./package-coverage.mjs";
|
|
|
6
10
|
import { REGISTRIES } from "./model.mjs";
|
|
7
11
|
import { inspectManifestRepositories } from "./repository-identity.mjs";
|
|
8
12
|
import {
|
|
13
|
+
RELEASE_EVIDENCE,
|
|
9
14
|
TRUSTED_REGISTRIES,
|
|
10
15
|
detectPublisher,
|
|
11
16
|
tokenSecretWarning,
|
|
@@ -32,6 +37,9 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
32
37
|
]);
|
|
33
38
|
|
|
34
39
|
const MANIFEST_NAMES = new Set([
|
|
40
|
+
"CMakeLists.txt",
|
|
41
|
+
"conanfile.py",
|
|
42
|
+
"vcpkg.json",
|
|
35
43
|
"package.json",
|
|
36
44
|
"Cargo.toml",
|
|
37
45
|
"pyproject.toml",
|
|
@@ -63,6 +71,7 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
63
71
|
);
|
|
64
72
|
const workflows = await readWorkflows(root);
|
|
65
73
|
const ignore = await readIgnoreList(root);
|
|
74
|
+
const config = await auditConfig(root);
|
|
66
75
|
const skipped = [];
|
|
67
76
|
const skip = (manifest, reason) => {
|
|
68
77
|
skipped.push({ manifest, reason });
|
|
@@ -77,7 +86,10 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
77
86
|
const directory = testDirectory(manifest);
|
|
78
87
|
return (
|
|
79
88
|
directory === null ||
|
|
80
|
-
|
|
89
|
+
config.publish?.includes(manifest) ||
|
|
90
|
+
(!["docs", "experiments", "case-studies", "dev/log"].includes(
|
|
91
|
+
directory,
|
|
92
|
+
) &&
|
|
81
93
|
publishable &&
|
|
82
94
|
referencedByWorkflow(workflows, manifest)) ||
|
|
83
95
|
skip(manifest, testDirectoryReason(directory))
|
|
@@ -89,7 +101,7 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
89
101
|
.filter(([, manifest]) => !ignored(manifest))
|
|
90
102
|
.map(async ([manifestPath, manifest]) => {
|
|
91
103
|
try {
|
|
92
|
-
return await parseManifest(manifestPath, manifest);
|
|
104
|
+
return await parseManifest(manifestPath, manifest, root);
|
|
93
105
|
} catch (error) {
|
|
94
106
|
if (testDirectory(manifest) === null) {
|
|
95
107
|
if (!options.onError) {
|
|
@@ -106,6 +118,7 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
106
118
|
}
|
|
107
119
|
}),
|
|
108
120
|
);
|
|
121
|
+
applyAuditConfig(parsed, config, workflows);
|
|
109
122
|
const coordinates = await githubCoordinates(
|
|
110
123
|
root,
|
|
111
124
|
preferredManifests.filter((manifest) => {
|
|
@@ -117,6 +130,56 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
117
130
|
for (const registry of TRUSTED_REGISTRIES) {
|
|
118
131
|
publishers.set(registry, await detectPublisher(root, workflows, registry));
|
|
119
132
|
}
|
|
133
|
+
// Generated names share source metadata, but are independent registry identities.
|
|
134
|
+
for (const item of [...parsed].filter(Boolean)) {
|
|
135
|
+
if (item.registry !== "npm") {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const data = JSON.parse(await readManifest(path.join(root, item.manifest)));
|
|
139
|
+
const aliases = data.publishAliases ?? [];
|
|
140
|
+
if (
|
|
141
|
+
!Array.isArray(aliases) ||
|
|
142
|
+
aliases.some(
|
|
143
|
+
(name) =>
|
|
144
|
+
typeof name !== "string" ||
|
|
145
|
+
!/^(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(name),
|
|
146
|
+
) ||
|
|
147
|
+
new Set([item.name, ...aliases]).size !== aliases.length + 1
|
|
148
|
+
) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`${item.manifest}: publishAliases must contain distinct valid npm names`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const generator =
|
|
154
|
+
Object.values(data.scripts ?? {}).flatMap((command) =>
|
|
155
|
+
[
|
|
156
|
+
...String(command).matchAll(
|
|
157
|
+
/(?:^|\s)(?:node|bun)\s+(?:\.\/)?([^\s]+prepare[^\s]*\.mjs)/g,
|
|
158
|
+
),
|
|
159
|
+
].map((match) => match[1]),
|
|
160
|
+
)[0] ?? null;
|
|
161
|
+
if (aliases.length) {
|
|
162
|
+
item.generated_artifact = {
|
|
163
|
+
source_manifest: item.manifest,
|
|
164
|
+
source_name: item.name,
|
|
165
|
+
generator,
|
|
166
|
+
names: [item.name, ...aliases],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
for (const name of aliases) {
|
|
170
|
+
parsed.push({
|
|
171
|
+
...item,
|
|
172
|
+
name,
|
|
173
|
+
generated_alias: {
|
|
174
|
+
source_manifest: item.manifest,
|
|
175
|
+
source_name: item.name,
|
|
176
|
+
generator,
|
|
177
|
+
names: [item.name, ...aliases],
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
applyAuditConfig(parsed, config, workflows);
|
|
120
183
|
// Extension packages can publish to both marketplaces independently.
|
|
121
184
|
for (const item of [...parsed].filter(Boolean)) {
|
|
122
185
|
if (item.registry !== "npm") {
|
|
@@ -137,10 +200,10 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
137
200
|
}
|
|
138
201
|
}
|
|
139
202
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
203
|
+
for (const registry of SCOPED_REGISTRIES) {
|
|
204
|
+
publishers.set(registry, await detectPublisher(root, workflows, registry));
|
|
205
|
+
}
|
|
206
|
+
canonicalComposer(parsed);
|
|
144
207
|
const containerFiles = dockerfiles
|
|
145
208
|
.map((item) => relativePath(root, item))
|
|
146
209
|
.filter((item) => !ignored(item) && kept(item, true));
|
|
@@ -169,11 +232,16 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
169
232
|
await Promise.all(
|
|
170
233
|
parsed
|
|
171
234
|
.filter(Boolean)
|
|
172
|
-
.filter((item) =>
|
|
235
|
+
.filter((item) =>
|
|
236
|
+
item.discovery_skip
|
|
237
|
+
? skip(item.manifest, item.discovery_skip)
|
|
238
|
+
: kept(item.manifest, item.publishable),
|
|
239
|
+
)
|
|
173
240
|
.map(async (item) => {
|
|
174
241
|
const scoped =
|
|
175
|
-
counts.get(item.registry) > 1 &&
|
|
176
|
-
|
|
242
|
+
(counts.get(item.registry) > 1 &&
|
|
243
|
+
TRUSTED_REGISTRIES.includes(item.registry)) ||
|
|
244
|
+
SCOPED_REGISTRIES.includes(item.registry)
|
|
177
245
|
? new Map([
|
|
178
246
|
[
|
|
179
247
|
item.registry,
|
|
@@ -185,10 +253,37 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
185
253
|
],
|
|
186
254
|
])
|
|
187
255
|
: publishers;
|
|
188
|
-
return withPublisher(
|
|
256
|
+
return withPublisher(
|
|
257
|
+
item,
|
|
258
|
+
workflows,
|
|
259
|
+
item.publisher_scope
|
|
260
|
+
? new Map([
|
|
261
|
+
[
|
|
262
|
+
item.registry,
|
|
263
|
+
await detectPublisher(
|
|
264
|
+
root,
|
|
265
|
+
packageWorkflows(workflows, {
|
|
266
|
+
...item,
|
|
267
|
+
manifest: item.publisher_scope,
|
|
268
|
+
}),
|
|
269
|
+
item.registry,
|
|
270
|
+
),
|
|
271
|
+
],
|
|
272
|
+
])
|
|
273
|
+
: scoped,
|
|
274
|
+
);
|
|
189
275
|
}),
|
|
190
276
|
)
|
|
191
277
|
)
|
|
278
|
+
.filter(
|
|
279
|
+
(item) =>
|
|
280
|
+
!internalWorkload(item) ||
|
|
281
|
+
config.publish?.includes(item.manifest) ||
|
|
282
|
+
skip(
|
|
283
|
+
item.manifest,
|
|
284
|
+
`internal ${internalWorkload(item)} workload: no workflow publishes it and no source decision lists it under "publish"`,
|
|
285
|
+
),
|
|
286
|
+
)
|
|
192
287
|
.concat(
|
|
193
288
|
containerPackages(containerFiles, workflows, coordinates),
|
|
194
289
|
chromePackages,
|
|
@@ -223,6 +318,7 @@ export async function inspectRepository(repository, options = {}) {
|
|
|
223
318
|
left.manifest.localeCompare(right.manifest),
|
|
224
319
|
);
|
|
225
320
|
}
|
|
321
|
+
markIdentityConflicts([inspection]);
|
|
226
322
|
return inspectManifestRepositories(inspection);
|
|
227
323
|
}
|
|
228
324
|
|
|
@@ -240,6 +336,47 @@ function releaseWorkflow(publishers) {
|
|
|
240
336
|
return null;
|
|
241
337
|
}
|
|
242
338
|
|
|
339
|
+
/** Registries whose publication evidence is scoped to one manifest. */
|
|
340
|
+
const SCOPED_REGISTRIES = ["maven-central", "packagist", "go-modules", "cmake"];
|
|
341
|
+
|
|
342
|
+
/** Benchmark/fixture manifests are internal unless a workflow releases them. */
|
|
343
|
+
function internalWorkload(item) {
|
|
344
|
+
if (item.workflow) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
const segment = item.manifest
|
|
348
|
+
.split("/")
|
|
349
|
+
.slice(0, -1)
|
|
350
|
+
.find((part) => /^(?:bench(?:mark)?(?:es|s)?|perf)$/i.test(part));
|
|
351
|
+
return segment ? "benchmark" : null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Packagist reads only the repository-root composer.json, so a same-name
|
|
356
|
+
* descriptor in a subdirectory is a development copy of that distribution.
|
|
357
|
+
* The root keeps the copy's declared version and the copy's publishing job.
|
|
358
|
+
*/
|
|
359
|
+
function canonicalComposer(parsed) {
|
|
360
|
+
const composer = parsed.filter(
|
|
361
|
+
(item) => item?.registry === "packagist" && !item.discovery_skip,
|
|
362
|
+
);
|
|
363
|
+
const root = composer.find((item) => item.manifest === "composer.json");
|
|
364
|
+
if (!root) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (const copy of composer.filter(
|
|
368
|
+
(item) => item !== root && item.name === root.name,
|
|
369
|
+
)) {
|
|
370
|
+
copy.discovery_skip = `Packagist reads only the repository-root composer.json; ${copy.manifest} is a development descriptor of ${root.name}`;
|
|
371
|
+
copy.publishable = false;
|
|
372
|
+
if (!root.version && copy.version) {
|
|
373
|
+
root.version = copy.version;
|
|
374
|
+
root.version_source = copy.manifest;
|
|
375
|
+
}
|
|
376
|
+
root.publisher_scope ??= copy.manifest;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
243
380
|
function withPublisher(item, workflows, publishers) {
|
|
244
381
|
const publisher = item.publishable ? publishers.get(item.registry) : null;
|
|
245
382
|
if (!publisher) {
|
|
@@ -255,16 +392,32 @@ function withPublisher(item, workflows, publishers) {
|
|
|
255
392
|
if (publisher.jobs.length > 0) {
|
|
256
393
|
result.workflow_jobs = publisher.jobs;
|
|
257
394
|
}
|
|
395
|
+
if (publisher.callees?.length) {
|
|
396
|
+
// Reusable workflows publish under the caller's identity; the callee
|
|
397
|
+
// jobs are kept so setup and recovery act on the actual publishing job.
|
|
398
|
+
result.workflow_callees = publisher.callees;
|
|
399
|
+
}
|
|
400
|
+
if (publisher.gate) {
|
|
401
|
+
result.authentication_gate = publisher.gate;
|
|
402
|
+
}
|
|
258
403
|
if (publisher.environment) {
|
|
259
404
|
result.environment = publisher.environment;
|
|
260
405
|
}
|
|
261
406
|
if (publisher.candidates.length > 1) {
|
|
262
407
|
result.workflow_candidates = publisher.candidates;
|
|
263
408
|
}
|
|
409
|
+
const credentials = credentialRequirements(item.registry, workflows);
|
|
410
|
+
if (credentials) {
|
|
411
|
+
result.credential_requirements = credentials;
|
|
412
|
+
}
|
|
264
413
|
const secrets = tokenSecrets(workflows, item.registry);
|
|
265
414
|
const warnings = [
|
|
266
415
|
...publisher.warnings,
|
|
267
|
-
|
|
416
|
+
// Tags and registry hooks release Go modules, Packagist packages and
|
|
417
|
+
// CMake archives without a workflow, so only evidence is reported.
|
|
418
|
+
...(!publisher.workflow &&
|
|
419
|
+
publisher.candidates.length === 0 &&
|
|
420
|
+
!RELEASE_EVIDENCE.has(item.registry)
|
|
268
421
|
? [
|
|
269
422
|
`no workflow publishes ${item.name} to ${item.registry}; CI releases will not reach it`,
|
|
270
423
|
]
|
|
@@ -301,10 +454,14 @@ async function collectManifests(directory, manifests, dockerfiles) {
|
|
|
301
454
|
}
|
|
302
455
|
}
|
|
303
456
|
|
|
304
|
-
async function parseManifest(manifestPath, manifest) {
|
|
457
|
+
async function parseManifest(manifestPath, manifest, root) {
|
|
305
458
|
const contents = await readManifest(manifestPath);
|
|
306
459
|
const filename = path.basename(manifestPath);
|
|
307
460
|
switch (filename) {
|
|
461
|
+
case "CMakeLists.txt":
|
|
462
|
+
case "conanfile.py":
|
|
463
|
+
case "vcpkg.json":
|
|
464
|
+
return cppDistribution(contents, filename, manifest, manifestPath);
|
|
308
465
|
case "jsr.json":
|
|
309
466
|
case "deno.json": {
|
|
310
467
|
const value = JSON.parse(contents);
|
|
@@ -315,7 +472,10 @@ async function parseManifest(manifestPath, manifest) {
|
|
|
315
472
|
case "package.json":
|
|
316
473
|
return parseNpm(contents, manifest);
|
|
317
474
|
case "Cargo.toml":
|
|
318
|
-
return parseCargo(
|
|
475
|
+
return parseCargo(
|
|
476
|
+
await cargoContents(contents, manifestPath, root),
|
|
477
|
+
manifest,
|
|
478
|
+
);
|
|
319
479
|
case "pyproject.toml":
|
|
320
480
|
return parsePyproject(contents, manifest);
|
|
321
481
|
case "setup.py":
|
|
@@ -326,13 +486,22 @@ async function parseManifest(manifestPath, manifest) {
|
|
|
326
486
|
capture(contents, /\bversion\s*=\s*['"]([^'"]+)/),
|
|
327
487
|
manifest,
|
|
328
488
|
);
|
|
329
|
-
case "go.mod":
|
|
330
|
-
|
|
489
|
+
case "go.mod": {
|
|
490
|
+
const item = packageInfo(
|
|
331
491
|
"go-modules",
|
|
332
492
|
capture(contents, /^\s*module\s+(\S+)/m) ?? "unknown-go-module",
|
|
333
|
-
|
|
493
|
+
await versionFile(manifestPath),
|
|
334
494
|
manifest,
|
|
335
495
|
);
|
|
496
|
+
if (item.version) {
|
|
497
|
+
item.version = `v${item.version.replace(/^v/, "")}`;
|
|
498
|
+
item.version_source = path.posix.join(
|
|
499
|
+
path.posix.dirname(manifest),
|
|
500
|
+
"VERSION",
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return item;
|
|
504
|
+
}
|
|
336
505
|
case "pom.xml":
|
|
337
506
|
return parseMaven(contents, manifest);
|
|
338
507
|
case "build.gradle":
|
|
@@ -373,6 +542,48 @@ function parseNpm(contents, manifest) {
|
|
|
373
542
|
);
|
|
374
543
|
}
|
|
375
544
|
|
|
545
|
+
async function cargoContents(contents, manifestPath, root) {
|
|
546
|
+
if (
|
|
547
|
+
!/version\s*\.\s*workspace\s*=\s*true|version\s*=\s*\{\s*workspace\s*=\s*true/.test(
|
|
548
|
+
contents,
|
|
549
|
+
)
|
|
550
|
+
) {
|
|
551
|
+
return contents;
|
|
552
|
+
}
|
|
553
|
+
const section = tomlSection(contents, "package") ?? "";
|
|
554
|
+
const explicit = tomlString(section, "workspace");
|
|
555
|
+
let directory = explicit
|
|
556
|
+
? path.resolve(path.dirname(manifestPath), explicit)
|
|
557
|
+
: path.dirname(manifestPath);
|
|
558
|
+
while (directory === root || directory.startsWith(`${root}${path.sep}`)) {
|
|
559
|
+
let workspace;
|
|
560
|
+
try {
|
|
561
|
+
workspace = await readManifest(path.join(directory, "Cargo.toml"));
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (error.code !== "ENOENT") {
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (workspace && tomlSection(workspace, "workspace") !== null) {
|
|
568
|
+
const version = tomlString(
|
|
569
|
+
tomlSection(workspace, "workspace.package") ?? "",
|
|
570
|
+
"version",
|
|
571
|
+
);
|
|
572
|
+
return version
|
|
573
|
+
? contents.replace(
|
|
574
|
+
/version\s*\.\s*workspace\s*=\s*true|version\s*=\s*\{\s*workspace\s*=\s*true\s*\}/,
|
|
575
|
+
`version = "${version}"`,
|
|
576
|
+
)
|
|
577
|
+
: contents;
|
|
578
|
+
}
|
|
579
|
+
if (explicit || directory === root) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
directory = path.dirname(directory);
|
|
583
|
+
}
|
|
584
|
+
return contents;
|
|
585
|
+
}
|
|
586
|
+
|
|
376
587
|
function parseCargo(contents, manifest) {
|
|
377
588
|
const section = tomlSection(contents, "package");
|
|
378
589
|
if (!section) {
|
|
@@ -414,16 +625,38 @@ function parsePyproject(contents, manifest) {
|
|
|
414
625
|
}
|
|
415
626
|
|
|
416
627
|
function parseDotnet(contents, manifestPath, manifest) {
|
|
628
|
+
if (xmlTag(contents, "IsPackable")?.toLowerCase() === "false") {
|
|
629
|
+
return {
|
|
630
|
+
manifest,
|
|
631
|
+
publishable: false,
|
|
632
|
+
discovery_skip: "MSBuild IsPackable is false",
|
|
633
|
+
};
|
|
634
|
+
}
|
|
417
635
|
return packageInfo(
|
|
418
636
|
"nuget",
|
|
419
637
|
xmlTag(contents, "PackageId") ??
|
|
420
638
|
xmlTag(contents, "AssemblyName") ??
|
|
421
639
|
path.basename(manifestPath, path.extname(manifestPath)),
|
|
422
|
-
|
|
640
|
+
dotnetVersion(contents),
|
|
423
641
|
manifest,
|
|
424
642
|
);
|
|
425
643
|
}
|
|
426
644
|
|
|
645
|
+
function dotnetVersion(contents) {
|
|
646
|
+
const literal = (tag) => {
|
|
647
|
+
const value = xmlTag(contents, tag);
|
|
648
|
+
return value && !value.includes("$(") ? value : null;
|
|
649
|
+
};
|
|
650
|
+
const explicit = literal("PackageVersion") ?? literal("Version");
|
|
651
|
+
if (explicit) {
|
|
652
|
+
return explicit;
|
|
653
|
+
}
|
|
654
|
+
// MSBuild derives Version from VersionPrefix and VersionSuffix.
|
|
655
|
+
const prefix = literal("VersionPrefix");
|
|
656
|
+
const suffix = literal("VersionSuffix");
|
|
657
|
+
return prefix ? (suffix ? `${prefix}-${suffix}` : prefix) : null;
|
|
658
|
+
}
|
|
659
|
+
|
|
427
660
|
function parseMaven(contents, manifest) {
|
|
428
661
|
const artifact = xmlTag(contents, "artifactId") ?? "unknown-maven-artifact";
|
|
429
662
|
const group = xmlTag(contents, "groupId");
|
|
@@ -436,16 +669,33 @@ function parseMaven(contents, manifest) {
|
|
|
436
669
|
}
|
|
437
670
|
|
|
438
671
|
function parseGradle(contents, manifest) {
|
|
439
|
-
const
|
|
672
|
+
const literal = (key) =>
|
|
673
|
+
capture(contents, new RegExp(`\\b${key}\\s*(?:=\\s*)?['"]([^'"]+)`));
|
|
674
|
+
const group = literal("groupId") ?? literal("group");
|
|
440
675
|
const artifact =
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
676
|
+
literal("artifactId") ??
|
|
677
|
+
literal("archivesBaseName") ??
|
|
678
|
+
literal("rootProject\\.name");
|
|
679
|
+
const version = literal("version");
|
|
680
|
+
if (
|
|
681
|
+
!group ||
|
|
682
|
+
!artifact ||
|
|
683
|
+
!version ||
|
|
684
|
+
/[$}{]/.test(`${group}${artifact}${version}`) ||
|
|
685
|
+
!/maven-publish|MavenPublication/.test(contents) ||
|
|
686
|
+
!/from\s*\(?\s*components|artifact\s*\(/.test(contents)
|
|
687
|
+
) {
|
|
688
|
+
return {
|
|
689
|
+
manifest,
|
|
690
|
+
publishable: false,
|
|
691
|
+
discovery_skip:
|
|
692
|
+
"Gradle application/build definition has no resolved Maven publication coordinates and component",
|
|
693
|
+
};
|
|
694
|
+
}
|
|
445
695
|
return packageInfo(
|
|
446
696
|
"maven-central",
|
|
447
|
-
|
|
448
|
-
|
|
697
|
+
`${group}:${artifact}`,
|
|
698
|
+
version,
|
|
449
699
|
manifest,
|
|
450
700
|
);
|
|
451
701
|
}
|
|
@@ -484,6 +734,21 @@ function packageInfo(
|
|
|
484
734
|
return result;
|
|
485
735
|
}
|
|
486
736
|
|
|
737
|
+
/** A plain sibling VERSION file, as release tooling for Go/C++ often keeps. */
|
|
738
|
+
export async function versionFile(manifestPath) {
|
|
739
|
+
try {
|
|
740
|
+
const value = (
|
|
741
|
+
await readManifest(path.join(path.dirname(manifestPath), "VERSION"))
|
|
742
|
+
).trim();
|
|
743
|
+
return /^v?\d+(?:\.\d+){1,3}(?:[-+][\w.-]+)?$/.test(value) ? value : null;
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (error.code === "ENOENT") {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
487
752
|
// Editors on Windows may save a byte order mark; npm and Cargo accept it.
|
|
488
753
|
async function readManifest(manifest) {
|
|
489
754
|
return (await readFile(manifest, "utf8")).replace(/^\uFEFF/, "");
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { githubServices } from "./github.mjs";
|
|
2
|
+
import { repositorySlug } from "./repository-identity.mjs";
|
|
3
|
+
|
|
4
|
+
/** GitHub configuration is evidence about a channel, never public registry presence. */
|
|
5
|
+
export async function auditDistributionChannel(session) {
|
|
6
|
+
const distribution = session.plan.package.distribution ?? {};
|
|
7
|
+
const scope = {
|
|
8
|
+
repo: repositorySlug(session.plan.repository),
|
|
9
|
+
...(distribution.environment
|
|
10
|
+
? { environment: distribution.environment }
|
|
11
|
+
: {}),
|
|
12
|
+
};
|
|
13
|
+
const github = session.options.github ?? githubServices(session.options);
|
|
14
|
+
const configured = distribution.remote
|
|
15
|
+
? { verified: true, value: distribution.remote }
|
|
16
|
+
: await github.variables(scope).effective("CONAN_REMOTE_URL");
|
|
17
|
+
let remote = { status: configured.verified ? "missing" : "unknown" };
|
|
18
|
+
if (configured.value) {
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(configured.value);
|
|
21
|
+
remote = {
|
|
22
|
+
status:
|
|
23
|
+
configured.verified &&
|
|
24
|
+
["https:", "http:"].includes(url.protocol) &&
|
|
25
|
+
!url.username &&
|
|
26
|
+
!url.password &&
|
|
27
|
+
!url.search &&
|
|
28
|
+
!url.hash
|
|
29
|
+
? "configured"
|
|
30
|
+
: "invalid-or-secret-bearing",
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
remote = { status: "invalid" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const credentials = [];
|
|
37
|
+
for (const name of ["CONAN_LOGIN_USERNAME", "CONAN_PASSWORD"]) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await github.health.health(name, {
|
|
40
|
+
scope: { repo: scope.repo },
|
|
41
|
+
failurePatterns: ["conan.*(?:unauthorized|forbidden|authentication)"],
|
|
42
|
+
});
|
|
43
|
+
credentials.push({
|
|
44
|
+
name,
|
|
45
|
+
status: result.status,
|
|
46
|
+
evidence: (result.evidence ?? []).map(
|
|
47
|
+
({ reason, status, workflow, job, step, runId, runUrl }) => ({
|
|
48
|
+
reason,
|
|
49
|
+
status,
|
|
50
|
+
workflow,
|
|
51
|
+
job,
|
|
52
|
+
step,
|
|
53
|
+
runId,
|
|
54
|
+
runUrl,
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
...(scope.environment
|
|
58
|
+
? {
|
|
59
|
+
limitation:
|
|
60
|
+
"health is repository workflow evidence; effective environment credentials require the selected CI job",
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
credentials.push({
|
|
66
|
+
name,
|
|
67
|
+
status: "unknown",
|
|
68
|
+
reason: "credential-health-unreadable",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const result = {
|
|
73
|
+
provider: "conan",
|
|
74
|
+
scope,
|
|
75
|
+
remote,
|
|
76
|
+
credentials,
|
|
77
|
+
publication: "unverified",
|
|
78
|
+
ci_validation: [
|
|
79
|
+
"conan create . --build=missing",
|
|
80
|
+
"run the recipe test_package consumer with the reviewed profiles",
|
|
81
|
+
"verify the exact reference on the chosen remote after reviewed CI publication",
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
session.outcome = { status: "unverified", distribution: result };
|
|
85
|
+
console.log(JSON.stringify(result));
|
|
86
|
+
return result;
|
|
87
|
+
}
|
package/src/exec.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { execa } from "command-stream/execa";
|
|
2
|
+
|
|
3
|
+
/** Capture an exact argument vector, including Windows PATHEXT launchers. */
|
|
4
|
+
export async function exec(program, args, options = {}) {
|
|
5
|
+
const output = options.mirror ? ["pipe", "inherit"] : "pipe";
|
|
6
|
+
const result = await execa(program, args, {
|
|
7
|
+
cwd: options.cwd,
|
|
8
|
+
env: options.env,
|
|
9
|
+
stdin: options.stdin ?? "ignore",
|
|
10
|
+
stdout: output,
|
|
11
|
+
stderr: output,
|
|
12
|
+
reject: false,
|
|
13
|
+
stripFinalNewline: false,
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
code: result.exitCode ?? 1,
|
|
17
|
+
stdout: result.stdout ?? "",
|
|
18
|
+
stderr: result.stderr ?? "",
|
|
19
|
+
};
|
|
20
|
+
}
|