@prd-improve/cli 1.0.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 +38 -0
- package/dist/anchor-slicer.d.ts +18 -0
- package/dist/anchor-slicer.js +114 -0
- package/dist/anchor-slicer.js.map +1 -0
- package/dist/api/client.d.ts +68 -0
- package/dist/api/client.js +175 -0
- package/dist/api/client.js.map +1 -0
- package/dist/changelog.d.ts +9 -0
- package/dist/changelog.js +34 -0
- package/dist/changelog.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +258 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/admin/_adminJson.d.ts +18 -0
- package/dist/commands/admin/_adminJson.js +27 -0
- package/dist/commands/admin/_adminJson.js.map +1 -0
- package/dist/commands/admin/_authResolve.d.ts +8 -0
- package/dist/commands/admin/_authResolve.js +56 -0
- package/dist/commands/admin/_authResolve.js.map +1 -0
- package/dist/commands/admin/index.d.ts +22 -0
- package/dist/commands/admin/index.js +100 -0
- package/dist/commands/admin/index.js.map +1 -0
- package/dist/commands/admin/io.d.ts +15 -0
- package/dist/commands/admin/io.js +30 -0
- package/dist/commands/admin/io.js.map +1 -0
- package/dist/commands/admin/member.d.ts +43 -0
- package/dist/commands/admin/member.js +362 -0
- package/dist/commands/admin/member.js.map +1 -0
- package/dist/commands/admin/project.d.ts +40 -0
- package/dist/commands/admin/project.js +182 -0
- package/dist/commands/admin/project.js.map +1 -0
- package/dist/commands/admin/team.d.ts +31 -0
- package/dist/commands/admin/team.js +157 -0
- package/dist/commands/admin/team.js.map +1 -0
- package/dist/commands/admin/teamMember.d.ts +33 -0
- package/dist/commands/admin/teamMember.js +262 -0
- package/dist/commands/admin/teamMember.js.map +1 -0
- package/dist/commands/admin/token.d.ts +29 -0
- package/dist/commands/admin/token.js +125 -0
- package/dist/commands/admin/token.js.map +1 -0
- package/dist/commands/admin/user.d.ts +2 -0
- package/dist/commands/admin/user.js +85 -0
- package/dist/commands/admin/user.js.map +1 -0
- package/dist/commands/config.d.ts +9 -0
- package/dist/commands/config.js +178 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/context.d.ts +11 -0
- package/dist/commands/context.js +235 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/diff.js +208 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/freeze.d.ts +10 -0
- package/dist/commands/freeze.js +269 -0
- package/dist/commands/freeze.js.map +1 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +229 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +26 -0
- package/dist/commands/login.js +241 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/logout.js +75 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/migrate-yaml.d.ts +17 -0
- package/dist/commands/migrate-yaml.js +288 -0
- package/dist/commands/migrate-yaml.js.map +1 -0
- package/dist/commands/project.d.ts +7 -0
- package/dist/commands/project.js +87 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/pull.d.ts +16 -0
- package/dist/commands/pull.js +197 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +9 -0
- package/dist/commands/push.js +392 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/refresh.d.ts +9 -0
- package/dist/commands/refresh.js +123 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +190 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/team.d.ts +6 -0
- package/dist/commands/team.js +84 -0
- package/dist/commands/team.js.map +1 -0
- package/dist/commands/validate.d.ts +5 -0
- package/dist/commands/validate.js +129 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/whoami.d.ts +6 -0
- package/dist/commands/whoami.js +81 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config/adminBackend.d.ts +43 -0
- package/dist/config/adminBackend.js +33 -0
- package/dist/config/adminBackend.js.map +1 -0
- package/dist/config/rc.d.ts +70 -0
- package/dist/config/rc.js +148 -0
- package/dist/config/rc.js.map +1 -0
- package/dist/config/schema.d.ts +132 -0
- package/dist/config/schema.js +36 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/diff/engine.d.ts +35 -0
- package/dist/diff/engine.js +305 -0
- package/dist/diff/engine.js.map +1 -0
- package/dist/diff/render.d.ts +18 -0
- package/dist/diff/render.js +129 -0
- package/dist/diff/render.js.map +1 -0
- package/dist/formatter/colors.d.ts +17 -0
- package/dist/formatter/colors.js +35 -0
- package/dist/formatter/colors.js.map +1 -0
- package/dist/formatter/human.d.ts +7 -0
- package/dist/formatter/human.js +70 -0
- package/dist/formatter/human.js.map +1 -0
- package/dist/fs/atomicWrite.d.ts +7 -0
- package/dist/fs/atomicWrite.js +37 -0
- package/dist/fs/atomicWrite.js.map +1 -0
- package/dist/i18n/dispatch.d.ts +19 -0
- package/dist/i18n/dispatch.js +27 -0
- package/dist/i18n/dispatch.js.map +1 -0
- package/dist/i18n/en.d.ts +3 -0
- package/dist/i18n/en.js +275 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/help.d.ts +3 -0
- package/dist/i18n/help.en.d.ts +149 -0
- package/dist/i18n/help.en.js +150 -0
- package/dist/i18n/help.en.js.map +1 -0
- package/dist/i18n/help.js +7 -0
- package/dist/i18n/help.js.map +1 -0
- package/dist/i18n/help.zh.d.ts +149 -0
- package/dist/i18n/help.zh.js +150 -0
- package/dist/i18n/help.zh.js.map +1 -0
- package/dist/i18n/helpFormat.d.ts +7 -0
- package/dist/i18n/helpFormat.js +51 -0
- package/dist/i18n/helpFormat.js.map +1 -0
- package/dist/i18n/helpGroups.d.ts +3 -0
- package/dist/i18n/helpGroups.js +11 -0
- package/dist/i18n/helpGroups.js.map +1 -0
- package/dist/i18n/index.d.ts +16 -0
- package/dist/i18n/index.js +46 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/peekLang.d.ts +1 -0
- package/dist/i18n/peekLang.js +16 -0
- package/dist/i18n/peekLang.js.map +1 -0
- package/dist/i18n/zh.d.ts +680 -0
- package/dist/i18n/zh.js +288 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/parser/discover.d.ts +2 -0
- package/dist/parser/discover.js +69 -0
- package/dist/parser/discover.js.map +1 -0
- package/dist/parser/load-yaml.d.ts +11 -0
- package/dist/parser/load-yaml.js +25 -0
- package/dist/parser/load-yaml.js.map +1 -0
- package/dist/pkgVersion.d.ts +9 -0
- package/dist/pkgVersion.js +22 -0
- package/dist/pkgVersion.js.map +1 -0
- package/dist/renderer/context-md.d.ts +27 -0
- package/dist/renderer/context-md.js +444 -0
- package/dist/renderer/context-md.js.map +1 -0
- package/dist/renderer/refresh-md.d.ts +18 -0
- package/dist/renderer/refresh-md.js +93 -0
- package/dist/renderer/refresh-md.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/modules.d.ts +461 -0
- package/dist/schema/modules.js +184 -0
- package/dist/schema/modules.js.map +1 -0
- package/dist/schema/prd.d.ts +1018 -0
- package/dist/schema/prd.js +65 -0
- package/dist/schema/prd.js.map +1 -0
- package/dist/sync/cliRcHelpers.d.ts +25 -0
- package/dist/sync/cliRcHelpers.js +62 -0
- package/dist/sync/cliRcHelpers.js.map +1 -0
- package/dist/sync/conflicts.d.ts +7 -0
- package/dist/sync/conflicts.js +72 -0
- package/dist/sync/conflicts.js.map +1 -0
- package/dist/sync/headProbe.d.ts +1 -0
- package/dist/sync/headProbe.js +95 -0
- package/dist/sync/headProbe.js.map +1 -0
- package/dist/sync/normalize.d.ts +10 -0
- package/dist/sync/normalize.js +13 -0
- package/dist/sync/normalize.js.map +1 -0
- package/dist/sync/syncJson.d.ts +18 -0
- package/dist/sync/syncJson.js +42 -0
- package/dist/sync/syncJson.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/dist/validators/blocking.d.ts +2 -0
- package/dist/validators/blocking.js +25 -0
- package/dist/validators/blocking.js.map +1 -0
- package/dist/validators/duplicate-ids.d.ts +2 -0
- package/dist/validators/duplicate-ids.js +58 -0
- package/dist/validators/duplicate-ids.js.map +1 -0
- package/dist/validators/references.d.ts +5 -0
- package/dist/validators/references.js +165 -0
- package/dist/validators/references.js.map +1 -0
- package/dist/validators/required-modules.d.ts +8 -0
- package/dist/validators/required-modules.js +59 -0
- package/dist/validators/required-modules.js.map +1 -0
- package/dist/validators/runner.d.ts +11 -0
- package/dist/validators/runner.js +73 -0
- package/dist/validators/runner.js.map +1 -0
- package/dist/workspace.d.ts +32 -0
- package/dist/workspace.js +37 -0
- package/dist/workspace.js.map +1 -0
- package/dist/yaml-indexer.d.ts +15 -0
- package/dist/yaml-indexer.js +112 -0
- package/dist/yaml-indexer.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @prd-improve/cli
|
|
2
|
+
|
|
3
|
+
PRD Improve CLI —— PRD 生命周期 + 团队协作。完整命令见 `prd --help`。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @prd-improve/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 用法
|
|
12
|
+
|
|
13
|
+
命令分 5 组(`prd --help` 列全;`--lang en` / `PRD_LANG=en` 切英文):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 生命周期(离线,无需后端)
|
|
17
|
+
prd init / validate / freeze / context / diff / refresh / migrate-yaml
|
|
18
|
+
|
|
19
|
+
# 认证 / 会话
|
|
20
|
+
prd login / logout / whoami
|
|
21
|
+
|
|
22
|
+
# 后端同步
|
|
23
|
+
prd push / pull / status
|
|
24
|
+
|
|
25
|
+
# 团队 / 项目
|
|
26
|
+
prd team list / project list
|
|
27
|
+
|
|
28
|
+
# 管理 / 配置
|
|
29
|
+
prd admin <user|token|team|project|member|team-member|audit> / config <show|set|use>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
每个命令的参数与示例见 `prd <command> --help`。
|
|
33
|
+
|
|
34
|
+
## 退出码(`prd validate`)
|
|
35
|
+
|
|
36
|
+
- `0`: 全部 PASS 或仅 SKIP
|
|
37
|
+
- `1`: 有 FAIL(模块缺失 / 引用错误 / 重复 ID / blocking 问题 / schema 违反)
|
|
38
|
+
- `2`: 解析错误(yaml 语法错 / 用户指定路径不存在 / .prd 目录缺失)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface AnchorMatch {
|
|
2
|
+
line: number;
|
|
3
|
+
heading: string;
|
|
4
|
+
level: number;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SliceResult {
|
|
8
|
+
found: boolean;
|
|
9
|
+
matches?: AnchorMatch[];
|
|
10
|
+
slice?: string;
|
|
11
|
+
sliceStart?: number;
|
|
12
|
+
sliceEnd?: number;
|
|
13
|
+
}
|
|
14
|
+
interface SliceOptions {
|
|
15
|
+
exact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function sliceByAnchor(markdown: string, anchor: string, opts: SliceOptions): SliceResult;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
function parseHeadings(markdown) {
|
|
2
|
+
const lines = markdown.split("\n");
|
|
3
|
+
const out = [];
|
|
4
|
+
let inFence = false;
|
|
5
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6
|
+
// fenced code block guard: 切换 inFence,fence 内不识别 heading
|
|
7
|
+
if (/^(```|~~~)/.test(lines[i])) {
|
|
8
|
+
inFence = !inFence;
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (inFence)
|
|
12
|
+
continue;
|
|
13
|
+
const m = lines[i].match(/^(#{1,6})\s+(.+?)\s*$/);
|
|
14
|
+
if (m) {
|
|
15
|
+
out.push({
|
|
16
|
+
line: i + 1,
|
|
17
|
+
heading: lines[i],
|
|
18
|
+
level: m[1].length,
|
|
19
|
+
text: m[2],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function distanceToNextSameOrHigher(headings, index) {
|
|
26
|
+
const h = headings[index];
|
|
27
|
+
for (let j = index + 1; j < headings.length; j++) {
|
|
28
|
+
if (headings[j].level <= h.level)
|
|
29
|
+
return j - index;
|
|
30
|
+
}
|
|
31
|
+
return headings.length - index;
|
|
32
|
+
}
|
|
33
|
+
function extractSlice(markdown, headings, matchIdx) {
|
|
34
|
+
const h = headings[matchIdx];
|
|
35
|
+
const lines = markdown.split("\n");
|
|
36
|
+
let endLine = lines.length;
|
|
37
|
+
for (let j = matchIdx + 1; j < headings.length; j++) {
|
|
38
|
+
if (headings[j].level <= h.level) {
|
|
39
|
+
endLine = headings[j].line - 1;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const slice = lines.slice(h.line - 1, endLine).join("\n");
|
|
44
|
+
return { slice, start: h.line, end: endLine };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 公共 tier resolver:消三级 tier 的 copy-paste 重复
|
|
48
|
+
* - 0 匹配 → null(交给下一级)
|
|
49
|
+
* - 1 匹配 → 切片返回
|
|
50
|
+
* - 多匹配 → 若允许 section-head ranking 且第一名距离严格大于第二名,选第一名切片;
|
|
51
|
+
* 否则返回候选列表不切片(found: false)
|
|
52
|
+
*/
|
|
53
|
+
function resolveTier(markdown, headings, candidates, allowSectionHeadRanking) {
|
|
54
|
+
if (candidates.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
if (candidates.length === 1) {
|
|
57
|
+
const idx = headings.indexOf(candidates[0]);
|
|
58
|
+
const s = extractSlice(markdown, headings, idx);
|
|
59
|
+
return {
|
|
60
|
+
found: true,
|
|
61
|
+
matches: candidates,
|
|
62
|
+
slice: s.slice,
|
|
63
|
+
sliceStart: s.start,
|
|
64
|
+
sliceEnd: s.end,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 多匹配
|
|
68
|
+
if (!allowSectionHeadRanking)
|
|
69
|
+
return { found: false, matches: candidates };
|
|
70
|
+
const ranked = candidates
|
|
71
|
+
.map((h) => ({
|
|
72
|
+
h,
|
|
73
|
+
idx: headings.indexOf(h),
|
|
74
|
+
dist: distanceToNextSameOrHigher(headings, headings.indexOf(h)),
|
|
75
|
+
}))
|
|
76
|
+
.sort((a, b) => b.dist - a.dist);
|
|
77
|
+
if (ranked[0].dist > ranked[1].dist) {
|
|
78
|
+
const s = extractSlice(markdown, headings, ranked[0].idx);
|
|
79
|
+
return {
|
|
80
|
+
found: true,
|
|
81
|
+
matches: [ranked[0].h],
|
|
82
|
+
slice: s.slice,
|
|
83
|
+
sliceStart: s.start,
|
|
84
|
+
sliceEnd: s.end,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { found: false, matches: candidates };
|
|
88
|
+
}
|
|
89
|
+
export function sliceByAnchor(markdown, anchor, opts) {
|
|
90
|
+
const headings = parseHeadings(markdown);
|
|
91
|
+
const trimmed = anchor.trim();
|
|
92
|
+
// tier 1: exact match (全 heading 文字精确等值,或带井号前缀的完整 heading 等值)
|
|
93
|
+
// 不允许 section-head ranking:精确等值多匹配只可能是重复 heading,应返回候选不切片
|
|
94
|
+
const tier1 = headings.filter((h) => h.text === trimmed ||
|
|
95
|
+
h.heading === trimmed ||
|
|
96
|
+
h.heading.replace(/^#+\s+/, "") === trimmed);
|
|
97
|
+
const r1 = resolveTier(markdown, headings, tier1, false);
|
|
98
|
+
if (r1)
|
|
99
|
+
return r1;
|
|
100
|
+
if (opts.exact)
|
|
101
|
+
return { found: false }; // exact mode 不 fallback
|
|
102
|
+
// tier 2: prefix match
|
|
103
|
+
const tier2 = headings.filter((h) => h.heading.startsWith(trimmed));
|
|
104
|
+
const r2 = resolveTier(markdown, headings, tier2, true);
|
|
105
|
+
if (r2)
|
|
106
|
+
return r2;
|
|
107
|
+
// tier 3: substring match
|
|
108
|
+
const tier3 = headings.filter((h) => h.heading.includes(trimmed));
|
|
109
|
+
const r3 = resolveTier(markdown, headings, tier3, true);
|
|
110
|
+
if (r3)
|
|
111
|
+
return r3;
|
|
112
|
+
return { found: false };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=anchor-slicer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-slicer.js","sourceRoot":"","sources":["../src/anchor-slicer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ResolvedBackend } from "../config/schema.js";
|
|
2
|
+
export interface ApiOk<T = unknown> {
|
|
3
|
+
ok: true;
|
|
4
|
+
status: number;
|
|
5
|
+
headers: Headers;
|
|
6
|
+
body: T;
|
|
7
|
+
}
|
|
8
|
+
export interface ApiErr {
|
|
9
|
+
ok: false;
|
|
10
|
+
status: number;
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
details?: unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Set when the failure was network-level (DNS, abort, refused) rather than
|
|
16
|
+
* a structured server response. status === 0 in that case.
|
|
17
|
+
*/
|
|
18
|
+
network?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type ApiResult<T = unknown> = ApiOk<T> | ApiErr;
|
|
21
|
+
interface FetchOpts {
|
|
22
|
+
method?: string;
|
|
23
|
+
body?: string | undefined;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
/** Override global fetch (tests inject in-process Hono app dispatcher). */
|
|
26
|
+
fetchFn?: typeof fetch;
|
|
27
|
+
}
|
|
28
|
+
export declare function apiFetch<T = unknown>(backend: ResolvedBackend, path: string, opts?: FetchOpts): Promise<ApiResult<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* v1.1 URL builder for scoped feature/comment/version routes
|
|
31
|
+
* (spec §4.2 / §6.2). Returns a leading-slash path relative to backend.url so
|
|
32
|
+
* apiFetch can concatenate it directly.
|
|
33
|
+
*
|
|
34
|
+
* projectUrl('team-a', 'p1', 'features', 'foo')
|
|
35
|
+
* → '/api/teams/team-a/projects/p1/features/foo'
|
|
36
|
+
* projectUrl('team-a', 'p1', 'features')
|
|
37
|
+
* → '/api/teams/team-a/projects/p1/features'
|
|
38
|
+
*
|
|
39
|
+
* Callers are responsible for slug-shape pre-validation; this builder just
|
|
40
|
+
* concatenates so a bad slug surfaces as 400/404 from the server rather than
|
|
41
|
+
* being silently rewritten.
|
|
42
|
+
*/
|
|
43
|
+
export declare function projectUrl(team: string, project: string, ...rest: string[]): string;
|
|
44
|
+
/**
|
|
45
|
+
* Read meta.team / meta.project / meta.feature from yaml content
|
|
46
|
+
* (spec §4.2 step 1). All three are required for v1.1 routing — v0.1 yaml
|
|
47
|
+
* (pre-`prd migrate-yaml`) lacks meta.team and intentionally errors here so
|
|
48
|
+
* the user sees a self-service hint instead of getting a 410 dance from a
|
|
49
|
+
* blindly-routed request.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseMetaScope(yamlContent: string): {
|
|
52
|
+
team: string;
|
|
53
|
+
project: string;
|
|
54
|
+
feature: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* HEAD specialization. Servers send no body, so we just return the status
|
|
58
|
+
* and headers. No body parse attempt.
|
|
59
|
+
*/
|
|
60
|
+
export declare function apiHead(backend: ResolvedBackend, path: string, opts?: {
|
|
61
|
+
fetchFn?: typeof fetch;
|
|
62
|
+
headers?: Record<string, string>;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
ok: true;
|
|
65
|
+
status: number;
|
|
66
|
+
headers: Headers;
|
|
67
|
+
} | ApiErr>;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for sync commands (T6).
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around global fetch with:
|
|
5
|
+
* - Bearer-token auth header (token comes from ResolvedBackend).
|
|
6
|
+
* - Uniform error shape: success → { ok, status, headers, body }; error →
|
|
7
|
+
* { ok:false, status, code, message, details? } matching server's
|
|
8
|
+
* {ok:false,code,message,details} body schema.
|
|
9
|
+
* - HEAD specialization: don't try to JSON-parse 200 body (servers may send
|
|
10
|
+
* null per RFC 7231 §4.3.2 — Hono does in our case).
|
|
11
|
+
*
|
|
12
|
+
* v1.1 additions (T-v1.1-6, spec §4.2 / §6.2):
|
|
13
|
+
* - projectUrl(team, project, ...rest) — URL builder for the scoped
|
|
14
|
+
* /api/teams/:teamSlug/projects/:projectSlug/* paths.
|
|
15
|
+
* - parseMetaScope(yaml) — read meta.team / meta.project / meta.feature
|
|
16
|
+
* from yaml so sync commands can route requests without flags.
|
|
17
|
+
* - 410 API_V0_GONE is already surfaced verbatim by apiFetch's structured
|
|
18
|
+
* error branch; callers detect by `res.code === 'API_V0_GONE'`.
|
|
19
|
+
*/
|
|
20
|
+
import { load as yamlLoad } from "js-yaml";
|
|
21
|
+
export async function apiFetch(backend, path, opts = {}) {
|
|
22
|
+
const url = backend.url.replace(/\/+$/, "") + path;
|
|
23
|
+
const fn = opts.fetchFn ?? globalThis.fetch;
|
|
24
|
+
let res;
|
|
25
|
+
try {
|
|
26
|
+
res = await fn(url, {
|
|
27
|
+
method: opts.method ?? "GET",
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${backend.token}`,
|
|
30
|
+
...(opts.headers ?? {}),
|
|
31
|
+
},
|
|
32
|
+
body: opts.body,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
status: 0,
|
|
39
|
+
code: "NETWORK_ERROR",
|
|
40
|
+
message: e.message,
|
|
41
|
+
network: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Empty body responses (204, 205) — short-circuit. Otherwise try JSON, fall
|
|
45
|
+
// back to text if parse fails (server returns YAML for `?for=pull`).
|
|
46
|
+
// 304 Not Modified is conventionally `ok:true` in this client — callers like
|
|
47
|
+
// `prd pull` distinguish via `status === 304` to render the F7 "已是最新"
|
|
48
|
+
// message (spec §8). The native Response.ok is false for 304, so this
|
|
49
|
+
// explicit branch overrides that.
|
|
50
|
+
if (res.status === 304) {
|
|
51
|
+
return { ok: true, status: 304, headers: res.headers, body: null };
|
|
52
|
+
}
|
|
53
|
+
if (res.status === 204 || res.status === 205) {
|
|
54
|
+
return { ok: true, status: res.status, headers: res.headers, body: null };
|
|
55
|
+
}
|
|
56
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
57
|
+
let parsed;
|
|
58
|
+
if (ct.includes("application/json")) {
|
|
59
|
+
try {
|
|
60
|
+
parsed = await res.json();
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
status: res.status,
|
|
66
|
+
code: "INVALID_RESPONSE",
|
|
67
|
+
message: `Failed to parse JSON: ${e.message}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
parsed = await res.text();
|
|
73
|
+
}
|
|
74
|
+
if (res.ok) {
|
|
75
|
+
// Server convention is { ok:true, data: ... } for JSON responses.
|
|
76
|
+
return { ok: true, status: res.status, headers: res.headers, body: parsed };
|
|
77
|
+
}
|
|
78
|
+
// Error path. JSON body: { ok:false, code, message, details? }. Plaintext
|
|
79
|
+
// body: stuff it into message so the user sees something.
|
|
80
|
+
if (parsed && typeof parsed === "object" && parsed.code) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
status: res.status,
|
|
84
|
+
code: String(parsed.code),
|
|
85
|
+
message: String(parsed.message ?? "(no message)"),
|
|
86
|
+
details: parsed.details,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
status: res.status,
|
|
92
|
+
code: "HTTP_ERROR",
|
|
93
|
+
message: typeof parsed === "string" ? parsed : `HTTP ${res.status}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* v1.1 URL builder for scoped feature/comment/version routes
|
|
98
|
+
* (spec §4.2 / §6.2). Returns a leading-slash path relative to backend.url so
|
|
99
|
+
* apiFetch can concatenate it directly.
|
|
100
|
+
*
|
|
101
|
+
* projectUrl('team-a', 'p1', 'features', 'foo')
|
|
102
|
+
* → '/api/teams/team-a/projects/p1/features/foo'
|
|
103
|
+
* projectUrl('team-a', 'p1', 'features')
|
|
104
|
+
* → '/api/teams/team-a/projects/p1/features'
|
|
105
|
+
*
|
|
106
|
+
* Callers are responsible for slug-shape pre-validation; this builder just
|
|
107
|
+
* concatenates so a bad slug surfaces as 400/404 from the server rather than
|
|
108
|
+
* being silently rewritten.
|
|
109
|
+
*/
|
|
110
|
+
export function projectUrl(team, project, ...rest) {
|
|
111
|
+
const base = `/api/teams/${team}/projects/${project}`;
|
|
112
|
+
if (rest.length === 0)
|
|
113
|
+
return base;
|
|
114
|
+
return `${base}/${rest.join("/")}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Read meta.team / meta.project / meta.feature from yaml content
|
|
118
|
+
* (spec §4.2 step 1). All three are required for v1.1 routing — v0.1 yaml
|
|
119
|
+
* (pre-`prd migrate-yaml`) lacks meta.team and intentionally errors here so
|
|
120
|
+
* the user sees a self-service hint instead of getting a 410 dance from a
|
|
121
|
+
* blindly-routed request.
|
|
122
|
+
*/
|
|
123
|
+
export function parseMetaScope(yamlContent) {
|
|
124
|
+
let doc;
|
|
125
|
+
try {
|
|
126
|
+
doc = yamlLoad(yamlContent);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
throw new Error(`parseMetaScope: yaml parse failed — ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
const meta = doc?.meta ?? {};
|
|
132
|
+
const team = typeof meta.team === "string" ? meta.team : "";
|
|
133
|
+
const project = typeof meta.project === "string" ? meta.project : "";
|
|
134
|
+
const feature = typeof meta.feature === "string" ? meta.feature : "";
|
|
135
|
+
if (!team || !project || !feature) {
|
|
136
|
+
throw new Error("meta.team / meta.project / meta.feature 必须全部存在;请先跑 prd migrate-yaml 升级 v0.1 → v0.2 yaml");
|
|
137
|
+
}
|
|
138
|
+
return { team, project, feature };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* HEAD specialization. Servers send no body, so we just return the status
|
|
142
|
+
* and headers. No body parse attempt.
|
|
143
|
+
*/
|
|
144
|
+
export async function apiHead(backend, path, opts = {}) {
|
|
145
|
+
const url = backend.url.replace(/\/+$/, "") + path;
|
|
146
|
+
const fn = opts.fetchFn ?? globalThis.fetch;
|
|
147
|
+
try {
|
|
148
|
+
const res = await fn(url, {
|
|
149
|
+
method: "HEAD",
|
|
150
|
+
headers: {
|
|
151
|
+
Authorization: `Bearer ${backend.token}`,
|
|
152
|
+
...(opts.headers ?? {}),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
if (res.ok || res.status === 304) {
|
|
156
|
+
return { ok: true, status: res.status, headers: res.headers };
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
status: res.status,
|
|
161
|
+
code: res.status === 404 ? "FEATURE_NOT_FOUND" : "HTTP_ERROR",
|
|
162
|
+
message: `HTTP ${res.status}`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
status: 0,
|
|
169
|
+
code: "NETWORK_ERROR",
|
|
170
|
+
message: e.message,
|
|
171
|
+
network: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote the [Unreleased] (or [未发布]) H2 section to a versioned H2,
|
|
3
|
+
* and insert a new empty [Unreleased] section right after the H1 (if any).
|
|
4
|
+
*
|
|
5
|
+
* If no [Unreleased] / [未发布] section is found, returns content unchanged
|
|
6
|
+
* (degraded fallback — user's CHANGELOG may use non-Keep-a-Changelog format).
|
|
7
|
+
* --- separators and other user formatting are preserved verbatim.
|
|
8
|
+
*/
|
|
9
|
+
export declare function promoteUnreleasedSection(content: string, version: string, frozenAt: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote the [Unreleased] (or [未发布]) H2 section to a versioned H2,
|
|
3
|
+
* and insert a new empty [Unreleased] section right after the H1 (if any).
|
|
4
|
+
*
|
|
5
|
+
* If no [Unreleased] / [未发布] section is found, returns content unchanged
|
|
6
|
+
* (degraded fallback — user's CHANGELOG may use non-Keep-a-Changelog format).
|
|
7
|
+
* --- separators and other user formatting are preserved verbatim.
|
|
8
|
+
*/
|
|
9
|
+
export function promoteUnreleasedSection(content, version, frozenAt) {
|
|
10
|
+
const lines = content.split("\n");
|
|
11
|
+
// 找 [Unreleased] 或 [未发布] H2
|
|
12
|
+
const unreleasedIdx = lines.findIndex((l) => /^##\s+\[Unreleased\]\s*$/.test(l) || /^##\s+\[未发布\]\s*$/.test(l));
|
|
13
|
+
if (unreleasedIdx < 0)
|
|
14
|
+
return content; // 降级:找不到不动
|
|
15
|
+
// promote
|
|
16
|
+
lines[unreleasedIdx] = `## [${version}] - ${frozenAt}`;
|
|
17
|
+
// 找 H1(`# `开头),在其后第一个非空行前插新 [Unreleased] 段
|
|
18
|
+
const h1Idx = lines.findIndex((l) => /^#\s+/.test(l));
|
|
19
|
+
const insertSegment = ["## [Unreleased]", ""];
|
|
20
|
+
if (h1Idx >= 0) {
|
|
21
|
+
// 跳过 H1 后的连续空行
|
|
22
|
+
let insertAt = h1Idx + 1;
|
|
23
|
+
while (insertAt < lines.length && lines[insertAt].trim() === "")
|
|
24
|
+
insertAt++;
|
|
25
|
+
// 在 insertAt 之前插:[Unreleased] 段 + 一个空行
|
|
26
|
+
lines.splice(insertAt, 0, ...insertSegment, "");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// 没 H1,塞最顶
|
|
30
|
+
lines.unshift(...insertSegment, "");
|
|
31
|
+
}
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=changelog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAe,EACf,OAAe,EACf,QAAgB;IAEhB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,4BAA4B;IAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CACnC,CAAC,CAAC,EAAE,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CACzE,CAAC;IACF,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,WAAW;IAElD,UAAU;IACV,KAAK,CAAC,aAAa,CAAC,GAAG,OAAO,OAAO,OAAO,QAAQ,EAAE,CAAC;IAEvD,2CAA2C;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IAC9C,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,eAAe;QACf,IAAI,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QACzB,OAAO,QAAQ,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,QAAQ,EAAE,CAAC;QAC5E,uCAAuC;QACvC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,aAAa,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,WAAW;QACX,KAAK,CAAC,OAAO,CAAC,GAAG,aAAa,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/dist/cli.d.ts
ADDED