@uniflowed/config 0.0.0-alpha.9 → 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.
package/index.js CHANGED
@@ -3,10 +3,21 @@
3
3
  // `@uniflowed/config`.
4
4
 
5
5
  export type {
6
+ BuilderSpec,
6
7
  CapabilityJsHost,
7
8
  CoverageThresholds,
9
+ DeployAdapter,
10
+ PackageManagerPreference,
11
+ PackageManagerSpec,
12
+ Permissions,
13
+ PluginEntry,
8
14
  RuleLevel,
15
+ RuntimeEngine,
16
+ RuntimeSpec,
17
+ SizeBudget,
18
+ TaskArgument,
9
19
  TaskDefinition,
20
+ TestRunnerSpec,
10
21
  UniflowedConfig,
11
22
  } from "./internal/schema.js";
12
23
 
@@ -2,9 +2,67 @@
2
2
  //
3
3
  // Owns the Flow shape of `uf.config.js`; `index.js` keeps the public package
4
4
  // entry point thin.
5
+ //
6
+ // # Every key uf reads, and nothing else
7
+ //
8
+ // The guide says a config file "is type-checked as Flow code", which is the
9
+ // whole reason to write one in Flow. That makes an undeclared key worse than a
10
+ // missing feature: `lint.files`, `lint.ignore` and `app.router.enabled` were
11
+ // documented in the configuration reference, accepted by the runtime, and
12
+ // absent from this type — so a project following the reference had to choose
13
+ // between the documented key and a config that checks
14
+ // (ubugeeei-prod/uf#481). They were not the only three.
15
+ //
16
+ // So the key *names* below are no longer a person's job to keep in step with
17
+ // the loader. `crates/uf_config`'s `the_flow_schema_declares_every_key_uf_reads`
18
+ // parses this file with uf's own Flow parser and compares the paths it declares
19
+ // against the paths `uf_config::UniflowedConfig` serializes — in both
20
+ // directions, because a key declared here and read by nothing is an option that
21
+ // silently does nothing, which is the same defect wearing the other face.
22
+ //
23
+ // The value *types* are still this file's own judgement, and deliberately so.
24
+ // A test over names cannot say whether `"biome" | "prettier" | "none"` is the
25
+ // right set, and several keys below are narrower than what the loader will
26
+ // parse — `orm.module` is `"@uniflowed/orm"` because there is one
27
+ // implementation, where the loader takes any string. Where this package means
28
+ // to be more opinionated than the parser, that is what these say.
29
+ //
30
+ // # What an editor shows
31
+ //
32
+ // `uf lsp` completes and explains `uf.config.js` from this file, compiled into
33
+ // the binary. A key's completion shows the comment directly above the key and
34
+ // the type as it is written here, and the members of a literal union are the
35
+ // values it offers — so a comment above a key is written for somebody typing
36
+ // that key. Either kind of comment counts above a key; above a type alias only
37
+ // a `/** */` block does, because the `//` notes on the aliases are this file's
38
+ // history rather than the key's meaning. `crates/uf_config/src/schema.rs` is
39
+ // the reader, and says exactly what it takes.
5
40
 
6
41
  export type RuleLevel = "off" | "warn" | "error" | 0 | 1 | 2 | boolean;
7
42
 
43
+ /**
44
+ * One argument a task takes, filled by `uf run <task>`.
45
+ *
46
+ * Given after the task's name, in the order declared, or as `--name value`; the
47
+ * values are appended to `command` in that order either way. At a terminal, a
48
+ * required argument that was not given is picked from `choices`, or typed; in
49
+ * CI and pipelines it is an error that names it.
50
+ */
51
+ export type TaskArgument = {
52
+ // What `--name` and the picker call it: letters, digits, `-` and `_`.
53
+ readonly name: string,
54
+ // One line saying what it is for, shown in the picker.
55
+ readonly description?: string,
56
+ // The only values it may take. Anything else is refused, and at a terminal
57
+ // these are the list to pick from.
58
+ readonly choices?: $ReadOnlyArray<string>,
59
+ // Used when it is not given. An argument with a default is never asked for.
60
+ readonly default?: string,
61
+ // Whether leaving it out is an error. Defaults to `true` unless there is a
62
+ // `default`; an optional argument with no default must come last.
63
+ readonly required?: boolean,
64
+ };
65
+
8
66
  export type TaskDefinition =
9
67
  | string
10
68
  | {
@@ -12,10 +70,260 @@ export type TaskDefinition =
12
70
  readonly cwd?: string,
13
71
  readonly dependsOn?: $ReadOnlyArray<string>,
14
72
  readonly env?: { readonly [string]: string },
73
+ // Everything the task reads, as paths or globs from the project root; a
74
+ // pattern beginning `!` excludes. This is the whole of the cache key, so
75
+ // a task that lists nothing is never cached and always runs.
76
+ readonly inputs?: $ReadOnlyArray<string>,
77
+ // Everything it writes. Checked rather than restored: a replayed result
78
+ // has to still have its files on disk, unchanged.
79
+ readonly outputs?: $ReadOnlyArray<string>,
80
+ // `false` keeps a task with declared inputs out of the cache.
81
+ readonly cache?: boolean,
82
+ // The arguments `uf run` fills, in the order they are appended to
83
+ // `command`.
84
+ readonly args?: $ReadOnlyArray<TaskArgument>,
15
85
  };
16
86
 
17
87
  export type CapabilityJsHost = "node" | "deno" | "bun";
18
88
 
89
+ /**
90
+ * The package manager uf drives, overriding what it infers from the project.
91
+ *
92
+ * `"auto"` reads the project itself: an explicit `"packageManager"` field, then
93
+ * a lockfile, then the nearest workspace root, then uf's own resolver.
94
+ */
95
+ // The eight below are the *names a project may pin*, not commands this module
96
+ // runs: `@uniflowed/config` declares a type and executes nothing at all.
97
+ // `uniflowed/no-npm-script-invocation` is a line scanner, and a string whose
98
+ // entire contents is `pnpm` reads exactly like the `spawn("pnpm", […])` the
99
+ // rule exists to catch — `crates/uf_lint/src/scan/search.rs` says so in its own
100
+ // documentation and calls what is left "rare and suppressible". This is that
101
+ // residue, and there is no spelling of these values that is not one of them.
102
+ // uf-lint-disable uniflowed/no-npm-script-invocation
103
+ export type PackageManagerPreference =
104
+ | "auto"
105
+ | "uf"
106
+ | "npm"
107
+ | "pnpm"
108
+ | "yarn"
109
+ | "yarn-classic"
110
+ | "yarn-berry"
111
+ | "bun";
112
+ // uf-lint-enable uniflowed/no-npm-script-invocation
113
+
114
+ // The runtimes a project may say it is written for, which are the runtimes uf
115
+ // has a host for. It read `"uf" | ... | "edge" | "serverless" | "container"`,
116
+ // and those four are rows `uf_runtime::HOSTS` grades planned with no Flow
117
+ // loader — so a project that named one could not import its own first file
118
+ // there, and every command went on running on Node anyway. uf refuses them at
119
+ // the key that named them now; this type is where a reader finds that out
120
+ // before `uf check` does. See docs/hosts.md and ubugeeei-prod/uf#246.
121
+ //
122
+ // A deployment target is `DeployAdapter`, on `app.runtime.deploy`, and is a
123
+ // different question with a longer list.
124
+ export type RuntimeEngine = "node" | "deno" | "bun";
125
+
126
+ export type DeployAdapter =
127
+ | "node"
128
+ | "bun"
129
+ | "deno"
130
+ | "edge"
131
+ | "serverless"
132
+ | "static"
133
+ | "container";
134
+
135
+ // # Tools, declared where they are used
136
+ //
137
+ // The four aliases below are strings to Flow and a grammar to uf, which reads
138
+ // every one of them where the config is read and refuses what it cannot run —
139
+ // naming the key, what was written, and what to write instead. They are
140
+ // aliases rather than bare `string` so the grammar has one place to be written
141
+ // down, and so an editor can find a tool-spec key by its type — which is what
142
+ // completion in `uf.config.js` keys on (ubugeeei-prod/uf#941). Rename one and
143
+ // that stops working without a type error anywhere.
144
+ //
145
+ // A spec is `name[@version]`, and what follows the `@` is one of three things:
146
+ //
147
+ // * nothing — `"node"` — the `node` on `PATH`, which is what every project
148
+ // got before it could say anything else;
149
+ // * a numeric prefix — `"node@26"`, `"bun@1.4"` — the newest release that
150
+ // starts with it, resolved once against the publisher's index and locked in
151
+ // `uf.lock`, so every machine runs the same release until somebody moves it;
152
+ // * a full version — `"pnpm@12.0.0"` — exactly that release.
153
+ //
154
+ // A range — `"node@^26"`, `"node@>=24"`, `"node@24.x"` — is refused: a range is
155
+ // not an environment, because it can resolve to a different release tomorrow.
156
+ // So is a tag such as `"node@lts"`, for the same reason. ubugeeei-prod/uf#940.
157
+
158
+ /**
159
+ * A JavaScript runtime, and optionally which release of it: `"node"`,
160
+ * `"node@26"`, `"bun@1.3.5"`.
161
+ *
162
+ * The names are `node`, `bun` and `deno`. No version is the one on `PATH`; a
163
+ * prefix is the newest release that starts with it, locked in `uf.lock`; a full
164
+ * version is exactly that release. A range or a tag is refused.
165
+ *
166
+ * The type of `runtime`, `build.runtime` and `test.runtime`.
167
+ */
168
+ export type RuntimeSpec = string;
169
+
170
+ /**
171
+ * A package manager, and optionally which release of it: `"pnpm"`,
172
+ * `"pnpm@10"`, `"pnpm@12.0.0"`.
173
+ *
174
+ * The names are `npm`, `pnpm`, `yarn` and `bun`. Yarn's edition is its major
175
+ * version — `"yarn@1"` is Classic. No version is the one on `PATH`; a prefix is
176
+ * the newest release that starts with it, locked in `uf.lock`; a full version
177
+ * is exactly that release. A range or a tag is refused.
178
+ *
179
+ * The type of `packageManager`.
180
+ */
181
+ export type PackageManagerSpec = string;
182
+
183
+ /**
184
+ * What runs the test suite: `"uf"` or `"bun[@version]"`.
185
+ *
186
+ * `"uf"` is the runner built into uf, and the default; it takes no version,
187
+ * because it is the binary that is running. `"bun"` is `bun test`, on the Bun
188
+ * it names — so it also decides the test runtime when `test.runtime` is
189
+ * absent, and a `test.runtime` naming anything else is an error. The version
190
+ * follows the same grammar as a runtime's. Under a Bun runner `uf test` hands
191
+ * the files its discovery found to `bun test`, with uf's Flow preload, and
192
+ * `@uniflowed/test` resolves to `bun:test`; what Bun has no equivalent for
193
+ * raises `UnsupportedError` by name. See guide/testing, "Choosing a runner".
194
+ *
195
+ * The type of `test.runner`.
196
+ */
197
+ export type TestRunnerSpec = string;
198
+
199
+ /**
200
+ * Which builder `uf dev`, `uf build`, `uf preview` and `uf start` drive:
201
+ * `"vite"`, or a module specifier.
202
+ *
203
+ * `"vite"` is `@uniflowed/vite`, the builder uf ships and the default. Any other
204
+ * string is a module specifier — a package found up `node_modules`, or a path
205
+ * starting with `.` or `/` that must stay inside the project — whose driver
206
+ * satisfies the contract in docs/architecture.md.
207
+ *
208
+ * The type of `build.builder`.
209
+ */
210
+ export type BuilderSpec = string;
211
+
212
+ /**
213
+ * One entry of `plugins: [...]`.
214
+ *
215
+ * A bare name takes the default band and applies to every pipeline; the long
216
+ * form says otherwise. Declaration order decides within a band, so the
217
+ * resolved pipeline is a function of this file alone.
218
+ */
219
+ export type PluginEntry =
220
+ | string
221
+ | {
222
+ readonly name: string,
223
+ readonly order?: "pre" | "normal" | "post",
224
+ readonly apply?: "build" | "serve" | "always",
225
+ };
226
+
227
+ // # `app.router`'s three lists
228
+ //
229
+ // Next.js's `redirects`, `rewrites` and `headers`, written as data rather than
230
+ // as async functions, because this file is read without being run. A `source`
231
+ // is a path in the route table's own grammar — a literal segment, `:name` for
232
+ // one segment, and a trailing `:name*` for the rest — and each rule is an exact
233
+ // object, so a misspelled field is an error here and where `uf` reads the file.
234
+
235
+ /**
236
+ * One redirect: a request for `source` is answered with a redirect to
237
+ * `destination`, before anything else answers — a file included.
238
+ *
239
+ * `destination` is a path of this application or an absolute `http(s)` URL,
240
+ * and may use the `:name` and `:name*` segments `source` declared. The
241
+ * request's query is passed through. `permanent: true` is a `308`, `false` a
242
+ * `307`; both keep the method.
243
+ */
244
+ export type RouteRedirect = {
245
+ readonly source: string,
246
+ readonly destination: string,
247
+ readonly permanent: boolean,
248
+ };
249
+
250
+ /**
251
+ * One rewrite: a request for `source` is answered by the route at
252
+ * `destination`, and the address bar keeps `source`.
253
+ *
254
+ * `destination` is a path of this application — never another origin, which
255
+ * is a route handler that fetches — and may use `source`'s segments. Applied
256
+ * after the build's own files and before middleware, so the middleware that
257
+ * runs is the destination's.
258
+ */
259
+ export type RouteRewrite = {
260
+ readonly source: string,
261
+ readonly destination: string,
262
+ };
263
+
264
+ /**
265
+ * Response headers for every request whose path matches `source`, files
266
+ * included. Every matching rule applies, in order, and a later one setting the
267
+ * same name wins — over the response's own header of that name, too.
268
+ */
269
+ export type RouteHeaders = {
270
+ readonly source: string,
271
+ readonly headers: { readonly [name: string]: string },
272
+ };
273
+
274
+ /**
275
+ * Which spelling of a path is the page.
276
+ *
277
+ * `"never"` answers `/about/` with a `308` to `/about` and writes the page as
278
+ * `about.html`; `"always"` answers `/about` with a `308` to `/about/` and
279
+ * writes `about/index.html`; `"ignore"`, the default, answers both and writes
280
+ * `about/index.html`. `next.config.js`'s `trailingSlash: false`, `true` and
281
+ * `skipTrailingSlashRedirect: true`.
282
+ */
283
+ export type RouterTrailingSlash = "never" | "always" | "ignore";
284
+
285
+ /**
286
+ * A ceiling `uf build` fails over, and what it is measured on.
287
+ *
288
+ * `max` accepts a byte count or a size a person would write — `"180kb"` —
289
+ * because a budget is written by hand and read back by a report.
290
+ */
291
+ export type SizeBudget = {
292
+ readonly max: number | string,
293
+ readonly metric?: "raw" | "gzip" | "brotli",
294
+ };
295
+
296
+ /**
297
+ * What the project's own code may reach.
298
+ *
299
+ * Absent from `uf.config.js` means no permission model: the toolchain starts
300
+ * its host the way it always has. Present means **deny by default** — the
301
+ * project's code gets what is listed and nothing else — and `permissions: {}`
302
+ * is a legitimate thing to write, meaning "nothing beyond what uf itself needs
303
+ * to load and transform the project". There is deliberately no way to spell
304
+ * "everything"; a project that wants everything does not declare a set.
305
+ *
306
+ * Every field is a list of literal strings, because `uf.config.js` is read as
307
+ * text and parsed as JSON5 rather than executed: a computed path or a
308
+ * `process.env` read here would not parse.
309
+ *
310
+ * The set is uf's, not a runtime's. Node.js enforces `read` and `write`, Deno
311
+ * enforces all five, and Bun has no permission model at all — a host that
312
+ * cannot enforce what is declared **refuses the run** rather than applying part
313
+ * of it. `docs/hosts.md` is the table.
314
+ *
315
+ * The declared paths are *added to* the ones uf needs to run the project, so
316
+ * what a set denies is the rest of the machine — `~/.ssh`, the network, the
317
+ * environment — and not the project's own files.
318
+ */
319
+ export type Permissions = {
320
+ readonly read?: $ReadOnlyArray<string>,
321
+ readonly write?: $ReadOnlyArray<string>,
322
+ readonly net?: $ReadOnlyArray<string>,
323
+ readonly env?: $ReadOnlyArray<string>,
324
+ readonly run?: $ReadOnlyArray<string>,
325
+ };
326
+
19
327
  /**
20
328
  * Percentages a coverage gate requires, as whole numbers between 0 and 100.
21
329
  *
@@ -30,7 +338,52 @@ export type CoverageThresholds = {
30
338
  };
31
339
 
32
340
  export type UniflowedConfig = {
341
+ /**
342
+ * The runtime accessibility audit: `expect(el).toHaveNoAxeViolations()` in a
343
+ * test, and the page `uf dev` is serving.
344
+ *
345
+ * One block for both on purpose. A rule a project has decided cannot be
346
+ * judged here — `color-contrast` against a DOM with no layout is the
347
+ * standing example — has to be the same rule in CI and in the loop somebody
348
+ * is working in, or the audit that finds a violation while the component is
349
+ * being written disagrees with the one that blocks the pull request.
350
+ *
351
+ * Inert without axe-core, which uf does not install: add it and both halves
352
+ * start working.
353
+ *
354
+ * Not the same thing as `lint.rules`' `a11y/*`, which read JSX that was
355
+ * never rendered.
356
+ */
357
+ readonly accessibility?: {
358
+ readonly devAudit?: boolean,
359
+ readonly axe?: {
360
+ // Run only rules carrying one of these axe tags; every rule when absent.
361
+ readonly tags?: $ReadOnlyArray<string>,
362
+ readonly disabledRules?: $ReadOnlyArray<string>,
363
+ readonly minImpact?: "minor" | "moderate" | "serious" | "critical",
364
+ },
365
+ },
33
366
  readonly app?: {
367
+ // Whether a component with no directive is rendered on the server or
368
+ // shipped to the browser.
369
+ readonly componentDefault?: "server" | "client",
370
+ readonly framework?: "uniflowed" | "react" | "react-native",
371
+ readonly react?: {
372
+ // React 19's Strict Mode, on by default in `uf dev`: it double-invokes
373
+ // render and effects so an impurity shows up in development rather
374
+ // than in production. Off has to be a choice a project makes.
375
+ readonly strictMode?: boolean,
376
+ readonly version?: string,
377
+ readonly asyncReact?: boolean,
378
+ readonly suspense?: boolean,
379
+ readonly useHook?: boolean,
380
+ },
381
+ // Whether the project builds React Server Components at all, and whether
382
+ // a `"use server"` export is wired to an endpoint.
383
+ readonly rsc?: boolean,
384
+ readonly serverActions?: boolean,
385
+ // The runtimes the build must satisfy.
386
+ readonly targets?: $ReadOnlyArray<"web" | "react-native" | "server" | "hermes">,
34
387
  readonly orm?: {
35
388
  readonly enabled?: boolean,
36
389
  readonly module?: "@uniflowed/orm",
@@ -39,11 +392,18 @@ export type UniflowedConfig = {
39
392
  readonly preparedByDefault?: true,
40
393
  },
41
394
  readonly builtins?: {
395
+ readonly data?: "uniflowed-query",
396
+ readonly effect?: "uniflowed-effect",
42
397
  readonly fetch?: {
43
398
  readonly module?: "@uniflowed/fetch",
44
399
  readonly overrideGlobalFetch?: false,
45
400
  },
46
401
  readonly cell?: boolean,
402
+ readonly frameworkLints?: boolean,
403
+ readonly nativeTestRunner?: boolean,
404
+ readonly reactTestingLibrary?: boolean,
405
+ readonly relay?: boolean,
406
+ readonly style?: "style-x",
47
407
  readonly reactCompiler?: {
48
408
  readonly enabled?: boolean,
49
409
  readonly implementation?: "official-rust",
@@ -66,6 +426,19 @@ export type UniflowedConfig = {
66
426
  readonly extensions?: $ReadOnlyArray<".mdx">,
67
427
  readonly jsxImportSource?: "@uniflowed/jsx-runtime",
68
428
  readonly pipelinePlugin?: "built-in",
429
+ // Colours are computed during the build and written into the HTML,
430
+ // so nothing ships to the browser to do it. Both themes are emitted
431
+ // together as CSS variables, because a build cannot know which the
432
+ // reader prefers.
433
+ readonly highlight?: {
434
+ readonly enabled?: boolean,
435
+ readonly themes?: {
436
+ readonly light?: string,
437
+ readonly dark?: string,
438
+ },
439
+ // Grammars beyond the ones a uf project uses by default.
440
+ readonly langs?: $ReadOnlyArray<string>,
441
+ },
69
442
  },
70
443
  readonly cache?: "opt-in",
71
444
  },
@@ -76,6 +449,34 @@ export type UniflowedConfig = {
76
449
  readonly widths?: $ReadOnlyArray<number>,
77
450
  readonly quality?: number,
78
451
  readonly placeholder?: boolean,
452
+ // The remote images `/__uf/image` may fetch, resize and re-encode —
453
+ // Next.js's `images.remotePatterns`, with its four keys and its
454
+ // wildcards. Empty, the default, means there is no endpoint at all. A
455
+ // redirect is followed only to a URL that matches too. See
456
+ // docs/app/guide/assets.
457
+ readonly remotePatterns?: $ReadOnlyArray<{
458
+ // `"https"` when absent; `"http"` has to be said.
459
+ readonly protocol?: "https" | "http",
460
+ // A name, or one under a leading `*.` (one more label) or `**.`
461
+ // (any number). A bare `*` is refused: it is not an allow-list.
462
+ readonly hostname: string,
463
+ // The protocol's default port when absent, and no other.
464
+ readonly port?: string,
465
+ // `*` is one segment and `**` any number. Any path when absent.
466
+ readonly pathname?: string,
467
+ }>,
468
+ // Qualities the endpoint answers besides `quality`. Each one is one
469
+ // more encode of every remote image a stranger can ask for, so the
470
+ // endpoint takes the listed ones and no others.
471
+ readonly qualities?: $ReadOnlyArray<number>,
472
+ // Fetch from loopback, private and link-local addresses too. For a
473
+ // test that serves its own images; never for a deployment.
474
+ readonly dangerouslyAllowPrivateAddresses?: boolean,
475
+ // A module exporting `createImageTransformer`, for a deploy target
476
+ // with no encoder of its own: `--adapter node`, `bun`, `deno`,
477
+ // `container` and `serverless`. `uf start` and `uf preview` encode
478
+ // with uf, and `--adapter edge` with Cloudflare's image binding.
479
+ readonly transformer?: string,
79
480
  },
80
481
  readonly fonts?: {
81
482
  readonly enabled?: boolean,
@@ -84,6 +485,27 @@ export type UniflowedConfig = {
84
485
  // faces `uf_assets::font::LOCAL_FACES` knows the metrics of, because
85
486
  // the scaling is a ratio against real numbers rather than a guess.
86
487
  readonly fallback?: string,
488
+ // How an imported family is cut down. `"none"` is the default and the
489
+ // argued position: subsetting is lossy, and a build cannot see the
490
+ // text a server will render or a user will type. `"ranges"` splits the
491
+ // font's own coverage into script buckets with exact `unicode-range`
492
+ // values and loses nothing.
493
+ readonly subset?: "none" | "ranges",
494
+ // Whether `Font` preloads the primary face. Exactly one file is ever
495
+ // preloaded, however many buckets a split produced.
496
+ readonly preload?: boolean,
497
+ },
498
+ readonly icons?: {
499
+ readonly enabled?: boolean,
500
+ // Where `uf:icon/<name>` looks for `<name>.svg`, from the project
501
+ // root. A directory rather than a claim on `.svg`, which stays Vite's.
502
+ readonly dir?: string,
503
+ },
504
+ readonly og?: {
505
+ readonly enabled?: boolean,
506
+ // The typeface every `*.og.json` is drawn with unless it names its
507
+ // own. uf embeds none, so a project that draws cards points at one.
508
+ readonly font?: string | null,
87
509
  },
88
510
  readonly motion?: {
89
511
  readonly module?: "@uniflowed/motion",
@@ -118,10 +540,8 @@ export type UniflowedConfig = {
118
540
  },
119
541
  },
120
542
  readonly runtime?: {
121
- readonly default?: "node" | "deno" | "bun" | "uf",
122
- readonly compatibility?: $ReadOnlyArray<
123
- "node" | "bun" | "deno" | "edge" | "serverless" | "container",
124
- >,
543
+ readonly default?: RuntimeEngine,
544
+ readonly compatibility?: $ReadOnlyArray<RuntimeEngine>,
125
545
  readonly capabilityJsHost?: {
126
546
  readonly default?: CapabilityJsHost,
127
547
  readonly hosts?: $ReadOnlyArray<CapabilityJsHost>,
@@ -129,36 +549,199 @@ export type UniflowedConfig = {
129
549
  },
130
550
  readonly deploy?: {
131
551
  readonly enabled?: boolean,
132
- readonly adapters?: $ReadOnlyArray<
133
- "node" | "bun" | "deno" | "edge" | "serverless" | "static" | "container",
134
- >,
552
+ // The target `uf build` writes an artefact for when none is named on
553
+ // the command line; `--adapter` beats it.
554
+ readonly adapter?: DeployAdapter,
555
+ readonly adapters?: $ReadOnlyArray<DeployAdapter>,
135
556
  },
136
557
  },
137
558
  readonly router?: {
559
+ // `false` says this project is not a uf application, and is the only way
560
+ // to say it: a library has no routes to scan for.
561
+ readonly enabled?: boolean,
138
562
  readonly entry?: string,
139
563
  readonly root?: string,
140
564
  readonly manifest?: string,
565
+ readonly convention?: "file-system",
566
+ // Turning the file-system router off is what makes a project a
567
+ // **library** rather than an application, and `uf build` reads it: see
568
+ // `build.lib` below and docs/app/reference/config.
569
+ readonly enabled?: boolean,
570
+ /**
571
+ * Redirects answered before anything else, in order; the first whose
572
+ * `source` matches wins. `next.config.js`'s `redirects()`. See
573
+ * `RouteRedirect` and docs/app/guide/routing.
574
+ */
575
+ readonly redirects?: $ReadOnlyArray<RouteRedirect>,
576
+ /**
577
+ * Routes served at another path, in order; the first whose `source`
578
+ * matches wins. `next.config.js`'s `rewrites()`, without proxying. See
579
+ * `RouteRewrite` and docs/app/guide/routing.
580
+ */
581
+ readonly rewrites?: $ReadOnlyArray<RouteRewrite>,
582
+ /**
583
+ * Response headers by path; every matching rule applies.
584
+ * `next.config.js`'s `headers()`. See `RouteHeaders` and
585
+ * docs/app/guide/routing.
586
+ */
587
+ readonly headers?: $ReadOnlyArray<RouteHeaders>,
588
+ /**
589
+ * The path the whole application is served under — `"/docs"` — with no
590
+ * trailing slash. Every link, redirect, asset URL, payload URL and
591
+ * sitemap entry carries it, and a request outside it is a `404`. The
592
+ * sources of the three lists above are written without it.
593
+ * `next.config.js`'s `basePath`.
594
+ */
595
+ readonly basePath?: string,
596
+ /**
597
+ * Which spelling of a path is the page; see `RouterTrailingSlash`.
598
+ * `"ignore"` when absent.
599
+ */
600
+ readonly trailingSlash?: RouterTrailingSlash,
601
+ /** HTTPS links explicitly claimed by the iOS and Android applications. */
602
+ readonly nativeLinks?: {|
603
+ readonly origins: $ReadOnlyArray<string>,
604
+ readonly routes: $ReadOnlyArray<string>,
605
+ readonly iosAppIds: $ReadOnlyArray<string>,
606
+ readonly androidPackage: string,
607
+ readonly androidSha256: $ReadOnlyArray<string>,
608
+ |},
141
609
  },
142
610
  readonly rendering?: {
143
- readonly modes?: $ReadOnlyArray<"ppr" | "ssr" | "ssg" | "isr">,
611
+ // `"csr"` is the one value that cannot share the list: it renders every
612
+ // route in the browser from one shell, where the others write a document
613
+ // per route, so there is no per-route choice left for the list to hold.
614
+ // `["csr"]` is a single-page application; see docs/app/guide/rendering.
615
+ readonly modes?: $ReadOnlyArray<"ppr" | "ssr" | "ssg" | "isr" | "csr">,
616
+ // What the browser does when a visitor follows a link, which is a
617
+ // different question from `modes` rather than a fifth value in it:
618
+ // `modes` says where a document comes from, per route, and this says
619
+ // what happens once the browser has one. `"document"` is a full document
620
+ // request — the client router is not installed and `Link` renders an
621
+ // ordinary anchor. See docs/app/guide/routing/navigation.
622
+ readonly navigation?: "client" | "document",
623
+ // How long, in whole seconds, the client router shows a route it already
624
+ // fetched or prefetched without asking the server again: a click on a
625
+ // prefetched link, a second visit and the back button all read it. `0`,
626
+ // the default, keeps nothing. `router.refresh()` and every server action
627
+ // clear it. See docs/app/guide/routing and docs/app/guide/cache.
628
+ readonly staleTime?: number,
144
629
  readonly cache?: {
145
630
  readonly actions?: boolean,
146
631
  readonly data?: boolean,
147
632
  readonly fetch?: boolean,
148
633
  readonly route?: boolean,
634
+ // Where cache entries live, and the only thing here that is a *name*
635
+ // rather than a switch: `"memory"` (the default) is one process,
636
+ // `"filesystem"` is uf's built-in durable provider, and anything else
637
+ // is a module specifier exporting `createCacheProvider` — the same
638
+ // shape `builder.module` has, and for the same red line. Turning this
639
+ // on caches nothing new: a route still has to state a lifetime.
640
+ readonly store?: string,
641
+ // Where `"filesystem"` keeps them. Defaults to `.uf/cache/route` under
642
+ // the project. A deployment that has one writable directory — `/tmp` on
643
+ // a Lambda, a mounted volume in a container — names it here.
644
+ readonly storeDir?: string,
149
645
  },
150
646
  },
151
647
  },
152
648
  readonly build?: {
649
+ // Size ceilings that fail the build. Unset by default: failing a build
650
+ // nobody asked uf to police is worse than reporting the size.
651
+ readonly budgets?: {
652
+ readonly total?: SizeBudget,
653
+ readonly initialJs?: SizeBudget,
654
+ readonly perRoute?: SizeBudget,
655
+ readonly perAsset?: SizeBudget,
656
+ },
153
657
  readonly entries?: $ReadOnlyArray<string>,
658
+ // Commands to run around the build, in the same shape as `tasks`.
659
+ readonly hooks?: { readonly [string]: TaskDefinition },
154
660
  readonly outDir?: string,
661
+ // Prerender every route and leave no server bundle behind. Read together
662
+ // with `app.rendering.modes`; see docs/app/reference/config.
155
663
  readonly staticBuild?: boolean,
156
664
  readonly sourcemap?: boolean,
665
+ /**
666
+ * What a **library** build writes, for a project whose
667
+ * `app.router.enabled` is false.
668
+ *
669
+ * Never the switch. `app.router.enabled` decides which of the two builds
670
+ * `uf build` runs, and declaring this key in a project whose router is on
671
+ * is refused where the config is read rather than resolved by precedence.
672
+ * Every field has a default that builds what `uf new --lib` scaffolds, so
673
+ * a library ordinarily writes none of this.
674
+ *
675
+ * `"umd"` and `"iife"` are deliberately absent from `formats`: each needs
676
+ * a global name per entry, and what a Flow library's global should be is
677
+ * not a decision uf has made.
678
+ */
679
+ readonly lib?: {
680
+ // The modules to build, relative to the project root. Each names its own
681
+ // output by its path — `internal/parse.js` is written to
682
+ // `dist/internal/parse.js` — so two entries cannot collide.
683
+ readonly entries?: $ReadOnlyArray<string>,
684
+ readonly formats?: $ReadOnlyArray<"es" | "cjs">,
685
+ // Package names to leave as imports *beyond* the ones the manifest
686
+ // declares. A library build already externalises `dependencies`,
687
+ // `peerDependencies`, `optionalDependencies` and the host's built-in
688
+ // modules; this is for what a manifest cannot say.
689
+ readonly external?: $ReadOnlyArray<string>,
690
+ /**
691
+ * Write a TypeScript declaration file beside each entry. On by
692
+ * default.
693
+ *
694
+ * Most people who install a Flow library write TypeScript, and to them
695
+ * a package with no `.d.ts` is `any`. Every Flow construct that has no
696
+ * TypeScript meaning is named in the build report rather than silently
697
+ * widened, so turning this off is for a library that would rather ship
698
+ * no declarations than ones with gaps in them — not for one that wants
699
+ * to stop hearing about the gaps.
700
+ */
701
+ readonly declarations?: boolean,
702
+ },
703
+ /**
704
+ * What `uf dev`, `uf build` and `uf preview` run on, when it is not the
705
+ * top-level `runtime`: `"node@26"`.
706
+ *
707
+ * Read before `runtime`, so a project that builds on Node and tests on Bun
708
+ * can say so. See `RuntimeSpec` for the grammar.
709
+ */
710
+ readonly runtime?: RuntimeSpec,
711
+ /**
712
+ * Which builder `uf dev`, `uf build`, `uf preview` and `uf start` drive:
713
+ * `"vite"`, or a module specifier.
714
+ *
715
+ * Vite is the default, not a dependency: any module satisfying the
716
+ * contract in docs/architecture.md can be named here. See `BuilderSpec`.
717
+ */
718
+ readonly builder?: BuilderSpec,
719
+ },
720
+ readonly builder?: {
721
+ /**
722
+ * The old spelling of `build.builder`.
723
+ *
724
+ * **Deprecated**, and read only when `build.builder` is absent; the two
725
+ * naming different builders is an error. `"@uniflowed/vite"` here is
726
+ * `builder: "vite"` there, and any other specifier moves as it is.
727
+ */
728
+ readonly module?: string,
157
729
  },
158
730
  readonly dev?: {
159
731
  readonly host?: string,
160
732
  readonly port?: number,
161
733
  readonly strictPort?: boolean,
734
+ // Which files the dev server may serve. `deny` is evaluated on the
735
+ // canonical path and beats `allow`; see docs/security.md.
736
+ readonly fs?: {
737
+ readonly allow?: $ReadOnlyArray<string>,
738
+ readonly deny?: $ReadOnlyArray<string>,
739
+ },
740
+ // `--host` refuses to bind a routable address while this is empty: a dev
741
+ // server reachable from the network with no host allow-list is a file
742
+ // server for your source tree.
743
+ readonly allowedHosts?: $ReadOnlyArray<string>,
744
+ readonly allowedOrigins?: $ReadOnlyArray<string>,
162
745
  },
163
746
  readonly docs?: {
164
747
  readonly enabled?: boolean,
@@ -168,13 +751,27 @@ export type UniflowedConfig = {
168
751
  readonly staticBuild?: boolean,
169
752
  readonly deploy?: "void",
170
753
  },
171
- readonly lint?: {
172
- readonly engine?: "rust",
173
- readonly flow?: {
174
- readonly builtins?: "mixed",
175
- readonly parser?: "official-flow-rust",
176
- },
177
- readonly rules?: { readonly [string]: RuleLevel },
754
+ /**
755
+ * The `.env` cascade and the mode it is read for.
756
+ *
757
+ * `active` empty means the command decides — `development` for `uf dev`,
758
+ * `production` for a build, `test` for `uf test`. `files` empty selects the
759
+ * conventional cascade rather than no files at all.
760
+ */
761
+ readonly env?: {
762
+ readonly active?: string,
763
+ readonly files?: $ReadOnlyArray<string>,
764
+ /**
765
+ * Runtimes and package managers by exact version — `{ node: "24.14.0" }`.
766
+ *
767
+ * **Deprecated.** It says which tools a project has and not what each is
768
+ * for, so a project that builds on Node and tests on Bun could not write
769
+ * that down. Declare each tool where it is used instead — `runtime`,
770
+ * `build.runtime`, `test.runtime` and `packageManager` — as
771
+ * `name@version`. It keeps working for `uf env install` and `uf env exec`,
772
+ * and a pin here that disagrees with one of those keys is an error.
773
+ */
774
+ readonly toolchain?: { readonly [string]: string },
178
775
  },
179
776
  readonly fmt?: {
180
777
  readonly indentWidth?: number,
@@ -186,10 +783,57 @@ export type UniflowedConfig = {
186
783
  },
187
784
  readonly nonFlow?: {
188
785
  readonly formatter?: "biome" | "prettier" | "none",
786
+ /**
787
+ * Extra arguments, passed to that formatter verbatim.
788
+ *
789
+ * Strings rather than a shape, so that reaching one of biome's or
790
+ * prettier's own options never waits for a uf release — a Tailwind 4
791
+ * project writes `["--css-parse-tailwind-directives=true"]` and needs no
792
+ * second configuration file. An argument that would turn `uf fmt
793
+ * --check` into a write is refused where the config is read.
794
+ */
795
+ readonly arguments?: $ReadOnlyArray<string>,
189
796
  },
190
797
  readonly quotes?: "single" | "double",
191
798
  readonly semicolons?: boolean,
192
799
  },
800
+ /**
801
+ * Paths no command walks into.
802
+ *
803
+ * Top level because every command that walks the project reads it: `uf fmt`,
804
+ * `uf lint`, `uf check`, `uf test` and `uf doc`. A bare name — `dist` — names
805
+ * a kind of directory and matches at any depth; a path — `src/generated` —
806
+ * names one place. `.uf` and `.git` are uf's and git's and are not a
807
+ * project's to opt back into.
808
+ *
809
+ * Absent takes uf's own list, `["node_modules", "dist", "target"]`. An empty
810
+ * list is a different instruction: it is a project that has looked at that
811
+ * list and wants none of it.
812
+ */
813
+ readonly ignore?: $ReadOnlyArray<string>,
814
+ readonly lint?: {
815
+ readonly engine?: "rust",
816
+ // Globs to lint.
817
+ readonly files?: $ReadOnlyArray<string>,
818
+ /**
819
+ * The old spelling of the top-level `ignore`.
820
+ *
821
+ * **Deprecated**, and read only when `ignore` is absent. It was never
822
+ * `uf lint`'s alone: `uf fmt`, `uf check`, `uf test` and `uf doc` walk the
823
+ * project through the same code and have always obeyed it, so the key was
824
+ * named after one of the five commands that read it. It keeps working for
825
+ * as long as alpha lasts, and every one of those commands says so once.
826
+ * See ubugeeei-prod/uf#575.
827
+ */
828
+ readonly ignore?: $ReadOnlyArray<string>,
829
+ readonly flow?: {
830
+ readonly builtins?: "mixed",
831
+ readonly parser?: "official-flow-rust",
832
+ },
833
+ // Changes to uf's rule table, not the whole of it: a rule you did not
834
+ // mention keeps the level uf ships. Say `"off"` to switch one off.
835
+ readonly rules?: { readonly [string]: RuleLevel },
836
+ },
193
837
  readonly package?: {
194
838
  readonly generator?: "napi-rs",
195
839
  readonly targets?: $ReadOnlyArray<
@@ -197,12 +841,73 @@ export type UniflowedConfig = {
197
841
  >,
198
842
  readonly typescriptDeclarationsToFlow?: true,
199
843
  },
844
+ /**
845
+ * The package manager `uf install`, `uf add`, `uf update` and the rest drive,
846
+ * and optionally which release of it: `"pnpm@12.0.0"`.
847
+ *
848
+ * Read before `pm.packageManager` — its deprecated spelling — and before
849
+ * `package.json#packageManager` and the lockfile. See `PackageManagerSpec`.
850
+ */
851
+ readonly packageManager?: PackageManagerSpec,
852
+ readonly permissions?: Permissions,
853
+ // Plugins the project adds, appended to uf's own and resolved in the order
854
+ // they are written. A name that names a file is code to run, so `uf_plugin`
855
+ // refuses any that reaches outside the project root.
856
+ readonly plugins?: $ReadOnlyArray<PluginEntry>,
200
857
  readonly pm?: {
201
858
  readonly module?: "@uniflowed/pm",
202
859
  readonly resolver?: "uf-native",
203
860
  readonly lockfile?: "uf.lock",
204
861
  readonly storeDir?: string,
205
862
  readonly allowLifecycleScripts?: false,
863
+ /**
864
+ * The package manager uf drives, in the spelling that came before the
865
+ * top-level `packageManager`.
866
+ *
867
+ * **Deprecated** for the managers `packageManager` can name — write
868
+ * `packageManager: "pnpm"` rather than `pm: { packageManager: "pnpm" }`,
869
+ * and `"yarn@1"` for `"yarn-classic"` — and read only when that key is
870
+ * absent. The two naming different managers is an error. `"uf"`, uf's own
871
+ * resolver, has no other spelling and is not deprecated.
872
+ */
873
+ readonly packageManager?: PackageManagerPreference,
874
+ /**
875
+ * The registry uf *reads* from: packuments, provenance attestations, and
876
+ * the versions `uf update` reports against.
877
+ *
878
+ * Unset means `publish.registry`, which is where this lived until it
879
+ * turned out to be answering two questions with one value. A project that
880
+ * publishes to a company registry and installs through a read-through
881
+ * mirror sets both; one that has only ever set `publish.registry` keeps
882
+ * working and is told, once, which key to move to.
883
+ */
884
+ readonly registry?: string,
885
+ /**
886
+ * Which registry answers for which scope: `{ "@company": "https://…" }`.
887
+ *
888
+ * A scope named here resolves from that registry **and nowhere else**.
889
+ * There is no fallback to the public registry, deliberately: publishing
890
+ * `@company/internal-thing` to npmjs and waiting for a resolver to fall
891
+ * back to it is the dependency-confusion attack, so the fallback is the
892
+ * vulnerability rather than a recovery from it. A name the bound registry
893
+ * does not have is an error that names the scope and the registry.
894
+ *
895
+ * uf refuses an install whose lockfile resolves a bound scope from
896
+ * somewhere else. It does not rewrite the project's `.npmrc`: the manager
897
+ * that resolves is the manager that has to be told, in its own
898
+ * configuration.
899
+ */
900
+ readonly scopes?: { readonly [scope: string]: string },
901
+ /**
902
+ * How hard `uf install` looks at npm provenance attestations.
903
+ *
904
+ * `"report"`, the default, reads the attestation of every package the
905
+ * install brought in or moved: an attestation that is not about the
906
+ * tarball being installed stops the install, and a package with none is a
907
+ * line in the summary. `"off"` reads none, for a machine with no route to
908
+ * a registry.
909
+ */
910
+ readonly provenance?: "report" | "off",
206
911
  },
207
912
  readonly rm?: {
208
913
  readonly module?: "@uniflowed/rm",
@@ -213,6 +918,17 @@ export type UniflowedConfig = {
213
918
  readonly apply?: "config-and-host",
214
919
  readonly doctor?: boolean,
215
920
  },
921
+ /**
922
+ * The runtime every command runs on unless a section names its own, and
923
+ * optionally which release of it: `"node@26"`.
924
+ *
925
+ * `uf start`, `uf run` and `uf exec` read it directly. `uf dev`, `uf build`
926
+ * and `uf preview` read `build.runtime` first; `uf test` reads `test.runtime`
927
+ * and the runtime its runner brings first. Absent, a command starts
928
+ * `app.runtime.capabilityJsHost` from `PATH`, as it always has. See
929
+ * `RuntimeSpec`.
930
+ */
931
+ readonly runtime?: RuntimeSpec,
216
932
  readonly server?: {
217
933
  readonly engine?: "native-rust",
218
934
  readonly native?: {
@@ -223,6 +939,19 @@ export type UniflowedConfig = {
223
939
  >,
224
940
  },
225
941
  },
942
+ // Where the built site is served from, and what `uf build` may therefore
943
+ // write for a crawler. `url` is the switch: without it no `sitemap.xml` and
944
+ // no `robots.txt` are written at all, because a build cannot guess the host
945
+ // it will be deployed to and a wrong `<loc>` is worse than a missing one.
946
+ readonly site?: {
947
+ readonly url?: string,
948
+ readonly sitemap?: boolean,
949
+ readonly robots?: {
950
+ readonly enabled?: boolean,
951
+ readonly allow?: $ReadOnlyArray<string>,
952
+ readonly disallow?: $ReadOnlyArray<string>,
953
+ },
954
+ },
226
955
  readonly std?: {
227
956
  readonly module?: "@uniflowed/std",
228
957
  readonly wintertcAligned?: true,
@@ -311,13 +1040,42 @@ export type UniflowedConfig = {
311
1040
  },
312
1041
  readonly test?: {
313
1042
  readonly module?: "@uniflowed/test",
314
- readonly runner?: {
315
- readonly runtime?: "capability-js-host" | "uf-self-hosted",
316
- readonly jsHosts?: $ReadOnlyArray<CapabilityJsHost>,
317
- readonly scheduler?: "native-work-stealing",
318
- readonly performanceTarget?: "faster-than-bun",
319
- readonly officialFlowParser?: true,
320
- },
1043
+ /**
1044
+ * What `uf test` runs on, when it is neither the runtime the runner brings
1045
+ * nor the top-level `runtime`: `"node@26"`.
1046
+ *
1047
+ * A runner that brings its own — `runner: "bun@1.4"` — decides this when it
1048
+ * is absent, and a `runtime` here naming anything else is an error. See
1049
+ * `RuntimeSpec`.
1050
+ */
1051
+ readonly runtime?: RuntimeSpec,
1052
+ /**
1053
+ * Which application host `uf test` targets.
1054
+ *
1055
+ * `"auto"` follows `app.framework`: React Native projects target
1056
+ * `"react-native"`, and every other project targets `"web"`. Write
1057
+ * `"web"` in a React Native project only for tests that intentionally
1058
+ * target a document.
1059
+ */
1060
+ readonly target?: "auto" | "web" | "react-native",
1061
+ /**
1062
+ * What runs the suite: `"uf"`, the default, or `"bun[@version]"`. See
1063
+ * `TestRunnerSpec`.
1064
+ *
1065
+ * The object is the old description of uf's own runner, field by field,
1066
+ * and is **deprecated**: it still parses, and `applicationTarget` in it is
1067
+ * still read when `target` is absent.
1068
+ */
1069
+ readonly runner?:
1070
+ | TestRunnerSpec
1071
+ | {
1072
+ readonly applicationTarget?: "auto" | "web" | "react-native",
1073
+ readonly runtime?: "vite-task" | "capability-js-host" | "uf-self-hosted",
1074
+ readonly jsHosts?: $ReadOnlyArray<CapabilityJsHost>,
1075
+ readonly scheduler?: "vite-task-cache" | "native-work-stealing",
1076
+ readonly performanceTarget?: "vite-task" | "faster-than-bun",
1077
+ readonly officialFlowParser?: true,
1078
+ },
321
1079
  readonly reactTestingLibraryNative?: true,
322
1080
  /**
323
1081
  * What `uf test --coverage` measures, writes and fails on.
@@ -338,6 +1096,38 @@ export type UniflowedConfig = {
338
1096
  },
339
1097
  },
340
1098
  readonly tasks?: { readonly [string]: TaskDefinition },
1099
+ /**
1100
+ * Tasks `uf prepare` runs before a commit, keyed by a glob over the staged
1101
+ * files.
1102
+ *
1103
+ * A glob with no `/` matches a file's name wherever it is; one with a `/`
1104
+ * matches its path from the project root. Each task named runs once, with
1105
+ * every staged file its glob matches appended to its command, over what is
1106
+ * staged rather than what is on disk. A fix to a fully staged file is staged
1107
+ * with it; a fix to a half-staged one is not kept, and stops the commit.
1108
+ */
1109
+ readonly staged?: { readonly [string]: string | $ReadOnlyArray<string> },
1110
+ /**
1111
+ * Where `uf ui add` writes the components a project owns, and where
1112
+ * `uf ui list` and `uf ui diff` look for them.
1113
+ *
1114
+ * `directory` is relative to the project root, and `app/components/ui` when
1115
+ * absent. Every component goes in the one directory, because each imports
1116
+ * the ones it builds on as siblings (`./button.js`). A path that leaves the
1117
+ * project is refused before anything is written.
1118
+ */
1119
+ readonly ui?: {
1120
+ readonly directory?: string,
1121
+ },
1122
+ /**
1123
+ * Vite's own configuration, merged over the one uf generates.
1124
+ *
1125
+ * uf reads none of it, which is the point: an option Vite adds tomorrow
1126
+ * works in a uf project tomorrow rather than after a uf release that names
1127
+ * it. Deliberately unshaped for the same reason — a Flow type over Vite's
1128
+ * options would be the re-declaration this key exists to avoid.
1129
+ */
1130
+ readonly vite?: { readonly [string]: mixed },
341
1131
  readonly vrt?: {
342
1132
  readonly enabled?: boolean,
343
1133
  readonly module?: "@uniflowed/vrt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniflowed/config",
3
- "version": "0.0.0-alpha.9",
3
+ "version": "0.1.0",
4
4
  "description": "Flow declarations for @uniflowed/config, part of the Unified Toolchain for Flow.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  "index.js",
18
- "internal"
18
+ "internal",
19
+ "!*.test.js"
19
20
  ]
20
21
  }