@chanom/cli 0.2.0 → 0.2.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/README.md +9 -0
- package/dist/index.js +55 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,6 +53,11 @@ Based on your answers, `brew` will:
|
|
|
53
53
|
|
|
54
54
|
Cancelling a prompt (Ctrl+C) exits cleanly without touching anything further.
|
|
55
55
|
|
|
56
|
+
`medium` sweetness must be brewed at the git repository root - husky can only
|
|
57
|
+
install hooks from the directory that holds `.git`. Run inside a subdirectory
|
|
58
|
+
(e.g. a workspace package), `brew` stops with an error naming the root before
|
|
59
|
+
installing anything. `light` sweetness works anywhere in the repo.
|
|
60
|
+
|
|
56
61
|
## What gets added
|
|
57
62
|
|
|
58
63
|
| Topping | Config file | Scripts added |
|
|
@@ -71,6 +76,10 @@ Config files re-export presets from [`@chanom/dev-config`](../dev-config).
|
|
|
71
76
|
- `lint-staged` - `.lintstagedrc.json` running the selected linter/formatter on staged files, wired into the `pre-commit` hook
|
|
72
77
|
- `commitlint` - `.commitlintrc.json` extending `@commitlint/config-conventional`, enforced in the `commit-msg` hook
|
|
73
78
|
|
|
79
|
+
When neither `oxlint` nor `oxfmt` is selected, lint-staged is skipped entirely
|
|
80
|
+
(an empty config would fail every commit) and the `npm test` pre-commit hook
|
|
81
|
+
seeded by `husky init` is removed - only the `commit-msg` gate remains.
|
|
82
|
+
|
|
74
83
|
## Version pinning
|
|
75
84
|
|
|
76
85
|
The CLI bundles the tool versions its generated configs are known to work with (baked in at build time). `oxlint`, `oxfmt`, and `@chanom/dev-config` are (re)installed whenever the installed version doesn't match the pinned one exactly - in either direction, since the generated config must match. `knip`, `husky`, `lint-staged`, and the commitlint packages are only installed when missing.
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,9 @@ import pc from "picocolors";
|
|
|
6
6
|
import * as clack from "@clack/prompts";
|
|
7
7
|
//#region src/bundled-versions.ts
|
|
8
8
|
const bundledVersions = {
|
|
9
|
-
oxlint: "1.
|
|
10
|
-
oxfmt: "0.
|
|
11
|
-
knip: "6.
|
|
9
|
+
oxlint: "1.73.0",
|
|
10
|
+
oxfmt: "0.58.0",
|
|
11
|
+
knip: "6.26.0",
|
|
12
12
|
devConfig: "0.0.3"
|
|
13
13
|
};
|
|
14
14
|
//#endregion
|
|
@@ -101,7 +101,16 @@ var Git = class extends Effect.Service()("cli/Git", { effect: Effect.gen(functio
|
|
|
101
101
|
"--cached",
|
|
102
102
|
"--quiet"
|
|
103
103
|
], cwd).pipe(Effect.map((clean) => !clean)),
|
|
104
|
-
|
|
104
|
+
/**
|
|
105
|
+
* True when `cwd` is anywhere inside a git work tree — a `.git` folder
|
|
106
|
+
* check would miss subdirectories (e.g. workspace packages) and lead to
|
|
107
|
+
* nested `git init` calls.
|
|
108
|
+
*/
|
|
109
|
+
isRepo: (cwd) => exitOk(["rev-parse", "--is-inside-work-tree"], cwd),
|
|
110
|
+
/** Path of `cwd` relative to the work-tree root: empty at the root, `None` outside a repository. */
|
|
111
|
+
prefix: (cwd) => run(["rev-parse", "--show-prefix"], cwd).pipe(Effect.map((result) => result.ok ? Option.some(result.stdout) : Option.none())),
|
|
112
|
+
/** Absolute path of the work-tree root, or `None` outside a repository. */
|
|
113
|
+
root: (cwd) => run(["rev-parse", "--show-toplevel"], cwd).pipe(Effect.map((result) => result.ok ? Option.some(result.stdout) : Option.none())),
|
|
105
114
|
writeGitignore: (cwd, contents = "node_modules\n") => fs.writeFileString(path.join(cwd, ".gitignore"), contents),
|
|
106
115
|
readGitignore: (cwd) => fs.readFileString(path.join(cwd, ".gitignore"))
|
|
107
116
|
};
|
|
@@ -429,6 +438,10 @@ function buildConfig(linters, formatters) {
|
|
|
429
438
|
//#endregion
|
|
430
439
|
//#region src/commands/add-lint-staged/index.ts
|
|
431
440
|
const apply$2 = Effect.fn("add-lint-staged.apply")(function* (cwd, linters = [], formatters = [], overwritePreCommit = false) {
|
|
441
|
+
if (linters.length === 0 && formatters.length === 0) {
|
|
442
|
+
yield* Effect.logDebug("no linters or formatters selected - skipping lint-staged setup");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
432
445
|
const fs = yield* FileSystem.FileSystem;
|
|
433
446
|
const path = yield* Path.Path;
|
|
434
447
|
const prompter = yield* Prompter;
|
|
@@ -524,9 +537,17 @@ function planPackages(pkg, toppings, sweetness, versions) {
|
|
|
524
537
|
if (toppings.includes("oxlint")) packages.push(...getPackages(pkg, versions));
|
|
525
538
|
if (toppings.includes("oxfmt")) packages.push(...getPackages$1(pkg, versions));
|
|
526
539
|
if (toppings.includes("knip")) packages.push(...getPackages$3(pkg, versions));
|
|
527
|
-
if (sweetness === "medium")
|
|
540
|
+
if (sweetness === "medium") {
|
|
541
|
+
packages.push(...getPackages$4(pkg));
|
|
542
|
+
if (wantsLintStaged(toppings)) packages.push(...getPackages$2(pkg));
|
|
543
|
+
packages.push(...getPackages$5(pkg));
|
|
544
|
+
}
|
|
528
545
|
return [...new Set(packages)];
|
|
529
546
|
}
|
|
547
|
+
/** True when at least one topping gives lint-staged something to run. */
|
|
548
|
+
function wantsLintStaged(toppings) {
|
|
549
|
+
return selectedLinters(toppings).length > 0 || selectedFormatters(toppings).length > 0;
|
|
550
|
+
}
|
|
530
551
|
/** True when the .gitignore content already has the entry as its own line. */
|
|
531
552
|
function hasGitignoreEntry(content, entry) {
|
|
532
553
|
const forms = /* @__PURE__ */ new Set([
|
|
@@ -550,6 +571,7 @@ function selectedFormatters(toppings) {
|
|
|
550
571
|
}
|
|
551
572
|
//#endregion
|
|
552
573
|
//#region src/commands/brew/index.ts
|
|
574
|
+
var NotAtRepoRoot = class extends Data.TaggedError("NotAtRepoRoot") {};
|
|
553
575
|
const ensureGitRepo = Effect.fn("brew.ensureGitRepo")(function* (cwd) {
|
|
554
576
|
const git = yield* Git;
|
|
555
577
|
if (yield* git.isRepo(cwd)) {
|
|
@@ -635,13 +657,35 @@ const persistScripts = Effect.fn("brew.persistScripts")(function* (pkgPath, pkg,
|
|
|
635
657
|
yield* writePkg(pkgPath, updated);
|
|
636
658
|
}
|
|
637
659
|
});
|
|
660
|
+
/**
|
|
661
|
+
* Fails when medium sweetness is chosen away from the git work-tree root:
|
|
662
|
+
* husky can only install hooks from the directory that holds `.git`, so
|
|
663
|
+
* running it in e.g. a workspace package would leave hooks that never fire.
|
|
664
|
+
*/
|
|
665
|
+
const ensureRepoRootForHooks = Effect.fn("brew.ensureRepoRootForHooks")(function* (cwd, sweetness) {
|
|
666
|
+
if (sweetness !== "medium") return;
|
|
667
|
+
const git = yield* Git;
|
|
668
|
+
const prefix = yield* git.prefix(cwd);
|
|
669
|
+
if (Option.isNone(prefix) || prefix.value === "") return;
|
|
670
|
+
const root = yield* git.root(cwd);
|
|
671
|
+
return yield* new NotAtRepoRoot({
|
|
672
|
+
cwd,
|
|
673
|
+
root: Option.getOrElse(root, () => cwd)
|
|
674
|
+
});
|
|
675
|
+
});
|
|
638
676
|
const setupCommitGate = Effect.fn("brew.setupCommitGate")(function* (cwd, pm, toppings, sweetness) {
|
|
639
677
|
if (sweetness !== "medium") return;
|
|
640
678
|
const fs = yield* FileSystem.FileSystem;
|
|
641
679
|
const path = yield* Path.Path;
|
|
680
|
+
const prompter = yield* Prompter;
|
|
642
681
|
const huskyExisted = yield* fs.exists(path.join(cwd, ".husky"));
|
|
643
682
|
yield* apply$4(cwd, pm);
|
|
644
|
-
yield* apply$2(cwd, selectedLinters(toppings), selectedFormatters(toppings), !huskyExisted);
|
|
683
|
+
if (wantsLintStaged(toppings)) yield* apply$2(cwd, selectedLinters(toppings), selectedFormatters(toppings), !huskyExisted);
|
|
684
|
+
else {
|
|
685
|
+
yield* prompter.warn("No linters or formatters selected - skipping lint-staged");
|
|
686
|
+
const seededHook = path.join(cwd, ".husky", "pre-commit");
|
|
687
|
+
if (!huskyExisted && (yield* fs.exists(seededHook))) yield* fs.remove(seededHook);
|
|
688
|
+
}
|
|
645
689
|
yield* apply$5(cwd, pm);
|
|
646
690
|
});
|
|
647
691
|
const commitChanges = Effect.fn("brew.commitChanges")(function* (cwd) {
|
|
@@ -673,10 +717,12 @@ const brew = (cwd = process.cwd()) => Effect.gen(function* () {
|
|
|
673
717
|
const { pkg, pkgPath } = yield* readPkg(cwd);
|
|
674
718
|
const { toppings, sweetness } = yield* askRecipe();
|
|
675
719
|
yield* Effect.logDebug(`recipe: toppings=[${toppings.join(", ")}] sweetness=${sweetness} esm=${isEsm(pkg)}`);
|
|
720
|
+
yield* ensureRepoRootForHooks(cwd, sweetness);
|
|
676
721
|
const packages = planPackages(pkg, toppings, sweetness, bundledVersions);
|
|
677
722
|
yield* Effect.logDebug(`planned packages: ${packages.length > 0 ? packages.join(", ") : "(none)"}`);
|
|
678
723
|
yield* installPackages(pm, cwd, packages);
|
|
679
|
-
|
|
724
|
+
const { pkg: installedPkg } = yield* readPkg(cwd);
|
|
725
|
+
yield* persistScripts(pkgPath, installedPkg, yield* applyToppings(cwd, isEsm(installedPkg), installedPkg, toppings));
|
|
680
726
|
yield* setupCommitGate(cwd, pm, toppings, sweetness);
|
|
681
727
|
yield* commitChanges(cwd);
|
|
682
728
|
yield* prompter.outro(pc.green(`Your Chanom with ${toppings.join(", ")} toppings and ${sweetness} sweetness is ready! Enjoy! 🧋`));
|
|
@@ -708,7 +754,8 @@ const run = (name, cwd = process.cwd(), options = {}) => {
|
|
|
708
754
|
PkgNotFound: (e) => reportError(`No package.json found in ${e.cwd}. Run this inside a project.`),
|
|
709
755
|
PkgInvalid: (e) => reportError(`Could not parse ${e.pkgPath}. Is it valid JSON?`),
|
|
710
756
|
InstallFailed: (e) => reportError(`Package installation with ${e.pm} failed.`),
|
|
711
|
-
HuskyInitFailed: (e) => reportError(`\`husky init\` failed (exit code ${e.exitCode}).`)
|
|
757
|
+
HuskyInitFailed: (e) => reportError(`\`husky init\` failed (exit code ${e.exitCode}).`),
|
|
758
|
+
NotAtRepoRoot: (e) => reportError(`Git hooks must be set up at the repository root: ${e.root}\nRun \`chanom brew\` there, or choose light sweetness.`)
|
|
712
759
|
}), Effect.catchAll((e) => reportError(`Unexpected error: ${e.message}`)), Logger.withMinimumLogLevel(debug ? LogLevel.Debug : LogLevel.Info));
|
|
713
760
|
};
|
|
714
761
|
//#endregion
|