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
5 changes: 5 additions & 0 deletions .changeset/push-fallback-build-dry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sfmc-bds/tools": patch
---

发版编排:push 缺失态回退复用 listUnpushedExistingVersionTags;changeset-release 经 build-publishable 对齐 NPM_PUBLISH_PACKAGES
11 changes: 2 additions & 9 deletions .github/workflows/changeset-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,8 @@ jobs:
- run: npm ci --no-audit --no-fund

- name: build publishable workspaces
run: |
npm run build --workspace @sfmc-bds/sdk
npm run build --workspace @sfmc-bds/eslint-plugin --if-present
npm run build --workspace @sfmc-bds/bds-tools --if-present
npm run build --workspace @sfmc-bds/db-server --if-present
npm run build --workspace @sfmc-bds/qq-bridge --if-present
npm run build --workspace @sfmc-bds/cli --if-present
npm run build --workspace @sfmc-bds/tools --if-present
npm run build --workspace @sfmc-bds/sfmc --if-present
# DRY:包清单权威在 tools/lib/npm-publish-packages.mjs,勿在此再抄 workspace 列表
run: node tools/build-publishable.mjs

# Version PR 需要「创建 PR」权限。仅用 GITHUB_TOKEN 且仓库关闭
# Settings→Actions→General→「Allow GitHub Actions to create and approve pull requests」时,
Expand Down
3 changes: 2 additions & 1 deletion docs/dev/npm-publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ npm run prerelease-packages
|------|--------|
| `prerelease-packages` | **现在**(pre/beta) |
| `release-packages` | 仅 `changeset pre exit` 之后(latest) |
| `ci-release-packages` | CI 专用(无交互)。`tag-packages` 默认按 `HEAD~1` 版本 diff;仅显式 `--from-existing` / `SFMC_TAG_FROM_EXISTING=1`(或浅克隆无法解析父提交)才只收录已有 tag。空的 `.sfmc-release-tags.json` 表示本轮无目标,下游不得回退扫全仓。 |
| `ci-release-packages` | CI 专用(无交互)。`tag-packages` 默认按 `HEAD~1` 版本 diff;仅显式 `--from-existing` / `SFMC_TAG_FROM_EXISTING=1`(或浅克隆无法解析父提交)才只收录已有 tag。空的 `.sfmc-release-tags.json` 表示本轮无目标,下游不得回退扫全仓。push / gh-release 缺失态分别回退 `listUnpushedExistingVersionTags` / `listPackagesWithExistingVersionTags`。 |
| `build:publishable` | 按 `NPM_PUBLISH_PACKAGES` 构建可发包(`changeset-release` workflow 使用;勿在 YAML 再抄 workspace 列表)。 |
| `publish-packages` | 仅 npm publish 包装 |

需要:本机已登录 `gh`、npm(或 `NPM_TOKEN`),且对 `origin` 有推送权限。
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"eslint-plugin:build": "npm run build --workspace @sfmc-bds/eslint-plugin",
"eslint-plugin:test": "npm run test --workspace @sfmc-bds/eslint-plugin",
"pack:verify": "node tools/pack-verify.mjs",
"build:publishable": "node tools/build-publishable.mjs",
"changeset": "changeset",
"version-packages": "changeset version",
"publish-packages": "node tools/changeset-publish.mjs",
Expand Down
23 changes: 23 additions & 0 deletions tools/build-publishable.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node
/**
* 按 NPM_PUBLISH_PACKAGES 构建全部可发包(changeset-release / 本地发版共用)。
* 勿在 workflow YAML 里再抄一份 workspace 列表(DRY/OCP)。
*/
import { spawnSync } from "node:child_process";
import { listPublishableBuildOrder } from "./lib/npm-publish-packages.mjs";
import { ROOT } from "./lib/changeset-release.mjs";

const order = listPublishableBuildOrder();
for (const name of order) {
console.log(`[build-publishable] npm run build --workspace ${name} --if-present`);
const r = spawnSync("npm", ["run", "build", "--workspace", name, "--if-present"], {
cwd: ROOT,
stdio: "inherit",
shell: process.platform === "win32",
});
if (r.status !== 0) {
process.exit(r.status === null ? 1 : r.status);
}
}

console.log(`[build-publishable] ok — ${order.length} workspaces`);
16 changes: 4 additions & 12 deletions tools/changeset-push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {
git,
gitCapture,
listUnpushedExistingVersionTags,
readReleaseTagsState,
resolveReleaseTagEntries,
} from "./lib/changeset-release.mjs";
Expand All @@ -28,19 +29,10 @@ if (!tagsOnly) {
}

const state = readReleaseTagsState();
/* DRY/LSP:缺失态与 gh-release 共用「当前可发包 + 当前版本 tag」候选,再过滤未推送 */
const entries = resolveReleaseTagEntries(state, () => {
/* 回退:仅当状态文件缺失时,推送本机尚未在 origin 的 name@version 风格 tag */
console.warn("[changeset] 无 .sfmc-release-tags.json,回退扫描本地未推送 tag");
/** @type {{ tag: string }[]} */
const out = [];
const local = gitCapture(["tag", "-l", "@sfmc-bds/*"])
.split(/\r?\n/)
.filter(Boolean);
for (const tag of local) {
const remote = gitCapture(["ls-remote", "--tags", "origin", `refs/tags/${tag}`]);
if (!remote) out.push({ tag });
}
return out;
console.warn("[changeset] 无 .sfmc-release-tags.json,回退为未推送的当前 name@version tag");
return listUnpushedExistingVersionTags();
});

const tags = entries.map((t) => t.tag).filter(Boolean);
Expand Down
31 changes: 31 additions & 0 deletions tools/changeset-release.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
extractChangelogNotes,
listPendingChangesetFiles,
listPackagesWithExistingVersionTags,
listUnpushedExistingVersionTags,
resolveReleaseTagEntries,
RELEASE_TAGS_STATE,
PRE_JSON,
ROOT,
} from "./lib/changeset-release.mjs";
import { listPublishableBuildOrder, NPM_PUBLISH_PACKAGES } from "./lib/npm-publish-packages.mjs";
import path from "node:path";
import fs from "node:fs";
import os from "node:os";
Expand Down Expand Up @@ -81,6 +83,35 @@ describe("resolveReleaseTagEntries (LSP)", () => {
assert.equal(e.tag, packageTagName(e.name, e.version));
}
});

it("push missing-state fallback is listUnpushedExistingVersionTags (DRY/LSP)", () => {
/* 契约:changeset-push 不得再硬编码 @sfmc-bds/* 全量扫描 */
const allExisting = listPackagesWithExistingVersionTags();
const existingTags = new Set(allExisting.map((e) => e.tag));
/** @type {ReturnType<typeof listUnpushedExistingVersionTags> | undefined} */
let fb;
const viaResolver = resolveReleaseTagEntries(null, () => {
fb = listUnpushedExistingVersionTags();
return fb;
});
assert.equal(viaResolver, fb);
assert.ok(Array.isArray(fb));
for (const e of fb) {
assert.equal(e.tag, packageTagName(e.name, e.version));
assert.ok(existingTags.has(e.tag), "unpushed 必须是 existing 的子集");
}
});
});

describe("listPublishableBuildOrder (DRY/OCP)", () => {
it("covers every NPM_PUBLISH_PACKAGES entry once; sdk and bds-tools first", () => {
const order = listPublishableBuildOrder();
const keys = Object.keys(NPM_PUBLISH_PACKAGES);
assert.equal(order.length, keys.length);
assert.deepEqual([...order].sort(), [...keys].sort());
assert.equal(order[0], "@sfmc-bds/sdk");
assert.equal(order[1], "@sfmc-bds/bds-tools");
});
});

describe("listPendingChangesetFiles (pre mode LSP)", () => {
Expand Down
14 changes: 14 additions & 0 deletions tools/lib/changeset-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ export function listPackagesWithExistingVersionTags(pkgs = listPublishablePackag
return out;
}

/**
* 当前版本 tag 已在本地、且 origin 尚无同名 tag 的可发包。
* push-release 缺失态回退权威实现:候选集与 gh-release 共用
* listPackagesWithExistingVersionTags(DRY/LSP),再叠加未推送过滤。
* @param {{ name: string, version: string, pkgPath: string }[]} [pkgs]
* @returns {ReleaseTagEntry[]}
*/
export function listUnpushedExistingVersionTags(pkgs = listPublishablePackages()) {
return listPackagesWithExistingVersionTags(pkgs).filter((e) => {
const remote = gitCapture(["ls-remote", "--tags", "origin", `refs/tags/${e.tag}`]);
return !remote;
});
}

/**
* 相对 HEAD~1 版本有变化的可发包;无法解析父提交时安全回退到 listPackagesWithExistingVersionTags。
* @param {{ fromExisting?: boolean }} [opts]
Expand Down
23 changes: 23 additions & 0 deletions tools/lib/npm-publish-packages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ export const NPM_PUBLISH_PACKAGES = {
"@sfmc-bds/sfmc": "sfmc-meta/package.json",
};

/**
* 可发包 build 顺序:优先编译被依赖的包,其余保持清单插入序(OCP:扩包只改表)。
* sdk / bds-tools 被 cli、tools 等引用,须先于依赖方。
* @returns {(keyof typeof NPM_PUBLISH_PACKAGES)[]}
*/
export function listPublishableBuildOrder() {
const all = Object.keys(NPM_PUBLISH_PACKAGES);
const priority = ["@sfmc-bds/sdk", "@sfmc-bds/bds-tools"];
/** @type {string[]} */
const out = [];
const seen = new Set();
for (const name of priority) {
if (all.includes(name)) {
out.push(name);
seen.add(name);
}
}
for (const name of all) {
if (!seen.has(name)) out.push(name);
}
return /** @type {(keyof typeof NPM_PUBLISH_PACKAGES)[]} */ (out);
}

/** @returns {keyof typeof NPM_PUBLISH_PACKAGES | null} */
export function resolvePublishPackage(pkg) {
if (Object.prototype.hasOwnProperty.call(NPM_PUBLISH_PACKAGES, pkg)) {
Expand Down