@savvy-web/cli 1.3.5 → 1.4.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/cli/index.js +34 -13
- package/commands/changeset/commands/deps-detect.js +22 -30
- package/commands/changeset/commands/deps-regen.js +28 -193
- package/index.d.ts +3 -4
- package/package.json +4 -4
package/cli/index.js
CHANGED
|
@@ -6,9 +6,9 @@ import { initCommand } from "../commands/init.js";
|
|
|
6
6
|
import { lintCommand } from "../commands/lint/index.js";
|
|
7
7
|
import { Command } from "@effect/cli";
|
|
8
8
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
9
|
-
import { BiomeSchemaSyncLive, ChangesetConfigReaderLive, Changesets, ConfigDiscoveryLive, ManagedSectionLive, SilkPublishabilityDetectorLive, ToolDiscoveryLive, VersioningStrategyLive } from "@savvy-web/silk-effects";
|
|
9
|
+
import { BiomeSchemaSyncLive, ChangesetConfigLive, ChangesetConfigReaderLive, Changesets, ConfigDiscoveryLive, ManagedSectionLive, SilkPublishabilityDetectorLive, ToolDiscoveryLive, VersioningStrategyLive } from "@savvy-web/silk-effects";
|
|
10
10
|
import { Effect, Layer } from "effect";
|
|
11
|
-
import { PackageManagerDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
|
|
11
|
+
import { PackageManagerDetectorLive, PointInTimeWorkspaceLive, PublishabilityDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
|
|
12
12
|
|
|
13
13
|
//#region src/cli/index.ts
|
|
14
14
|
/**
|
|
@@ -34,10 +34,9 @@ import { PackageManagerDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive }
|
|
|
34
34
|
* `ConfigDiscoveryLive`, `ToolDiscoveryLive`, and `VersioningStrategyLive`
|
|
35
35
|
* (provided `ChangesetConfigReaderLive`).
|
|
36
36
|
* - Changesets-namespace services — `Changesets.ConfigInspectorLive` (provided
|
|
37
|
-
* `ChangesetConfigReaderLive`), `Changesets.
|
|
38
|
-
* `
|
|
39
|
-
* `
|
|
40
|
-
* instance built once via `provideMerge`.
|
|
37
|
+
* `ChangesetConfigReaderLive`), `Changesets.ReleasePlannerLive` (provided
|
|
38
|
+
* `ConfigInspectorLive`), and `Changesets.BranchAnalyzerLive`, which shares
|
|
39
|
+
* the single `ConfigInspectorLive` instance built once via `provideMerge`.
|
|
41
40
|
*
|
|
42
41
|
* The CLI version is injected at build time via `process.env.__PACKAGE_VERSION__`.
|
|
43
42
|
*
|
|
@@ -57,7 +56,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
|
|
|
57
56
|
]));
|
|
58
57
|
const cli = Command.run(rootCommand, {
|
|
59
58
|
name: "savvy",
|
|
60
|
-
version: "1.
|
|
59
|
+
version: "1.4.0"
|
|
61
60
|
});
|
|
62
61
|
/**
|
|
63
62
|
* Shared base layer: workspace services, the changeset config reader, and the
|
|
@@ -77,12 +76,12 @@ const cli = Command.run(rootCommand, {
|
|
|
77
76
|
const WorkspaceLive = Layer.mergeAll(WorkspaceRootLive, PackageManagerDetectorLive, WorkspaceDiscoveryLive.pipe(Layer.provide(WorkspaceRootLive)));
|
|
78
77
|
/**
|
|
79
78
|
* Base layer membership: silk-effects leaf services (`ManagedSection`,
|
|
80
|
-
* `BiomeSchemaSync`, `ConfigDiscovery`, `SilkPublishabilityDetector
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* `BiomeSchemaSync`, `ConfigDiscovery`, `SilkPublishabilityDetector`) that
|
|
80
|
+
* depend only on the platform, plus the changeset base layers
|
|
81
|
+
* (`WorkspaceLive`, `ChangesetConfigReader`) that `AppLive`'s upper services
|
|
82
|
+
* build upon.
|
|
84
83
|
*/
|
|
85
|
-
const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, ManagedSectionLive, BiomeSchemaSyncLive, ConfigDiscoveryLive, SilkPublishabilityDetectorLive
|
|
84
|
+
const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, ManagedSectionLive, BiomeSchemaSyncLive, ConfigDiscoveryLive, SilkPublishabilityDetectorLive);
|
|
86
85
|
/**
|
|
87
86
|
* Merged runtime Layer stack — the union of the three source CLIs' stacks with
|
|
88
87
|
* every inter-layer dependency satisfied.
|
|
@@ -102,12 +101,34 @@ const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, Manage
|
|
|
102
101
|
* `BranchAnalyzerLive` AND re-exposes it for the surviving `config validate`
|
|
103
102
|
* handler that yields it directly, so it is never constructed twice per run.
|
|
104
103
|
*
|
|
104
|
+
* `Changesets.DepsRegenLive` (the `deps regen`/`deps detect` orchestration
|
|
105
|
+
* service) needs `ConfigInspector`, `WorkspaceDiscovery` (from
|
|
106
|
+
* `BaseLive`/`InspectorAndAnalyzerLive`), plus `PointInTimeWorkspace` and
|
|
107
|
+
* `PublishabilityDetector` from `workspaces-effect`, and `ChangesetConfig`
|
|
108
|
+
* (provided its own `ChangesetConfigReaderLive`, since `Layer.mergeAll` does
|
|
109
|
+
* not cross-feed sibling layers) — none of which the other commands need, so
|
|
110
|
+
* they're composed only for `DepsRegenGroupLive`. `PointInTimeWorkspaceLive`
|
|
111
|
+
* in turn needs `WorkspaceRoot`, `WorkspaceDiscovery`, `CommandExecutor`,
|
|
112
|
+
* `FileSystem`, `Path` — the workspace pair supplied by `WorkspaceLive`, the
|
|
113
|
+
* platform trio by `NodeContext.layer` below.
|
|
114
|
+
* `DepsRegenGroupLive` reuses the exact `InspectorAndAnalyzerLive` layer
|
|
115
|
+
* reference (not a fresh copy), so Effect's layer memoization builds that
|
|
116
|
+
* single `ConfigInspector` instance once and shares it here too.
|
|
117
|
+
*
|
|
105
118
|
* `provideMerge(BaseLive)` feeds the remaining deps and re-exposes the base
|
|
106
119
|
* services for handlers that yield them directly. `provideMerge(NodeContext.layer)`
|
|
107
120
|
* supplies `FileSystem`, `Path`, and `CommandExecutor` to everything underneath.
|
|
108
121
|
*/
|
|
109
122
|
const InspectorAndAnalyzerLive = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ReleasePlannerLive), Layer.provideMerge(Changesets.ConfigInspectorLive));
|
|
110
|
-
|
|
123
|
+
/**
|
|
124
|
+
* `Changesets.DepsRegen`, fully composed except for the base services
|
|
125
|
+
* (`WorkspaceDiscovery`, platform layers) supplied by `BaseLive` /
|
|
126
|
+
* `NodeContext.layer`. `PointInTimeWorkspaceLive` is provided `WorkspaceLive`
|
|
127
|
+
* so its `WorkspaceRoot`/`WorkspaceDiscovery` requirements resolve; the
|
|
128
|
+
* remaining `CommandExecutor`/`FileSystem`/`Path` flow up to `NodeContext.layer`.
|
|
129
|
+
*/
|
|
130
|
+
const DepsRegenGroupLive = Changesets.DepsRegenLive.pipe(Layer.provide(InspectorAndAnalyzerLive), Layer.provide(PointInTimeWorkspaceLive.pipe(Layer.provide(WorkspaceLive))), Layer.provide(PublishabilityDetectorLive), Layer.provide(ChangesetConfigLive.pipe(Layer.provide(ChangesetConfigReaderLive))));
|
|
131
|
+
const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive, DepsRegenGroupLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeContext.layer));
|
|
111
132
|
/**
|
|
112
133
|
* Bootstrap and run the `savvy` CLI application.
|
|
113
134
|
*
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import { Command, Options } from "@effect/cli";
|
|
2
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
3
3
|
import { Console, Effect, Option } from "effect";
|
|
4
|
-
import { WorkspaceDiscovery } from "workspaces-effect";
|
|
5
|
-
import { resolve } from "node:path";
|
|
6
4
|
|
|
7
5
|
//#region src/commands/changeset/commands/deps-detect.ts
|
|
8
6
|
/**
|
|
9
7
|
* `deps detect` command — read-only dependency-diff inspection.
|
|
10
8
|
*
|
|
11
9
|
* @remarks
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* A thin adapter over {@link Changesets.DepsRegen}: this command calls
|
|
11
|
+
* `plan({ includeDevDeps: true, ... })` — the read-only path, so devDeps
|
|
12
|
+
* stay in the diff and no file is written or deleted — then renders
|
|
13
|
+
* `plan.toWrite`'s diffs either as structured JSON (one row per change) or
|
|
14
|
+
* as ready-to-paste CSH005 markdown. No file writes.
|
|
15
15
|
*
|
|
16
16
|
* Defaults:
|
|
17
17
|
* - `--from` → `git merge-base <baseBranch> HEAD`
|
|
18
18
|
* - `--to` → working tree (i.e., `HEAD` plus staged + unstaged + untracked).
|
|
19
|
-
* Passed as the special value
|
|
20
|
-
* `WORKTREE` to {@link WorkspaceSnapshotReader} — implementations resolve
|
|
21
|
-
* this against the live working tree rather than `git show`.
|
|
22
19
|
*
|
|
23
20
|
* @example
|
|
24
21
|
* ```bash
|
|
@@ -29,7 +26,7 @@ import { resolve } from "node:path";
|
|
|
29
26
|
*
|
|
30
27
|
* @internal
|
|
31
28
|
*/
|
|
32
|
-
const {
|
|
29
|
+
const { DepsRegen, serializeDependencyTableToMarkdown } = Changesets;
|
|
33
30
|
/* v8 ignore start -- CLI option definitions */
|
|
34
31
|
const fromOption = Options.text("from").pipe(Options.withDescription("Older ref to diff from (defaults to merge-base with base branch)"), Options.optional);
|
|
35
32
|
const toOption = Options.text("to").pipe(Options.withDescription("Newer ref to diff to (defaults to working tree)"), Options.optional);
|
|
@@ -61,27 +58,22 @@ function renderMarkdownBlocks(diffs) {
|
|
|
61
58
|
*/
|
|
62
59
|
function runDepsDetect(cwd, from, to, pkg, json, markdown) {
|
|
63
60
|
return Effect.gen(function* () {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (targetPkg) diffs = diffs.filter((d) => d.package === targetPkg);
|
|
81
|
-
else {
|
|
82
|
-
const publishable = yield* listPublishablePackageNames(yield* (yield* WorkspaceDiscovery).listPackages(resolvedCwd).pipe(Effect.catchAll(() => Effect.succeed([]))));
|
|
83
|
-
diffs = diffs.filter((d) => publishable.has(d.package));
|
|
84
|
-
}
|
|
61
|
+
const diffs = (yield* (yield* DepsRegen).plan({
|
|
62
|
+
cwd,
|
|
63
|
+
includeDevDeps: true,
|
|
64
|
+
...Option.isSome(pkg) ? { package: pkg.value } : {},
|
|
65
|
+
...Option.isSome(from) ? { from: from.value } : {},
|
|
66
|
+
...Option.isSome(to) ? { to: to.value } : {}
|
|
67
|
+
}).pipe(Effect.catchTags({
|
|
68
|
+
GitError: (err) => {
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return Effect.fail(err);
|
|
71
|
+
},
|
|
72
|
+
GitReadError: (err) => {
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return Effect.fail(err);
|
|
75
|
+
}
|
|
76
|
+
}))).toWrite.map((entry) => entry.diff);
|
|
85
77
|
if (markdown && !json) {
|
|
86
78
|
yield* Effect.log(renderMarkdownBlocks(diffs));
|
|
87
79
|
return;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Command, Options } from "@effect/cli";
|
|
2
2
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
3
3
|
import { Console, Effect, Option } from "effect";
|
|
4
|
-
import { WorkspaceDiscovery } from "workspaces-effect";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
4
|
|
|
8
5
|
//#region src/commands/changeset/commands/deps-regen.ts
|
|
9
6
|
/**
|
|
@@ -12,7 +9,14 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
|
|
|
12
9
|
* cumulative dep diff from base to working tree.
|
|
13
10
|
*
|
|
14
11
|
* @remarks
|
|
15
|
-
*
|
|
12
|
+
* This is a thin adapter over {@link Changesets.DepsRegen}: the plan/execute
|
|
13
|
+
* orchestration (pure-dependency-changeset detection, protocol-specifier
|
|
14
|
+
* resolution, filename generation, and the filesystem writes themselves)
|
|
15
|
+
* lives in `@savvy-web/silk-effects`. This command only translates CLI
|
|
16
|
+
* options into a `plan()` call, conditionally applies the plan via
|
|
17
|
+
* `execute()`, and renders the resulting {@link RegenPlan}.
|
|
18
|
+
*
|
|
19
|
+
* **The single-package-per-changeset rule.** The service enforces our
|
|
16
20
|
* convention that each `.changeset/*.md` file lists exactly one package
|
|
17
21
|
* in its frontmatter. `@changesets/cli` technically supports multi-package
|
|
18
22
|
* frontmatter, but our agent (and this command) always produces single-
|
|
@@ -28,18 +32,9 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
|
|
|
28
32
|
* Anything else (multi-package frontmatter, additional sections, comments,
|
|
29
33
|
* `### Sub-headings`, etc.) is treated as "mixed" and left untouched.
|
|
30
34
|
* That's the safe default — if a human authored something idiosyncratic,
|
|
31
|
-
* we don't clobber it.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* 1. Compute dep diff from `merge-base(baseBranch, HEAD)` to working
|
|
36
|
-
* tree, grouped by workspace package.
|
|
37
|
-
* 2. Find every pure-dependency changeset (strict definition).
|
|
38
|
-
* 3. Delete every one of them — even those for packages with no current
|
|
39
|
-
* dep changes (their changeset is stale by definition).
|
|
40
|
-
* 4. Write a fresh `<adjective>-<noun>-<verb>.md` per workspace package
|
|
41
|
-
* that has current dep changes: single-package frontmatter, `patch`
|
|
42
|
-
* bump, one `## Dependencies` section, one CSH005 table.
|
|
35
|
+
* we don't clobber it. `devDependency` rows are dropped by the service
|
|
36
|
+
* (the regen default) and protocol specifiers (`catalog:`/`workspace:`)
|
|
37
|
+
* are resolved to concrete versions.
|
|
43
38
|
*
|
|
44
39
|
* @example
|
|
45
40
|
* ```bash
|
|
@@ -50,7 +45,7 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
|
|
|
50
45
|
*
|
|
51
46
|
* @internal
|
|
52
47
|
*/
|
|
53
|
-
const {
|
|
48
|
+
const { DepsRegen } = Changesets;
|
|
54
49
|
/* v8 ignore start -- CLI option definitions */
|
|
55
50
|
const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
|
|
56
51
|
const baseOption = Options.text("base").pipe(Options.withDescription("Override the base branch (defaults to config baseBranch)"), Options.optional);
|
|
@@ -58,141 +53,6 @@ const packageOption = Options.text("package").pipe(Options.withDescription("Rest
|
|
|
58
53
|
const dryRunOption = Options.boolean("dry-run").pipe(Options.withDescription("Print the plan without writing or deleting"), Options.withDefault(false));
|
|
59
54
|
const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit a structured plan as JSON"), Options.withDefault(false));
|
|
60
55
|
/* v8 ignore stop */
|
|
61
|
-
const ADJECTIVES = [
|
|
62
|
-
"brave",
|
|
63
|
-
"clever",
|
|
64
|
-
"swift",
|
|
65
|
-
"silver",
|
|
66
|
-
"lucky",
|
|
67
|
-
"happy",
|
|
68
|
-
"calm",
|
|
69
|
-
"bright",
|
|
70
|
-
"quiet",
|
|
71
|
-
"wild"
|
|
72
|
-
];
|
|
73
|
-
const NOUNS = [
|
|
74
|
-
"dogs",
|
|
75
|
-
"cats",
|
|
76
|
-
"wolves",
|
|
77
|
-
"foxes",
|
|
78
|
-
"cups",
|
|
79
|
-
"ships",
|
|
80
|
-
"trees",
|
|
81
|
-
"owls",
|
|
82
|
-
"cranes",
|
|
83
|
-
"hills"
|
|
84
|
-
];
|
|
85
|
-
const VERBS = [
|
|
86
|
-
"laugh",
|
|
87
|
-
"dream",
|
|
88
|
-
"fly",
|
|
89
|
-
"sing",
|
|
90
|
-
"dance",
|
|
91
|
-
"wander",
|
|
92
|
-
"soar",
|
|
93
|
-
"rest",
|
|
94
|
-
"leap",
|
|
95
|
-
"ponder"
|
|
96
|
-
];
|
|
97
|
-
function pickRandomTriplet() {
|
|
98
|
-
return `${ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]}-${NOUNS[Math.floor(Math.random() * NOUNS.length)]}-${VERBS[Math.floor(Math.random() * VERBS.length)]}`;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Pick a `<adjective>-<noun>-<verb>` filename slug that does not collide
|
|
102
|
-
* with an existing `.changeset/*.md`. The triplet space is 1,000
|
|
103
|
-
* combinations, so a busy repo can plausibly exhaust it across runs;
|
|
104
|
-
* fall back to a timestamp suffix after 20 unlucky picks.
|
|
105
|
-
*
|
|
106
|
-
* @internal
|
|
107
|
-
*/
|
|
108
|
-
function randomFilename(changesetDir) {
|
|
109
|
-
for (let i = 0; i < 20; i++) {
|
|
110
|
-
const candidate = pickRandomTriplet();
|
|
111
|
-
if (!existsSync(join(changesetDir, `${candidate}.md`))) return candidate;
|
|
112
|
-
}
|
|
113
|
-
return `${pickRandomTriplet()}-${Date.now()}`;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Strict detection of "pure dependency changesets" per the documented
|
|
117
|
-
* rules: single-package frontmatter, single `## Dependencies` heading,
|
|
118
|
-
* no other body content beyond that section.
|
|
119
|
-
*
|
|
120
|
-
* @internal
|
|
121
|
-
*/
|
|
122
|
-
function isPureDependencyChangeset(content) {
|
|
123
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
124
|
-
if (!fmMatch) return {
|
|
125
|
-
isPure: false,
|
|
126
|
-
package: null
|
|
127
|
-
};
|
|
128
|
-
const frontmatter = fmMatch[1];
|
|
129
|
-
const body = (fmMatch[2] ?? "").trim();
|
|
130
|
-
const fmLines = frontmatter.split(/\r?\n/).filter((l) => l.trim().length > 0 && !/^\s*#/.test(l));
|
|
131
|
-
if (fmLines.length !== 1) return {
|
|
132
|
-
isPure: false,
|
|
133
|
-
package: null
|
|
134
|
-
};
|
|
135
|
-
const pkgMatch = fmLines[0].match(/^\s*["']?([^"':\s]+)["']?\s*:\s*([a-z]+)\s*$/);
|
|
136
|
-
if (!pkgMatch) return {
|
|
137
|
-
isPure: false,
|
|
138
|
-
package: null
|
|
139
|
-
};
|
|
140
|
-
const pkg = pkgMatch[1];
|
|
141
|
-
const bodyTrimmed = body.replace(/^\s+/, "");
|
|
142
|
-
if (!/^## Dependencies\b/.test(bodyTrimmed)) return {
|
|
143
|
-
isPure: false,
|
|
144
|
-
package: null
|
|
145
|
-
};
|
|
146
|
-
if ((bodyTrimmed.match(/^## /gm) ?? []).length !== 1) return {
|
|
147
|
-
isPure: false,
|
|
148
|
-
package: null
|
|
149
|
-
};
|
|
150
|
-
if (/^# /m.test(bodyTrimmed)) return {
|
|
151
|
-
isPure: false,
|
|
152
|
-
package: null
|
|
153
|
-
};
|
|
154
|
-
return {
|
|
155
|
-
isPure: true,
|
|
156
|
-
package: pkg
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function listChangesetFiles(changesetDir) {
|
|
160
|
-
if (!existsSync(changesetDir)) return [];
|
|
161
|
-
return readdirSync(changesetDir).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => join(changesetDir, f));
|
|
162
|
-
}
|
|
163
|
-
function findPureDependencyChangesets(changesetDir) {
|
|
164
|
-
const result = [];
|
|
165
|
-
for (const file of listChangesetFiles(changesetDir)) {
|
|
166
|
-
let content;
|
|
167
|
-
try {
|
|
168
|
-
content = readFileSync(file, "utf8");
|
|
169
|
-
} catch {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const detection = isPureDependencyChangeset(content);
|
|
173
|
-
if (detection.isPure && detection.package) result.push({
|
|
174
|
-
file,
|
|
175
|
-
package: detection.package
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return result;
|
|
179
|
-
}
|
|
180
|
-
function findMixedDependencyChangesets(changesetDir) {
|
|
181
|
-
const result = [];
|
|
182
|
-
for (const file of listChangesetFiles(changesetDir)) {
|
|
183
|
-
let content;
|
|
184
|
-
try {
|
|
185
|
-
content = readFileSync(file, "utf8");
|
|
186
|
-
} catch {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (/^## Dependencies\b/m.test(content) && !isPureDependencyChangeset(content).isPure) result.push(file);
|
|
190
|
-
}
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
function renderChangesetContent(diff) {
|
|
194
|
-
return `${`---\n"${diff.package}": patch\n---`}\n\n## Dependencies\n\n${serializeDependencyTableToMarkdown([...diff.rows])}\n`;
|
|
195
|
-
}
|
|
196
56
|
/**
|
|
197
57
|
* Handler exported for direct invocation in tests.
|
|
198
58
|
*
|
|
@@ -200,47 +60,22 @@ function renderChangesetContent(diff) {
|
|
|
200
60
|
*/
|
|
201
61
|
function runDepsRegen(cwd, base, pkg, dryRun, json) {
|
|
202
62
|
return Effect.gen(function* () {
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const publishable = yield* listPublishablePackageNames(yield* (yield* WorkspaceDiscovery).listPackages(resolvedCwd).pipe(Effect.catchAll(() => Effect.succeed([]))));
|
|
218
|
-
if (targetPkg) diffs = diffs.filter((d) => d.package === targetPkg);
|
|
219
|
-
else diffs = diffs.filter((d) => publishable.has(d.package));
|
|
220
|
-
const existingPure = findPureDependencyChangesets(changesetDir);
|
|
221
|
-
const skippedMixed = findMixedDependencyChangesets(changesetDir);
|
|
222
|
-
const toDelete = targetPkg ? existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => publishable.has(p.package));
|
|
223
|
-
const toWrite = diffs.map((diff) => ({
|
|
224
|
-
file: join(changesetDir, `${randomFilename(changesetDir)}.md`),
|
|
225
|
-
package: diff.package,
|
|
226
|
-
diff
|
|
63
|
+
const service = yield* DepsRegen;
|
|
64
|
+
const plan = yield* service.plan({
|
|
65
|
+
cwd,
|
|
66
|
+
...Option.isSome(base) ? { base: base.value } : {},
|
|
67
|
+
...Option.isSome(pkg) ? { package: pkg.value } : {}
|
|
68
|
+
}).pipe(Effect.catchTags({
|
|
69
|
+
GitError: (err) => {
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return Effect.fail(err);
|
|
72
|
+
},
|
|
73
|
+
GitReadError: (err) => {
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return Effect.fail(err);
|
|
76
|
+
}
|
|
227
77
|
}));
|
|
228
|
-
|
|
229
|
-
toDelete,
|
|
230
|
-
toWrite,
|
|
231
|
-
skippedMixed
|
|
232
|
-
};
|
|
233
|
-
if (dryRun) {
|
|
234
|
-
if (json) yield* Console.log(JSON.stringify(plan, null, 2));
|
|
235
|
-
else yield* renderHumanPlan(plan);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
for (const entry of toDelete) try {
|
|
239
|
-
unlinkSync(entry.file);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
yield* Effect.logWarning(`Failed to delete ${entry.file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
|
-
}
|
|
243
|
-
for (const entry of toWrite) writeFileSync(entry.file, renderChangesetContent(entry.diff));
|
|
78
|
+
if (!dryRun) yield* service.execute(plan);
|
|
244
79
|
if (json) yield* Console.log(JSON.stringify(plan, null, 2));
|
|
245
80
|
else yield* renderHumanPlan(plan);
|
|
246
81
|
});
|
|
@@ -274,4 +109,4 @@ const depsRegenCommand = Command.make("regen", {
|
|
|
274
109
|
}, ({ cwd, base, package: pkg, dryRun, json }) => runDepsRegen(cwd, base, pkg, dryRun, json)).pipe(Command.withDescription("Delete pure dependency changesets and regenerate them from the current diff"));
|
|
275
110
|
|
|
276
111
|
//#endregion
|
|
277
|
-
export { depsRegenCommand,
|
|
112
|
+
export { depsRegenCommand, runDepsRegen };
|
package/index.d.ts
CHANGED
|
@@ -30,10 +30,9 @@ import { JsoncParseError } from "jsonc-effect";
|
|
|
30
30
|
* `ConfigDiscoveryLive`, `ToolDiscoveryLive`, and `VersioningStrategyLive`
|
|
31
31
|
* (provided `ChangesetConfigReaderLive`).
|
|
32
32
|
* - Changesets-namespace services — `Changesets.ConfigInspectorLive` (provided
|
|
33
|
-
* `ChangesetConfigReaderLive`), `Changesets.
|
|
34
|
-
* `
|
|
35
|
-
* `
|
|
36
|
-
* instance built once via `provideMerge`.
|
|
33
|
+
* `ChangesetConfigReaderLive`), `Changesets.ReleasePlannerLive` (provided
|
|
34
|
+
* `ConfigInspectorLive`), and `Changesets.BranchAnalyzerLive`, which shares
|
|
35
|
+
* the single `ConfigInspectorLive` instance built once via `provideMerge`.
|
|
37
36
|
*
|
|
38
37
|
* The CLI version is injected at build time via `process.env.__PACKAGE_VERSION__`.
|
|
39
38
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The savvy CLI — unified commit, changeset, and lint commands for the Silk Suite",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/cli",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"@effect/platform-node": "^0.107.0",
|
|
37
37
|
"@effect/rpc": "^0.75.1",
|
|
38
38
|
"@effect/sql": "^0.51.1",
|
|
39
|
-
"@savvy-web/silk-effects": "
|
|
39
|
+
"@savvy-web/silk-effects": "2.0.0",
|
|
40
40
|
"effect": "^3.21.4",
|
|
41
|
-
"jsonc-effect": "^0.
|
|
42
|
-
"workspaces-effect": "^
|
|
41
|
+
"jsonc-effect": "^0.3.0",
|
|
42
|
+
"workspaces-effect": "^2.0.0",
|
|
43
43
|
"yaml": "^2.9.0"
|
|
44
44
|
}
|
|
45
45
|
}
|