package-registry-manager 0.25.0 → 0.25.1
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 +2 -2
- package/src/audit-config.mjs +96 -0
- package/src/bootstrap-recovery.mjs +163 -0
- package/src/cpp-distribution.mjs +127 -0
- package/src/credential-cycle.mjs +8 -3
- package/src/default-browser.mjs +1 -1
- package/src/default-scope.mjs +1 -1
- package/src/discovery.mjs +151 -12
- package/src/distribution-channel.mjs +87 -0
- package/src/exec.mjs +20 -0
- package/src/extra-flows.mjs +39 -0
- 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 +207 -0
- package/src/nuget-migration.mjs +98 -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/plan.mjs +7 -2
- package/src/prerequisites.mjs +1 -1
- package/src/profile.mjs +1 -1
- package/src/publication-state.mjs +265 -0
- package/src/publishers.mjs +5 -2
- package/src/publishing-policy.mjs +7 -1
- package/src/publishing-state.mjs +14 -0
- 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/tokens.mjs +32 -4
- package/src/workflow-migration.mjs +15 -2
- package/src/workflow-proposal.mjs +6 -2
package/README.md
CHANGED
|
@@ -77,3 +77,8 @@ OIDC publication and verification that no workflow still uses the credentials.
|
|
|
77
77
|
Saved crates.io API sessions run headlessly; sign-in opens a visible window only
|
|
78
78
|
when user action is needed. Organization migration skips and reports scan errors
|
|
79
79
|
while continuing for clean repositories.
|
|
80
|
+
|
|
81
|
+
NuGet setup uses an explicit login profile, package owner and exact package IDs.
|
|
82
|
+
Trusted publishing is preferred; existing provided fallback credentials are retained.
|
|
83
|
+
See [audit and NuGet configuration](../docs/issue-73-requirements.md) for setup,
|
|
84
|
+
identity decisions and the distinction between verified configuration and release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "package-registry-manager",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Inspect repositories and guide secure package-registry setup",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"audit": "node scripts/npm-audit.mjs"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@link-foundation/gh-manager": "https://codeload.github.com/link-foundation/gh-manager/tar.gz/
|
|
43
|
+
"@link-foundation/gh-manager": "https://codeload.github.com/link-foundation/gh-manager/tar.gz/0b3ceb1590be7eb0340ae088730698b5ae9bebfc",
|
|
44
44
|
"browser-commander": "^0.34.0",
|
|
45
45
|
"command-stream": "^2.6.0",
|
|
46
46
|
"playwright": "^1.63.0",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Explicit source decisions supplement static discovery without executing build files. */
|
|
5
|
+
export async function auditConfig(root) {
|
|
6
|
+
const config = JSON.parse(
|
|
7
|
+
await readFile(
|
|
8
|
+
path.join(root, ".package-registry-manager.json"),
|
|
9
|
+
"utf8",
|
|
10
|
+
).catch((error) => {
|
|
11
|
+
if (error.code === "ENOENT") {
|
|
12
|
+
return "{}";
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
if (
|
|
18
|
+
config.publish !== undefined &&
|
|
19
|
+
(!Array.isArray(config.publish) ||
|
|
20
|
+
config.publish.some(
|
|
21
|
+
(file) =>
|
|
22
|
+
typeof file !== "string" ||
|
|
23
|
+
path.posix.normalize(file) !== file ||
|
|
24
|
+
file.startsWith("/") ||
|
|
25
|
+
file.split("/").includes(".."),
|
|
26
|
+
))
|
|
27
|
+
) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"publish must list exact repository-relative manifest paths",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A reviewed mapping chooses one canonical artifact; references never publish a different tarball. */
|
|
36
|
+
export function applyAuditConfig(items, config, workflows) {
|
|
37
|
+
for (const item of items.filter(Boolean)) {
|
|
38
|
+
const gradle = config.gradle?.find(
|
|
39
|
+
(entry) => entry.manifest === item.manifest,
|
|
40
|
+
);
|
|
41
|
+
if (gradle) {
|
|
42
|
+
if (
|
|
43
|
+
![
|
|
44
|
+
gradle.group,
|
|
45
|
+
gradle.artifact,
|
|
46
|
+
gradle.version,
|
|
47
|
+
gradle.component,
|
|
48
|
+
].every((value) => typeof value === "string" && /^[\w.-]+$/.test(value))
|
|
49
|
+
) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"explicit Gradle publication requires resolved group, artifact, version and component",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
Object.assign(item, {
|
|
55
|
+
registry: "maven-central",
|
|
56
|
+
name: `${gradle.group}:${gradle.artifact}`,
|
|
57
|
+
version: gradle.version,
|
|
58
|
+
publishable: true,
|
|
59
|
+
publication_component: gradle.component,
|
|
60
|
+
});
|
|
61
|
+
delete item.discovery_skip;
|
|
62
|
+
}
|
|
63
|
+
const shared = config.shared_artifacts?.find(
|
|
64
|
+
(entry) =>
|
|
65
|
+
`${entry.registry}:${entry.name}` === `${item.registry}:${item.name}`,
|
|
66
|
+
);
|
|
67
|
+
if (shared) {
|
|
68
|
+
item.shared_artifact = shared;
|
|
69
|
+
}
|
|
70
|
+
if (item.distribution) {
|
|
71
|
+
item.distribution = {
|
|
72
|
+
...item.distribution,
|
|
73
|
+
...config.distribution?.[item.manifest],
|
|
74
|
+
workflows: workflows
|
|
75
|
+
.filter((workflow) =>
|
|
76
|
+
item.registry === "conan"
|
|
77
|
+
? /conan\s+upload/.test(workflow.contents)
|
|
78
|
+
: item.registry === "vcpkg"
|
|
79
|
+
? /vcpkg.*(?:x-add-version|registry|publish)/.test(
|
|
80
|
+
workflow.contents,
|
|
81
|
+
)
|
|
82
|
+
: /cpack|cmake.*--install/.test(workflow.contents),
|
|
83
|
+
)
|
|
84
|
+
.map((workflow) => ({
|
|
85
|
+
workflow: workflow.name,
|
|
86
|
+
remote_variable: /CONAN_REMOTE_URL/.test(workflow.contents)
|
|
87
|
+
? "CONAN_REMOTE_URL"
|
|
88
|
+
: undefined,
|
|
89
|
+
credential_names: ["CONAN_LOGIN_USERNAME", "CONAN_PASSWORD"].filter(
|
|
90
|
+
(name) => workflow.contents.includes(name),
|
|
91
|
+
),
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { probePackage } from "./registry-state.mjs";
|
|
2
|
+
import {
|
|
3
|
+
publisherIdentities,
|
|
4
|
+
publisherMatches,
|
|
5
|
+
repositorySlug,
|
|
6
|
+
} from "./repository-identity.mjs";
|
|
7
|
+
|
|
8
|
+
/** Bootstrap is a repaired prerequisite, separate from OIDC rejection. */
|
|
9
|
+
export function bootstrapNames(log) {
|
|
10
|
+
const names = [];
|
|
11
|
+
for (const match of log.matchAll(
|
|
12
|
+
/npm owner setup required for ([^:\n]+):/gi,
|
|
13
|
+
)) {
|
|
14
|
+
for (const name of match[1].split(/[,\s]+/).filter(Boolean)) {
|
|
15
|
+
if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(name)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
names.push(name);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [...new Set(names)];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Fresh name and exact publisher checks are equivalent to the owner-setup preflight. */
|
|
25
|
+
export async function verifyBootstrapRecovery(session) {
|
|
26
|
+
if (!session.bootstrapNames?.length) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const name of session.bootstrapNames) {
|
|
30
|
+
const state = await (session.options.probePackage ?? probePackage)(
|
|
31
|
+
{ ...session.plan.package, name },
|
|
32
|
+
session.options,
|
|
33
|
+
);
|
|
34
|
+
if (state.exists !== true) {
|
|
35
|
+
throw new Error(`bootstrap preflight still unresolved for ${name}`);
|
|
36
|
+
}
|
|
37
|
+
const trust = session.plan.steps?.find(
|
|
38
|
+
(step) => step.id === "verify-trusted-publisher",
|
|
39
|
+
)?.command;
|
|
40
|
+
const args = trust
|
|
41
|
+
? trust.args.map((argument) =>
|
|
42
|
+
argument === session.plan.package.name ? name : argument,
|
|
43
|
+
)
|
|
44
|
+
: ["-y", "npm@^11.15", "trust", "list", name, "--browser=false"];
|
|
45
|
+
const result = await session.capture({
|
|
46
|
+
command: { program: trust?.program ?? "npx", args },
|
|
47
|
+
});
|
|
48
|
+
if (
|
|
49
|
+
result.code !== 0 ||
|
|
50
|
+
!publisherIdentities(String(result.stdout)).some((publisher) =>
|
|
51
|
+
publisherMatches(publisher, session.plan.trusted_publisher),
|
|
52
|
+
)
|
|
53
|
+
) {
|
|
54
|
+
throw new Error(`bootstrap publisher is not verified for ${name}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const slug = repositorySlug(session.plan.repository);
|
|
58
|
+
const result = await session.capture({
|
|
59
|
+
command: {
|
|
60
|
+
program: "gh",
|
|
61
|
+
args: [
|
|
62
|
+
"api",
|
|
63
|
+
`repos/${slug}/commits/${session.releaseSelection.branch}`,
|
|
64
|
+
"--jq",
|
|
65
|
+
".sha",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
cwd: ".",
|
|
69
|
+
});
|
|
70
|
+
if (
|
|
71
|
+
result.code !== 0 ||
|
|
72
|
+
String(result.stdout).trim() !== session.releaseSelection.run.headSha
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"default branch changed; follow the new revision's release instead of rerunning the old SHA",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Retry only jobs whose own failed log contains the repaired prerequisite. */
|
|
81
|
+
export async function recoverBootstrap(session) {
|
|
82
|
+
await verifyBootstrapRecovery(session);
|
|
83
|
+
const selection = session.releaseSelection;
|
|
84
|
+
const slug = repositorySlug(session.plan.repository);
|
|
85
|
+
const jobIds = [];
|
|
86
|
+
for (const job of selection.jobs ?? []) {
|
|
87
|
+
if (job.conclusion !== "failure") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const log = await session.capture({
|
|
91
|
+
command: {
|
|
92
|
+
program: "gh",
|
|
93
|
+
args: [
|
|
94
|
+
"run",
|
|
95
|
+
"view",
|
|
96
|
+
String(selection.run.databaseId),
|
|
97
|
+
"--repo",
|
|
98
|
+
slug,
|
|
99
|
+
"--job",
|
|
100
|
+
String(job.databaseId),
|
|
101
|
+
"--log",
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
cwd: ".",
|
|
105
|
+
});
|
|
106
|
+
if (log.code !== 0) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"causal failed-job logs are unavailable; no CI was restarted",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
bootstrapNames(String(log.stdout)).some((name) =>
|
|
113
|
+
session.bootstrapNames.includes(name),
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
jobIds.push(job.databaseId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!jobIds.length) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
"no causal bootstrap job could be verified; unrelated failed jobs were not retried",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const changes =
|
|
125
|
+
session.options.recoveryChanges?.get(
|
|
126
|
+
`${slug}:${session.plan.trusted_publisher.workflow}`,
|
|
127
|
+
) ?? [];
|
|
128
|
+
if (!changes.length) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"no verified configuration change in this setup; CI was not restarted",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const { githubServices } = await import("./github.mjs");
|
|
134
|
+
const github = session.options.github ?? githubServices(session.options);
|
|
135
|
+
const result = await github.repair.verify({
|
|
136
|
+
repo: slug,
|
|
137
|
+
workflow: session.plan.trusted_publisher.workflow,
|
|
138
|
+
runId: selection.run.databaseId,
|
|
139
|
+
jobIds,
|
|
140
|
+
changes: changes.map(() => ({
|
|
141
|
+
kind: "setting",
|
|
142
|
+
changed: true,
|
|
143
|
+
verified: true,
|
|
144
|
+
})),
|
|
145
|
+
timeout: session.options.waitTimeoutMs ?? 600000,
|
|
146
|
+
});
|
|
147
|
+
console.log(JSON.stringify({ bootstrap_recovery: result }));
|
|
148
|
+
session.conditions.delete("release-dispatch");
|
|
149
|
+
session.values.run_id = String(result.runId);
|
|
150
|
+
if (result.status !== "success") {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`repaired bootstrap CI remains ${result.status}; ${result.runUrl ?? selection.run.url}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const { verifyNpmRelease } = await import("./npm-release.mjs");
|
|
156
|
+
const packages = await verifyNpmRelease(session, result.runId);
|
|
157
|
+
session.outcome = {
|
|
158
|
+
status: "verified",
|
|
159
|
+
url: result.runUrl,
|
|
160
|
+
ci: result,
|
|
161
|
+
packages,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Read distribution metadata without executing a CMake file or Python recipe. */
|
|
5
|
+
export async function cppDistribution(
|
|
6
|
+
contents,
|
|
7
|
+
filename,
|
|
8
|
+
manifest,
|
|
9
|
+
manifestPath,
|
|
10
|
+
) {
|
|
11
|
+
let cmake = filename === "CMakeLists.txt" ? contents : "";
|
|
12
|
+
if (!cmake) {
|
|
13
|
+
try {
|
|
14
|
+
cmake = await readFile(
|
|
15
|
+
path.join(path.dirname(manifestPath), "CMakeLists.txt"),
|
|
16
|
+
"utf8",
|
|
17
|
+
);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error.code !== "ENOENT") {
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const project =
|
|
25
|
+
/\bproject\s*\(\s*([\w.-]+)\s+VERSION\s+(\d+(?:\.\d+)+)/i.exec(cmake);
|
|
26
|
+
let name = project?.[1];
|
|
27
|
+
let version = project?.[2];
|
|
28
|
+
const registry =
|
|
29
|
+
filename === "vcpkg.json"
|
|
30
|
+
? "vcpkg"
|
|
31
|
+
: filename === "conanfile.py"
|
|
32
|
+
? "conan"
|
|
33
|
+
: "cmake";
|
|
34
|
+
if (registry === "vcpkg") {
|
|
35
|
+
const data = JSON.parse(contents);
|
|
36
|
+
name = data.name;
|
|
37
|
+
version =
|
|
38
|
+
data.version ??
|
|
39
|
+
data["version-semver"] ??
|
|
40
|
+
data["version-string"] ??
|
|
41
|
+
data["version-date"];
|
|
42
|
+
} else if (registry === "conan") {
|
|
43
|
+
name = /^\s*name\s*=\s*["']([^"']+)/m.exec(contents)?.[1] ?? name;
|
|
44
|
+
version = /^\s*version\s*=\s*["']([^"']+)/m.exec(contents)?.[1] ?? version;
|
|
45
|
+
}
|
|
46
|
+
if (
|
|
47
|
+
!name ||
|
|
48
|
+
(registry === "cmake" &&
|
|
49
|
+
!/install\s*\(|CPack|configure_package_config_file/i.test(contents))
|
|
50
|
+
) {
|
|
51
|
+
return {
|
|
52
|
+
manifest,
|
|
53
|
+
discovery_skip:
|
|
54
|
+
"CMake build definition has no distribution installation/configuration",
|
|
55
|
+
publishable: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
registry,
|
|
60
|
+
name,
|
|
61
|
+
version: version ?? null,
|
|
62
|
+
manifest,
|
|
63
|
+
publishable: true,
|
|
64
|
+
distribution: {
|
|
65
|
+
version_source: project
|
|
66
|
+
? path.posix.join(path.posix.dirname(manifest), "CMakeLists.txt")
|
|
67
|
+
: manifest,
|
|
68
|
+
provider_status: "unverified",
|
|
69
|
+
...(registry === "conan"
|
|
70
|
+
? {
|
|
71
|
+
remote_required: "CONAN_REMOTE_URL",
|
|
72
|
+
credential_names: ["CONAN_LOGIN_USERNAME", "CONAN_PASSWORD"],
|
|
73
|
+
}
|
|
74
|
+
: {}),
|
|
75
|
+
},
|
|
76
|
+
warnings: [
|
|
77
|
+
registry === "conan"
|
|
78
|
+
? "Conan remote configuration and publication remain unverified; a local recipe does not establish registry presence"
|
|
79
|
+
: `${registry} distribution metadata does not establish a public registry publication`,
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Validation and explicit channel prerequisites; no upload is performed. */
|
|
85
|
+
export function cppPlan(item) {
|
|
86
|
+
const commands = {
|
|
87
|
+
cmake: {
|
|
88
|
+
program: "cmake",
|
|
89
|
+
args: ["-S", ".", "-B", "build/prm-validation"],
|
|
90
|
+
},
|
|
91
|
+
conan: { program: "conan", args: ["inspect", ".", "--format=json"] },
|
|
92
|
+
vcpkg: {
|
|
93
|
+
program: "vcpkg",
|
|
94
|
+
args: ["install", "--dry-run", "--x-manifest-root=."],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
id: "validate-package",
|
|
100
|
+
title: `Validate ${item.registry} distribution`,
|
|
101
|
+
kind: "check",
|
|
102
|
+
command: commands[item.registry],
|
|
103
|
+
description:
|
|
104
|
+
"Validate the manifest locally before reviewing any publication settings.",
|
|
105
|
+
},
|
|
106
|
+
...(item.registry === "conan"
|
|
107
|
+
? [
|
|
108
|
+
{
|
|
109
|
+
id: "audit-distribution-channel",
|
|
110
|
+
title: "Read Conan remote and credential health",
|
|
111
|
+
kind: "api",
|
|
112
|
+
description:
|
|
113
|
+
"Read effective remote configuration and credential health through gh-manager. Unknown credentials and missing remotes remain explicit; do not upload or rotate credentials.",
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
: []),
|
|
117
|
+
{
|
|
118
|
+
id: "review-distribution-channel",
|
|
119
|
+
title: "Resolve the distribution provider and CI configuration",
|
|
120
|
+
kind: "manual",
|
|
121
|
+
description:
|
|
122
|
+
item.registry === "conan"
|
|
123
|
+
? "Read CONAN_REMOTE_URL and credential health for CONAN_LOGIN_USERNAME/CONAN_PASSWORD through gh-manager at the effective repository/environment scope. Choose the exact remote, validate conan create and consumer tests in reviewed CI, then configure narrowly scoped existing credentials. No public registry state or permission is established by a recipe."
|
|
124
|
+
: `Review ${item.registry === "vcpkg" ? "the chosen vcpkg registry repository, baseline, port and version database" : "CMake install/export, package config, CPack and GitHub Release workflow"}; validate installation and a consumer in CI. Provider lookup/setup is unsupported until the destination is explicitly configured; publication remains unverified.`,
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
}
|
package/src/credential-cycle.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/** Registry-specific policy; tokens are never a fallback for OIDC registries. */
|
|
2
2
|
export function credentialPolicy(registry) {
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
if (registry === "nuget") {
|
|
4
|
+
return {
|
|
5
|
+
mode: "oidc-with-token-fallback",
|
|
6
|
+
description:
|
|
7
|
+
"NuGet: NuGet/login OIDC first, retain provided fallback secrets; never create a persistent publishing key",
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
if (["npm", "pypi", "crates-io", "rubygems", "jsr"].includes(registry)) {
|
|
6
11
|
return {
|
|
7
12
|
mode: "trusted",
|
|
8
13
|
description: `${registry}: trusted publishing; remove unused long-lived tokens after verification`,
|
package/src/default-browser.mjs
CHANGED
package/src/default-scope.mjs
CHANGED