@smoothbricks/cli 0.3.0 → 0.3.2
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 +2 -1
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +6 -0
- package/dist/release/candidates.d.ts +1 -0
- package/dist/release/candidates.d.ts.map +1 -1
- package/dist/release/candidates.js +13 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +22 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +1 -5
- package/managed/raw/tooling/direnv/repo-path +34 -0
- package/managed/raw/tooling/git-hooks/commit-msg.sh +1 -1
- package/managed/raw/tooling/git-hooks/pre-commit.sh +1 -1
- package/managed/templates/github/actions/save-nix-devenv/action.yml +3 -3
- package/package.json +3 -3
- package/src/monorepo/managed-files.ts +6 -0
- package/src/monorepo/package-policy.test.ts +8 -8
- package/src/release/__tests__/candidates.test.ts +57 -1
- package/src/release/candidates.ts +14 -1
- package/src/release/index.ts +23 -0
package/README.md
CHANGED
|
@@ -80,7 +80,8 @@ Packages with internal workspace dependencies also need Nx-aware scripts so depe
|
|
|
80
80
|
For safe build, test, typecheck, benchmark, dev, and preview commands, `smoo monorepo validate --fix` moves the real
|
|
81
81
|
command into `package.json` `nx.targets.<target>.options.command`, sets `cwd` to `{projectRoot}`, and replaces the
|
|
82
82
|
script with an `nx run <project>:<target>` alias. Continuous commands such as `astro dev`, `vite dev`, and previews get
|
|
83
|
-
|
|
83
|
+
an explicit output style on the alias and `continuous: true` on the Nx target. Astro/Vite dev servers use
|
|
84
|
+
`--outputStyle=dynamic-legacy`; other continuous targets use `--outputStyle=stream`. Simple leading environment
|
|
84
85
|
assignments are moved into `nx.targets.<target>.options.env` so commands such as
|
|
85
86
|
`NODE_OPTIONS='--import=extensionless/register' astro dev` remain shell-independent.
|
|
86
87
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;CACxG;
|
|
1
|
+
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,GAAG,YAAY,CAAC;CACxG;AA8ED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,EAAE,CAG/F;AA0ED,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzD"}
|
|
@@ -4,6 +4,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { listReleasePackages, readPackageJson } from '../lib/workspace.js';
|
|
5
5
|
import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
6
6
|
const managedFiles = [
|
|
7
|
+
{
|
|
8
|
+
kind: 'raw',
|
|
9
|
+
source: 'tooling/direnv/repo-path',
|
|
10
|
+
target: 'tooling/direnv/repo-path',
|
|
11
|
+
executable: true,
|
|
12
|
+
},
|
|
7
13
|
{
|
|
8
14
|
kind: 'raw',
|
|
9
15
|
source: 'tooling/direnv/github-actions-bootstrap.sh',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReleasePackageInfo } from './core.js';
|
|
2
2
|
export interface AutoReleaseCandidateShell {
|
|
3
3
|
gitRefExists(ref: string): Promise<boolean>;
|
|
4
|
+
latestStableReleaseRef(projectName: string): Promise<string | null>;
|
|
4
5
|
packageChangedFilesSince(ref: string, packagePath: string): Promise<string[]>;
|
|
5
6
|
packageJsonAtRef(ref: string, packagePath: string): Promise<Record<string, unknown> | null>;
|
|
6
7
|
currentPackageJson(packagePath: string): Promise<Record<string, unknown> | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/release/candidates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGpD,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5F,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACjF,yBAAyB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvF,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAgCD,wBAAsB,4BAA4B,CAAC,OAAO,SAAS,kBAAkB,EACnF,KAAK,EAAE,yBAAyB,EAChC,QAAQ,EAAE,OAAO,EAAE,GAClB,OAAO,CAAC,OAAO,EAAE,CAAC,CAQpB"}
|
|
1
|
+
{"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/release/candidates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGpD,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpE,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5F,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACjF,yBAAyB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvF,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAgCD,wBAAsB,4BAA4B,CAAC,OAAO,SAAS,kBAAkB,EACnF,KAAK,EAAE,yBAAyB,EAChC,QAAQ,EAAE,OAAO,EAAE,GAClB,OAAO,CAAC,OAAO,EAAE,CAAC,CAQpB"}
|
|
@@ -43,7 +43,19 @@ async function isAutoReleaseCandidate(shell, pkg) {
|
|
|
43
43
|
if (await shell.gitRefExists(tagRef)) {
|
|
44
44
|
return packageHasReleasableChangesSince(shell, tagRef, pkg);
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
if (pkg.version.includes('-')) {
|
|
47
|
+
// Prerelease version (e.g. 1.0.1-next.0) without its own tag: check for
|
|
48
|
+
// releasable changes since the last stable release. The prerelease bump is
|
|
49
|
+
// added after every release to make it obvious during development that the
|
|
50
|
+
// working version is unreleased — it should not suppress the next release.
|
|
51
|
+
const stableRef = await shell.latestStableReleaseRef(pkg.projectName);
|
|
52
|
+
if (stableRef) {
|
|
53
|
+
return packageHasReleasableChangesSince(shell, stableRef, pkg);
|
|
54
|
+
}
|
|
55
|
+
// No prior stable release — first release must be intentional, not auto.
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return shell.packageHasHistory(pkg.path);
|
|
47
59
|
}
|
|
48
60
|
async function packageHasReleasableChangesSince(shell, ref, pkg) {
|
|
49
61
|
const changedFiles = await shell.packageChangedFilesSince(ref, pkg.path);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
|
package/dist/release/index.js
CHANGED
|
@@ -337,6 +337,7 @@ async function releaseVersionPackages(root, packages, bump) {
|
|
|
337
337
|
}
|
|
338
338
|
return autoReleaseCandidatePackages({
|
|
339
339
|
gitRefExists: (ref) => gitRefExists(root, ref),
|
|
340
|
+
latestStableReleaseRef: (projectName) => latestStableReleaseRef(root, projectName),
|
|
340
341
|
packageChangedFilesSince: (ref, packagePath) => packageChangedFilesSince(root, ref, packagePath),
|
|
341
342
|
packageJsonAtRef: (ref, packagePath) => packageJsonAtRef(root, ref, packagePath),
|
|
342
343
|
currentPackageJson: (packagePath) => currentPackageJson(root, packagePath),
|
|
@@ -795,6 +796,27 @@ async function gitRefExists(root, ref) {
|
|
|
795
796
|
const result = await $ `git rev-parse --verify ${ref}`.cwd(root).quiet().nothrow();
|
|
796
797
|
return result.exitCode === 0;
|
|
797
798
|
}
|
|
799
|
+
async function latestStableReleaseRef(root, projectName) {
|
|
800
|
+
// List tags matching <projectName>@* sorted newest-first by version, then
|
|
801
|
+
// return the first non-prerelease tag as a full ref.
|
|
802
|
+
const pattern = `${projectName}@*`;
|
|
803
|
+
const result = await $ `git tag --list ${pattern} --sort=-v:refname`.cwd(root).quiet().nothrow();
|
|
804
|
+
if (result.exitCode !== 0) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
const tags = decode(result.stdout)
|
|
808
|
+
.split('\n')
|
|
809
|
+
.map((t) => t.trim())
|
|
810
|
+
.filter(Boolean);
|
|
811
|
+
const prefix = `${projectName}@`;
|
|
812
|
+
for (const tagName of tags) {
|
|
813
|
+
const version = tagName.slice(prefix.length);
|
|
814
|
+
if (version && !version.includes('-')) {
|
|
815
|
+
return `refs/tags/${tagName}`;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
798
820
|
async function packageChangedFilesSince(root, ref, packagePath) {
|
|
799
821
|
const result = await $ `git diff --name-only ${`${ref}..HEAD`} -- ${packagePath}`.cwd(root).quiet().nothrow();
|
|
800
822
|
if (result.exitCode !== 0) {
|
|
@@ -14,11 +14,7 @@ clear_devenv_cache_state() {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
add_repo_paths() {
|
|
17
|
-
|
|
18
|
-
echo "$repo_root/tooling/direnv/.devenv/profile/bin"
|
|
19
|
-
echo "$repo_root/node_modules/.bin"
|
|
20
|
-
echo "$repo_root/tooling"
|
|
21
|
-
} >> "$GITHUB_PATH"
|
|
17
|
+
"$repo_root/tooling/direnv/repo-path" --github-path
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
restore_nix_store() {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Single source of truth for repo-local PATH entries.
|
|
3
|
+
# All tooling that sets up PATH must call this instead of hardcoding paths.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# export PATH="$(tooling/direnv/repo-path)" # shell
|
|
7
|
+
# tooling/direnv/repo-path --github-path # CI — appends to $GITHUB_PATH
|
|
8
|
+
# tooling/direnv/repo-path --print # debug — one entry per line
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
12
|
+
|
|
13
|
+
# Order: most-specific → least-specific.
|
|
14
|
+
entries=(
|
|
15
|
+
"$root/tooling/direnv/.devenv/profile/bin"
|
|
16
|
+
"$root/tooling/git-hooks"
|
|
17
|
+
"$root/tooling"
|
|
18
|
+
"$root/tooling/node_modules/.bin"
|
|
19
|
+
"$root/node_modules/.bin"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
case "${1:-}" in
|
|
23
|
+
--github-path)
|
|
24
|
+
# GITHUB_PATH prepends entries; last line written = first in PATH.
|
|
25
|
+
# Reverse so most-specific directories are searched first.
|
|
26
|
+
for (( i=${#entries[@]}-1; i>=0; i-- )); do
|
|
27
|
+
echo "${entries[i]}"
|
|
28
|
+
done >> "$GITHUB_PATH" ;;
|
|
29
|
+
--print)
|
|
30
|
+
printf '%s\n' "${entries[@]}" ;;
|
|
31
|
+
*)
|
|
32
|
+
IFS=:
|
|
33
|
+
echo "${entries[*]}:$PATH" ;;
|
|
34
|
+
esac
|
|
@@ -20,9 +20,9 @@ runs:
|
|
|
20
20
|
working-directory: tooling/direnv
|
|
21
21
|
# build-shell normally adds repo paths to GITHUB_PATH, but this cleanup
|
|
22
22
|
# must still run when build-shell fails before that happens.
|
|
23
|
-
run:
|
|
24
|
-
PATH="$
|
|
25
|
-
github-ci cleanup-cache
|
|
23
|
+
run: |
|
|
24
|
+
export PATH="$(../../tooling/direnv/repo-path)"
|
|
25
|
+
smoo github-ci cleanup-cache
|
|
26
26
|
|
|
27
27
|
- name: 🧊 Save Nix profiles and store NAR
|
|
28
28
|
if: ${{ steps.cleanup-cache.outputs.cache-ready == 'true' && inputs.nix-cache-hit != 'true' }}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SmoothBricks monorepo automation CLI",
|
|
6
6
|
"bin": {
|
|
7
7
|
"smoo": "./bin/smoo"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "nx run cli:test --
|
|
10
|
+
"test": "nx run cli:test --outputStyle=stream"
|
|
11
11
|
},
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"module": "./dist/index.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
48
|
-
"@smoothbricks/nx-plugin": "0.2.0",
|
|
48
|
+
"@smoothbricks/nx-plugin": "0.2.2-next.0",
|
|
49
49
|
"@smoothbricks/validation": "0.1.1",
|
|
50
50
|
"commander": "^14.0.3",
|
|
51
51
|
"publint": "^0.3.18",
|
|
@@ -26,6 +26,12 @@ interface ManagedFileContext {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const managedFiles: ManagedFile[] = [
|
|
29
|
+
{
|
|
30
|
+
kind: 'raw',
|
|
31
|
+
source: 'tooling/direnv/repo-path',
|
|
32
|
+
target: 'tooling/direnv/repo-path',
|
|
33
|
+
executable: true,
|
|
34
|
+
},
|
|
29
35
|
{
|
|
30
36
|
kind: 'raw',
|
|
31
37
|
source: 'tooling/direnv/github-actions-bootstrap.sh',
|
|
@@ -603,7 +603,7 @@ describe('workspace package script policy', () => {
|
|
|
603
603
|
},
|
|
604
604
|
},
|
|
605
605
|
},
|
|
606
|
-
scripts: { test: 'nx run native:test --
|
|
606
|
+
scripts: { test: 'nx run native:test --outputStyle=stream' },
|
|
607
607
|
},
|
|
608
608
|
],
|
|
609
609
|
});
|
|
@@ -800,9 +800,9 @@ describe('workspace package script policy', () => {
|
|
|
800
800
|
const app = await readJson(join(root, 'packages/app/package.json'));
|
|
801
801
|
expect(app.dependencies).toEqual({ '@smoothbricks/lib': 'workspace:*' });
|
|
802
802
|
expect(app.scripts).toEqual({
|
|
803
|
-
dev: 'nx run app:dev --
|
|
804
|
-
serve: 'nx run app:serve --
|
|
805
|
-
test: 'nx run app:test --
|
|
803
|
+
dev: 'nx run app:dev --outputStyle=dynamic-legacy',
|
|
804
|
+
serve: 'nx run app:serve --outputStyle=dynamic-legacy',
|
|
805
|
+
test: 'nx run app:test --outputStyle=stream',
|
|
806
806
|
build: 'nx run app:build',
|
|
807
807
|
deploy: 'wrangler deploy',
|
|
808
808
|
astro: 'astro',
|
|
@@ -853,7 +853,7 @@ describe('workspace package script policy', () => {
|
|
|
853
853
|
dir: 'app',
|
|
854
854
|
name: '@smoothbricks/app',
|
|
855
855
|
dependencies: { '@smoothbricks/lib': 'workspace:*' },
|
|
856
|
-
scripts: { test: 'nx run app:test --
|
|
856
|
+
scripts: { test: 'nx run app:test --outputStyle=stream' },
|
|
857
857
|
nx: {
|
|
858
858
|
name: 'app',
|
|
859
859
|
targets: {
|
|
@@ -902,9 +902,9 @@ describe('workspace package script policy', () => {
|
|
|
902
902
|
|
|
903
903
|
const website = await readJson(join(root, 'packages/website/package.json'));
|
|
904
904
|
expect(website.scripts).toEqual({
|
|
905
|
-
dev: 'nx run website:dev --
|
|
905
|
+
dev: 'nx run website:dev --outputStyle=dynamic-legacy',
|
|
906
906
|
build: 'nx run website:build',
|
|
907
|
-
preview: 'nx run website:preview --
|
|
907
|
+
preview: 'nx run website:preview --outputStyle=stream',
|
|
908
908
|
astro: 'astro',
|
|
909
909
|
});
|
|
910
910
|
expect(website.nx).toEqual({
|
|
@@ -963,7 +963,7 @@ describe('workspace package script policy', () => {
|
|
|
963
963
|
applyWorkspaceDependencyDefaults(root);
|
|
964
964
|
|
|
965
965
|
const app = await readJson(join(root, 'packages/app/package.json'));
|
|
966
|
-
expect(app.scripts).toEqual({ test: 'nx run @external/app:test --
|
|
966
|
+
expect(app.scripts).toEqual({ test: 'nx run @external/app:test --outputStyle=stream' });
|
|
967
967
|
expect(app.nx).toEqual({
|
|
968
968
|
name: '@external/app',
|
|
969
969
|
targets: {
|
|
@@ -175,7 +175,7 @@ describe('auto release candidate filtering', () => {
|
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
it('does not auto-release an untagged next prerelease package version', async () => {
|
|
178
|
+
it('does not auto-release an untagged next prerelease package version with no prior stable release', async () => {
|
|
179
179
|
await withFixtureRepo(async (root) => {
|
|
180
180
|
await writePackage(root, next.name, next.path, next.version);
|
|
181
181
|
await git(root, ['add', '.']);
|
|
@@ -185,6 +185,46 @@ describe('auto release candidate filtering', () => {
|
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
it('selects a prerelease package when releasable changes exist since the last stable tag', async () => {
|
|
189
|
+
await withFixtureRepo(async (root) => {
|
|
190
|
+
await writePackage(root, next.name, next.path, '1.0.0');
|
|
191
|
+
await git(root, ['add', '.']);
|
|
192
|
+
await git(root, ['commit', '-m', 'chore(release): publish']);
|
|
193
|
+
await tag(root, 'next@1.0.0', '2025-01-01T00:00:00Z');
|
|
194
|
+
|
|
195
|
+
// Simulate the post-release prerelease bump
|
|
196
|
+
await writePackage(root, next.name, next.path, next.version);
|
|
197
|
+
await git(root, ['add', '.']);
|
|
198
|
+
await git(root, ['commit', '-m', 'chore(release): prepare next prerelease']);
|
|
199
|
+
|
|
200
|
+
// A real fix lands on the prerelease version
|
|
201
|
+
await mkdir(join(root, next.path, 'src'), { recursive: true });
|
|
202
|
+
await writeFile(join(root, next.path, 'src/index.ts'), 'export const fix = true;\n');
|
|
203
|
+
await git(root, ['add', join(next.path, 'src/index.ts')]);
|
|
204
|
+
await git(root, ['commit', '-m', 'fix(next): repair something']);
|
|
205
|
+
|
|
206
|
+
await expect(autoReleaseCandidatePackages(gitCandidateShell(root), [next])).resolves.toEqual([next]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('does not select a prerelease package when no releasable changes exist since the last stable tag', async () => {
|
|
211
|
+
await withFixtureRepo(async (root) => {
|
|
212
|
+
await writePackage(root, next.name, next.path, '1.0.0');
|
|
213
|
+
await mkdir(join(root, next.path, 'src'), { recursive: true });
|
|
214
|
+
await writeFile(join(root, next.path, 'src/index.ts'), 'export const v1 = true;\n');
|
|
215
|
+
await git(root, ['add', '.']);
|
|
216
|
+
await git(root, ['commit', '-m', 'chore(release): publish']);
|
|
217
|
+
await tag(root, 'next@1.0.0', '2025-01-01T00:00:00Z');
|
|
218
|
+
|
|
219
|
+
// Only the prerelease bump, no releasable source changes
|
|
220
|
+
await writePackage(root, next.name, next.path, next.version);
|
|
221
|
+
await git(root, ['add', '.']);
|
|
222
|
+
await git(root, ['commit', '-m', 'chore(release): prepare next prerelease']);
|
|
223
|
+
|
|
224
|
+
await expect(autoReleaseCandidatePackages(gitCandidateShell(root), [next])).resolves.toEqual([]);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
188
228
|
it('uses a repaired project-name tag as the follow-up auto release baseline for a scoped package', async () => {
|
|
189
229
|
await withFixtureRepo(async (root) => {
|
|
190
230
|
await writePackage(root, cli.name, cli.path, cli.version);
|
|
@@ -220,6 +260,22 @@ function gitCandidateShell(
|
|
|
220
260
|
const result = await $`git rev-parse --verify ${ref}`.cwd(root).quiet().nothrow();
|
|
221
261
|
return result.exitCode === 0;
|
|
222
262
|
},
|
|
263
|
+
latestStableReleaseRef: async (projectName) => {
|
|
264
|
+
const pattern = `${projectName}@*`;
|
|
265
|
+
const result = await $`git tag --list ${pattern} --sort=-v:refname`.cwd(root).quiet().nothrow();
|
|
266
|
+
if (result.exitCode !== 0) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const prefix = `${projectName}@`;
|
|
270
|
+
for (const line of new TextDecoder().decode(result.stdout).split('\n')) {
|
|
271
|
+
const tagName = line.trim();
|
|
272
|
+
const version = tagName.slice(prefix.length);
|
|
273
|
+
if (version && !version.includes('-')) {
|
|
274
|
+
return `refs/tags/${tagName}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
},
|
|
223
279
|
packageChangedFilesSince: async (ref, packagePath) => {
|
|
224
280
|
const result = await $`git diff --name-only ${`${ref}..HEAD`} -- ${packagePath}`.cwd(root).quiet().nothrow();
|
|
225
281
|
if (result.exitCode === 0) {
|
|
@@ -4,6 +4,7 @@ import { releaseTag } from './core.js';
|
|
|
4
4
|
|
|
5
5
|
export interface AutoReleaseCandidateShell {
|
|
6
6
|
gitRefExists(ref: string): Promise<boolean>;
|
|
7
|
+
latestStableReleaseRef(projectName: string): Promise<string | null>;
|
|
7
8
|
packageChangedFilesSince(ref: string, packagePath: string): Promise<string[]>;
|
|
8
9
|
packageJsonAtRef(ref: string, packagePath: string): Promise<Record<string, unknown> | null>;
|
|
9
10
|
currentPackageJson(packagePath: string): Promise<Record<string, unknown> | null>;
|
|
@@ -62,7 +63,19 @@ async function isAutoReleaseCandidate<Package extends ReleasePackageInfo>(
|
|
|
62
63
|
if (await shell.gitRefExists(tagRef)) {
|
|
63
64
|
return packageHasReleasableChangesSince(shell, tagRef, pkg);
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
if (pkg.version.includes('-')) {
|
|
67
|
+
// Prerelease version (e.g. 1.0.1-next.0) without its own tag: check for
|
|
68
|
+
// releasable changes since the last stable release. The prerelease bump is
|
|
69
|
+
// added after every release to make it obvious during development that the
|
|
70
|
+
// working version is unreleased — it should not suppress the next release.
|
|
71
|
+
const stableRef = await shell.latestStableReleaseRef(pkg.projectName);
|
|
72
|
+
if (stableRef) {
|
|
73
|
+
return packageHasReleasableChangesSince(shell, stableRef, pkg);
|
|
74
|
+
}
|
|
75
|
+
// No prior stable release — first release must be intentional, not auto.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return shell.packageHasHistory(pkg.path);
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
async function packageHasReleasableChangesSince<Package extends ReleasePackageInfo>(
|
package/src/release/index.ts
CHANGED
|
@@ -531,6 +531,7 @@ async function releaseVersionPackages(
|
|
|
531
531
|
return autoReleaseCandidatePackages(
|
|
532
532
|
{
|
|
533
533
|
gitRefExists: (ref) => gitRefExists(root, ref),
|
|
534
|
+
latestStableReleaseRef: (projectName) => latestStableReleaseRef(root, projectName),
|
|
534
535
|
packageChangedFilesSince: (ref, packagePath) => packageChangedFilesSince(root, ref, packagePath),
|
|
535
536
|
packageJsonAtRef: (ref, packagePath) => packageJsonAtRef(root, ref, packagePath),
|
|
536
537
|
currentPackageJson: (packagePath) => currentPackageJson(root, packagePath),
|
|
@@ -1071,6 +1072,28 @@ async function gitRefExists(root: string, ref: string): Promise<boolean> {
|
|
|
1071
1072
|
return result.exitCode === 0;
|
|
1072
1073
|
}
|
|
1073
1074
|
|
|
1075
|
+
async function latestStableReleaseRef(root: string, projectName: string): Promise<string | null> {
|
|
1076
|
+
// List tags matching <projectName>@* sorted newest-first by version, then
|
|
1077
|
+
// return the first non-prerelease tag as a full ref.
|
|
1078
|
+
const pattern = `${projectName}@*`;
|
|
1079
|
+
const result = await $`git tag --list ${pattern} --sort=-v:refname`.cwd(root).quiet().nothrow();
|
|
1080
|
+
if (result.exitCode !== 0) {
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
const tags = decode(result.stdout)
|
|
1084
|
+
.split('\n')
|
|
1085
|
+
.map((t) => t.trim())
|
|
1086
|
+
.filter(Boolean);
|
|
1087
|
+
const prefix = `${projectName}@`;
|
|
1088
|
+
for (const tagName of tags) {
|
|
1089
|
+
const version = tagName.slice(prefix.length);
|
|
1090
|
+
if (version && !version.includes('-')) {
|
|
1091
|
+
return `refs/tags/${tagName}`;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1074
1097
|
async function packageChangedFilesSince(root: string, ref: string, packagePath: string): Promise<string[]> {
|
|
1075
1098
|
const result = await $`git diff --name-only ${`${ref}..HEAD`} -- ${packagePath}`.cwd(root).quiet().nothrow();
|
|
1076
1099
|
if (result.exitCode !== 0) {
|