@chanom/cli 0.0.1 → 0.0.3

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.
Files changed (3) hide show
  1. package/README.md +62 -0
  2. package/dist/index.js +159 -112
  3. package/package.json +3 -3
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @chanom/cli
2
+
3
+ Interactive CLI that brews dev tooling config into your project - oxlint, oxfmt, knip, and commit hooks, picked with a couple of prompts.
4
+
5
+ ## Install
6
+
7
+ Run it directly, no install needed:
8
+
9
+ ```sh
10
+ npx @chanom/cli brew
11
+ # or
12
+ pnpm dlx @chanom/cli brew
13
+ ```
14
+
15
+ or if you want to install it within your project:
16
+
17
+ ```sh
18
+ pnpm add -D @chanom/cli
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```sh
24
+ chanom brew
25
+ ```
26
+
27
+ `brew` walks you through two prompts:
28
+
29
+ 1. **Toppings** - which tools to add, any combination of:
30
+ - `oxlint` - fast Rust-based linter
31
+ - `oxfmt` - fast Rust-based formatter
32
+ - `knip` - dead code remover
33
+ 2. **Sweetness** - how strict to be:
34
+ - `light` - warn only, no blocking
35
+ - `medium` - block on commit via husky + lint-staged + commitlint
36
+
37
+ Based on your answers, `brew` will:
38
+
39
+ - Initialize a git repo if one doesn't exist yet (with a `.gitignore`)
40
+ - Detect your package manager (`pnpm`, `npm`, `yarn`, or `bun`) from `package.json`/`packageManager` or the invoking user agent
41
+ - Install the packages needed for your selected toppings, skipping any already installed
42
+ - Write config files (`oxlint.config.ts`, `oxfmt.config.ts`, `knip.config.ts`, `.commitlintrc.json`, `.lintstagedrc.json`) and wire up `package.json` scripts
43
+ - If `medium` sweetness is selected, run `husky init` and add `pre-commit` / `commit-msg` hooks
44
+ - Stage and commit the changes it made
45
+
46
+ ## What gets added
47
+
48
+ | Topping | Config file | Scripts added |
49
+ | -------- | ------------------ | ------------------------ |
50
+ | `oxlint` | `oxlint.config.ts` | `lint`, `lint:fix` |
51
+ | `oxfmt` | `oxfmt.config.ts` | `format`, `format:check` |
52
+ | `knip` | `knip.config.ts` | `knip` |
53
+
54
+ Config files re-export presets from [`@chanom/dev-config`](../dev-config).
55
+
56
+ `medium` sweetness additionally adds:
57
+
58
+ - `husky` - installed via `husky init`
59
+ - `lint-staged` - `.lintstagedrc.json` running the selected linter/formatter on staged files
60
+ - `commitlint` - `.commitlintrc.json` extending `@commitlint/config-conventional`, enforced in the `commit-msg` hook
61
+
62
+ Existing scripts and config are never overwritten - if a script name already exists in `package.json`, `brew` logs a warning and leaves it alone.
package/dist/index.js CHANGED
@@ -1,10 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import { intro, log, multiselect, outro, select, spinner } from "@clack/prompts";
3
- import pc from "picocolors";
4
3
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
- import { join } from "node:path";
4
+ import { basename, join } from "node:path";
5
+ import pc from "picocolors";
6
6
  import { spawnSync } from "node:child_process";
7
7
  //#region src/utils/detect-pm.ts
8
+ /** How to run a locally installed binary with each package manager. */
9
+ const PM_EXEC = {
10
+ pnpm: ["pnpm", "exec"],
11
+ npm: [
12
+ "npx",
13
+ "--no",
14
+ "--"
15
+ ],
16
+ yarn: ["yarn"],
17
+ bun: ["bunx"]
18
+ };
8
19
  const KNOWN_PMS = [
9
20
  "pnpm",
10
21
  "yarn",
@@ -92,14 +103,14 @@ function writeGitignore(cwd, contents = "node_modules\n") {
92
103
  //#endregion
93
104
  //#region src/utils/install.ts
94
105
  function installDev(pm, cwd, ...packages) {
95
- spawnSync(pm, [
106
+ return spawnSync(pm, [
96
107
  "add",
97
108
  "-D",
98
109
  ...packages
99
110
  ], {
100
111
  stdio: "inherit",
101
112
  cwd
102
- });
113
+ }).status === 0;
103
114
  }
104
115
  //#endregion
105
116
  //#region src/utils/pkg.ts
@@ -108,6 +119,7 @@ function isEsm(pkg) {
108
119
  }
109
120
  function readPkg(cwd) {
110
121
  const pkgPath = join(cwd, "package.json");
122
+ if (!existsSync(pkgPath)) throw new Error(`No package.json found in ${cwd}`);
111
123
  return {
112
124
  pkg: JSON.parse(readFileSync(pkgPath, "utf-8")),
113
125
  pkgPath
@@ -117,46 +129,90 @@ function isPackageInstalled(pkg, name) {
117
129
  return name in (pkg.dependencies ?? {}) || name in (pkg.devDependencies ?? {});
118
130
  }
119
131
  //#endregion
132
+ //#region src/utils/detect-setup.ts
133
+ const CONFIG_FILES = {
134
+ oxlint: [
135
+ "oxlint.config.js",
136
+ "oxlint.config.cjs",
137
+ "oxlint.config.mjs",
138
+ "oxlint.config.ts",
139
+ "oxlint.config.cts",
140
+ "oxlint.config.mts",
141
+ ".oxlintrc.json"
142
+ ],
143
+ oxfmt: [
144
+ "oxfmt.config.js",
145
+ "oxfmt.config.cjs",
146
+ "oxfmt.config.mjs",
147
+ "oxfmt.config.ts",
148
+ "oxfmt.config.cts",
149
+ "oxfmt.config.mts",
150
+ ".oxfmtrc.json"
151
+ ],
152
+ knip: [
153
+ "knip.json",
154
+ "knip.ts",
155
+ "knip.config.ts",
156
+ "knip.config.mts",
157
+ ".knip.json"
158
+ ],
159
+ husky: [".husky"],
160
+ "lint-staged": [
161
+ ".lintstagedrc.json",
162
+ ".lintstagedrc.js",
163
+ "lint-staged.config.js"
164
+ ],
165
+ commitlint: [
166
+ ".commitlintrc.json",
167
+ ".commitlintrc.js",
168
+ "commitlint.config.js"
169
+ ],
170
+ vite: [
171
+ "vite.config.ts",
172
+ "vite.config.js",
173
+ "vite.config.mjs"
174
+ ]
175
+ };
176
+ function detectSetupFile(type, cwd = process.cwd()) {
177
+ for (const filename of CONFIG_FILES[type]) {
178
+ const filepath = join(cwd, filename);
179
+ if (existsSync(filepath)) return filepath;
180
+ }
181
+ return null;
182
+ }
183
+ //#endregion
120
184
  //#region src/commands/add-commitlint/index.ts
121
- function getPackages$7(pkg) {
185
+ function getPackages$5(pkg) {
122
186
  const missing = [];
123
187
  if (!isPackageInstalled(pkg, "@commitlint/cli")) missing.push("@commitlint/cli");
124
188
  if (!isPackageInstalled(pkg, "@commitlint/config-conventional")) missing.push("@commitlint/config-conventional");
125
189
  return missing;
126
190
  }
127
- function apply$7(cwd) {
128
- writeFileSync(join(cwd, ".commitlintrc.json"), JSON.stringify({ extends: ["@commitlint/config-conventional"] }, null, 2) + "\n");
129
- if (existsSync(join(cwd, ".husky"))) writeFileSync(join(cwd, ".husky", "commit-msg"), "npx --no -- commitlint --edit $1\n");
130
- }
131
- //#endregion
132
- //#region src/commands/add-eslint/index.ts
133
- function getPackages$6(pkg) {
134
- return isPackageInstalled(pkg, "eslint") ? [] : ["eslint"];
135
- }
136
- function apply$6(cwd, esm) {
137
- writeFileSync(join(cwd, `eslint.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/eslint/config';\n`);
138
- const { pkg, pkgPath } = readPkg(cwd);
139
- pkg.scripts ??= {};
140
- const missing = {};
141
- if (!pkg.scripts["lint"]) missing["lint"] = "eslint .";
142
- if (!pkg.scripts["lint:fix"]) missing["lint:fix"] = "eslint . --fix";
143
- for (const [key, value] of Object.entries(missing)) pkg.scripts[key] = value;
144
- const skipped = ["lint", "lint:fix"].filter((k) => !(k in missing));
145
- for (const key of skipped) log.warn(`\`${key}\` script already exists in package.json - skipping`);
146
- if (Object.keys(missing).length > 0) writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
191
+ function apply$5(cwd, pm) {
192
+ const existing = detectSetupFile("commitlint", cwd);
193
+ if (existing) log.warn(`\`${basename(existing)}\` already exists - skipping commitlint config`);
194
+ else writeFileSync(join(cwd, ".commitlintrc.json"), JSON.stringify({ extends: ["@commitlint/config-conventional"] }, null, 2) + "\n");
195
+ if (existsSync(join(cwd, ".husky"))) {
196
+ const hookPath = join(cwd, ".husky", "commit-msg");
197
+ if (existsSync(hookPath)) log.warn("`.husky/commit-msg` already exists - skipping");
198
+ else writeFileSync(hookPath, [
199
+ ...PM_EXEC[pm],
200
+ "commitlint",
201
+ "--edit",
202
+ "$1"
203
+ ].join(" ") + "\n");
204
+ }
147
205
  }
148
206
  //#endregion
149
207
  //#region src/commands/add-husky/index.ts
150
- const PM_EXEC = {
151
- pnpm: ["pnpm", "exec"],
152
- npm: ["npx"],
153
- yarn: ["yarn"],
154
- bun: ["bunx"]
155
- };
156
- function getPackages$5(pkg) {
208
+ function getPackages$4(pkg) {
157
209
  return isPackageInstalled(pkg, "husky") ? [] : ["husky"];
158
210
  }
159
- function apply$5(cwd, pm) {
211
+ function apply$4(cwd, pm) {
212
+ if (existsSync(join(cwd, ".husky"))) {
213
+ log.warn("`.husky` already exists - skipping husky init");
214
+ return;
215
+ }
160
216
  const [cmd, ...args] = PM_EXEC[pm];
161
217
  spawnSync(cmd, [
162
218
  ...args,
@@ -168,46 +224,55 @@ function apply$5(cwd, pm) {
168
224
  });
169
225
  }
170
226
  //#endregion
227
+ //#region src/commands/add-knip/logic.ts
228
+ function getMissingScripts$2(scripts) {
229
+ const missing = {};
230
+ if (!scripts["knip"]) missing["knip"] = "knip";
231
+ return missing;
232
+ }
233
+ //#endregion
171
234
  //#region src/commands/add-knip/index.ts
172
- function getPackages$4(pkg) {
173
- return isPackageInstalled(pkg, "knip") ? [] : ["knip"];
235
+ function getPackages$3(pkg) {
236
+ return isPackageInstalled(pkg, "knip") ? [] : [`knip@6.23.0`, `@chanom/dev-config@0.0.3`];
174
237
  }
175
- function apply$4(cwd) {
176
- writeFileSync(join(cwd, "knip.json"), JSON.stringify({ entry: ["src/index.ts"] }, null, 2) + "\n");
238
+ function apply$3(cwd, esm) {
239
+ const existing = detectSetupFile("knip", cwd);
240
+ if (existing) log.warn(`\`${basename(existing)}\` already exists - skipping knip config`);
241
+ else writeFileSync(join(cwd, `knip.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/knip/config';\n`);
177
242
  const { pkg, pkgPath } = readPkg(cwd);
178
243
  pkg.scripts ??= {};
179
- if (pkg.scripts["knip"]) log.warn("`knip` script already exists in package.json - skipping");
180
- else {
181
- pkg.scripts["knip"] = "knip";
182
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
183
- }
244
+ const missing = getMissingScripts$2(pkg.scripts);
245
+ for (const [key, value] of Object.entries(missing)) pkg.scripts[key] = value;
246
+ const skipped = ["knip"].filter((k) => !(k in missing));
247
+ for (const key of skipped) log.warn(`\`${key}\` script already exists in package.json - skipping`);
248
+ if (Object.keys(missing).length > 0) writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
184
249
  }
185
250
  //#endregion
186
251
  //#region src/commands/add-lint-staged/index.ts
187
- function getPackages$3(pkg) {
252
+ function getPackages$2(pkg) {
188
253
  return isPackageInstalled(pkg, "lint-staged") ? [] : ["lint-staged"];
189
254
  }
190
- function apply$3(cwd, linters = [], formatters = []) {
191
- const config = {};
192
- const jsGlob = "**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}";
193
- if (linters.includes("oxlint")) {
194
- config[jsGlob] ??= [];
195
- config[jsGlob].push("oxlint --fix");
196
- }
197
- if (linters.includes("eslint")) {
198
- config[jsGlob] ??= [];
199
- config[jsGlob].push("eslint --fix");
200
- }
201
- if (formatters.includes("oxfmt")) {
202
- config["*"] ??= [];
203
- config["*"].push("oxfmt");
255
+ function apply$2(cwd, linters = [], formatters = [], overwritePreCommit = false) {
256
+ const existing = detectSetupFile("lint-staged", cwd);
257
+ if (existing) log.warn(`\`${basename(existing)}\` already exists - skipping lint-staged config`);
258
+ else {
259
+ const config = {};
260
+ const jsGlob = "**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}";
261
+ if (linters.includes("oxlint")) {
262
+ config[jsGlob] ??= [];
263
+ config[jsGlob].push("oxlint --fix");
264
+ }
265
+ if (formatters.includes("oxfmt")) {
266
+ config["*"] ??= [];
267
+ config["*"].push("oxfmt");
268
+ }
269
+ writeFileSync(join(cwd, ".lintstagedrc.json"), JSON.stringify(config, null, 2) + "\n");
204
270
  }
205
- if (formatters.includes("prettier")) {
206
- config["*"] ??= [];
207
- config["*"].push("prettier --ignore-unknown --write");
271
+ if (existsSync(join(cwd, ".husky"))) {
272
+ const hookPath = join(cwd, ".husky", "pre-commit");
273
+ if (existsSync(hookPath) && !overwritePreCommit) log.warn("`.husky/pre-commit` already exists - skipping");
274
+ else writeFileSync(hookPath, "lint-staged\n");
208
275
  }
209
- writeFileSync(join(cwd, ".lintstagedrc.json"), JSON.stringify(config, null, 2) + "\n");
210
- if (existsSync(join(cwd, ".husky"))) writeFileSync(join(cwd, ".husky", "pre-commit"), "lint-staged\n");
211
276
  }
212
277
  //#endregion
213
278
  //#region src/commands/add-oxfmt/logic.ts
@@ -219,11 +284,13 @@ function getMissingScripts$1(scripts) {
219
284
  }
220
285
  //#endregion
221
286
  //#region src/commands/add-oxfmt/index.ts
222
- function getPackages$2(pkg) {
223
- return isPackageInstalled(pkg, "oxfmt") ? [] : [`oxfmt@0.56.0`, "@chanom/dev-config"];
287
+ function getPackages$1(pkg) {
288
+ return isPackageInstalled(pkg, "oxfmt") ? [] : [`oxfmt@0.56.0`, `@chanom/dev-config@0.0.3`];
224
289
  }
225
- function apply$2(cwd, esm) {
226
- writeFileSync(join(cwd, `oxfmt.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/oxfmt/config';\n`);
290
+ function apply$1(cwd, esm) {
291
+ const existing = detectSetupFile("oxfmt", cwd);
292
+ if (existing) log.warn(`\`${basename(existing)}\` already exists - skipping oxfmt config`);
293
+ else writeFileSync(join(cwd, `oxfmt.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/oxfmt/config';\n`);
227
294
  const { pkg, pkgPath } = readPkg(cwd);
228
295
  pkg.scripts ??= {};
229
296
  const missing = getMissingScripts$1(pkg.scripts);
@@ -242,11 +309,13 @@ function getMissingScripts(scripts) {
242
309
  }
243
310
  //#endregion
244
311
  //#region src/commands/add-oxlint/index.ts
245
- function getPackages$1(pkg) {
246
- return isPackageInstalled(pkg, "oxlint") ? [] : [`oxlint@1.71.0`, "@chanom/dev-config"];
312
+ function getPackages(pkg) {
313
+ return isPackageInstalled(pkg, "oxlint") ? [] : [`oxlint@1.71.0`, `@chanom/dev-config@0.0.3`];
247
314
  }
248
- function apply$1(cwd, esm) {
249
- writeFileSync(join(cwd, `oxlint.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/oxlint/config';\n`);
315
+ function apply(cwd, esm) {
316
+ const existing = detectSetupFile("oxlint", cwd);
317
+ if (existing) log.warn(`\`${basename(existing)}\` already exists - skipping oxlint config`);
318
+ else writeFileSync(join(cwd, `oxlint.config.${esm ? "ts" : "mts"}`), `export { default } from '@chanom/dev-config/oxlint/config';\n`);
250
319
  const { pkg, pkgPath } = readPkg(cwd);
251
320
  pkg.scripts ??= {};
252
321
  const missing = getMissingScripts(pkg.scripts);
@@ -256,23 +325,6 @@ function apply$1(cwd, esm) {
256
325
  if (Object.keys(missing).length > 0) writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
257
326
  }
258
327
  //#endregion
259
- //#region src/commands/add-prettier/index.ts
260
- function getPackages(pkg) {
261
- return isPackageInstalled(pkg, "prettier") ? [] : ["prettier"];
262
- }
263
- function apply(cwd) {
264
- writeFileSync(join(cwd, ".prettierrc"), JSON.stringify({}, null, 2) + "\n");
265
- const { pkg, pkgPath } = readPkg(cwd);
266
- pkg.scripts ??= {};
267
- const missing = {};
268
- if (!pkg.scripts["format"]) missing["format"] = "prettier --write .";
269
- if (!pkg.scripts["format:check"]) missing["format:check"] = "prettier --check .";
270
- for (const [key, value] of Object.entries(missing)) pkg.scripts[key] = value;
271
- const skipped = ["format", "format:check"].filter((k) => !(k in missing));
272
- for (const key of skipped) log.warn(`\`${key}\` script already exists in package.json - skipping`);
273
- if (Object.keys(missing).length > 0) writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
274
- }
275
- //#endregion
276
328
  //#region src/commands/brew/index.ts
277
329
  async function brew(cwd = process.cwd()) {
278
330
  intro(pc.magenta("🧋 Chanom - Let's brew your project!"));
@@ -284,6 +336,10 @@ async function brew(cwd = process.cwd()) {
284
336
  s.stop("Git initialized");
285
337
  }
286
338
  const pm = detectPm(cwd);
339
+ if (!existsSync(join(cwd, "package.json"))) {
340
+ log.error(`No package.json found. Run \`${pm} init\` first, then re-run \`chanom brew\`.`);
341
+ process.exit(1);
342
+ }
287
343
  const toppings = await multiselect({
288
344
  message: "Which toppings would you like?",
289
345
  options: [
@@ -297,20 +353,10 @@ async function brew(cwd = process.cwd()) {
297
353
  label: "oxfmt",
298
354
  hint: "fast Rust-based formatter"
299
355
  },
300
- {
301
- value: "eslint",
302
- label: "eslint",
303
- hint: "stub config"
304
- },
305
- {
306
- value: "prettier",
307
- label: "prettier",
308
- hint: "stub config"
309
- },
310
356
  {
311
357
  value: "knip",
312
358
  label: "knip",
313
- hint: "stub config"
359
+ hint: "dead code remover"
314
360
  }
315
361
  ],
316
362
  required: false
@@ -332,28 +378,29 @@ async function brew(cwd = process.cwd()) {
332
378
  const { pkg } = readPkg(cwd);
333
379
  const esm = isEsm(pkg);
334
380
  const packages = [];
335
- if (toppings.includes("oxlint")) packages.push(...getPackages$1(pkg));
336
- if (toppings.includes("oxfmt")) packages.push(...getPackages$2(pkg));
337
- if (toppings.includes("eslint")) packages.push(...getPackages$6(pkg));
338
- if (toppings.includes("prettier")) packages.push(...getPackages(pkg));
339
- if (toppings.includes("knip")) packages.push(...getPackages$4(pkg));
340
- if (sweetness === "medium") packages.push(...getPackages$5(pkg), ...getPackages$3(pkg), ...getPackages$7(pkg));
381
+ if (toppings.includes("oxlint")) packages.push(...getPackages(pkg));
382
+ if (toppings.includes("oxfmt")) packages.push(...getPackages$1(pkg));
383
+ if (toppings.includes("knip")) packages.push(...getPackages$3(pkg));
384
+ if (sweetness === "medium") packages.push(...getPackages$4(pkg), ...getPackages$2(pkg), ...getPackages$5(pkg));
341
385
  if (packages.length > 0) {
342
386
  const s = spinner();
343
387
  const uniquePackages = Array.from(new Set(packages));
344
388
  s.start(`Installing ${uniquePackages.join(", ")}...`);
345
- installDev(pm, cwd, ...uniquePackages);
389
+ if (!installDev(pm, cwd, ...uniquePackages)) {
390
+ s.stop(pc.red("Package installation failed"));
391
+ log.error(`\`${pm} add -D\` failed. Fix the error above and re-run \`chanom brew\`.`);
392
+ process.exit(1);
393
+ }
346
394
  s.stop("Packages installed");
347
395
  }
348
- if (toppings.includes("oxlint")) apply$1(cwd, esm);
349
- if (toppings.includes("oxfmt")) apply$2(cwd, esm);
350
- if (toppings.includes("eslint")) apply$6(cwd, esm);
351
- if (toppings.includes("prettier")) apply(cwd);
352
- if (toppings.includes("knip")) apply$4(cwd);
396
+ if (toppings.includes("oxlint")) apply(cwd, esm);
397
+ if (toppings.includes("oxfmt")) apply$1(cwd, esm);
398
+ if (toppings.includes("knip")) apply$3(cwd, esm);
353
399
  if (sweetness === "medium") {
400
+ const huskyExisted = existsSync(join(cwd, ".husky"));
401
+ apply$4(cwd, pm);
402
+ apply$2(cwd, toppings.filter((t) => ["oxlint"].includes(t)), toppings.filter((t) => ["oxfmt"].includes(t)), !huskyExisted);
354
403
  apply$5(cwd, pm);
355
- apply$3(cwd, toppings.filter((t) => ["oxlint", "eslint"].includes(t)), toppings.filter((t) => ["oxfmt", "prettier"].includes(t)));
356
- apply$7(cwd);
357
404
  }
358
405
  const s2 = spinner();
359
406
  s2.start("Committing changes...");
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@chanom/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "",
5
5
  "keywords": [],
6
- "type": "module",
7
6
  "bin": {
8
7
  "chanom": "./dist/index.js"
9
8
  },
10
9
  "files": [
11
10
  "dist"
12
11
  ],
12
+ "type": "module",
13
13
  "dependencies": {
14
14
  "@clack/prompts": "1.6.0",
15
15
  "picocolors": "1.1.1"
@@ -20,7 +20,7 @@
20
20
  "@types/node": "26.0.1",
21
21
  "tsdown": "0.22.3",
22
22
  "typescript": "6.0.3",
23
- "@chanom/dev-config": "0.0.2"
23
+ "@chanom/dev-config": "0.0.3"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsdown",