Skip to content
Closed
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
8 changes: 1 addition & 7 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
"@sfmc-bds/tools": "0.1.1"
},
"changesets": [
"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"
"fix-release-tag-shallow"
]
}
9 changes: 7 additions & 2 deletions .github/workflows/changeset-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,23 @@ jobs:
npm run build --workspace @sfmc-bds/tools --if-present
npm run build --workspace @sfmc-bds/sfmc --if-present

# Version PR 需要「创建 PR」权限。若仅用 GITHUB_TOKEN 且仓库关闭
# Settings→Actions→General→「Allow GitHub Actions to create and approve pull requests」,
# changesets/action 会以 HttpError 失败(分支已推送但 PR 未创建)。
# 优先使用 PAT Secret CHANGESETS_GITHUB_TOKEN(repo 或 contents+pull_requests);
# 未配置时回退 GITHUB_TOKEN(需上述勾选为开)。
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
version: npm run version-packages
# CI 不跑交互 changeset/version;只 publish + tag + gh release
# tag-packages 在 CI=true 时默认 --from-existing(publish 已建 tag,避免浅克隆误打全量)
# tag-packages 默认 HEAD~1 diff(本 workflow fetch-depth:0);勿默认 --from-existing
publish: npm run ci-release-packages
title: "chore(release): version packages"
commit: "chore(release): version packages"
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.CHANGESETS_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
16 changes: 15 additions & 1 deletion docs/dev/npm-publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
2. PR 合入 `main` 后,[changeset-release.yml](../../.github/workflows/changeset-release.yml) 会开/更新 **Version Packages** PR。
3. 维护者审查并合并 Version PR → CI 跑 `ci-release-packages`(publish + tag + GitHub Release;pre mode → npm **`beta`**)。

### Version PR 权限(必读)

`changesets/action` 用 `GITHUB_TOKEN` 开 PR 时,若仓库/组织关闭了 **Allow GitHub Actions to create and approve pull requests**,会报:

`HttpError: GitHub Actions is not permitted to create or approve pull requests`

(版本 bump 分支 `changeset-release/main` 可能已推送,但 PR 未创建。)

任选其一:

1. **推荐(零 Secret)**:Repo Settings → Actions → General → Workflow permissions → 勾选 *Allow GitHub Actions to create and approve pull requests*(需 `pull-requests: write`,workflow 已声明)。
2. **PAT**:新建 classic/fine-grained token(至少能开本仓 PR),写入 Secret **`CHANGESETS_GITHUB_TOKEN`**;workflow 优先用该 token,未配置时回退 `GITHUB_TOKEN`。

### 本地一键发版(`npm-run-all2`)

当前 pre mode 请用:
Expand All @@ -53,7 +66,7 @@ npm run prerelease-packages
|------|--------|
| `prerelease-packages` | **现在**(pre/beta) |
| `release-packages` | 仅 `changeset pre exit` 之后(latest) |
| `ci-release-packages` | CI 专用(无交互)。`CI=true` 时 `tag-packages` 默认 `--from-existing`:只收录 `changeset publish` 已创建的 tag,避免浅克隆误为未发版包打 tag / 建 GH Release。 |
| `ci-release-packages` | CI 专用(无交互)。`tag-packages` 默认按 `HEAD~1` 版本 diff 收录(workflow `fetch-depth: 0`);仅显式 `--from-existing` / 无法解析父提交时才回退「已有 tag」。`RELEASE_TAGS_STATE` 空数组表示本轮无 tag,push/gh-release **不得**回退全量扫描。 |
| `publish-packages` | 仅 npm publish 包装 |

需要:本机已登录 `gh`、npm(或 `NPM_TOKEN`),且对 `origin` 有推送权限。
Expand Down Expand Up @@ -95,6 +108,7 @@ npm run pack:verify

1. npm 账号登录并确认 org [sfmc-bds](https://www.npmjs.com/org/sfmc-bds)
2. Granular Access Token(Automation)写入 GitHub Secret `NPM_TOKEN`
3. (可选)若无法开启 Actions「create and approve pull requests」:写入 `CHANGESETS_GITHUB_TOKEN`(见上文 Version PR 权限)

## 模块包

Expand Down
32 changes: 20 additions & 12 deletions tools/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# @sfmc-bds/tools

## 0.2.0-beta.0

### Minor Changes

- tsc7 双轨入口;sdk-lint / manifest schema / 模块脚手架;zipx DRY;pack:verify 从 NPM_PUBLISH_PACKAGES 派生;ootb 断言对齐。

### Patch Changes

- Updated dependencies
- @sfmc-bds/bds-tools@0.2.0-beta.0
# @sfmc-bds/tools

## 0.2.0-beta.1

### Patch Changes

- 44ca909: 修复发版编排:浅克隆不再误打全量 tag;CHANGELOG 摘录正则;release-tags 状态 DRY
- listPendingChangesetFiles:pre mode 下排除已写入 pre.json 的 id,避免误判待消费(LSP)
- tag-packages:CI 不再默认 --from-existing;空 RELEASE_TAGS_STATE 禁止 push/gh 全量回退

## 0.2.0-beta.0

### Minor Changes

- tsc7 双轨入口;sdk-lint / manifest schema / 模块脚手架;zipx DRY;pack:verify 从 NPM_PUBLISH_PACKAGES 派生;ootb 断言对齐。

### Patch Changes

- Updated dependencies
- @sfmc-bds/bds-tools@0.2.0-beta.0
8 changes: 7 additions & 1 deletion tools/changeset-github-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
packageDirFor,
packageTagName,
readReleaseTagsState,
releaseTagsStateKind,
run,
} from "./lib/changeset-release.mjs";

Expand All @@ -25,13 +26,18 @@ const forceLatest = process.argv.includes("--latest");

function loadTargets() {
const state = readReleaseTagsState();
if (state && state.tags.length > 0) {
const kind = releaseTagsStateKind(state);
if (kind === "present") {
return state.tags.map((t) => ({
name: t.name,
version: t.version,
tag: t.tag,
}));
}
if (kind === "empty") {
/* LSP:与 push 一致 — 空态表示本轮无 Release,禁止回退全量 */
return [];
}
const out = [];
for (const p of listPublishablePackages()) {
const tag = packageTagName(p.name, p.version);
Expand Down
14 changes: 9 additions & 5 deletions tools/changeset-push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
git,
gitCapture,
readReleaseTagsState,
releaseTagsStateKind,
} from "./lib/changeset-release.mjs";

const tagsOnly =
Expand All @@ -29,12 +30,15 @@ if (!tagsOnly) {
/** @type {string[]} */
let tags = [];
const state = readReleaseTagsState();
if (state && state.tags.length > 0) {
const kind = releaseTagsStateKind(state);
if (kind === "present") {
tags = state.tags.map((t) => t.tag).filter(Boolean);
}

if (tags.length === 0) {
/* 回退:推送本机尚未在 origin 的 name@version 风格 tag */
} else if (kind === "empty") {
/* LSP:tag-packages 已显式写入空列表 = 本轮无 tag,禁止回退全量扫描 */
console.log("[changeset] 发版态为空(本轮无 tag),跳过推送");
process.exit(0);
} else {
/* 文件缺失:本地/应急回退 */
const local = gitCapture(["tag", "-l", "@sfmc-bds/*"])
.split(/\r?\n/)
.filter(Boolean);
Expand Down
30 changes: 30 additions & 0 deletions tools/changeset-release.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { describe, it } from "node:test";
import {
packageTagName,
extractChangelogNotes,
listPendingChangesetFiles,
releaseTagsStateKind,
RELEASE_TAGS_STATE,
PRE_JSON,
ROOT,
Expand Down Expand Up @@ -46,3 +48,31 @@ describe("release paths (DRY)", () => {
assert.equal(path.basename(RELEASE_TAGS_STATE), ".sfmc-release-tags.json");
});
});

describe("listPendingChangesetFiles (pre mode LSP)", () => {
it("excludes ids already recorded in pre.json#changesets", () => {
const pre = JSON.parse(fs.readFileSync(PRE_JSON, "utf8"));
assert.equal(pre.mode, "pre");
assert.ok(Array.isArray(pre.changesets) && pre.changesets.length > 0);
// pre mode 下已消费的 .md 仍留在磁盘;待消费列表不得再包含它们
const pending = listPendingChangesetFiles();
const pendingIds = pending.map((p) => path.basename(p, ".md"));
for (const id of pre.changesets) {
assert.ok(!pendingIds.includes(id), `consumed ${id} must not be pending`);
}
});
});

describe("releaseTagsStateKind (empty-state LSP)", () => {
it("distinguishes missing / empty / present", () => {
assert.equal(releaseTagsStateKind(null), "missing");
assert.equal(releaseTagsStateKind({ tags: [], createdAt: "" }), "empty");
assert.equal(
releaseTagsStateKind({
tags: [{ name: "@sfmc-bds/tools", version: "0.2.0-beta.1", tag: "@sfmc-bds/tools@0.2.0-beta.1" }],
createdAt: "",
}),
"present"
);
});
});
13 changes: 7 additions & 6 deletions tools/changeset-tag.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
* 为当前可发包创建 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 之后)
* node tools/changeset-tag.mjs # 按 HEAD~1 版本 diff 打 tag
* node tools/changeset-tag.mjs --from-existing # 仅收录本地已有 name@version
* node tools/changeset-tag.mjs --create # 显式按 diff 创建(与默认相同,保留兼容)
*
* CI=true 且未传 --create 时默认 --from-existing(changeset publish 已建 tag,避免浅克隆误打全量)。
* 默认走 HEAD~1 diff(workflow fetch-depth:0)。仅显式 --from-existing / SFMC_TAG_FROM_EXISTING=1
* 时用「已有 tag」路径;浅克隆无法解析 HEAD~1 时由 resolvePackagesNeedingTags 安全回退。
* 切勿在 CI 无条件 from-existing:全量拉 tag 后会过收录未在本轮 bump 的包。
*/
import path from "node:path";
import {
Expand All @@ -19,9 +22,7 @@ import {
} from "./lib/changeset-release.mjs";

const fromExisting =
process.argv.includes("--from-existing") ||
process.env.SFMC_TAG_FROM_EXISTING === "1" ||
(process.env.CI === "true" && !process.argv.includes("--create"));
process.argv.includes("--from-existing") || process.env.SFMC_TAG_FROM_EXISTING === "1";

const needed = resolvePackagesNeedingTags({ fromExisting });
if (needed.length === 0) {
Expand Down
24 changes: 23 additions & 1 deletion tools/lib/changeset-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ export function isPreMode() {
return Boolean(pre && pre.mode === "pre");
}

/** 待消费的 changeset 文件(不含 README / config / pre) */
/**
* 待消费的 changeset 文件(不含 README / config / pre)。
* pre mode 下 `.md` 会留在磁盘直到 `pre exit`;须排除已写入 `pre.json#changesets` 的 id(与 @changesets getRelevantChangesets 一致)。
*/
export function listPendingChangesetFiles() {
if (!fs.existsSync(CHANGESET_DIR)) return [];
const pre = readPreState();
const consumed = new Set(
pre && pre.mode === "pre" && Array.isArray(pre.changesets) ? pre.changesets : []
);
return fs
.readdirSync(CHANGESET_DIR)
.filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md")
.filter((f) => !consumed.has(f.replace(/\.md$/i, "")))
.map((f) => path.join(CHANGESET_DIR, f));
}

Expand Down Expand Up @@ -85,6 +93,20 @@ export function writeReleaseTagsState(tags) {
return state;
}

/**
* 发版态契约(LSP):
* - 文件缺失 → `missing`(允许回退扫描)
* - 文件存在且 tags=[] → `empty`(本轮无 tag,禁止回退全量)
* - 文件存在且有条目 → `present`
* @param {ReleaseTagsState | null} state
* @returns {"missing" | "empty" | "present"}
*/
export function releaseTagsStateKind(state) {
if (!state) return "missing";
if (!Array.isArray(state.tags) || state.tags.length === 0) return "empty";
return "present";
}

/**
* 仅收录「当前版本对应 tag 已在本地存在」的可发包。
* 用于:changeset publish 之后的 CI(publish 已建 tag),或浅克隆无法 diff 时的安全回退。
Expand Down
2 changes: 1 addition & 1 deletion tools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sfmc-bds/tools",
"version": "0.2.0-beta.0",
"version": "0.2.0-beta.1",
"description": "SFMC dev tools - testing before build",
"type": "module",
"license": "AGPL-3.0-only",
Expand Down