Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-release-tag-shallow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sfmc-bds/tools": patch
---

修复发版编排:浅克隆不再误打全量 tag;CHANGELOG 摘录正则;release-tags 状态 DRY
8 changes: 7 additions & 1 deletion .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
"@sfmc-bds/tools": "0.1.1"
},
"changesets": [
"tame-suns-begin"
"bds-tools-0-2-beta",
"cli-0-2-beta",
"db-server-0-2-beta",
"qq-bridge-0-1-1-beta",
"sdk-0-2-beta",
"sfmc-meta-0-2-beta",
"tools-0-2-beta"
]
}
14 changes: 0 additions & 14 deletions .changeset/tame-suns-begin.md

This file was deleted.

4 changes: 4 additions & 0 deletions .github/workflows/changeset-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ jobs:
pull-requests: write
id-token: write
steps:
# fetch-depth:0:tag-packages 需 HEAD~1 做版本 diff;浅克隆会误触发全量 tag 回退路径
- uses: actions/checkout@v5
with:
fetch-depth: 0

- uses: actions/setup-node@v5
with:
Expand All @@ -50,6 +53,7 @@ jobs:
with:
version: npm run version-packages
# CI 不跑交互 changeset/version;只 publish + tag + gh release
# tag-packages 在 CI=true 时默认 --from-existing(publish 已建 tag,避免浅克隆误打全量)
publish: npm run ci-release-packages
title: "chore(release): version packages"
commit: "chore(release): version packages"
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/npm-publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ npm run prerelease-packages
|------|--------|
| `prerelease-packages` | **现在**(pre/beta) |
| `release-packages` | 仅 `changeset pre exit` 之后(latest) |
| `ci-release-packages` | CI 专用(无交互) |
| `ci-release-packages` | CI 专用(无交互)。`CI=true` 时 `tag-packages` 默认 `--from-existing`:只收录 `changeset publish` 已创建的 tag,避免浅克隆误为未发版包打 tag / 建 GH Release。 |
| `publish-packages` | 仅 npm publish 包装 |

需要:本机已登录 `gh`、npm(或 `NPM_TOKEN`),且对 `origin` 有推送权限。
Expand Down
37 changes: 12 additions & 25 deletions tools/changeset-github-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,23 @@ import {
extractChangelogNotes,
gitCapture,
listPublishablePackages,
packageDirFor,
packageTagName,
readReleaseTagsState,
run,
} from "./lib/changeset-release.mjs";
import { NPM_PUBLISH_PACKAGES } from "./lib/npm-publish-packages.mjs";

const forcePre = process.argv.includes("--prerelease");
const forceLatest = process.argv.includes("--latest");
const statePath = path.join(ROOT, ".sfmc-release-tags.json");

function loadTargets() {
if (fs.existsSync(statePath)) {
const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
if (Array.isArray(state.tags) && state.tags.length > 0) {
return state.tags.map((t) => ({
name: t.name,
version: t.version,
tag: t.tag,
}));
}
const state = readReleaseTagsState();
if (state && state.tags.length > 0) {
return state.tags.map((t) => ({
name: t.name,
version: t.version,
tag: t.tag,
}));
}
const out = [];
for (const p of listPublishablePackages()) {
Expand Down Expand Up @@ -72,29 +70,18 @@ for (const t of targets) {
}

const pre = isPrereleaseVersion(t.version);
const rel = NPM_PUBLISH_PACKAGES[t.name];
const pkgDir = rel ? path.join(ROOT, path.dirname(rel)) : ROOT;
const pkgDir = packageDirFor(t.name) ?? ROOT;
const notes = extractChangelogNotes(pkgDir, t.version);
const notesFile = path.join(
ROOT,
`.sfmc-release-notes-${t.name.replace(/[/@]/g, "_")}.md`
);
fs.writeFileSync(notesFile, notes + "\n");

const args = [
"release",
"create",
t.tag,
"--title",
t.tag,
"--notes-file",
notesFile,
];
const args = ["release", "create", t.tag, "--title", t.tag, "--notes-file", notesFile];
if (pre) args.push("--prerelease");

console.log(
`[changeset] gh release create ${t.tag}` + (pre ? " --prerelease" : "")
);
console.log(`[changeset] gh release create ${t.tag}` + (pre ? " --prerelease" : ""));
try {
run("gh", args);
} finally {
Expand Down
16 changes: 4 additions & 12 deletions tools/changeset-publish.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,10 @@
* - pre mode 下由 changesets 自动使用 pre.json 的 tag(勿再传 --tag,会报错)
*/
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { ROOT, isPreMode, readPreState } from "./lib/changeset-release.mjs";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const prePath = path.join(root, ".changeset", "pre.json");

let preTag = null;
if (fs.existsSync(prePath)) {
const pre = JSON.parse(fs.readFileSync(prePath, "utf8"));
if (pre.mode === "pre" && pre.tag) preTag = String(pre.tag);
}
const pre = readPreState();
const preTag = isPreMode() && pre?.tag ? String(pre.tag) : null;

const env = {
...process.env,
Expand All @@ -34,5 +26,5 @@ console.log(
`[changeset-publish] running: npx ${args.join(" ")}` +
(preTag ? ` (pre mode → npm tag ${preTag})` : "")
);
const r = spawnSync("npx", args, { cwd: root, env, stdio: "inherit", shell: true });
const r = spawnSync("npx", args, { cwd: ROOT, env, stdio: "inherit", shell: true });
process.exit(r.status === null ? 1 : r.status);
15 changes: 8 additions & 7 deletions tools/changeset-push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* 推送当前分支与发版 tag(优先读 .sfmc-release-tags.json)。
* CI 下默认只推 tag(SFMC_PUSH_TAGS_ONLY=1 或 CI=true)。
*/
import fs from "node:fs";
import path from "node:path";
import { ROOT, git, gitCapture } from "./lib/changeset-release.mjs";
import {
git,
gitCapture,
readReleaseTagsState,
} from "./lib/changeset-release.mjs";

const statePath = path.join(ROOT, ".sfmc-release-tags.json");
const tagsOnly =
process.env.SFMC_PUSH_TAGS_ONLY === "1" ||
process.env.CI === "true" ||
Expand All @@ -27,9 +28,9 @@ if (!tagsOnly) {

/** @type {string[]} */
let tags = [];
if (fs.existsSync(statePath)) {
const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
tags = (state.tags ?? []).map((t) => t.tag).filter(Boolean);
const state = readReleaseTagsState();
if (state && state.tags.length > 0) {
tags = state.tags.map((t) => t.tag).filter(Boolean);
}

if (tags.length === 0) {
Expand Down
48 changes: 48 additions & 0 deletions tools/changeset-release.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* changeset-release 共用库单测(node:test)
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
packageTagName,
extractChangelogNotes,
RELEASE_TAGS_STATE,
PRE_JSON,
ROOT,
} from "./lib/changeset-release.mjs";
import path from "node:path";
import fs from "node:fs";
import os from "node:os";

describe("packageTagName", () => {
it("matches changeset publish format (no v prefix)", () => {
assert.equal(packageTagName("@sfmc-bds/db-server", "0.2.0-beta.1"), "@sfmc-bds/db-server@0.2.0-beta.1");
});
});

describe("extractChangelogNotes", () => {
it("extracts body for a version heading", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-cl-"));
fs.writeFileSync(
path.join(dir, "CHANGELOG.md"),
"# pkg\n\n## 1.2.3\n\n### Patch Changes\n\n- hello\n\n## 1.2.2\n\n- old\n"
);
const notes = extractChangelogNotes(dir, "1.2.3");
assert.match(notes, /hello/);
assert.doesNotMatch(notes, /old/);
});

it("falls back when version missing", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-cl-"));
fs.writeFileSync(path.join(dir, "CHANGELOG.md"), "# pkg\n\n## 1.0.0\n\n- x\n");
assert.equal(extractChangelogNotes(dir, "9.9.9"), "Release 9.9.9");
});
});

describe("release paths (DRY)", () => {
it("RELEASE_TAGS_STATE and PRE_JSON live under ROOT", () => {
assert.ok(RELEASE_TAGS_STATE.startsWith(ROOT));
assert.ok(PRE_JSON.startsWith(ROOT));
assert.equal(path.basename(RELEASE_TAGS_STATE), ".sfmc-release-tags.json");
});
});
85 changes: 43 additions & 42 deletions tools/changeset-tag.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,69 @@
/**
* 为当前可发包创建 git tag(与 changeset publish 同格式: name@version)。
* 已存在的 tag 跳过。写入 .sfmc-release-tags.json 供后续 push / gh release 使用。
*
* node tools/changeset-tag.mjs # 按 HEAD~1 版本 diff 打 tag
* node tools/changeset-tag.mjs --from-existing # 仅收录本地已有 name@version(CI publish 之后)
*
* CI=true 且未传 --create 时默认 --from-existing(changeset publish 已建 tag,避免浅克隆误打全量)。
*/
import fs from "node:fs";
import path from "node:path";
import {
ROOT,
git,
gitCapture,
listPublishablePackages,
packageTagName,
resolvePackagesNeedingTags,
writeReleaseTagsState,
RELEASE_TAGS_STATE,
} from "./lib/changeset-release.mjs";

const statePath = path.join(ROOT, ".sfmc-release-tags.json");
const fromExisting =
process.argv.includes("--from-existing") ||
process.env.SFMC_TAG_FROM_EXISTING === "1" ||
(process.env.CI === "true" && !process.argv.includes("--create"));

/** 相对上一提交(或 upstream)版本有变化的包;首次则全部打 tag */
function packagesNeedingTags() {
const pkgs = listPublishablePackages();
const headParent = gitCapture(["rev-parse", "HEAD~1"]);
if (!headParent) {
return pkgs.map((p) => ({ ...p, tag: packageTagName(p.name, p.version) }));
}

const needed = [];
for (const p of pkgs) {
const rel = path.relative(ROOT, p.pkgPath).replace(/\\/g, "/");
let prev = "";
try {
const raw = gitCapture(["show", `HEAD~1:${rel}`]);
if (raw) prev = String(JSON.parse(raw).version ?? "");
} catch {
prev = "";
}
if (prev !== p.version) {
needed.push({ ...p, tag: packageTagName(p.name, p.version), prev });
}
}
return needed;
}

const needed = packagesNeedingTags();
const needed = resolvePackagesNeedingTags({ fromExisting });
if (needed.length === 0) {
console.log("[changeset] 无包版本变化,跳过打 tag");
fs.writeFileSync(statePath, JSON.stringify({ tags: [], createdAt: new Date().toISOString() }, null, 2));
console.log(
fromExisting
? "[changeset] 无已存在的 name@version tag 可收录,跳过"
: "[changeset] 无包版本变化,跳过打 tag"
);
writeReleaseTagsState([]);
process.exit(0);
}

const created = [];
for (const p of needed) {
const exists = gitCapture(["tag", "-l", p.tag]);
const exists = Boolean(gitCapture(["tag", "-l", p.tag]));
if (exists) {
console.log(`[changeset] tag 已存在,跳过: ${p.tag}`);
created.push({ name: p.name, version: p.version, tag: p.tag, existed: true });
created.push({
name: p.name,
version: p.version,
tag: p.tag,
pkgPath: p.pkgPath,
existed: true,
});
continue;
}
if (fromExisting) {
/* from-existing 契约:不新建 tag(LSP:与「仅收录」语义一致) */
continue;
}
git(["tag", "-a", p.tag, "-m", `Release ${p.tag}`]);
console.log(
`[changeset] tagged ${p.tag}` + (p.prev ? ` (${p.prev} → ${p.version})` : "")
);
created.push({ name: p.name, version: p.version, tag: p.tag, existed: false });
console.log(`[changeset] tagged ${p.tag}` + (p.prev ? ` (${p.prev} → ${p.version})` : ""));
created.push({
name: p.name,
version: p.version,
tag: p.tag,
pkgPath: p.pkgPath,
existed: false,
});
}

fs.writeFileSync(
statePath,
JSON.stringify({ tags: created, createdAt: new Date().toISOString() }, null, 2) + "\n"
writeReleaseTagsState(created);
console.log(
`[changeset] 写入 ${path.relative(ROOT, RELEASE_TAGS_STATE)} (${created.length} tags)` +
(fromExisting ? " [from-existing]" : "")
);
console.log(`[changeset] 写入 ${path.relative(ROOT, statePath)} (${created.length} tags)`);
Loading