@exadev/semantic-release-workspace 0.0.0 → 1.0.1

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.
@@ -0,0 +1,259 @@
1
+ import { AnalyzeCommitsContext, BranchSpec, GenerateNotesContext } from "semantic-release";
2
+ //#region src/package-name.d.ts
3
+ declare const packageName = "@exadev/semantic-release-workspace";
4
+ //#endregion
5
+ //#region src/manifest.d.ts
6
+ /**
7
+ * The manifest fields that can name a workspace sibling.
8
+ *
9
+ * All four contribute edges to the release order: whatever field a dependency sits in, the sibling has to have released before the dependent's manifest can name its new version. All four also contribute to the decision to release a dependent (see `releaseWorkspace`), because pnpm rewrites `workspace:` ranges in every one of them at pack time -- a `devDependencies` entry is part of the published artifact even though consumers never install it.
10
+ */
11
+ type DependencyField = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
12
+ interface PackageManifest {
13
+ readonly name: string;
14
+ readonly version: string;
15
+ /** Only the fields actually present in the file, each mapping dependency name to its declared range. */
16
+ readonly dependencies: ReadonlyMap<DependencyField, ReadonlyMap<string, string>>;
17
+ }
18
+ declare function readManifest(path: string): Promise<PackageManifest>;
19
+ /**
20
+ * Rewrites one dependency range in a manifest on disk.
21
+ *
22
+ * Deliberately re-reads the file rather than editing a copy held from discovery time: by the time a cross-package bump is applied, semantic-release's own `@semantic-release/npm` prepare step may already have rewritten `version` in this same file for an earlier package in the run. Writing back a manifest parsed before that would silently revert it.
23
+ */
24
+ declare function writeDependencyRange(path: string, field: DependencyField, dependency: string, range: string): Promise<void>;
25
+ //#endregion
26
+ //#region src/workspace.d.ts
27
+ interface WorkspacePackage {
28
+ readonly name: string;
29
+ readonly version: string;
30
+ /** Absolute path to the package directory. */
31
+ readonly directory: string;
32
+ /** Path relative to the workspace root, always POSIX-separated. Used for filesystem and git-pathspec purposes scoped to the workspace itself (for example `git add` run with the workspace root as `cwd`) -- never for matching against `git log` output, which `git` always reports relative to the repository's toplevel, not to whatever `cwd` a command happened to run from. Compare `repoRelativeDirectory` for that. */
33
+ readonly relativeDirectory: string;
34
+ /** Path relative to the git repository's toplevel, always POSIX-separated. This is the base every `git log --name-only` path is compared against, because git reports changed paths relative to the repository root regardless of the directory a command runs from -- equal to `relativeDirectory` only when `pnpm-workspace.yaml` itself sits at the repository's toplevel. */
35
+ readonly repoRelativeDirectory: string;
36
+ readonly manifestPath: string;
37
+ readonly dependencies: ReadonlyMap<DependencyField, ReadonlyMap<string, string>>;
38
+ }
39
+ interface Workspace {
40
+ /** Absolute path to the directory holding `pnpm-workspace.yaml`. */
41
+ readonly root: string;
42
+ /** Every discovered package, ordered by directory so discovery is reproducible regardless of filesystem iteration order. */
43
+ readonly packages: readonly WorkspacePackage[];
44
+ }
45
+ /**
46
+ * Reads `pnpm-workspace.yaml` and every package manifest its globs match, producing the input to both the dependency graph and the per-package release runs.
47
+ *
48
+ * Nothing here knows anything about a particular repository's layout: the globs come from the workspace file, and the package names, versions, and dependency ranges come from the manifests those globs match. Pointing this at any pnpm workspace is the entire configuration.
49
+ */
50
+ declare function discoverWorkspace(root: string): Promise<Workspace>;
51
+ //#endregion
52
+ //#region src/graph.d.ts
53
+ /** One package's dependency on another package in the same workspace. */
54
+ interface WorkspaceDependency {
55
+ /** The depending package's name. */
56
+ readonly dependent: string;
57
+ /** The depended-upon package's name. */
58
+ readonly dependency: string;
59
+ readonly field: DependencyField;
60
+ /** The range exactly as written in the dependent's manifest. */
61
+ readonly range: string;
62
+ }
63
+ interface DependencyGraph {
64
+ readonly packages: ReadonlyMap<string, WorkspacePackage>;
65
+ /** For each package, the workspace siblings it depends on. */
66
+ readonly dependencies: ReadonlyMap<string, readonly WorkspaceDependency[]>;
67
+ /** For each package, the workspace siblings that depend on it. */
68
+ readonly dependents: ReadonlyMap<string, readonly WorkspaceDependency[]>;
69
+ }
70
+ /**
71
+ * Builds the inter-package dependency graph from the manifests alone.
72
+ *
73
+ * A dependency on a package outside the workspace is not an edge: it neither constrains the release order nor gets rewritten when something here releases. A package that names itself becomes a self-edge, which `topologicalOrder` then reports as the one-package cycle it is, rather than being quietly dropped.
74
+ */
75
+ declare function buildDependencyGraph(packages: readonly WorkspacePackage[]): DependencyGraph;
76
+ /**
77
+ * Orders packages so every package appears after every workspace sibling it depends on, using Kahn's algorithm.
78
+ *
79
+ * Packages whose dependencies have all been placed are taken in name order, so the same workspace always produces the same order -- a release run that reorders itself between CI runs is impossible to reason about when something goes wrong halfway through.
80
+ *
81
+ * A cycle has no valid order at all, so it throws rather than picking one of the wrong answers. In a release context an arbitrary order is worse than a failure: it would publish a package whose sibling dependency range points at a version that does not exist yet.
82
+ */
83
+ declare function topologicalOrder(graph: DependencyGraph): readonly string[];
84
+ //#endregion
85
+ //#region src/version-range.d.ts
86
+ /**
87
+ * What happens to one dependency range when the sibling it points at releases a new version.
88
+ *
89
+ * The distinction between `rewritten` and `resolved-at-publish` matters for the manifest, not for the release decision: both mean the dependent's *published* dependency range changes, and therefore that the dependent needs a release of its own for that change to reach consumers. Only `wildcard` leaves the published artifact genuinely identical.
90
+ */
91
+ type DependencyRangeUpdate =
92
+ /** The range names a concrete version that has to be rewritten in the manifest. */
93
+ {
94
+ readonly kind: 'rewritten';
95
+ readonly range: string;
96
+ } |
97
+ /** A bare `workspace:*`, `workspace:^`, or `workspace:~` range: pnpm substitutes the sibling's current version at pack time, so the manifest on disk needs no edit even though the published range does change. */
98
+ {
99
+ readonly kind: 'resolved-at-publish';
100
+ } |
101
+ /** A range naming no version at all (`*`, `x`, `latest`). Nothing to rewrite, and the published range is unaffected by the sibling's new version. */
102
+ {
103
+ readonly kind: 'wildcard';
104
+ };
105
+ /**
106
+ * What a dependency range's own shape supports, independent of any particular version -- the classification that decides whether `updateDependencyRange` can succeed at all, split out so it can be checked for every workspace dependency edge before a release run starts, not just when the range's sibling actually releases.
107
+ */
108
+ type DependencyRangeShape = {
109
+ readonly kind: 'rewritable';
110
+ readonly workspacePrefixed: boolean;
111
+ readonly comparator: string;
112
+ } | {
113
+ readonly kind: 'resolved-at-publish';
114
+ } | {
115
+ readonly kind: 'wildcard';
116
+ };
117
+ /**
118
+ * Classifies a dependency range's shape, throwing `UnsupportedDependencyRangeError` for anything this tool cannot rewrite with confidence: a compound range (`>=1.0.0 <2.0.0`), a union (`1.x || 2.x`), a `catalog:` reference whose real version lives in `pnpm-workspace.yaml`, an `npm:` alias, a git or tarball URL. Guessing at those would either corrupt the range or silently leave it pointing at a version that no longer exists in the workspace, and a stale published range is exactly the divergence this tool exists to prevent.
119
+ *
120
+ * This never needs the version a sibling is releasing: every case above depends only on the shape of `current` itself, which is what lets `releaseWorkspace` validate every workspace dependency edge up front, before any package has published anything, rather than discovering an unsupported range only when the first dependency it names happens to release.
121
+ */
122
+ declare function classifyDependencyRange(current: string): DependencyRangeShape;
123
+ /**
124
+ * Computes what a dependency range on a workspace sibling becomes once that sibling releases `version`, by classifying the range's shape and then, for a rewritable shape, substituting `version` in place of the version it currently names.
125
+ */
126
+ declare function updateDependencyRange(current: string, version: string): DependencyRangeUpdate;
127
+ //#endregion
128
+ //#region src/plugins.d.ts
129
+ /**
130
+ * One workspace dependency range that changed because its package released a new version during this run. `rewritten` means the dependent's manifest was edited on disk; `resolved-at-publish` means a `workspace:^`-style range whose on-disk text is unchanged but whose published value pnpm re-resolves at pack time. Both change the dependent's published artifact, which is why both count towards its release.
131
+ */
132
+ interface DependencyBump {
133
+ readonly dependency: string;
134
+ readonly version: string;
135
+ /** The range as it now stands in the dependent's manifest -- the new concrete range for `rewritten`, the untouched `workspace:` range for `resolved-at-publish`. */
136
+ readonly range: string;
137
+ readonly kind: 'rewritten' | 'resolved-at-publish';
138
+ }
139
+ /** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
140
+ interface DependencyBumpSource {
141
+ bumpsFor(dependent: string): readonly DependencyBump[];
142
+ }
143
+ /** A publish-pipeline plugin entry as the orchestrator accepts it: a module name, optionally with a config object. */
144
+ type PublishPluginSpec = string | readonly [string] | readonly [string, Record<string, unknown>];
145
+ /** The standard publish pipeline this orchestrator coordinates when a workspace configures none of its own. Every entry reuses the corresponding official plugin -- the orchestrator scopes and sequences them per package, it does not reimplement npm publishing, GitHub release creation, or changelog writing. */
146
+ declare const DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
147
+ interface ScopedPlugins {
148
+ readonly analyzeCommits: (pluginConfig: Record<string, unknown>, context: AnalyzeCommitsContext & {
149
+ cwd: string;
150
+ }) => Promise<string | false | undefined>;
151
+ readonly generateNotes: (pluginConfig: Record<string, unknown>, context: GenerateNotesContext & {
152
+ cwd: string;
153
+ }) => Promise<string | false | undefined>;
154
+ }
155
+ /**
156
+ * Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
157
+ *
158
+ * Both apply the same path scoping before delegating to the real @semantic-release/commit-analyzer and @semantic-release/release-notes-generator: the commit list semantic-release already fetched for the release range is filtered down to commits whose `git log --name-only` file list intersects the package's own directory, and only the filtered list reaches the standard plugin. Conventional-commit parsing and changelog formatting stay entirely inside the standard plugins.
159
+ *
160
+ * The `analyzeCommits` wrapper carries one addition beyond filtering: when the standard analyzer finds no releasable commits but a workspace dependency range of the package's has changed, it returns 'patch' anyway. A dependent whose only change is a dependency bump still needs a release for that range to reach the registry. "Has changed" is read from two sources, merged: bumps recorded in memory earlier in the current run (`scope.bumps`), and bumps recorded in the package's own filtered commit history via the trailer `dependency-bump-commit.ts` writes and reads -- the latter is what lets a run that starts after a previous run already committed and pushed the bump (a crash recovery, or simply a later run) reach the same decision, rather than depending on state that existed only inside the process that made the commit.
161
+ */
162
+ declare function createScopedPlugins(scope: {
163
+ readonly pkg: WorkspacePackage;
164
+ readonly analyzeCommitsConfig: Record<string, unknown>;
165
+ readonly generateNotesConfig: Record<string, unknown>;
166
+ readonly bumps: DependencyBumpSource;
167
+ }): ScopedPlugins;
168
+ /**
169
+ * Keeps a commit for the package when any path it changed lies under the package's directory. The trailing-slash prefix comparison stops `packages/a` from matching `packages/abc/x`.
170
+ *
171
+ * A commit missing from the changed-paths map is kept rather than dropped: it is inside the package's release range (semantic-release put it there), so a failure to parse its file list must not silently swallow a release. Absent evidence errs towards publishing, which is the visible direction for a release tool.
172
+ */
173
+ declare function filterCommitsToDirectory<T extends {
174
+ readonly hash: string;
175
+ }>(commits: readonly T[], changedPaths: ReadonlyMap<string, ReadonlySet<string>>, directory: string): readonly T[];
176
+ /** A publish plugin entry with its module name resolved to an absolute path, so semantic-release loads the workspace's installed plugins regardless of the package directory it runs from. */
177
+ type ResolvedPublishPlugin = [string, Record<string, unknown>];
178
+ declare function resolvePublishPlugins(specs: readonly PublishPluginSpec[], workspaceRoot: string, options: {
179
+ readonly requireGitPlugin: boolean;
180
+ }): readonly ResolvedPublishPlugin[];
181
+ //#endregion
182
+ //#region src/release.d.ts
183
+ interface ReleaseWorkspaceOptions {
184
+ /** Directory holding the workspace's `pnpm-workspace.yaml`. Defaults to the process working directory. */
185
+ readonly root?: string;
186
+ /** Environment for the per-package semantic-release runs. Defaults to `process.env`; passing a copy lets tests and embedders control the CI detection semantic-release performs on it. */
187
+ readonly env?: NodeJS.ProcessEnv;
188
+ /** Passed straight through to every per-package semantic-release run: analysis runs, nothing is published, tagged, committed, or pushed. */
189
+ readonly dryRun?: boolean;
190
+ /** Release branch configuration for semantic-release. Defaults to semantic-release's own default branch list. */
191
+ readonly branches?: readonly BranchSpec[];
192
+ /** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS. */
193
+ readonly plugins?: readonly PublishPluginSpec[];
194
+ /** Options for the wrapped @semantic-release/commit-analyzer, applied per package after path filtering. */
195
+ readonly analyzeCommits?: Record<string, unknown>;
196
+ /** Options for the wrapped @semantic-release/release-notes-generator, applied per package after path filtering. */
197
+ readonly generateNotes?: Record<string, unknown>;
198
+ /** Progress sink for the orchestrator's own narration (semantic-release logs its own detail). Defaults to `console.log`. */
199
+ readonly log?: (message: string) => void;
200
+ }
201
+ /** One dependency-range change applied to a dependent package's manifest during the run, attached to the dependent's own outcome. */
202
+ interface AppliedDependencyBump extends DependencyBump {
203
+ readonly dependent: string;
204
+ /** Which manifest field held the range that was rewritten. */
205
+ readonly field: DependencyField;
206
+ }
207
+ interface PackageReleaseOutcome {
208
+ readonly name: string;
209
+ readonly directory: string;
210
+ readonly released: boolean;
211
+ readonly version: string | undefined;
212
+ readonly gitTag: string | undefined;
213
+ /** The semantic-release release type ('minor', 'patch', ...), including the forced 'patch' of a dependency-bump-only release. */
214
+ readonly type: string | undefined;
215
+ /** Dependency ranges rewritten in this package's own manifest because a workspace dependency released earlier in the run. */
216
+ readonly dependencyBumps: readonly AppliedDependencyBump[];
217
+ }
218
+ interface WorkspaceReleaseOutcome {
219
+ /** The topological order the packages were released in. */
220
+ readonly order: readonly string[];
221
+ readonly packages: readonly PackageReleaseOutcome[];
222
+ }
223
+ /**
224
+ * Releases every package in a pnpm workspace with independent versions, in dependency order.
225
+ *
226
+ * For each package, in topological order: run semantic-release's programmatic API with `cwd` scoped to the package directory, a `name@version` tag format to keep each package's tags distinct in the one shared tag namespace, and inline `analyzeCommits`/`generateNotes` plugins that filter the release range's commits down to the package's own directory before delegating to the standard plugins. When a package releases, every workspace package that depends on it and has not run yet gets its dependency range rewritten in its manifest and committed immediately -- before its own turn, so its commit analysis and its published manifest both see the new range.
227
+ */
228
+ declare function releaseWorkspace(options?: ReleaseWorkspaceOptions): Promise<WorkspaceReleaseOutcome>;
229
+ //#endregion
230
+ //#region src/errors.d.ts
231
+ /**
232
+ * Every failure this package raises deliberately is one of these, so a caller (or the CLI) can tell an orchestration failure it should report cleanly apart from an unexpected crash it should let propagate with a stack trace.
233
+ *
234
+ * All of them are thrown, never returned as a status: the orchestrator deliberately has no "skip this package and carry on" path. A workspace that can't be discovered, ordered, or bumped correctly would otherwise publish a partially-consistent set of packages, which is strictly worse than publishing nothing.
235
+ */
236
+ declare class WorkspaceReleaseError extends Error {
237
+ constructor(message: string);
238
+ }
239
+ /** The workspace itself could not be read: no `pnpm-workspace.yaml`, no `packages` globs, an unreadable or malformed `package.json`, two packages claiming the same name, or a package sitting at the workspace root (which cannot be path-scoped -- see `discoverWorkspace`). */
240
+ declare class WorkspaceDiscoveryError extends WorkspaceReleaseError {}
241
+ /** The intra-workspace dependency graph contains a cycle, so no release order exists in which every package releases after its own dependencies. */
242
+ declare class DependencyCycleError extends WorkspaceReleaseError {
243
+ /** The packages forming the cycle, in dependency order, with the first package repeated at the end so the loop reads end to end. */
244
+ readonly cycle: readonly string[];
245
+ constructor(cycle: readonly string[]);
246
+ }
247
+ /** A dependency on a workspace sibling uses a range this tool cannot rewrite with confidence. Rewriting it wrongly, or leaving it silently stale, both produce a published manifest that disagrees with the repository, so the run stops instead. */
248
+ declare class UnsupportedDependencyRangeError extends WorkspaceReleaseError {}
249
+ /** The semantic-release options handed to the orchestrator cannot be scoped to a single package -- typically a publish plugin list that would leave a release commit or a cross-package manifest bump uncommitted. */
250
+ declare class ReleaseConfigurationError extends WorkspaceReleaseError {}
251
+ /** A git command the orchestrator runs itself (history filtering, dependency-bump commits, pushes) failed. Carries the exit code so callers can distinguish "configuration is missing" (exit 1) from real repository failures. */
252
+ declare class GitCommandError extends WorkspaceReleaseError {
253
+ readonly exitCode: number | undefined;
254
+ constructor(args: readonly string[], cwd: string, exitCode: number | undefined, detail: string);
255
+ }
256
+ /** The workspace's git state does not support the release operation -- for example a detached HEAD, which names no branch that dependency-bump commits could be pushed to. */
257
+ declare class WorkspaceStateError extends WorkspaceReleaseError {}
258
+ //#endregion
259
+ export { type AppliedDependencyBump, DEFAULT_PUBLISH_PLUGINS, type DependencyBump, type DependencyBumpSource, DependencyCycleError, type DependencyField, type DependencyGraph, type DependencyRangeShape, type DependencyRangeUpdate, GitCommandError, type PackageManifest, type PackageReleaseOutcome, type PublishPluginSpec, ReleaseConfigurationError, type ReleaseWorkspaceOptions, type ResolvedPublishPlugin, type ScopedPlugins, UnsupportedDependencyRangeError, type Workspace, type WorkspaceDependency, WorkspaceDiscoveryError, type WorkspacePackage, WorkspaceReleaseError, type WorkspaceReleaseOutcome, WorkspaceStateError, buildDependencyGraph, classifyDependencyRange, createScopedPlugins, discoverWorkspace, filterCommitsToDirectory, packageName, readManifest, releaseWorkspace, resolvePublishPlugins, topologicalOrder, updateDependencyRange, writeDependencyRange };
@@ -0,0 +1,259 @@
1
+ import { AnalyzeCommitsContext, BranchSpec, GenerateNotesContext } from "semantic-release";
2
+ //#region src/package-name.d.ts
3
+ declare const packageName = "@exadev/semantic-release-workspace";
4
+ //#endregion
5
+ //#region src/manifest.d.ts
6
+ /**
7
+ * The manifest fields that can name a workspace sibling.
8
+ *
9
+ * All four contribute edges to the release order: whatever field a dependency sits in, the sibling has to have released before the dependent's manifest can name its new version. All four also contribute to the decision to release a dependent (see `releaseWorkspace`), because pnpm rewrites `workspace:` ranges in every one of them at pack time -- a `devDependencies` entry is part of the published artifact even though consumers never install it.
10
+ */
11
+ type DependencyField = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
12
+ interface PackageManifest {
13
+ readonly name: string;
14
+ readonly version: string;
15
+ /** Only the fields actually present in the file, each mapping dependency name to its declared range. */
16
+ readonly dependencies: ReadonlyMap<DependencyField, ReadonlyMap<string, string>>;
17
+ }
18
+ declare function readManifest(path: string): Promise<PackageManifest>;
19
+ /**
20
+ * Rewrites one dependency range in a manifest on disk.
21
+ *
22
+ * Deliberately re-reads the file rather than editing a copy held from discovery time: by the time a cross-package bump is applied, semantic-release's own `@semantic-release/npm` prepare step may already have rewritten `version` in this same file for an earlier package in the run. Writing back a manifest parsed before that would silently revert it.
23
+ */
24
+ declare function writeDependencyRange(path: string, field: DependencyField, dependency: string, range: string): Promise<void>;
25
+ //#endregion
26
+ //#region src/workspace.d.ts
27
+ interface WorkspacePackage {
28
+ readonly name: string;
29
+ readonly version: string;
30
+ /** Absolute path to the package directory. */
31
+ readonly directory: string;
32
+ /** Path relative to the workspace root, always POSIX-separated. Used for filesystem and git-pathspec purposes scoped to the workspace itself (for example `git add` run with the workspace root as `cwd`) -- never for matching against `git log` output, which `git` always reports relative to the repository's toplevel, not to whatever `cwd` a command happened to run from. Compare `repoRelativeDirectory` for that. */
33
+ readonly relativeDirectory: string;
34
+ /** Path relative to the git repository's toplevel, always POSIX-separated. This is the base every `git log --name-only` path is compared against, because git reports changed paths relative to the repository root regardless of the directory a command runs from -- equal to `relativeDirectory` only when `pnpm-workspace.yaml` itself sits at the repository's toplevel. */
35
+ readonly repoRelativeDirectory: string;
36
+ readonly manifestPath: string;
37
+ readonly dependencies: ReadonlyMap<DependencyField, ReadonlyMap<string, string>>;
38
+ }
39
+ interface Workspace {
40
+ /** Absolute path to the directory holding `pnpm-workspace.yaml`. */
41
+ readonly root: string;
42
+ /** Every discovered package, ordered by directory so discovery is reproducible regardless of filesystem iteration order. */
43
+ readonly packages: readonly WorkspacePackage[];
44
+ }
45
+ /**
46
+ * Reads `pnpm-workspace.yaml` and every package manifest its globs match, producing the input to both the dependency graph and the per-package release runs.
47
+ *
48
+ * Nothing here knows anything about a particular repository's layout: the globs come from the workspace file, and the package names, versions, and dependency ranges come from the manifests those globs match. Pointing this at any pnpm workspace is the entire configuration.
49
+ */
50
+ declare function discoverWorkspace(root: string): Promise<Workspace>;
51
+ //#endregion
52
+ //#region src/graph.d.ts
53
+ /** One package's dependency on another package in the same workspace. */
54
+ interface WorkspaceDependency {
55
+ /** The depending package's name. */
56
+ readonly dependent: string;
57
+ /** The depended-upon package's name. */
58
+ readonly dependency: string;
59
+ readonly field: DependencyField;
60
+ /** The range exactly as written in the dependent's manifest. */
61
+ readonly range: string;
62
+ }
63
+ interface DependencyGraph {
64
+ readonly packages: ReadonlyMap<string, WorkspacePackage>;
65
+ /** For each package, the workspace siblings it depends on. */
66
+ readonly dependencies: ReadonlyMap<string, readonly WorkspaceDependency[]>;
67
+ /** For each package, the workspace siblings that depend on it. */
68
+ readonly dependents: ReadonlyMap<string, readonly WorkspaceDependency[]>;
69
+ }
70
+ /**
71
+ * Builds the inter-package dependency graph from the manifests alone.
72
+ *
73
+ * A dependency on a package outside the workspace is not an edge: it neither constrains the release order nor gets rewritten when something here releases. A package that names itself becomes a self-edge, which `topologicalOrder` then reports as the one-package cycle it is, rather than being quietly dropped.
74
+ */
75
+ declare function buildDependencyGraph(packages: readonly WorkspacePackage[]): DependencyGraph;
76
+ /**
77
+ * Orders packages so every package appears after every workspace sibling it depends on, using Kahn's algorithm.
78
+ *
79
+ * Packages whose dependencies have all been placed are taken in name order, so the same workspace always produces the same order -- a release run that reorders itself between CI runs is impossible to reason about when something goes wrong halfway through.
80
+ *
81
+ * A cycle has no valid order at all, so it throws rather than picking one of the wrong answers. In a release context an arbitrary order is worse than a failure: it would publish a package whose sibling dependency range points at a version that does not exist yet.
82
+ */
83
+ declare function topologicalOrder(graph: DependencyGraph): readonly string[];
84
+ //#endregion
85
+ //#region src/version-range.d.ts
86
+ /**
87
+ * What happens to one dependency range when the sibling it points at releases a new version.
88
+ *
89
+ * The distinction between `rewritten` and `resolved-at-publish` matters for the manifest, not for the release decision: both mean the dependent's *published* dependency range changes, and therefore that the dependent needs a release of its own for that change to reach consumers. Only `wildcard` leaves the published artifact genuinely identical.
90
+ */
91
+ type DependencyRangeUpdate =
92
+ /** The range names a concrete version that has to be rewritten in the manifest. */
93
+ {
94
+ readonly kind: 'rewritten';
95
+ readonly range: string;
96
+ } |
97
+ /** A bare `workspace:*`, `workspace:^`, or `workspace:~` range: pnpm substitutes the sibling's current version at pack time, so the manifest on disk needs no edit even though the published range does change. */
98
+ {
99
+ readonly kind: 'resolved-at-publish';
100
+ } |
101
+ /** A range naming no version at all (`*`, `x`, `latest`). Nothing to rewrite, and the published range is unaffected by the sibling's new version. */
102
+ {
103
+ readonly kind: 'wildcard';
104
+ };
105
+ /**
106
+ * What a dependency range's own shape supports, independent of any particular version -- the classification that decides whether `updateDependencyRange` can succeed at all, split out so it can be checked for every workspace dependency edge before a release run starts, not just when the range's sibling actually releases.
107
+ */
108
+ type DependencyRangeShape = {
109
+ readonly kind: 'rewritable';
110
+ readonly workspacePrefixed: boolean;
111
+ readonly comparator: string;
112
+ } | {
113
+ readonly kind: 'resolved-at-publish';
114
+ } | {
115
+ readonly kind: 'wildcard';
116
+ };
117
+ /**
118
+ * Classifies a dependency range's shape, throwing `UnsupportedDependencyRangeError` for anything this tool cannot rewrite with confidence: a compound range (`>=1.0.0 <2.0.0`), a union (`1.x || 2.x`), a `catalog:` reference whose real version lives in `pnpm-workspace.yaml`, an `npm:` alias, a git or tarball URL. Guessing at those would either corrupt the range or silently leave it pointing at a version that no longer exists in the workspace, and a stale published range is exactly the divergence this tool exists to prevent.
119
+ *
120
+ * This never needs the version a sibling is releasing: every case above depends only on the shape of `current` itself, which is what lets `releaseWorkspace` validate every workspace dependency edge up front, before any package has published anything, rather than discovering an unsupported range only when the first dependency it names happens to release.
121
+ */
122
+ declare function classifyDependencyRange(current: string): DependencyRangeShape;
123
+ /**
124
+ * Computes what a dependency range on a workspace sibling becomes once that sibling releases `version`, by classifying the range's shape and then, for a rewritable shape, substituting `version` in place of the version it currently names.
125
+ */
126
+ declare function updateDependencyRange(current: string, version: string): DependencyRangeUpdate;
127
+ //#endregion
128
+ //#region src/plugins.d.ts
129
+ /**
130
+ * One workspace dependency range that changed because its package released a new version during this run. `rewritten` means the dependent's manifest was edited on disk; `resolved-at-publish` means a `workspace:^`-style range whose on-disk text is unchanged but whose published value pnpm re-resolves at pack time. Both change the dependent's published artifact, which is why both count towards its release.
131
+ */
132
+ interface DependencyBump {
133
+ readonly dependency: string;
134
+ readonly version: string;
135
+ /** The range as it now stands in the dependent's manifest -- the new concrete range for `rewritten`, the untouched `workspace:` range for `resolved-at-publish`. */
136
+ readonly range: string;
137
+ readonly kind: 'rewritten' | 'resolved-at-publish';
138
+ }
139
+ /** What the scoped plugins need to know about bumps recorded so far in the run, for the package they are about to analyse. */
140
+ interface DependencyBumpSource {
141
+ bumpsFor(dependent: string): readonly DependencyBump[];
142
+ }
143
+ /** A publish-pipeline plugin entry as the orchestrator accepts it: a module name, optionally with a config object. */
144
+ type PublishPluginSpec = string | readonly [string] | readonly [string, Record<string, unknown>];
145
+ /** The standard publish pipeline this orchestrator coordinates when a workspace configures none of its own. Every entry reuses the corresponding official plugin -- the orchestrator scopes and sequences them per package, it does not reimplement npm publishing, GitHub release creation, or changelog writing. */
146
+ declare const DEFAULT_PUBLISH_PLUGINS: readonly PublishPluginSpec[];
147
+ interface ScopedPlugins {
148
+ readonly analyzeCommits: (pluginConfig: Record<string, unknown>, context: AnalyzeCommitsContext & {
149
+ cwd: string;
150
+ }) => Promise<string | false | undefined>;
151
+ readonly generateNotes: (pluginConfig: Record<string, unknown>, context: GenerateNotesContext & {
152
+ cwd: string;
153
+ }) => Promise<string | false | undefined>;
154
+ }
155
+ /**
156
+ * Builds the per-package `analyzeCommits` and `generateNotes` functions handed to semantic-release as inline plugins.
157
+ *
158
+ * Both apply the same path scoping before delegating to the real @semantic-release/commit-analyzer and @semantic-release/release-notes-generator: the commit list semantic-release already fetched for the release range is filtered down to commits whose `git log --name-only` file list intersects the package's own directory, and only the filtered list reaches the standard plugin. Conventional-commit parsing and changelog formatting stay entirely inside the standard plugins.
159
+ *
160
+ * The `analyzeCommits` wrapper carries one addition beyond filtering: when the standard analyzer finds no releasable commits but a workspace dependency range of the package's has changed, it returns 'patch' anyway. A dependent whose only change is a dependency bump still needs a release for that range to reach the registry. "Has changed" is read from two sources, merged: bumps recorded in memory earlier in the current run (`scope.bumps`), and bumps recorded in the package's own filtered commit history via the trailer `dependency-bump-commit.ts` writes and reads -- the latter is what lets a run that starts after a previous run already committed and pushed the bump (a crash recovery, or simply a later run) reach the same decision, rather than depending on state that existed only inside the process that made the commit.
161
+ */
162
+ declare function createScopedPlugins(scope: {
163
+ readonly pkg: WorkspacePackage;
164
+ readonly analyzeCommitsConfig: Record<string, unknown>;
165
+ readonly generateNotesConfig: Record<string, unknown>;
166
+ readonly bumps: DependencyBumpSource;
167
+ }): ScopedPlugins;
168
+ /**
169
+ * Keeps a commit for the package when any path it changed lies under the package's directory. The trailing-slash prefix comparison stops `packages/a` from matching `packages/abc/x`.
170
+ *
171
+ * A commit missing from the changed-paths map is kept rather than dropped: it is inside the package's release range (semantic-release put it there), so a failure to parse its file list must not silently swallow a release. Absent evidence errs towards publishing, which is the visible direction for a release tool.
172
+ */
173
+ declare function filterCommitsToDirectory<T extends {
174
+ readonly hash: string;
175
+ }>(commits: readonly T[], changedPaths: ReadonlyMap<string, ReadonlySet<string>>, directory: string): readonly T[];
176
+ /** A publish plugin entry with its module name resolved to an absolute path, so semantic-release loads the workspace's installed plugins regardless of the package directory it runs from. */
177
+ type ResolvedPublishPlugin = [string, Record<string, unknown>];
178
+ declare function resolvePublishPlugins(specs: readonly PublishPluginSpec[], workspaceRoot: string, options: {
179
+ readonly requireGitPlugin: boolean;
180
+ }): readonly ResolvedPublishPlugin[];
181
+ //#endregion
182
+ //#region src/release.d.ts
183
+ interface ReleaseWorkspaceOptions {
184
+ /** Directory holding the workspace's `pnpm-workspace.yaml`. Defaults to the process working directory. */
185
+ readonly root?: string;
186
+ /** Environment for the per-package semantic-release runs. Defaults to `process.env`; passing a copy lets tests and embedders control the CI detection semantic-release performs on it. */
187
+ readonly env?: NodeJS.ProcessEnv;
188
+ /** Passed straight through to every per-package semantic-release run: analysis runs, nothing is published, tagged, committed, or pushed. */
189
+ readonly dryRun?: boolean;
190
+ /** Release branch configuration for semantic-release. Defaults to semantic-release's own default branch list. */
191
+ readonly branches?: readonly BranchSpec[];
192
+ /** Publish-pipeline plugins (changelog, npm, GitHub, git), each scoped per package by semantic-release's own `cwd`. Defaults to the standard pipeline in DEFAULT_PUBLISH_PLUGINS. */
193
+ readonly plugins?: readonly PublishPluginSpec[];
194
+ /** Options for the wrapped @semantic-release/commit-analyzer, applied per package after path filtering. */
195
+ readonly analyzeCommits?: Record<string, unknown>;
196
+ /** Options for the wrapped @semantic-release/release-notes-generator, applied per package after path filtering. */
197
+ readonly generateNotes?: Record<string, unknown>;
198
+ /** Progress sink for the orchestrator's own narration (semantic-release logs its own detail). Defaults to `console.log`. */
199
+ readonly log?: (message: string) => void;
200
+ }
201
+ /** One dependency-range change applied to a dependent package's manifest during the run, attached to the dependent's own outcome. */
202
+ interface AppliedDependencyBump extends DependencyBump {
203
+ readonly dependent: string;
204
+ /** Which manifest field held the range that was rewritten. */
205
+ readonly field: DependencyField;
206
+ }
207
+ interface PackageReleaseOutcome {
208
+ readonly name: string;
209
+ readonly directory: string;
210
+ readonly released: boolean;
211
+ readonly version: string | undefined;
212
+ readonly gitTag: string | undefined;
213
+ /** The semantic-release release type ('minor', 'patch', ...), including the forced 'patch' of a dependency-bump-only release. */
214
+ readonly type: string | undefined;
215
+ /** Dependency ranges rewritten in this package's own manifest because a workspace dependency released earlier in the run. */
216
+ readonly dependencyBumps: readonly AppliedDependencyBump[];
217
+ }
218
+ interface WorkspaceReleaseOutcome {
219
+ /** The topological order the packages were released in. */
220
+ readonly order: readonly string[];
221
+ readonly packages: readonly PackageReleaseOutcome[];
222
+ }
223
+ /**
224
+ * Releases every package in a pnpm workspace with independent versions, in dependency order.
225
+ *
226
+ * For each package, in topological order: run semantic-release's programmatic API with `cwd` scoped to the package directory, a `name@version` tag format to keep each package's tags distinct in the one shared tag namespace, and inline `analyzeCommits`/`generateNotes` plugins that filter the release range's commits down to the package's own directory before delegating to the standard plugins. When a package releases, every workspace package that depends on it and has not run yet gets its dependency range rewritten in its manifest and committed immediately -- before its own turn, so its commit analysis and its published manifest both see the new range.
227
+ */
228
+ declare function releaseWorkspace(options?: ReleaseWorkspaceOptions): Promise<WorkspaceReleaseOutcome>;
229
+ //#endregion
230
+ //#region src/errors.d.ts
231
+ /**
232
+ * Every failure this package raises deliberately is one of these, so a caller (or the CLI) can tell an orchestration failure it should report cleanly apart from an unexpected crash it should let propagate with a stack trace.
233
+ *
234
+ * All of them are thrown, never returned as a status: the orchestrator deliberately has no "skip this package and carry on" path. A workspace that can't be discovered, ordered, or bumped correctly would otherwise publish a partially-consistent set of packages, which is strictly worse than publishing nothing.
235
+ */
236
+ declare class WorkspaceReleaseError extends Error {
237
+ constructor(message: string);
238
+ }
239
+ /** The workspace itself could not be read: no `pnpm-workspace.yaml`, no `packages` globs, an unreadable or malformed `package.json`, two packages claiming the same name, or a package sitting at the workspace root (which cannot be path-scoped -- see `discoverWorkspace`). */
240
+ declare class WorkspaceDiscoveryError extends WorkspaceReleaseError {}
241
+ /** The intra-workspace dependency graph contains a cycle, so no release order exists in which every package releases after its own dependencies. */
242
+ declare class DependencyCycleError extends WorkspaceReleaseError {
243
+ /** The packages forming the cycle, in dependency order, with the first package repeated at the end so the loop reads end to end. */
244
+ readonly cycle: readonly string[];
245
+ constructor(cycle: readonly string[]);
246
+ }
247
+ /** A dependency on a workspace sibling uses a range this tool cannot rewrite with confidence. Rewriting it wrongly, or leaving it silently stale, both produce a published manifest that disagrees with the repository, so the run stops instead. */
248
+ declare class UnsupportedDependencyRangeError extends WorkspaceReleaseError {}
249
+ /** The semantic-release options handed to the orchestrator cannot be scoped to a single package -- typically a publish plugin list that would leave a release commit or a cross-package manifest bump uncommitted. */
250
+ declare class ReleaseConfigurationError extends WorkspaceReleaseError {}
251
+ /** A git command the orchestrator runs itself (history filtering, dependency-bump commits, pushes) failed. Carries the exit code so callers can distinguish "configuration is missing" (exit 1) from real repository failures. */
252
+ declare class GitCommandError extends WorkspaceReleaseError {
253
+ readonly exitCode: number | undefined;
254
+ constructor(args: readonly string[], cwd: string, exitCode: number | undefined, detail: string);
255
+ }
256
+ /** The workspace's git state does not support the release operation -- for example a detached HEAD, which names no branch that dependency-bump commits could be pushed to. */
257
+ declare class WorkspaceStateError extends WorkspaceReleaseError {}
258
+ //#endregion
259
+ export { type AppliedDependencyBump, DEFAULT_PUBLISH_PLUGINS, type DependencyBump, type DependencyBumpSource, DependencyCycleError, type DependencyField, type DependencyGraph, type DependencyRangeShape, type DependencyRangeUpdate, GitCommandError, type PackageManifest, type PackageReleaseOutcome, type PublishPluginSpec, ReleaseConfigurationError, type ReleaseWorkspaceOptions, type ResolvedPublishPlugin, type ScopedPlugins, UnsupportedDependencyRangeError, type Workspace, type WorkspaceDependency, WorkspaceDiscoveryError, type WorkspacePackage, WorkspaceReleaseError, type WorkspaceReleaseOutcome, WorkspaceStateError, buildDependencyGraph, classifyDependencyRange, createScopedPlugins, discoverWorkspace, filterCommitsToDirectory, packageName, readManifest, releaseWorkspace, resolvePublishPlugins, topologicalOrder, updateDependencyRange, writeDependencyRange };