@savvy-web/cli 1.1.2 → 1.3.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 +9 -7
- package/commands/changeset/commands/version.js +18 -151
- package/commands/clean.js +1 -1
- package/index.d.ts +2 -1
- package/package.json +4 -4
- package/bin/savvy.d.ts +0 -1
package/cli/index.js
CHANGED
|
@@ -34,7 +34,8 @@ 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.WorkspaceSnapshotReaderLive`,
|
|
37
|
+
* `ChangesetConfigReaderLive`), `Changesets.WorkspaceSnapshotReaderLive`,
|
|
38
|
+
* `Changesets.ReleasePlannerLive` (provided `ConfigInspectorLive`), and
|
|
38
39
|
* `Changesets.BranchAnalyzerLive`, which shares the single `ConfigInspectorLive`
|
|
39
40
|
* instance built once via `provideMerge`.
|
|
40
41
|
*
|
|
@@ -56,7 +57,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
|
|
|
56
57
|
]));
|
|
57
58
|
const cli = Command.run(rootCommand, {
|
|
58
59
|
name: "savvy",
|
|
59
|
-
version: "1.
|
|
60
|
+
version: "1.3.0"
|
|
60
61
|
});
|
|
61
62
|
/**
|
|
62
63
|
* Shared base layer: workspace services, the changeset config reader, and the
|
|
@@ -93,18 +94,19 @@ const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, Manage
|
|
|
93
94
|
* `Changesets.ConfigInspectorLive` needs `ChangesetConfigReader`,
|
|
94
95
|
* `WorkspaceDiscovery`, and `FileSystem` (the last for its publishConfig-driven
|
|
95
96
|
* fallback when no explicit `packages` record is configured);
|
|
96
|
-
* `Changesets.BranchAnalyzerLive` needs `ConfigInspector
|
|
97
|
+
* `Changesets.BranchAnalyzerLive` needs `ConfigInspector`;
|
|
98
|
+
* `Changesets.ReleasePlannerLive` needs `ConfigInspector`.
|
|
97
99
|
*
|
|
98
100
|
* `ConfigInspectorLive` is built once via {@link Layer.provideMerge}: the merge
|
|
99
|
-
* feeds that single `ConfigInspector` instance into `
|
|
100
|
-
* re-exposes it for the surviving `config validate`
|
|
101
|
-
* directly, so it is never constructed twice per run.
|
|
101
|
+
* feeds that single `ConfigInspector` instance into `ReleasePlannerLive` and
|
|
102
|
+
* `BranchAnalyzerLive` AND re-exposes it for the surviving `config validate`
|
|
103
|
+
* handler that yields it directly, so it is never constructed twice per run.
|
|
102
104
|
*
|
|
103
105
|
* `provideMerge(BaseLive)` feeds the remaining deps and re-exposes the base
|
|
104
106
|
* services for handlers that yield them directly. `provideMerge(NodeContext.layer)`
|
|
105
107
|
* supplies `FileSystem`, `Path`, and `CommandExecutor` to everything underneath.
|
|
106
108
|
*/
|
|
107
|
-
const InspectorAndAnalyzerLive = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ConfigInspectorLive));
|
|
109
|
+
const InspectorAndAnalyzerLive = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ReleasePlannerLive), Layer.provideMerge(Changesets.ConfigInspectorLive));
|
|
108
110
|
const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeContext.layer));
|
|
109
111
|
/**
|
|
110
112
|
* Bootstrap and run the `savvy` CLI application.
|
|
@@ -2,177 +2,44 @@ import { requireValidConfig } from "../utils/config-gate.js";
|
|
|
2
2
|
import { Command, Options } from "@effect/cli";
|
|
3
3
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
|
-
import { PackageManagerDetector, WorkspaceDiscovery } from "workspaces-effect";
|
|
6
|
-
import { join, resolve } from "node:path";
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
9
5
|
|
|
10
6
|
//#region src/commands/changeset/commands/version.ts
|
|
11
7
|
/**
|
|
12
|
-
* Version command --
|
|
8
|
+
* Version command -- natively apply pending changesets and report the result.
|
|
13
9
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @remarks
|
|
19
|
-
* The command performs six steps:
|
|
20
|
-
* 1. Detect the package manager (`pnpm`, `npm`, `yarn`, `bun`) via
|
|
21
|
-
* `PackageManagerDetector` from `workspaces-effect`.
|
|
22
|
-
* 2. **Require a valid `.changeset/config.json` via {@link ConfigInspector}**.
|
|
23
|
-
* If the config has overlap conflicts, unknown package keys, dual-shape,
|
|
24
|
-
* or schema errors, refuse to run and exit non-zero — a broken config
|
|
25
|
-
* means we cannot determine the right versions to write.
|
|
26
|
-
* 3. Run `changeset version` (skipped with `--dry-run`).
|
|
27
|
-
* 4. Discover all CHANGELOG.md files across workspace packages via
|
|
28
|
-
* `WorkspaceDiscovery` from `workspaces-effect`.
|
|
29
|
-
* 5. Transform each discovered changelog with
|
|
30
|
-
* {@link ChangelogTransformer.transformFile}.
|
|
31
|
-
* 6. Update version files using the **resolved** {@link InspectedConfig}
|
|
32
|
-
* via {@link VersionFiles.processResolvedVersionFiles}.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```bash
|
|
36
|
-
* savvy changeset version
|
|
37
|
-
* savvy changeset version --dry-run
|
|
38
|
-
* ```
|
|
10
|
+
* Validates the config, then runs {@link ReleasePlanner.apply}, which bumps
|
|
11
|
+
* versions, writes + transforms CHANGELOGs, deletes consumed changesets, and
|
|
12
|
+
* updates configured versionFiles -- all without shelling out to a `changeset`
|
|
13
|
+
* CLI binary.
|
|
39
14
|
*
|
|
40
15
|
* @internal
|
|
41
16
|
*/
|
|
42
|
-
const { ChangelogTransformer, ConfigInspector, VersionFileError, VersionFiles } = Changesets;
|
|
43
|
-
/**
|
|
44
|
-
* Map package manager to the correct `changeset version` shell command.
|
|
45
|
-
*
|
|
46
|
-
* @internal
|
|
47
|
-
*/
|
|
48
|
-
function getChangesetVersionCommand(pm) {
|
|
49
|
-
switch (pm) {
|
|
50
|
-
case "pnpm": return "pnpm exec changeset version";
|
|
51
|
-
case "yarn": return "yarn exec changeset version";
|
|
52
|
-
case "bun": return "bun x changeset version";
|
|
53
|
-
default: return "npx changeset version";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
17
|
/* v8 ignore start -- CLI option definitions; handler tested via runVersion */
|
|
57
|
-
const dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDescription("
|
|
18
|
+
const dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDescription("Compute and report the release without writing anything"), Options.withDefault(false));
|
|
58
19
|
/* v8 ignore stop */
|
|
59
20
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* Detects the package manager, validates the config, optionally runs
|
|
63
|
-
* `changeset version`, discovers and transforms all workspace changelogs,
|
|
64
|
-
* and updates version files using the resolved per-package scopes.
|
|
65
|
-
*
|
|
66
|
-
* @param dryRun - When `true`, skip `changeset version` and only transform
|
|
67
|
-
* existing CHANGELOG files
|
|
68
|
-
* @returns An Effect that performs the versioning pipeline
|
|
21
|
+
* Validate config, then natively apply (or dry-run) the release via
|
|
22
|
+
* {@link ReleasePlanner}.
|
|
69
23
|
*
|
|
70
|
-
* @
|
|
24
|
+
* @param dryRun - When `true`, write nothing; only report planned changes.
|
|
71
25
|
*/
|
|
72
26
|
function runVersion(dryRun) {
|
|
73
27
|
return Effect.gen(function* () {
|
|
74
28
|
const cwd = process.cwd();
|
|
75
|
-
const pm = (yield* (yield* PackageManagerDetector).detect(cwd).pipe(Effect.catchAll(() => Effect.succeed({
|
|
76
|
-
type: "npm",
|
|
77
|
-
version: void 0
|
|
78
|
-
})))).type;
|
|
79
|
-
yield* Effect.log(`Detected package manager: ${pm}`);
|
|
80
29
|
yield* requireValidConfig(cwd);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
yield* Effect.log(
|
|
84
|
-
|
|
85
|
-
try: () => execSync(cmd, {
|
|
86
|
-
cwd,
|
|
87
|
-
stdio: "inherit"
|
|
88
|
-
}),
|
|
89
|
-
catch: (error) => /* @__PURE__ */ new Error(`changeset version failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
90
|
-
});
|
|
91
|
-
} else yield* Effect.log("Dry run: skipping changeset version");
|
|
92
|
-
const packages = yield* (yield* WorkspaceDiscovery).listPackages().pipe(Effect.catchAll(() => Effect.succeed([])));
|
|
93
|
-
const changelogs = [];
|
|
94
|
-
const seen = /* @__PURE__ */ new Set();
|
|
95
|
-
const resolvedCwd = resolve(cwd);
|
|
96
|
-
for (const pkg of packages) {
|
|
97
|
-
const changelogPath = join(pkg.path, "CHANGELOG.md");
|
|
98
|
-
if (existsSync(changelogPath) && !seen.has(pkg.path)) {
|
|
99
|
-
seen.add(pkg.path);
|
|
100
|
-
changelogs.push({
|
|
101
|
-
name: pkg.name,
|
|
102
|
-
path: pkg.path,
|
|
103
|
-
changelogPath
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!seen.has(resolvedCwd)) {
|
|
108
|
-
const rootChangelog = join(resolvedCwd, "CHANGELOG.md");
|
|
109
|
-
if (existsSync(rootChangelog)) {
|
|
110
|
-
let rootName = "root";
|
|
111
|
-
try {
|
|
112
|
-
const pkg = JSON.parse(readFileSync(join(resolvedCwd, "package.json"), "utf-8"));
|
|
113
|
-
if (pkg.name) rootName = pkg.name;
|
|
114
|
-
} catch {}
|
|
115
|
-
changelogs.push({
|
|
116
|
-
name: rootName,
|
|
117
|
-
path: resolvedCwd,
|
|
118
|
-
changelogPath: rootChangelog
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (changelogs.length === 0) yield* Effect.log("No CHANGELOG.md files found.");
|
|
123
|
-
else {
|
|
124
|
-
yield* Effect.log(`Found ${changelogs.length} CHANGELOG.md file(s)`);
|
|
125
|
-
for (const entry of changelogs) {
|
|
126
|
-
yield* Effect.try({
|
|
127
|
-
try: () => ChangelogTransformer.transformFile(entry.changelogPath),
|
|
128
|
-
catch: (error) => /* @__PURE__ */ new Error(`Failed to transform ${entry.changelogPath}: ${error instanceof Error ? error.message : String(error)}`)
|
|
129
|
-
});
|
|
130
|
-
yield* Effect.log(`Transformed ${entry.name} → ${entry.changelogPath}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!existsSync(join(resolvedCwd, ".changeset", "config.json"))) return;
|
|
134
|
-
const scopesWithVersionFiles = (yield* (yield* ConfigInspector).inspect(resolvedCwd)).packages.filter((p) => p.versionFiles.length > 0).map((p) => {
|
|
135
|
-
const fresh = readPackageVersionFromDisk(p.workspaceDir);
|
|
136
|
-
return fresh && fresh !== p.version ? {
|
|
137
|
-
...p,
|
|
138
|
-
version: fresh
|
|
139
|
-
} : p;
|
|
140
|
-
});
|
|
141
|
-
if (scopesWithVersionFiles.length === 0) return;
|
|
142
|
-
yield* Effect.log(`Found ${scopesWithVersionFiles.length} package${scopesWithVersionFiles.length === 1 ? "" : "s"} with versionFiles`);
|
|
143
|
-
const updates = yield* Effect.try({
|
|
144
|
-
try: () => VersionFiles.processResolvedVersionFiles(scopesWithVersionFiles, dryRun),
|
|
145
|
-
catch: (error) => {
|
|
146
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
-
return new VersionFileError({
|
|
148
|
-
filePath: message.match(/Failed to update (.+?):/)?.[1] ?? cwd,
|
|
149
|
-
reason: message
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
for (const update of updates) {
|
|
154
|
-
const action = dryRun ? "Would update" : "Updated";
|
|
155
|
-
yield* Effect.log(`${action} ${update.filePath} → ${update.version}`);
|
|
30
|
+
const result = yield* (yield* Changesets.ReleasePlanner).apply(cwd, { dryRun });
|
|
31
|
+
if (result.releases.length === 0) {
|
|
32
|
+
yield* Effect.log("No pending changesets.");
|
|
33
|
+
return;
|
|
156
34
|
}
|
|
35
|
+
const verb = dryRun ? "Would release" : "Released";
|
|
36
|
+
for (const r of result.releases) yield* Effect.log(`${verb} ${r.name}: ${r.oldVersion} -> ${r.newVersion} (${r.type})`);
|
|
37
|
+
if (!dryRun) yield* Effect.log(`Touched ${result.touchedFiles.length} file(s)`);
|
|
38
|
+
for (const u of result.versionFileUpdates) yield* Effect.log(`${dryRun ? "Would update" : "Updated"} ${u.filePath} -> ${u.version}`);
|
|
157
39
|
});
|
|
158
40
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Read the `version` field from a workspace package's `package.json` on
|
|
161
|
-
* disk. Used to bypass cached state in `ConfigInspector` and
|
|
162
|
-
* `WorkspaceDiscovery` after `changeset version` has rewritten each
|
|
163
|
-
* workspace's manifest.
|
|
164
|
-
*
|
|
165
|
-
* @internal
|
|
166
|
-
*/
|
|
167
|
-
function readPackageVersionFromDisk(workspaceDir) {
|
|
168
|
-
try {
|
|
169
|
-
return JSON.parse(readFileSync(join(workspaceDir, "package.json"), "utf-8")).version ?? null;
|
|
170
|
-
} catch {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
41
|
/* v8 ignore next 4 -- CLI registration; handler tested via runVersion */
|
|
175
|
-
const versionCommand = Command.make("version", { dryRun: dryRunOption }, ({ dryRun }) => runVersion(dryRun)).pipe(Command.withDescription("
|
|
42
|
+
const versionCommand = Command.make("version", { dryRun: dryRunOption }, ({ dryRun }) => runVersion(dryRun)).pipe(Command.withDescription("Apply pending changesets: bump versions and transform CHANGELOGs"));
|
|
176
43
|
|
|
177
44
|
//#endregion
|
|
178
45
|
export { versionCommand };
|
package/commands/clean.js
CHANGED
|
@@ -23,7 +23,7 @@ const DEFAULT_GLOBS = [
|
|
|
23
23
|
".rslib"
|
|
24
24
|
];
|
|
25
25
|
/** Directory names a recursive (`**`) glob must never descend into. */
|
|
26
|
-
const NO_DESCEND = new Set(["node_modules", ".git"]);
|
|
26
|
+
const NO_DESCEND = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
27
27
|
/** Unexpected failure while planning or removing artifacts. */
|
|
28
28
|
var CleanError = class extends Data.TaggedError("CleanError") {};
|
|
29
29
|
/**
|
package/index.d.ts
CHANGED
|
@@ -30,7 +30,8 @@ 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.WorkspaceSnapshotReaderLive`,
|
|
33
|
+
* `ChangesetConfigReaderLive`), `Changesets.WorkspaceSnapshotReaderLive`,
|
|
34
|
+
* `Changesets.ReleasePlannerLive` (provided `ConfigInspectorLive`), and
|
|
34
35
|
* `Changesets.BranchAnalyzerLive`, which shares the single `ConfigInspectorLive`
|
|
35
36
|
* instance built once via `provideMerge`.
|
|
36
37
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@effect/cli": "^0.75.2",
|
|
34
34
|
"@effect/cluster": "^0.59.0",
|
|
35
|
-
"@effect/platform": "^0.96.
|
|
35
|
+
"@effect/platform": "^0.96.2",
|
|
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": "1.
|
|
40
|
-
"effect": "^3.21.
|
|
39
|
+
"@savvy-web/silk-effects": "1.5.0",
|
|
40
|
+
"effect": "^3.21.4",
|
|
41
41
|
"jsonc-effect": "^0.2.1",
|
|
42
42
|
"workspaces-effect": "^1.2.0",
|
|
43
43
|
"yaml": "^2.9.0"
|
package/bin/savvy.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|