@astra-kit/rp 0.0.0-stage → 0.10.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/CHANGELOG.md +196 -0
- package/LICENSE.md +21 -0
- package/README.md +352 -2
- package/bin/astra-rp.js +51 -0
- package/package.json +93 -4
- package/src/build-gate.js +157 -0
- package/src/changelog-blocks.js +53 -0
- package/src/changelog.js +108 -0
- package/src/constants/build-gate.js +30 -0
- package/src/constants/changelog-draft.js +180 -0
- package/src/constants/changelog.js +74 -0
- package/src/constants/ci-release.js +346 -0
- package/src/constants/cli.js +12 -0
- package/src/constants/create-version.js +523 -0
- package/src/constants/guard-publish.js +29 -0
- package/src/constants/index.js +18 -0
- package/src/constants/monorepo.js +57 -0
- package/src/constants/package-manager.js +56 -0
- package/src/constants/registry.js +72 -0
- package/src/constants/terminal-ui.js +93 -0
- package/src/constants/version-files.js +63 -0
- package/src/constants/versions.js +60 -0
- package/src/create-version/artifact.js +343 -0
- package/src/create-version/changelog-draft-workspace.js +124 -0
- package/src/create-version/changelog-draft.js +380 -0
- package/src/create-version/changelog.js +264 -0
- package/src/create-version/ci-config.js +54 -0
- package/src/create-version/ci-publication-history.js +113 -0
- package/src/create-version/ci-release-identity-step.js +58 -0
- package/src/create-version/ci-setup.js +794 -0
- package/src/create-version/ci.js +555 -0
- package/src/create-version/committed-tree.js +75 -0
- package/src/create-version/config.js +489 -0
- package/src/create-version/errors.js +46 -0
- package/src/create-version/github-workflow.js +199 -0
- package/src/create-version/index.js +54 -0
- package/src/create-version/json.js +32 -0
- package/src/create-version/jsonc.js +76 -0
- package/src/create-version/jsr.js +96 -0
- package/src/create-version/local-changes.js +178 -0
- package/src/create-version/npm-auth.js +142 -0
- package/src/create-version/npm.js +812 -0
- package/src/create-version/plan.js +1200 -0
- package/src/create-version/process.js +334 -0
- package/src/create-version/registry-config.js +121 -0
- package/src/create-version/registry-http.js +62 -0
- package/src/create-version/registry.js +220 -0
- package/src/create-version/release-execution.js +45 -0
- package/src/create-version/release-tag-config.js +76 -0
- package/src/create-version/run.js +2087 -0
- package/src/create-version/state.js +461 -0
- package/src/create-version/strict-utf8.js +11 -0
- package/src/create-version/templates/release.yml +52 -0
- package/src/create-version/templates/vercel-ignore-build.mjs +66 -0
- package/src/guard-publish.js +77 -0
- package/src/index.js +42 -0
- package/src/monorepo/plan.js +475 -0
- package/src/monorepo/release-commit.js +46 -0
- package/src/monorepo/run.js +927 -0
- package/src/monorepo/state.js +206 -0
- package/src/monorepo/workspaces.js +327 -0
- package/src/package-manager.js +107 -0
- package/src/terminal-ui.js +548 -0
- package/src/testing.js +70 -0
- package/src/version-files.js +142 -0
- package/src/versions.js +303 -0
- package/types/build-gate.d.ts +43 -0
- package/types/changelog-blocks.d.ts +33 -0
- package/types/changelog.d.ts +61 -0
- package/types/constants/build-gate.d.ts +23 -0
- package/types/constants/changelog-draft.d.ts +138 -0
- package/types/constants/changelog.d.ts +56 -0
- package/types/constants/ci-release.d.ts +284 -0
- package/types/constants/cli.d.ts +11 -0
- package/types/constants/create-version.d.ts +397 -0
- package/types/constants/guard-publish.d.ts +25 -0
- package/types/constants/index.d.ts +17 -0
- package/types/constants/monorepo.d.ts +45 -0
- package/types/constants/package-manager.d.ts +40 -0
- package/types/constants/registry.d.ts +79 -0
- package/types/constants/terminal-ui.d.ts +73 -0
- package/types/constants/version-files.d.ts +51 -0
- package/types/constants/versions.d.ts +47 -0
- package/types/create-version/artifact.d.ts +113 -0
- package/types/create-version/changelog-draft-workspace.d.ts +33 -0
- package/types/create-version/changelog-draft.d.ts +127 -0
- package/types/create-version/changelog.d.ts +141 -0
- package/types/create-version/ci-config.d.ts +33 -0
- package/types/create-version/ci-publication-history.d.ts +51 -0
- package/types/create-version/ci-release-identity-step.d.ts +12 -0
- package/types/create-version/ci-setup.d.ts +148 -0
- package/types/create-version/ci.d.ts +145 -0
- package/types/create-version/committed-tree.d.ts +55 -0
- package/types/create-version/config.d.ts +128 -0
- package/types/create-version/errors.d.ts +39 -0
- package/types/create-version/github-workflow.d.ts +41 -0
- package/types/create-version/index.d.ts +28 -0
- package/types/create-version/json.d.ts +21 -0
- package/types/create-version/jsonc.d.ts +21 -0
- package/types/create-version/jsr.d.ts +52 -0
- package/types/create-version/local-changes.d.ts +52 -0
- package/types/create-version/npm-auth.d.ts +60 -0
- package/types/create-version/npm.d.ts +331 -0
- package/types/create-version/plan.d.ts +369 -0
- package/types/create-version/process.d.ts +151 -0
- package/types/create-version/registry-config.d.ts +56 -0
- package/types/create-version/registry-http.d.ts +22 -0
- package/types/create-version/registry.d.ts +86 -0
- package/types/create-version/release-execution.d.ts +22 -0
- package/types/create-version/release-tag-config.d.ts +18 -0
- package/types/create-version/run.d.ts +407 -0
- package/types/create-version/state.d.ts +204 -0
- package/types/create-version/strict-utf8.d.ts +10 -0
- package/types/guard-publish.d.ts +49 -0
- package/types/index.d.ts +23 -0
- package/types/monorepo/plan.d.ts +68 -0
- package/types/monorepo/release-commit.d.ts +29 -0
- package/types/monorepo/run.d.ts +68 -0
- package/types/monorepo/state.d.ts +96 -0
- package/types/monorepo/workspaces.d.ts +86 -0
- package/types/package-manager.d.ts +58 -0
- package/types/terminal-ui.d.ts +196 -0
- package/types/testing.d.ts +31 -0
- package/types/version-files.d.ts +59 -0
- package/types/versions.d.ts +158 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gathers the snapshot consumed by `plan.js`: current branch, uncommitted
|
|
3
|
+
* changes, `main` compared with `origin/main`, the version and subject of
|
|
4
|
+
* `HEAD`, the commit of a detached release tag on `origin`, the last release,
|
|
5
|
+
* the commits waiting to be released, the pull request of a feature branch,
|
|
6
|
+
* the published versions, the npm credentials (when the plan would publish to
|
|
7
|
+
* npm) and pending migrations.
|
|
8
|
+
*
|
|
9
|
+
* Every reader is read-only; the only network operations are `git fetch`,
|
|
10
|
+
* `git ls-remote`, `gh pr view`, `npm view`, `npm whoami`, `npm owner ls` and
|
|
11
|
+
* whatever the project migrations adapter reads.
|
|
12
|
+
*
|
|
13
|
+
* @module create-version/state
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
import { readUnreleased } from "../changelog.js";
|
|
20
|
+
import { CHANGELOG_FILE } from "../constants/changelog.js";
|
|
21
|
+
import {
|
|
22
|
+
MAIN_BRANCH,
|
|
23
|
+
MIGRATION_STATUS,
|
|
24
|
+
NPM_AUTH_STATUS,
|
|
25
|
+
NPM_NOT_FOUND_CODE,
|
|
26
|
+
NO_PULL_REQUEST_MESSAGE_PATTERN,
|
|
27
|
+
PULL_REQUEST_JSON_FIELDS,
|
|
28
|
+
RELEASE_REMOTE,
|
|
29
|
+
REMOTE_MAIN_REF,
|
|
30
|
+
} from "../constants/create-version.js";
|
|
31
|
+
import { toReleaseTag } from "../versions.js";
|
|
32
|
+
import { readChangelogUpdateState } from "./changelog.js";
|
|
33
|
+
import { describeError } from "./errors.js";
|
|
34
|
+
import { readJsonManifest } from "./json.js";
|
|
35
|
+
import { checkRegistryAccess, lookupRegistryVersions } from "./registry.js";
|
|
36
|
+
import { checkNpmPublishAccess, failedLookup, lookupPublishedVersions, resolvePublishRegistry } from "./npm.js";
|
|
37
|
+
import { createGitReader, findVersionChangeCommit, listCommits, readPackageVersionAt, runCaptured } from "./process.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {import("./process.js").GitReader} GitReader
|
|
41
|
+
* @typedef {import("./plan.js").ReleaseState} ReleaseState
|
|
42
|
+
* @typedef {import("./plan.js").PullRequestSnapshot} PullRequestSnapshot
|
|
43
|
+
* @typedef {import("./config.js").MigrationCheck} MigrationCheck
|
|
44
|
+
* @typedef {import("./plan.js").LastReleaseSnapshot} LastReleaseSnapshot
|
|
45
|
+
* @typedef {ReleaseState & { packageName: string, releasedVersion: string | null, lastRelease: LastReleaseSnapshot | null }} ReleaseSnapshot
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the release tag of a version when it points exactly at a commit.
|
|
50
|
+
*
|
|
51
|
+
* @param {GitReader} reader - Git reader.
|
|
52
|
+
* @param {string | null} version - Version whose `vX.Y.Z` tag is looked up.
|
|
53
|
+
* @param {string} sha - Commit the tag must point at.
|
|
54
|
+
* @returns {Promise<string | null>} Tag name, or `null` when it is missing or points elsewhere.
|
|
55
|
+
*/
|
|
56
|
+
async function findReleaseTagAt(reader, version, sha) {
|
|
57
|
+
if (!version) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tag = toReleaseTag(version);
|
|
62
|
+
const taggedSha = await reader.tryGit(["rev-parse", "--verify", "--quiet", `refs/tags/${tag}^{commit}`]);
|
|
63
|
+
return taggedSha === sha ? tag : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads the commit a release tag points at on `origin`, so a detached publication can prove that
|
|
68
|
+
* its commit and tag already reached the remote. An annotated tag is resolved through its peeled
|
|
69
|
+
* `^{}` entry; a lightweight tag points at the commit directly.
|
|
70
|
+
*
|
|
71
|
+
* @param {GitReader} reader - Git reader.
|
|
72
|
+
* @param {string} tag - Release tag such as `v1.2.0`.
|
|
73
|
+
* @returns {Promise<string | null>} Commit of the tag on `origin`, or `null` when it is missing or
|
|
74
|
+
* `origin` cannot be read.
|
|
75
|
+
*/
|
|
76
|
+
async function readRemoteReleaseTagCommit(reader, tag) {
|
|
77
|
+
const tagRef = `refs/tags/${tag}`;
|
|
78
|
+
const peeledRef = `${tagRef}^{}`;
|
|
79
|
+
const output = await reader.tryGit(["ls-remote", RELEASE_REMOTE, tagRef, peeledRef]);
|
|
80
|
+
const shaByRef = new Map(
|
|
81
|
+
(output ?? "")
|
|
82
|
+
.split("\n")
|
|
83
|
+
.map((line) => line.trim().split(/\s+/u))
|
|
84
|
+
.filter((fields) => fields.length === 2)
|
|
85
|
+
.map(([sha, ref]) => [ref, sha])
|
|
86
|
+
);
|
|
87
|
+
return shaByRef.get(peeledRef) ?? shaByRef.get(tagRef) ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Finds the last release on a revision: the newest commit that changed the
|
|
92
|
+
* top-level `version` of `package.json`. It covers `X.Y.Z` release commits,
|
|
93
|
+
* other release subjects and projects that bumped versions by hand.
|
|
94
|
+
*
|
|
95
|
+
* @param {GitReader} reader - Git reader.
|
|
96
|
+
* @param {string} revision - Revision such as `origin/main`.
|
|
97
|
+
* @returns {Promise<LastReleaseSnapshot | null>} Last release with its subject and whether its
|
|
98
|
+
* `vX.Y.Z` tag points at it, or `null` without history.
|
|
99
|
+
*/
|
|
100
|
+
export async function findLastRelease(reader, revision) {
|
|
101
|
+
const sha = await findVersionChangeCommit(reader, revision);
|
|
102
|
+
|
|
103
|
+
if (!sha) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const version = await readPackageVersionAt(reader, sha);
|
|
108
|
+
const subject = await reader.tryGit(["log", "-1", "--format=%s", sha]);
|
|
109
|
+
return { sha, version, subject, tagged: (await findReleaseTagAt(reader, version, sha)) !== null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Looks up the pull request of a branch with the GitHub CLI.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} repositoryRoot - Repository root.
|
|
116
|
+
* @param {string} branchName - Head branch.
|
|
117
|
+
* @returns {Promise<{ pullRequest: PullRequestSnapshot | null, githubError: string | null }>} Pull request or error.
|
|
118
|
+
*/
|
|
119
|
+
export async function lookupPullRequest(repositoryRoot, branchName) {
|
|
120
|
+
const result = await runCaptured("gh", ["pr", "view", branchName, "--json", PULL_REQUEST_JSON_FIELDS], { cwd: repositoryRoot });
|
|
121
|
+
|
|
122
|
+
if (result.status === 0) {
|
|
123
|
+
try {
|
|
124
|
+
return { pullRequest: JSON.parse(result.stdout), githubError: null };
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return { pullRequest: null, githubError: `respuesta inválida de gh (${describeError(error)})` };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (NO_PULL_REQUEST_MESSAGE_PATTERN.test(result.stderr)) {
|
|
131
|
+
return { pullRequest: null, githubError: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { pullRequest: null, githubError: result.stderr.split("\n")[0] || "gh no respondió" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reads the feature branch snapshot (upstream, unpushed and unmerged commits).
|
|
139
|
+
*
|
|
140
|
+
* @param {GitReader} reader - Git reader.
|
|
141
|
+
* @param {string} branchName - Current branch.
|
|
142
|
+
* @returns {Promise<import("./plan.js").FeatureBranchSnapshot>} Snapshot.
|
|
143
|
+
*/
|
|
144
|
+
async function readFeatureBranch(reader, branchName) {
|
|
145
|
+
const headSha = await reader.git(["rev-parse", "HEAD"]);
|
|
146
|
+
const aheadOfMainCount = Number((await reader.tryGit(["rev-list", "--count", `${REMOTE_MAIN_REF}..HEAD`])) ?? 0);
|
|
147
|
+
const upstreamUnpushed = await reader.tryGit(["rev-list", "--count", "@{upstream}..HEAD"]);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: branchName,
|
|
151
|
+
headSha,
|
|
152
|
+
hasUpstream: upstreamUnpushed !== null,
|
|
153
|
+
unpushedCount: upstreamUnpushed === null ? aheadOfMainCount : Number(upstreamUnpushed),
|
|
154
|
+
aheadOfMainCount,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reads the `[Unreleased]` block of the working-tree CHANGELOG.md.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} repositoryRoot - Repository root.
|
|
162
|
+
* @returns {{ exists: boolean, entryCount: number, unknownSections: string[] }} Unreleased state.
|
|
163
|
+
*/
|
|
164
|
+
export function readChangelogState(repositoryRoot) {
|
|
165
|
+
const changelogPath = path.join(repositoryRoot, CHANGELOG_FILE);
|
|
166
|
+
|
|
167
|
+
if (!existsSync(changelogPath)) {
|
|
168
|
+
return { exists: false, entryCount: 0, unknownSections: [] };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { exists, entryCount, unknownSections } = readUnreleased(readFileSync(changelogPath, "utf8"));
|
|
172
|
+
return { exists, entryCount, unknownSections };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Runs the project migrations check, turning any failure into an `unknown` result.
|
|
177
|
+
*
|
|
178
|
+
* @param {(() => Promise<MigrationCheck> | MigrationCheck) | null} checkMigrations - Adapter bound to its hook context.
|
|
179
|
+
* @returns {Promise<MigrationCheck | null>} Migration state, or `null` for projects without migrations.
|
|
180
|
+
*/
|
|
181
|
+
export async function readMigrations(checkMigrations) {
|
|
182
|
+
if (!checkMigrations) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
return await checkMigrations();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return { status: MIGRATION_STATUS.unknown, pending: [], target: null, reason: describeError(error) };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Lists the published versions on the registry the working-tree manifest publishes to, so the
|
|
195
|
+
* diagnosis sees the same versions `npm publish` would conflict with.
|
|
196
|
+
*
|
|
197
|
+
* @param {typeof lookupPublishedVersions} lookupNpm - npm lookup adapter.
|
|
198
|
+
* @param {Record<string, unknown>} manifest - Working-tree `package.json`.
|
|
199
|
+
* @param {string} repositoryRoot - Repository root.
|
|
200
|
+
* @returns {Promise<import("./npm.js").NpmLookup>} Published versions, or a failed lookup when the registry is invalid
|
|
201
|
+
* or npm cannot report it.
|
|
202
|
+
*/
|
|
203
|
+
export async function lookupNpmOnPublishRegistry(lookupNpm, manifest, repositoryRoot) {
|
|
204
|
+
const registry = await resolveRegistrySafely(manifest, repositoryRoot);
|
|
205
|
+
|
|
206
|
+
return registry.registryUrl === null
|
|
207
|
+
? failedLookup(registry.reason)
|
|
208
|
+
: lookupNpm(String(manifest.name), repositoryRoot, registry.registryUrl);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Lists the published versions of a package for the diagnosis: on the selected registry, or on the
|
|
213
|
+
* registry the working-tree manifest publishes to when no registry is selected; shared with the monorepo mode.
|
|
214
|
+
*
|
|
215
|
+
* @param {import("./registry.js").RegistrySelection | undefined} registrySelection - Selected registry, if any.
|
|
216
|
+
* @param {Record<string, unknown>} manifest - Working-tree manifest of the package.
|
|
217
|
+
* @param {string} repositoryRoot - Repository root, where routing and credentials are read.
|
|
218
|
+
* @param {string} packageRoot - Package directory.
|
|
219
|
+
* @param {typeof lookupPublishedVersions} lookupNpm - npm lookup adapter.
|
|
220
|
+
* @returns {Promise<import("./npm.js").NpmLookup>} Published versions, or a failed lookup.
|
|
221
|
+
*/
|
|
222
|
+
export function lookupPackageVersions(registrySelection, manifest, repositoryRoot, packageRoot, lookupNpm) {
|
|
223
|
+
return registrySelection ? lookupRegistryVersions(registrySelection, manifest, repositoryRoot, packageRoot, lookupNpm) : lookupNpmOnPublishRegistry(lookupNpm, manifest, repositoryRoot);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Checks the credentials that would publish a package and confirms a first publication against its
|
|
228
|
+
* published versions (see {@link confirmFirstPublication}); shared with the monorepo mode.
|
|
229
|
+
*
|
|
230
|
+
* @param {import("./registry.js").RegistrySelection | undefined} registrySelection - Selected registry, if any.
|
|
231
|
+
* @param {Record<string, unknown>} manifest - Working-tree manifest of the package.
|
|
232
|
+
* @param {string} repositoryRoot - Repository root, where routing and credentials are read.
|
|
233
|
+
* @param {string} packageRoot - Package directory.
|
|
234
|
+
* @param {typeof checkNpmPublishAccess} checkNpmAccess - npm credential check adapter.
|
|
235
|
+
* @param {import("./npm.js").NpmLookup | null} npm - Published versions, or `null` when npm is not tracked.
|
|
236
|
+
* @returns {Promise<import("./npm.js").NpmAuthCheck>} Credential check.
|
|
237
|
+
*/
|
|
238
|
+
export async function checkPackageAccess(registrySelection, manifest, repositoryRoot, packageRoot, checkNpmAccess, npm) {
|
|
239
|
+
const npmAuth = registrySelection ? await checkRegistryAccess(registrySelection, manifest, repositoryRoot, packageRoot, checkNpmAccess) : await checkNpmAuthOnPublishRegistry(checkNpmAccess, manifest, repositoryRoot);
|
|
240
|
+
return confirmFirstPublication(npmAuth, npm);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reads the changelog of a package for the diagnosis: its `[Unreleased]` block and whether it was
|
|
245
|
+
* updated since the last release; shared with the monorepo mode.
|
|
246
|
+
*
|
|
247
|
+
* @param {GitReader} reader - Git reader.
|
|
248
|
+
* @param {string} repositoryRoot - Repository root.
|
|
249
|
+
* @param {string} packageRoot - Package directory holding the changelog.
|
|
250
|
+
* @param {string} changelogPath - Changelog path relative to the root.
|
|
251
|
+
* @param {string | null} lastReleaseSha - Commit of the last release, or `null` without one.
|
|
252
|
+
* @returns {Promise<ReleaseSnapshot["changelog"]>} Changelog state.
|
|
253
|
+
*/
|
|
254
|
+
export async function readPackageChangelogState(reader, repositoryRoot, packageRoot, changelogPath, lastReleaseSha) {
|
|
255
|
+
return {
|
|
256
|
+
...readChangelogState(packageRoot),
|
|
257
|
+
...(await readChangelogUpdateState(reader, repositoryRoot, changelogPath, lastReleaseSha)),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Resolves the publish registry without throwing.
|
|
263
|
+
*
|
|
264
|
+
* @param {Record<string, unknown>} manifest - Working-tree `package.json`.
|
|
265
|
+
* @param {string} repositoryRoot - Repository root.
|
|
266
|
+
* @returns {Promise<{ registryUrl: string | null, reason: string | null }>} Registry, or why it cannot be resolved.
|
|
267
|
+
*/
|
|
268
|
+
async function resolveRegistrySafely(manifest, repositoryRoot) {
|
|
269
|
+
try {
|
|
270
|
+
return { registryUrl: await resolvePublishRegistry(manifest, repositoryRoot), reason: null };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return { registryUrl: null, reason: describeError(error) };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Checks the npm credentials on the registry the working-tree manifest publishes to.
|
|
278
|
+
*
|
|
279
|
+
* @param {typeof checkNpmPublishAccess} checkAccess - npm credential check adapter.
|
|
280
|
+
* @param {Record<string, unknown>} manifest - Working-tree `package.json`.
|
|
281
|
+
* @param {string} repositoryRoot - Repository root.
|
|
282
|
+
* @returns {Promise<import("./npm.js").NpmAuthCheck>} Check result; an `unknown` one when the registry cannot be resolved.
|
|
283
|
+
*/
|
|
284
|
+
export async function checkNpmAuthOnPublishRegistry(checkAccess, manifest, repositoryRoot) {
|
|
285
|
+
const registry = await resolveRegistrySafely(manifest, repositoryRoot);
|
|
286
|
+
|
|
287
|
+
return registry.registryUrl === null
|
|
288
|
+
? { status: NPM_AUTH_STATUS.unknown, user: null, source: null, registryUrl: "", packageName: String(manifest.name), owners: [], firstPublication: false, reason: registry.reason }
|
|
289
|
+
: checkAccess(String(manifest.name), repositoryRoot, registry.registryUrl);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Confirms a first publication (`npm owner ls` answered E404) against the versions `npm view`
|
|
294
|
+
* listed with the same token. Registries hide a private package from users without access, so an
|
|
295
|
+
* owner E404 only means "new package" when `npm view` does not list versions either: when it does,
|
|
296
|
+
* the package exists and the token cannot manage it, which blocks like a user that is not an owner.
|
|
297
|
+
*
|
|
298
|
+
* @param {import("./npm.js").NpmAuthCheck} npmAuth - Credential check.
|
|
299
|
+
* @param {import("./npm.js").NpmLookup | null} npm - Published versions, or `null` when npm is not tracked.
|
|
300
|
+
* @returns {import("./npm.js").NpmAuthCheck} The same check, or a `notOwner` one when the package already has versions.
|
|
301
|
+
*/
|
|
302
|
+
export function confirmFirstPublication(npmAuth, npm) {
|
|
303
|
+
const publishedCount = npm?.publishedVersions.length ?? 0;
|
|
304
|
+
|
|
305
|
+
if (!npmAuth.firstPublication || publishedCount === 0) {
|
|
306
|
+
return npmAuth;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
...npmAuth,
|
|
311
|
+
status: NPM_AUTH_STATUS.notOwner,
|
|
312
|
+
firstPublication: false,
|
|
313
|
+
reason: `npm view lista versiones publicadas de ${npmAuth.packageName} (${publishedCount}), pero npm owner ls respondió ${NPM_NOT_FOUND_CODE}: el token no tiene acceso al paquete.`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @typedef {{
|
|
319
|
+
* currentBranch: string | null,
|
|
320
|
+
* workingTreeChanges: string[],
|
|
321
|
+
* branch: import("./plan.js").FeatureBranchSnapshot | null,
|
|
322
|
+
* pullRequest: PullRequestSnapshot | null,
|
|
323
|
+
* githubError: string | null,
|
|
324
|
+
* main: { aheadCommits: import("./process.js").CommitRecord[], behindCount: number },
|
|
325
|
+
* remoteMainExists: boolean,
|
|
326
|
+
* }} RepositorySnapshot
|
|
327
|
+
* What every release mode reads from Git before looking at packages: the branch and its pull
|
|
328
|
+
* request, the working tree and `main` compared with `origin/main`.
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Fetches `origin` and reads the repository part of the snapshot, shared by the single-package and
|
|
333
|
+
* the monorepo modes.
|
|
334
|
+
*
|
|
335
|
+
* @param {{
|
|
336
|
+
* repositoryRoot: string,
|
|
337
|
+
* reader: GitReader,
|
|
338
|
+
* onProgress: (label: string) => void,
|
|
339
|
+
* lookupPullRequestFor: typeof lookupPullRequest,
|
|
340
|
+
* }} options - Repository, reader, progress callback and pull request adapter.
|
|
341
|
+
* @returns {Promise<RepositorySnapshot>} Repository snapshot.
|
|
342
|
+
*/
|
|
343
|
+
export async function readRepositorySnapshot({ repositoryRoot, reader, onProgress, lookupPullRequestFor }) {
|
|
344
|
+
onProgress("Sincronizando con origin (fetch)");
|
|
345
|
+
await reader.git(["fetch", RELEASE_REMOTE, "--prune", "--tags", "--quiet"]);
|
|
346
|
+
|
|
347
|
+
onProgress("Leyendo el estado de Git");
|
|
348
|
+
const currentBranch = await reader.tryGit(["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
349
|
+
// Every untracked file, not only its directory, so the plan sees the extension of each one; with
|
|
350
|
+
// core.quotePath off, non-ASCII paths come verbatim instead of octal-escaped.
|
|
351
|
+
const statusOutput = await reader.git(["-c", "core.quotePath=false", "status", "--porcelain", "--untracked-files=all"]);
|
|
352
|
+
const workingTreeChanges = statusOutput.split("\n").map((line) => line.trimEnd()).filter(Boolean);
|
|
353
|
+
const localMainExists = (await reader.tryGit(["rev-parse", "--verify", "--quiet", MAIN_BRANCH])) !== null;
|
|
354
|
+
const remoteMainExists = (await reader.tryGit(["rev-parse", "--verify", "--quiet", REMOTE_MAIN_REF])) !== null;
|
|
355
|
+
const aheadCommits = localMainExists && remoteMainExists ? await listCommits(reader, `${REMOTE_MAIN_REF}..${MAIN_BRANCH}`) : [];
|
|
356
|
+
const behindCount =
|
|
357
|
+
localMainExists && remoteMainExists ? Number((await reader.tryGit(["rev-list", "--count", `${MAIN_BRANCH}..${REMOTE_MAIN_REF}`])) ?? 0) : 0;
|
|
358
|
+
|
|
359
|
+
let branch = null;
|
|
360
|
+
let pullRequest = null;
|
|
361
|
+
let githubError = null;
|
|
362
|
+
|
|
363
|
+
if (currentBranch && currentBranch !== MAIN_BRANCH) {
|
|
364
|
+
onProgress(`Revisando la rama ${currentBranch} y su PR`);
|
|
365
|
+
branch = await readFeatureBranch(reader, currentBranch);
|
|
366
|
+
({ pullRequest, githubError } = await lookupPullRequestFor(repositoryRoot, currentBranch));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return { currentBranch, workingTreeChanges, branch, pullRequest, githubError, main: { aheadCommits, behindCount }, remoteMainExists };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Gathers the complete release snapshot.
|
|
374
|
+
*
|
|
375
|
+
* @param {{
|
|
376
|
+
* repositoryRoot: string,
|
|
377
|
+
* trackNpm?: boolean,
|
|
378
|
+
* checkMigrations?: (() => Promise<MigrationCheck> | MigrationCheck) | null,
|
|
379
|
+
* onProgress?: (label: string) => void,
|
|
380
|
+
* lookupNpm?: typeof lookupPublishedVersions,
|
|
381
|
+
* checkNpmAuth?: ((snapshot: ReleaseSnapshot) => boolean) | null,
|
|
382
|
+
* checkNpmAccess?: typeof checkNpmPublishAccess,
|
|
383
|
+
* lookupPullRequestFor?: typeof lookupPullRequest,
|
|
384
|
+
* registrySelection?: import("./registry.js").RegistrySelection,
|
|
385
|
+
* }} options - Repository, adapters and progress callback. `checkNpmAuth` receives the snapshot
|
|
386
|
+
* without credentials and decides whether the npm credentials are checked (when the plan would publish to npm).
|
|
387
|
+
* @returns {Promise<ReleaseSnapshot>} Snapshot accepted by `buildReleasePlan`.
|
|
388
|
+
*/
|
|
389
|
+
export async function collectReleaseState({
|
|
390
|
+
repositoryRoot,
|
|
391
|
+
trackNpm = false,
|
|
392
|
+
checkMigrations = null,
|
|
393
|
+
onProgress = () => {},
|
|
394
|
+
lookupNpm = lookupPublishedVersions,
|
|
395
|
+
checkNpmAuth = null,
|
|
396
|
+
checkNpmAccess = checkNpmPublishAccess,
|
|
397
|
+
lookupPullRequestFor = lookupPullRequest,
|
|
398
|
+
registrySelection,
|
|
399
|
+
}) {
|
|
400
|
+
const reader = createGitReader(repositoryRoot);
|
|
401
|
+
const repository = await readRepositorySnapshot({ repositoryRoot, reader, onProgress, lookupPullRequestFor });
|
|
402
|
+
const { currentBranch, workingTreeChanges, branch, pullRequest, githubError, main, remoteMainExists } = repository;
|
|
403
|
+
const headVersion = await readPackageVersionAt(reader, "HEAD");
|
|
404
|
+
const headSubject = await reader.tryGit(["log", "-1", "--format=%s", "HEAD"]);
|
|
405
|
+
const headSha = await reader.tryGit(["rev-parse", "HEAD"]);
|
|
406
|
+
const headReleaseTag = headSha ? await findReleaseTagAt(reader, headVersion, headSha) : null;
|
|
407
|
+
// Only a detached publication from a tag needs to prove that the tag is on origin.
|
|
408
|
+
const remoteReleaseTagSha = !currentBranch && headReleaseTag ? await readRemoteReleaseTagCommit(reader, headReleaseTag) : null;
|
|
409
|
+
// A tag missing from origin can still be pushed from the detached HEAD when origin/main already has its commit.
|
|
410
|
+
const headOnRemoteMain =
|
|
411
|
+
!currentBranch && headReleaseTag && !remoteReleaseTagSha && remoteMainExists
|
|
412
|
+
? (await reader.tryGit(["merge-base", "--is-ancestor", "HEAD", REMOTE_MAIN_REF])) !== null
|
|
413
|
+
: false;
|
|
414
|
+
const releasedVersion = remoteMainExists ? await readPackageVersionAt(reader, REMOTE_MAIN_REF) : null;
|
|
415
|
+
const lastRelease = remoteMainExists ? await findLastRelease(reader, REMOTE_MAIN_REF) : null;
|
|
416
|
+
const unreleasedCommits = remoteMainExists ? await listCommits(reader, lastRelease ? `${lastRelease.sha}..${REMOTE_MAIN_REF}` : REMOTE_MAIN_REF) : [];
|
|
417
|
+
|
|
418
|
+
const manifest = readJsonManifest(repositoryRoot);
|
|
419
|
+
let npm = null;
|
|
420
|
+
|
|
421
|
+
if (trackNpm) {
|
|
422
|
+
onProgress("Consultando npm");
|
|
423
|
+
npm = await lookupPackageVersions(registrySelection, manifest, repositoryRoot, repositoryRoot, lookupNpm);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (checkMigrations) {
|
|
427
|
+
onProgress("Consultando migraciones pendientes");
|
|
428
|
+
}
|
|
429
|
+
const migrations = await readMigrations(checkMigrations);
|
|
430
|
+
|
|
431
|
+
/** @type {ReleaseSnapshot} */
|
|
432
|
+
const snapshot = {
|
|
433
|
+
packageName: manifest.name,
|
|
434
|
+
currentBranch,
|
|
435
|
+
workingTreeChanges,
|
|
436
|
+
branch,
|
|
437
|
+
pullRequest,
|
|
438
|
+
githubError,
|
|
439
|
+
main,
|
|
440
|
+
headVersion,
|
|
441
|
+
headSubject,
|
|
442
|
+
headSha,
|
|
443
|
+
headReleaseTag,
|
|
444
|
+
remoteReleaseTagSha,
|
|
445
|
+
headOnRemoteMain,
|
|
446
|
+
releasedVersion,
|
|
447
|
+
lastRelease,
|
|
448
|
+
unreleasedCommits,
|
|
449
|
+
npm,
|
|
450
|
+
npmAuth: null,
|
|
451
|
+
migrations,
|
|
452
|
+
changelog: await readPackageChangelogState(reader, repositoryRoot, repositoryRoot, CHANGELOG_FILE, lastRelease?.sha ?? null),
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
if (checkNpmAuth?.(snapshot)) {
|
|
456
|
+
onProgress("Verificando las credenciales de npm");
|
|
457
|
+
snapshot.npmAuth = await checkPackageAccess(registrySelection, manifest, repositoryRoot, repositoryRoot, checkNpmAccess, npm);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return snapshot;
|
|
461
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict UTF-8 decoding for files the release rewrites as text.
|
|
3
|
+
*
|
|
4
|
+
* @module create-version/strict-utf8
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Strict UTF-8 decoder: invalid bytes (such as an ISO-8859-1 file) throw instead of turning into
|
|
9
|
+
* U+FFFD, and a byte order mark is kept as text, so writing the text back never changes other bytes.
|
|
10
|
+
*/
|
|
11
|
+
export const STRICT_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
run-name: "astra-rp release ${{ inputs.tag }} ${{ inputs.sha }}"
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
version:
|
|
8
|
+
description: "Versión creada desde local"
|
|
9
|
+
required: true
|
|
10
|
+
type: string
|
|
11
|
+
tag:
|
|
12
|
+
description: "Tag exacto del release"
|
|
13
|
+
required: true
|
|
14
|
+
type: string
|
|
15
|
+
sha:
|
|
16
|
+
description: "Commit exacto del tag"
|
|
17
|
+
required: true
|
|
18
|
+
type: string
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
__RELEASE_PERMISSIONS__
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: "astra-rp-release"
|
|
26
|
+
cancel-in-progress: false
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
release:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
env:
|
|
32
|
+
HUSKY: '0'
|
|
33
|
+
__RELEASE_ENVIRONMENT__
|
|
34
|
+
steps:
|
|
35
|
+
- name: Checkout del release
|
|
36
|
+
uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
ref: refs/tags/${{ inputs.tag }}
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
persist-credentials: true
|
|
41
|
+
__RELEASE_IDENTITY_CHECK__
|
|
42
|
+
__NODE_SETUP__
|
|
43
|
+
__DENO_SETUP__
|
|
44
|
+
__PACKAGE_SETUP__
|
|
45
|
+
__DEPLOYMENT_SETUP__
|
|
46
|
+
- name: Checks y publicación del release
|
|
47
|
+
env:
|
|
48
|
+
ASTRA_RP_RELEASE_VERSION: ${{ inputs.version }}
|
|
49
|
+
ASTRA_RP_RELEASE_SHA: ${{ inputs.sha }}
|
|
50
|
+
RELEASE_TAG: ${{ inputs.tag }}
|
|
51
|
+
run: __RELEASE_COMMAND__
|
|
52
|
+
__DEPLOYMENT_STEP__
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gates Git deployments while an exact release is owned by CI.
|
|
3
|
+
*
|
|
4
|
+
* A commit cannot contain its own SHA, so the release commit is identified as the one that
|
|
5
|
+
* changes `.astra-rp/release.json` against its first parent; later commits keep the metadata
|
|
6
|
+
* untouched and fall through to the original gate. Without a readable parent (shallow clone or
|
|
7
|
+
* first commit) the release commit is recognized by its own `Astra-Rp-Execution: ci` trailer,
|
|
8
|
+
* which later commits never carry, so the gate stays closed until the release workflow deploys.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
|
|
13
|
+
const RELEASE_METADATA_FILE = '.astra-rp/release.json';
|
|
14
|
+
/** Revision compared against the deployed commit; absent in shallow clones and on the first commit. */
|
|
15
|
+
const PARENT_REVISION = 'HEAD^';
|
|
16
|
+
/** `git diff --quiet` status reporting that the path changed between both commits. */
|
|
17
|
+
const GIT_DIFF_CHANGED_STATUS = 1;
|
|
18
|
+
/** Trailer that `astra-rp create-version` writes in the release commit (mirrors `RELEASE_EXECUTION_TRAILER`). */
|
|
19
|
+
const RELEASE_EXECUTION_TRAILER = 'Astra-Rp-Execution';
|
|
20
|
+
/** Trailer value of a release whose deployment is delegated to CI. */
|
|
21
|
+
const CI_RELEASE_EXECUTION = 'ci';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Runs a read-only Git command in the deployed checkout.
|
|
25
|
+
* @param {string[]} gitArguments - Git arguments.
|
|
26
|
+
* @returns {import("node:child_process").SpawnSyncReturns<string>} Git result.
|
|
27
|
+
*/
|
|
28
|
+
function runGit(gitArguments) {
|
|
29
|
+
return spawnSync('git', gitArguments, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @returns {boolean} Whether the deployed commit carries exactly one `Astra-Rp-Execution: ci` trailer value. */
|
|
33
|
+
function hasCiReleaseTrailer() {
|
|
34
|
+
const trailers = runGit(['log', '-1', `--format=%(trailers:key=${RELEASE_EXECUTION_TRAILER},valueonly=true)`, 'HEAD']);
|
|
35
|
+
if (trailers.status !== 0) return false;
|
|
36
|
+
const recordedValues = [...new Set(trailers.stdout.split('\n').map((value) => value.trim()).filter(Boolean))];
|
|
37
|
+
return recordedValues.length === 1 && recordedValues[0] === CI_RELEASE_EXECUTION;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @returns {boolean} Whether the deployed commit is the one that wrote the release metadata. */
|
|
41
|
+
function isReleaseMetadataCommit() {
|
|
42
|
+
const parent = runGit(['rev-parse', '--verify', '--quiet', `${PARENT_REVISION}^{commit}`]);
|
|
43
|
+
if (parent.status !== 0) return hasCiReleaseTrailer();
|
|
44
|
+
const changed = runGit(['diff', '--quiet', PARENT_REVISION, 'HEAD', '--', RELEASE_METADATA_FILE]);
|
|
45
|
+
return changed.status === GIT_DIFF_CHANGED_STATUS;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let delegated = false;
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(RELEASE_METADATA_FILE)) {
|
|
51
|
+
const metadata = JSON.parse(readFileSync(RELEASE_METADATA_FILE, 'utf8'));
|
|
52
|
+
const manifest = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
53
|
+
delegated = metadata.execution === 'ci' && metadata.version === manifest.version && isReleaseMetadataCommit();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
console.error('No se pudo leer la metadata del release; se omite el despliegue automático.');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
if (delegated) {
|
|
60
|
+
console.log('El release está en CI; producción espera a que pasen los checks.');
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
const originalIgnoreCommand = __ORIGINAL_IGNORE_COMMAND__;
|
|
64
|
+
process.exitCode = originalIgnoreCommand
|
|
65
|
+
? spawnSync(originalIgnoreCommand, { shell: true, stdio: 'inherit', windowsHide: true }).status ?? 0
|
|
66
|
+
: 1;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `prepublishOnly` guard of the packages released with astra-rp: they are published with npm by
|
|
3
|
+
* `create-version` (run through the project's package manager), which verifies the tarball against
|
|
4
|
+
* `npm pack --dry-run`, so a `pnpm publish` (or yarn/bun) is blocked.
|
|
5
|
+
*
|
|
6
|
+
* The guard only blocks package managers it recognizes. Without a user agent,
|
|
7
|
+
* or with an unknown one, the publisher cannot be determined and it allows the
|
|
8
|
+
* publication. It can be bypassed on purpose with `--ignore-scripts`.
|
|
9
|
+
*
|
|
10
|
+
* @module guard-publish
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ALLOWED_PUBLISH_USER_AGENT_PREFIX,
|
|
15
|
+
BLOCKED_PUBLISH_PACKAGE_MANAGERS,
|
|
16
|
+
GUARD_PUBLISH_EXIT_CODE,
|
|
17
|
+
PACKAGE_MANAGER_USER_AGENT_VARIABLE,
|
|
18
|
+
} from "./constants/guard-publish.js";
|
|
19
|
+
import { describeProjectCommands } from "./package-manager.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{ allowed: boolean, packageManager: string | null, message: string | null, exitCode: number }} PublishGuardDecision
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Finds the blocked package manager that runs the publication.
|
|
27
|
+
*
|
|
28
|
+
* @param {string | undefined} userAgent - Value of `npm_config_user_agent`.
|
|
29
|
+
* @returns {string | null} Blocked package manager name, or `null` for npm, unknown or missing user agents.
|
|
30
|
+
*/
|
|
31
|
+
export function findBlockedPublishPackageManager(userAgent) {
|
|
32
|
+
if (!userAgent || userAgent.startsWith(ALLOWED_PUBLISH_USER_AGENT_PREFIX)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const blockedEntry = Object.entries(BLOCKED_PUBLISH_PACKAGE_MANAGERS).find(([userAgentPrefix]) => userAgent.startsWith(userAgentPrefix));
|
|
37
|
+
return blockedEntry ? blockedEntry[1] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builds the explanation printed when a publication is blocked.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} packageManager - Blocked package manager name (pnpm, yarn or bun), which is also
|
|
44
|
+
* the one the project uses: the message names its own create-version command.
|
|
45
|
+
* @returns {string} Message in Spanish with what to run instead.
|
|
46
|
+
*/
|
|
47
|
+
export function buildBlockedPublishMessage(packageManager) {
|
|
48
|
+
const { createVersion } = describeProjectCommands(/** @type {import("./package-manager.js").PackageManagerName} */ (packageManager));
|
|
49
|
+
return (
|
|
50
|
+
`astra-rp guard-publish: no publiques con ${packageManager}. Este paquete se publica con \`${createVersion}\`, ` +
|
|
51
|
+
"que publica con npm y verifica el tarball contra `npm pack --dry-run`."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Decides whether the publication may continue.
|
|
57
|
+
*
|
|
58
|
+
* @param {string | undefined} userAgent - Value of `npm_config_user_agent`.
|
|
59
|
+
* @returns {PublishGuardDecision} Decision, message to print on stderr and exit code.
|
|
60
|
+
*/
|
|
61
|
+
export function decidePublishGuard(userAgent) {
|
|
62
|
+
const packageManager = findBlockedPublishPackageManager(userAgent);
|
|
63
|
+
|
|
64
|
+
return packageManager === null
|
|
65
|
+
? { allowed: true, packageManager: null, message: null, exitCode: GUARD_PUBLISH_EXIT_CODE.allowed }
|
|
66
|
+
: { allowed: false, packageManager, message: buildBlockedPublishMessage(packageManager), exitCode: GUARD_PUBLISH_EXIT_CODE.blocked };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Decides with the user agent of an environment (the current process by default).
|
|
71
|
+
*
|
|
72
|
+
* @param {NodeJS.ProcessEnv} [environment] - Environment of the lifecycle script.
|
|
73
|
+
* @returns {PublishGuardDecision} Decision for its `npm_config_user_agent`.
|
|
74
|
+
*/
|
|
75
|
+
export function decidePublishGuardForEnvironment(environment = process.env) {
|
|
76
|
+
return decidePublishGuard(environment[PACKAGE_MANAGER_USER_AGENT_VARIABLE]);
|
|
77
|
+
}
|