tegami 1.1.2 → 1.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.
@@ -1,9 +1,9 @@
1
1
  import { i as formatPackageVersion, r as formatNpmDistTag } from "../semver-EKJ8yK5U.js";
2
2
  import { t as changelogFilename } from "../generate-Bg86OJP4.js";
3
3
  import { a as cached, n as execFailure, o as isCI, s as joinPath } from "../error-BhMYq9iW.js";
4
- import { o as readChangelogEntries, t as createDraft } from "../draft-CzUiQasJ.js";
4
+ import { a as createDraft, l as readChangelogEntries } from "../publish-v30y8Lwq.js";
5
5
  import { git } from "./git.js";
6
- import { n as createVersionRequestBody, r as hasGitChanges, t as commitVersionBranchChanges } from "../version-request-oxy16TJ1.js";
6
+ import { t as onVersionRequest } from "../version-request-l6IJdFM6.js";
7
7
  import { readFile, writeFile } from "node:fs/promises";
8
8
  import { basename, join, relative, resolve } from "node:path";
9
9
  import { x } from "tinyexec";
@@ -309,8 +309,38 @@ function gitlab(options = {}) {
309
309
  function getRenderer(context) {
310
310
  return renderer ??= createChangelogRenderer(context);
311
311
  }
312
- const cliOriginalPackageVersions = /* @__PURE__ */ new Map();
313
- return [git(options), {
312
+ const versionRequests = onVersionRequest({
313
+ name: "gitlab",
314
+ options: options.versionMr,
315
+ canCreate(context) {
316
+ const { repo, token } = context.gitlab ?? {};
317
+ return Boolean(repo && token);
318
+ },
319
+ async upsert(context, request, update) {
320
+ const repo = context.gitlab.repo;
321
+ const api = gitLabApiOptions(context.gitlab);
322
+ const openMr = await findOpenMergeRequest(repo, {
323
+ head: request.head,
324
+ base: request.base,
325
+ ...api
326
+ });
327
+ if (openMr === void 0) await createMergeRequest(repo, {
328
+ title: request.title,
329
+ body: request.body,
330
+ head: request.head,
331
+ base: request.base,
332
+ ...api
333
+ });
334
+ else if (update) await updateMergeRequest(repo, openMr, {
335
+ title: request.title,
336
+ body: request.body,
337
+ base: request.base,
338
+ ...api
339
+ });
340
+ }
341
+ });
342
+ const plugin = {
343
+ ...versionRequests,
314
344
  name: "gitlab",
315
345
  init() {
316
346
  this.gitlab = {
@@ -321,6 +351,7 @@ function gitlab(options = {}) {
321
351
  };
322
352
  },
323
353
  async resolvePlanStatus({ plan }) {
354
+ if (versionRequests.resolvePlanStatus.call(this, { plan }) === "pending") return "pending";
324
355
  const { repo, token } = this.gitlab;
325
356
  if (!repo || !token || releaseOptions === false) return;
326
357
  const requiredTags = /* @__PURE__ */ new Set();
@@ -392,46 +423,9 @@ function gitlab(options = {}) {
392
423
  gitlabRemoteUrl(repo, token, webUrl)
393
424
  ], { nodeOptions: { cwd: this.cwd } });
394
425
  if (result.exitCode !== 0) throw execFailure("Failed to configure git remote for GitLab CI.", result);
395
- },
396
- initCliDraft() {
397
- for (const pkg of this.graph.getPackages()) cliOriginalPackageVersions.set(pkg.id, pkg.version);
398
- },
399
- async applyCliDraft(draft) {
400
- const config = options.versionMr ?? {};
401
- if (config === false || !(config.forceCreate || isCI()) || !await hasGitChanges(this.cwd)) return;
402
- const repo = this.gitlab?.repo;
403
- const { branch = "tegami/version-packages", base = "main" } = config;
404
- const baseMR = await config.create?.call(this, { draft });
405
- const mr = {
406
- title: baseMR?.title ?? "Version Packages",
407
- body: baseMR?.body ?? createVersionRequestBody(draft, this, cliOriginalPackageVersions, "Merge this MR to publish the versioned packages.")
408
- };
409
- await commitVersionBranchChanges(this.cwd, branch, mr.title);
410
- const api = gitLabApiOptions(this.gitlab);
411
- if (!repo) return;
412
- const openMr = await findOpenMergeRequest(repo, {
413
- head: branch,
414
- base,
415
- ...api
416
- });
417
- if (openMr !== void 0) {
418
- await updateMergeRequest(repo, openMr, {
419
- title: mr.title,
420
- body: mr.body,
421
- base,
422
- ...api
423
- });
424
- return;
425
- }
426
- await createMergeRequest(repo, {
427
- title: mr.title,
428
- body: mr.body,
429
- head: branch,
430
- base,
431
- ...api
432
- });
433
426
  }
434
- }];
427
+ };
428
+ return [git(options), plugin];
435
429
  }
436
430
  function createChangelogRenderer(context) {
437
431
  const { repo, webUrl } = context.gitlab;
@@ -1,4 +1,4 @@
1
- import { A as WorkspacePackage, P as BumpType, s as TegamiPlugin } from "../types-B50RK1rR.js";
1
+ import { A as WorkspacePackage, P as BumpType, s as TegamiPlugin } from "../types-BbwOrNZ2.js";
2
2
 
3
3
  //#region src/plugins/go.d.ts
4
4
  interface GoModFile {
@@ -1,2 +1,2 @@
1
- import { C as NpmPackage, S as NpmGraph, l as NpmPluginOptions, u as npm, x as DependencySpec } from "../types-B50RK1rR.js";
1
+ import { C as NpmPackage, S as NpmGraph, l as NpmPluginOptions, u as npm, x as DependencySpec } from "../types-BbwOrNZ2.js";
2
2
  export { type DependencySpec, type NpmGraph, NpmPackage, NpmPluginOptions, npm };
@@ -1,2 +1,2 @@
1
- import { n as NpmPackage, t as npm } from "../npm-DoPhFKji.js";
1
+ import { n as NpmPackage, t as npm } from "../npm-DaUHTtlV.js";
2
2
  export { NpmPackage, npm };
@@ -1,11 +1,11 @@
1
1
  import { a as maxBump } from "./semver-EKJ8yK5U.js";
2
- import { r as handlePluginError } from "./error-BhMYq9iW.js";
2
+ import { c as somePromise, r as handlePluginError } from "./error-BhMYq9iW.js";
3
3
  import { t as _accessExpressionAsString } from "./_accessExpressionAsString-QhbUZzwv.js";
4
4
  import { n as _validateReport, t as _createStandardSchema } from "./_createStandardSchema-BGQyz-uA.js";
5
5
  import { n as validateChangelogFrontmatter, t as renderChangelog } from "./shared-C_iSTp_s.js";
6
6
  import { simpleGenerator } from "./generators/simple.js";
7
7
  import { t as _assertGuard } from "./_assertGuard-BBn2NbSz.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$1 from "semver";
11
11
  import { parse as parse$1, stringify } from "yaml";
@@ -178,10 +178,14 @@ function headingToBump(depth) {
178
178
  /**
179
179
  * the data structure of `publish-lock.yaml` file.
180
180
  */
181
- var PublishLock = class {
181
+ var PublishLock = class PublishLock {
182
+ /** namespace -> data array */
182
183
  data;
183
- constructor(data = /* @__PURE__ */ new Map()) {
184
- this.data = data;
184
+ constructor(input = /* @__PURE__ */ new Map()) {
185
+ if (input instanceof PublishLock) {
186
+ this.data = /* @__PURE__ */ new Map();
187
+ for (const [k, v] of input.data) this.data.set(k, [...v]);
188
+ } else this.data = input;
185
189
  }
186
190
  /** write data to namespace, note that the `data` must be serializable in yaml */
187
191
  write(namespace, data) {
@@ -618,4 +622,182 @@ function attachChangelog(draft, entry) {
618
622
  draft.changelogs.push(entry);
619
623
  }
620
624
  //#endregion
621
- export { parseChangelogFile as a, parsePublishLock as i, validateChangelogStore as n, readChangelogEntries as o, validatePackageStore as r, createDraft as t };
625
+ //#region src/plans/publish.ts
626
+ async function initPublishPlan(context, options) {
627
+ let lock;
628
+ try {
629
+ lock = parsePublishLock(await fs.readFile(context.lockPath, "utf8"));
630
+ } catch {
631
+ return;
632
+ }
633
+ let data;
634
+ const packages = /* @__PURE__ */ new Map();
635
+ const changelogs = /* @__PURE__ */ new Map();
636
+ while (data = lock.read("core:changelogs")) {
637
+ const validated = validateChangelogStore(data);
638
+ if (!validated.success) continue;
639
+ const entry = validated.data;
640
+ const parsed = parseChangelogFile(entry.filename, entry.content);
641
+ if (!parsed) continue;
642
+ changelogs.set(parsed.id, parsed);
643
+ }
644
+ while (data = lock.read("core:packages")) {
645
+ const validated = validatePackageStore(data);
646
+ if (!validated.success) continue;
647
+ const parsed = validated.data;
648
+ if (!context.graph.get(parsed.id)) continue;
649
+ const pkgChangelogs = [];
650
+ for (const id of parsed.changelogIds ?? []) {
651
+ const entry = changelogs.get(id);
652
+ if (entry) pkgChangelogs.push(entry);
653
+ }
654
+ packages.set(parsed.id, {
655
+ changelogs: pkgChangelogs,
656
+ updated: parsed.updated
657
+ });
658
+ }
659
+ const plan = {
660
+ options,
661
+ changelogs,
662
+ packages
663
+ };
664
+ for (const plugin of context.plugins) await handlePluginError(plugin, "initPublishPlan", () => plugin.initPublishPlan?.call(context, {
665
+ lock,
666
+ plan
667
+ }));
668
+ return plan;
669
+ }
670
+ function resolvePublishTargets(plan) {
671
+ /** the iteration order = publish order */
672
+ const orderedMap = /* @__PURE__ */ new Map();
673
+ let lastSplitIndex = -1;
674
+ /** package id -> true while scanning hard wait, false while scanning optional wait */
675
+ const stack = /* @__PURE__ */ new Map();
676
+ function scan(id) {
677
+ const preflight = plan.packages.get(id)?.preflight;
678
+ if (!preflight || !preflight.shouldPublish) return;
679
+ switch (stack.get(id)) {
680
+ case true: throw new Error(`circular reference of deps: ${[...stack.keys(), id].join(" -> ")}`);
681
+ case false: return;
682
+ }
683
+ let ordered = orderedMap.get(id);
684
+ if (ordered) return ordered;
685
+ let split = false;
686
+ if (preflight.wait) {
687
+ stack.set(id, true);
688
+ for (const dep of preflight.wait) {
689
+ const ordered = scan(dep);
690
+ split ||= ordered !== void 0 && ordered.index >= lastSplitIndex;
691
+ }
692
+ }
693
+ if (preflight.optionalWait) {
694
+ stack.set(id, false);
695
+ for (const dep of preflight.optionalWait) {
696
+ const ordered = scan(dep);
697
+ split ||= ordered !== void 0 && ordered.index >= lastSplitIndex;
698
+ }
699
+ }
700
+ stack.delete(id);
701
+ ordered = {
702
+ split,
703
+ index: orderedMap.size
704
+ };
705
+ if (split) lastSplitIndex = ordered.index;
706
+ orderedMap.set(id, ordered);
707
+ return ordered;
708
+ }
709
+ for (const id of plan.packages.keys()) scan(id);
710
+ return orderedMap;
711
+ }
712
+ async function runPublishPlan(context, plan) {
713
+ const { dryRun = false, unstable_maxChunk = 5 } = plan.options;
714
+ for (const plugin of context.plugins) await handlePluginError(plugin, "beforePublishAll", () => plugin.beforePublishAll?.call(context, { plan }));
715
+ async function publish(pkg) {
716
+ if (dryRun) return { type: "published" };
717
+ try {
718
+ for (const plugin of context.plugins) if (await handlePluginError(plugin, "willPublish", () => plugin.willPublish?.call(context, { pkg })) === false) return { type: "skipped" };
719
+ for (const plugin of context.plugins) {
720
+ const publishResult = await handlePluginError(plugin, "publish", () => plugin.publish?.call(context, {
721
+ pkg,
722
+ plan
723
+ }));
724
+ if (publishResult) return publishResult;
725
+ }
726
+ } catch (e) {
727
+ return {
728
+ type: "failed",
729
+ error: e instanceof Error ? e.message : String(e)
730
+ };
731
+ }
732
+ return {
733
+ type: "failed",
734
+ error: `There is no plugin to publish package "${pkg.id}", please make sure the package has a supported provider plugin.`
735
+ };
736
+ }
737
+ let promises = [];
738
+ for (const [id, { split }] of resolvePublishTargets(plan)) {
739
+ const pkg = context.graph.get(id);
740
+ const packagePlan = plan.packages.get(pkg.id);
741
+ if (!pkg || !packagePlan) continue;
742
+ if (split || promises.length >= unstable_maxChunk) {
743
+ await Promise.all(promises);
744
+ promises = [];
745
+ }
746
+ promises.push(publish(pkg).then(async (result) => {
747
+ packagePlan.publishResult = result;
748
+ if (result.type === "skipped") return;
749
+ for (const plugin of context.plugins) await handlePluginError(plugin, "afterPublish", () => plugin.afterPublish?.call(context, {
750
+ pkg,
751
+ plan
752
+ }));
753
+ }));
754
+ }
755
+ await Promise.all(promises);
756
+ for (const packagePlan of plan.packages.values()) packagePlan.publishResult ??= { type: "skipped" };
757
+ for (const plugin of context.plugins) await handlePluginError(plugin, "afterPublishAll", () => plugin.afterPublishAll?.call(context, { plan }));
758
+ }
759
+ async function runPreflights(context, plan) {
760
+ const { graph } = context;
761
+ await Promise.all(Array.from(plan.packages, async ([id, packagePlan]) => {
762
+ const pkg = graph.get(id);
763
+ packagePlan.preflight = { shouldPublish: false };
764
+ if (!packagePlan.updated) return;
765
+ for (const plugin of context.plugins) {
766
+ const res = await handlePluginError(plugin, "publishPreflight", () => plugin.publishPreflight?.call(context, {
767
+ pkg,
768
+ plan
769
+ }));
770
+ if (res) {
771
+ packagePlan.preflight = res;
772
+ break;
773
+ }
774
+ }
775
+ }));
776
+ if (plan.options.packages) {
777
+ const only = /* @__PURE__ */ new Set();
778
+ function addOnly(id) {
779
+ if (only.has(id)) return;
780
+ const preflight = plan.packages.get(id)?.preflight;
781
+ if (!preflight) return;
782
+ only.add(id);
783
+ if (preflight.wait) for (const dep of preflight.wait) addOnly(dep);
784
+ if (preflight.optionalWait) for (const dep of preflight.optionalWait) addOnly(dep);
785
+ }
786
+ for (const name of plan.options.packages) for (const pkg of graph.getByName(name)) addOnly(pkg.id);
787
+ for (const [id, packagePlan] of plan.packages) {
788
+ if (only.has(id)) continue;
789
+ packagePlan.preflight.shouldPublish = false;
790
+ }
791
+ }
792
+ for (const plugin of context.plugins) await handlePluginError(plugin, "afterPreflight", () => plugin.afterPreflight?.call(context, { plan }));
793
+ }
794
+ async function publishPlanStatus(plan, context) {
795
+ for (const plugin of context.plugins) {
796
+ const status = await handlePluginError(plugin, "resolvePlanStatus", () => plugin.resolvePlanStatus?.call(context, { plan }));
797
+ if (Array.isArray(status) && await somePromise(status, (v) => v === "pending")) return "pending";
798
+ if (status === "pending") return "pending";
799
+ }
800
+ return "success";
801
+ }
802
+ //#endregion
803
+ export { createDraft as a, parseChangelogFile as c, runPublishPlan as i, readChangelogEntries as l, publishPlanStatus as n, PublishLock as o, runPreflights as r, parsePublishLock as s, initPublishPlan as t };
@@ -560,6 +560,10 @@ interface PublishPlan {
560
560
  changelogs: Map<string, ChangelogEntry>;
561
561
  /** id -> package data. The package id will always exist in package graph, stale items will be pruned at init-time */
562
562
  packages: Map<string, PackagePublishPlan>;
563
+ /** generated by GitHub/GitLab plugin (internal) */
564
+ $versionRequest?: {
565
+ publishGroups: Map<string, "active" | "pending">;
566
+ };
563
567
  }
564
568
  interface PackagePublishPlan {
565
569
  changelogs: ChangelogEntry[];
@@ -582,6 +586,20 @@ interface PackagePublishPlan {
582
586
  interface PublishOptions {
583
587
  /** Validate the publish plan without publishing packages, creating tags, or running release plugins. */
584
588
  dryRun?: boolean;
589
+ /**
590
+ * Publish only the given packages and their dependencies.
591
+ *
592
+ * Each entry can be a package id, package name, or `group:name`.
593
+ *
594
+ * When empty, no packages will be published.
595
+ */
596
+ packages?: string[];
597
+ /**
598
+ * The max amount of concurrent publishes (unstable, can change in anytime).
599
+ *
600
+ * @default 5
601
+ */
602
+ unstable_maxChunk?: number;
585
603
  }
586
604
  type PackagePublishResult = {
587
605
  type: "published" | "skipped";
@@ -597,9 +615,8 @@ type PackagePublishResult = {
597
615
  declare class PublishLock {
598
616
  /** namespace -> data array */
599
617
  private readonly data;
600
- constructor(/** namespace -> data array */
601
-
602
- data?: Map<string, unknown[]>);
618
+ constructor(lock: PublishLock);
619
+ constructor(data?: Map<string, unknown[]>);
603
620
  /** write data to namespace, note that the `data` must be serializable in yaml */
604
621
  write(namespace: string, data: unknown): void;
605
622
  read(namespace: string): unknown | undefined;
@@ -628,9 +645,9 @@ interface Tegami {
628
645
  *
629
646
  * Prefer `publish()` over this if you are publishing packages, it will also check the publish status.
630
647
  */
631
- publishStatus(): Promise<"pending" | "success" | "idle">;
648
+ publishStatus(options?: PublishOptions): Promise<"pending" | "success" | "idle">;
632
649
  /** Remove the publish lock file after publishing has finished successfully. */
633
- cleanup(): Promise<{
650
+ cleanup(options?: PublishOptions): Promise<{
634
651
  state: "removed";
635
652
  } | {
636
653
  state: "skipped";
@@ -647,13 +664,17 @@ interface Tegami {
647
664
  declare function tegami<const Groups extends string = string>(options?: TegamiOptions<Groups>): Tegami;
648
665
  //#endregion
649
666
  //#region src/cli/core.d.ts
650
- interface TegamiCliCommand<Values extends Record<string, boolean | string | undefined>, Positionals extends Record<string, string | undefined>> {
651
- option<const Name extends string, const T extends "string" | "boolean">(name: Name, opts: {
667
+ type ArgValue = boolean | string | string[] | boolean[];
668
+ type PositionalValue = string | string[];
669
+ interface TegamiCliCommand<Values extends Record<string, ArgValue | undefined>, Positionals extends Record<string, PositionalValue | undefined>> {
670
+ option<const Name extends string, const T extends "string" | "boolean", const Multiple extends boolean = false>(name: Name, opts: {
652
671
  type: T;
653
672
  short?: string;
654
673
  description?: string;
655
- }): TegamiCliCommand<Values & { [k in Name]?: T extends "string" ? string : boolean }, Positionals>;
674
+ multiple?: Multiple;
675
+ }): TegamiCliCommand<Values & { [k in Name]?: Multiple extends true ? T extends "string" ? string[] : boolean[] : T extends "string" ? string : boolean }, Positionals>;
656
676
  positional<const Name extends string, const Required extends boolean = true>(name: Name, required?: Required): TegamiCliCommand<Values, Positionals & { [K in Name]: Required extends true ? string : string | undefined }>;
677
+ positionals<const Name extends string>(name: Name): TegamiCliCommand<Values, Positionals & { [K in Name]: string[] }>;
657
678
  action(fn: (options: {
658
679
  context: TegamiContext;
659
680
  values: Values;
@@ -839,6 +860,10 @@ interface TegamiPlugin {
839
860
  resolvePlanStatus?(this: TegamiContext, opts: {
840
861
  plan: PublishPlan;
841
862
  }): Awaitable<"success" | "pending" | undefined | void | Awaitable<"success" | "pending" | undefined>[]>;
863
+ /** Called after preflights, before Tegami starts publishing the packages of a plan. */
864
+ beforePublishAll?(this: TegamiContext, opts: {
865
+ plan: PublishPlan;
866
+ }): Awaitable<void>;
842
867
  /** Called before a package will be published, return `false` to prevent from publishing. */
843
868
  willPublish?(this: TegamiContext, opts: {
844
869
  pkg: WorkspacePackage;
@@ -861,17 +886,23 @@ type Awaitable<T> = T | Promise<T>;
861
886
  type RequireFields<Obj, K extends keyof Obj> = Obj & { [R in K]-?: NonNullable<Obj[R]> };
862
887
  interface PublishPreflight {
863
888
  /**
864
- * Whether the package should be published, the state **must not** be changed across different runs.
889
+ * Whether the package should be published, the state **must only depend on local inputs** (e.g. local `package.json` or `publish-lock.yaml`).
865
890
  *
866
891
  * To note if the package is already published, hook `resolvePlanStatus` on plugins, or skip at publish-time.
867
892
  */
868
893
  shouldPublish: boolean;
869
894
  /**
870
- * Package ids that must be published before this one, this will automatically disallow circular dependency.
895
+ * Package ids that **must be** published before this one, this disallows circular dependency.
871
896
  *
872
- * It is okay to add unpublished packages to `wait`, they will be ignored.
897
+ * It is okay to add unpublished packages, they will be ignored.
873
898
  */
874
899
  wait?: string[];
900
+ /**
901
+ * Package ids that **are preferred** to publish before this one, this allows circular dependency.
902
+ *
903
+ * It is okay to add unpublished packages, they will be ignored.
904
+ */
905
+ optionalWait?: string[];
875
906
  }
876
907
  //#endregion
877
908
  export { WorkspacePackage as A, NpmPackage as C, cargo as D, CargoPluginOptions as E, DraftPolicy as M, PackageDraft as N, PackageGraph as O, BumpType as P, NpmGraph as S, CargoPackage as T, PublishOptions as _, PublishPreflight as a, TegamiContext as b, TegamiPluginOption as c, GenerateChangelogOptions as d, Tegami as f, PackagePublishResult as g, PackagePublishPlan as h, PackageOptions as i, Draft as j, PackageGroup as k, NpmPluginOptions as l, PublishLock as m, GroupOptions as n, TegamiOptions as o, tegami as p, LogGenerator as r, TegamiPlugin as s, Awaitable as t, npm as u, PublishPlan as v, CargoGraph as w, DependencySpec as x, CommitChangelog as y };
@@ -0,0 +1,39 @@
1
+ import { b as TegamiContext, j as Draft, t as Awaitable, v as PublishPlan } from "./types-BbwOrNZ2.js";
2
+
3
+ //#region src/utils/version-request.d.ts
4
+ interface VersionRequestOptions {
5
+ /**
6
+ * Create the pull/merge request even outside of CI.
7
+ *
8
+ * @default false
9
+ */
10
+ forceCreate?: boolean;
11
+ /**
12
+ * Pull/merge request branch. Publish group PRs are created under `<branch>/`.
13
+ *
14
+ * @default "tegami/version-packages"
15
+ */
16
+ branch?: string;
17
+ /**
18
+ * Pull/merge request base branch.
19
+ *
20
+ * @default "main"
21
+ */
22
+ base?: string;
23
+ /** Publish groups to split into separate version requests. */
24
+ groups?: (string | string[])[];
25
+ /** Override details of a version request at version-time. */
26
+ create?: (this: TegamiContext, opts: VersionRequestContext) => Awaitable<{
27
+ title?: string;
28
+ body?: string;
29
+ }>;
30
+ }
31
+ interface VersionRequestContext {
32
+ draft: Draft;
33
+ /** predicted publish plan (after preflight) */
34
+ plan: PublishPlan | undefined;
35
+ getPreviousVersion(packageId: string): string | undefined;
36
+ }
37
+ /** adapter over the version request API of a git provider */
38
+ //#endregion
39
+ export { VersionRequestOptions as t };