tegami 0.1.5 → 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/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { a as maxBump } from "./semver-C4vJ4SK8.js";
2
- import { c as changelogFrontmatterSchema, i as readPlanStore, n as publishPlanStatus, o as generateChangelog, r as createPlanStore, t as assertPublishPlanFinished } from "./checks-Cwz-Ezkq.js";
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 { t as PackageGraph } from "./graph-DLKPUXSD.js";
4
+ import { t as PackageGraph } from "./graph-DrzluXw8.js";
5
5
  import { cargo } from "./providers/cargo.js";
6
6
  import { npm } from "./providers/npm.js";
7
7
  import { simpleGenerator } from "./generators/simple.js";
8
- import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
8
+ import fs, { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
9
9
  import path, { basename, dirname, join } from "node:path";
10
10
  import * as semver from "semver";
11
- import { dump, load } from "js-yaml";
11
+ import z from "zod";
12
+ import { dump, load, visit } from "js-yaml";
12
13
  import { fromMarkdown } from "mdast-util-from-markdown";
13
14
  import { toMarkdown } from "mdast-util-to-markdown";
14
15
  //#region src/context.ts
@@ -16,31 +17,17 @@ async function createTegamiContext(options = {}) {
16
17
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
17
18
  const changelogDir = path.resolve(cwd, options.changelogDir ?? ".tegami");
18
19
  const graph = new PackageGraph();
19
- const registryClients = /* @__PURE__ */ new Map();
20
20
  const ctx = {
21
21
  cwd,
22
22
  changelogDir,
23
- planPath: options.planPath ? path.resolve(cwd, options.planPath) : path.join(changelogDir, "publish-plan"),
23
+ lockPath: options.lockPath ? path.resolve(cwd, options.lockPath) : path.join(changelogDir, "publish-lock.yaml"),
24
24
  options,
25
25
  plugins: resolvePlugins([
26
26
  npm(options.npm),
27
27
  cargo(options.cargo),
28
28
  ...options.plugins ?? []
29
29
  ]),
30
- graph,
31
- getRegistryClient(pkgOrId) {
32
- let client;
33
- if (typeof pkgOrId === "string") client = registryClients.get(pkgOrId);
34
- else for (const item of registryClients.values()) if (item.supports && item.supports(pkgOrId)) {
35
- client = item;
36
- break;
37
- }
38
- if (!client) {
39
- const id = typeof pkgOrId === "string" ? pkgOrId : pkgOrId.manager;
40
- throw new Error(`No registry client is available for ${id}.`);
41
- }
42
- return client;
43
- }
30
+ graph
44
31
  };
45
32
  for (const plugin of ctx.plugins) await handlePluginError(plugin, "init", () => plugin.init?.call(ctx));
46
33
  for (const plugin of ctx.plugins) await handlePluginError(plugin, "resolve", () => plugin.resolve?.call(ctx));
@@ -59,11 +46,6 @@ async function createTegamiContext(options = {}) {
59
46
  pkg.setPackageOptions(packageOptions);
60
47
  if (packageOptions.group) graph.addGroupMember(packageOptions.group, pkg.id);
61
48
  }
62
- for (const plugin of ctx.plugins) {
63
- const clients = await handlePluginError(plugin, "createRegistryClient", () => plugin.createRegistryClient?.call(ctx));
64
- if (!clients) continue;
65
- for (const client of Array.isArray(clients) ? clients : [clients]) registryClients.set(client.id, client);
66
- }
67
49
  return ctx;
68
50
  }
69
51
  const PLUGIN_ORDER = {
@@ -210,18 +192,68 @@ function headingToBump(depth) {
210
192
  }
211
193
  }
212
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
213
245
  //#region src/plans/policy.ts
214
246
  function groupPolicy({ graph }) {
215
247
  return {
216
248
  id: "group",
217
- onUpdate({ pkg, plan }) {
218
- if (!plan.type) return;
249
+ onUpdate({ pkg, packageDraft }) {
250
+ if (!packageDraft.type) return;
219
251
  const group = graph.getPackageGroup(pkg.id);
220
252
  if (!group || !group.options.syncBump) return;
221
253
  for (const member of group.packages) {
222
254
  if (member === pkg) continue;
223
255
  this.bumpPackage(member, {
224
- type: plan.type,
256
+ type: packageDraft.type,
225
257
  reason: `sync "${group.name}" group package versions`
226
258
  });
227
259
  }
@@ -230,57 +262,70 @@ function groupPolicy({ graph }) {
230
262
  }
231
263
  //#endregion
232
264
  //#region src/plans/draft.ts
233
- var DraftPlan = class {
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 {
234
277
  context;
235
278
  #applied = false;
279
+ /** package id -> draft */
236
280
  packages = /* @__PURE__ */ new Map();
281
+ /** id -> changelog */
237
282
  changelogs = /* @__PURE__ */ new Map();
238
283
  policies = [];
239
284
  constructor(context) {
240
285
  this.context = context;
241
286
  this.policies.push(groupPolicy(context));
242
287
  }
243
- getPackagePlans() {
288
+ getPackageDrafts() {
244
289
  return this.packages;
245
290
  }
246
- getPackagePlan(id) {
291
+ getPackageDraft(id) {
247
292
  return this.packages.get(id);
248
293
  }
249
294
  bumpPackage(pkg, { type, reason }) {
250
- return this.dispatchPackage(pkg, (plan) => {
251
- plan.type = plan.type ? maxBump(plan.type, type) : type;
295
+ return this.dispatchPackage(pkg, (draft) => {
296
+ draft.type = draft.type ? maxBump(draft.type, type) : type;
252
297
  if (reason) {
253
- plan.bumpReasons ??= /* @__PURE__ */ new Set();
254
- plan.bumpReasons.add(reason);
298
+ draft.bumpReasons ??= /* @__PURE__ */ new Set();
299
+ draft.bumpReasons.add(reason);
255
300
  }
256
301
  });
257
302
  }
258
303
  dispatchPackage(pkg, dispatch, onUpdate) {
259
- const plan = this.getOrInitPackage(pkg);
260
- const prevVersion = plan.bumpVersion(pkg);
261
- dispatch(plan);
262
- if (prevVersion !== plan.bumpVersion(pkg)) {
263
- onUpdate?.(plan);
304
+ const packageDraft = this.getOrInitPackage(pkg);
305
+ const prevVersion = packageDraft.bumpVersion(pkg);
306
+ dispatch(packageDraft);
307
+ if (prevVersion !== packageDraft.bumpVersion(pkg)) {
308
+ onUpdate?.(packageDraft);
264
309
  for (const policy of this.policies) policy.onUpdate?.call(this, {
265
- plan,
310
+ packageDraft,
266
311
  pkg
267
312
  });
268
313
  }
269
- return plan;
314
+ return packageDraft;
270
315
  }
271
316
  getOrInitPackage(pkg) {
272
317
  const existing = this.packages.get(pkg.id);
273
318
  if (existing) return existing;
274
- this.packages.set(pkg.id, pkg.initPlan());
275
- return this.dispatchPackage(pkg, (plan) => pkg.configurePlan(plan, this.context.graph.getPackageGroup(pkg.id)), (plan) => {
276
- (plan.bumpReasons ??= /* @__PURE__ */ new Set()).add("align with script-level configs");
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");
277
322
  });
278
323
  }
279
324
  hasPending() {
280
325
  const { graph } = this.context;
281
- for (const [id, plan] of this.packages) {
326
+ for (const [id, draft] of this.packages) {
282
327
  const pkg = graph.get(id);
283
- if (pkg && plan.bumpVersion(pkg) !== pkg.version) return true;
328
+ if (pkg && draft.bumpVersion(pkg) !== pkg.version) return true;
284
329
  }
285
330
  return false;
286
331
  }
@@ -303,30 +348,53 @@ var DraftPlan = class {
303
348
  deleteChangelog(id) {
304
349
  return this.changelogs.delete(id);
305
350
  }
306
- /** Apply the publish plan: update package versions, write the plan file, and consume changelog files. */
307
- async applyPlan() {
308
- if (this.#applied) throw new Error("This draft has already applied a publish plan.");
309
- 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.");
310
354
  const { graph } = this.context;
311
355
  this.#applied = true;
312
356
  const snapshots = /* @__PURE__ */ new Map();
313
357
  for (const pkg of graph.getPackages()) snapshots.set(pkg.id, { version: pkg.version });
314
- for (const plugin of this.context.plugins) await handlePluginError(plugin, "applyPlan", () => plugin.applyPlan?.call(this.context, this));
358
+ for (const plugin of this.context.plugins) await handlePluginError(plugin, "applyDraft", () => plugin.applyDraft?.call(this.context, this));
315
359
  const updatedChangelogs = this.applyReplays(snapshots);
316
360
  const writes = [];
317
- for (const [id, packagePlan] of this.packages) {
361
+ for (const [id, packageDraft] of this.packages) {
318
362
  const pkg = graph.get(id);
319
363
  if (!pkg) continue;
320
- writes.push(this.appendChangelog(pkg, packagePlan));
364
+ writes.push(this.appendChangelog(pkg, packageDraft));
321
365
  }
322
366
  for (const entry of this.changelogs.values()) {
323
367
  const updated = updatedChangelogs.get(entry.id);
324
368
  const filePath = path.join(this.context.changelogDir, entry.filename);
325
- writes.push(updated ? writeFile(filePath, updated.getRawContent()) : rm(filePath, { force: true }));
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 }));
326
371
  }
372
+ writes.push(this.writeLockFile(snapshots));
327
373
  await Promise.all(writes);
328
- await mkdir(dirname(this.context.planPath), { recursive: true });
329
- await writeFile(this.context.planPath, createPlanStore(this, this.context));
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());
330
398
  }
331
399
  addPolicy(policy) {
332
400
  this.policies.push(policy);
@@ -378,183 +446,191 @@ var DraftPlan = class {
378
446
  }
379
447
  return updated;
380
448
  }
381
- async appendChangelog(pkg, plan) {
382
- if (!plan.changelogs || plan.changelogs.length === 0) return;
449
+ async appendChangelog(pkg, draft) {
450
+ if (!draft.changelogs || draft.changelogs.length === 0) return;
383
451
  const { generator = simpleGenerator() } = this.context.options;
384
452
  const generated = await generator.generate.call(this.context, {
385
- packageId: pkg.id,
386
- packageName: pkg.name,
387
- version: pkg.version,
388
- plan,
389
- changelogs: plan.changelogs,
390
- unstable_draft: this
453
+ pkg,
454
+ packageDraft: draft,
455
+ draft: this
391
456
  });
392
457
  const path = join(pkg.path, "CHANGELOG.md");
393
458
  const existing = await readFile(path, "utf8").catch(() => "");
394
459
  await writeFile(path, `${generated.trim()}\n\n${existing}`.trimEnd() + "\n");
395
460
  }
396
- /** {@link applyPlan} but for `await using` syntax */
461
+ /** {@link apply} but for `await using` syntax */
397
462
  async [Symbol.asyncDispose]() {
398
- return this.applyPlan();
463
+ return this.apply();
399
464
  }
400
465
  };
401
- async function cleanupPublishPlan(context) {
402
- const store = await readPlanStore(context);
403
- if (!store) return {
404
- state: "skipped",
405
- reason: "missing"
406
- };
407
- if ((await publishPlanStatus(store, context)).state !== "success") return {
408
- state: "skipped",
409
- reason: "pending"
410
- };
411
- await rm(context.planPath, { force: true });
412
- return { state: "removed" };
413
- }
414
- async function createDraftPlan(changelogs, context) {
415
- let draft = new DraftPlan(context);
466
+ async function createDraft(changelogs, context) {
467
+ let draft = new Draft(context);
416
468
  for (const plugin of context.plugins) {
417
- const result = await handlePluginError(plugin, "initPlan", () => plugin.initPlan?.call(context, draft));
469
+ const result = await handlePluginError(plugin, "initDraft", () => plugin.initDraft?.call(context, draft));
418
470
  if (result) draft = result;
419
471
  }
420
472
  for (const pkg of context.graph.getPackages()) draft.getOrInitPackage(pkg);
421
473
  for (const entry of changelogs) draft.addChangelog(entry);
422
474
  return draft;
423
475
  }
424
- function attachChangelog(plan, entry) {
425
- if (plan.changelogs?.some((item) => item.id === entry.id)) return;
426
- plan.changelogs ??= [];
427
- plan.changelogs.push(entry);
476
+ function attachChangelog(draft, entry) {
477
+ if (draft.changelogs?.some((item) => item.id === entry.id)) return;
478
+ draft.changelogs ??= [];
479
+ draft.changelogs.push(entry);
428
480
  }
429
481
  //#endregion
430
- //#region src/publish.ts
431
- async function resolvePublishTargets(context, store) {
432
- const targets = [];
433
- const preflightPromises = [];
434
- for (const [id, plan] of Object.entries(store.packages)) {
435
- if (!plan.publish) continue;
436
- const pkg = context.graph.get(id);
437
- if (!pkg) continue;
438
- targets.push(pkg.id);
439
- preflightPromises.push(context.getRegistryClient(pkg).publishPreflight?.(pkg, {
440
- store,
441
- packageStore: plan
442
- }));
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;
443
489
  }
444
- const preflights = await Promise.all(preflightPromises);
445
- const children = /* @__PURE__ */ new Map();
446
- for (let i = 0; i < targets.length; i++) {
447
- const id = targets[i];
448
- children.set(id, preflights[i]?.wait);
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);
449
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) {
450
526
  const ordered = [];
451
527
  function scan(id, stack = /* @__PURE__ */ new Set()) {
528
+ const preflight = plan.packages.get(id)?.preflight;
529
+ if (!preflight || preflight.publish === false) return;
452
530
  if (stack.has(id)) throw new Error(`circular reference of deps: ${[...stack, id].join(" -> ")}`);
453
531
  if (ordered.includes(id)) return;
454
- const deps = children.get(id);
455
- if (deps) {
532
+ if (preflight.wait) {
456
533
  stack.add(id);
457
- for (const dep of deps) scan(dep, stack);
534
+ for (const dep of preflight.wait) scan(dep, stack);
458
535
  stack.delete(id);
459
536
  }
460
537
  ordered.push(id);
461
538
  }
462
- for (const id of targets) scan(id);
539
+ for (const id of plan.packages.keys()) scan(id);
463
540
  return ordered;
464
541
  }
465
- async function publishFromPlan(context, store, options) {
466
- const { dryRun = false } = options;
467
- const packages = [];
468
- if ((await publishPlanStatus(store, context)).state !== "pending") return { state: "skipped" };
469
- const orderedIds = await resolvePublishTargets(context, store);
470
- const parsedChangelogs = /* @__PURE__ */ new Map();
471
- for (const [id, entry] of Object.entries(store.changelogs)) {
472
- const parsed = parseChangelogFile(entry.filename, entry.content);
473
- if (!parsed) continue;
474
- parsedChangelogs.set(id, parsed);
475
- }
476
- for (const id of orderedIds) {
477
- const plan = store.packages[id];
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)) {
478
554
  const pkg = context.graph.get(id);
479
- const registryClient = context.getRegistryClient(pkg);
480
- const changelogs = [];
481
- for (const id of plan.changelogIds ?? []) {
482
- const entry = parsedChangelogs.get(id);
483
- if (entry) changelogs.push(entry);
555
+ if (dryRun) {
556
+ await onPublishResult(pkg, { type: "published" });
557
+ continue;
484
558
  }
485
- if (!dryRun) {
486
- if (await registryClient.isPackagePublished(pkg)) {
487
- packages.push({
488
- id: pkg.id,
489
- name: pkg.name,
490
- version: pkg.version,
491
- npm: plan.npm,
492
- state: "success",
493
- changelogs
494
- });
495
- continue;
496
- }
497
- } else {
498
- packages.push({
499
- id: pkg.id,
500
- name: pkg.name,
501
- version: pkg.version,
502
- npm: plan.npm,
503
- state: "success",
504
- changelogs
505
- });
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" });
506
566
  continue;
507
567
  }
508
- try {
509
- let result;
510
- for (const plugin of context.plugins) {
511
- const next = await handlePluginError(plugin, "willPublish", () => plugin.willPublish?.call(context, { pkg }));
512
- if (next !== void 0) {
513
- result = next;
514
- break;
515
- }
516
- }
517
- if (result === void 0) {
518
- await registryClient.publish(pkg, {
519
- packageStore: plan,
520
- store
521
- });
522
- result = {
523
- id: pkg.id,
524
- name: pkg.name,
525
- version: pkg.version,
526
- npm: plan.npm,
527
- state: "success",
528
- changelogs
529
- };
530
- }
531
- if (result === false) continue;
532
- for (const plugin of context.plugins) {
533
- const next = await handlePluginError(plugin, "afterPublish", () => plugin.afterPublish?.call(context, {
534
- pkg,
535
- result
536
- }));
537
- if (next) result = next;
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;
538
578
  }
539
- packages.push(result);
540
- } catch (error) {
541
- packages.push({
542
- id: pkg.id,
543
- name: pkg.name,
544
- version: pkg.version,
545
- npm: plan.npm,
546
- changelogs,
547
- state: "failed",
548
- error: error instanceof Error ? error.message : String(error)
549
- });
550
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
+ });
551
584
  }
552
- if (packages.length === 0) return { state: "skipped" };
553
- return {
554
- state: packages.some((pkg) => pkg.state === "failed") ? "failed" : "created",
555
- packages,
556
- _rawPlan: store
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;
557
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
+ }
558
634
  }
559
635
  //#endregion
560
636
  //#region src/index.ts
@@ -565,8 +641,15 @@ function tegami(options = {}) {
565
641
  return createTegamiContext(options);
566
642
  }
567
643
  return {
568
- async generateChangelog(createOptions = {}) {
569
- return generateChangelog(await $context, createOptions);
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;
570
653
  },
571
654
  _internal: {
572
655
  options,
@@ -579,25 +662,49 @@ function tegami(options = {}) {
579
662
  },
580
663
  async draft() {
581
664
  const context = await $context;
582
- return createDraftPlan(await readChangelogEntries(context), context);
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);
583
683
  },
584
684
  async publish(publishOptions = {}) {
585
685
  const context = await $context;
586
- const parsed = await readPlanStore(context);
587
- if (!parsed) return { state: "skipped" };
588
- let result = await publishFromPlan(context, parsed, publishOptions);
589
- const publishCtx = {
590
- ...context,
591
- publishOptions
592
- };
593
- for (const plugin of context.plugins) {
594
- const next = await handlePluginError(plugin, "afterPublishAll", () => plugin.afterPublishAll?.call(publishCtx, result));
595
- if (next) result = next;
596
- }
597
- 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;
598
693
  },
599
694
  async cleanup() {
600
- return cleanupPublishPlan(await $context);
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" };
601
708
  }
602
709
  };
603
710
  }