tegami 0.1.6 → 0.2.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/dist/api-D-jf_8xY.js +116 -0
- package/dist/cli/index.d.ts +4 -4
- package/dist/cli/index.js +94 -126
- package/dist/{generate-B3bcuKLY.js → generate-BMlrn-2e.js} +36 -18
- package/dist/generators/simple.d.ts +1 -1
- package/dist/generators/simple.js +3 -3
- package/dist/{graph-DLKPUXSD.js → graph-DrzluXw8.js} +17 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +478 -105
- package/dist/plugins/git.d.ts +1 -1
- package/dist/plugins/git.js +39 -35
- package/dist/plugins/github.d.ts +28 -15
- package/dist/plugins/github.js +158 -158
- package/dist/providers/cargo.d.ts +2 -2
- package/dist/providers/cargo.js +86 -100
- package/dist/providers/npm.d.ts +2 -2
- package/dist/providers/npm.js +132 -129
- package/dist/schemas-B7N6EE2k.js +26 -0
- package/dist/{types-DexeJdl7.d.ts → types-CurHqnWl.d.ts} +199 -229
- package/package.json +6 -7
- package/dist/checks-azjuIFt7.js +0 -109
- package/dist/publish-DkRT0yh6.js +0 -282
- package/dist/schemas-CurBAaW5.js +0 -42
package/dist/index.js
CHANGED
|
@@ -1,45 +1,33 @@
|
|
|
1
1
|
import { a as maxBump } from "./semver-C4vJ4SK8.js";
|
|
2
|
-
import { n as
|
|
2
|
+
import { i as changelogFrontmatterSchema, n as generateFromCommits } from "./generate-BMlrn-2e.js";
|
|
3
3
|
import { r as handlePluginError } from "./error-DNy8R5ue.js";
|
|
4
|
-
import {
|
|
5
|
-
import { t as PackageGraph } from "./graph-DLKPUXSD.js";
|
|
4
|
+
import { t as PackageGraph } from "./graph-DrzluXw8.js";
|
|
6
5
|
import { cargo } from "./providers/cargo.js";
|
|
7
6
|
import { npm } from "./providers/npm.js";
|
|
8
7
|
import { simpleGenerator } from "./generators/simple.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import path, { dirname, join } from "node:path";
|
|
8
|
+
import fs, { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import path, { basename, dirname, join } from "node:path";
|
|
12
10
|
import * as semver from "semver";
|
|
11
|
+
import z from "zod";
|
|
12
|
+
import { dump, load, visit } from "js-yaml";
|
|
13
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
14
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
13
15
|
//#region src/context.ts
|
|
14
16
|
async function createTegamiContext(options = {}) {
|
|
15
17
|
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
16
18
|
const changelogDir = path.resolve(cwd, options.changelogDir ?? ".tegami");
|
|
17
19
|
const graph = new PackageGraph();
|
|
18
|
-
const registryClients = /* @__PURE__ */ new Map();
|
|
19
20
|
const ctx = {
|
|
20
21
|
cwd,
|
|
21
22
|
changelogDir,
|
|
22
|
-
|
|
23
|
+
lockPath: options.lockPath ? path.resolve(cwd, options.lockPath) : path.join(changelogDir, "publish-lock.yaml"),
|
|
23
24
|
options,
|
|
24
25
|
plugins: resolvePlugins([
|
|
25
26
|
npm(options.npm),
|
|
26
27
|
cargo(options.cargo),
|
|
27
28
|
...options.plugins ?? []
|
|
28
29
|
]),
|
|
29
|
-
graph
|
|
30
|
-
getRegistryClient(pkgOrId) {
|
|
31
|
-
let client;
|
|
32
|
-
if (typeof pkgOrId === "string") client = registryClients.get(pkgOrId);
|
|
33
|
-
else for (const item of registryClients.values()) if (item.supports && item.supports(pkgOrId)) {
|
|
34
|
-
client = item;
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
if (!client) {
|
|
38
|
-
const id = typeof pkgOrId === "string" ? pkgOrId : pkgOrId.manager;
|
|
39
|
-
throw new Error(`No registry client is available for ${id}.`);
|
|
40
|
-
}
|
|
41
|
-
return client;
|
|
42
|
-
}
|
|
30
|
+
graph
|
|
43
31
|
};
|
|
44
32
|
for (const plugin of ctx.plugins) await handlePluginError(plugin, "init", () => plugin.init?.call(ctx));
|
|
45
33
|
for (const plugin of ctx.plugins) await handlePluginError(plugin, "resolve", () => plugin.resolve?.call(ctx));
|
|
@@ -58,11 +46,6 @@ async function createTegamiContext(options = {}) {
|
|
|
58
46
|
pkg.setPackageOptions(packageOptions);
|
|
59
47
|
if (packageOptions.group) graph.addGroupMember(packageOptions.group, pkg.id);
|
|
60
48
|
}
|
|
61
|
-
for (const plugin of ctx.plugins) {
|
|
62
|
-
const clients = await handlePluginError(plugin, "createRegistryClient", () => plugin.createRegistryClient?.call(ctx));
|
|
63
|
-
if (!clients) continue;
|
|
64
|
-
for (const client of Array.isArray(clients) ? clients : [clients]) registryClients.set(client.id, client);
|
|
65
|
-
}
|
|
66
49
|
return ctx;
|
|
67
50
|
}
|
|
68
51
|
const PLUGIN_ORDER = {
|
|
@@ -74,18 +57,203 @@ function resolvePlugins(plugins = []) {
|
|
|
74
57
|
return plugins.flat(Infinity).sort((a, b) => PLUGIN_ORDER[a.enforce ?? "default"] - PLUGIN_ORDER[b.enforce ?? "default"]);
|
|
75
58
|
}
|
|
76
59
|
//#endregion
|
|
60
|
+
//#region src/utils/frontmatter.ts
|
|
61
|
+
/**
|
|
62
|
+
* Inspired by https://github.com/jonschlinkert/gray-matter
|
|
63
|
+
*/
|
|
64
|
+
const regex = /^---\r?\n(.+?)\r?\n---\r?\n?/s;
|
|
65
|
+
/**
|
|
66
|
+
* parse frontmatter, it supports only yaml format
|
|
67
|
+
*/
|
|
68
|
+
function frontmatter(input) {
|
|
69
|
+
const output = {
|
|
70
|
+
matter: "",
|
|
71
|
+
data: {},
|
|
72
|
+
content: input
|
|
73
|
+
};
|
|
74
|
+
const match = regex.exec(input);
|
|
75
|
+
if (!match) return output;
|
|
76
|
+
output.matter = match[0];
|
|
77
|
+
output.content = input.slice(match[0].length);
|
|
78
|
+
output.data = load(match[1]) ?? {};
|
|
79
|
+
return output;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/changelog/parse.ts
|
|
83
|
+
async function getChangelogFiles(context) {
|
|
84
|
+
return (await readdir(context.changelogDir).catch(() => [])).filter((file) => file.endsWith(".md"));
|
|
85
|
+
}
|
|
86
|
+
async function readChangelogEntries(context) {
|
|
87
|
+
const dir = context.changelogDir;
|
|
88
|
+
const files = await getChangelogFiles(context);
|
|
89
|
+
return (await Promise.all(files.map(async (file) => {
|
|
90
|
+
const filePath = join(dir, file);
|
|
91
|
+
const content = await readFile(filePath, "utf8");
|
|
92
|
+
return parseChangelogFile(basename(filePath), content);
|
|
93
|
+
}))).filter((v) => v !== void 0);
|
|
94
|
+
}
|
|
95
|
+
/** Parse one changelog markdown file into release entries. */
|
|
96
|
+
function parseChangelogFile(filename, content) {
|
|
97
|
+
const parsed = frontmatter(content);
|
|
98
|
+
const { success, data } = changelogFrontmatterSchema.safeParse(parsed.data);
|
|
99
|
+
if (!success || !data.packages) return;
|
|
100
|
+
const tree = fromMarkdown(parsed.content);
|
|
101
|
+
let headingBump;
|
|
102
|
+
const packages = /* @__PURE__ */ new Map();
|
|
103
|
+
const sections = [];
|
|
104
|
+
for (const section of getHeadingSections(tree)) {
|
|
105
|
+
const sectionBumpType = headingToBump(section.heading.depth);
|
|
106
|
+
if (sectionBumpType) headingBump = headingBump ? maxBump(headingBump, sectionBumpType) : sectionBumpType;
|
|
107
|
+
sections.push({
|
|
108
|
+
depth: section.heading.depth,
|
|
109
|
+
title: sectionToMarkdown(section.heading.children),
|
|
110
|
+
content: sectionToMarkdown(section.children)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (sections.length === 0) return;
|
|
114
|
+
if (Array.isArray(data.packages)) {
|
|
115
|
+
if (!headingBump) return;
|
|
116
|
+
for (const pkg of data.packages) packages.set(pkg, { type: headingBump });
|
|
117
|
+
} else for (const [k, v] of Object.entries(data.packages)) {
|
|
118
|
+
let config;
|
|
119
|
+
if (typeof v === "string") config = { type: v };
|
|
120
|
+
else if (v === null) config = { type: headingBump };
|
|
121
|
+
else config = v;
|
|
122
|
+
if (config.type || config.replay?.length) packages.set(k, config);
|
|
123
|
+
}
|
|
124
|
+
if (packages.size === 0) return;
|
|
125
|
+
const entry = {
|
|
126
|
+
id: filename,
|
|
127
|
+
filename,
|
|
128
|
+
subject: data.subject,
|
|
129
|
+
packages,
|
|
130
|
+
sections,
|
|
131
|
+
_raw_body: parsed.content,
|
|
132
|
+
getRawContent() {
|
|
133
|
+
return [
|
|
134
|
+
"---",
|
|
135
|
+
dump({
|
|
136
|
+
subject: this.subject,
|
|
137
|
+
packages: Object.fromEntries(this.packages.entries())
|
|
138
|
+
}).trim(),
|
|
139
|
+
"---",
|
|
140
|
+
"",
|
|
141
|
+
entry._raw_body.trim(),
|
|
142
|
+
""
|
|
143
|
+
].join("\n");
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
return entry;
|
|
147
|
+
}
|
|
148
|
+
function parseReplayCondition(condition) {
|
|
149
|
+
if (condition.startsWith("exit prerelease:")) return {
|
|
150
|
+
type: "on-exit-prerelease",
|
|
151
|
+
name: condition.slice(16).trimStart()
|
|
152
|
+
};
|
|
153
|
+
const idx = condition.lastIndexOf("@");
|
|
154
|
+
if (idx <= 0) return null;
|
|
155
|
+
return {
|
|
156
|
+
type: "on-version",
|
|
157
|
+
name: condition.slice(0, idx),
|
|
158
|
+
version: condition.slice(idx + 1)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function getHeadingSections(tree) {
|
|
162
|
+
const sections = [];
|
|
163
|
+
let current;
|
|
164
|
+
for (const child of tree.children) {
|
|
165
|
+
if (child.type === "heading") {
|
|
166
|
+
current = {
|
|
167
|
+
heading: child,
|
|
168
|
+
children: []
|
|
169
|
+
};
|
|
170
|
+
sections.push(current);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
current?.children.push(child);
|
|
174
|
+
}
|
|
175
|
+
return sections;
|
|
176
|
+
}
|
|
177
|
+
function sectionToMarkdown(children) {
|
|
178
|
+
if (children.length === 0) return "";
|
|
179
|
+
return toMarkdown({
|
|
180
|
+
type: "root",
|
|
181
|
+
children
|
|
182
|
+
}, {
|
|
183
|
+
bullet: "-",
|
|
184
|
+
fence: "`"
|
|
185
|
+
}).trim();
|
|
186
|
+
}
|
|
187
|
+
function headingToBump(depth) {
|
|
188
|
+
switch (depth) {
|
|
189
|
+
case 1: return "major";
|
|
190
|
+
case 2: return "minor";
|
|
191
|
+
case 3: return "patch";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/plans/lock.ts
|
|
196
|
+
function inlineMultilineScalars(documents) {
|
|
197
|
+
visit(documents, (node) => {
|
|
198
|
+
if (node.kind !== "scalar" || !node.value.includes("\n")) return;
|
|
199
|
+
node.style.doubleQuoted = true;
|
|
200
|
+
node.style.literal = false;
|
|
201
|
+
node.style.folded = false;
|
|
202
|
+
node.style.singleQuoted = false;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const lockDumpOptions = {
|
|
206
|
+
sortKeys: true,
|
|
207
|
+
lineWidth: -1,
|
|
208
|
+
transform: inlineMultilineScalars
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* the data structure of `publish-lock.yaml` file.
|
|
212
|
+
*/
|
|
213
|
+
var PublishLock = class {
|
|
214
|
+
data;
|
|
215
|
+
constructor(data = /* @__PURE__ */ new Map()) {
|
|
216
|
+
this.data = data;
|
|
217
|
+
}
|
|
218
|
+
/** write data to namespace, note that the `data` must be serializable in yaml */
|
|
219
|
+
write(namespace, data) {
|
|
220
|
+
let arr = this.data.get(namespace);
|
|
221
|
+
if (!arr) {
|
|
222
|
+
arr = [];
|
|
223
|
+
this.data.set(namespace, arr);
|
|
224
|
+
}
|
|
225
|
+
arr.push(data);
|
|
226
|
+
}
|
|
227
|
+
read(namespace) {
|
|
228
|
+
return this.data.get(namespace)?.shift();
|
|
229
|
+
}
|
|
230
|
+
size(namespace) {
|
|
231
|
+
return this.data.get(namespace)?.length ?? 0;
|
|
232
|
+
}
|
|
233
|
+
serialize() {
|
|
234
|
+
let res = dump(Object.fromEntries(this.data.entries()), lockDumpOptions);
|
|
235
|
+
if (!res.endsWith("\n")) res += "\n";
|
|
236
|
+
return res;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const baseSchema = z.record(z.string(), z.array(z.unknown()));
|
|
240
|
+
function parsePublishLock(content) {
|
|
241
|
+
const data = baseSchema.parse(load(content));
|
|
242
|
+
return new PublishLock(new Map(Object.entries(data)));
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
77
245
|
//#region src/plans/policy.ts
|
|
78
246
|
function groupPolicy({ graph }) {
|
|
79
247
|
return {
|
|
80
248
|
id: "group",
|
|
81
|
-
onUpdate({ pkg,
|
|
82
|
-
if (!
|
|
249
|
+
onUpdate({ pkg, packageDraft }) {
|
|
250
|
+
if (!packageDraft.type) return;
|
|
83
251
|
const group = graph.getPackageGroup(pkg.id);
|
|
84
252
|
if (!group || !group.options.syncBump) return;
|
|
85
253
|
for (const member of group.packages) {
|
|
86
254
|
if (member === pkg) continue;
|
|
87
255
|
this.bumpPackage(member, {
|
|
88
|
-
type:
|
|
256
|
+
type: packageDraft.type,
|
|
89
257
|
reason: `sync "${group.name}" group package versions`
|
|
90
258
|
});
|
|
91
259
|
}
|
|
@@ -94,57 +262,70 @@ function groupPolicy({ graph }) {
|
|
|
94
262
|
}
|
|
95
263
|
//#endregion
|
|
96
264
|
//#region src/plans/draft.ts
|
|
97
|
-
|
|
265
|
+
const changelogStoreSchema = z.object({
|
|
266
|
+
v: z.literal("0.0.0"),
|
|
267
|
+
filename: z.string(),
|
|
268
|
+
content: z.string()
|
|
269
|
+
});
|
|
270
|
+
const packageStoreSchema = z.object({
|
|
271
|
+
id: z.string(),
|
|
272
|
+
updated: z.boolean(),
|
|
273
|
+
changelogIds: z.array(z.string()).optional()
|
|
274
|
+
});
|
|
275
|
+
/** a draft describes all operations to perform before the actual publishing, such as version bumps. */
|
|
276
|
+
var Draft = class {
|
|
98
277
|
context;
|
|
99
278
|
#applied = false;
|
|
279
|
+
/** package id -> draft */
|
|
100
280
|
packages = /* @__PURE__ */ new Map();
|
|
281
|
+
/** id -> changelog */
|
|
101
282
|
changelogs = /* @__PURE__ */ new Map();
|
|
102
283
|
policies = [];
|
|
103
284
|
constructor(context) {
|
|
104
285
|
this.context = context;
|
|
105
286
|
this.policies.push(groupPolicy(context));
|
|
106
287
|
}
|
|
107
|
-
|
|
288
|
+
getPackageDrafts() {
|
|
108
289
|
return this.packages;
|
|
109
290
|
}
|
|
110
|
-
|
|
291
|
+
getPackageDraft(id) {
|
|
111
292
|
return this.packages.get(id);
|
|
112
293
|
}
|
|
113
294
|
bumpPackage(pkg, { type, reason }) {
|
|
114
|
-
return this.dispatchPackage(pkg, (
|
|
115
|
-
|
|
295
|
+
return this.dispatchPackage(pkg, (draft) => {
|
|
296
|
+
draft.type = draft.type ? maxBump(draft.type, type) : type;
|
|
116
297
|
if (reason) {
|
|
117
|
-
|
|
118
|
-
|
|
298
|
+
draft.bumpReasons ??= /* @__PURE__ */ new Set();
|
|
299
|
+
draft.bumpReasons.add(reason);
|
|
119
300
|
}
|
|
120
301
|
});
|
|
121
302
|
}
|
|
122
303
|
dispatchPackage(pkg, dispatch, onUpdate) {
|
|
123
|
-
const
|
|
124
|
-
const prevVersion =
|
|
125
|
-
dispatch(
|
|
126
|
-
if (prevVersion !==
|
|
127
|
-
onUpdate?.(
|
|
304
|
+
const packageDraft = this.getOrInitPackage(pkg);
|
|
305
|
+
const prevVersion = packageDraft.bumpVersion(pkg);
|
|
306
|
+
dispatch(packageDraft);
|
|
307
|
+
if (prevVersion !== packageDraft.bumpVersion(pkg)) {
|
|
308
|
+
onUpdate?.(packageDraft);
|
|
128
309
|
for (const policy of this.policies) policy.onUpdate?.call(this, {
|
|
129
|
-
|
|
310
|
+
packageDraft,
|
|
130
311
|
pkg
|
|
131
312
|
});
|
|
132
313
|
}
|
|
133
|
-
return
|
|
314
|
+
return packageDraft;
|
|
134
315
|
}
|
|
135
316
|
getOrInitPackage(pkg) {
|
|
136
317
|
const existing = this.packages.get(pkg.id);
|
|
137
318
|
if (existing) return existing;
|
|
138
|
-
this.packages.set(pkg.id, pkg.
|
|
139
|
-
return this.dispatchPackage(pkg, (
|
|
140
|
-
(
|
|
319
|
+
this.packages.set(pkg.id, pkg.initDraft());
|
|
320
|
+
return this.dispatchPackage(pkg, (draft) => pkg.configureDraft(draft, this.context.graph.getPackageGroup(pkg.id)), (draft) => {
|
|
321
|
+
(draft.bumpReasons ??= /* @__PURE__ */ new Set()).add("align with script-level configs");
|
|
141
322
|
});
|
|
142
323
|
}
|
|
143
324
|
hasPending() {
|
|
144
325
|
const { graph } = this.context;
|
|
145
|
-
for (const [id,
|
|
326
|
+
for (const [id, draft] of this.packages) {
|
|
146
327
|
const pkg = graph.get(id);
|
|
147
|
-
if (pkg &&
|
|
328
|
+
if (pkg && draft.bumpVersion(pkg) !== pkg.version) return true;
|
|
148
329
|
}
|
|
149
330
|
return false;
|
|
150
331
|
}
|
|
@@ -167,30 +348,53 @@ var DraftPlan = class {
|
|
|
167
348
|
deleteChangelog(id) {
|
|
168
349
|
return this.changelogs.delete(id);
|
|
169
350
|
}
|
|
170
|
-
/** Apply
|
|
171
|
-
async
|
|
172
|
-
if (this.#applied) throw new Error("This draft has already applied
|
|
173
|
-
await assertPublishPlanFinished(this.context);
|
|
351
|
+
/** Apply version bumps, lock file, and changelog files. */
|
|
352
|
+
async apply() {
|
|
353
|
+
if (this.#applied) throw new Error("This draft has already been applied.");
|
|
174
354
|
const { graph } = this.context;
|
|
175
355
|
this.#applied = true;
|
|
176
356
|
const snapshots = /* @__PURE__ */ new Map();
|
|
177
357
|
for (const pkg of graph.getPackages()) snapshots.set(pkg.id, { version: pkg.version });
|
|
178
|
-
for (const plugin of this.context.plugins) await handlePluginError(plugin, "
|
|
358
|
+
for (const plugin of this.context.plugins) await handlePluginError(plugin, "applyDraft", () => plugin.applyDraft?.call(this.context, this));
|
|
179
359
|
const updatedChangelogs = this.applyReplays(snapshots);
|
|
180
360
|
const writes = [];
|
|
181
|
-
for (const [id,
|
|
361
|
+
for (const [id, packageDraft] of this.packages) {
|
|
182
362
|
const pkg = graph.get(id);
|
|
183
363
|
if (!pkg) continue;
|
|
184
|
-
writes.push(this.appendChangelog(pkg,
|
|
364
|
+
writes.push(this.appendChangelog(pkg, packageDraft));
|
|
185
365
|
}
|
|
186
366
|
for (const entry of this.changelogs.values()) {
|
|
187
367
|
const updated = updatedChangelogs.get(entry.id);
|
|
188
368
|
const filePath = path.join(this.context.changelogDir, entry.filename);
|
|
189
|
-
writes.push(
|
|
369
|
+
if (updated) writes.push(mkdir(this.context.changelogDir, { recursive: true }).then(() => writeFile(filePath, updated.getRawContent())));
|
|
370
|
+
else if (!entry.virtual) writes.push(rm(filePath, { force: true }));
|
|
190
371
|
}
|
|
372
|
+
writes.push(this.writeLockFile(snapshots));
|
|
191
373
|
await Promise.all(writes);
|
|
192
|
-
|
|
193
|
-
|
|
374
|
+
}
|
|
375
|
+
/** write persistent data to publish lock */
|
|
376
|
+
async writeLockFile(snapshots) {
|
|
377
|
+
const lock = new PublishLock();
|
|
378
|
+
for (const entry of this.getChangelogs()) lock.write("core:changelogs", {
|
|
379
|
+
v: "0.0.0",
|
|
380
|
+
filename: entry.filename,
|
|
381
|
+
content: entry.getRawContent()
|
|
382
|
+
});
|
|
383
|
+
for (const pkg of this.context.graph.getPackages()) {
|
|
384
|
+
const draft = this.getPackageDraft(pkg.id);
|
|
385
|
+
const snapshot = snapshots.get(pkg.id);
|
|
386
|
+
lock.write("core:packages", {
|
|
387
|
+
id: pkg.id,
|
|
388
|
+
updated: draft !== void 0 && (snapshot === void 0 || snapshot.version !== pkg.version),
|
|
389
|
+
changelogIds: draft?.changelogs?.map((entry) => entry.id)
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
for (const plugin of this.context.plugins) await handlePluginError(plugin, "initPublishLock", () => plugin.initPublishLock?.call(this.context, {
|
|
393
|
+
lock,
|
|
394
|
+
draft: this
|
|
395
|
+
}));
|
|
396
|
+
await mkdir(dirname(this.context.lockPath), { recursive: true });
|
|
397
|
+
await writeFile(this.context.lockPath, lock.serialize());
|
|
194
398
|
}
|
|
195
399
|
addPolicy(policy) {
|
|
196
400
|
this.policies.push(policy);
|
|
@@ -242,53 +446,191 @@ var DraftPlan = class {
|
|
|
242
446
|
}
|
|
243
447
|
return updated;
|
|
244
448
|
}
|
|
245
|
-
async appendChangelog(pkg,
|
|
246
|
-
if (!
|
|
449
|
+
async appendChangelog(pkg, draft) {
|
|
450
|
+
if (!draft.changelogs || draft.changelogs.length === 0) return;
|
|
247
451
|
const { generator = simpleGenerator() } = this.context.options;
|
|
248
452
|
const generated = await generator.generate.call(this.context, {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
plan,
|
|
253
|
-
changelogs: plan.changelogs,
|
|
254
|
-
unstable_draft: this
|
|
453
|
+
pkg,
|
|
454
|
+
packageDraft: draft,
|
|
455
|
+
draft: this
|
|
255
456
|
});
|
|
256
457
|
const path = join(pkg.path, "CHANGELOG.md");
|
|
257
458
|
const existing = await readFile(path, "utf8").catch(() => "");
|
|
258
459
|
await writeFile(path, `${generated.trim()}\n\n${existing}`.trimEnd() + "\n");
|
|
259
460
|
}
|
|
260
|
-
/** {@link
|
|
461
|
+
/** {@link apply} but for `await using` syntax */
|
|
261
462
|
async [Symbol.asyncDispose]() {
|
|
262
|
-
return this.
|
|
463
|
+
return this.apply();
|
|
263
464
|
}
|
|
264
465
|
};
|
|
265
|
-
async function
|
|
266
|
-
|
|
267
|
-
if (!store) return {
|
|
268
|
-
state: "skipped",
|
|
269
|
-
reason: "missing"
|
|
270
|
-
};
|
|
271
|
-
if ((await publishPlanStatus(store, context)).state !== "success") return {
|
|
272
|
-
state: "skipped",
|
|
273
|
-
reason: "pending"
|
|
274
|
-
};
|
|
275
|
-
await rm(context.planPath, { force: true });
|
|
276
|
-
return { state: "removed" };
|
|
277
|
-
}
|
|
278
|
-
async function createDraftPlan(changelogs, context) {
|
|
279
|
-
let draft = new DraftPlan(context);
|
|
466
|
+
async function createDraft(changelogs, context) {
|
|
467
|
+
let draft = new Draft(context);
|
|
280
468
|
for (const plugin of context.plugins) {
|
|
281
|
-
const result = await handlePluginError(plugin, "
|
|
469
|
+
const result = await handlePluginError(plugin, "initDraft", () => plugin.initDraft?.call(context, draft));
|
|
282
470
|
if (result) draft = result;
|
|
283
471
|
}
|
|
284
472
|
for (const pkg of context.graph.getPackages()) draft.getOrInitPackage(pkg);
|
|
285
473
|
for (const entry of changelogs) draft.addChangelog(entry);
|
|
286
474
|
return draft;
|
|
287
475
|
}
|
|
288
|
-
function attachChangelog(
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
476
|
+
function attachChangelog(draft, entry) {
|
|
477
|
+
if (draft.changelogs?.some((item) => item.id === entry.id)) return;
|
|
478
|
+
draft.changelogs ??= [];
|
|
479
|
+
draft.changelogs.push(entry);
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/plans/publish.ts
|
|
483
|
+
async function initPublishPlan(context, options) {
|
|
484
|
+
let lock;
|
|
485
|
+
try {
|
|
486
|
+
lock = parsePublishLock(await fs.readFile(context.lockPath, "utf8"));
|
|
487
|
+
} catch {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
let data;
|
|
491
|
+
const packages = /* @__PURE__ */ new Map();
|
|
492
|
+
const changelogs = /* @__PURE__ */ new Map();
|
|
493
|
+
while (data = lock.read("core:changelogs")) {
|
|
494
|
+
const entry = changelogStoreSchema.safeParse(data).data;
|
|
495
|
+
if (!entry) continue;
|
|
496
|
+
const parsed = parseChangelogFile(entry.filename, entry.content);
|
|
497
|
+
if (!parsed) continue;
|
|
498
|
+
changelogs.set(parsed.id, parsed);
|
|
499
|
+
}
|
|
500
|
+
while (data = lock.read("core:packages")) {
|
|
501
|
+
const parsed = packageStoreSchema.safeParse(data).data;
|
|
502
|
+
if (!parsed) continue;
|
|
503
|
+
if (!context.graph.get(parsed.id)) continue;
|
|
504
|
+
const pkgChangelogs = [];
|
|
505
|
+
for (const id of parsed.changelogIds ?? []) {
|
|
506
|
+
const entry = changelogs.get(id);
|
|
507
|
+
if (entry) pkgChangelogs.push(entry);
|
|
508
|
+
}
|
|
509
|
+
packages.set(parsed.id, {
|
|
510
|
+
changelogs: pkgChangelogs,
|
|
511
|
+
updated: parsed.updated
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const plan = {
|
|
515
|
+
options,
|
|
516
|
+
changelogs,
|
|
517
|
+
packages
|
|
518
|
+
};
|
|
519
|
+
for (const plugin of context.plugins) await handlePluginError(plugin, "initPublishPlan", () => plugin.initPublishPlan?.call(context, {
|
|
520
|
+
lock,
|
|
521
|
+
plan
|
|
522
|
+
}));
|
|
523
|
+
return plan;
|
|
524
|
+
}
|
|
525
|
+
function resolvePublishTargets(plan) {
|
|
526
|
+
const ordered = [];
|
|
527
|
+
function scan(id, stack = /* @__PURE__ */ new Set()) {
|
|
528
|
+
const preflight = plan.packages.get(id)?.preflight;
|
|
529
|
+
if (!preflight || preflight.publish === false) return;
|
|
530
|
+
if (stack.has(id)) throw new Error(`circular reference of deps: ${[...stack, id].join(" -> ")}`);
|
|
531
|
+
if (ordered.includes(id)) return;
|
|
532
|
+
if (preflight.wait) {
|
|
533
|
+
stack.add(id);
|
|
534
|
+
for (const dep of preflight.wait) scan(dep, stack);
|
|
535
|
+
stack.delete(id);
|
|
536
|
+
}
|
|
537
|
+
ordered.push(id);
|
|
538
|
+
}
|
|
539
|
+
for (const id of plan.packages.keys()) scan(id);
|
|
540
|
+
return ordered;
|
|
541
|
+
}
|
|
542
|
+
async function runPublishPlan(context, plan) {
|
|
543
|
+
const { dryRun = false } = plan.options;
|
|
544
|
+
const onPublishResult = async (pkg, publishResult) => {
|
|
545
|
+
const packagePlan = plan.packages.get(pkg.id);
|
|
546
|
+
if (!packagePlan) return;
|
|
547
|
+
packagePlan.publishResult = publishResult;
|
|
548
|
+
for (const plugin of context.plugins) await handlePluginError(plugin, "afterPublish", () => plugin.afterPublish?.call(context, {
|
|
549
|
+
pkg,
|
|
550
|
+
plan
|
|
551
|
+
}));
|
|
552
|
+
};
|
|
553
|
+
for (const id of resolvePublishTargets(plan)) {
|
|
554
|
+
const pkg = context.graph.get(id);
|
|
555
|
+
if (dryRun) {
|
|
556
|
+
await onPublishResult(pkg, { type: "published" });
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
let isSkipped = false;
|
|
560
|
+
for (const plugin of context.plugins) if (await handlePluginError(plugin, "willPublish", () => plugin.willPublish?.call(context, { pkg })) === false) {
|
|
561
|
+
isSkipped = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
if (isSkipped) {
|
|
565
|
+
await onPublishResult(pkg, { type: "skipped" });
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
let published = false;
|
|
569
|
+
for (const plugin of context.plugins) {
|
|
570
|
+
const publishResult = await handlePluginError(plugin, "publish", () => plugin.publish?.call(context, {
|
|
571
|
+
pkg,
|
|
572
|
+
plan
|
|
573
|
+
}));
|
|
574
|
+
if (publishResult) {
|
|
575
|
+
published = true;
|
|
576
|
+
await onPublishResult(pkg, publishResult);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (!published) await onPublishResult(pkg, {
|
|
581
|
+
type: "failed",
|
|
582
|
+
error: `There is no plugin to publish package "${pkg.id}", please make sure the package has a supported provider plugin.`
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
await Promise.all(Array.from(plan.packages, async ([id, packagePlan]) => {
|
|
586
|
+
const pkg = context.graph.get(id);
|
|
587
|
+
if (!packagePlan.publishResult) await onPublishResult(pkg, { type: "skipped" });
|
|
588
|
+
}));
|
|
589
|
+
for (const plugin of context.plugins) await handlePluginError(plugin, "afterPublishAll", () => plugin.afterPublishAll?.call(context, { plan }));
|
|
590
|
+
}
|
|
591
|
+
async function runPreflights(context, plan) {
|
|
592
|
+
const promises = [];
|
|
593
|
+
const runPreflight = async (pkg) => {
|
|
594
|
+
const out = {};
|
|
595
|
+
for (const plugin of context.plugins) {
|
|
596
|
+
const res = await handlePluginError(plugin, "publishPreflight", () => plugin.publishPreflight?.call(context, {
|
|
597
|
+
pkg,
|
|
598
|
+
plan
|
|
599
|
+
}));
|
|
600
|
+
if (res?.publish !== void 0) out.publish = res.publish;
|
|
601
|
+
if (res?.wait) {
|
|
602
|
+
out.wait ??= [];
|
|
603
|
+
out.wait.push(...res.wait);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
};
|
|
608
|
+
for (const [id, packagePlan] of plan.packages) {
|
|
609
|
+
const pkg = context.graph.get(id);
|
|
610
|
+
if (!packagePlan.updated) {
|
|
611
|
+
packagePlan.preflight = { publish: false };
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
promises.push(runPreflight(pkg).then((preflight) => {
|
|
615
|
+
packagePlan.preflight = preflight;
|
|
616
|
+
}));
|
|
617
|
+
}
|
|
618
|
+
await Promise.all(promises);
|
|
619
|
+
}
|
|
620
|
+
async function publishPlanStatus(plan, context) {
|
|
621
|
+
for (const pkg of plan.packages.values()) {
|
|
622
|
+
if (!pkg.preflight) throw new Error("Should perform preflight before checking plan status.");
|
|
623
|
+
if (pkg.preflight.publish ?? true) return "pending";
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
await Promise.all(context.plugins.map(async (plugin) => {
|
|
627
|
+
if (await handlePluginError(plugin, "resolvePlanStatus", () => plugin.resolvePlanStatus?.call(context, { plan })) === "pending") throw "pending";
|
|
628
|
+
}));
|
|
629
|
+
return "success";
|
|
630
|
+
} catch (e) {
|
|
631
|
+
if (e === "pending") return "pending";
|
|
632
|
+
throw e;
|
|
633
|
+
}
|
|
292
634
|
}
|
|
293
635
|
//#endregion
|
|
294
636
|
//#region src/index.ts
|
|
@@ -299,8 +641,15 @@ function tegami(options = {}) {
|
|
|
299
641
|
return createTegamiContext(options);
|
|
300
642
|
}
|
|
301
643
|
return {
|
|
302
|
-
async generateChangelog(
|
|
303
|
-
|
|
644
|
+
async generateChangelog(generateOptions = {}) {
|
|
645
|
+
const { write = true } = generateOptions;
|
|
646
|
+
const context = await $context;
|
|
647
|
+
const changelogs = await generateFromCommits(context, generateOptions);
|
|
648
|
+
if (write && changelogs.length > 0) {
|
|
649
|
+
await mkdir(context.changelogDir, { recursive: true });
|
|
650
|
+
await Promise.all(changelogs.map((entry) => writeFile(join(context.changelogDir, entry.filename), entry.content)));
|
|
651
|
+
}
|
|
652
|
+
return changelogs;
|
|
304
653
|
},
|
|
305
654
|
_internal: {
|
|
306
655
|
options,
|
|
@@ -313,25 +662,49 @@ function tegami(options = {}) {
|
|
|
313
662
|
},
|
|
314
663
|
async draft() {
|
|
315
664
|
const context = await $context;
|
|
316
|
-
|
|
665
|
+
const changelogs = await readChangelogEntries(context);
|
|
666
|
+
if (context.options.conventionalCommits) {
|
|
667
|
+
const generated = await generateFromCommits(context);
|
|
668
|
+
for (const entry of generated) {
|
|
669
|
+
const parsed = parseChangelogFile(entry.filename, entry.content);
|
|
670
|
+
if (!parsed) continue;
|
|
671
|
+
parsed.virtual = true;
|
|
672
|
+
changelogs.push(parsed);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return createDraft(changelogs, context);
|
|
676
|
+
},
|
|
677
|
+
async publishStatus() {
|
|
678
|
+
const context = await $context;
|
|
679
|
+
const plan = await initPublishPlan(context, {});
|
|
680
|
+
if (!plan) return "idle";
|
|
681
|
+
await runPreflights(context, plan);
|
|
682
|
+
return publishPlanStatus(plan, context);
|
|
317
683
|
},
|
|
318
684
|
async publish(publishOptions = {}) {
|
|
319
685
|
const context = await $context;
|
|
320
|
-
const
|
|
321
|
-
if (!
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (const plugin of context.plugins) {
|
|
328
|
-
const next = await handlePluginError(plugin, "afterPublishAll", () => plugin.afterPublishAll?.call(publishCtx, result));
|
|
329
|
-
if (next) result = next;
|
|
330
|
-
}
|
|
331
|
-
return result;
|
|
686
|
+
const plan = await initPublishPlan(context, publishOptions);
|
|
687
|
+
if (!plan) return "skipped";
|
|
688
|
+
await runPreflights(context, plan);
|
|
689
|
+
if (await publishPlanStatus(plan, context) === "success") return "skipped";
|
|
690
|
+
await runPublishPlan(context, plan);
|
|
691
|
+
if (Array.from(plan.packages.values()).every((pkg) => pkg.publishResult.type === "skipped")) return "skipped";
|
|
692
|
+
return plan;
|
|
332
693
|
},
|
|
333
694
|
async cleanup() {
|
|
334
|
-
|
|
695
|
+
const context = await $context;
|
|
696
|
+
const plan = await initPublishPlan(context, {});
|
|
697
|
+
if (!plan) return {
|
|
698
|
+
state: "skipped",
|
|
699
|
+
reason: "no-plan"
|
|
700
|
+
};
|
|
701
|
+
await runPreflights(context, plan);
|
|
702
|
+
if (await publishPlanStatus(plan, context) !== "success") return {
|
|
703
|
+
state: "skipped",
|
|
704
|
+
reason: "pending"
|
|
705
|
+
};
|
|
706
|
+
await rm(context.lockPath, { force: true });
|
|
707
|
+
return { state: "removed" };
|
|
335
708
|
}
|
|
336
709
|
};
|
|
337
710
|
}
|