@jterrazz/test 18.0.4 → 18.2.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/dist/oxlint.cjs CHANGED
@@ -172,6 +172,7 @@ function testPackageOf(specifier) {
172
172
  const SPECIFICATIONS = /* @__PURE__ */ new Set([
173
173
  "api",
174
174
  "cli",
175
+ "deployment",
175
176
  "integration",
176
177
  "job",
177
178
  "lint",
@@ -212,10 +213,10 @@ var manifest_default = {
212
213
  {
213
214
  "audience": "consumer",
214
215
  "channel": "static",
215
- "sentence": "Seven constructors and only seven: `specification.api()`, `specification.job()`, `specification.cli(bin)`, `specification.integration()`, `specification.website()`, `specification.mobile()`, `specification.lint()`. Any other member (`.app`, `.http`, `.stack`...) is an error. A rendered component starts nothing: it is a chain, not a member of this record.",
216
+ "sentence": "Eight constructors and only eight: `specification.api()`, `specification.job()`, `specification.cli(bin)`, `specification.integration()`, `specification.website()`, `specification.deployment()`, `specification.mobile()`, `specification.lint()`. Any other member (`.app`, `.http`, `.stack`...) is an error. A rendered component starts nothing: it is a chain, not a member of this record.",
216
217
  "specification": "model",
217
218
  "family": "setup",
218
- "fix": "Name one of the seven, or reach for the `component` chain: an eighth subject is a decision before it is a constructor.",
219
+ "fix": "Name one of the eight, or reach for the `component` chain: a ninth subject is a decision before it is a constructor.",
219
220
  "name": "no-unknown-constructor",
220
221
  "rationale": "A closed surface stops parallel constructors being invented and keeps the API memorable.",
221
222
  "reach": "all"
@@ -953,7 +954,7 @@ var manifest_default = {
953
954
  {
954
955
  "audience": "consumer",
955
956
  "channel": "checker",
956
- "sentence": "A first-level folder named for one of the seven specifications holds a `*.specification.ts(x)` calling `specification.<specification>(`. Positive-only: any other first-level name is a repository suite, judged by `specs-at-declared-depth`'s declared depth alone.",
957
+ "sentence": "A first-level folder named for one of the eight specifications holds a `*.specification.ts(x)` calling `specification.<specification>(`. Positive-only: any other first-level name is a repository suite, judged by `specs-at-declared-depth`'s declared depth alone.",
957
958
  "specification": "model",
958
959
  "family": "layout",
959
960
  "fix": "Create the specification the folder promises, or rename the folder.",
@@ -1052,7 +1053,7 @@ var manifest_default = {
1052
1053
  {
1053
1054
  "audience": "consumer",
1054
1055
  "channel": "checker",
1055
- "sentence": "The first line of a depth-1 `.http` follows its grammar: a request line (`METHOD /path`) under `__requests__/`, a status line (`HTTP/1.1 <status>`) under `__expected__/`.",
1056
+ "sentence": "The first line of a depth-1 `.http` follows its grammar: a request line (`METHOD /path`, or `METHOD https://host/path` for a deployment) under `__requests__/`, a status line (`HTTP/1.1 <status>`, or `unreachable: <reason>` for an exchange that never answered) under `__expected__/`.",
1056
1057
  "specification": "api",
1057
1058
  "family": "expectations",
1058
1059
  "fix": "Write the opening line the folder calls for, or move the file to the other one.",
@@ -2251,6 +2252,7 @@ function subjectsOf(source, readRequest, reading = NO_READING) {
2251
2252
  const SETTINGS = {
2252
2253
  api: ["clock", "headers"],
2253
2254
  cli: ["env"],
2255
+ deployment: ["headers"],
2254
2256
  integration: ["clock"],
2255
2257
  job: ["clock"],
2256
2258
  website: ["clock", "headers"]
@@ -5997,6 +5999,7 @@ const noSimulatedDom = (0, _jterrazz_typescript_rules.defineRule)({
5997
5999
  const KNOWN_CONSTRUCTORS = /* @__PURE__ */ new Set([
5998
6000
  "api",
5999
6001
  "cli",
6002
+ "deployment",
6000
6003
  "integration",
6001
6004
  "job",
6002
6005
  "lint",
@@ -6008,7 +6011,7 @@ const noUnknownConstructor = (0, _jterrazz_typescript_rules.defineRule)({
6008
6011
  docs: row$7,
6009
6012
  messages: { unknownConstructor: {
6010
6013
  found: "`specification.{{member}}` is no constructor",
6011
- fix: "call `specification.api()`, `.job()`, `.cli(bin)`, `.integration()`, `.website()`, `.mobile()` or `.lint()`; a rendered unit is the `component` chain"
6014
+ fix: "call `specification.api()`, `.job()`, `.cli(bin)`, `.integration()`, `.website()`, `.deployment()`, `.mobile()` or `.lint()`; a rendered unit is the `component` chain"
6012
6015
  } },
6013
6016
  read: (0, _jterrazz_typescript_rules.readAt)(row$7, CATALOGUE_CHAPTER),
6014
6017
  type: "problem"
package/dist/oxlint.d.cts CHANGED
@@ -12,7 +12,7 @@ type RowDocs<Family extends string = string> = Omit<RuleDocs, 'family' | 'name'>
12
12
  /** One per row: a convention enforced two ways is two rows, so a reader knows which pass a finding comes from. */
13
13
  channel: 'checker' | 'meta' | 'process' | 'runtime' | 'static' | 'toolchain' | 'type' | 'upstream';
14
14
  /** Required: `model` is a statement (this reaches every specification), never a default. */
15
- specification: 'api' | 'cli' | 'component' | 'integration' | 'job' | 'lint' | 'mobile' | 'model' | 'website';
15
+ specification: 'api' | 'cli' | 'component' | 'deployment' | 'integration' | 'job' | 'lint' | 'mobile' | 'model' | 'website';
16
16
  family: Family;
17
17
  rationale: string;
18
18
  /** An `upstream` row names the rule whose option carries it; the completeness meta-test reads it. */
package/dist/oxlint.d.ts CHANGED
@@ -12,7 +12,7 @@ type RowDocs<Family extends string = string> = Omit<RuleDocs, 'family' | 'name'>
12
12
  /** One per row: a convention enforced two ways is two rows, so a reader knows which pass a finding comes from. */
13
13
  channel: 'checker' | 'meta' | 'process' | 'runtime' | 'static' | 'toolchain' | 'type' | 'upstream';
14
14
  /** Required: `model` is a statement (this reaches every specification), never a default. */
15
- specification: 'api' | 'cli' | 'component' | 'integration' | 'job' | 'lint' | 'mobile' | 'model' | 'website';
15
+ specification: 'api' | 'cli' | 'component' | 'deployment' | 'integration' | 'job' | 'lint' | 'mobile' | 'model' | 'website';
16
16
  family: Family;
17
17
  rationale: string;
18
18
  /** An `upstream` row names the rule whose option carries it; the completeness meta-test reads it. */
package/dist/oxlint.js CHANGED
@@ -368,6 +368,7 @@ const noRedundantServiceUrl = defineRule({
368
368
  const SETTINGS = {
369
369
  api: ["clock", "headers"],
370
370
  cli: ["env"],
371
+ deployment: ["headers"],
371
372
  integration: ["clock"],
372
373
  job: ["clock"],
373
374
  website: ["clock", "headers"]
@@ -3738,6 +3739,7 @@ const noSimulatedDom = defineRule({
3738
3739
  const KNOWN_CONSTRUCTORS = /* @__PURE__ */ new Set([
3739
3740
  "api",
3740
3741
  "cli",
3742
+ "deployment",
3741
3743
  "integration",
3742
3744
  "job",
3743
3745
  "lint",
@@ -3749,7 +3751,7 @@ const noUnknownConstructor = defineRule({
3749
3751
  docs: row$7,
3750
3752
  messages: { unknownConstructor: {
3751
3753
  found: "`specification.{{member}}` is no constructor",
3752
- fix: "call `specification.api()`, `.job()`, `.cli(bin)`, `.integration()`, `.website()`, `.mobile()` or `.lint()`; a rendered unit is the `component` chain"
3754
+ fix: "call `specification.api()`, `.job()`, `.cli(bin)`, `.integration()`, `.website()`, `.deployment()`, `.mobile()` or `.lint()`; a rendered unit is the `component` chain"
3753
3755
  } },
3754
3756
  read: readAt(row$7, CATALOGUE_CHAPTER),
3755
3757
  type: "problem"
@@ -33,10 +33,10 @@ var manifest_default = {
33
33
  {
34
34
  "audience": "consumer",
35
35
  "channel": "static",
36
- "sentence": "Seven constructors and only seven: `specification.api()`, `specification.job()`, `specification.cli(bin)`, `specification.integration()`, `specification.website()`, `specification.mobile()`, `specification.lint()`. Any other member (`.app`, `.http`, `.stack`...) is an error. A rendered component starts nothing: it is a chain, not a member of this record.",
36
+ "sentence": "Eight constructors and only eight: `specification.api()`, `specification.job()`, `specification.cli(bin)`, `specification.integration()`, `specification.website()`, `specification.deployment()`, `specification.mobile()`, `specification.lint()`. Any other member (`.app`, `.http`, `.stack`...) is an error. A rendered component starts nothing: it is a chain, not a member of this record.",
37
37
  "specification": "model",
38
38
  "family": "setup",
39
- "fix": "Name one of the seven, or reach for the `component` chain: an eighth subject is a decision before it is a constructor.",
39
+ "fix": "Name one of the eight, or reach for the `component` chain: a ninth subject is a decision before it is a constructor.",
40
40
  "name": "no-unknown-constructor",
41
41
  "rationale": "A closed surface stops parallel constructors being invented and keeps the API memorable.",
42
42
  "reach": "all"
@@ -774,7 +774,7 @@ var manifest_default = {
774
774
  {
775
775
  "audience": "consumer",
776
776
  "channel": "checker",
777
- "sentence": "A first-level folder named for one of the seven specifications holds a `*.specification.ts(x)` calling `specification.<specification>(`. Positive-only: any other first-level name is a repository suite, judged by `specs-at-declared-depth`'s declared depth alone.",
777
+ "sentence": "A first-level folder named for one of the eight specifications holds a `*.specification.ts(x)` calling `specification.<specification>(`. Positive-only: any other first-level name is a repository suite, judged by `specs-at-declared-depth`'s declared depth alone.",
778
778
  "specification": "model",
779
779
  "family": "layout",
780
780
  "fix": "Create the specification the folder promises, or rename the folder.",
@@ -873,7 +873,7 @@ var manifest_default = {
873
873
  {
874
874
  "audience": "consumer",
875
875
  "channel": "checker",
876
- "sentence": "The first line of a depth-1 `.http` follows its grammar: a request line (`METHOD /path`) under `__requests__/`, a status line (`HTTP/1.1 <status>`) under `__expected__/`.",
876
+ "sentence": "The first line of a depth-1 `.http` follows its grammar: a request line (`METHOD /path`, or `METHOD https://host/path` for a deployment) under `__requests__/`, a status line (`HTTP/1.1 <status>`, or `unreachable: <reason>` for an exchange that never answered) under `__expected__/`.",
877
877
  "specification": "api",
878
878
  "family": "expectations",
879
879
  "fix": "Write the opening line the folder calls for, or move the file to the other one.",
@@ -3,6 +3,7 @@ import { basename } from "node:path";
3
3
  const SPECIFICATIONS = /* @__PURE__ */ new Set([
4
4
  "api",
5
5
  "cli",
6
+ "deployment",
6
7
  "integration",
7
8
  "job",
8
9
  "lint",
package/dist/vitest.d.ts CHANGED
@@ -86,6 +86,11 @@ type ComponentProjectOptions = ComponentOptions & SpecificationProjectOptions;
86
86
  /** A component project: renders in a real Chromium; the default `exclude` keeps it out of `specs/`. */
87
87
  export declare function component(options: ComponentProjectOptions): Promise<TestProjectInlineConfiguration>;
88
88
  //#endregion
89
+ //#region src/specifications/deployment/deployment.project.d.ts
90
+ /** `deployment()`: a running deployment, met over the network through complete HTTP exchanges. */
91
+ type DeploymentProjectOptions = SpecificationProjectOptions;
92
+ export declare function deployment(options?: DeploymentProjectOptions): TestProjectInlineConfiguration;
93
+ //#endregion
89
94
  //#region src/specifications/integration/integration.project.d.ts
90
95
  /** `integration()`: a module against real services, or against a golden. */
91
96
  type IntegrationProjectOptions = InProcessProjectOptions;
@@ -98,7 +103,10 @@ export declare function job(options?: JobProjectOptions): TestProjectInlineConfi
98
103
  //#endregion
99
104
  //#region src/specifications/lint/lint.project.d.ts
100
105
  /** `lint()`: the rules a project ships, run by the real linters over fixture trees. */
101
- type LintProjectOptions = SpecificationProjectOptions;
106
+ type LintProjectOptions = SpecificationProjectOptions & {
107
+ /** `false` shares one module graph per worker (`isolate: false`); for specs that keep no state in a module. */
108
+ isolate?: boolean;
109
+ };
102
110
  export declare function lint(options?: LintProjectOptions): TestProjectInlineConfiguration;
103
111
  //#endregion
104
112
  //#region src/specifications/mobile/mobile.project.d.ts
@@ -132,5 +140,5 @@ export declare function unit(options?: UnitProjectOptions): TestProjectInlineCon
132
140
  //#region src/index.vitest.d.ts
133
141
  export declare const vitestConfig: (configUrl: string) => Promise<ViteUserConfig>;
134
142
  //#endregion
135
- export type { ApiProjectOptions, CliProjectOptions, ComponentProjectOptions, InProcessProjectOptions, IntegrationProjectOptions, JobProjectOptions, LintProjectOptions, LiterateDoor, LiterateOptions, LiteratePlugin, MobileProjectOptions, SpecificationProjectOptions, UnitProjectOptions, WebsiteProjectOptions };
143
+ export type { ApiProjectOptions, CliProjectOptions, ComponentProjectOptions, DeploymentProjectOptions, InProcessProjectOptions, IntegrationProjectOptions, JobProjectOptions, LintProjectOptions, LiterateDoor, LiterateOptions, LiteratePlugin, MobileProjectOptions, SpecificationProjectOptions, UnitProjectOptions, WebsiteProjectOptions };
136
144
  //# sourceMappingURL=vitest.d.ts.map
package/dist/vitest.js CHANGED
@@ -366,6 +366,11 @@ async function component(options) {
366
366
  });
367
367
  }
368
368
  //#endregion
369
+ //#region src/specifications/deployment/deployment.project.ts
370
+ function deployment(options = {}) {
371
+ return specificationProject("deployment", options);
372
+ }
373
+ //#endregion
369
374
  //#region src/specifications/integration/integration.project.ts
370
375
  function integration(options = {}) {
371
376
  return mergeConfig(runnerProject("integration", options), { test: { globalSetup: [servicesSetup()] } });
@@ -378,7 +383,14 @@ function job(options = {}) {
378
383
  //#endregion
379
384
  //#region src/specifications/lint/lint.project.ts
380
385
  function lint(options = {}) {
381
- return specificationProject("lint", options);
386
+ const project = specificationProject("lint", options);
387
+ return options.isolate === void 0 ? project : {
388
+ ...project,
389
+ test: {
390
+ ...project.test,
391
+ isolate: options.isolate
392
+ }
393
+ };
382
394
  }
383
395
  //#endregion
384
396
  //#region src/specifications/mobile/mobile.project.ts
@@ -497,6 +509,7 @@ const BUILDERS = /* @__PURE__ */ new Map([
497
509
  ["job", job],
498
510
  ["integration", integration],
499
511
  ["website", website],
512
+ ["deployment", deployment],
500
513
  ["mobile", mobile],
501
514
  ["component", component]
502
515
  ]);
@@ -580,6 +593,6 @@ async function derivedFrom(root) {
580
593
  }
581
594
  const vitestConfig = async (configUrl) => await derivedFrom(dirname(fileURLToPath(configUrl)));
582
595
  //#endregion
583
- export { api, cli, component, integration, job, lint, literate, mobile, unit, vitestConfig, website };
596
+ export { api, cli, component, deployment, integration, job, lint, literate, mobile, unit, vitestConfig, website };
584
597
 
585
598
  //# sourceMappingURL=vitest.js.map
package/docs/README.md CHANGED
@@ -22,6 +22,7 @@ The pages follow the package's four export families (what is tested, what surrou
22
22
  | [Component specs](guides/component-specs.md) | A rendered unit in a real Chromium, in its folder's `__tests__/`: the `component` chain |
23
23
  | [Website specs](guides/website-specs.md) | A served page, fetched or driven in a browser: `specification.website()` |
24
24
  | [Mobile specs](guides/mobile-specs.md) | A native app on an iOS simulator: `specification.mobile()` |
25
+ | [Deployment specs](guides/deployment-specs.md) | A running deployment, one HTTP exchange over the network: `specification.deployment()` |
25
26
  | [API specs](guides/api-specs.md) | An HTTP app, in-process, through complete requests: `specification.api()` |
26
27
  | [Job specs](guides/job-specs.md) | A pipeline triggered by name: `specification.job()` |
27
28
  | [CLI specs](guides/cli-specs.md) | A binary in a fresh working directory: `specification.cli()` |
@@ -8,7 +8,7 @@ The rules themselves (what they say and why) are [Conventions](conventions.md);
8
8
 
9
9
  Every specification (the code behind one kind of test) is the same four steps. A **constructor** takes options and returns a **handle**; the handle opens a **chain** of zero or more setups closed by exactly one terminal action; the action executes and resolves to a **typed result**; the result is asserted through vitest's `expect()`. Nothing else is public: there is no imperative escape hatch, because a spec that can do anything proves nothing in particular.
10
10
 
11
- Seven constructors exist and the list is closed (`src/specifications/specification.ts`):
11
+ Eight constructors exist and the list is closed (`src/specifications/specification.ts`):
12
12
 
13
13
  | Constructor | Handle destructures to | Subject under test |
14
14
  | ----------------------------- | --------------------------- | ---------------------------------------------- |
@@ -17,10 +17,11 @@ Seven constructors exist and the list is closed (`src/specifications/specificati
17
17
  | `specification.cli()` | `{ cli, cleanup, docker }` | A command binary in a fresh temp directory |
18
18
  | `specification.integration()` | `{ integration, cleanup }` | A module against real services, or a golden |
19
19
  | `specification.website()` | `{ website, cleanup, url }` | A rendered page, fetched or driven in chromium |
20
+ | `specification.deployment()` | `{ deployment, cleanup }` | A running deployment, over the network |
20
21
  | `specification.mobile()` | `{ mobile, cleanup, udid }` | A native screen on an iOS simulator |
21
22
  | `specification.lint()` | `{ lint, cleanup }` | The rules a project ships, over a fixture tree |
22
23
 
23
- The asymmetry in that column is the model, not an oversight: `docker` reads a container the subject itself started, and neither a pipeline triggered by name nor a module called in-process is specified by the containers it starts, so `job` and `integration` are handed none; `website` and `mobile` drive a browser and a simulator rather than an orchestrated stack, so they carry neither; and `lint` starts nothing at all, its `cleanup` releasing nothing.
24
+ The asymmetry in that column is the model, not an oversight: `docker` reads a container the subject itself started, and neither a pipeline triggered by name nor a module called in-process is specified by the containers it starts, so `job` and `integration` are handed none; `website` and `mobile` drive a browser and a simulator rather than an orchestrated stack, so they carry neither; and `lint` and `deployment` start nothing at all, their `cleanup` releasing nothing: a deployment is running before the suite and after it, and its exchanges hold a socket for their own length only.
24
25
 
25
26
  **One specification has no constructor**, and the same reason explains it: a rendered component starts nothing. `component` ([Component specs](../guides/component-specs.md)) is a chain the package exports directly (`component.intercept(...).render(<X />)`), so there is no handle to destructure and no `cleanup` to pass to `afterAll`. What every render of a project shares (the providers, the Vite pipeline, the viewport) is the PROJECT's, stated once by `component()` in `vitest.config.ts`.
26
27
 
@@ -28,7 +29,7 @@ A runner is created **once per suite**, in a `*.specification.ts` file, and impo
28
29
 
29
30
  ### The seam under the chain
30
31
 
31
- A specification's chain is COMPOSED: `src/specifications/<specification>/<specification>.composition.ts` lists the setups (each a `*.capability.ts`) and the terminal actions (each a `*.action.ts`) it is made of and the adapter each one is armed through, `defineSpecification` opens the chain, and every action runs through the one pipeline of `src/chain/perform.ts`. Every specification (`job`, `api`, `website`, `integration`, `cli`, `mobile` and `component`) is composed, and there is no other chain. The infrastructure a chain needs is reached through **ports**, each a `*.port.ts` beside the area or the specification that owns it (`database`, `service`, `container`, `isolation` under `src/services/`; `fixture`, `link` under `src/disk/`; `server`, `cli`, `browser`, `device` in their specification), and an adapter implements one. So the chain knows "a database exists"; it never knows Postgres.
32
+ A specification's chain is COMPOSED: `src/specifications/<specification>/<specification>.composition.ts` lists the setups (each a `*.capability.ts`) and the terminal actions (each a `*.action.ts`) it is made of and the adapter each one is armed through, `defineSpecification` opens the chain, and every action runs through the one pipeline of `src/chain/perform.ts`. Every specification (`job`, `api`, `website`, `deployment`, `integration`, `cli`, `mobile`, `lint` and `component`) is composed, and there is no other chain. The infrastructure a chain needs is reached through **ports**, each a `*.port.ts` beside the area or the specification that owns it (`database`, `service`, `container`, `isolation` under `src/services/`; `fixture`, `link` under `src/disk/`; `server`, `cli`, `browser`, `device` in their specification), and an adapter implements one. So the chain knows "a database exists"; it never knows Postgres.
32
33
 
33
34
  Every seam is opened lazily rather than imported, and each one is an optional peer loaded by the one module that owns it, so a project that tests no page, no component and no database installs none of them. Which peer each specification and each service needs, and what its refusal says, is [Set up a project § What to install](../start/set-up-a-project.md#what-to-install)'s list; a service's refusal is built by `src/services/peer.ts`, and a specification's by the adapter that loads its peer.
34
35
 
@@ -48,7 +49,7 @@ The root reads the sentence of a test. A test opens a **specification** on its s
48
49
  | `disk/` | `chain/`, `lint/kit/`, itself | How a test names the disk and what a chain lays on it before its action: `file()` and `directory()` (`spec-path.ts`) and their resolution from the spec file (`resolved-spec-path.ts`), `.fixture()` and `.link()`, their ports and the file-system adapters behind them |
49
50
  | `clock/` | `chain/`, `lint/kit/`, itself, `vitest` | The pinned clock and `.clock()`, the instant grammar, the process clock adapter |
50
51
  | `mocks/` | `chain/`, `lint/kit/`, itself, `vitest`, `vitest-mock-extended` | `mockOf()`, the one double of a port |
51
- | `specifications/` | `chain/`, every notion above, `lint/kit/`, each specification its own library; a specification's project reaches the runner config (`vitest/project`) | What is tested, one slice per constructor: `<specification>.face.ts` (the names it publishes), `<specification>.composition.ts` (the composition), `<specification>.handle.ts` (the constructor), `<specification>.project.ts` (its Vitest project), and its concept folders (`request/`, `server/`, `routes/`, `documents/`, `render/`, `device/` ...); the lint specification holds `rule-tester.ts` beside them, `ruleTester()`, the other half of the kit to test a rule; at its root, `specification.ts`, the seven as one record |
52
+ | `specifications/` | `chain/`, every notion above, `lint/kit/`, each specification its own library; a specification's project reaches the runner config (`vitest/project`) | What is tested, one slice per constructor: `<specification>.face.ts` (the names it publishes), `<specification>.composition.ts` (the composition), `<specification>.handle.ts` (the constructor), `<specification>.project.ts` (its Vitest project), and its concept folders (`request/`, `server/`, `routes/`, `documents/`, `render/`, `device/`, `exchange/` ...); the lint specification holds `rule-tester.ts` beside them, `ruleTester()`, the other half of the kit to test a rule; at its root, `specification.ts`, the eight as one record |
52
53
  | `vitest/` | itself, `chain/`, `lint/kit/`, update mode's setup, `vitest`, never a specification | What every Vitest project receives, knowing no specification: `preset.ts` (the root config), `project.ts` (the defaults every project is built on, `specificationProject`) and `unit.project.ts` (`unit()`), which wire the global setups their owners provide and resolve (`expectations/in-place/`, `chain/paths/temp-root`); a specification's own setups (`services/containers/`, `specifications/mobile/session/`) ride on its project |
53
54
  | `standard/` | `@jterrazz/typescript/config`, nothing of the tree | The defaults of the `test` section, data only: `standard.ts`, which the `test()` preset hands on as written ([`@jterrazz/typescript`'s Configuration § The standard](https://github.com/jterrazz/package-typescript/blob/main/docs/reference/configuration.md#the-standard)) |
54
55
  | `lint/` | itself and `src/standard/`, never the rest of `src/`: `self/` alone reads the product it holds to account | At the root, not under `src/`: the tool-facing channel, laid out below. The layer map names its files `lint/**`, and cruises it beside `src/` |
@@ -101,7 +102,7 @@ The bundle is built by `typescript build`, from the manifest and the names of th
101
102
 
102
103
  **There are SEVEN entries and no more**: `@jterrazz/test` for what writes a spec (the constructors and their faces, `ruleTester()`, the targets, `match` and the tokens, `clock`, `mockOf()`, `packed()`), `@jterrazz/test/services` for the services a specification starts, `@jterrazz/test/stubs` for what the network replies, `@jterrazz/test/vitest` for the project helpers and the `vitestConfig` the derived `vitest.config.ts` awaits, `@jterrazz/test/oxlint` for the lint plugin, `@jterrazz/test/config` for the `test()` preset a `jterrazz.config.ts` lists, `@jterrazz/test/schema` for an editor validating a `<case>.spec.yaml`. That is rule `no-unpublished-subpath` rather than a convention of taste, and the exemption is derived from the manifest's own `exports` map (the kit's `declaredSubpaths`), so a subpath is exempt the moment it is published and stops being exempt the moment it is withdrawn. A service or a stub imported from the root is rule `service-or-stub-from-its-subpath`, whose fix moves the import: the root then loads no service constructor and no stub builder, though it still carries the startup a specification runs over the services it is handed.
103
104
 
104
- **Each spec entry has two runtimes.** A component test renders in a real browser, and the node entry cannot load there: it imports node builtins at load (`node:fs` and `node:child_process` among them), while the native seams (`pg`, `better-sqlite3`, `testcontainers`) are lazy peers, opened on first use. So `exports["."]`, `exports["./services"]` and `exports["./stubs"]` each carry a `browser` condition, resolving to `dist/browser/index.js`, `dist/browser/services.js` and `dist/browser/stubs.js`: the same public surface, built for a page, where every node-only name (the seven constructors, `ruleTester()`, the services, the docker accessors, the results that walk a disk) is a stub that throws where it was called, naming the specification and saying it runs under node. The split is the RUNTIME's and never the specifications': one specifier per entry, so no spec has to remember which build it may import.
105
+ **Each spec entry has two runtimes.** A component test renders in a real browser, and the node entry cannot load there: it imports node builtins at load (`node:fs` and `node:child_process` among them), while the native seams (`pg`, `better-sqlite3`, `testcontainers`) are lazy peers, opened on first use. So `exports["."]`, `exports["./services"]` and `exports["./stubs"]` each carry a `browser` condition, resolving to `dist/browser/index.js`, `dist/browser/services.js` and `dist/browser/stubs.js`: the same public surface, built for a page, where every node-only name (the eight constructors, `ruleTester()`, the services, the docker accessors, the results that walk a disk) is a stub that throws where it was called, naming the specification and saying it runs under node. The split is the RUNTIME's and never the specifications': one specifier per entry, so no spec has to remember which build it may import.
105
106
 
106
107
  `types` is stated once, and it is the node build's, so the two runtimes are ONE type surface. Each page entry takes its types from its node entry (`export type *`), so they cannot drift, and `specs/cli/node/names.spec.ts`, over the packed tarball, records every build's names and fails a value a page build lacks. The check runs that one way: a page build may carry a value its node build does not export, and the names it records show which.
107
108
 
@@ -110,7 +111,7 @@ The API is the types the package publishes, and nothing re-states it. What the c
110
111
  ## Pitfalls
111
112
 
112
113
  - **Reaching for a dependency from `chain/`.** A new external package belongs to the ONE slice that needs it (a notion (`stubs/`, `services/` ...) or a specification) and nowhere else. Only a library every layer truly shares sits behind the chain, in the one folder `FRAMEWORK_LAYERS` grants it to: vitest's test hooks in `paths/`; the `yaml` library and the toolchain's config types sit in `lint/kit/project/`, which the chain reaches. Anything else the chain imports is the boundary this package cannot afford to blur, and the layer map and the library-ownership rules refuse it.
113
- - **Adding an eighth constructor.** The seven are the closed vocabulary the conventions, the linter and the documentation are all shaped around. A new SUBJECT to specify is a design decision, and it earns a record in [`decisions/`](../decisions/) before it earns a constructor, as `integration` did in [ADR-004](../decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-specification.md) and `lint` in [ADR-015](../decisions/015-a-rule-a-project-ships-is-the-lint-specification.md).
114
+ - **Adding a ninth constructor.** The eight are the closed vocabulary the conventions, the linter and the documentation are all shaped around. A new SUBJECT to specify is a design decision, and it earns a record in [`decisions/`](../decisions/) before it earns a constructor, as `integration` did in [ADR-004](../decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-specification.md), `lint` in [ADR-015](../decisions/015-a-rule-a-project-ships-is-the-lint-specification.md) and `deployment` in [ADR-022](../decisions/022-a-running-deployment-is-the-deployment-specification.md).
114
115
  - **Writing a rule's normative sentence into a chapter.** It belongs in `lint/consumer/catalogue/manifest.json` beside the implementation; a chapter that restates it is the second copy the whole design exists to prevent.
115
116
 
116
117
  ## Related
@@ -166,9 +166,9 @@ Two more hold what each path says. **The verb and the folder agree**: `seed()` r
166
166
 
167
167
  | Thing | Rule |
168
168
  | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
- | Specs root | `specs/` (`api/`, `job/`, `cli/`, `integration/`, `website/`, `mobile/`, `lint/`): one folder per constructor, beside the special folders of what the specifications share: `__setup__/`, `__fixtures__/`, `__seeds__/`, `__requests__/`, `__expected__/`, and any repository suite ([the third door](#the-third-door-a-repository-suite)) |
169
+ | Specs root | `specs/` (`api/`, `job/`, `cli/`, `integration/`, `website/`, `deployment/`, `mobile/`, `lint/`): one folder per constructor, beside the special folders of what the specifications share: `__setup__/`, `__fixtures__/`, `__seeds__/`, `__requests__/`, `__expected__/`, and any repository suite ([the third door](#the-third-door-a-repository-suite)) |
170
170
  | Specification | `specs/<specification>/<name>.specification.ts` (at the specification root) |
171
- | Instances | `api`, `job`, `cli`, `integration`, `website`, `mobile`, `lint`, enforced by the destructuring (`no-runner-alias`) |
171
+ | Instances | `api`, `job`, `cli`, `integration`, `website`, `deployment`, `mobile`, `lint`, enforced by the destructuring (`no-runner-alias`) |
172
172
  | Spec file | `specs/<specification>/<subject>/<aspect>.spec.ts`, the assembled product's word (`file-suffix-matches-place`); the subject's folder derived where the specification has one ([One file, one subject](#one-file-one-subject)) |
173
173
  | Spec document | `<case>.spec.yaml`, beside the spec it belongs to, never under `__expected__/` ([Spec documents](../guides/spec-documents.md)) |
174
174
  | Module test | `__tests__/<file>.test.ts`, in the folder of `<file>.ts` (under `src/`) |
@@ -17,7 +17,7 @@ An api spec answers one question: **given this state and this request, what does
17
17
  | What it proves | Application logic against real databases, caches and processes |
18
18
  | Parallel isolation | Per-worker schema / db-index / file copy (rule `state-isolated-per-worker`) |
19
19
 
20
- Because the app shares this process, three things the framework offers are real here and nowhere else: `.intercept()` (msw runs in-process), `.clock()` (it pins the `Date` the app reads), and a golden of the response the app actually built. What the SHIPPED artefact does (its Dockerfile, its wiring, its networking) is a deployment probe, and this specification does not claim it.
20
+ Because the app shares this process, three things the framework offers are real here and nowhere else: `.intercept()` (msw runs in-process), `.clock()` (it pins the `Date` the app reads), and a golden of the response the app actually built. What the SHIPPED artefact does (its Dockerfile, its wiring, its networking) is a deployment probe, and this specification does not claim it: probe the running deployment with [`specification.deployment()`](deployment-specs.md).
21
21
 
22
22
  A `postgres()` service runs `docker/<service>/init.sql` when it starts, under the kebab-case of its record key; `sqlite()` takes its schema from its own `init` or `prismaSchema` option instead ([Services](../reference/services.md#dockerserviceinitsql)).
23
23
 
@@ -32,7 +32,7 @@ The subject is a unit that draws itself IN A BROWSER, which leaves four neighbou
32
32
  | A React island inside an `.astro` page | The island is a rendered unit; only the file around it is not | a `.test.tsx` in the `__tests__/` of the island's folder, here |
33
33
  | A Next.js server component | It renders on the server and reads what the server reads; there is no tree to mount | a website spec, through the served page |
34
34
 
35
- **What runs under node stays there.** In a page, each node-only name the entry publishes (the seven constructors, the services, the docker accessors, the results that walk a disk) refuses where it is called: a page has no filesystem, no child process and no socket.
35
+ **What runs under node stays there.** In a page, each node-only name the entry publishes (the eight constructors, the services, the docker accessors, the results that walk a disk) refuses where it is called: a page has no filesystem, no child process and no socket.
36
36
 
37
37
  ## The constructor: there is none
38
38