@savvy-web/cli 2.10.0 → 2.11.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.
- package/cli/index.js +1 -1
- package/commands/commit/check.js +11 -1
- package/commands/commit/init.js +17 -7
- package/commands/lint/check.js +21 -1
- package/commands/lint/init.js +51 -8
- package/package.json +10 -10
package/cli/index.js
CHANGED
|
@@ -76,7 +76,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
|
|
|
76
76
|
* CLI application: reads argv from the Stdio service provided by NodeServices.
|
|
77
77
|
* (v4's `Command.run` takes only `version` — the name comes from the root command.)
|
|
78
78
|
*/
|
|
79
|
-
const cli = Command.run(rootCommand, { version: "2.
|
|
79
|
+
const cli = Command.run(rootCommand, { version: "2.11.1" });
|
|
80
80
|
/**
|
|
81
81
|
* Shared workspace services from `@effected/workspaces`, wired as a
|
|
82
82
|
* self-contained unit and built ONCE (layers memoize by reference).
|
package/commands/commit/check.js
CHANGED
|
@@ -2,7 +2,7 @@ import { HUSKY_HOOK_PATH, POST_CHECKOUT_HOOK_PATH, POST_COMMIT_HOOK_PATH, POST_M
|
|
|
2
2
|
import { SECTION_DEF, savvyCommitBlock } from "./init.js";
|
|
3
3
|
import { CheckOutcome, ManagedSection } from "@effected/templates";
|
|
4
4
|
import { VersioningStrategy } from "@effected/workspaces";
|
|
5
|
-
import { ChangesetConfigReader, Commitlint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
5
|
+
import { ChangesetConfigReader, Commitlint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
6
6
|
import { Effect, FileSystem, Option } from "effect";
|
|
7
7
|
|
|
8
8
|
//#region src/commands/commit/check.ts
|
|
@@ -155,6 +155,16 @@ function runCommitCheck() {
|
|
|
155
155
|
yield* Effect.log(`${BULLET} Hygiene hook: ${hookPath} section not found (run 'savvy init' to add)`);
|
|
156
156
|
}
|
|
157
157
|
if (hookPath === ".husky/post-commit") continue;
|
|
158
|
+
const installHook = hookPath === ".husky/post-checkout" ? "post-checkout" : "post-merge";
|
|
159
|
+
const installStatus = yield* ms.check(hookPath, savvyInstallBlock(installHook));
|
|
160
|
+
if (CheckOutcome.$is("UpToDate")(installStatus)) yield* Effect.log(`${"✓"} Dependency install: ${hookPath}`);
|
|
161
|
+
else if (CheckOutcome.$is("Drifted")(installStatus)) {
|
|
162
|
+
sectionsHealthy = false;
|
|
163
|
+
yield* Effect.log(`${"⚠"} Dependency install: ${hookPath} outdated (run 'savvy init' to update)`);
|
|
164
|
+
} else {
|
|
165
|
+
sectionsHealthy = false;
|
|
166
|
+
yield* Effect.log(`${BULLET} Dependency install: ${hookPath} section not found (run 'savvy init' to add)`);
|
|
167
|
+
}
|
|
158
168
|
const toolchainStatus = yield* ms.check(hookPath, SavvyToolchainSection.section(savvyToolchainCheck()));
|
|
159
169
|
if (CheckOutcome.$is("UpToDate")(toolchainStatus)) yield* Effect.log(`${"✓"} Toolchain check: ${hookPath}`);
|
|
160
170
|
else if (CheckOutcome.$is("Drifted")(toolchainStatus)) {
|
package/commands/commit/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HUSKY_HOOK_PATH, POST_CHECKOUT_HOOK_PATH, POST_COMMIT_HOOK_PATH, POST_MERGE_HOOK_PATH } from "./constants.js";
|
|
2
2
|
import { CommentStyle, ManagedSection, SectionId } from "@effected/templates";
|
|
3
|
-
import { SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolSection, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
3
|
+
import { SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyToolSection, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
4
4
|
import { Effect, FileSystem } from "effect";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
6
|
import { chmod } from "node:fs/promises";
|
|
@@ -20,6 +20,17 @@ const SECTION_DEF = SectionId.make({
|
|
|
20
20
|
});
|
|
21
21
|
/** Header written when creating a fresh commit-msg hook. */
|
|
22
22
|
const COMMIT_MSG_HEADER = "#!/usr/bin/env sh\n# Commit-msg hook with savvy managed sections\n# Custom hooks can go above, below, or between the managed sections\n\n";
|
|
23
|
+
/**
|
|
24
|
+
* The hygiene hooks, each paired with the install variant it should carry.
|
|
25
|
+
*
|
|
26
|
+
* `undefined` means the hook gets hygiene only — post-commit fires on every
|
|
27
|
+
* commit, where neither a drift warning nor an install is worth the noise.
|
|
28
|
+
*/
|
|
29
|
+
const HYGIENE_HOOKS = [
|
|
30
|
+
[POST_CHECKOUT_HOOK_PATH, "post-checkout"],
|
|
31
|
+
[POST_MERGE_HOOK_PATH, "post-merge"],
|
|
32
|
+
[POST_COMMIT_HOOK_PATH, void 0]
|
|
33
|
+
];
|
|
23
34
|
/** Header written when creating a fresh hygiene hook (post-checkout / post-merge / post-commit). */
|
|
24
35
|
const HYGIENE_HEADER = "#!/usr/bin/env sh\n# Managed by savvy-hooks\n# Custom hooks can go above or below the managed section\n\n";
|
|
25
36
|
/**
|
|
@@ -85,14 +96,13 @@ function runCommitInit(opts) {
|
|
|
85
96
|
const commitResults = yield* ms.syncAll(HUSKY_HOOK_PATH, [SavvyBaseSection.section(savvyBasePreamble()), savvyCommitBlock(config)]);
|
|
86
97
|
yield* makeExecutable(HUSKY_HOOK_PATH);
|
|
87
98
|
yield* Effect.log(`${"✓"} ${force ? "Replaced" : "Synced"} ${HUSKY_HOOK_PATH} (${commitResults.map((r) => r._tag).join(", ")})`);
|
|
88
|
-
for (const hookPath of
|
|
89
|
-
POST_CHECKOUT_HOOK_PATH,
|
|
90
|
-
POST_MERGE_HOOK_PATH,
|
|
91
|
-
POST_COMMIT_HOOK_PATH
|
|
92
|
-
]) {
|
|
99
|
+
for (const [hookPath, installHook] of HYGIENE_HOOKS) {
|
|
93
100
|
yield* ensureHookFile(hookPath, HYGIENE_HEADER);
|
|
94
101
|
const sections = [SavvyHooksSection.section(savvyHooksHygiene())];
|
|
95
|
-
if (
|
|
102
|
+
if (installHook !== void 0) {
|
|
103
|
+
sections.push(SavvyToolchainSection.section(savvyToolchainCheck()));
|
|
104
|
+
sections.push(savvyInstallBlock(installHook));
|
|
105
|
+
}
|
|
96
106
|
yield* ms.syncAll(hookPath, sections);
|
|
97
107
|
yield* makeExecutable(hookPath);
|
|
98
108
|
yield* Effect.log(`${"✓"} Synced ${hookPath}`);
|
package/commands/lint/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BIOME_VERSION } from "./biome-version.js";
|
|
2
2
|
import { Tool, ToolDiscovery } from "@effected/commands";
|
|
3
3
|
import { CheckOutcome, ManagedSection } from "@effected/templates";
|
|
4
|
-
import { ConfigDiscovery, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
4
|
+
import { ConfigDiscovery, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
5
5
|
import { Effect, FileSystem, Option } from "effect";
|
|
6
6
|
import { Jsonc } from "@effected/jsonc";
|
|
7
7
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -197,6 +197,7 @@ function runLintCheck(opts) {
|
|
|
197
197
|
];
|
|
198
198
|
const shellHookStatuses = [];
|
|
199
199
|
const toolchainStatuses = [];
|
|
200
|
+
const installStatuses = [];
|
|
200
201
|
for (const hookPath of shellHookPaths) {
|
|
201
202
|
if (!(yield* fs.exists(hookPath))) {
|
|
202
203
|
shellHookStatuses.push({
|
|
@@ -222,6 +223,22 @@ function runLintCheck(opts) {
|
|
|
222
223
|
warnings.push(`${WARNING} ${hookPath} savvy-hooks section is outdated.\n Run 'savvy init' to update.`);
|
|
223
224
|
}
|
|
224
225
|
if (hookPath === Lint.POST_COMMIT_HOOK_PATH) continue;
|
|
226
|
+
const installHook = hookPath === Lint.POST_CHECKOUT_HOOK_PATH ? "post-checkout" : "post-merge";
|
|
227
|
+
const installResult = yield* ms.check(hookPath, savvyInstallBlock(installHook));
|
|
228
|
+
const installFound = !CheckOutcome.$is("Absent")(installResult);
|
|
229
|
+
const installUpToDate = CheckOutcome.$is("UpToDate")(installResult);
|
|
230
|
+
installStatuses.push({
|
|
231
|
+
path: hookPath,
|
|
232
|
+
found: installFound,
|
|
233
|
+
isUpToDate: installUpToDate
|
|
234
|
+
});
|
|
235
|
+
if (!installFound) {
|
|
236
|
+
sectionsHealthy = false;
|
|
237
|
+
warnings.push(`${WARNING} ${hookPath} has no savvy-install section.\n Run 'savvy init' to add it.`);
|
|
238
|
+
} else if (!installUpToDate) {
|
|
239
|
+
sectionsHealthy = false;
|
|
240
|
+
warnings.push(`${WARNING} ${hookPath} savvy-install section is outdated.\n Run 'savvy init' to update.`);
|
|
241
|
+
}
|
|
225
242
|
const toolchainResult = yield* ms.check(hookPath, SavvyToolchainSection.section(savvyToolchainCheck()));
|
|
226
243
|
const toolchainFound = !CheckOutcome.$is("Absent")(toolchainResult);
|
|
227
244
|
const toolchainUpToDate = CheckOutcome.$is("UpToDate")(toolchainResult);
|
|
@@ -276,6 +293,9 @@ function runLintCheck(opts) {
|
|
|
276
293
|
for (const status of shellHookStatuses) if (!status.found) yield* Effect.log(`${BULLET} ${status.path}: savvy-hooks section not found`);
|
|
277
294
|
else if (status.isUpToDate) yield* Effect.log(`${CHECK_MARK} ${status.path}: up-to-date`);
|
|
278
295
|
else yield* Effect.log(`${WARNING} ${status.path}: outdated (run 'savvy init' to update)`);
|
|
296
|
+
for (const status of installStatuses) if (!status.found) yield* Effect.log(`${BULLET} ${status.path}: savvy-install section not found`);
|
|
297
|
+
else if (status.isUpToDate) yield* Effect.log(`${CHECK_MARK} ${status.path}: savvy-install up-to-date`);
|
|
298
|
+
else yield* Effect.log(`${WARNING} ${status.path}: savvy-install outdated (run 'savvy init' to update)`);
|
|
279
299
|
for (const status of toolchainStatuses) if (!status.found) yield* Effect.log(`${BULLET} ${status.path}: savvy-toolchain section not found`);
|
|
280
300
|
else if (status.isUpToDate) yield* Effect.log(`${CHECK_MARK} ${status.path}: savvy-toolchain up-to-date`);
|
|
281
301
|
else yield* Effect.log(`${WARNING} ${status.path}: savvy-toolchain outdated (run 'savvy init' to update)`);
|
package/commands/lint/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BIOME_VERSION } from "./biome-version.js";
|
|
2
2
|
import { ManagedSection } from "@effected/templates";
|
|
3
3
|
import { WorkspaceDiscovery } from "@effected/workspaces";
|
|
4
|
-
import { BiomeSchemaSync, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
4
|
+
import { BiomeSchemaSync, LIFECYCLE_SCRIPTS_CONFIG_KEY, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, publishesBuiltLinkDirectory, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
5
5
|
import { Effect, FileSystem } from "effect";
|
|
6
|
-
import { dirname } from "node:path";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
import { Jsonc, JsoncEdit, JsoncModifier } from "@effected/jsonc";
|
|
8
8
|
import { chmod } from "node:fs/promises";
|
|
9
9
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -27,6 +27,17 @@ const JSONC_FORMAT = {
|
|
|
27
27
|
};
|
|
28
28
|
/** Header written when creating a fresh pre-commit hook. */
|
|
29
29
|
const PRE_COMMIT_HEADER = "#!/usr/bin/env sh\n# Pre-commit hook with savvy managed sections\n# Custom hooks can go above, below, or between the managed sections\n\n";
|
|
30
|
+
/**
|
|
31
|
+
* The hygiene hooks, each paired with the install variant it should carry.
|
|
32
|
+
*
|
|
33
|
+
* `undefined` means the hook gets hygiene only — post-commit fires on every
|
|
34
|
+
* commit, where neither a drift warning nor an install is worth the noise.
|
|
35
|
+
*/
|
|
36
|
+
const HYGIENE_HOOKS = [
|
|
37
|
+
[Lint.POST_CHECKOUT_HOOK_PATH, "post-checkout"],
|
|
38
|
+
[Lint.POST_MERGE_HOOK_PATH, "post-merge"],
|
|
39
|
+
[Lint.POST_COMMIT_HOOK_PATH, void 0]
|
|
40
|
+
];
|
|
30
41
|
/** Header written when creating a fresh hygiene hook (post-checkout / post-merge / post-commit). */
|
|
31
42
|
const HYGIENE_HEADER = "#!/usr/bin/env sh\n# Managed by savvy-hooks\n# Custom hooks can go above or below the managed section\n\n";
|
|
32
43
|
/**
|
|
@@ -148,6 +159,38 @@ function biomeConfigRoots() {
|
|
|
148
159
|
});
|
|
149
160
|
}
|
|
150
161
|
/**
|
|
162
|
+
* Point out that this workspace needs lifecycle scripts, and how to allow them.
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* The `savvy-install` hook block skips lifecycle scripts unless the LOCAL git
|
|
166
|
+
* config key authorizes them. A workspace publishing through built link
|
|
167
|
+
* directories cannot survive that default — its own workspace links resolve
|
|
168
|
+
* through directories a `prepare` script produces — so it has to be told.
|
|
169
|
+
*
|
|
170
|
+
* This reports and does not set. Enabling scripts means every later hook-time
|
|
171
|
+
* install may execute code from whatever revision was just checked out, which is
|
|
172
|
+
* a trust decision belonging to the person who owns the checkout, not to a
|
|
173
|
+
* command they ran to write some config files. Setting it for them silently
|
|
174
|
+
* would give back most of what reading the flag from local config bought.
|
|
175
|
+
*
|
|
176
|
+
* Every failure reads as "say nothing": an unreadable or malformed manifest
|
|
177
|
+
* cannot make the notice wrong, only absent.
|
|
178
|
+
*/
|
|
179
|
+
function noticeLifecycleScripts() {
|
|
180
|
+
return Effect.gen(function* () {
|
|
181
|
+
const fs = yield* FileSystem.FileSystem;
|
|
182
|
+
const roots = yield* biomeConfigRoots();
|
|
183
|
+
for (const root of roots) {
|
|
184
|
+
const manifest = yield* fs.readFileString(join(root, "package.json")).pipe(Effect.map((raw) => JSON.parse(raw)), Effect.catch(() => Effect.succeed(void 0)));
|
|
185
|
+
if (publishesBuiltLinkDirectory(manifest)) {
|
|
186
|
+
yield* Effect.log(`${WARNING} This workspace publishes through built link directories, so a hook-time install must run lifecycle scripts.`);
|
|
187
|
+
yield* Effect.log(` Allow them with: git config --local ${LIFECYCLE_SCRIPTS_CONFIG_KEY} true`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
151
194
|
* Find and sync biome config `$schema` URLs to match the pinned {@link BIOME_VERSION}.
|
|
152
195
|
*
|
|
153
196
|
* @remarks
|
|
@@ -214,19 +257,19 @@ function runLintInit(opts) {
|
|
|
214
257
|
const preCommitResults = yield* ms.syncAll(Lint.HUSKY_HOOK_PATH, [SavvyBaseSection.section(savvyBasePreamble()), Lint.savvyLintBlock(config)]);
|
|
215
258
|
yield* makeExecutable(Lint.HUSKY_HOOK_PATH);
|
|
216
259
|
yield* Effect.log(`${CHECK_MARK} ${force ? "Replaced" : "Synced"} ${Lint.HUSKY_HOOK_PATH} (${preCommitResults.map((r) => r._tag).join(", ")})`);
|
|
217
|
-
if (presetIncludesShellScripts(preset)) for (const hookPath of
|
|
218
|
-
Lint.POST_CHECKOUT_HOOK_PATH,
|
|
219
|
-
Lint.POST_MERGE_HOOK_PATH,
|
|
220
|
-
Lint.POST_COMMIT_HOOK_PATH
|
|
221
|
-
]) {
|
|
260
|
+
if (presetIncludesShellScripts(preset)) for (const [hookPath, installHook] of HYGIENE_HOOKS) {
|
|
222
261
|
yield* ensureHookFile(hookPath, HYGIENE_HEADER);
|
|
223
262
|
yield* ms.remove(hookPath, Lint.LegacySavvyLintHygieneDef);
|
|
224
263
|
const sections = [SavvyHooksSection.section(savvyHooksHygiene())];
|
|
225
|
-
if (
|
|
264
|
+
if (installHook !== void 0) {
|
|
265
|
+
sections.push(SavvyToolchainSection.section(savvyToolchainCheck()));
|
|
266
|
+
sections.push(savvyInstallBlock(installHook));
|
|
267
|
+
}
|
|
226
268
|
yield* ms.syncAll(hookPath, sections);
|
|
227
269
|
yield* makeExecutable(hookPath);
|
|
228
270
|
yield* Effect.log(`${CHECK_MARK} Synced ${hookPath}`);
|
|
229
271
|
}
|
|
272
|
+
if (presetIncludesShellScripts(preset)) yield* noticeLifecycleScripts();
|
|
230
273
|
if (presetIncludesMarkdown(preset)) yield* writeMarkdownlintConfig(fs, preset, force);
|
|
231
274
|
yield* syncBiomeSchemas();
|
|
232
275
|
if ((yield* fs.exists(config)) && !force) yield* Effect.log(`${WARNING} ${config} already exists (use --force to overwrite)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.1",
|
|
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",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"savvy": "bin/savvy.js"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
35
|
-
"@effected/commands": "^0.
|
|
36
|
-
"@effected/git": "^0.
|
|
37
|
-
"@effected/jsonc": "^0.
|
|
38
|
-
"@effected/templates": "^0.
|
|
39
|
-
"@effected/workspaces": "^0.
|
|
40
|
-
"@effected/yaml": "^0.
|
|
41
|
-
"@savvy-web/silk-effects": "7.
|
|
42
|
-
"effect": "4.0.0-rc.
|
|
34
|
+
"@effect/platform-node": "4.0.0-rc.112",
|
|
35
|
+
"@effected/commands": "^0.6.1",
|
|
36
|
+
"@effected/git": "^0.12.0",
|
|
37
|
+
"@effected/jsonc": "^0.9.0",
|
|
38
|
+
"@effected/templates": "^0.5.0",
|
|
39
|
+
"@effected/workspaces": "^0.20.1",
|
|
40
|
+
"@effected/yaml": "^0.14.0",
|
|
41
|
+
"@savvy-web/silk-effects": "7.5.1",
|
|
42
|
+
"effect": "4.0.0-rc.112"
|
|
43
43
|
}
|
|
44
44
|
}
|