@savvy-web/cli 3.2.8 → 3.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 +8 -5
- package/commands/changeset/commands/check.js +11 -7
- package/commands/changeset/commands/config-validate.js +6 -4
- package/commands/changeset/commands/deps-detect.js +4 -4
- package/commands/changeset/commands/deps-regen.js +11 -10
- package/commands/changeset/commands/init.js +13 -11
- package/commands/changeset/commands/lint.js +4 -3
- package/commands/changeset/commands/transform.js +7 -5
- package/commands/changeset/commands/validate-file.js +8 -6
- package/commands/changeset/commands/version.js +5 -4
- package/commands/changeset/utils/config-gate.js +3 -3
- package/commands/check.js +3 -2
- package/commands/clean.js +7 -8
- package/commands/commit/check.js +33 -35
- package/commands/commit/constants.js +5 -0
- package/commands/commit/init.js +8 -6
- package/commands/commit/lint.js +2 -1
- package/commands/lint/check.js +43 -47
- package/commands/lint/init.js +20 -22
- package/commands/repos/commands/add.js +8 -9
- package/commands/repos/commands/deregister.js +7 -7
- package/commands/repos/commands/note.js +7 -8
- package/commands/repos/commands/pin.js +11 -13
- package/commands/repos/commands/remove.js +13 -15
- package/commands/repos/commands/rename.js +10 -12
- package/commands/repos/commands/restore.js +12 -14
- package/commands/repos/commands/status.js +16 -8
- package/commands/repos/commands/sync.js +12 -13
- package/index.d.ts +16 -15
- package/internal/failure-line.js +39 -0
- package/internal/output.js +52 -0
- package/internal/version-line.js +19 -0
- package/main.d.ts +18 -2
- package/main.js +26 -16
- package/package.json +3 -1
- package/version.js +13 -0
package/commands/commit/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Output } from "../../internal/output.js";
|
|
1
2
|
import { HUSKY_HOOK_PATH, POST_CHECKOUT_HOOK_PATH, POST_COMMIT_HOOK_PATH, POST_MERGE_HOOK_PATH } from "./constants.js";
|
|
2
3
|
import { dirname } from "node:path";
|
|
3
4
|
import { SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyToolSection, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
@@ -89,13 +90,13 @@ function runCommitInit(opts) {
|
|
|
89
90
|
const fs = yield* FileSystem.FileSystem;
|
|
90
91
|
const ms = yield* ManagedSection;
|
|
91
92
|
if (config.startsWith("/")) yield* Effect.fail(/* @__PURE__ */ new Error("Config path must be relative to repository root, not absolute"));
|
|
92
|
-
yield*
|
|
93
|
+
yield* Output.heading("commitlint");
|
|
93
94
|
yield* fs.makeDirectory(".husky", { recursive: true });
|
|
94
95
|
if (force) yield* fs.writeFileString(HUSKY_HOOK_PATH, COMMIT_MSG_HEADER);
|
|
95
96
|
else yield* ensureHookFile(HUSKY_HOOK_PATH, COMMIT_MSG_HEADER);
|
|
96
97
|
const commitResults = yield* ms.syncAll(HUSKY_HOOK_PATH, [SavvyBaseSection.section(savvyBasePreamble()), savvyCommitBlock(config)]);
|
|
97
98
|
yield* makeExecutable(HUSKY_HOOK_PATH);
|
|
98
|
-
yield*
|
|
99
|
+
yield* Output.ok(`${force ? "Replaced" : "Synced"} ${HUSKY_HOOK_PATH} (${commitResults.map((r) => r._tag).join(", ")})`);
|
|
99
100
|
for (const [hookPath, installHook] of HYGIENE_HOOKS) {
|
|
100
101
|
yield* ensureHookFile(hookPath, HYGIENE_HEADER);
|
|
101
102
|
const sections = [SavvyHooksSection.section(savvyHooksHygiene())];
|
|
@@ -105,16 +106,17 @@ function runCommitInit(opts) {
|
|
|
105
106
|
}
|
|
106
107
|
yield* ms.syncAll(hookPath, sections);
|
|
107
108
|
yield* makeExecutable(hookPath);
|
|
108
|
-
yield*
|
|
109
|
+
yield* Output.ok(`Synced ${hookPath}`);
|
|
109
110
|
}
|
|
110
|
-
if ((yield* fs.exists(config)) && !force) yield*
|
|
111
|
+
if ((yield* fs.exists(config)) && !force) yield* Output.warn(`${config} already exists (use --force to overwrite)`);
|
|
111
112
|
else {
|
|
112
113
|
const configDir = dirname(config);
|
|
113
114
|
if (configDir && configDir !== ".") yield* fs.makeDirectory(configDir, { recursive: true });
|
|
114
115
|
yield* fs.writeFileString(config, CONFIG_CONTENT);
|
|
115
|
-
yield*
|
|
116
|
+
yield* Output.ok(`Created ${config}`);
|
|
116
117
|
}
|
|
117
|
-
yield*
|
|
118
|
+
yield* Output.line("");
|
|
119
|
+
yield* Output.ok("Install @commitlint/cli if it is not already installed");
|
|
118
120
|
});
|
|
119
121
|
}
|
|
120
122
|
|
package/commands/commit/lint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Output } from "../../internal/output.js";
|
|
1
2
|
import { buildCommitlintInvocation } from "./commitlint-invocation.js";
|
|
2
3
|
import { Argument, CliError, Command } from "effect/unstable/cli";
|
|
3
4
|
import { Commitlint } from "@savvy-web/silk-effects";
|
|
@@ -75,7 +76,7 @@ function runCommitLint(file) {
|
|
|
75
76
|
if (stdout.length > 0) yield* Effect.sync(() => process.stdout.write(stdout));
|
|
76
77
|
if (stderr.length > 0) yield* Effect.sync(() => process.stderr.write(stderr));
|
|
77
78
|
if (!passed) return yield* Effect.fail(new CliError.UserError({ cause: `Commit message at ${file} failed commitlint validation` }));
|
|
78
|
-
yield*
|
|
79
|
+
yield* Output.ok("Commit message passes the Silk commitlint preset");
|
|
79
80
|
});
|
|
80
81
|
}
|
|
81
82
|
/* v8 ignore next */
|
package/commands/lint/check.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Output } from "../../internal/output.js";
|
|
1
2
|
import { BIOME_VERSION } from "./biome-version.js";
|
|
2
3
|
import { ConfigDiscovery, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyOkfBlock, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
3
4
|
import { Effect, FileSystem, Option } from "effect";
|
|
@@ -12,14 +13,8 @@ import { Tool, ToolDiscovery } from "@effected/commands";
|
|
|
12
13
|
*
|
|
13
14
|
* @internal
|
|
14
15
|
*/
|
|
15
|
-
/** Unicode
|
|
16
|
-
const CHECK_MARK = "✓";
|
|
17
|
-
/** Unicode cross symbol. */
|
|
18
|
-
const CROSS_MARK = "✗";
|
|
19
|
-
/** Unicode warning symbol. */
|
|
16
|
+
/** Unicode warning symbol, for the quiet-mode warning lines. */
|
|
20
17
|
const WARNING = "⚠";
|
|
21
|
-
/** Unicode bullet symbol. */
|
|
22
|
-
const BULLET = "•";
|
|
23
18
|
/** Paths to search for config files. */
|
|
24
19
|
const CONFIG_SEARCH_PATHS = [
|
|
25
20
|
"lib/configs/lint-staged.config.ts",
|
|
@@ -285,42 +280,43 @@ function runLintCheck(opts) {
|
|
|
285
280
|
if (!markdownlintStatus.configMatches) warnings.push(`${WARNING} ${Lint.MARKDOWNLINT_CONFIG_PATH}: config rules differ from template.\n Run 'savvy init --force' to overwrite.`);
|
|
286
281
|
}
|
|
287
282
|
if (quiet) {
|
|
288
|
-
if (warnings.length > 0) for (const warning of warnings) yield*
|
|
283
|
+
if (warnings.length > 0) for (const warning of warnings) yield* Output.line(warning);
|
|
289
284
|
return;
|
|
290
285
|
}
|
|
291
|
-
yield*
|
|
292
|
-
if (foundConfig) yield*
|
|
293
|
-
else yield*
|
|
294
|
-
if (hasHuskyHook) yield*
|
|
295
|
-
else yield*
|
|
286
|
+
yield* Output.heading("lint-staged configuration");
|
|
287
|
+
if (foundConfig) yield* Output.ok(`Config file: ${foundConfig}`);
|
|
288
|
+
else yield* Output.fail(`No lint-staged config file found`);
|
|
289
|
+
if (hasHuskyHook) yield* Output.ok(`Husky hook: ${Lint.HUSKY_HOOK_PATH}`);
|
|
290
|
+
else yield* Output.fail(`No husky pre-commit hook found`);
|
|
296
291
|
if (hasHuskyHook) {
|
|
297
|
-
if (baseStatusLabel === "up-to-date") yield*
|
|
298
|
-
else if (baseStatusLabel === "outdated") yield*
|
|
299
|
-
else yield*
|
|
292
|
+
if (baseStatusLabel === "up-to-date") yield* Output.ok(`Base section: up-to-date`);
|
|
293
|
+
else if (baseStatusLabel === "outdated") yield* Output.warn(`Base section: outdated (run 'savvy init' to update)`);
|
|
294
|
+
else yield* Output.skip(`Base section: not found (run 'savvy init' to add)`);
|
|
300
295
|
const lintLabel = detectedConfigPath ? ` (config: ${detectedConfigPath})` : "";
|
|
301
|
-
if (lintStatusLabel === "up-to-date") yield*
|
|
302
|
-
else if (lintStatusLabel === "outdated") yield*
|
|
303
|
-
else yield*
|
|
304
|
-
if (okfStatusLabel === "up-to-date") yield*
|
|
305
|
-
else if (okfStatusLabel === "outdated") yield*
|
|
306
|
-
else yield*
|
|
296
|
+
if (lintStatusLabel === "up-to-date") yield* Output.ok(`Lint section: up-to-date${lintLabel}`);
|
|
297
|
+
else if (lintStatusLabel === "outdated") yield* Output.warn(`Lint section: outdated (run 'savvy init' to update)`);
|
|
298
|
+
else yield* Output.skip(`Lint section: not found (run 'savvy init' to add)`);
|
|
299
|
+
if (okfStatusLabel === "up-to-date") yield* Output.ok(`OKF section: up-to-date`);
|
|
300
|
+
else if (okfStatusLabel === "outdated") yield* Output.warn(`OKF section: outdated (run 'savvy init' to update)`);
|
|
301
|
+
else yield* Output.skip(`OKF section: not found (run 'savvy init' to add)`);
|
|
307
302
|
}
|
|
308
|
-
for (const status of shellHookStatuses) if (!status.found) yield*
|
|
309
|
-
else if (status.isUpToDate) yield*
|
|
310
|
-
else yield*
|
|
311
|
-
for (const status of installStatuses) if (!status.found) yield*
|
|
312
|
-
else if (status.isUpToDate) yield*
|
|
313
|
-
else yield*
|
|
314
|
-
for (const status of toolchainStatuses) if (!status.found) yield*
|
|
315
|
-
else if (status.isUpToDate) yield*
|
|
316
|
-
else yield*
|
|
317
|
-
yield*
|
|
303
|
+
for (const status of shellHookStatuses) if (!status.found) yield* Output.skip(`${status.path}: savvy-hooks section not found`);
|
|
304
|
+
else if (status.isUpToDate) yield* Output.ok(`${status.path}: up-to-date`);
|
|
305
|
+
else yield* Output.warn(`${status.path}: outdated (run 'savvy init' to update)`);
|
|
306
|
+
for (const status of installStatuses) if (!status.found) yield* Output.skip(`${status.path}: savvy-install section not found`);
|
|
307
|
+
else if (status.isUpToDate) yield* Output.ok(`${status.path}: savvy-install up-to-date`);
|
|
308
|
+
else yield* Output.warn(`${status.path}: savvy-install outdated (run 'savvy init' to update)`);
|
|
309
|
+
for (const status of toolchainStatuses) if (!status.found) yield* Output.skip(`${status.path}: savvy-toolchain section not found`);
|
|
310
|
+
else if (status.isUpToDate) yield* Output.ok(`${status.path}: savvy-toolchain up-to-date`);
|
|
311
|
+
else yield* Output.warn(`${status.path}: savvy-toolchain outdated (run 'savvy init' to update)`);
|
|
312
|
+
yield* Output.line("");
|
|
313
|
+
yield* Output.heading("Tool availability");
|
|
318
314
|
const biomeAvailable = yield* td.isAvailable(Tool.named("biome"));
|
|
319
315
|
const biomeConfig = yield* findConfig(discovery, ["biome.jsonc", "biome.json"]);
|
|
320
316
|
if (biomeAvailable) {
|
|
321
317
|
const configInfo = biomeConfig ? ` (config: ${biomeConfig})` : "";
|
|
322
|
-
yield*
|
|
323
|
-
} else yield*
|
|
318
|
+
yield* Output.ok(`Biome${configInfo}`);
|
|
319
|
+
} else yield* Output.skip(`Biome: not installed`);
|
|
324
320
|
const markdownAvailable = yield* td.isAvailable(Tool.named("markdownlint-cli2"));
|
|
325
321
|
const markdownConfig = yield* findConfig(discovery, [
|
|
326
322
|
".markdownlint-cli2.jsonc",
|
|
@@ -333,29 +329,29 @@ function runLintCheck(opts) {
|
|
|
333
329
|
]);
|
|
334
330
|
if (markdownAvailable) {
|
|
335
331
|
const configInfo = markdownConfig ? ` (config: ${markdownConfig})` : "";
|
|
336
|
-
yield*
|
|
337
|
-
} else yield*
|
|
332
|
+
yield* Output.ok(`markdownlint-cli2${configInfo}`);
|
|
333
|
+
} else yield* Output.skip(`markdownlint-cli2: not installed`);
|
|
338
334
|
const tscAvailable = yield* td.isAvailable(Tool.named("tsc"));
|
|
339
335
|
const tsgoAvailable = yield* td.isAvailable(Tool.named("tsgo"));
|
|
340
|
-
if (tscAvailable) yield*
|
|
341
|
-
else if (tsgoAvailable) yield*
|
|
342
|
-
else yield*
|
|
336
|
+
if (tscAvailable) yield* Output.ok(`TypeScript (tsc)`);
|
|
337
|
+
else if (tsgoAvailable) yield* Output.ok(`TypeScript (tsgo)`);
|
|
338
|
+
else yield* Output.skip(`TypeScript: not installed`);
|
|
343
339
|
if (hasMarkdownlintConfig) {
|
|
344
|
-
if (markdownlintStatus.isUpToDate) yield*
|
|
340
|
+
if (markdownlintStatus.isUpToDate) yield* Output.ok(`${Lint.MARKDOWNLINT_CONFIG_PATH}: up-to-date`);
|
|
345
341
|
else {
|
|
346
342
|
const issues = [];
|
|
347
343
|
if (!markdownlintStatus.schemaMatches) issues.push("$schema");
|
|
348
344
|
if (!markdownlintStatus.configMatches) issues.push("config");
|
|
349
|
-
yield*
|
|
345
|
+
yield* Output.warn(`${Lint.MARKDOWNLINT_CONFIG_PATH}: ${issues.join(", ")} differ from template`);
|
|
350
346
|
}
|
|
351
|
-
} else yield*
|
|
352
|
-
for (const status of biomeSchemaStatus.statuses) if (status.matches) yield*
|
|
353
|
-
else yield*
|
|
354
|
-
yield*
|
|
347
|
+
} else yield* Output.skip(`${Lint.MARKDOWNLINT_CONFIG_PATH}: not found`);
|
|
348
|
+
for (const status of biomeSchemaStatus.statuses) if (status.matches) yield* Output.ok(`${status.path}: biome $schema up-to-date`);
|
|
349
|
+
else yield* Output.warn(`${status.path}: biome $schema outdated (run 'savvy init' to update)`);
|
|
350
|
+
yield* Output.line("");
|
|
355
351
|
const hasMarkdownlintIssues = hasMarkdownlintConfig && !markdownlintStatus.isUpToDate;
|
|
356
352
|
const hasBiomeSchemaIssues = biomeSchemaStatus.statuses.some((s) => !s.matches);
|
|
357
|
-
if (!foundConfig || !hasHuskyHook || !sectionsHealthy || hasMarkdownlintIssues || hasBiomeSchemaIssues) yield*
|
|
358
|
-
else yield*
|
|
353
|
+
if (!foundConfig || !hasHuskyHook || !sectionsHealthy || hasMarkdownlintIssues || hasBiomeSchemaIssues) yield* Output.warn(`Some issues found. Run 'savvy init' to fix.`);
|
|
354
|
+
else yield* Output.ok(`Lint-staged is configured correctly.`);
|
|
359
355
|
});
|
|
360
356
|
}
|
|
361
357
|
|
package/commands/lint/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Output } from "../../internal/output.js";
|
|
1
2
|
import { BIOME_VERSION } from "./biome-version.js";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { BiomeSchemaSync, LIFECYCLE_SCRIPTS_CONFIG_KEY, Lint, SavvyBaseSection, SavvyHooksSection, SavvyToolchainSection, publishesBuiltLinkDirectory, savvyBasePreamble, savvyHooksHygiene, savvyInstallBlock, savvyOkfBlock, savvyToolchainCheck } from "@savvy-web/silk-effects";
|
|
@@ -14,10 +15,6 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
14
15
|
*
|
|
15
16
|
* @internal
|
|
16
17
|
*/
|
|
17
|
-
/** Unicode checkmark symbol. */
|
|
18
|
-
const CHECK_MARK = "✓";
|
|
19
|
-
/** Unicode warning symbol. */
|
|
20
|
-
const WARNING = "⚠";
|
|
21
18
|
/** Executable file permission mode. */
|
|
22
19
|
const EXECUTABLE_MODE = 493;
|
|
23
20
|
/** Formatting options for jsonc-effect surgical edits. */
|
|
@@ -85,16 +82,16 @@ function writeMarkdownlintConfig(fs, preset, force) {
|
|
|
85
82
|
if (!configExists) {
|
|
86
83
|
yield* fs.makeDirectory("lib/configs", { recursive: true });
|
|
87
84
|
yield* fs.writeFileString(Lint.MARKDOWNLINT_CONFIG_PATH, `${fullTemplate}\n`);
|
|
88
|
-
yield*
|
|
85
|
+
yield* Output.ok(`Created ${Lint.MARKDOWNLINT_CONFIG_PATH}`);
|
|
89
86
|
return;
|
|
90
87
|
}
|
|
91
88
|
if (preset !== "silk") {
|
|
92
|
-
yield*
|
|
89
|
+
yield* Output.ok(`${Lint.MARKDOWNLINT_CONFIG_PATH}: exists (not managed by ${preset} preset)`);
|
|
93
90
|
return;
|
|
94
91
|
}
|
|
95
92
|
if (force) {
|
|
96
93
|
yield* fs.writeFileString(Lint.MARKDOWNLINT_CONFIG_PATH, `${fullTemplate}\n`);
|
|
97
|
-
yield*
|
|
94
|
+
yield* Output.ok(`Replaced ${Lint.MARKDOWNLINT_CONFIG_PATH} (--force)`);
|
|
98
95
|
return;
|
|
99
96
|
}
|
|
100
97
|
const existingText = yield* fs.readFileString(Lint.MARKDOWNLINT_CONFIG_PATH);
|
|
@@ -116,11 +113,11 @@ function writeMarkdownlintConfig(fs, preset, force) {
|
|
|
116
113
|
}
|
|
117
114
|
const existingConfig = existingParsed.config;
|
|
118
115
|
const configMatches = existingConfig !== void 0 && isDeepStrictEqual(existingConfig, Lint.MARKDOWNLINT_CONFIG);
|
|
119
|
-
if (!configMatches) yield*
|
|
116
|
+
if (!configMatches) yield* Output.warn(`${Lint.MARKDOWNLINT_CONFIG_PATH}: config rules differ from template (use --force to overwrite)`);
|
|
120
117
|
if (applied.length > 0) {
|
|
121
118
|
yield* fs.writeFileString(Lint.MARKDOWNLINT_CONFIG_PATH, updatedText);
|
|
122
|
-
yield*
|
|
123
|
-
} else if (configMatches) yield*
|
|
119
|
+
yield* Output.ok(`Updated ${applied.join(", ")} in ${Lint.MARKDOWNLINT_CONFIG_PATH}`);
|
|
120
|
+
} else if (configMatches) yield* Output.ok(`${Lint.MARKDOWNLINT_CONFIG_PATH}: up-to-date`);
|
|
124
121
|
});
|
|
125
122
|
}
|
|
126
123
|
/**
|
|
@@ -151,7 +148,7 @@ function writeMarkdownlintConfig(fs, preset, force) {
|
|
|
151
148
|
*/
|
|
152
149
|
function biomeConfigRoots() {
|
|
153
150
|
return Effect.gen(function* () {
|
|
154
|
-
return yield* (yield* WorkspaceDiscovery).listPackages().pipe(Effect.map((packages) => packages.map((pkg) => pkg.path)), Effect.catchTag("WorkspaceRootNotFoundError", () => Effect.succeed([])), Effect.catch((e) => Effect.as(Effect.
|
|
151
|
+
return yield* (yield* WorkspaceDiscovery).listPackages().pipe(Effect.map((packages) => packages.map((pkg) => pkg.path)), Effect.catchTag("WorkspaceRootNotFoundError", () => Effect.succeed([])), Effect.catch((e) => Effect.as(Effect.logWarning(`Only syncing biome $schema in the current directory: ${e.message}`), [])));
|
|
155
152
|
});
|
|
156
153
|
}
|
|
157
154
|
/**
|
|
@@ -179,8 +176,8 @@ function noticeLifecycleScripts() {
|
|
|
179
176
|
for (const root of roots) {
|
|
180
177
|
const manifest = yield* fs.readFileString(join(root, "package.json")).pipe(Effect.map((raw) => JSON.parse(raw)), Effect.catch(() => Effect.succeed(void 0)));
|
|
181
178
|
if (publishesBuiltLinkDirectory(manifest)) {
|
|
182
|
-
yield*
|
|
183
|
-
yield*
|
|
179
|
+
yield* Output.warn("This workspace publishes through built link directories, so a hook-time install must run lifecycle scripts.");
|
|
180
|
+
yield* Output.detail(`Allow them with: git config --local ${LIFECYCLE_SCRIPTS_CONFIG_KEY} true`);
|
|
184
181
|
return;
|
|
185
182
|
}
|
|
186
183
|
}
|
|
@@ -210,9 +207,9 @@ function syncBiomeSchemas() {
|
|
|
210
207
|
const roots = yield* biomeConfigRoots();
|
|
211
208
|
const passes = roots.length > 0 ? roots.map((cwd) => ({ cwd })) : [{ cwd: process.cwd() }];
|
|
212
209
|
for (const options of passes) yield* syncer.sync(BIOME_VERSION, options).pipe(Effect.flatMap((result) => Effect.gen(function* () {
|
|
213
|
-
for (const configPath of result.current) yield*
|
|
214
|
-
for (const configPath of result.updated) yield*
|
|
215
|
-
})), Effect.catchTag("BiomeSyncError", (e) =>
|
|
210
|
+
for (const configPath of result.current) yield* Output.ok(`${configPath}: biome $schema up-to-date`);
|
|
211
|
+
for (const configPath of result.updated) yield* Output.ok(`Updated $schema in ${configPath}`);
|
|
212
|
+
})), Effect.catchTag("BiomeSyncError", (e) => Output.warn(`Could not sync biome $schema: ${e.message}`)));
|
|
216
213
|
});
|
|
217
214
|
}
|
|
218
215
|
/** Make a file executable. */
|
|
@@ -246,7 +243,7 @@ function runLintInit(opts) {
|
|
|
246
243
|
const fs = yield* FileSystem.FileSystem;
|
|
247
244
|
const ms = yield* ManagedSection;
|
|
248
245
|
if (config.startsWith("/")) yield* Effect.fail(/* @__PURE__ */ new Error("Config path must be relative to repository root, not absolute"));
|
|
249
|
-
yield*
|
|
246
|
+
yield* Output.heading("lint-staged");
|
|
250
247
|
yield* fs.makeDirectory(".husky", { recursive: true });
|
|
251
248
|
if (force) yield* fs.writeFileString(Lint.HUSKY_HOOK_PATH, PRE_COMMIT_HEADER);
|
|
252
249
|
else yield* ensureHookFile(Lint.HUSKY_HOOK_PATH, PRE_COMMIT_HEADER);
|
|
@@ -256,7 +253,7 @@ function runLintInit(opts) {
|
|
|
256
253
|
savvyOkfBlock()
|
|
257
254
|
]);
|
|
258
255
|
yield* makeExecutable(Lint.HUSKY_HOOK_PATH);
|
|
259
|
-
yield*
|
|
256
|
+
yield* Output.ok(`${force ? "Replaced" : "Synced"} ${Lint.HUSKY_HOOK_PATH} (${preCommitResults.map((r) => r._tag).join(", ")})`);
|
|
260
257
|
if (presetIncludesShellScripts(preset)) for (const [hookPath, installHook] of HYGIENE_HOOKS) {
|
|
261
258
|
yield* ensureHookFile(hookPath, HYGIENE_HEADER);
|
|
262
259
|
yield* ms.remove(hookPath, Lint.LegacySavvyLintHygieneDef);
|
|
@@ -267,19 +264,20 @@ function runLintInit(opts) {
|
|
|
267
264
|
}
|
|
268
265
|
yield* ms.syncAll(hookPath, sections);
|
|
269
266
|
yield* makeExecutable(hookPath);
|
|
270
|
-
yield*
|
|
267
|
+
yield* Output.ok(`Synced ${hookPath}`);
|
|
271
268
|
}
|
|
272
269
|
if (presetIncludesShellScripts(preset)) yield* noticeLifecycleScripts();
|
|
273
270
|
if (presetIncludesMarkdown(preset)) yield* writeMarkdownlintConfig(fs, preset, force);
|
|
274
271
|
yield* syncBiomeSchemas();
|
|
275
|
-
if ((yield* fs.exists(config)) && !force) yield*
|
|
272
|
+
if ((yield* fs.exists(config)) && !force) yield* Output.warn(`${config} already exists (use --force to overwrite)`);
|
|
276
273
|
else {
|
|
277
274
|
const configDir = dirname(config);
|
|
278
275
|
if (configDir && configDir !== ".") yield* fs.makeDirectory(configDir, { recursive: true });
|
|
279
276
|
yield* fs.writeFileString(config, generateConfigContent(preset));
|
|
280
|
-
yield*
|
|
277
|
+
yield* Output.ok(`Created ${config} (preset: ${preset})`);
|
|
281
278
|
}
|
|
282
|
-
yield*
|
|
279
|
+
yield* Output.line("");
|
|
280
|
+
yield* Output.ok("lint-staged is ready to use");
|
|
283
281
|
});
|
|
284
282
|
}
|
|
285
283
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect, Option } from "effect";
|
|
4
6
|
|
|
@@ -43,18 +45,15 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
43
45
|
*/
|
|
44
46
|
const runReposAdd = (cwd, opts) => Effect.gen(function* () {
|
|
45
47
|
const result = yield* (yield* Repos.ReposManager).add(cwd, opts);
|
|
46
|
-
yield*
|
|
47
|
-
yield*
|
|
48
|
+
yield* Output.ok(`${result.name} @ ${result.ref} -> ${result.path}`);
|
|
49
|
+
yield* Output.detail("staged — review and commit");
|
|
48
50
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
49
|
-
if (error.kind === "missing") return
|
|
50
|
-
|
|
51
|
-
return Effect.log(error.message);
|
|
51
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
52
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
52
53
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
53
|
-
|
|
54
|
-
return Effect.log(error.message);
|
|
54
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
55
55
|
}), Effect.catchTag("ReposLockdownError", (error) => {
|
|
56
|
-
|
|
57
|
-
return Effect.log(error.message);
|
|
56
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
58
57
|
}));
|
|
59
58
|
/* v8 ignore start -- CLI registration; handler tested via runReposAdd */
|
|
60
59
|
const addCommand = Command.make("add", {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -46,15 +48,13 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
46
48
|
*/
|
|
47
49
|
const runReposDeregister = (cwd, section) => Effect.gen(function* () {
|
|
48
50
|
const result = yield* (yield* Repos.ReposManager).deregister(cwd, section);
|
|
49
|
-
yield*
|
|
50
|
-
for (const key of result.removedKeys) yield*
|
|
51
|
-
yield*
|
|
51
|
+
yield* Output.ok(`${result.section}: deregistered (${result.removedKeys.length} config keys removed)`);
|
|
52
|
+
for (const key of result.removedKeys) yield* Output.detail(`removed ${key}`);
|
|
53
|
+
yield* Output.detail("local git config only — nothing to commit");
|
|
52
54
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
53
|
-
|
|
54
|
-
return Effect.log(error.message);
|
|
55
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
55
56
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
56
|
-
|
|
57
|
-
return Effect.log(error.message);
|
|
57
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
58
58
|
}));
|
|
59
59
|
/* v8 ignore start -- CLI registration; handler tested via runReposDeregister */
|
|
60
60
|
const deregisterCommand = Command.make("deregister", {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -39,17 +41,14 @@ import { Effect } from "effect";
|
|
|
39
41
|
*/
|
|
40
42
|
const runReposNote = (cwd, name, op) => Effect.gen(function* () {
|
|
41
43
|
const result = yield* (yield* Repos.ReposManager).note(cwd, name, op);
|
|
42
|
-
yield*
|
|
44
|
+
yield* Output.ok(`${result.name}: ${result.op} note ${result.id} (${result.noteCount} notes)`);
|
|
43
45
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
44
|
-
if (error.kind === "missing") return
|
|
45
|
-
|
|
46
|
-
return Effect.log(error.message);
|
|
46
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
47
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
47
48
|
}), Effect.catchTag("RepoNotFoundError", (error) => {
|
|
48
|
-
|
|
49
|
-
return Effect.log(error.message);
|
|
49
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
50
50
|
}), Effect.catchTag("NoteNotFoundError", (error) => {
|
|
51
|
-
|
|
52
|
-
return Effect.log(error.message);
|
|
51
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
53
52
|
}));
|
|
54
53
|
/* v8 ignore start -- CLI registration; handler tested via runReposNote */
|
|
55
54
|
const nameArg = Argument.String("name");
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -39,23 +41,19 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
39
41
|
*/
|
|
40
42
|
const runReposPin = (cwd, name, ref) => Effect.gen(function* () {
|
|
41
43
|
const result = yield* (yield* Repos.ReposManager).pin(cwd, name, ref);
|
|
42
|
-
yield*
|
|
43
|
-
yield*
|
|
44
|
-
yield*
|
|
45
|
-
for (const staleId of result.staleNoteIds) yield*
|
|
44
|
+
yield* Output.ok(`${result.name}: ${result.oldCommit ?? "unknown"} -> ${result.newCommit}`);
|
|
45
|
+
yield* Output.detail(result.commitMessage);
|
|
46
|
+
yield* Output.detail("staged — review and commit");
|
|
47
|
+
for (const staleId of result.staleNoteIds) yield* Output.warn(`note ${staleId} is now stale against ${ref}`);
|
|
46
48
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
47
|
-
if (error.kind === "missing") return
|
|
48
|
-
|
|
49
|
-
return Effect.log(error.message);
|
|
49
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
50
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
50
51
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
51
|
-
|
|
52
|
-
return Effect.log(error.message);
|
|
52
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
53
53
|
}), Effect.catchTag("RepoNotFoundError", (error) => {
|
|
54
|
-
|
|
55
|
-
return Effect.log(error.message);
|
|
54
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
56
55
|
}), Effect.catchTag("ReposLockdownError", (error) => {
|
|
57
|
-
|
|
58
|
-
return Effect.log(error.message);
|
|
56
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
59
57
|
}));
|
|
60
58
|
/* v8 ignore start -- CLI registration; handler tested via runReposPin */
|
|
61
59
|
const pinCommand = Command.make("pin", {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -43,27 +45,23 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
43
45
|
*/
|
|
44
46
|
const runReposRemove = (cwd, name) => Effect.gen(function* () {
|
|
45
47
|
const result = yield* (yield* Repos.ReposManager).remove(cwd, name);
|
|
46
|
-
yield*
|
|
47
|
-
yield*
|
|
48
|
-
yield*
|
|
49
|
-
for (const note of result.removedNotes) yield*
|
|
48
|
+
yield* Output.ok(`${result.name}: removed (${result.path})`);
|
|
49
|
+
yield* Output.detail(result.commitMessage);
|
|
50
|
+
yield* Output.detail("staged — review and commit");
|
|
51
|
+
for (const note of result.removedNotes) yield* Output.warn(`note ${note.id} (${note.ref}) was removed with the entry — promote first if durable`);
|
|
50
52
|
if (result.removedEntry.orientation) {
|
|
51
|
-
yield*
|
|
52
|
-
yield*
|
|
53
|
+
yield* Output.warn(`the orientation block for ${result.name} was removed with the entry and add will NOT restore it — re-vendoring? capture it now:`);
|
|
54
|
+
yield* Output.line(JSON.stringify(result.removedEntry.orientation, null, 2));
|
|
53
55
|
}
|
|
54
56
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
55
|
-
if (error.kind === "missing") return
|
|
56
|
-
|
|
57
|
-
return Effect.log(error.message);
|
|
57
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
58
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
58
59
|
}), Effect.catchTag("RepoNotFoundError", (error) => {
|
|
59
|
-
|
|
60
|
-
return Effect.log(error.message);
|
|
60
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
61
61
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
62
|
-
|
|
63
|
-
return Effect.log(error.message);
|
|
62
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
64
63
|
}), Effect.catchTag("ReposLockdownError", (error) => {
|
|
65
|
-
|
|
66
|
-
return Effect.log(error.message);
|
|
64
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
67
65
|
}));
|
|
68
66
|
/* v8 ignore start -- CLI registration; handler tested via runReposRemove */
|
|
69
67
|
const removeCommand = Command.make("remove", {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -45,22 +47,18 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
45
47
|
*/
|
|
46
48
|
const runReposRename = (cwd, oldName, newName) => Effect.gen(function* () {
|
|
47
49
|
const result = yield* (yield* Repos.ReposManager).rename(cwd, oldName, newName);
|
|
48
|
-
yield*
|
|
49
|
-
yield*
|
|
50
|
-
yield*
|
|
50
|
+
yield* Output.ok(`${result.oldName}: renamed to ${result.newName} (${result.path})`);
|
|
51
|
+
yield* Output.detail(result.commitMessage);
|
|
52
|
+
yield* Output.detail("staged — review and commit");
|
|
51
53
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
52
|
-
if (error.kind === "missing") return
|
|
53
|
-
|
|
54
|
-
return Effect.log(error.message);
|
|
54
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
55
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
55
56
|
}), Effect.catchTag("RepoNotFoundError", (error) => {
|
|
56
|
-
|
|
57
|
-
return Effect.log(error.message);
|
|
57
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
58
58
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
59
|
-
|
|
60
|
-
return Effect.log(error.message);
|
|
59
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
61
60
|
}), Effect.catchTag("ReposLockdownError", (error) => {
|
|
62
|
-
|
|
63
|
-
return Effect.log(error.message);
|
|
61
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
64
62
|
}));
|
|
65
63
|
/* v8 ignore start -- CLI registration; handler tested via runReposRename */
|
|
66
64
|
const renameCommand = Command.make("rename", {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Output } from "../../../internal/output.js";
|
|
1
2
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
import { CliExit } from "@effected/cli";
|
|
2
4
|
import { Repos } from "@savvy-web/silk-effects";
|
|
3
5
|
import { Effect } from "effect";
|
|
4
6
|
|
|
@@ -50,24 +52,20 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
|
|
|
50
52
|
*/
|
|
51
53
|
const runReposRestore = (cwd, names) => Effect.gen(function* () {
|
|
52
54
|
const result = yield* (yield* Repos.ReposManager).restore(cwd, names.length > 0 ? names : void 0);
|
|
53
|
-
for (const entry of result.restored) yield*
|
|
54
|
-
for (const name of result.skippedClean) yield*
|
|
55
|
-
for (const name of result.stillDirty) yield*
|
|
56
|
-
if (result.stillDirty.length > 0)
|
|
57
|
-
if (result.restored.length === 0 && result.skippedClean.length === 0) yield*
|
|
55
|
+
for (const entry of result.restored) yield* Output.ok(`${entry.name}: restored to ${entry.commit}`);
|
|
56
|
+
for (const name of result.skippedClean) yield* Output.skip(`${name}: clean — skipped`);
|
|
57
|
+
for (const name of result.stillDirty) yield* Output.fail(`${name}: reset ran but the worktree is STILL dirty; run \`savvy repos status --drift\``);
|
|
58
|
+
if (result.stillDirty.length > 0) yield* CliExit.set(1);
|
|
59
|
+
if (result.restored.length === 0 && result.skippedClean.length === 0) yield* Output.ok("nothing to restore");
|
|
58
60
|
}).pipe(Effect.catchTag("ReposConfigError", (error) => {
|
|
59
|
-
if (error.kind === "missing") return
|
|
60
|
-
|
|
61
|
-
return Effect.log(error.message);
|
|
61
|
+
if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
|
|
62
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
62
63
|
}), Effect.catchTag("RepoNotFoundError", (error) => {
|
|
63
|
-
|
|
64
|
-
return Effect.log(error.message);
|
|
64
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
65
65
|
}), Effect.catchTag("GitSubmoduleError", (error) => {
|
|
66
|
-
|
|
67
|
-
return Effect.log(error.message);
|
|
66
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
68
67
|
}), Effect.catchTag("ReposLockdownError", (error) => {
|
|
69
|
-
|
|
70
|
-
return Effect.log(error.message);
|
|
68
|
+
return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
|
|
71
69
|
}));
|
|
72
70
|
/* v8 ignore start -- CLI registration; handler tested via runReposRestore */
|
|
73
71
|
const restoreCommand = Command.make("restore", {
|