@letstri/oxc-config 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +20 -15
  2. package/dist/cli.mjs +72 -72
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -13,14 +13,23 @@ Opinionated, shared [oxlint](https://oxc.rs/docs/guide/usage/linter.html) and [o
13
13
  npm i -D @letstri/oxc-config oxlint oxfmt
14
14
  ```
15
15
 
16
- Then scaffold the config files and editor settings with the `oxc-config` CLI:
16
+ Then scaffold everything with the `oxc-config` CLI:
17
17
 
18
18
  ```bash
19
- npx oxc-config config # create oxlint.config.ts + oxfmt.config.ts
20
- npx oxc-config editors # write .vscode + .zed settings
19
+ npx oxc-config init
21
20
  ```
22
21
 
23
- Run `oxc-config --help` for all commands. Details: [Usage](#usage) and
22
+ `init` prompts you to pick what to set up — oxlint config, oxfmt config, VS Code,
23
+ Zed. Pass flags to skip the prompt (useful in CI/scripts):
24
+
25
+ ```bash
26
+ npx oxc-config init --oxlint --oxfmt # just the config files
27
+ npx oxc-config init --vscode --zed # just the editor settings
28
+ ```
29
+
30
+ With flags, only the chosen targets run; with none in a non-interactive shell,
31
+ all four run. Config files are skipped if they already exist (`--force` to
32
+ overwrite); editor settings are deep-merged into any existing files. See
24
33
  [Editor setup](#editor-setup).
25
34
 
26
35
  ## AI setup prompt
@@ -157,16 +166,12 @@ If the plugin is missing, `tailwind()` throws with an install hint.
157
166
  Both editors use the official [oxc](https://oxc.rs) tooling — `oxlint` for
158
167
  linting and `oxfmt` for formatting — replacing ESLint and Prettier.
159
168
 
160
- The `oxc-config editors` command writes (or updates) the editor configs for you,
161
- deep-merging into any existing files so your other settings are kept:
169
+ `oxc-config init` writes (or updates) the editor configs for you, deep-merging
170
+ into any existing files so your other settings are kept:
162
171
 
163
172
  ```bash
164
173
  # both editors
165
- pnpm exec oxc-config editors
166
-
167
- # or just one
168
- pnpm exec oxc-config editors --vscode
169
- pnpm exec oxc-config editors --zed
174
+ pnpm exec oxc-config init --vscode --zed
170
175
  ```
171
176
 
172
177
  It's idempotent — safe to re-run to pull the latest recommended settings.
@@ -174,13 +179,13 @@ It's idempotent — safe to re-run to pull the latest recommended settings.
174
179
  ### VS Code
175
180
 
176
181
  Install the [`oxc.oxc-vscode`](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode)
177
- extension (`oxc-config editors --vscode` also adds it to `.vscode/extensions.json`). The
178
- CLI writes `.vscode/settings.json` — oxlint as linter, oxfmt as the default
179
- formatter with format-on-save, and Prettier's import organization turned off.
182
+ extension (`init --vscode` also adds it to `.vscode/extensions.json`). It writes
183
+ `.vscode/settings.json` — oxlint as linter, oxfmt as the default formatter with
184
+ format-on-save, and Prettier's import organization turned off.
180
185
 
181
186
  ### Zed
182
187
 
183
188
  Zed ships with the oxc language servers built in, so no extension is needed.
184
- `oxc-config editors --zed` writes `.zed/settings.json` — oxfmt as the formatter (with
189
+ `init --zed` writes `.zed/settings.json` — oxfmt as the formatter (with
185
190
  `source.fixAll.oxc` on save for JS/TS) and Prettier disabled, across the file
186
191
  types oxfmt supports.
package/dist/cli.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
  import process from "node:process";
3
3
  import { createDefu } from "defu";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, resolve } from "node:path";
6
- import { boolean, command, run } from "@drizzle-team/brocli";
5
+ import { dirname } from "node:path";
6
+ import { cancel, isCI, isCancel, multiselect } from "@clack/prompts";
7
7
  import { parse } from "jsonc-parser";
8
8
  //#region src/editors.ts
9
9
  /**
@@ -73,93 +73,93 @@ const zedSettings = {
73
73
  };
74
74
  //#endregion
75
75
  //#region src/cli.ts
76
- /**
77
- * Like defu, but arrays are merged as a de-duplicated union instead of being
78
- * concatenated — keeps updates idempotent and preserves user-added entries.
79
- */
80
- const mergeConfig = createDefu((obj, key, value) => {
76
+ const log = (message) => process.stdout.write(`${message}\n`);
77
+ const merge = createDefu((obj, key, value) => {
81
78
  const current = obj[key];
82
79
  if (Array.isArray(current) && Array.isArray(value)) {
83
- const seen = /* @__PURE__ */ new Set();
84
- obj[key] = [...value, ...current].filter((item) => {
85
- const id = JSON.stringify(item);
86
- if (seen.has(id)) return false;
87
- seen.add(id);
88
- return true;
89
- });
80
+ const union = [...value, ...current].map((item) => JSON.stringify(item));
81
+ obj[key] = [...new Set(union)].map((item) => JSON.parse(item));
90
82
  return true;
91
83
  }
92
84
  return false;
93
85
  });
94
- function readJsonc(path) {
95
- if (!existsSync(path)) return {};
96
- return parse(readFileSync(path, "utf-8"), [], { allowTrailingComma: true }) ?? {};
97
- }
98
- /** Deep-merge `base` into the JSON(C) file at `path`, creating it if absent. */
99
- function mergeJsonFile(path, base) {
86
+ function mergeJson(path, base) {
100
87
  const existed = existsSync(path);
101
- const merged = mergeConfig(base, readJsonc(path));
88
+ const current = existed ? parse(readFileSync(path, "utf-8"), [], { allowTrailingComma: true }) : {};
102
89
  mkdirSync(dirname(path), { recursive: true });
103
- writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`);
90
+ writeFileSync(path, `${JSON.stringify(merge(base, current ?? {}), null, 2)}\n`);
104
91
  return existed ? "updated" : "created";
105
92
  }
106
- /** Write `content` to `path`, skipping an existing file unless `force`. */
107
- function writeFile(path, content, force) {
93
+ function writeConfig(path, content, force) {
108
94
  const existed = existsSync(path);
109
95
  if (existed && !force) return "skipped";
110
- mkdirSync(dirname(path), { recursive: true });
111
96
  writeFileSync(path, content);
112
97
  return existed ? "overwritten" : "created";
113
98
  }
114
- const OXLINT_CONFIG = `import { oxlintConfig } from '@letstri/oxc-config'
99
+ const template = (fn) => `import { ${fn} } from '@letstri/oxc-config'\n\nexport default ${fn}()\n`;
100
+ const TARGETS = {
101
+ oxlint: {
102
+ label: "oxlint.config.ts",
103
+ run: (force) => `oxlint: ${writeConfig("oxlint.config.ts", template("oxlintConfig"), force)}`
104
+ },
105
+ oxfmt: {
106
+ label: "oxfmt.config.ts",
107
+ run: (force) => `oxfmt: ${writeConfig("oxfmt.config.ts", template("oxfmtConfig"), force)}`
108
+ },
109
+ vscode: {
110
+ label: "VS Code settings (.vscode)",
111
+ run: () => `vscode: ${mergeJson(".vscode/settings.json", vscodeSettings)} settings, ${mergeJson(".vscode/extensions.json", vscodeExtensions)} extensions`
112
+ },
113
+ zed: {
114
+ label: "Zed settings (.zed)",
115
+ run: () => `zed: ${mergeJson(".zed/settings.json", zedSettings)}`
116
+ }
117
+ };
118
+ const ALL = Object.keys(TARGETS);
119
+ async function pickTargets(flags) {
120
+ const flagged = ALL.filter((target) => flags.has(`--${target}`));
121
+ if (flagged.length > 0) return flagged;
122
+ if (isCI() || !process.stdout.isTTY) return ALL;
123
+ const selected = await multiselect({
124
+ message: "What do you want to set up?",
125
+ options: ALL.map((value) => ({
126
+ value,
127
+ label: TARGETS[value].label
128
+ })),
129
+ initialValues: ALL,
130
+ required: true
131
+ });
132
+ if (isCancel(selected)) {
133
+ cancel("Cancelled.");
134
+ return null;
135
+ }
136
+ return selected;
137
+ }
138
+ function version() {
139
+ const url = new URL("../package.json", import.meta.url);
140
+ return JSON.parse(readFileSync(url, "utf-8")).version;
141
+ }
142
+ const HELP = `oxc-config — set up @letstri/oxc-config in your project
115
143
 
116
- export default oxlintConfig()
117
- `;
118
- const OXFMT_CONFIG = `import { oxfmtConfig } from '@letstri/oxc-config'
144
+ Usage:
145
+ oxc-config [flags]
119
146
 
120
- export default oxfmtConfig()
121
- `;
122
- function version() {
123
- try {
124
- return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8")).version ?? "0.0.0";
125
- } catch {
126
- return "0.0.0";
127
- }
147
+ Flags (default: prompt for what to set up):
148
+ --oxlint create oxlint.config.ts
149
+ --oxfmt create oxfmt.config.ts
150
+ --vscode write .vscode settings
151
+ --zed write .zed settings
152
+ -f, --force overwrite existing config files
153
+ -h, --help show this help
154
+ -v, --version show version`;
155
+ const argv = process.argv.slice(2);
156
+ if (argv.includes("--help") || argv.includes("-h")) log(HELP);
157
+ else if (argv.includes("--version") || argv.includes("-v")) log(version());
158
+ else {
159
+ const flags = new Set(argv);
160
+ const force = flags.has("--force") || flags.has("-f");
161
+ const targets = await pickTargets(flags);
162
+ for (const target of targets ?? []) log(TARGETS[target].run(force));
128
163
  }
129
- run([command({
130
- name: "config",
131
- desc: "Create oxlint.config.ts and oxfmt.config.ts",
132
- options: { force: boolean().alias("f").desc("Overwrite existing files").default(false) },
133
- handler: ({ force }) => {
134
- const cwd = process.cwd();
135
- const lint = writeFile(resolve(cwd, "oxlint.config.ts"), OXLINT_CONFIG, force);
136
- const fmt = writeFile(resolve(cwd, "oxfmt.config.ts"), OXFMT_CONFIG, force);
137
- process.stdout.write(`config: ${lint} oxlint.config.ts, ${fmt} oxfmt.config.ts\n`);
138
- }
139
- }), command({
140
- name: "editors",
141
- desc: "Write/update VS Code and Zed editor configs (deep-merged into existing files)",
142
- options: {
143
- vscode: boolean().desc("Only VS Code").default(false),
144
- zed: boolean().desc("Only Zed").default(false)
145
- },
146
- handler: ({ vscode, zed }) => {
147
- const both = !vscode && !zed;
148
- const cwd = process.cwd();
149
- if (both || vscode) {
150
- const s = mergeJsonFile(resolve(cwd, ".vscode/settings.json"), vscodeSettings);
151
- const e = mergeJsonFile(resolve(cwd, ".vscode/extensions.json"), vscodeExtensions);
152
- process.stdout.write(`vscode: ${s} .vscode/settings.json, ${e} .vscode/extensions.json\n`);
153
- }
154
- if (both || zed) {
155
- const s = mergeJsonFile(resolve(cwd, ".zed/settings.json"), zedSettings);
156
- process.stdout.write(`zed: ${s} .zed/settings.json\n`);
157
- }
158
- }
159
- })], {
160
- name: "oxc-config",
161
- description: "Set up @letstri/oxc-config in your project",
162
- version: version()
163
- });
164
164
  //#endregion
165
165
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letstri/oxc-config",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs",
5
5
  "homepage": "https://github.com/letstri/oxc-config#readme",
6
6
  "bugs": {
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@drizzle-team/brocli": "^0.12.0",
34
+ "@clack/prompts": "^1.7.0",
35
35
  "defu": "^6.1.7",
36
36
  "jsonc-parser": "^3.3.1"
37
37
  },