@enhance-eng/archangel 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Runs the archangel binary from the platform package that matches this machine.
3
+ import { spawnSync } from 'node:child_process';
4
+ import { createRequire } from 'node:module';
5
+ import { PACKAGES, missing, platformKey, unsupported, usesMusl } from '../lib/platform.js';
6
+
7
+ const key = platformKey({ platform: process.platform, arch: process.arch, musl: usesMusl() });
8
+ const name = PACKAGES[key];
9
+ if (name === undefined) {
10
+ process.stderr.write(`${unsupported(key)}\n`);
11
+ process.exit(2);
12
+ }
13
+
14
+ const binary = process.platform === 'win32' ? 'bin/archangel.exe' : 'bin/archangel';
15
+ let path;
16
+ try {
17
+ path = createRequire(import.meta.url).resolve(`${name}/${binary}`);
18
+ } catch {
19
+ process.stderr.write(`${missing(key, name)}\n`);
20
+ process.exit(2);
21
+ }
22
+
23
+ const run = spawnSync(path, process.argv.slice(2), { stdio: 'inherit' });
24
+ if (run.error) {
25
+ process.stderr.write(`archangel could not start ${path}: ${run.error.message}\n`);
26
+ process.exit(2);
27
+ }
28
+ process.exit(run.status ?? 2);
package/index.d.ts ADDED
@@ -0,0 +1,418 @@
1
+ // The types of an archangel config. They check, as the config is written, that every rule names a
2
+ // declared element or selector, that a sentence has exactly one condition, and that an isolate
3
+ // rule names an element with a capture such as `{feature}`. The names inside `selectors` are
4
+ // left to archangel, because checking them here makes TypeScript infer mutable lists for them.
5
+
6
+ import type { AnyCondition, Plugin } from './plugin.js';
7
+
8
+ export * from './plugin.js';
9
+
10
+ /** A pattern, a list of patterns, or the packages of a package element. */
11
+ export type ElementSpec = string | readonly string[] | { readonly packages: readonly string[] };
12
+
13
+ /** The elements, by name. */
14
+ export type Elements = Record<string, ElementSpec>;
15
+
16
+ /** Whether a pattern has a capture: braces around a name, not a comma-separated alternative. */
17
+ type PatternCaptures<P extends string> = P extends `${string}{${infer Inside}}${infer Rest}`
18
+ ? Inside extends `${string},${string}`
19
+ ? PatternCaptures<Rest>
20
+ : true
21
+ : false;
22
+
23
+ /** Whether an element's pattern, or any of its patterns, has a capture. */
24
+ type Captures<S> = S extends string
25
+ ? PatternCaptures<S>
26
+ : S extends readonly (infer P extends string)[]
27
+ ? true extends PatternCaptures<P>
28
+ ? true
29
+ : false
30
+ : false;
31
+
32
+ /** The name of a declared element. */
33
+ export type ElementName<E extends Elements> = Extract<keyof E, string>;
34
+
35
+ /** The name of a declared element of files, not of packages. */
36
+ export type FileElementName<E extends Elements> = {
37
+ [K in ElementName<E>]: E[K] extends { readonly packages: readonly string[] } ? never : K;
38
+ }[ElementName<E>];
39
+
40
+ /** The name of a declared element whose pattern has a capture, as isolate needs. */
41
+ export type CapturedElement<E extends Elements> = {
42
+ [K in ElementName<E>]: Captures<E[K]> extends true ? K : never;
43
+ }[ElementName<E>];
44
+
45
+ /** A path pattern: a string with a `/` or a `*`, which no declared name needs. */
46
+ export type PathPattern = `${string}/${string}` | `${string}*${string}`;
47
+
48
+ /**
49
+ * Which files a selector or a rule is about: a declared name `N`, a path pattern, a list of these
50
+ * (any of them), or one combination.
51
+ */
52
+ export type SelectorDefinition<N extends string, F extends string = N> =
53
+ | N
54
+ | PathPattern
55
+ | readonly SelectorDefinition<N, F>[]
56
+ | SelectorObject<N, F>;
57
+
58
+ /** The combinations a selector may use, each an object of one shape. */
59
+ export type SelectorObject<N extends string, F extends string = N> =
60
+ | { readonly all: readonly SelectorDefinition<N, F>[]; readonly any?: never; readonly not?: never }
61
+ | { readonly any: readonly SelectorDefinition<N, F>[]; readonly all?: never; readonly not?: never }
62
+ | { readonly not: SelectorDefinition<N, F>; readonly all?: never; readonly any?: never }
63
+ /** The files of one element, `'*'` for the files of any element. */
64
+ | { readonly element: F | '*'; readonly capture?: never; readonly directive?: never }
65
+ /** The files of one instance of an element, such as `{ element: 'feature', capture: { feature: 'login' } }`. */
66
+ | {
67
+ readonly element: F;
68
+ readonly capture: Readonly<Record<string, string>>;
69
+ readonly directive?: never;
70
+ }
71
+ /** The files that start with a directive, such as `'use client'`. */
72
+ | { readonly directive: string; readonly element?: never };
73
+
74
+ /** The selectors, by name. */
75
+ export type Selectors = Record<string, SelectorDefinition<string>>;
76
+
77
+ /** A name a selector or a sentence's `files` may use: a file element or a selector. */
78
+ type FilesName<E extends Elements, S extends Selectors> = FileElementName<E> | Extract<keyof S, string>;
79
+
80
+ /** Which files a sentence is about. */
81
+ export type Files<E extends Elements, S extends Selectors> = SelectorDefinition<
82
+ NoInfer<FilesName<E, S>>,
83
+ NoInfer<FileElementName<E>>
84
+ >;
85
+
86
+ /** One thing on the other side of an import condition. */
87
+ export type SideItem<E extends Elements, S extends Selectors> =
88
+ | NoInfer<ElementName<E> | Extract<keyof S, string>>
89
+ | PathPattern
90
+ /** Packages by name; a name may end in `*`, and `'*'` is every package. */
91
+ | { readonly packages: readonly string[] }
92
+ | SelectorObject<NoInfer<FilesName<E, S>>, NoInfer<FileElementName<E>>>;
93
+
94
+ /** The other side of an import condition: one thing, or a list of them. */
95
+ export type Side<E extends Elements, S extends Selectors> =
96
+ | SideItem<E, S>
97
+ | readonly SideItem<E, S>[];
98
+
99
+ /** Files by path, relative to the config folder, with their text. */
100
+ export type ExampleFiles = Readonly<Record<string, string>>;
101
+
102
+ /** What every rule may have. */
103
+ export interface RuleOptions {
104
+ /** The rule's name, for exceptions and the baseline; each rule gives a default. */
105
+ readonly name?: string;
106
+ /** Why the rule exists, shown with each violation. */
107
+ readonly reason?: string;
108
+ /** Where to read more, shown with each violation. */
109
+ readonly link?: string;
110
+ /** Which imports the rule checks: all of them, those that stay at runtime, or type imports. */
111
+ readonly imports?: 'all' | 'runtime' | 'types';
112
+ /** File sets the rule must accept and must reject, which `archangel test` checks. */
113
+ readonly examples?: {
114
+ readonly accepted?: readonly ExampleFiles[];
115
+ readonly rejected?: readonly ExampleFiles[];
116
+ };
117
+ }
118
+
119
+ /**
120
+ * A rule name `N` that the config writes itself. A slash separates a preset's name from the names
121
+ * it declares, so the config's own name has one only after the name of a preset in `X`, the
122
+ * presets the config extends, and then replaces that preset's rule of the name. Any other name
123
+ * with a slash becomes the text of the type error.
124
+ */
125
+ export type OwnRuleName<N extends string, X extends string> = N extends `${infer Prefix}/${string}`
126
+ ? Prefix extends X
127
+ ? N
128
+ : "a rule's own name has no slash, except after the name of a preset the config extends, to replace its rule"
129
+ : N;
130
+
131
+ /** A rule's `name`, checked by {@link OwnRuleName}. */
132
+ type Named<N extends string, X extends string> = { readonly name?: OwnRuleName<N, X> };
133
+
134
+ /** What limits an import condition to some imports. */
135
+ export interface ImportLimits {
136
+ /** Only imports of these names. */
137
+ readonly names?: readonly string[];
138
+ /**
139
+ * Only imports written this way: starting with `./` or `../`, starting with `../`, through a
140
+ * tsconfig path or a package `imports` field, or as a package name.
141
+ */
142
+ readonly specifier?: 'relative' | 'parent' | 'alias' | 'package';
143
+ /** Only imports between the same instance, or between different instances, of `files`. */
144
+ readonly instance?: 'same' | 'different';
145
+ /**
146
+ * Only imports of packages that the importing file's nearest package.json lists only as
147
+ * development dependencies, or does not list at all.
148
+ */
149
+ readonly dependency?: 'dev' | 'undeclared';
150
+ }
151
+
152
+ /** A way of writing the words of a name. */
153
+ export type NameStyle = 'kebab-case' | 'camelCase' | 'PascalCase' | 'snake_case' | 'SCREAMING_SNAKE_CASE';
154
+
155
+ /**
156
+ * A name: text, `*` for any text, and `{FileName}`, `{fileName}`, `{file-name}`, `{file_name}` or
157
+ * `{FILE_NAME}` for the file's name in each style, or the same spellings of `{FolderName}` for the
158
+ * name of the folder that holds it.
159
+ */
160
+ export type NamePattern = string;
161
+
162
+ type Conditions<Other> = {
163
+ /** The files may not import the other side. */
164
+ readonly mayNotImport: Other;
165
+ /** The files may import only the other side. */
166
+ readonly mayOnlyImport: Other;
167
+ /** Only the other side may import the files. */
168
+ readonly mayOnlyBeImportedBy: Other;
169
+ /**
170
+ * The name, up to the first dot, is in a style, or the full name, without the last extension,
171
+ * matches a name pattern.
172
+ */
173
+ readonly mustBeNamed: NameStyle | (NamePattern & {});
174
+ /**
175
+ * Each instance of `files`, an element with a capture, has a file that matches the pattern, or
176
+ * one of the patterns.
177
+ */
178
+ readonly mustContain: string | readonly string[];
179
+ /** No file may match `files`. */
180
+ readonly mustNotExist: true;
181
+ /** Each file exports a name that matches the pattern; re-exports and `export *` count. */
182
+ readonly mustExport: NamePattern;
183
+ /** No file has a default export, an `export *`, or a named export that matches the pattern. */
184
+ readonly mayNotExport: 'default' | 'star' | (NamePattern & {});
185
+ /** Each file holds only imports, exports from other files and `export { … }` of imports. */
186
+ readonly mustOnlyReExport: true;
187
+ /** The files may not import each other in a cycle. */
188
+ readonly noCycles: true;
189
+ };
190
+
191
+ /** Condition `C`, with every other condition ruled out. */
192
+ type Only<Other, C extends keyof Conditions<Other>> = Pick<Conditions<Other>, C> & {
193
+ readonly [K in Exclude<keyof Conditions<Other>, C>]?: never;
194
+ };
195
+
196
+ type NoLimits = { readonly [K in keyof ImportLimits]?: never };
197
+
198
+ type NoChains = { readonly transitively?: false; readonly stopAt?: never };
199
+
200
+ /** A rule written as a sentence: `files`, and exactly one condition. */
201
+ export type Sentence<E extends Elements, S extends Selectors> = Omit<RuleOptions, 'imports'> & {
202
+ readonly kind?: never;
203
+ /** The files the rule is about. */
204
+ readonly files: Files<E, S>;
205
+ } & (
206
+ | (Only<Side<E, S>, 'mayNotImport'> & ImportLimits & NoChains & Pick<RuleOptions, 'imports'>)
207
+ | (Only<Side<E, S>, 'mayNotImport'> &
208
+ NoLimits & {
209
+ /** Follow chains of imports, not only direct ones. */
210
+ readonly transitively: true;
211
+ /** Files where a chain ends, such as `{ directive: 'use server' }`. */
212
+ readonly stopAt?: Files<E, S>;
213
+ /** A chain of type imports never runs, so a transitive rule checks no type imports. */
214
+ readonly imports?: 'all' | 'runtime';
215
+ })
216
+ | (Only<Side<E, S>, 'mayOnlyImport'> & ImportLimits & NoChains & Pick<RuleOptions, 'imports'>)
217
+ | (Only<Side<E, S>, 'mayOnlyBeImportedBy'> &
218
+ ImportLimits &
219
+ NoChains &
220
+ Pick<RuleOptions, 'imports'>)
221
+ | (Only<Side<E, S>, 'mustBeNamed'> & NoLimits & NoChains & NoImports)
222
+ | (Only<Side<E, S>, 'mustContain'> & NoLimits & NoChains & NoImports)
223
+ | (Only<Side<E, S>, 'mustNotExist'> & NoLimits & NoChains & NoImports)
224
+ | (Only<Side<E, S>, 'mustExport'> & NoLimits & NoChains & NoImports)
225
+ | (Only<Side<E, S>, 'mayNotExport'> & NoLimits & NoChains & NoImports)
226
+ | (Only<Side<E, S>, 'mustOnlyReExport'> & NoLimits & NoChains & NoImports)
227
+ | (Only<Side<E, S>, 'noCycles'> & NoLimits & NoChains & Pick<RuleOptions, 'imports'>)
228
+ );
229
+
230
+ type NoImports = { readonly imports?: never };
231
+
232
+ /** A rule about folders: `folders`, and `mustBeNamed` or `mustContain`. */
233
+ export type FolderSentence = Omit<RuleOptions, 'imports'> & {
234
+ readonly kind?: never;
235
+ readonly files?: never;
236
+ /**
237
+ * The folders the rule is about: those that one of these patterns matches, below the
238
+ * pattern's fixed part, so `src/**` is every folder inside `src`. A pattern that starts with
239
+ * `!` leaves the folders it matches out.
240
+ */
241
+ readonly folders: string | readonly string[];
242
+ } & NoLimits &
243
+ NoChains &
244
+ NoImports &
245
+ (
246
+ | (Only<never, 'mustBeNamed'>)
247
+ | (Only<never, 'mustContain'>)
248
+ | (Only<never, 'mustNotExist'>)
249
+ );
250
+
251
+ /** A sentence of a rule of several sentences: the rule holds its name, reason and examples. */
252
+ type InnerSentence<T> = T extends unknown ? Omit<T, 'name' | 'reason' | 'link' | 'examples' | 'imports'> : never;
253
+
254
+ /** A shorthand as one sentence of a rule of several sentences. */
255
+ type InnerShorthand<E extends Elements> =
256
+ | { readonly kind: 'layers'; readonly order: readonly NoInfer<ElementName<E>>[] }
257
+ | { readonly kind: 'isolate'; readonly element: NoInfer<CapturedElement<E>> }
258
+ | { readonly kind: 'entry'; readonly element: NoInfer<ElementName<E>>; readonly files: readonly string[] };
259
+
260
+ /** A rule of several sentences, broken when any of them is, which needs a name. */
261
+ export type SeveralSentences<E extends Elements, S extends Selectors> = Omit<RuleOptions, 'name'> & {
262
+ readonly kind?: never;
263
+ readonly files?: never;
264
+ /** The rule's name, for exceptions and the baseline. */
265
+ readonly name: string;
266
+ /** The sentences, each about `files` or `folders` with one condition, or a shorthand. */
267
+ readonly sentences: readonly (InnerSentence<Sentence<E, S> | FolderSentence> | InnerShorthand<E>)[];
268
+ };
269
+
270
+ /** The plugins a config loads, by the name its rules use them by. */
271
+ export type Plugins = Readonly<Record<string, Plugin>>;
272
+
273
+ /** The name of a condition of a loaded plugin, as a rule writes it: `<plugin>/<condition>`. */
274
+ export type ConditionName<P extends Plugins> = {
275
+ [K in Extract<keyof P, string>]: P[K]['conditions'][number] extends infer C
276
+ ? C extends AnyCondition
277
+ ? `${K}/${C['name']}`
278
+ : never
279
+ : never;
280
+ }[Extract<keyof P, string>];
281
+
282
+ /** A rule that uses a plugin's condition, whose options the condition's schema checks. */
283
+ export type PluginRule<E extends Elements, S extends Selectors, P extends Plugins> = Omit<
284
+ RuleOptions,
285
+ 'imports'
286
+ > & {
287
+ readonly kind?: never;
288
+ /** The files whose records the condition sees. */
289
+ readonly files: Files<E, S>;
290
+ /** The condition, as `<plugin>/<condition>`. */
291
+ readonly plugin: NoInfer<ConditionName<P>>;
292
+ /** The options, which the condition's JSON Schema checks when the config loads. */
293
+ readonly options?: unknown;
294
+ };
295
+
296
+ /** A rule, as an object: one of the three shorthands, a sentence, or a plugin rule. */
297
+ export type Rule<E extends Elements, S extends Selectors = {}, P extends Plugins = {}> =
298
+ | (RuleOptions & { readonly kind: 'layers'; readonly order: readonly NoInfer<ElementName<E>>[] })
299
+ | (RuleOptions & { readonly kind: 'isolate'; readonly element: NoInfer<CapturedElement<E>> })
300
+ | (RuleOptions & {
301
+ readonly kind: 'entry';
302
+ readonly element: NoInfer<ElementName<E>>;
303
+ readonly files: readonly string[];
304
+ })
305
+ | Sentence<E, S>
306
+ | FolderSentence
307
+ | SeveralSentences<E, S>
308
+ | PluginRule<E, S, P>;
309
+
310
+ /**
311
+ * The rule builders that a `rules` function receives, for a config that extends the presets
312
+ * named in `X`.
313
+ */
314
+ export interface RuleBuilders<
315
+ E extends Elements,
316
+ S extends Selectors = {},
317
+ P extends Plugins = {},
318
+ X extends string = string,
319
+ > {
320
+ /** Each listed element may import only itself and later listed elements. */
321
+ layers<const N extends string = string>(
322
+ order: readonly NoInfer<ElementName<E>>[],
323
+ options?: RuleOptions & Named<N, X>,
324
+ ): Rule<E, S, P>;
325
+ /** The instances of the element may not import each other. */
326
+ isolate<const N extends string = string>(
327
+ element: NoInfer<CapturedElement<E>>,
328
+ options?: RuleOptions & Named<N, X>,
329
+ ): Rule<E, S, P>;
330
+ /** Code outside an instance of the element may import it only through these files. */
331
+ entry<const N extends string = string>(
332
+ element: NoInfer<ElementName<E>>,
333
+ files: string | readonly string[],
334
+ options?: RuleOptions & Named<N, X>,
335
+ ): Rule<E, S, P>;
336
+ /** A rule as a sentence: `files`, or `folders`, and exactly one condition or plugin condition. */
337
+ rule<const N extends string = string>(
338
+ sentence: (Sentence<E, S> | FolderSentence | SeveralSentences<E, S> | PluginRule<E, S, P>) &
339
+ Named<N, X>,
340
+ ): Rule<E, S, P>;
341
+ }
342
+
343
+ /**
344
+ * What a preset returns: part of a config, which `extends` puts under the config. Archangel names
345
+ * each element, selector and rule `<name>/<its name>`, and reads paths relative to `root`.
346
+ */
347
+ export interface Preset<X extends string = string> {
348
+ /** The prefix of everything the preset declares, such as `fsd`. */
349
+ readonly name: X;
350
+ /** The package the preset comes from, which a problem its user cannot fix names. */
351
+ readonly package?: string;
352
+ /** The folder the preset applies to, relative to the config folder. */
353
+ readonly root?: string;
354
+ readonly elements?: Elements;
355
+ readonly selectors?: Selectors;
356
+ readonly plugins?: Plugins;
357
+ /** The rules, each with a name. */
358
+ readonly rules?: readonly unknown[] | ((builders: RuleBuilders<Elements, Selectors, Plugins>) => readonly unknown[]);
359
+ }
360
+
361
+ /** The options of the Feature-Sliced Design preset. */
362
+ export interface FsdOptions<X extends string = string> {
363
+ /** The folder that holds the layers, such as `src`. */
364
+ readonly root: string;
365
+ /** Turn on Steiger's four opt-in rules too. */
366
+ readonly strict?: boolean;
367
+ /** The prefix, for a second use in one repository; `fsd` by default. */
368
+ readonly name?: X;
369
+ }
370
+
371
+ /** The Feature-Sliced Design preset: Steiger's rules for the layers under `root`. */
372
+ export declare function fsd<const X extends string = 'fsd'>(options: FsdOptions<X>): Preset<X>;
373
+
374
+ /** The Next.js preset: code a client component reaches stays out of server-only code. */
375
+ export declare function nextjs<const X extends string = 'nextjs'>(options?: {
376
+ readonly name?: X;
377
+ readonly root?: string;
378
+ }): Preset<X>;
379
+
380
+ /**
381
+ * An archangel config, which extends the presets named in `X` and names its own rules with the
382
+ * names in `N`.
383
+ */
384
+ export interface Config<
385
+ E extends Elements,
386
+ S extends Selectors = {},
387
+ P extends Plugins = {},
388
+ X extends string = string,
389
+ N extends string = string,
390
+ > {
391
+ /** The elements: parts of the code by file pattern, or packages; a config that extends a preset may have none. */
392
+ readonly elements?: E;
393
+ /** Named sets of files, which rules and other selectors use by name. */
394
+ readonly selectors?: S;
395
+ /** Lines in `.gitignore` syntax that remove files from the check. */
396
+ readonly ignore?: readonly string[];
397
+ /** Specifiers that may stay unresolved, each with the reason; a specifier may end in `*`. */
398
+ readonly allowUnresolved?: Readonly<Record<string, string>>;
399
+ /** The plugins, by the name the rules use them by, as `plugin: '<name>/<condition>'`. */
400
+ readonly plugins?: P;
401
+ /** The presets the config extends; the config's own rules replace preset rules of one name. */
402
+ readonly extends?: readonly Preset<X>[];
403
+ /** Rule names to turn off after the presets are merged, such as `fsd/insignificant-slice`. */
404
+ readonly off?: readonly string[];
405
+ /** The rules, or a function that builds them. */
406
+ readonly rules:
407
+ | readonly (Rule<E, S, P> & Named<N, X>)[]
408
+ | ((builders: RuleBuilders<E, S, P, X>) => readonly (Rule<E, S, P> & Named<N, X>)[]);
409
+ }
410
+
411
+ /** Declares an archangel config, with types that check its names as it is written. */
412
+ export declare function defineConfig<
413
+ const E extends Elements,
414
+ const S extends Selectors = {},
415
+ const P extends Plugins = {},
416
+ const X extends string = never,
417
+ const N extends string = string,
418
+ >(config: Config<E, S, P, X, N>): Config<E, S, P>;
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // The config helper and the presets. `defineConfig` returns its argument: its only work is in the
2
+ // types, which check names, conditions and captures as the config is written.
3
+
4
+ /** @type {import('./index.d.ts').defineConfig} */
5
+ export const defineConfig = (config) => config;
6
+
7
+ export * from './plugin.js';
8
+ export { fsd } from './presets/fsd.js';
9
+ export { nextjs } from './presets/nextjs.js';
@@ -0,0 +1,41 @@
1
+ // Which platform package holds the binary for this machine. Kept apart from the launcher so the
2
+ // tests can ask about any platform.
3
+
4
+ /** The platform packages, by the key the launcher computes for a machine. */
5
+ export const PACKAGES = {
6
+ 'darwin-arm64': '@enhance-eng/archangel-darwin-arm64',
7
+ 'darwin-x64': '@enhance-eng/archangel-darwin-x64',
8
+ 'linux-arm64-gnu': '@enhance-eng/archangel-linux-arm64-gnu',
9
+ 'linux-arm64-musl': '@enhance-eng/archangel-linux-arm64-musl',
10
+ 'linux-x64-gnu': '@enhance-eng/archangel-linux-x64-gnu',
11
+ 'linux-x64-musl': '@enhance-eng/archangel-linux-x64-musl',
12
+ 'win32-x64': '@enhance-eng/archangel-win32-x64',
13
+ };
14
+
15
+ /** The key of a machine: the platform and the CPU, and on Linux the C library. */
16
+ export function platformKey({ platform, arch, musl }) {
17
+ if (platform !== 'linux') return `${platform}-${arch}`;
18
+ return `${platform}-${arch}-${musl ? 'musl' : 'gnu'}`;
19
+ }
20
+
21
+ /** Whether this Linux uses musl: Node reports a glibc version only when it runs on glibc. */
22
+ export function usesMusl(report = process.report?.getReport()) {
23
+ return process.platform === 'linux' && !report?.header?.glibcVersionRuntime;
24
+ }
25
+
26
+ /** What to say when no binary was built for the machine. */
27
+ export function unsupported(key) {
28
+ return (
29
+ `archangel has no prebuilt binary for ${key}. ` +
30
+ 'Install it from source with `cargo install archangel`, which needs Rust.'
31
+ );
32
+ }
33
+
34
+ /** What to say when the machine's package is not installed, as after `npm install --no-optional`. */
35
+ export function missing(key, name) {
36
+ return (
37
+ `archangel's binary for ${key} is in the package ${name}, which is not installed. ` +
38
+ 'Install without --no-optional or --omit=optional, or install it from source with ' +
39
+ '`cargo install archangel`.'
40
+ );
41
+ }
package/package.json CHANGED
@@ -1,16 +1,64 @@
1
1
  {
2
2
  "name": "@enhance-eng/archangel",
3
- "version": "0.0.1",
4
- "description": "Bootstrap placeholder for Archangel, the TypeScript architecture linter",
3
+ "version": "0.1.1",
4
+ "description": "Make architecture verifiable. Architecture tests for TypeScript, checked in milliseconds: imports, cycles, names, folder structure and exports.",
5
+ "keywords": [
6
+ "architecture",
7
+ "archunit",
8
+ "architecture-tests",
9
+ "lint",
10
+ "typescript",
11
+ "imports",
12
+ "boundaries",
13
+ "dependency-cruiser",
14
+ "feature-sliced-design",
15
+ "monorepo",
16
+ "language-server"
17
+ ],
5
18
  "license": "MIT OR Apache-2.0",
6
19
  "repository": {
7
20
  "type": "git",
8
- "url": "git+https://github.com/enhance-labs-builds/archangel.git"
21
+ "url": "git+https://github.com/enhance-labs-builds/archangel.git",
22
+ "directory": "npm/archangel"
9
23
  },
10
- "publishConfig": {
11
- "access": "public"
24
+ "homepage": "https://github.com/enhance-labs-builds/archangel#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/enhance-labs-builds/archangel/issues"
27
+ },
28
+ "type": "module",
29
+ "types": "index.d.ts",
30
+ "bin": {
31
+ "archangel": "bin/archangel.js"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./index.d.ts",
36
+ "default": "./index.js"
37
+ }
12
38
  },
13
39
  "files": [
14
- "README.md"
15
- ]
40
+ "bin/",
41
+ "lib/",
42
+ "index.js",
43
+ "index.d.ts",
44
+ "plugin.js",
45
+ "plugin.d.ts",
46
+ "presets/",
47
+ "archangel.schema.json"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "dependencies": {
53
+ "pluralize": "8.0.0"
54
+ },
55
+ "optionalDependencies": {
56
+ "@enhance-eng/archangel-darwin-arm64": "0.1.1",
57
+ "@enhance-eng/archangel-darwin-x64": "0.1.1",
58
+ "@enhance-eng/archangel-linux-arm64-gnu": "0.1.1",
59
+ "@enhance-eng/archangel-linux-arm64-musl": "0.1.1",
60
+ "@enhance-eng/archangel-linux-x64-gnu": "0.1.1",
61
+ "@enhance-eng/archangel-linux-x64-musl": "0.1.1",
62
+ "@enhance-eng/archangel-win32-x64": "0.1.1"
63
+ }
16
64
  }