@entro314labs/release-kit 2.0.0 → 2.1.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 +49 -0
- package/package.json +2 -2
- package/release.mjs +178 -47
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ Released v2.5.0
|
|
|
56
56
|
| [⚡ Usage](#-usage) | targets, bumps, flags |
|
|
57
57
|
| [🧩 Steps](#-steps) | the seven steps and how to select them |
|
|
58
58
|
| [🤖 Assistant](#-assistant-optional) | optional AI drafting |
|
|
59
|
+
| [🌍 Any language](#-any-language) | Rust, Python, tag-only, anything |
|
|
59
60
|
| [✅ Preflight](#-preflight) | what is checked before anything mutates |
|
|
60
61
|
| [♻️ Recovering from a failed run](#️-recovering-from-a-failed-run) | why re-running is safe |
|
|
61
62
|
| [⚙️ Configuration](#️-configuration) | `release.config.json`, publishing, auth |
|
|
@@ -249,6 +250,54 @@ it stopped. There is no cleanup step, no `--resume`, and nothing to remember.
|
|
|
249
250
|
The one case that is not recoverable by re-running is a tag that exists at a _different_
|
|
250
251
|
commit than `HEAD`. That is a genuine conflict, and it aborts rather than guessing.
|
|
251
252
|
|
|
253
|
+
## 🌍 Any language
|
|
254
|
+
|
|
255
|
+
Only one step is Node-specific: `publish`. Committing, changelog rolling, tagging, pushing
|
|
256
|
+
and GitHub releases are the same everywhere, so `versionFile` points at wherever a project
|
|
257
|
+
keeps its version and the rest works unchanged.
|
|
258
|
+
|
|
259
|
+
| Project | Config |
|
|
260
|
+
| ------------------------------ | ------------------------------------------------------------------------------- |
|
|
261
|
+
| Node (npm) | nothing — `package.json` and `npm publish` are the defaults |
|
|
262
|
+
| Node (pnpm / bun) | `{"publish": "pnpm publish --tag %d"}` or `{"publish": "bun publish --tag %d"}` |
|
|
263
|
+
| Rust | `{"versionFile": "Cargo.toml", "publish": "cargo publish"}` |
|
|
264
|
+
| Python | `{"versionFile": "pyproject.toml", "publish": "uv publish"}` |
|
|
265
|
+
| Go | `{"versionFile": null, "publish": "go list -m %n@%t"}` — the tag is the release |
|
|
266
|
+
| Anything with a `VERSION` file | `{"versionFile": "VERSION", "publish": null}` |
|
|
267
|
+
| Versioned only by tag | `{"versionFile": null}`, then `release-kit 1.2.3` |
|
|
268
|
+
|
|
269
|
+
The publish step also gets a preflight when the command is one it recognises:
|
|
270
|
+
|
|
271
|
+
| Publish command | Authentication | Already published? |
|
|
272
|
+
| --------------- | ------------------------------------- | ----------------------------------- |
|
|
273
|
+
| `npm` / `pnpm` | `whoami` | `view <name>@<version>` |
|
|
274
|
+
| `bun` | `bun pm whoami` | `bun pm view <name>@<version>` |
|
|
275
|
+
| `uv` | `UV_PUBLISH_TOKEN` in the environment | none — `uv` skips duplicates itself |
|
|
276
|
+
| `go` | none needed | `go list -m <module>@<tag>` |
|
|
277
|
+
|
|
278
|
+
Anything else runs as written with no preflight. The project name comes from the manifest —
|
|
279
|
+
`name` in `package.json`, `Cargo.toml` or `pyproject.toml`, `module` in `go.mod` — falling
|
|
280
|
+
back to the repository directory.
|
|
281
|
+
|
|
282
|
+
The format is inferred from the file name: `.json` reads the `"version"` field, `.toml`
|
|
283
|
+
reads the first `version = "x.y.z"` line, and any other file is treated as containing just
|
|
284
|
+
the version. Only the version itself is rewritten, so comments and formatting survive — and
|
|
285
|
+
because the TOML match is anchored to the start of a line, a dependency's
|
|
286
|
+
`serde = { version = "1.0" }` is left alone.
|
|
287
|
+
|
|
288
|
+
For anything else, give a pattern with one capture group around the version. `versionFiles`
|
|
289
|
+
takes the same entries, so several files stay in sync across formats:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"versionFile": { "path": "version.go", "pattern": "^const Version = \"(.+)\"" },
|
|
294
|
+
"versionFiles": [{ "path": "Chart.yaml", "pattern": "^version: (.+)$" }]
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The project name comes from the manifest when there is one (`name` in `package.json`,
|
|
299
|
+
`Cargo.toml` or `pyproject.toml`), and falls back to the repository directory.
|
|
300
|
+
|
|
252
301
|
## ⚙️ Configuration
|
|
253
302
|
|
|
254
303
|
`release.config.json`, beside `package.json`. Every key is optional; unknown keys abort
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entro314labs/release-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Single-file, zero-dependency release mechanism for JS/TS/Node projects: version bump, changelog roll, commit, annotated tag, push, publish, GitHub release",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
"oxlint": "^1.78.0"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
|
-
"node": ">=
|
|
52
|
+
"node": ">=22"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/release.mjs
CHANGED
|
@@ -51,7 +51,10 @@ import { createInterface } from 'node:readline/promises'
|
|
|
51
51
|
* branch string the only branch a release may run from; null to allow any
|
|
52
52
|
* remote string git remote to push to
|
|
53
53
|
* changelog string changelog path; null to disable changelog handling
|
|
54
|
-
*
|
|
54
|
+
* versionFile string|object|null where the project's version lives; null when the
|
|
55
|
+
* repository versions by git tag alone
|
|
56
|
+
* versionFiles array further files whose version is kept in sync; each is a path
|
|
57
|
+
* or { path, pattern }
|
|
55
58
|
* publish string publish command; null to skip publishing entirely
|
|
56
59
|
* commitMessage string release commit subject
|
|
57
60
|
* releaseTitle string GitHub release title
|
|
@@ -88,6 +91,7 @@ const DEFAULTS = {
|
|
|
88
91
|
branch: 'main',
|
|
89
92
|
remote: 'origin',
|
|
90
93
|
changelog: 'CHANGELOG.md',
|
|
94
|
+
versionFile: 'package.json',
|
|
91
95
|
versionFiles: [],
|
|
92
96
|
publish: 'npm publish --tag %d',
|
|
93
97
|
commitMessage: 'chore(release): %t',
|
|
@@ -645,18 +649,84 @@ function insertChangelogSection(text, version, date, body) {
|
|
|
645
649
|
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'))
|
|
646
650
|
|
|
647
651
|
/**
|
|
648
|
-
*
|
|
649
|
-
*
|
|
652
|
+
* Where a project keeps its version. The format is inferred from the file name, so the
|
|
653
|
+
* common cases need nothing but a path:
|
|
654
|
+
*
|
|
655
|
+
* *.json the JSON `"version": "x.y.z"` field
|
|
656
|
+
* *.toml the first `version = "x.y.z"` line — the `[package]` / `[project]`
|
|
657
|
+
* table comes first in Cargo.toml and pyproject.toml
|
|
658
|
+
* anything else the whole file is the version (a plain VERSION file)
|
|
659
|
+
*
|
|
660
|
+
* An explicit `pattern` overrides inference for formats not listed. Whatever the source,
|
|
661
|
+
* it must capture the version in exactly one group, which is what gets replaced on write.
|
|
662
|
+
*/
|
|
663
|
+
const VERSION_PATTERNS = {
|
|
664
|
+
json: /^\s*"version"\s*:\s*"([^"]*)"/m,
|
|
665
|
+
toml: /^version\s*=\s*"([^"]*)"/m,
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** The project name in the same files, used for display and the registry lookup. */
|
|
669
|
+
const NAME_PATTERNS = {
|
|
670
|
+
json: /^\s*"name"\s*:\s*"([^"]*)"/m,
|
|
671
|
+
toml: /^name\s*=\s*"([^"]*)"/m,
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** @returns {string | null} the project name recorded beside the version */
|
|
675
|
+
function readNameFrom(entry) {
|
|
676
|
+
const source = versionSource(entry)
|
|
677
|
+
const kind = source.path.endsWith('.json')
|
|
678
|
+
? 'json'
|
|
679
|
+
: source.path.endsWith('.toml')
|
|
680
|
+
? 'toml'
|
|
681
|
+
: null
|
|
682
|
+
if (!kind || !existsSync(source.path)) return null
|
|
683
|
+
return NAME_PATTERNS[kind].exec(readFileSync(source.path, 'utf8'))?.[1] ?? null
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** Normalise a versionFile / versionFiles entry to { path, pattern }. */
|
|
687
|
+
const versionSource = (entry) => (typeof entry === 'string' ? { path: entry } : entry)
|
|
688
|
+
|
|
689
|
+
/** The regex for a source, or null when the whole file is the version. */
|
|
690
|
+
function patternFor({ path, pattern }) {
|
|
691
|
+
if (pattern) return new RegExp(pattern, 'm')
|
|
692
|
+
if (path.endsWith('.json')) return VERSION_PATTERNS.json
|
|
693
|
+
if (path.endsWith('.toml')) return VERSION_PATTERNS.toml
|
|
694
|
+
return null
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** @returns {string | null} the version recorded in a source file */
|
|
698
|
+
function readVersionFrom(entry) {
|
|
699
|
+
const source = versionSource(entry)
|
|
700
|
+
const text = readFileSync(source.path, 'utf8')
|
|
701
|
+
const pattern = patternFor(source)
|
|
702
|
+
if (!pattern) return text.trim() || null
|
|
703
|
+
const match = pattern.exec(text)
|
|
704
|
+
return match ? match[1] : null
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Replace the version in a source file, touching nothing else: only the captured range is
|
|
709
|
+
* rewritten, so formatting, key order and comments all survive.
|
|
650
710
|
*
|
|
651
711
|
* @returns {boolean} whether the file needed changing
|
|
652
712
|
*/
|
|
653
|
-
function writeVersionInto(
|
|
654
|
-
const
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
713
|
+
function writeVersionInto(entry, version) {
|
|
714
|
+
const source = versionSource(entry)
|
|
715
|
+
const text = readFileSync(source.path, 'utf8')
|
|
716
|
+
const pattern = patternFor(source)
|
|
717
|
+
|
|
718
|
+
let updated
|
|
719
|
+
if (pattern) {
|
|
720
|
+
const match = pattern.exec(text)
|
|
721
|
+
if (!match) throw new Error(`${source.path} has no version matching ${pattern}`)
|
|
722
|
+
const start = match.index + match[0].indexOf(match[1])
|
|
723
|
+
updated = text.slice(0, start) + version + text.slice(start + match[1].length)
|
|
724
|
+
} else {
|
|
725
|
+
updated = `${version}\n`
|
|
726
|
+
}
|
|
727
|
+
|
|
658
728
|
if (updated === text) return false
|
|
659
|
-
if (!dryRun) writeFileSync(path, updated)
|
|
729
|
+
if (!dryRun) writeFileSync(source.path, updated)
|
|
660
730
|
return true
|
|
661
731
|
}
|
|
662
732
|
|
|
@@ -771,11 +841,6 @@ if (existsSync(localManifest) && localManifest !== rootManifest) {
|
|
|
771
841
|
}
|
|
772
842
|
process.chdir(root)
|
|
773
843
|
|
|
774
|
-
if (!existsSync('package.json')) abort(`no package.json at ${root}`)
|
|
775
|
-
const pkg = readJson('package.json')
|
|
776
|
-
if (!pkg.version) abort('package.json has no "version" field')
|
|
777
|
-
if (!parseVersion(pkg.version)) abort(`package.json version "${pkg.version}" is not semver`)
|
|
778
|
-
|
|
779
844
|
const config = {
|
|
780
845
|
...DEFAULTS,
|
|
781
846
|
...(existsSync('release.config.json') ? readJson('release.config.json') : {}),
|
|
@@ -793,6 +858,34 @@ const parseStepList = (value) =>
|
|
|
793
858
|
.map((name) => name.trim())
|
|
794
859
|
.filter(Boolean)
|
|
795
860
|
|
|
861
|
+
/**
|
|
862
|
+
* The project's current version and name. Both normally come from package.json, but the
|
|
863
|
+
* only Node-specific thing about a release is publishing: `versionFile` points at whatever
|
|
864
|
+
* file this project keeps its version in, and `null` means the repository versions by git
|
|
865
|
+
* tag alone and the version has to be passed explicitly.
|
|
866
|
+
*/
|
|
867
|
+
const manifest = existsSync('package.json') ? readJson('package.json') : null
|
|
868
|
+
const versionFile = config.versionFile ? versionSource(config.versionFile) : null
|
|
869
|
+
|
|
870
|
+
if (versionFile && !existsSync(versionFile.path)) {
|
|
871
|
+
abort(`versionFile ${versionFile.path} does not exist`)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const currentVersion = versionFile ? readVersionFrom(versionFile) : null
|
|
875
|
+
if (versionFile && !currentVersion) {
|
|
876
|
+
abort(`could not read a version from ${versionFile.path}`)
|
|
877
|
+
}
|
|
878
|
+
if (currentVersion && !parseVersion(currentVersion)) {
|
|
879
|
+
abort(`${versionFile.path} version "${currentVersion}" is not semver`)
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/** Used for display, the registry lookup, and the %n token. */
|
|
883
|
+
const goModule = existsSync('go.mod')
|
|
884
|
+
? (/^module\s+(\S+)/m.exec(readFileSync('go.mod', 'utf8'))?.[1] ?? null)
|
|
885
|
+
: null
|
|
886
|
+
const projectName =
|
|
887
|
+
manifest?.name ?? (versionFile ? readNameFrom(versionFile) : null) ?? goModule ?? basename(root)
|
|
888
|
+
|
|
796
889
|
// Validate every name that was asked for, not just the ones that survive: a typo in
|
|
797
890
|
// --skip would otherwise delete nothing and silently run the step you meant to drop.
|
|
798
891
|
const requestedStepNames = [
|
|
@@ -850,20 +943,30 @@ const assistant = assistantName ? ASSISTANTS[assistantName] : null
|
|
|
850
943
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
851
944
|
|
|
852
945
|
console.log(
|
|
853
|
-
bold(`${
|
|
946
|
+
bold(`${projectName} release`) +
|
|
947
|
+
(dryRun ? ` ${yellow('(dry run — nothing will execute)')}` : ''),
|
|
854
948
|
)
|
|
855
949
|
|
|
856
950
|
let version
|
|
857
951
|
if (!target) {
|
|
858
|
-
|
|
952
|
+
if (!currentVersion) {
|
|
953
|
+
abort(
|
|
954
|
+
'this repository has no versionFile, so there is no version to default to.\n' +
|
|
955
|
+
' Pass one explicitly: release-kit 1.2.3',
|
|
956
|
+
)
|
|
957
|
+
}
|
|
958
|
+
version = currentVersion
|
|
859
959
|
} else if (BUMPS.has(target)) {
|
|
860
|
-
|
|
960
|
+
if (!currentVersion) {
|
|
961
|
+
abort(`a ${target} bump needs a versionFile to bump from. Pass a version explicitly instead.`)
|
|
962
|
+
}
|
|
963
|
+
const preid = requestedPreid ?? preidOf(currentVersion)
|
|
861
964
|
if (target.startsWith('pre') && !preid) {
|
|
862
965
|
abort(
|
|
863
966
|
`a ${target} bump from a stable version needs --preid <${[...KNOWN_CHANNELS].sort().join('|')}>`,
|
|
864
967
|
)
|
|
865
968
|
}
|
|
866
|
-
version = incrementVersion(
|
|
969
|
+
version = incrementVersion(currentVersion, target, preid)
|
|
867
970
|
} else if (parseVersion(target)) {
|
|
868
971
|
version = target
|
|
869
972
|
} else {
|
|
@@ -872,7 +975,7 @@ if (!target) {
|
|
|
872
975
|
|
|
873
976
|
const tag = `${config.tagPrefix}${version}`
|
|
874
977
|
const isPrerelease = parseVersion(version).pre.length > 0
|
|
875
|
-
const bumping = version !==
|
|
978
|
+
const bumping = !!versionFile && version !== currentVersion && runs('version')
|
|
876
979
|
|
|
877
980
|
let distTag
|
|
878
981
|
try {
|
|
@@ -885,7 +988,7 @@ const expandWith = (template, transform) =>
|
|
|
885
988
|
template
|
|
886
989
|
.replaceAll('%v', transform(version))
|
|
887
990
|
.replaceAll('%t', transform(tag))
|
|
888
|
-
.replaceAll('%n', transform(
|
|
991
|
+
.replaceAll('%n', transform(projectName))
|
|
889
992
|
.replaceAll('%d', transform(distTag))
|
|
890
993
|
|
|
891
994
|
/** Expand tokens for a message or title, which never reaches a shell. */
|
|
@@ -902,14 +1005,29 @@ const expandShell = (template) => expandWith(template, shellQuote)
|
|
|
902
1005
|
const publishCommand = runs('publish') && config.publish ? expandShell(config.publish) : null
|
|
903
1006
|
|
|
904
1007
|
/**
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
1008
|
+
* Registries whose preflight can be run, keyed by the first word of the publish command.
|
|
1009
|
+
* Each declares how that CLI answers "who am I" and "does this version already exist";
|
|
1010
|
+
* either may be null when the tool has no such notion. A publish command outside this
|
|
1011
|
+
* table (vsce, a shell pipeline) is run as written with no preflight — it cannot be
|
|
1012
|
+
* introspected, and guessing would invent failures.
|
|
909
1013
|
*/
|
|
910
|
-
const
|
|
1014
|
+
const REGISTRIES = {
|
|
1015
|
+
npm: { whoami: ['whoami'], published: (name, v) => ['view', `${name}@${v}`, 'version'] },
|
|
1016
|
+
pnpm: { whoami: ['whoami'], published: (name, v) => ['view', `${name}@${v}`, 'version'] },
|
|
1017
|
+
bun: {
|
|
1018
|
+
whoami: ['pm', 'whoami'],
|
|
1019
|
+
published: (name, v) => ['pm', 'view', `${name}@${v}`, 'version'],
|
|
1020
|
+
},
|
|
1021
|
+
// uv authenticates with a token from the environment rather than a logged-in session,
|
|
1022
|
+
// and skips duplicate uploads itself via --check-url, so there is no version lookup.
|
|
1023
|
+
uv: { env: ['UV_PUBLISH_TOKEN', 'UV_PUBLISH_PASSWORD'], login: 'set UV_PUBLISH_TOKEN' },
|
|
1024
|
+
// For Go the tag is the release; `go list` warms the module proxy and doubles as the
|
|
1025
|
+
// check for whether this version is already resolvable.
|
|
1026
|
+
go: { published: (name, v) => ['list', '-m', `${name}@${v}`] },
|
|
1027
|
+
}
|
|
1028
|
+
|
|
911
1029
|
const publishCli = publishCommand?.trim().split(/\s+/)[0]
|
|
912
|
-
const
|
|
1030
|
+
const registry = publishCli ? REGISTRIES[publishCli] : null
|
|
913
1031
|
|
|
914
1032
|
/**
|
|
915
1033
|
* CI publishing over OIDC ("trusted publishing") carries no token at all: `whoami` fails
|
|
@@ -923,7 +1041,9 @@ const isTrustedPublishing =
|
|
|
923
1041
|
!!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) ||
|
|
924
1042
|
!!process.env.NPM_ID_TOKEN
|
|
925
1043
|
|
|
926
|
-
console.log(
|
|
1044
|
+
console.log(
|
|
1045
|
+
` ${dim(`${currentVersion ?? '(no version file)'} → ${version} tag ${tag} dist-tag ${distTag}`)}`,
|
|
1046
|
+
)
|
|
927
1047
|
console.log(` ${dim(`steps: ${STEPS.filter(runs).join(' → ')}`)}`)
|
|
928
1048
|
|
|
929
1049
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -938,12 +1058,14 @@ const fail = (message) => {
|
|
|
938
1058
|
problems.push(message)
|
|
939
1059
|
}
|
|
940
1060
|
|
|
941
|
-
if (bumping && compareVersions(version,
|
|
942
|
-
fail(`${version} is not greater than the current version ${
|
|
1061
|
+
if (bumping && compareVersions(version, currentVersion) <= 0) {
|
|
1062
|
+
fail(`${version} is not greater than the current version ${currentVersion}`)
|
|
943
1063
|
} else if (bumping) {
|
|
944
|
-
ok(`version ${
|
|
1064
|
+
ok(`version ${currentVersion} → ${version}`)
|
|
1065
|
+
} else if (versionFile) {
|
|
1066
|
+
ok(`releasing the version already in ${versionFile.path} (${version})`)
|
|
945
1067
|
} else {
|
|
946
|
-
ok(`releasing
|
|
1068
|
+
ok(`releasing ${version} (no version file; the tag is the version)`)
|
|
947
1069
|
}
|
|
948
1070
|
|
|
949
1071
|
const dirty = tryRead('git', ['status', '--porcelain'])
|
|
@@ -1030,27 +1152,35 @@ if (!runs('release')) {
|
|
|
1030
1152
|
let alreadyPublished = false
|
|
1031
1153
|
if (!publishCommand) {
|
|
1032
1154
|
note(runs('publish') ? 'no publish command configured' : 'publish step not selected')
|
|
1033
|
-
} else if (
|
|
1155
|
+
} else if (manifest?.private) {
|
|
1034
1156
|
fail('package.json is private but a publish command is configured')
|
|
1035
|
-
} else if (!
|
|
1157
|
+
} else if (!registry) {
|
|
1036
1158
|
ok(`publish: ${publishCommand}`)
|
|
1037
1159
|
} else {
|
|
1038
1160
|
if (isTrustedPublishing) {
|
|
1039
1161
|
ok('trusted publishing (OIDC) — no token needed')
|
|
1040
|
-
} else {
|
|
1041
|
-
|
|
1162
|
+
} else if (registry.env) {
|
|
1163
|
+
// Token-in-the-environment auth: there is no session to interrogate, only credentials.
|
|
1164
|
+
const found = registry.env.find((name) => process.env[name])
|
|
1165
|
+
if (found) ok(`${publishCli} credentials found (${found})`)
|
|
1166
|
+
else fail(`${publishCli} has no publish credentials — ${registry.login}`)
|
|
1167
|
+
} else if (registry.whoami) {
|
|
1168
|
+
const user = tryRead(publishCli, registry.whoami)
|
|
1042
1169
|
if (user === null) {
|
|
1043
1170
|
// npm replaced long-lived tokens with two-hour sessions in December 2025, so the
|
|
1044
1171
|
// usual cause is an expired session rather than a missing login.
|
|
1045
1172
|
fail(
|
|
1046
|
-
`${
|
|
1173
|
+
`${publishCli} is not authenticated — run \`${publishCli} login\`. ` +
|
|
1047
1174
|
'npm logins are two-hour sessions, so an earlier one may have expired.',
|
|
1048
1175
|
)
|
|
1049
|
-
} else ok(`${
|
|
1176
|
+
} else ok(`${publishCli} authenticated (${user || 'unknown user'})`)
|
|
1050
1177
|
}
|
|
1051
|
-
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1178
|
+
|
|
1179
|
+
if (registry.published) {
|
|
1180
|
+
alreadyPublished = succeeds(publishCli, registry.published(projectName, version))
|
|
1181
|
+
if (alreadyPublished) {
|
|
1182
|
+
note(`${projectName}@${version} is already published — will skip the publish step`)
|
|
1183
|
+
}
|
|
1054
1184
|
}
|
|
1055
1185
|
}
|
|
1056
1186
|
|
|
@@ -1128,7 +1258,7 @@ if (!assumeYes && !dryRun && process.stdin.isTTY) {
|
|
|
1128
1258
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
1129
1259
|
let answer = ''
|
|
1130
1260
|
try {
|
|
1131
|
-
answer = await rl.question(`\nRelease ${bold(tag)} of ${
|
|
1261
|
+
answer = await rl.question(`\nRelease ${bold(tag)} of ${projectName}? [y/N] `)
|
|
1132
1262
|
} catch {
|
|
1133
1263
|
// Ctrl+C or Ctrl+D at the prompt rejects the question. That is a decline, not a
|
|
1134
1264
|
// crash — without this it exits on an unhandled AbortError and a stack trace.
|
|
@@ -1168,11 +1298,12 @@ if (dirty && runs('commit')) {
|
|
|
1168
1298
|
|
|
1169
1299
|
if (bumping) {
|
|
1170
1300
|
step(`Write version ${version}`)
|
|
1171
|
-
for (const
|
|
1172
|
-
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
|
|
1301
|
+
for (const entry of [versionFile, ...config.versionFiles]) {
|
|
1302
|
+
const source = versionSource(entry)
|
|
1303
|
+
if (!existsSync(source.path)) abort(`versionFiles entry ${source.path} does not exist`)
|
|
1304
|
+
if (writeVersionInto(source, version)) {
|
|
1305
|
+
staged.push(source.path)
|
|
1306
|
+
console.log(` ${dryRun ? yellow('would write') : dim('wrote')} ${source.path}`)
|
|
1176
1307
|
}
|
|
1177
1308
|
}
|
|
1178
1309
|
// A package-lock.json embeds the root version twice, so it goes stale on a bump.
|
|
@@ -1222,7 +1353,7 @@ if (runs('tag') && !taggedCommit) {
|
|
|
1222
1353
|
tag,
|
|
1223
1354
|
'--cleanup=verbatim',
|
|
1224
1355
|
'-m',
|
|
1225
|
-
`${notes ?? `${
|
|
1356
|
+
`${notes ?? `${projectName} ${tag}`}\n`,
|
|
1226
1357
|
])
|
|
1227
1358
|
}
|
|
1228
1359
|
|