adamantite 0.13.2 → 0.14.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/README.md CHANGED
@@ -165,7 +165,7 @@ Adamantite's Biome preset includes:
165
165
  - React/JSX patterns
166
166
  - **File Patterns**: Pre-configured for TypeScript, JavaScript, JSON, and more
167
167
 
168
- ### TypeScript Configuration ([presets/tsconfig.json](./src/presets/tsconfig.json))
168
+ ### TypeScript Configuration ([presets/tsconfig.json](./presets/tsconfig.json))
169
169
 
170
170
  The TypeScript preset includes strict settings for maximum type safety:
171
171
 
package/bin/adamantite CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import("../dist/index.js")
3
+ import("../dist/index.mjs")
package/dist/index.mjs CHANGED
@@ -1,12 +1,14 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
- import { execSync } from "child_process";
3
+ import process$1 from "process";
4
+ import { err, fromPromise, fromSafePromise, fromThrowable, ok, safeTry } from "neverthrow";
4
5
  import { addDevDependency, detectPackageManager, dlxCommand } from "nypm";
5
6
  import { access, mkdir, readFile, writeFile } from "fs/promises";
6
7
  import { join } from "path";
8
+ import { Fault } from "faultier";
9
+ import { execSync } from "child_process";
7
10
  import defu from "defu";
8
11
  import { parse } from "jsonc-parser";
9
- import process$1 from "process";
10
12
  import { cancel, confirm, intro, isCancel, log, multiselect, outro, spinner } from "@clack/prompts";
11
13
  import { readFileSync } from "fs";
12
14
 
@@ -14,51 +16,20 @@ import { readFileSync } from "fs";
14
16
  function defineCommand(input) {
15
17
  return input;
16
18
  }
17
- async function getPackageManagerName() {
18
- const result = await detectPackageManager(process$1.cwd());
19
- if (!result) throw new Error("No package manager found");
20
- const { warnings, ...packageManager } = result;
21
- if (warnings && warnings.length > 0) console.warn(warnings.join("\n"));
22
- return packageManager.name;
23
- }
24
- function handleCommandError(error) {
25
- const message = error instanceof Error ? error.message : "An unknown error occurred";
26
- console.error(message);
27
- cancel("Failed to run Adamantite");
28
- process$1.exit(1);
29
- }
30
- async function checkIfExists(path) {
31
- try {
32
- await access(path);
33
- return true;
34
- } catch {
35
- return false;
36
- }
37
- }
38
- /**
39
- * Reads and parses package.json with proper typing
40
- */
41
- async function readPackageJson(cwd = process$1.cwd()) {
42
- const currentPath = join(cwd, "package.json");
43
- if (!await checkIfExists(currentPath)) throw new Error("package.json not found in the current directory");
44
- try {
45
- const content = await readFile(currentPath, "utf-8");
46
- return JSON.parse(content);
47
- } catch (error) {
48
- throw new Error(`Failed to parse package.json: ${error instanceof Error ? error.message : "Unknown error"}`);
49
- }
50
- }
51
- /**
52
- * Writes package.json with proper formatting
53
- */
54
- async function writePackageJson(packageJson, cwd = process$1.cwd()) {
55
- const currentPath = join(cwd, "package.json");
56
- try {
57
- await writeFile(currentPath, JSON.stringify(packageJson, null, 2));
58
- } catch (error) {
59
- throw new Error(`Failed to write package.json: ${error instanceof Error ? error.message : "Unknown error"}`);
60
- }
61
- }
19
+ const runCommand = fromThrowable(execSync, (error) => Fault.wrap(error).withTag("FAILED_TO_RUN_COMMAND"));
20
+ const getPackageManagerName = () => fromPromise(detectPackageManager(process$1.cwd()), () => Fault.create("NO_PACKAGE_MANAGER").withDescription("Error while detecting the package manager.", "We're unable to detect the package manager used in this project.")).andThen((result) => {
21
+ if (!result) return err(Fault.create("NO_PACKAGE_MANAGER").withDescription("No package manager detected.", "We're unable to detect the package manager used in this project."));
22
+ return ok(result.name);
23
+ });
24
+ const checkIfExists = (path) => fromPromise(access(path), () => /* @__PURE__ */ new Error("File not found")).match(() => true, () => false);
25
+ const parseJson = (content) => {
26
+ const errors = [];
27
+ const parsed = parse(content, errors);
28
+ if (errors.length > 0) return err(Fault.create("FAILED_TO_PARSE_FILE").withDescription("Failed to parse JSON", "We're unable to parse the provided JSON file.").withContext({ errors }));
29
+ return ok(parsed);
30
+ };
31
+ const mergeConfig = fromThrowable(defu, (error) => Fault.wrap(error).withTag("FAILED_TO_MERGE_CONFIG").withDescription("Failed to merge configuration", "We're unable to merge the configuration files."));
32
+ const readPackageJson = (cwd = process$1.cwd()) => fromPromise(readFile(join(cwd, "package.json"), "utf-8"), (error) => Fault.wrap(error).withTag("FAILED_TO_READ_FILE").withDescription("Failed to read package.json", "We're unable to read the package.json file in the current directory.").withContext({ path: join(cwd, "package.json") })).andThen((content) => parseJson(content)).andThen((parsed) => ok(parsed));
62
33
  function getTitle() {
63
34
  if ((process$1.stdout.columns || 80) >= 120) return `
64
35
  █████ █████ ███ █████
@@ -78,96 +49,68 @@ function getTitle() {
78
49
  }
79
50
 
80
51
  //#endregion
81
- //#region src/commands/helpers.ts
82
- const tsconfig = {
83
- name: "tsconfig",
84
- config: { extends: "adamantite/tsconfig" },
85
- async exists() {
86
- return await checkIfExists(join(process.cwd(), "tsconfig.json"));
87
- },
88
- async create() {
89
- await writeFile(join(process.cwd(), "tsconfig.json"), JSON.stringify(this.config, null, 2));
90
- },
91
- async update() {
92
- const existingConfig = parse(await readFile(join(process.cwd(), "tsconfig.json"), "utf-8"));
93
- const newConfig = defu(this.config, existingConfig);
94
- await writeFile(join(process.cwd(), "tsconfig.json"), JSON.stringify(newConfig, null, 2));
95
- }
96
- };
52
+ //#region src/helpers/packages/biome.ts
97
53
  const biome = {
98
54
  name: "@biomejs/biome",
99
55
  version: "2.3.10",
100
56
  config: { $schema: "./node_modules/@biomejs/biome/configuration_schema.json" },
101
- async exists() {
102
- return await checkIfExists(join(process.cwd(), "biome.jsonc"));
57
+ exists: async () => {
58
+ if (await checkIfExists(join(process.cwd(), "biome.jsonc"))) return { path: join(process.cwd(), "biome.jsonc") };
59
+ if (await checkIfExists(join(process.cwd(), "biome.json"))) return { path: join(process.cwd(), "biome.json") };
60
+ return { path: null };
103
61
  },
104
- async create() {
105
- await writeFile(join(process.cwd(), "biome.jsonc"), JSON.stringify({
106
- ...this.config,
107
- extends: ["adamantite"]
108
- }, null, 2));
109
- },
110
- async update() {
111
- const newConfig = { ...parse(await readFile(await checkIfExists(join(process.cwd(), "biome.jsonc")) ? join(process.cwd(), "biome.jsonc") : join(process.cwd(), "biome.json"), "utf-8")) };
62
+ create: () => fromPromise(writeFile(join(process.cwd(), "biome.jsonc"), JSON.stringify({
63
+ ...biome.config,
64
+ extends: ["adamantite"]
65
+ }, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write Biome configuration", "We're unable to write the Biome configuration to the current directory.")),
66
+ update: () => safeTry(async function* () {
67
+ const exists = await biome.exists();
68
+ if (!exists.path) return err(Fault.create("FILE_NOT_FOUND").withDescription("No `biome.jsonc` or `biome.json` found", "We're unable to find a Biome configuration in the current directory."));
69
+ const existingConfig = yield* parseJson(yield* fromPromise(readFile(exists.path, "utf-8"), (error) => Fault.wrap(error).withTag("FAILED_TO_READ_FILE").withDescription("Failed to read Biome configuration", "We're unable to read the Biome configuration from the current directory.")));
70
+ if (!existingConfig || Object.keys(existingConfig).length === 0) return err(Fault.create("INVALID_BIOME_CONFIG").withDescription("Invalid Biome configuration", "The Biome configuration file is empty or invalid.").withContext({ path: exists.path }));
71
+ const newConfig = { ...existingConfig };
112
72
  if (!Array.isArray(newConfig.extends)) newConfig.extends = newConfig.extends ? [newConfig.extends] : [];
113
73
  if (!newConfig.extends.includes("adamantite")) newConfig.extends.push("adamantite");
114
- const mergedConfig = defu(this.config, newConfig);
115
- await writeFile(join(process.cwd(), "biome.jsonc"), JSON.stringify(mergedConfig, null, 2));
116
- }
74
+ const mergedConfig = yield* mergeConfig(newConfig, biome.config);
75
+ mergedConfig.$schema = biome.config.$schema;
76
+ yield* fromPromise(writeFile(exists.path, JSON.stringify(mergedConfig, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write Biome configuration", "We're unable to write the Biome configuration to the current directory.").withContext({ path: exists.path }));
77
+ return ok();
78
+ })
117
79
  };
118
- const vscode = {
119
- name: ".vscode",
120
- config: {
121
- "typescript.tsdk": "node_modules/typescript/lib",
122
- "editor.formatOnSave": true,
123
- "editor.formatOnPaste": true,
124
- "editor.codeActionsOnSave": {
125
- "source.organizeImports.biome": "explicit",
126
- "source.fixAll.biome": "explicit"
127
- },
128
- "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": { "editor.defaultFormatter": "biomejs.biome" }
129
- },
130
- async exists() {
131
- return await checkIfExists(join(process.cwd(), ".vscode", "settings.json"));
132
- },
133
- async create() {
134
- const vscodePath = join(process.cwd(), ".vscode");
135
- await mkdir(vscodePath, { recursive: true });
136
- await writeFile(join(vscodePath, "settings.json"), JSON.stringify(this.config, null, 2));
137
- },
138
- async update() {
139
- const existingConfig = parse(await readFile(join(process.cwd(), ".vscode", "settings.json"), "utf-8"));
140
- const newConfig = defu(this.config, existingConfig);
141
- await writeFile(join(process.cwd(), ".vscode", "settings.json"), JSON.stringify(newConfig, null, 2));
142
- }
143
- };
144
- const sherif = { version: "1.9.0" };
145
80
 
146
81
  //#endregion
147
82
  //#region src/commands/check.ts
148
83
  var check_default = defineCommand({
149
- command: "check",
84
+ command: "check [files..]",
150
85
  describe: "Run Biome linter and check files for issues",
151
86
  builder: (yargs$1) => yargs$1.positional("files", {
152
87
  describe: "Specific files to lint (optional)",
153
- type: "string"
88
+ type: "string",
89
+ array: true
154
90
  }).option("summary", {
155
91
  type: "boolean",
156
92
  description: "Show summary of lint results"
157
93
  }),
158
94
  handler: async (argv) => {
159
- try {
160
- const packageManager = await getPackageManagerName();
95
+ if ((await safeTry(async function* () {
96
+ const packageManager = yield* getPackageManagerName();
161
97
  const args = ["check"];
162
98
  if (argv.summary) args.push("--reporter", "summary");
163
99
  if (argv.files && argv.files.length > 0) args.push(...argv.files);
164
- execSync(dlxCommand(packageManager, biome.name, { args }), { stdio: "inherit" });
165
- } catch (error) {
166
- handleCommandError(error);
167
- }
100
+ yield* runCommand(dlxCommand(packageManager, biome.name, { args }), { stdio: "inherit" });
101
+ return ok(void 0);
102
+ })).isOk()) return;
103
+ process$1.exit(1);
168
104
  }
169
105
  });
170
106
 
107
+ //#endregion
108
+ //#region src/helpers/packages/sherif.ts
109
+ const sherif = {
110
+ name: "sherif",
111
+ version: "1.9.0"
112
+ };
113
+
171
114
  //#endregion
172
115
  //#region src/commands/ci.ts
173
116
  var ci_default = defineCommand({
@@ -181,174 +124,192 @@ var ci_default = defineCommand({
181
124
  description: "Use GitHub reporter"
182
125
  }),
183
126
  handler: async (argv) => {
184
- try {
185
- const packageManager = await getPackageManagerName();
127
+ if ((await safeTry(async function* () {
128
+ const packageManager = yield* getPackageManagerName();
186
129
  const tools = [{
187
- package: "@biomejs/biome",
130
+ package: biome.name,
188
131
  args: ["ci", ...argv.github ? ["--reporter", "github"] : []]
189
- }, ...argv.monorepo ? [{
190
- package: "sherif",
132
+ }];
133
+ if (argv.monorepo) tools.push({
134
+ package: sherif.name,
191
135
  args: []
192
- }] : []];
193
- for (const tool of tools) execSync(dlxCommand(packageManager, tool.package, { args: tool.args }), { stdio: "inherit" });
194
- } catch (error) {
195
- handleCommandError(error);
196
- }
136
+ });
137
+ for (const tool of tools) yield* runCommand(dlxCommand(packageManager, tool.package, { args: tool.args }), { stdio: "inherit" });
138
+ return ok(void 0);
139
+ })).isOk()) return;
140
+ process$1.exit(1);
197
141
  }
198
142
  });
199
143
 
200
144
  //#endregion
201
145
  //#region src/commands/fix.ts
202
146
  var fix_default = defineCommand({
203
- command: "fix",
147
+ command: "fix [files..]",
204
148
  describe: "Run Biome linter and fix issues in files",
205
149
  builder: (yargs$1) => yargs$1.positional("files", {
206
150
  describe: "Specific files to fix (optional)",
207
- type: "string"
151
+ type: "string",
152
+ array: true
208
153
  }).option("unsafe", {
209
154
  type: "boolean",
210
155
  description: "Apply unsafe fixes"
211
156
  }),
212
157
  handler: async (argv) => {
213
- try {
214
- const packageManager = await getPackageManagerName();
158
+ if ((await safeTry(async function* () {
159
+ const packageManager = yield* getPackageManagerName();
215
160
  const args = ["check", "--write"];
216
161
  if (argv.unsafe) args.push("--unsafe");
217
162
  if (argv.files && argv.files.length > 0) args.push(...argv.files);
218
- execSync(dlxCommand(packageManager, biome.name, { args }), { stdio: "inherit" });
219
- } catch (error) {
220
- handleCommandError(error);
221
- }
163
+ yield* runCommand(dlxCommand(packageManager, biome.name, { args }), { stdio: "inherit" });
164
+ return ok(void 0);
165
+ })).isOk()) return;
166
+ process$1.exit(1);
222
167
  }
223
168
  });
224
169
 
170
+ //#endregion
171
+ //#region src/helpers/editors/vscode.ts
172
+ const vscode = {
173
+ config: {
174
+ "typescript.tsdk": "node_modules/typescript/lib",
175
+ "editor.formatOnSave": true,
176
+ "editor.formatOnPaste": true,
177
+ "editor.codeActionsOnSave": {
178
+ "source.organizeImports.biome": "explicit",
179
+ "source.fixAll.biome": "explicit"
180
+ },
181
+ "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": { "editor.defaultFormatter": "biomejs.biome" }
182
+ },
183
+ exists: () => checkIfExists(join(process.cwd(), ".vscode", "settings.json")),
184
+ create: () => safeTry(async function* () {
185
+ const vscodePath = join(process.cwd(), ".vscode");
186
+ yield* fromPromise(mkdir(vscodePath, { recursive: true }), (error) => Fault.wrap(error).withTag("FAILED_TO_CREATE_DIRECTORY").withDescription("Failed to create .vscode directory", "We're unable to create the .vscode directory in the current directory.").withContext({ path: vscodePath }));
187
+ yield* fromPromise(writeFile(join(vscodePath, "settings.json"), JSON.stringify(vscode.config, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write .vscode/settings.json", "We're unable to write the .vscode/settings.json file in the current directory."));
188
+ return ok();
189
+ }),
190
+ update: () => safeTry(async function* () {
191
+ const vscodePath = join(process.cwd(), ".vscode", "settings.json");
192
+ const existingConfig = yield* parseJson(yield* fromPromise(readFile(vscodePath, "utf-8"), (error) => Fault.wrap(error).withTag("FAILED_TO_READ_FILE").withDescription("Failed to read .vscode/settings.json", "We're unable to read the .vscode/settings.json file in the current directory.").withContext({ path: vscodePath })));
193
+ const newConfig = yield* mergeConfig(vscode.config, existingConfig);
194
+ yield* fromPromise(writeFile(join(process.cwd(), ".vscode", "settings.json"), JSON.stringify(newConfig, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write .vscode/settings.json", "We're unable to write the .vscode/settings.json file in the current directory.").withContext({ path: vscodePath }));
195
+ return ok();
196
+ })
197
+ };
198
+
199
+ //#endregion
200
+ //#region src/helpers/tsconfig.ts
201
+ const tsconfig = {
202
+ config: { extends: "adamantite/tsconfig" },
203
+ exists: () => checkIfExists(join(process.cwd(), "tsconfig.json")),
204
+ create: () => fromPromise(writeFile(join(process.cwd(), "tsconfig.json"), JSON.stringify(tsconfig.config, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write tsconfig.json", "We're unable to write the tsconfig.json file in the current directory.")),
205
+ update: () => safeTry(async function* () {
206
+ const existingConfig = yield* parseJson(yield* fromPromise(readFile(join(process.cwd(), "tsconfig.json"), "utf-8"), (error) => Fault.wrap(error).withTag("FAILED_TO_READ_FILE").withDescription("Failed to read tsconfig.json", "We're unable to read the tsconfig.json file in the current directory.")));
207
+ const newConfig = yield* mergeConfig(tsconfig.config, existingConfig);
208
+ yield* fromPromise(writeFile(join(process.cwd(), "tsconfig.json"), JSON.stringify(newConfig, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write tsconfig.json", "We're unable to write the tsconfig.json file in the current directory."));
209
+ return ok();
210
+ })
211
+ };
212
+
225
213
  //#endregion
226
214
  //#region src/commands/init.ts
227
- async function checkIsMonorepo() {
228
- if (await checkIfExists(join(process$1.cwd(), "pnpm-workspace.yaml"))) return true;
229
- try {
230
- return (await readPackageJson()).workspaces !== void 0;
231
- } catch {
232
- return false;
233
- }
234
- }
235
- async function setupBiomeConfig() {
236
- const s = spinner();
237
- if (await biome.exists()) {
238
- s.start("Biome config found, updating...");
239
- await biome.update();
240
- s.stop("Biome config updated with Adamantite preset");
241
- } else {
242
- s.start("Biome config not found, creating...");
243
- await biome.create();
244
- s.stop("Biome config created with Adamantite preset");
245
- }
246
- }
247
- async function setupScripts({ check, fix, monorepo }) {
248
- const s = spinner();
249
- s.start("Adding scripts to your `package.json`...");
250
- try {
251
- const packageJson = await readPackageJson();
252
- if (!packageJson.scripts) packageJson.scripts = {};
253
- if (check) packageJson.scripts["check"] = "adamantite check";
254
- if (fix) packageJson.scripts["fix"] = "adamantite fix";
255
- if (monorepo) packageJson.scripts["lint:monorepo"] = "adamantite monorepo";
256
- await writePackageJson(packageJson);
257
- s.stop("Scripts added to your `package.json`");
258
- } catch (error) {
259
- s.stop("Failed to add scripts");
260
- throw new Error(`Failed to modify package.json: ${error instanceof Error ? error.message : "Unknown error"}`);
261
- }
262
- }
263
- async function setupTsConfig() {
264
- const s = spinner();
265
- if (await tsconfig.exists()) {
266
- s.start("`tsconfig.json` found, updating...");
267
- await tsconfig.update();
268
- s.stop("Updated `tsconfig.json` with preset");
269
- } else {
270
- s.start("`tsconfig.json` not found, creating...");
271
- await tsconfig.create();
272
- s.stop("Created `tsconfig.json` with preset");
273
- }
274
- }
275
- async function selectEditorConfig() {
276
- const selected = await multiselect({
277
- message: "Which editors do you want to configure (recommended)?",
278
- options: [{
279
- label: "VSCode / Cursor / Windsurf",
280
- value: "vscode"
281
- }, {
282
- label: "Zed (coming soon)",
283
- value: "zed"
284
- }],
285
- required: false
286
- });
287
- if (isCancel(selected)) throw new Error("Operation cancelled");
288
- return selected;
289
- }
290
- async function setupEditorConfig(selectedEditors) {
291
- if (!selectedEditors || selectedEditors.length === 0) return;
292
- const s = spinner();
293
- if (selectedEditors.includes("vscode")) if (await vscode.exists()) {
294
- s.start("VSCode settings found, updating...");
295
- await vscode.update();
296
- s.stop("VSCode settings updated with Adamantite preset");
297
- } else {
298
- s.start("VSCode settings not found, creating...");
299
- await vscode.create();
300
- s.stop("VSCode settings created with Adamantite preset");
301
- }
302
- if (selectedEditors.includes("zed")) {
303
- s.start("Zed configuration coming soon...");
304
- s.stop("Zed configuration coming soon...");
305
- }
306
- }
307
- async function confirmAction(message) {
308
- const result = await confirm({ message });
309
- if (isCancel(result)) throw new Error("Operation cancelled");
310
- return result;
311
- }
312
- async function installDependencies(options) {
313
- const s = spinner();
314
- s.start("Installing dependencies...");
315
- try {
316
- await addDevDependency("adamantite");
317
- const biomeVersion = biome.version;
318
- await addDevDependency(`@biomejs/biome@${biomeVersion}`);
319
- if (options?.monorepo) await addDevDependency(`sherif@${sherif.version}`);
320
- s.stop("Dependencies installed successfully");
321
- } catch (error) {
322
- s.stop("Failed to install dependencies");
323
- throw new Error(`Failed to install dependencies: ${error instanceof Error ? error.message : "Unknown error"}`);
324
- }
325
- }
326
215
  var init_default = defineCommand({
327
216
  command: "init",
328
217
  describe: "Initialize Adamantite in the current directory",
329
218
  builder: (yargs$1) => yargs$1,
330
219
  handler: async () => {
331
220
  intro(getTitle());
332
- try {
333
- const isMonorepo = await checkIsMonorepo();
334
- const installScripts = await confirmAction("Do you want to add the `check` and `fix` scripts to your `package.json`?");
335
- const installMonorepoScript = isMonorepo ? await confirmAction("We've detected a monorepo setup in your project. Would you like to install monorepo linting scripts?") : false;
336
- const installTypeScript = await confirmAction("Adamantite provides a TypeScript preset to enforce strict type-safety. Would you like to install it?");
337
- const selectedEditors = await selectEditorConfig();
338
- await installDependencies({ monorepo: installMonorepoScript });
339
- await setupBiomeConfig();
340
- if (installScripts || installMonorepoScript) await setupScripts({
341
- check: installScripts,
342
- fix: installScripts,
343
- monorepo: installMonorepoScript
344
- });
345
- if (installTypeScript) await setupTsConfig();
346
- await setupEditorConfig(selectedEditors);
221
+ const cwd = process$1.cwd();
222
+ const result = await safeTry(async function* () {
223
+ let packageJson = yield* readPackageJson();
224
+ const hasPnpmWorkspace = await checkIfExists(join(process$1.cwd(), "pnpm-workspace.yaml"));
225
+ const isMonorepo = packageJson.workspaces !== void 0 || hasPnpmWorkspace;
226
+ const shouldInstallScripts = yield* fromSafePromise(confirm({ message: "Do you want to add the `check` and `fix` scripts to your `package.json`?" })).andThen((r) => isCancel(r) ? err(Fault.create("OPERATION_CANCELLED")) : ok(r));
227
+ const shouldInstallMonorepoScripts = isMonorepo ? yield* fromSafePromise(confirm({ message: "We've detected a monorepo setup in your project. Would you like to install monorepo linting scripts?" })).andThen((r) => isCancel(r) ? err(Fault.create("OPERATION_CANCELLED")) : ok(r)) : false;
228
+ const shouldInstallTypeScriptPreset = yield* fromSafePromise(confirm({ message: "Adamantite provides a TypeScript preset to enforce strict type-safety. Would you like to install it?" })).andThen((r) => isCancel(r) ? err(Fault.create("OPERATION_CANCELLED")) : ok(r));
229
+ const selectedEditors = yield* fromSafePromise(multiselect({
230
+ message: "Which editors do you want to configure (recommended)?",
231
+ options: [{
232
+ label: "VSCode / Cursor / Windsurf",
233
+ value: "vscode"
234
+ }, {
235
+ label: "Zed (coming soon)",
236
+ value: "zed"
237
+ }],
238
+ required: false
239
+ })).andThen((r) => isCancel(r) ? err(Fault.create("OPERATION_CANCELLED")) : ok(r));
240
+ const installingDependencies = spinner();
241
+ installingDependencies.start("Installing dependencies...");
242
+ yield* fromPromise(addDevDependency("adamantite"), (error) => Fault.wrap(error).withTag("FAILED_TO_INSTALL_DEPENDENCY").withMessage("Failed to install Adamantite"));
243
+ yield* fromPromise(addDevDependency(`${biome.name}@${biome.version}`), (error) => Fault.wrap(error).withTag("FAILED_TO_INSTALL_DEPENDENCY").withMessage("Failed to install Biome"));
244
+ if (shouldInstallMonorepoScripts) yield* fromPromise(addDevDependency(`${sherif.name}@${sherif.version}`), (error) => Fault.wrap(error).withTag("FAILED_TO_INSTALL_DEPENDENCY").withMessage("Failed to install Sherif"));
245
+ installingDependencies.stop("Dependencies installed successfully");
246
+ const settingUpBiomeConfig = spinner();
247
+ settingUpBiomeConfig.start("Setting up Biome config...");
248
+ if ((await biome.exists()).path) {
249
+ settingUpBiomeConfig.message("Biome config found, updating...");
250
+ yield* biome.update();
251
+ settingUpBiomeConfig.stop("Biome config updated successfully");
252
+ } else {
253
+ settingUpBiomeConfig.message("Biome config not found, creating...");
254
+ yield* biome.create();
255
+ settingUpBiomeConfig.stop("Biome config created successfully");
256
+ }
257
+ if (shouldInstallScripts) {
258
+ const addingScripts = spinner();
259
+ packageJson = yield* readPackageJson();
260
+ addingScripts.start("Adding scripts to your `package.json`...");
261
+ if (!packageJson.scripts) packageJson.scripts = {};
262
+ packageJson.scripts.check = "adamantite check";
263
+ packageJson.scripts.fix = "adamantite fix";
264
+ if (shouldInstallMonorepoScripts) packageJson.scripts["lint:monorepo"] = "adamantite monorepo";
265
+ yield* fromPromise(writeFile(join(cwd, "package.json"), JSON.stringify(packageJson, null, 2)), (error) => Fault.wrap(error).withTag("FAILED_TO_WRITE_FILE").withDescription("Failed to write package.json", "We're unable to update the package.json file.").withContext({ path: join(cwd, "package.json") }));
266
+ addingScripts.stop("Scripts added to your `package.json`");
267
+ }
268
+ if (shouldInstallTypeScriptPreset) {
269
+ const settingUpTypeScriptConfig = spinner();
270
+ settingUpTypeScriptConfig.start("Setting up TypeScript config...");
271
+ if (await tsconfig.exists()) {
272
+ settingUpTypeScriptConfig.message("`tsconfig.json` found, updating...");
273
+ yield* tsconfig.update();
274
+ settingUpTypeScriptConfig.stop("`tsconfig.json` updated successfully");
275
+ } else {
276
+ settingUpTypeScriptConfig.message("`tsconfig.json` not found, creating...");
277
+ yield* tsconfig.create();
278
+ settingUpTypeScriptConfig.stop("`tsconfig.json` created successfully");
279
+ }
280
+ }
281
+ if (selectedEditors.length > 0) {
282
+ const settingUpEditorConfig = spinner();
283
+ settingUpEditorConfig.start("Setting up editor config...");
284
+ if (selectedEditors.includes("vscode")) {
285
+ const settingUpVSCodeConfig = spinner();
286
+ settingUpVSCodeConfig.start("Setting up VSCode config...");
287
+ if (await vscode.exists()) {
288
+ settingUpVSCodeConfig.message("VSCode settings found, updating...");
289
+ yield* vscode.update();
290
+ settingUpVSCodeConfig.stop("VSCode settings updated with Adamantite preset");
291
+ } else {
292
+ settingUpVSCodeConfig.message("VSCode settings not found, creating...");
293
+ yield* vscode.create();
294
+ settingUpVSCodeConfig.stop("VSCode settings created with Adamantite preset");
295
+ }
296
+ }
297
+ if (selectedEditors.includes("zed")) log.warning("Zed configuration coming soon...");
298
+ settingUpEditorConfig.stop("Editor config set up successfully");
299
+ }
300
+ return ok();
301
+ });
302
+ if (result.isOk()) {
347
303
  outro("💠 Adamantite initialized successfully!");
348
- } catch (error) {
349
- log.error(`${error instanceof Error ? error.message : "Unknown error"}`);
350
- cancel("Failed to initialize Adamantite");
304
+ return;
351
305
  }
306
+ if (result.error.tag === "OPERATION_CANCELLED") {
307
+ cancel("You've cancelled the initialization process.");
308
+ return;
309
+ }
310
+ log.error(result.error.message);
311
+ cancel("Failed to initialize Adamantite");
312
+ process$1.exit(1);
352
313
  }
353
314
  });
354
315
 
@@ -359,100 +320,62 @@ var monorepo_default = defineCommand({
359
320
  describe: "Lint and automatically fix monorepo-specific issues using Sherif",
360
321
  builder: (yargs$1) => yargs$1,
361
322
  handler: async () => {
362
- try {
363
- execSync(dlxCommand(await getPackageManagerName(), "sherif", { args: ["--fix"] }), { stdio: "inherit" });
364
- } catch (error) {
365
- handleCommandError(error);
366
- }
323
+ if ((await safeTry(async function* () {
324
+ yield* runCommand(dlxCommand(yield* getPackageManagerName(), sherif.name, { args: ["--fix"] }), { stdio: "inherit" });
325
+ return ok(void 0);
326
+ })).isOk()) return;
327
+ process$1.exit(1);
367
328
  }
368
329
  });
369
330
 
370
331
  //#endregion
371
332
  //#region src/commands/update.ts
372
- async function detectUpdatesNeeded() {
373
- const updates = [];
374
- try {
375
- const packageJson = await readPackageJson();
376
- const biomeDep = packageJson.devDependencies?.["@biomejs/biome"];
377
- if (biomeDep && biomeDep !== biome.version) updates.push({
378
- name: "@biomejs/biome",
379
- currentVersion: biomeDep,
380
- targetVersion: biome.version,
381
- isDevDependency: true
382
- });
383
- const sherifDep = packageJson.devDependencies?.sherif;
384
- if (sherifDep && sherifDep !== sherif.version) updates.push({
385
- name: "sherif",
386
- currentVersion: sherifDep,
387
- targetVersion: sherif.version,
388
- isDevDependency: true
389
- });
390
- return updates;
391
- } catch (error) {
392
- throw new Error(`Failed to read package.json: ${error instanceof Error ? error.message : "Unknown error"}`);
393
- }
394
- }
395
- async function updateDependencies(updates) {
396
- const s = spinner();
397
- s.start("Updating dependencies...");
398
- try {
399
- const tasks = updates.map((dep) => addDevDependency(`${dep.name}@${dep.targetVersion}`));
400
- const results = await Promise.allSettled(tasks);
401
- const failures = [];
402
- const successes = [];
403
- for (const [index, result] of results.entries()) {
404
- const dep = updates[index];
405
- if (!dep) continue;
406
- const depName = dep.name;
407
- if (result.status === "fulfilled") successes.push(depName);
408
- else failures.push(`${depName}: ${result.reason?.message || "Unknown error"}`);
409
- }
410
- if (failures.length === 0) s.stop("Dependencies updated successfully");
411
- else if (successes.length === 0) {
412
- s.stop("Failed to update dependencies");
413
- throw new Error(`All dependency updates failed:\n${failures.join("\n")}`);
414
- } else {
415
- s.stop("Partial update completed");
416
- log.warn("Some dependencies failed to update:");
417
- for (const failure of failures) log.warn(` ${failure}`);
418
- log.success(`Successfully updated: ${successes.join(", ")}`);
419
- }
420
- } catch (error) {
421
- s.stop("Failed to update dependencies");
422
- throw error;
423
- }
424
- }
425
- async function confirmUpdate(updates) {
426
- log.message("The following dependencies will be updated:");
427
- log.message("");
428
- for (const dep of updates) log.message(` ${dep.name}: ${dep.currentVersion} → ${dep.targetVersion}`);
429
- log.message("");
430
- const result = await confirm({ message: "Do you want to proceed with these updates?" });
431
- if (isCancel(result)) throw new Error("Operation cancelled");
432
- return result;
433
- }
434
333
  var update_default = defineCommand({
435
334
  command: "update",
436
335
  describe: "Update adamantite dependencies to latest compatible versions",
437
336
  builder: (yargs$1) => yargs$1,
438
337
  handler: async () => {
439
338
  intro(getTitle());
440
- try {
441
- const updates = await detectUpdatesNeeded();
339
+ const result = await safeTry(async function* () {
340
+ const packageJson = yield* readPackageJson();
341
+ const updates = [];
342
+ for (const pkg of [biome, sherif]) {
343
+ const dependency = packageJson.devDependencies?.[pkg.name];
344
+ if (dependency && dependency !== pkg.version) updates.push({
345
+ name: pkg.name,
346
+ currentVersion: dependency,
347
+ targetVersion: pkg.version,
348
+ isDevDependency: true
349
+ });
350
+ }
442
351
  if (updates.length === 0) {
443
352
  log.success("All adamantite dependencies are already up to date!");
444
- outro("💠 No updates needed");
445
- return;
353
+ return ok("no-updates");
446
354
  }
447
- if (!await confirmUpdate(updates)) {
448
- outro("💠 Update cancelled");
449
- return;
450
- }
451
- await updateDependencies(updates);
452
- outro("💠 Dependencies updated successfully!");
453
- } catch (error) {
454
- handleCommandError(error);
355
+ log.message("The following dependencies will be updated:");
356
+ log.message("");
357
+ for (const dep of updates) log.message(` ${dep.name}: ${dep.currentVersion} → ${dep.targetVersion}`);
358
+ log.message("");
359
+ if (!(yield* fromSafePromise(confirm({ message: "Do you want to proceed with these updates?" })).andThen((r) => isCancel(r) ? err(Fault.create("OPERATION_CANCELLED")) : ok(r)))) return ok("cancelled");
360
+ const s = spinner();
361
+ s.start("Updating dependencies...");
362
+ for (const dep of updates) yield* fromPromise(addDevDependency(`${dep.name}@${dep.targetVersion}`), (error) => Fault.wrap(error).withTag("FAILED_TO_INSTALL_DEPENDENCY").withMessage(`Failed to update ${dep.name}`));
363
+ s.stop("Dependencies updated successfully");
364
+ return ok("updated");
365
+ });
366
+ if (result.isOk()) {
367
+ if (result.value === "no-updates") outro("💠 No updates needed");
368
+ else if (result.value === "cancelled") outro("💠 Update cancelled");
369
+ else if (result.value === "updated") outro("💠 Dependencies updated successfully!");
370
+ return;
371
+ }
372
+ if (result.error.tag === "OPERATION_CANCELLED") {
373
+ cancel("You've cancelled the update process.");
374
+ return;
455
375
  }
376
+ log.error(result.error.message);
377
+ cancel("Failed to update dependencies");
378
+ process$1.exit(1);
456
379
  }
457
380
  });
458
381
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adamantite",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "An strict and opinionated set of presets for modern TypeScript applications",
5
5
  "keywords": [
6
6
  "adamantite",
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "author": "Adel Rodriguez <hello@adelrodriguez.com>",
23
23
  "type": "module",
24
- "main": "src/presets/biome.jsonc",
24
+ "main": "presets/biome.jsonc",
25
25
  "bin": {
26
26
  "adamantite": "bin/adamantite"
27
27
  },
@@ -29,15 +29,15 @@
29
29
  "#*": "./src/*"
30
30
  },
31
31
  "exports": {
32
- ".": "./src/presets/biome.jsonc",
33
- "./biome": "./src/presets/biome.jsonc",
34
- "./tsconfig": "./src/presets/tsconfig.json",
35
- "./presets/*": "./src/presets/*"
32
+ ".": "./presets/biome.jsonc",
33
+ "./biome": "./presets/biome.jsonc",
34
+ "./tsconfig": "./presets/tsconfig.json",
35
+ "./presets/*": "./presets/*"
36
36
  },
37
37
  "files": [
38
38
  "bin",
39
39
  "dist",
40
- "src/presets"
40
+ "presets"
41
41
  ],
42
42
  "scripts": {
43
43
  "build": "tsdown",
@@ -54,7 +54,9 @@
54
54
  "dependencies": {
55
55
  "@clack/prompts": "0.11.0",
56
56
  "defu": "6.1.4",
57
+ "faultier": "^1.0.3",
57
58
  "jsonc-parser": "3.3.1",
59
+ "neverthrow": "8.2.0",
58
60
  "nypm": "0.6.2",
59
61
  "yargs": "18.0.0"
60
62
  },
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "../../node_modules/@biomejs/biome/configuration_schema.json",
2
+ "$schema": "../node_modules/@biomejs/biome/configuration_schema.json",
3
3
  "root": false,
4
4
  // ---------------------------- FORMATTER -----------------------------
5
5
  // These are the generic settings that apply to all files in the project.
@@ -15,7 +15,7 @@
15
15
  // Use the system line ending.
16
16
  "lineEnding": "auto",
17
17
  // The expected line width for most editors.
18
- "lineWidth": 80,
18
+ "lineWidth": 100,
19
19
  // Don't force attributes to be on the same line as the element.
20
20
  "attributePosition": "auto",
21
21
  "bracketSpacing": true
@@ -103,9 +103,9 @@
103
103
  "noEmptyTypeParameters": "error",
104
104
  "noExtraBooleanCast": "error",
105
105
  "noExcessiveCognitiveComplexity": {
106
- "level": "error",
106
+ "level": "warn",
107
107
  "options": {
108
- "maxAllowedComplexity": 20
108
+ "maxAllowedComplexity": 40
109
109
  }
110
110
  },
111
111
  "noExcessiveNestedTestSuites": "error",
File without changes