@echojs-ecosystem/architect 0.1.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.
@@ -0,0 +1,791 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from 'citty';
3
+ import { relative, join, dirname, resolve, sep, basename } from 'path';
4
+ import * as process2 from 'process';
5
+ import process2__default from 'process';
6
+ import { fromError } from 'zod-validation-error';
7
+ import { ZodError, z } from 'zod';
8
+ import { loadConfig, watchConfig as watchConfig$1 } from 'c12';
9
+ import { map, switchMap, catchError, from, Observable, debounceTime, filter } from 'rxjs';
10
+ import { open, rm, mkdir, rename } from 'fs/promises';
11
+ import ts from 'typescript';
12
+ import { minimatch } from 'minimatch';
13
+ import * as fs from 'fs';
14
+ import precinct from 'precinct';
15
+ import { parse } from 'tsconfck';
16
+ import { produce } from 'immer';
17
+ import chokidar from 'chokidar';
18
+ import { isGitIgnored } from 'globby';
19
+ import chalk from 'chalk';
20
+ import figures from 'figures';
21
+ import terminalLink from 'terminal-link';
22
+ import prexit from 'prexit';
23
+
24
+ var __defProp = Object.defineProperty;
25
+ var __getOwnPropNames = Object.getOwnPropertyNames;
26
+ var __esm = (fn, res) => function __init() {
27
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
28
+ };
29
+ var __export = (target, all) => {
30
+ for (var name in all)
31
+ __defProp(target, name, { get: all[name], enumerable: true });
32
+ };
33
+
34
+ // src/cli/commands/init.ts
35
+ var init_exports = {};
36
+ __export(init_exports, {
37
+ default: () => init_default
38
+ });
39
+ var init_default;
40
+ var init_init = __esm({
41
+ "src/cli/commands/init.ts"() {
42
+ init_default = defineCommand({
43
+ meta: {
44
+ name: "init",
45
+ description: "Initialize a fresh project"
46
+ }
47
+ });
48
+ }
49
+ });
50
+ var ConfigurationNotFoundError, ConfigurationInvalidError;
51
+ var init_errors = __esm({
52
+ "src/config/errors.ts"() {
53
+ ConfigurationNotFoundError = class extends Error {
54
+ constructor() {
55
+ super(`Configuration not found in ${process2.cwd()}`);
56
+ }
57
+ };
58
+ ConfigurationInvalidError = class extends Error {
59
+ constructor(error, filepath) {
60
+ super(
61
+ fromError(error, {
62
+ prefix: `Invalid configuration in ${relative(process2.cwd(), filepath)}`
63
+ }).toString()
64
+ );
65
+ this.error = error;
66
+ }
67
+ error;
68
+ };
69
+ }
70
+ });
71
+ var RuleSchema, AbstractionSchema, EvolutionConfigSchema;
72
+ var init_schema = __esm({
73
+ "src/config/schema.ts"() {
74
+ RuleSchema = z.object({
75
+ name: z.string(),
76
+ check: z.custom(),
77
+ severity: z.enum(["off", "warn", "error"]),
78
+ descriptionUrl: z.string().optional()
79
+ });
80
+ AbstractionSchema = z.object({
81
+ name: z.string(),
82
+ children: z.record(z.lazy(() => AbstractionSchema)),
83
+ rules: z.array(RuleSchema),
84
+ fractal: z.string().optional(),
85
+ fileTemplate: z.custom()
86
+ });
87
+ EvolutionConfigSchema = z.object({
88
+ root: AbstractionSchema,
89
+ baseUrl: z.string().optional(),
90
+ files: z.array(z.string()).optional(),
91
+ ignores: z.array(z.string()).optional()
92
+ });
93
+ }
94
+ });
95
+ var CONFIG_NAME, parseConfigResult, watchConfig;
96
+ var init_load = __esm({
97
+ "src/config/load.ts"() {
98
+ init_errors();
99
+ init_schema();
100
+ CONFIG_NAME = "architect";
101
+ parseConfigResult = (filepath, data) => {
102
+ const parseResult = EvolutionConfigSchema.safeParse(data);
103
+ if (!parseResult.success) {
104
+ throw new ConfigurationInvalidError(parseResult.error, filepath);
105
+ }
106
+ return {
107
+ config: parseResult.data,
108
+ configPath: filepath
109
+ };
110
+ };
111
+ watchConfig = ({
112
+ cwd: cwd2,
113
+ onlyOne
114
+ }) => {
115
+ const config$ = from(
116
+ loadConfig({
117
+ cwd: cwd2,
118
+ name: CONFIG_NAME
119
+ })
120
+ ).pipe(
121
+ map(({ configFile, config }) => {
122
+ if (!configFile) {
123
+ throw new ConfigurationNotFoundError();
124
+ }
125
+ return parseConfigResult(configFile, config);
126
+ })
127
+ );
128
+ if (onlyOne) {
129
+ return config$;
130
+ }
131
+ return config$.pipe(
132
+ switchMap(
133
+ ({ configPath, config }) => new Observable((subscriber) => {
134
+ subscriber.next({ configPath, config });
135
+ let unwatchCallback = () => {
136
+ };
137
+ watchConfig$1({
138
+ cwd: cwd2,
139
+ name: CONFIG_NAME,
140
+ onUpdate: (config2) => {
141
+ subscriber.next(
142
+ parseConfigResult(configPath, config2.newConfig.config)
143
+ );
144
+ }
145
+ }).then(({ unwatch }) => {
146
+ unwatchCallback = unwatch;
147
+ });
148
+ return () => unwatchCallback();
149
+ })
150
+ )
151
+ );
152
+ };
153
+ }
154
+ });
155
+ var applyAutofixes, tryToApplyFixes;
156
+ var init_auto_fix = __esm({
157
+ "src/linter/auto-fix.ts"() {
158
+ applyAutofixes = async (diagnostics) => {
159
+ const stillRelevantDiagnostics = [];
160
+ const fixableDiagnostics = [];
161
+ for (const diagnostic of diagnostics) {
162
+ const fixes = diagnostic.fixes;
163
+ if (!fixes) {
164
+ stillRelevantDiagnostics.push(diagnostic);
165
+ continue;
166
+ }
167
+ fixableDiagnostics.push(diagnostic);
168
+ }
169
+ try {
170
+ await Promise.all(fixableDiagnostics.map(tryToApplyFixes));
171
+ } catch (error) {
172
+ stillRelevantDiagnostics.push(...fixableDiagnostics);
173
+ console.error(error);
174
+ }
175
+ return stillRelevantDiagnostics;
176
+ };
177
+ tryToApplyFixes = async (diagnostic) => {
178
+ const fixes = diagnostic.fixes ?? [];
179
+ return Promise.all(
180
+ fixes.map((fix) => {
181
+ switch (fix.type) {
182
+ case "rename":
183
+ return rename(fix.path, join(dirname(fix.path), fix.newName));
184
+ case "create-file":
185
+ return open(fix.path, "w").then((file) => {
186
+ file.write(fix.content);
187
+ return file.close();
188
+ });
189
+ case "create-folder":
190
+ return mkdir(fix.path, { recursive: true });
191
+ case "delete":
192
+ return rm(fix.path, { recursive: true });
193
+ case "modify-file":
194
+ return open(fix.path, "w").then(async (file) => {
195
+ await file.write(fix.content);
196
+ return file.close();
197
+ });
198
+ default:
199
+ return void 0;
200
+ }
201
+ })
202
+ );
203
+ };
204
+ }
205
+ });
206
+
207
+ // src/shared/memoize.ts
208
+ var memoize;
209
+ var init_memoize = __esm({
210
+ "src/shared/memoize.ts"() {
211
+ memoize = (fn) => {
212
+ const cache = /* @__PURE__ */ new WeakMap();
213
+ return function(arg) {
214
+ if (cache.has(arg)) {
215
+ return cache.get(arg);
216
+ }
217
+ const result = fn.call(this, arg);
218
+ cache.set(arg, result);
219
+ return result;
220
+ };
221
+ };
222
+ }
223
+ });
224
+ var resolveImport, imperfectKeys, normalizeCompilerOptions;
225
+ var init_resolve_import = __esm({
226
+ "src/shared/resolve-import.ts"() {
227
+ resolveImport = (importedPath, importerPath, tsCompilerOptions, fileExists, directoryExists) => {
228
+ return ts.resolveModuleName(
229
+ importedPath,
230
+ importerPath,
231
+ normalizeCompilerOptions(tsCompilerOptions),
232
+ {
233
+ ...ts.sys,
234
+ fileExists,
235
+ directoryExists
236
+ }
237
+ ).resolvedModule?.resolvedFileName?.replaceAll("/", sep) ?? null;
238
+ };
239
+ imperfectKeys = {
240
+ module: ts.ModuleKind,
241
+ moduleResolution: {
242
+ ...ts.ModuleResolutionKind,
243
+ node: ts.ModuleResolutionKind.Node10
244
+ },
245
+ moduleDetection: ts.ModuleDetectionKind,
246
+ newLine: ts.NewLineKind,
247
+ target: ts.ScriptTarget
248
+ };
249
+ normalizeCompilerOptions = (compilerOptions) => {
250
+ return Object.fromEntries(
251
+ Object.entries(compilerOptions).map(([key, value]) => {
252
+ if (Object.keys(imperfectKeys).includes(key) && typeof value === "string") {
253
+ for (const [enumKey, enumValue] of Object.entries(
254
+ imperfectKeys[key]
255
+ )) {
256
+ if (enumKey.toLowerCase() === value.toLowerCase()) {
257
+ return [key, enumValue];
258
+ }
259
+ }
260
+ }
261
+ return [key, value];
262
+ })
263
+ );
264
+ };
265
+ }
266
+ });
267
+
268
+ // src/shared/index.ts
269
+ var init_shared = __esm({
270
+ "src/shared/index.ts"() {
271
+ init_memoize();
272
+ init_resolve_import();
273
+ }
274
+ });
275
+ var parseAbstractionInstance;
276
+ var init_parse = __esm({
277
+ "src/abstraction/instance/parse.ts"() {
278
+ init_shared();
279
+ parseAbstractionInstance = memoize(
280
+ (abstraction) => memoize((node) => {
281
+ if (node.type === "file") {
282
+ return {
283
+ abstraction,
284
+ path: node.path,
285
+ children: [],
286
+ childNodes: []
287
+ };
288
+ }
289
+ const children = {};
290
+ for (const [pattern, childAbstraction] of Object.entries(
291
+ abstraction.children
292
+ )) {
293
+ const nodeAbstractionInstance = parseAbstractionInstance(childAbstraction);
294
+ const nodesStack = [node];
295
+ while (nodesStack.length) {
296
+ const currentNode = nodesStack.pop();
297
+ if (minimatch(relative(node.path, currentNode.path), pattern)) {
298
+ children[currentNode.path] = nodeAbstractionInstance(currentNode);
299
+ continue;
300
+ }
301
+ if (children[currentNode.path]) {
302
+ continue;
303
+ }
304
+ if (currentNode.type === "folder") {
305
+ nodesStack.push(...currentNode.children);
306
+ }
307
+ }
308
+ }
309
+ const childNodes = [];
310
+ const childrenNodesStack = [node];
311
+ while (childrenNodesStack.length) {
312
+ const currentNode = childrenNodesStack.pop();
313
+ if (children[currentNode.path]) {
314
+ continue;
315
+ }
316
+ if (currentNode.path !== node.path) {
317
+ childNodes.push(currentNode.path);
318
+ }
319
+ if (currentNode.type === "folder") {
320
+ childrenNodesStack.push(...currentNode.children);
321
+ }
322
+ }
323
+ return {
324
+ abstraction,
325
+ path: node.path,
326
+ childNodes: Object.values(childNodes),
327
+ children: Object.values(children)
328
+ };
329
+ })
330
+ );
331
+ }
332
+ });
333
+
334
+ // src/vfs/get-flatten-files.ts
335
+ var getFlattenFiles;
336
+ var init_get_flatten_files = __esm({
337
+ "src/vfs/get-flatten-files.ts"() {
338
+ init_shared();
339
+ getFlattenFiles = memoize((node) => {
340
+ if (node.type === "file") {
341
+ return [node];
342
+ }
343
+ return node.children.reduce((acc, child) => {
344
+ if (child.type === "file") {
345
+ return [...acc, child];
346
+ }
347
+ return [...acc, ...getFlattenFiles(child)];
348
+ }, []);
349
+ });
350
+ }
351
+ });
352
+ var paperwork, parseDependenciesMap;
353
+ var init_parse2 = __esm({
354
+ "src/dependencies/parse.ts"() {
355
+ init_shared();
356
+ init_get_flatten_files();
357
+ ({ paperwork } = precinct);
358
+ parseDependenciesMap = async (vfs) => {
359
+ const dependenciesMap = {
360
+ dependencies: {},
361
+ dependencyFor: {}
362
+ };
363
+ const basePath = vfs.path;
364
+ const { tsconfig } = await parse(basePath);
365
+ const files = getFlattenFiles(vfs);
366
+ for (const file of files) {
367
+ const dependencies = paperwork(file.path, {
368
+ includeCore: false,
369
+ fileSystem: fs
370
+ });
371
+ const resolvedDependencies = dependencies.map(
372
+ (dependency) => resolveImport(
373
+ dependency,
374
+ file.path,
375
+ tsconfig?.compilerOptions ?? {},
376
+ fs.existsSync,
377
+ fs.existsSync
378
+ )
379
+ ).filter((dependency) => dependency !== null);
380
+ dependenciesMap.dependencies[file.path] = new Set(resolvedDependencies);
381
+ for (const dependency of resolvedDependencies) {
382
+ if (!dependenciesMap.dependencyFor[dependency]) {
383
+ dependenciesMap.dependencyFor[dependency] = /* @__PURE__ */ new Set();
384
+ }
385
+ dependenciesMap.dependencyFor[dependency].add(file.path);
386
+ }
387
+ }
388
+ return dependenciesMap;
389
+ };
390
+ }
391
+ });
392
+ var addDirectory;
393
+ var init_add_directory = __esm({
394
+ "src/vfs/add-directory.ts"() {
395
+ addDirectory = (tree, newDirectoryPath) => {
396
+ const rootPath = tree.path;
397
+ return produce(tree, (draft) => {
398
+ const pathSegments = relative(rootPath, newDirectoryPath).split(sep);
399
+ let currentFolder = draft;
400
+ for (const pathSegment of pathSegments.slice(0, -1)) {
401
+ const existingChild = currentFolder.children.find(
402
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
403
+ );
404
+ if (existingChild === void 0) {
405
+ currentFolder.children.push({
406
+ type: "folder",
407
+ path: join(currentFolder.path, pathSegment),
408
+ children: []
409
+ });
410
+ currentFolder = currentFolder.children[currentFolder.children.length - 1];
411
+ } else {
412
+ currentFolder = existingChild;
413
+ }
414
+ }
415
+ currentFolder.children.push({
416
+ type: "folder",
417
+ path: newDirectoryPath,
418
+ children: []
419
+ });
420
+ });
421
+ };
422
+ }
423
+ });
424
+ var addFile;
425
+ var init_add_file = __esm({
426
+ "src/vfs/add-file.ts"() {
427
+ addFile = (tree, newFilePath) => {
428
+ const rootPath = tree.path;
429
+ return produce(tree, (draft) => {
430
+ const pathSegments = relative(rootPath, newFilePath).split(sep);
431
+ let currentFolder = draft;
432
+ for (const pathSegment of pathSegments.slice(0, -1)) {
433
+ const existingChild = currentFolder.children.find(
434
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
435
+ );
436
+ if (existingChild === void 0) {
437
+ currentFolder.children.push({
438
+ type: "folder",
439
+ path: join(currentFolder.path, pathSegment),
440
+ children: []
441
+ });
442
+ currentFolder = currentFolder.children[currentFolder.children.length - 1];
443
+ } else {
444
+ currentFolder = existingChild;
445
+ }
446
+ }
447
+ currentFolder.children.push({ type: "file", path: newFilePath });
448
+ });
449
+ };
450
+ }
451
+ });
452
+
453
+ // src/vfs/create-root.ts
454
+ var createVfsRoot;
455
+ var init_create_root = __esm({
456
+ "src/vfs/create-root.ts"() {
457
+ createVfsRoot = (path) => ({
458
+ type: "folder",
459
+ path,
460
+ children: []
461
+ });
462
+ }
463
+ });
464
+ var removeNode;
465
+ var init_remove_node = __esm({
466
+ "src/vfs/remove-node.ts"() {
467
+ removeNode = (tree, removedNodePath) => {
468
+ const rootPath = tree.path;
469
+ return produce(tree, (draft) => {
470
+ const pathSegments = relative(rootPath, removedNodePath).split(sep);
471
+ let currentFolder = draft;
472
+ for (const pathSegment of pathSegments.slice(0, -1)) {
473
+ const existingChild = currentFolder.children.find(
474
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
475
+ );
476
+ if (existingChild === void 0) {
477
+ return tree;
478
+ } else {
479
+ currentFolder = existingChild;
480
+ }
481
+ }
482
+ const removedNodeIndex = currentFolder.children.findIndex(
483
+ (child) => child.path === removedNodePath
484
+ );
485
+ if (removedNodeIndex === -1) {
486
+ return tree;
487
+ }
488
+ currentFolder.children.splice(removedNodeIndex, 1);
489
+ });
490
+ };
491
+ }
492
+ });
493
+ var watchFs, createWatcherObservable;
494
+ var init_watch_fs = __esm({
495
+ "src/vfs/watch-fs.ts"() {
496
+ init_add_directory();
497
+ init_add_file();
498
+ init_create_root();
499
+ init_remove_node();
500
+ watchFs = (path, { onlyReady } = {}) => {
501
+ const isIgnored$ = from(isGitIgnored({ cwd: path }));
502
+ let vfs$ = isIgnored$.pipe(
503
+ switchMap((isIgnored) => createWatcherObservable({ path, isIgnored }))
504
+ );
505
+ if (onlyReady) {
506
+ vfs$ = vfs$.pipe(filter((e) => e.type === "ready"));
507
+ }
508
+ return vfs$;
509
+ };
510
+ createWatcherObservable = ({
511
+ path,
512
+ isIgnored
513
+ }) => {
514
+ return new Observable((observer) => {
515
+ let vfs = createVfsRoot(path);
516
+ const watcher = chokidar.watch(path, {
517
+ ignored: (path2) => path2.split(sep).includes("node_modules") || isIgnored(path2),
518
+ ignoreInitial: false,
519
+ alwaysStat: true,
520
+ awaitWriteFinish: true,
521
+ disableGlobbing: true,
522
+ cwd: path
523
+ });
524
+ watcher.on("add", async (relativePath) => {
525
+ vfs = addFile(vfs, join(path, relativePath));
526
+ observer.next({ type: "add", vfs });
527
+ });
528
+ watcher.on("addDir", async (relativePath) => {
529
+ vfs = addDirectory(vfs, join(path, relativePath));
530
+ observer.next({ type: "addDir", vfs });
531
+ });
532
+ watcher.on("change", async () => {
533
+ observer.next({ type: "change", vfs });
534
+ });
535
+ watcher.on("unlink", async (relativePath) => {
536
+ vfs = removeNode(vfs, join(path, relativePath));
537
+ observer.next({ type: "unlink", vfs });
538
+ });
539
+ watcher.on("unlinkDir", async (relativePath) => {
540
+ vfs = removeNode(vfs, join(path, relativePath));
541
+ observer.next({ type: "unlinkDir", vfs });
542
+ });
543
+ watcher.on("ready", () => {
544
+ observer.next({ type: "ready", vfs });
545
+ });
546
+ return () => {
547
+ watcher.close();
548
+ };
549
+ });
550
+ };
551
+ }
552
+ });
553
+
554
+ // src/linter/run-rules.ts
555
+ var runRules;
556
+ var init_run_rules = __esm({
557
+ "src/linter/run-rules.ts"() {
558
+ runRules = async ({
559
+ root,
560
+ instance,
561
+ dependenciesMap
562
+ }) => {
563
+ const ruleDiagnostics = (currentInstance) => async (rule) => {
564
+ if (rule.severity === "off") {
565
+ return [];
566
+ }
567
+ const { diagnostics } = await rule.check({
568
+ root,
569
+ instance: currentInstance,
570
+ dependenciesMap
571
+ });
572
+ return diagnostics.map((d) => ({ ...d, rule }));
573
+ };
574
+ const runAbstractionRules = (currentInstance) => {
575
+ return currentInstance.abstraction.rules.map(ruleDiagnostics(currentInstance)).concat(...currentInstance.children.flatMap(runAbstractionRules));
576
+ };
577
+ return await Promise.all(runAbstractionRules(instance)).then((r) => r.flat());
578
+ };
579
+ }
580
+ });
581
+ var lint;
582
+ var init_lint = __esm({
583
+ "src/linter/lint.ts"() {
584
+ init_parse();
585
+ init_parse2();
586
+ init_watch_fs();
587
+ init_run_rules();
588
+ lint = ({
589
+ watch,
590
+ config,
591
+ configPath
592
+ }) => {
593
+ const rootPath = resolve(dirname(configPath), config.baseUrl ?? "./");
594
+ const parseNode = parseAbstractionInstance(config.root);
595
+ return watchFs(rootPath, { onlyReady: !watch }).pipe(
596
+ debounceTime(500),
597
+ switchMap(async ({ vfs }) => ({
598
+ root: vfs,
599
+ instance: parseNode(vfs),
600
+ dependenciesMap: await parseDependenciesMap(vfs)
601
+ })),
602
+ switchMap(runRules)
603
+ );
604
+ };
605
+ }
606
+ });
607
+ var formatLocation, formatSingleDiagnostic;
608
+ var init_format_single_diagnostic = __esm({
609
+ "src/linter/reporter/format-single-diagnostic.ts"() {
610
+ formatLocation = (location, cwd2) => {
611
+ let path = relative(cwd2, location.path);
612
+ if (location.line !== void 0) {
613
+ path += `:${location.line}`;
614
+ if (location.column !== void 0) {
615
+ path += `:${location.column}`;
616
+ }
617
+ }
618
+ return path;
619
+ };
620
+ formatSingleDiagnostic = (d, cwd2) => {
621
+ const x = d.rule.severity === "error" ? chalk.red(figures.cross) : chalk.yellow(figures.warning);
622
+ const s2 = chalk.reset(figures.lineDownRight);
623
+ const bar = chalk.reset(figures.lineVertical);
624
+ const e = chalk.reset(figures.lineUpRight);
625
+ const message = chalk.reset(d.message);
626
+ const autofixable = d.fixes !== void 0 && d.fixes.length > 0 ? chalk.green(`${figures.tick} Auto-fixable`) : null;
627
+ const location = chalk.gray(formatLocation(d.location, cwd2));
628
+ const ruleName = d.rule.descriptionUrl ? chalk.blue(terminalLink(d.rule.name, d.rule.descriptionUrl)) : d.rule.name;
629
+ return `
630
+ ${s2} ${location}
631
+ ${x} ${message}
632
+ ${autofixable ? `${autofixable}
633
+ ${bar}` : bar}
634
+ ${e} ${ruleName}
635
+ `.trim();
636
+ };
637
+ }
638
+ });
639
+
640
+ // src/linter/reporter/pluralization.ts
641
+ var s;
642
+ var init_pluralization = __esm({
643
+ "src/linter/reporter/pluralization.ts"() {
644
+ s = (amount) => amount === 1 ? "" : "s";
645
+ }
646
+ });
647
+ var formatPretty, reportPretty;
648
+ var init_reporter = __esm({
649
+ "src/linter/reporter/index.ts"() {
650
+ init_format_single_diagnostic();
651
+ init_pluralization();
652
+ formatPretty = (diagnostics, cwd2) => {
653
+ if (diagnostics.length === 0) {
654
+ return chalk.green(`${figures.tick} No problems found!`);
655
+ }
656
+ const errors = diagnostics.filter((d) => d.rule.severity === "error");
657
+ const warnings = diagnostics.filter((d) => d.rule.severity === "warn");
658
+ let footer = `Found ${[
659
+ errors.length > 0 && chalk.red.bold(`${errors.length} error${s(errors.length)}`),
660
+ warnings.length > 0 && chalk.yellow.bold(`${warnings.length} warning${s(warnings.length)}`)
661
+ ].filter(Boolean).join(" and ")}`;
662
+ const autofixable = diagnostics.filter((d) => (d.fixes?.length ?? 0) > 0);
663
+ if (autofixable.length === diagnostics.length) {
664
+ footer += ` (all can be fixed automatically with ${chalk.green.bold("--fix")})`;
665
+ } else if (autofixable.length > 0) {
666
+ footer += ` (${autofixable.length} can be fixed automatically with ${chalk.green.bold("--fix")})`;
667
+ } else {
668
+ footer += " (none can be fixed automatically)";
669
+ }
670
+ return `
671
+ ${diagnostics.map((d) => formatSingleDiagnostic(d, cwd2)).join("\n\n")}
672
+
673
+ ${chalk.gray(figures.line.repeat(footer.length))}
674
+ ${footer}
675
+ `;
676
+ };
677
+ reportPretty = (diagnostics, cwd2) => {
678
+ console.error(formatPretty(diagnostics, cwd2));
679
+ };
680
+ }
681
+ });
682
+
683
+ // src/linter/index.ts
684
+ var init_linter = __esm({
685
+ "src/linter/index.ts"() {
686
+ init_auto_fix();
687
+ init_lint();
688
+ init_reporter();
689
+ }
690
+ });
691
+
692
+ // src/cli/commands/lint.ts
693
+ var lint_exports = {};
694
+ __export(lint_exports, {
695
+ default: () => lint_default
696
+ });
697
+ var lint_default;
698
+ var init_lint2 = __esm({
699
+ "src/cli/commands/lint.ts"() {
700
+ init_load();
701
+ init_linter();
702
+ lint_default = defineCommand({
703
+ meta: {
704
+ name: "lint",
705
+ description: "Lint the project"
706
+ },
707
+ args: {
708
+ watch: {
709
+ type: "boolean",
710
+ description: "Watch for changes",
711
+ default: false
712
+ },
713
+ fix: {
714
+ type: "boolean",
715
+ description: "Apply autofixes",
716
+ default: false
717
+ },
718
+ "fail-on-warning": {
719
+ type: "boolean",
720
+ description: "Fail on warnings",
721
+ default: false
722
+ }
723
+ },
724
+ async run(ctx) {
725
+ const { watch, fix, "fail-on-warning": failOnWarning } = ctx.args;
726
+ const subscription = watchConfig({
727
+ cwd: process2__default.cwd(),
728
+ onlyOne: !watch
729
+ }).pipe(
730
+ map(({ configPath, config }) => ({ configPath, config, watch })),
731
+ switchMap(lint),
732
+ catchError((e) => {
733
+ if (e instanceof ZodError) {
734
+ console.error(fromError(e).toString());
735
+ } else if (e instanceof Error) {
736
+ console.error(e.message);
737
+ }
738
+ process2__default.exit(1);
739
+ })
740
+ ).subscribe(async (diagnostics) => {
741
+ if (watch) {
742
+ console.clear();
743
+ reportPretty(diagnostics, process2__default.cwd());
744
+ if (fix) {
745
+ await applyAutofixes(diagnostics);
746
+ }
747
+ } else {
748
+ let stillRelevantDiagnostics = diagnostics;
749
+ reportPretty(diagnostics, process2__default.cwd());
750
+ if (fix) {
751
+ stillRelevantDiagnostics = await applyAutofixes(diagnostics);
752
+ }
753
+ if (stillRelevantDiagnostics.length > 0) {
754
+ const onlyWarnings = stillRelevantDiagnostics.every(
755
+ (d) => d.rule.severity === "warn"
756
+ );
757
+ if (failOnWarning || !onlyWarnings) {
758
+ process2__default.exit(1);
759
+ }
760
+ }
761
+ process2__default.exit(0);
762
+ }
763
+ });
764
+ prexit(() => subscription.unsubscribe());
765
+ }
766
+ });
767
+ }
768
+ });
769
+
770
+ // src/cli/version.ts
771
+ var version = "0.1.0";
772
+
773
+ // src/cli/commands/index.ts
774
+ var _rDefault = (r) => r.default || r;
775
+ var commands = {
776
+ init: () => Promise.resolve().then(() => (init_init(), init_exports)).then(_rDefault),
777
+ lint: () => Promise.resolve().then(() => (init_lint2(), lint_exports)).then(_rDefault)
778
+ };
779
+
780
+ // src/cli/index.ts
781
+ var main = defineCommand({
782
+ meta: {
783
+ name: "echo-architect",
784
+ version,
785
+ description: "EchoJS architecture linter CLI"
786
+ },
787
+ subCommands: commands
788
+ });
789
+ runMain(main);
790
+ //# sourceMappingURL=index.js.map
791
+ //# sourceMappingURL=index.js.map