@savvy-web/silk-effects 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changesets/errors.js +44 -1
- package/changesets/index.js +8 -11
- package/changesets/services/deps-regen.js +152 -113
- package/changesets/services/release-planner.js +105 -81
- package/changesets/utils/dep-diff.js +81 -47
- package/changesets/utils/git.js +51 -0
- package/changesets/utils/publishability.js +14 -2
- package/index.d.ts +157 -141
- package/package.json +2 -2
- package/changesets/services/workspace-snapshot.js +0 -181
- package/changesets/utils/worktree-snapshot.js +0 -142
|
@@ -3,9 +3,8 @@ import { ChangelogTransformer } from "../api/transformer.js";
|
|
|
3
3
|
import { ConfigInspector } from "./config-inspector.js";
|
|
4
4
|
import { VersionFiles } from "../utils/version-files.js";
|
|
5
5
|
import { Context, Effect, Layer } from "effect";
|
|
6
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
7
6
|
import { isAbsolute, join, relative } from "node:path";
|
|
8
|
-
import {
|
|
7
|
+
import { FileSystem } from "@effect/platform";
|
|
9
8
|
import applyReleasePlan from "@changesets/apply-release-plan";
|
|
10
9
|
import { read } from "@changesets/config";
|
|
11
10
|
import getReleasePlan from "@changesets/get-release-plan";
|
|
@@ -37,8 +36,8 @@ const _tag = Context.Tag("ReleasePlanner");
|
|
|
37
36
|
const ReleasePlannerBase = _tag();
|
|
38
37
|
/** Effect service tag for the release planner. @public */
|
|
39
38
|
var ReleasePlanner = class extends ReleasePlannerBase {};
|
|
40
|
-
/** Build the service shape over a resolved {@link ConfigInspector}. */
|
|
41
|
-
function makeShape(inspector) {
|
|
39
|
+
/** Build the service shape over a resolved {@link ConfigInspector} and {@link FileSystem.FileSystem}. */
|
|
40
|
+
function makeShape(inspector, fs) {
|
|
42
41
|
const plan = (root) => Effect.tryPromise({
|
|
43
42
|
try: () => getReleasePlan(root),
|
|
44
43
|
catch: (e) => new ReleasePlanError({
|
|
@@ -46,23 +45,17 @@ function makeShape(inspector) {
|
|
|
46
45
|
reason: errMsg(e)
|
|
47
46
|
})
|
|
48
47
|
});
|
|
49
|
-
const preview = (root) =>
|
|
50
|
-
|
|
51
|
-
catch: (e) => new ReleasePlanError({
|
|
52
|
-
phase: "preview",
|
|
53
|
-
reason: errMsg(e)
|
|
54
|
-
})
|
|
55
|
-
});
|
|
56
|
-
const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, inspector);
|
|
48
|
+
const preview = (root) => previewEffect(root, fs);
|
|
49
|
+
const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, inspector, fs);
|
|
57
50
|
return {
|
|
58
51
|
plan,
|
|
59
52
|
preview,
|
|
60
53
|
apply
|
|
61
54
|
};
|
|
62
55
|
}
|
|
63
|
-
/** Production layer. Requires {@link ConfigInspector} (used by `apply`)
|
|
56
|
+
/** Production layer. Requires {@link ConfigInspector} (used by `apply`) and `FileSystem`. @public */
|
|
64
57
|
const ReleasePlannerLive = Layer.effect(ReleasePlanner, Effect.gen(function* () {
|
|
65
|
-
return makeShape(yield* ConfigInspector);
|
|
58
|
+
return makeShape(yield* ConfigInspector, yield* FileSystem.FileSystem);
|
|
66
59
|
}));
|
|
67
60
|
/**
|
|
68
61
|
* Test factory — supply fixed results for any subset of methods. Unsupplied
|
|
@@ -93,31 +86,52 @@ function extractVersionBlock(changelog, version) {
|
|
|
93
86
|
}
|
|
94
87
|
return lines.slice(start, end).join("\n").trim();
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Render a non-destructive preview by redirecting every write into a
|
|
91
|
+
* scope-managed temp directory (cleaned up automatically when the scope
|
|
92
|
+
* closes) and reading the generated CHANGELOG blocks back.
|
|
93
|
+
*/
|
|
94
|
+
function previewEffect(root, fs) {
|
|
95
|
+
const program = Effect.gen(function* () {
|
|
96
|
+
const [plan, packages] = yield* Effect.tryPromise({
|
|
97
|
+
try: () => Promise.all([getReleasePlan(root), buildPackages(root)]),
|
|
98
|
+
catch: (e) => new ReleasePlanError({
|
|
99
|
+
phase: "preview",
|
|
100
|
+
reason: errMsg(e)
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
const config = yield* Effect.tryPromise({
|
|
104
|
+
try: () => read(root, packages),
|
|
105
|
+
catch: (e) => new ReleasePlanError({
|
|
106
|
+
phase: "preview",
|
|
107
|
+
reason: errMsg(e)
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
const preMode = plan.preState ? plan.preState.mode : null;
|
|
111
|
+
const changesets = plan.changesets.map((cs) => ({
|
|
112
|
+
id: cs.id,
|
|
113
|
+
summary: cs.summary,
|
|
114
|
+
releases: cs.releases.filter((r) => r.type !== "none").map((r) => ({
|
|
115
|
+
name: r.name,
|
|
116
|
+
type: r.type
|
|
117
|
+
}))
|
|
118
|
+
}));
|
|
119
|
+
const releasesToRender = plan.releases.filter((r) => r.type !== "none");
|
|
120
|
+
if (releasesToRender.length === 0) return {
|
|
121
|
+
preMode,
|
|
122
|
+
releases: [],
|
|
123
|
+
changesets
|
|
124
|
+
};
|
|
125
|
+
const tempRoot = yield* fs.makeTempDirectoryScoped({ prefix: "silk-preview-" });
|
|
116
126
|
const mapDir = (dir) => {
|
|
117
127
|
const rel = relative(packages.root.dir, dir);
|
|
118
|
-
if (rel.startsWith("..") || isAbsolute(rel))
|
|
119
|
-
|
|
128
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return Effect.fail(new ReleasePlanError({
|
|
129
|
+
phase: "preview",
|
|
130
|
+
reason: `Package directory is outside the workspace root: ${dir}`
|
|
131
|
+
}));
|
|
132
|
+
return Effect.succeed(join(tempRoot, rel));
|
|
120
133
|
};
|
|
134
|
+
const tempDirs = yield* Effect.forEach(packages.packages, (p) => mapDir(p.dir));
|
|
121
135
|
const tempPackages = {
|
|
122
136
|
tool: packages.tool,
|
|
123
137
|
root: {
|
|
@@ -125,26 +139,33 @@ async function previewImpl(root) {
|
|
|
125
139
|
dir: tempRoot,
|
|
126
140
|
packageJson: structuredClone(packages.root.packageJson)
|
|
127
141
|
},
|
|
128
|
-
packages: packages.packages.map((p) => ({
|
|
142
|
+
packages: packages.packages.map((p, i) => ({
|
|
129
143
|
...p,
|
|
130
|
-
dir:
|
|
144
|
+
dir: tempDirs[i],
|
|
131
145
|
packageJson: structuredClone(p.packageJson)
|
|
132
146
|
}))
|
|
133
147
|
};
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
yield* fs.makeDirectory(join(tempRoot, ".changeset"), { recursive: true });
|
|
149
|
+
yield* fs.copyFile(join(root, "package.json"), join(tempRoot, "package.json"));
|
|
136
150
|
const preJson = join(root, ".changeset", "pre.json");
|
|
137
|
-
if (
|
|
138
|
-
for (
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
if (yield* fs.exists(preJson)) yield* fs.copyFile(preJson, join(tempRoot, ".changeset", "pre.json"));
|
|
152
|
+
for (let i = 0; i < packages.packages.length; i++) {
|
|
153
|
+
const p = packages.packages[i];
|
|
154
|
+
const tDir = tempDirs[i];
|
|
155
|
+
yield* fs.makeDirectory(tDir, { recursive: true });
|
|
156
|
+
yield* fs.copyFile(join(p.dir, "package.json"), join(tDir, "package.json"));
|
|
142
157
|
const realCl = join(p.dir, "CHANGELOG.md");
|
|
143
|
-
if (
|
|
158
|
+
if (yield* fs.exists(realCl)) yield* fs.copyFile(realCl, join(tDir, "CHANGELOG.md"));
|
|
144
159
|
}
|
|
145
160
|
const rootCl = join(packages.root.dir, "CHANGELOG.md");
|
|
146
|
-
if (
|
|
147
|
-
|
|
161
|
+
if (yield* fs.exists(rootCl)) yield* fs.copyFile(rootCl, join(tempRoot, "CHANGELOG.md"));
|
|
162
|
+
yield* Effect.tryPromise({
|
|
163
|
+
try: () => applyReleasePlan(plan, tempPackages, config, void 0, root),
|
|
164
|
+
catch: (e) => new ReleasePlanError({
|
|
165
|
+
phase: "preview",
|
|
166
|
+
reason: errMsg(e)
|
|
167
|
+
})
|
|
168
|
+
});
|
|
148
169
|
const dirByName = /* @__PURE__ */ new Map();
|
|
149
170
|
for (const p of tempPackages.packages) dirByName.set(p.packageJson.name, p.dir);
|
|
150
171
|
if (tempPackages.root.packageJson.name) dirByName.set(tempPackages.root.packageJson.name, tempRoot);
|
|
@@ -153,9 +174,15 @@ async function previewImpl(root) {
|
|
|
153
174
|
const dir = dirByName.get(r.name);
|
|
154
175
|
if (!dir) continue;
|
|
155
176
|
const clPath = join(dir, "CHANGELOG.md");
|
|
156
|
-
if (!
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
if (!(yield* fs.exists(clPath))) continue;
|
|
178
|
+
yield* Effect.try({
|
|
179
|
+
try: () => ChangelogTransformer.transformFile(clPath),
|
|
180
|
+
catch: (e) => new ReleasePlanError({
|
|
181
|
+
phase: "preview",
|
|
182
|
+
reason: errMsg(e)
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
const content = yield* fs.readFileString(clPath);
|
|
159
186
|
releases.push({
|
|
160
187
|
name: r.name,
|
|
161
188
|
type: r.type,
|
|
@@ -170,22 +197,17 @@ async function previewImpl(root) {
|
|
|
170
197
|
releases,
|
|
171
198
|
changesets
|
|
172
199
|
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
200
|
+
});
|
|
201
|
+
return Effect.scoped(program).pipe(Effect.mapError((e) => e instanceof ReleasePlanError ? e : new ReleasePlanError({
|
|
202
|
+
phase: "preview",
|
|
203
|
+
reason: errMsg(e)
|
|
204
|
+
})));
|
|
179
205
|
}
|
|
180
|
-
/** Re-read a package's version from disk (post-bump) to feed versionFiles. */
|
|
181
|
-
function diskVersion(workspaceDir, fallback) {
|
|
182
|
-
try
|
|
183
|
-
return JSON.parse(readFileSync(join(workspaceDir, "package.json"), "utf-8")).version ?? fallback;
|
|
184
|
-
} catch {
|
|
185
|
-
return fallback;
|
|
186
|
-
}
|
|
206
|
+
/** Re-read a package's version from disk (post-bump) to feed versionFiles; unreadable/unparseable falls back. */
|
|
207
|
+
function diskVersion(workspaceDir, fallback, fs) {
|
|
208
|
+
return fs.readFileString(join(workspaceDir, "package.json")).pipe(Effect.flatMap((raw) => Effect.try(() => JSON.parse(raw).version ?? fallback)), Effect.orElseSucceed(() => fallback));
|
|
187
209
|
}
|
|
188
|
-
function applyEffect(root, dryRun, inspector) {
|
|
210
|
+
function applyEffect(root, dryRun, inspector, fs) {
|
|
189
211
|
return Effect.gen(function* () {
|
|
190
212
|
const { plan, packages, config } = yield* Effect.tryPromise({
|
|
191
213
|
try: async () => {
|
|
@@ -222,22 +244,24 @@ function applyEffect(root, dryRun, inspector) {
|
|
|
222
244
|
const newVersionByName = new Map(plan.releases.map((r) => [r.name, r.newVersion]));
|
|
223
245
|
const inspected = yield* inspector.inspect(root).pipe(Effect.catchAll((error) => Effect.logWarning(`Skipping versionFiles update: ${errMsg(error)}`).pipe(Effect.as(null))));
|
|
224
246
|
let versionFileUpdates = [];
|
|
225
|
-
if (inspected)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
247
|
+
if (inspected) {
|
|
248
|
+
const candidates = inspected.packages.filter((p) => p.versionFiles.length > 0);
|
|
249
|
+
const freshVersions = yield* Effect.forEach(candidates, (p) => dryRun ? Effect.succeed(newVersionByName.get(p.name) ?? p.version) : diskVersion(p.workspaceDir, p.version, fs));
|
|
250
|
+
const scopes = candidates.map((p, i) => {
|
|
251
|
+
const fresh = freshVersions[i];
|
|
252
|
+
return fresh !== p.version ? {
|
|
253
|
+
...p,
|
|
254
|
+
version: fresh
|
|
255
|
+
} : p;
|
|
256
|
+
});
|
|
257
|
+
versionFileUpdates = yield* Effect.try({
|
|
258
|
+
try: () => scopes.length > 0 ? VersionFiles.processResolvedVersionFiles(scopes, dryRun) : [],
|
|
259
|
+
catch: (e) => new ReleasePlanError({
|
|
260
|
+
phase: "apply",
|
|
261
|
+
reason: errMsg(e)
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
}
|
|
241
265
|
return {
|
|
242
266
|
dryRun,
|
|
243
267
|
touchedFiles,
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { sortDependencyRows } from "./dependency-table.js";
|
|
2
|
+
import { Option } from "effect";
|
|
2
3
|
|
|
3
4
|
//#region src/changesets/utils/dep-diff.ts
|
|
5
|
+
/**
|
|
6
|
+
* Compute per-workspace-package dependency-table rows from two
|
|
7
|
+
* {@link WorkspaceStateSnapshot}s, resolving `catalog:` / `workspace:`
|
|
8
|
+
* specifiers against each side's own catalogs and package versions BEFORE
|
|
9
|
+
* comparing.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Operates on declared dependencies only (the `dependencies` /
|
|
13
|
+
* `devDependencies` / `peerDependencies` / `optionalDependencies` fields
|
|
14
|
+
* of each workspace's `package.json`). Lockfile-only movements
|
|
15
|
+
* (resolved versions changing while declared ranges stay put) are
|
|
16
|
+
* intentionally excluded — those happen on every `pnpm install` and
|
|
17
|
+
* would generate constant noise.
|
|
18
|
+
*
|
|
19
|
+
* Each side carries its own catalogs and package versions, so a specifier
|
|
20
|
+
* is resolved against the snapshot it belongs to: `catalog:silk` resolves
|
|
21
|
+
* to that ref's `silk` catalog entry, `workspace:*` to that ref's target
|
|
22
|
+
* package version. A row is emitted iff the two RESOLVED values differ (or
|
|
23
|
+
* the dependency was added/removed) — a package that merely adopted a
|
|
24
|
+
* `catalog:` specifier without changing the concrete version produces NO
|
|
25
|
+
* row. When a side cannot resolve a specifier (no matching catalog entry,
|
|
26
|
+
* plain range, etc.) it falls back to the raw specifier string.
|
|
27
|
+
*
|
|
28
|
+
* @see {@link DependencyTableRow} for the row schema
|
|
29
|
+
* @see {@link WorkspaceStateSnapshot} for the input shape
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
/** The em-dash sentinel (U+2014) used for added ("from") / removed ("to") cells. */
|
|
4
33
|
const EM_DASH = "—";
|
|
5
34
|
const DEP_TYPE_MAP = [
|
|
6
35
|
["dependencies", "dependency"],
|
|
@@ -8,65 +37,70 @@ const DEP_TYPE_MAP = [
|
|
|
8
37
|
["peerDependencies", "peerDependency"],
|
|
9
38
|
["optionalDependencies", "optionalDependency"]
|
|
10
39
|
];
|
|
11
|
-
function diffOneRecord(before, after, type) {
|
|
12
|
-
const rows = [];
|
|
13
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14
|
-
for (const [name, beforeVersion] of Object.entries(before)) {
|
|
15
|
-
seen.add(name);
|
|
16
|
-
const afterVersion = after[name];
|
|
17
|
-
if (afterVersion === void 0) rows.push({
|
|
18
|
-
dependency: name,
|
|
19
|
-
type,
|
|
20
|
-
action: "removed",
|
|
21
|
-
from: beforeVersion,
|
|
22
|
-
to: EM_DASH
|
|
23
|
-
});
|
|
24
|
-
else if (afterVersion !== beforeVersion) rows.push({
|
|
25
|
-
dependency: name,
|
|
26
|
-
type,
|
|
27
|
-
action: "updated",
|
|
28
|
-
from: beforeVersion,
|
|
29
|
-
to: afterVersion
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
for (const [name, afterVersion] of Object.entries(after)) {
|
|
33
|
-
if (seen.has(name)) continue;
|
|
34
|
-
rows.push({
|
|
35
|
-
dependency: name,
|
|
36
|
-
type,
|
|
37
|
-
action: "added",
|
|
38
|
-
from: EM_DASH,
|
|
39
|
-
to: afterVersion
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
return rows;
|
|
43
|
-
}
|
|
44
40
|
/**
|
|
45
|
-
*
|
|
41
|
+
* Resolve a specifier against the snapshot it belongs to, falling back to
|
|
42
|
+
* the raw specifier string when the snapshot cannot resolve it.
|
|
43
|
+
*/
|
|
44
|
+
const resolveOrRaw = (snapshot, dep, spec) => Option.getOrElse(snapshot.resolve(dep, spec), () => spec);
|
|
45
|
+
/**
|
|
46
|
+
* Diff two workspace snapshots and return per-package dependency-table rows,
|
|
47
|
+
* comparing already-resolved specifier values per side.
|
|
46
48
|
*
|
|
47
|
-
* @param before - Snapshot at the older ref (typically the merge base).
|
|
48
|
-
*
|
|
49
|
-
* declared dep is then reported as `"added"`.
|
|
49
|
+
* @param before - Snapshot at the older ref (typically the merge base). A
|
|
50
|
+
* workspace package absent here reports every declared dep as `"added"`.
|
|
50
51
|
* @param after - Snapshot at the newer ref (typically the working tree).
|
|
51
52
|
* @returns One {@link WorkspaceDependencyDiff} entry per workspace package
|
|
52
|
-
* that has at least one row. Packages with no changes are
|
|
53
|
+
* that has at least one row. Packages with no resolved-value changes are
|
|
54
|
+
* omitted.
|
|
53
55
|
*
|
|
54
56
|
* @public
|
|
55
57
|
*/
|
|
56
|
-
function computeWorkspaceDependencyDiffs(
|
|
57
|
-
const beforeByName = new Map(beforeSnapshots.map((s) => [s.name, s]));
|
|
58
|
+
function computeWorkspaceDependencyDiffs(before, after) {
|
|
58
59
|
const result = [];
|
|
59
|
-
for (const
|
|
60
|
-
const
|
|
60
|
+
for (const afterPkg of after.packages) {
|
|
61
|
+
const beforePkg = Option.getOrNull(before.package(afterPkg.name));
|
|
61
62
|
const rows = [];
|
|
62
63
|
for (const [field, type] of DEP_TYPE_MAP) {
|
|
63
|
-
const beforeRecord =
|
|
64
|
-
const afterRecord =
|
|
65
|
-
|
|
64
|
+
const beforeRecord = beforePkg?.[field] ?? {};
|
|
65
|
+
const afterRecord = afterPkg[field];
|
|
66
|
+
const seen = /* @__PURE__ */ new Set();
|
|
67
|
+
for (const [name, beforeSpec] of Object.entries(beforeRecord)) {
|
|
68
|
+
seen.add(name);
|
|
69
|
+
const from = resolveOrRaw(before, name, beforeSpec);
|
|
70
|
+
const afterSpec = afterRecord[name];
|
|
71
|
+
if (afterSpec === void 0) {
|
|
72
|
+
rows.push({
|
|
73
|
+
dependency: name,
|
|
74
|
+
type,
|
|
75
|
+
action: "removed",
|
|
76
|
+
from,
|
|
77
|
+
to: EM_DASH
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const to = resolveOrRaw(after, name, afterSpec);
|
|
82
|
+
if (from !== to) rows.push({
|
|
83
|
+
dependency: name,
|
|
84
|
+
type,
|
|
85
|
+
action: "updated",
|
|
86
|
+
from,
|
|
87
|
+
to
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const [name, afterSpec] of Object.entries(afterRecord)) {
|
|
91
|
+
if (seen.has(name)) continue;
|
|
92
|
+
rows.push({
|
|
93
|
+
dependency: name,
|
|
94
|
+
type,
|
|
95
|
+
action: "added",
|
|
96
|
+
from: EM_DASH,
|
|
97
|
+
to: resolveOrRaw(after, name, afterSpec)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
66
100
|
}
|
|
67
101
|
if (rows.length > 0) result.push({
|
|
68
|
-
package:
|
|
69
|
-
relativePath:
|
|
102
|
+
package: afterPkg.name,
|
|
103
|
+
relativePath: afterPkg.relativePath,
|
|
70
104
|
rows: sortDependencyRows(rows)
|
|
71
105
|
});
|
|
72
106
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GitError } from "../errors.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
//#region src/changesets/utils/git.ts
|
|
6
|
+
/**
|
|
7
|
+
* Git helpers for the changesets deps regen/detect orchestration.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* `PointInTimeWorkspace` (from `workspaces-effect`) reads both sides of a
|
|
11
|
+
* dependency diff over `CommandExecutor`. The one git operation it does not
|
|
12
|
+
* cover is resolving the default `--from` ref — the merge-base with the base
|
|
13
|
+
* branch — which stays here as a synchronous `execFileSync` shell-out.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Run `git merge-base <base> HEAD`, returning the SHA. Errors propagate
|
|
19
|
+
* as {@link GitError}.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
function gitMergeBase(cwd, base) {
|
|
24
|
+
return Effect.try({
|
|
25
|
+
try: () => execFileSync("git", [
|
|
26
|
+
"merge-base",
|
|
27
|
+
base,
|
|
28
|
+
"HEAD"
|
|
29
|
+
], {
|
|
30
|
+
cwd,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
stdio: [
|
|
33
|
+
"ignore",
|
|
34
|
+
"pipe",
|
|
35
|
+
"pipe"
|
|
36
|
+
]
|
|
37
|
+
}).trim(),
|
|
38
|
+
catch: (error) => {
|
|
39
|
+
const stderr = error.stderr;
|
|
40
|
+
const text = typeof stderr === "string" ? stderr : stderr?.toString() ?? "";
|
|
41
|
+
return new GitError({
|
|
42
|
+
command: `git merge-base ${base} HEAD`,
|
|
43
|
+
cwd,
|
|
44
|
+
reason: text.trim() || (error.message ?? String(error))
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { gitMergeBase };
|
|
@@ -20,16 +20,28 @@ import { PublishabilityDetector } from "workspaces-effect";
|
|
|
20
20
|
* Uses the currently-active {@link SilkPublishability} — wire the
|
|
21
21
|
* {@link SilkPublishabilityDetectorLive} layer to get silk semantics.
|
|
22
22
|
*
|
|
23
|
+
* `root` is passed through verbatim to `detector.detect(pkg, root)` for every
|
|
24
|
+
* package — it must be the project root (the directory containing
|
|
25
|
+
* `.changeset/`), NOT the individual package's directory. The vanilla
|
|
26
|
+
* `PublishabilityDetectorLive` and plain `SilkPublishabilityDetectorLive`
|
|
27
|
+
* both ignore this argument, but the ignore/mode-aware
|
|
28
|
+
* `PublishabilityDetectorAdaptiveLive` reads `.changeset/config.json`
|
|
29
|
+
* relative to it, so passing a package subdirectory silently makes every
|
|
30
|
+
* package resolve to "not publishable". Mirrors
|
|
31
|
+
* {@link SilkPublishability.listPublishable}, which takes the same
|
|
32
|
+
* single-root parameter for the same reason.
|
|
33
|
+
*
|
|
23
34
|
* @param packages - The workspace packages to evaluate
|
|
35
|
+
* @param root - Absolute path to the project root containing `.changeset/`
|
|
24
36
|
* @returns An Effect yielding a `Set` of publishable package names
|
|
25
37
|
*
|
|
26
38
|
* @public
|
|
27
39
|
*/
|
|
28
|
-
function listPublishablePackageNames(packages) {
|
|
40
|
+
function listPublishablePackageNames(packages, root) {
|
|
29
41
|
return Effect.gen(function* () {
|
|
30
42
|
const detector = yield* PublishabilityDetector;
|
|
31
43
|
const names = /* @__PURE__ */ new Set();
|
|
32
|
-
for (const pkg of packages) if ((yield* detector.detect(pkg,
|
|
44
|
+
for (const pkg of packages) if ((yield* detector.detect(pkg, root)).length > 0) names.add(pkg.name);
|
|
33
45
|
return names;
|
|
34
46
|
});
|
|
35
47
|
}
|