@intentset/architecture 0.6.1 → 0.7.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.
- package/README.md +48 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/patterns.d.ts +1 -1
- package/dist/patterns.js +1 -1
- package/dist/subject-source.d.ts +42 -0
- package/dist/subject-source.d.ts.map +1 -0
- package/dist/subject-source.js +133 -0
- package/dist/subject-source.js.map +1 -0
- package/package.json +10 -2
- package/src/index.ts +3 -2
- package/src/patterns.ts +1 -1
- package/src/subject-source.ts +159 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @intentset/architecture
|
|
2
|
+
|
|
3
|
+
The Traceable Vertical Slice Architecture checker (`spec/vsa-0.2.md`) with the TypeScript and AWS Amplify Gen 2
|
|
4
|
+
profile's boundaries: path claims and ownership, the import graph read with TypeScript 7's scanner, regions, layers,
|
|
5
|
+
areas, exceptions and a baseline of known violations. It reads files it is given and runs nothing.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @intentset/architecture
|
|
11
|
+
pnpm add @intentset/architecture
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Example
|
|
15
|
+
|
|
16
|
+
Most repositories run it through the CLI, `@intentset/cli`, at L2:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx -p @intentset/cli intentset architecture check
|
|
20
|
+
npx -p @intentset/cli intentset architecture check --mode strict # no baseline: every violation fails
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With pnpm, `pnpm dlx --package=@intentset/cli intentset …`, or `pnpm exec intentset …` once `@intentset/cli` is
|
|
24
|
+
installed. The command is always `@intentset/cli`'s: this package has no `intentset` binary, and npx asked
|
|
25
|
+
for `intentset` alone would fetch an unrelated unscoped package from the registry.
|
|
26
|
+
|
|
27
|
+
From code, after core's validation:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { checkArchitecture } from "@intentset/architecture";
|
|
31
|
+
|
|
32
|
+
// result from validate(), registries from readRegistries(), files: a Map of repository-relative path -> text
|
|
33
|
+
const check = checkArchitecture(result.graph, registries, { files });
|
|
34
|
+
for (const d of check.diagnostics) console.log(d.path, d.code, d.message);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It needs TypeScript 7 as a peer. On an earlier TypeScript, run the CLI without installing it:
|
|
38
|
+
`npx -p @intentset/cli -p typescript@7 intentset architecture check`.
|
|
39
|
+
|
|
40
|
+
## Learn more
|
|
41
|
+
|
|
42
|
+
Part of [Intentset](https://intentset.org/), which keeps product intent connected to the code that delivers it and the
|
|
43
|
+
checks that verify it, as readable Markdown files in a repository. The specifications are at
|
|
44
|
+
https://intentset.org/specifications/ and the source at https://github.com/intentset/intentset.
|
|
45
|
+
|
|
46
|
+
## Licence
|
|
47
|
+
|
|
48
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.
|
|
2
|
+
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
|
|
3
3
|
* the TypeScript + Amplify Gen 2 profile's boundaries
|
|
4
|
-
* (spec/profile-typescript-amplify-gen2-0.
|
|
4
|
+
* (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
|
|
5
5
|
* whole check; the rest is exported for reports, tools and tests.
|
|
6
6
|
*/
|
|
7
7
|
export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.ts";
|
|
@@ -17,6 +17,7 @@ export type { Finding, SubjectEdge } from "./finding.ts";
|
|
|
17
17
|
export { buildImportGraph, type ImportEdge, type ImportGraph, type UnresolvedImport } from "./imports.ts";
|
|
18
18
|
export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.ts";
|
|
19
19
|
export { checkOwnership } from "./ownership.ts";
|
|
20
|
+
export { type ArchitectureSubjectSource, type SubjectSourceOptions, subjectSource } from "./subject-source.ts";
|
|
20
21
|
export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.ts";
|
|
21
22
|
export { activeSlices, buildModel, type Model, type Region, type RegionKind, regionLabel, type SliceInfo, } from "./regions.ts";
|
|
22
23
|
export { type Resolution, Resolver, type Via } from "./resolve.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,EACX,KAAK,IAAI,EACT,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,KAAK,eAAe,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,UAAU,EACf,cAAc,EACd,aAAa,EACb,SAAS,EACT,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,UAAU,EACf,WAAW,EACX,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,EACX,KAAK,IAAI,EACT,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,KAAK,eAAe,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,UAAU,EACf,cAAc,EACd,aAAa,EACb,SAAS,EACT,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,UAAU,EACf,WAAW,EACX,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.
|
|
2
|
+
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
|
|
3
3
|
* the TypeScript + Amplify Gen 2 profile's boundaries
|
|
4
|
-
* (spec/profile-typescript-amplify-gen2-0.
|
|
4
|
+
* (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
|
|
5
5
|
* whole check; the rest is exported for reports, tools and tests.
|
|
6
6
|
*/
|
|
7
7
|
export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.js";
|
|
@@ -16,6 +16,7 @@ export { extractImports, extractSchema, findCalls, SCHEMA_KINDS, } from "./extra
|
|
|
16
16
|
export { buildImportGraph } from "./imports.js";
|
|
17
17
|
export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.js";
|
|
18
18
|
export { checkOwnership } from "./ownership.js";
|
|
19
|
+
export { subjectSource } from "./subject-source.js";
|
|
19
20
|
export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.js";
|
|
20
21
|
export { activeSlices, buildModel, regionLabel, } from "./regions.js";
|
|
21
22
|
export { Resolver } from "./resolve.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EAEb,WAAW,EAEX,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAuB,eAAe,EAAE,iBAAiB,EAAkB,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAIL,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EAGxB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EAEnB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,cAAc,EACd,aAAa,EACb,SAAS,EAET,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAA4D,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EAIV,WAAW,GAEZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAmB,QAAQ,EAAY,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAiB,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EAEb,WAAW,EAEX,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAuB,eAAe,EAAE,iBAAiB,EAAkB,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAIL,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EAGxB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EAEnB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,cAAc,EACd,aAAa,EACb,SAAS,EAET,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAA4D,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAA6D,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EAIV,WAAW,GAEZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAmB,QAAQ,EAAY,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAiB,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/patterns.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* only way to write a star that spans segments is `**` mixed with other
|
|
7
7
|
* characters, which validatePattern rejects.
|
|
8
8
|
*/
|
|
9
|
-
/** Why a pattern is not a v0.
|
|
9
|
+
/** Why a pattern is not a v0.2 pattern, or null when it is one. */
|
|
10
10
|
export declare function validatePattern(pattern: string): string | null;
|
|
11
11
|
/** Does a repository-relative path match a VSA §3 pattern? Invalid patterns match nothing. */
|
|
12
12
|
export declare function matchPattern(pattern: string, path: string): boolean;
|
package/dist/patterns.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* only way to write a star that spans segments is `**` mixed with other
|
|
7
7
|
* characters, which validatePattern rejects.
|
|
8
8
|
*/
|
|
9
|
-
/** Why a pattern is not a v0.
|
|
9
|
+
/** Why a pattern is not a v0.2 pattern, or null when it is one. */
|
|
10
10
|
export function validatePattern(pattern) {
|
|
11
11
|
if (pattern === "")
|
|
12
12
|
return "is empty";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the evidence package's subject hash needs from the architecture
|
|
3
|
+
* (Core §8, ADR 0013): the files each slice's claims resolve to, the files of
|
|
4
|
+
* each registry resource, the local files a test imports (resolved with the
|
|
5
|
+
* same TypeScript-aware resolver as the import graph, and stopping at a
|
|
6
|
+
* package boundary), the global inputs expanded to paths, which resource files a
|
|
7
|
+
* subject hashes (`evidence.resources`, Core §8) and which files can move any
|
|
8
|
+
* subject at all. Pure: it reads the tree it is given and the file
|
|
9
|
+
* contents through the function it is given.
|
|
10
|
+
*/
|
|
11
|
+
import { type EvidenceDependencies, type EvidenceResources, type Graph, type Registries } from "@intentset/core";
|
|
12
|
+
import { type ArchitectureConfig } from "./config.ts";
|
|
13
|
+
export interface SubjectSourceOptions {
|
|
14
|
+
/** Laid over the defaults and `.intentset/architecture.yaml`, as for `checkArchitecture`. */
|
|
15
|
+
config?: Partial<ArchitectureConfig>;
|
|
16
|
+
/** `evidence.globalInputs`: paths, or patterns expanded over the tree. */
|
|
17
|
+
globalInputs: readonly string[];
|
|
18
|
+
/** `evidence.resources`: `exercised` (the default) or `used`; the evidence package's subject reads it from the source this returns. */
|
|
19
|
+
resources?: EvidenceResources;
|
|
20
|
+
/** `evidence.dependencies`: what a dependency slice contributes; the subject reads it from the source this returns. */
|
|
21
|
+
dependencies?: EvidenceDependencies;
|
|
22
|
+
/** The bytes of a repository-relative file, or undefined when it does not exist. */
|
|
23
|
+
readFile: (path: string) => Uint8Array | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Shaped as the evidence package's `SubjectSource`, which this package does not import. */
|
|
26
|
+
export interface ArchitectureSubjectSource {
|
|
27
|
+
readFile: (path: string) => Uint8Array | undefined;
|
|
28
|
+
sliceFiles: Map<string, string[]>;
|
|
29
|
+
resources: Map<string, {
|
|
30
|
+
record: unknown;
|
|
31
|
+
files: string[];
|
|
32
|
+
}>;
|
|
33
|
+
localImports: (path: string) => string[];
|
|
34
|
+
globalInputs: string[];
|
|
35
|
+
resourceMode: EvidenceResources;
|
|
36
|
+
dependencyMode: EvidenceDependencies;
|
|
37
|
+
covers: (path: string) => boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function subjectSource(graph: Graph, registries: Registries, tree: {
|
|
40
|
+
files: ReadonlyMap<string, string>;
|
|
41
|
+
}, options: SubjectSourceOptions): ArchitectureSubjectSource;
|
|
42
|
+
//# sourceMappingURL=subject-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subject-source.d.ts","sourceRoot":"","sources":["../src/subject-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,KAAK,EACV,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAA4B,KAAK,kBAAkB,EAAyC,MAAM,aAAa,CAAC;AAOvH,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,uIAAuI;IACvI,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,uHAAuH;IACvH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oFAAoF;IACpF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;CACpD;AAED,4FAA4F;AAC5F,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACnD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7D,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,iBAAiB,CAAC;IAChC,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAaD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAC5C,OAAO,EAAE,oBAAoB,GAC5B,yBAAyB,CA6F3B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the evidence package's subject hash needs from the architecture
|
|
3
|
+
* (Core §8, ADR 0013): the files each slice's claims resolve to, the files of
|
|
4
|
+
* each registry resource, the local files a test imports (resolved with the
|
|
5
|
+
* same TypeScript-aware resolver as the import graph, and stopping at a
|
|
6
|
+
* package boundary), the global inputs expanded to paths, which resource files a
|
|
7
|
+
* subject hashes (`evidence.resources`, Core §8) and which files can move any
|
|
8
|
+
* subject at all. Pure: it reads the tree it is given and the file
|
|
9
|
+
* contents through the function it is given.
|
|
10
|
+
*/
|
|
11
|
+
import { DEFAULT_EVIDENCE_DEPENDENCIES, } from "@intentset/core";
|
|
12
|
+
import { ARCHITECTURE_CONFIG_PATH, readArchitectureConfig, resolveConfig } from "./config.js";
|
|
13
|
+
import { extractImports } from "./extract.js";
|
|
14
|
+
import { isCode } from "./paths.js";
|
|
15
|
+
import { compareStrings, matchPattern } from "./patterns.js";
|
|
16
|
+
import { buildModel } from "./regions.js";
|
|
17
|
+
import { Resolver } from "./resolve.js";
|
|
18
|
+
/** A global input with no `*` is a path, present or not (a missing one moves the subject when it appears); the rest are patterns. */
|
|
19
|
+
function expandGlobalInputs(inputs, paths) {
|
|
20
|
+
const all = [...paths];
|
|
21
|
+
const out = new Set();
|
|
22
|
+
for (const input of inputs) {
|
|
23
|
+
if (!input.includes("*"))
|
|
24
|
+
out.add(input);
|
|
25
|
+
else
|
|
26
|
+
for (const path of all)
|
|
27
|
+
if (matchPattern(input, path))
|
|
28
|
+
out.add(path);
|
|
29
|
+
}
|
|
30
|
+
return [...out].sort(compareStrings);
|
|
31
|
+
}
|
|
32
|
+
export function subjectSource(graph, registries, tree, options) {
|
|
33
|
+
const configText = tree.files.get(ARCHITECTURE_CONFIG_PATH);
|
|
34
|
+
const fileConfig = configText === undefined ? undefined : readArchitectureConfig(configText, ARCHITECTURE_CONFIG_PATH).config;
|
|
35
|
+
const config = resolveConfig(fileConfig, options.config);
|
|
36
|
+
const model = buildModel(graph, registries, tree.files, config);
|
|
37
|
+
const sliceFiles = new Map();
|
|
38
|
+
for (const slice of model.slices) {
|
|
39
|
+
sliceFiles.set(slice.id, [...new Set(slice.claimFiles.flat())].sort(compareStrings));
|
|
40
|
+
}
|
|
41
|
+
const resources = new Map();
|
|
42
|
+
for (const info of model.resources)
|
|
43
|
+
resources.set(info.resource.id, { record: info.resource, files: [...info.files] });
|
|
44
|
+
let resolver = null;
|
|
45
|
+
const closures = new Map();
|
|
46
|
+
const importsOf = (path) => {
|
|
47
|
+
resolver ??= new Resolver(model.files, config);
|
|
48
|
+
const text = model.files.get(path);
|
|
49
|
+
if (text === undefined || !isCode(path))
|
|
50
|
+
return [];
|
|
51
|
+
const targets = [];
|
|
52
|
+
for (const raw of extractImports(path, text).imports) {
|
|
53
|
+
const resolution = resolver.resolve(path, raw.specifier);
|
|
54
|
+
if (resolution.problem === undefined && resolution.to !== null)
|
|
55
|
+
targets.push(resolution.to);
|
|
56
|
+
}
|
|
57
|
+
return targets;
|
|
58
|
+
};
|
|
59
|
+
const localImports = (start) => {
|
|
60
|
+
const cached = closures.get(start);
|
|
61
|
+
if (cached !== undefined)
|
|
62
|
+
return cached;
|
|
63
|
+
const boundary = model.packageOf(start);
|
|
64
|
+
const seen = new Set([start]);
|
|
65
|
+
const queue = [start];
|
|
66
|
+
while (queue.length > 0) {
|
|
67
|
+
const file = queue.pop();
|
|
68
|
+
for (const target of importsOf(file)) {
|
|
69
|
+
if (seen.has(target) || model.packageOf(target) !== boundary)
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(target);
|
|
72
|
+
queue.push(target);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
seen.delete(start);
|
|
76
|
+
const list = [...seen].sort(compareStrings);
|
|
77
|
+
closures.set(start, list);
|
|
78
|
+
return list;
|
|
79
|
+
};
|
|
80
|
+
const globalInputs = expandGlobalInputs(options.globalInputs, tree.files.keys());
|
|
81
|
+
const resourceMode = options.resources ?? "exercised";
|
|
82
|
+
let covered = null;
|
|
83
|
+
const covers = (path) => {
|
|
84
|
+
if (covered === null) {
|
|
85
|
+
covered = new Set(globalInputs);
|
|
86
|
+
for (const files of sliceFiles.values())
|
|
87
|
+
for (const file of files)
|
|
88
|
+
covered.add(file);
|
|
89
|
+
// The resources some subject hashes: under `used`, every one a slice lists; under `exercised`, the ones a slice
|
|
90
|
+
// that implements a behavior lists and also claims a file of (its own), and the ones a verification exercises.
|
|
91
|
+
const used = new Set();
|
|
92
|
+
for (const slice of model.slices) {
|
|
93
|
+
const implementing = (graph.artifacts.get(slice.id)?.meta.links.implements ?? []).length > 0;
|
|
94
|
+
for (const id of slice.meta.usesResources) {
|
|
95
|
+
if (resourceMode === "used")
|
|
96
|
+
used.add(id);
|
|
97
|
+
else if (implementing) {
|
|
98
|
+
const files = new Set(resources.get(id)?.files ?? []);
|
|
99
|
+
if ((sliceFiles.get(slice.id) ?? []).some((file) => files.has(file)))
|
|
100
|
+
used.add(id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const artifact of graph.artifacts.values()) {
|
|
105
|
+
covered.add(artifact.path);
|
|
106
|
+
const verification = artifact.meta.verification;
|
|
107
|
+
if (artifact.meta.type !== "verification" || verification === undefined)
|
|
108
|
+
continue;
|
|
109
|
+
covered.add(verification.locator);
|
|
110
|
+
for (const file of localImports(verification.locator))
|
|
111
|
+
covered.add(file);
|
|
112
|
+
for (const id of verification.exercises ?? [])
|
|
113
|
+
used.add(id);
|
|
114
|
+
}
|
|
115
|
+
for (const id of used)
|
|
116
|
+
for (const file of resources.get(id)?.files ?? [])
|
|
117
|
+
covered.add(file);
|
|
118
|
+
}
|
|
119
|
+
return covered.has(path);
|
|
120
|
+
};
|
|
121
|
+
const dependencyMode = options.dependencies ?? DEFAULT_EVIDENCE_DEPENDENCIES;
|
|
122
|
+
return {
|
|
123
|
+
readFile: options.readFile,
|
|
124
|
+
sliceFiles,
|
|
125
|
+
resources,
|
|
126
|
+
localImports,
|
|
127
|
+
globalInputs,
|
|
128
|
+
resourceMode,
|
|
129
|
+
dependencyMode,
|
|
130
|
+
covers,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=subject-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subject-source.js","sourceRoot":"","sources":["../src/subject-source.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentset/architecture",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Traceable VSA checks: path claims, import graph, layer matrix, resources, exceptions, baseline.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"intentset",
|
|
7
|
+
"architecture",
|
|
8
|
+
"vertical-slice",
|
|
9
|
+
"import-graph",
|
|
10
|
+
"typescript",
|
|
11
|
+
"amplify"
|
|
12
|
+
],
|
|
5
13
|
"license": "MIT",
|
|
6
14
|
"type": "module",
|
|
7
15
|
"engines": {
|
|
@@ -28,7 +36,7 @@
|
|
|
28
36
|
}
|
|
29
37
|
},
|
|
30
38
|
"dependencies": {
|
|
31
|
-
"@intentset/core": "^0.
|
|
39
|
+
"@intentset/core": "^0.7.1"
|
|
32
40
|
},
|
|
33
41
|
"peerDependencies": {
|
|
34
42
|
"typescript": "^7.0.2"
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.
|
|
2
|
+
* @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
|
|
3
3
|
* the TypeScript + Amplify Gen 2 profile's boundaries
|
|
4
|
-
* (spec/profile-typescript-amplify-gen2-0.
|
|
4
|
+
* (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
|
|
5
5
|
* whole check; the rest is exported for reports, tools and tests.
|
|
6
6
|
*/
|
|
7
7
|
export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.ts";
|
|
@@ -59,6 +59,7 @@ export type { Finding, SubjectEdge } from "./finding.ts";
|
|
|
59
59
|
export { buildImportGraph, type ImportEdge, type ImportGraph, type UnresolvedImport } from "./imports.ts";
|
|
60
60
|
export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.ts";
|
|
61
61
|
export { checkOwnership } from "./ownership.ts";
|
|
62
|
+
export { type ArchitectureSubjectSource, type SubjectSourceOptions, subjectSource } from "./subject-source.ts";
|
|
62
63
|
export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.ts";
|
|
63
64
|
export {
|
|
64
65
|
activeSlices,
|
package/src/patterns.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* characters, which validatePattern rejects.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/** Why a pattern is not a v0.
|
|
10
|
+
/** Why a pattern is not a v0.2 pattern, or null when it is one. */
|
|
11
11
|
export function validatePattern(pattern: string): string | null {
|
|
12
12
|
if (pattern === "") return "is empty";
|
|
13
13
|
if (pattern.startsWith("/")) return "is absolute";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the evidence package's subject hash needs from the architecture
|
|
3
|
+
* (Core §8, ADR 0013): the files each slice's claims resolve to, the files of
|
|
4
|
+
* each registry resource, the local files a test imports (resolved with the
|
|
5
|
+
* same TypeScript-aware resolver as the import graph, and stopping at a
|
|
6
|
+
* package boundary), the global inputs expanded to paths, which resource files a
|
|
7
|
+
* subject hashes (`evidence.resources`, Core §8) and which files can move any
|
|
8
|
+
* subject at all. Pure: it reads the tree it is given and the file
|
|
9
|
+
* contents through the function it is given.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_EVIDENCE_DEPENDENCIES,
|
|
13
|
+
type EvidenceDependencies,
|
|
14
|
+
type EvidenceResources,
|
|
15
|
+
type Graph,
|
|
16
|
+
type Registries,
|
|
17
|
+
} from "@intentset/core";
|
|
18
|
+
import { ARCHITECTURE_CONFIG_PATH, type ArchitectureConfig, readArchitectureConfig, resolveConfig } from "./config.ts";
|
|
19
|
+
import { extractImports } from "./extract.ts";
|
|
20
|
+
import { isCode } from "./paths.ts";
|
|
21
|
+
import { compareStrings, matchPattern } from "./patterns.ts";
|
|
22
|
+
import { buildModel } from "./regions.ts";
|
|
23
|
+
import { Resolver } from "./resolve.ts";
|
|
24
|
+
|
|
25
|
+
export interface SubjectSourceOptions {
|
|
26
|
+
/** Laid over the defaults and `.intentset/architecture.yaml`, as for `checkArchitecture`. */
|
|
27
|
+
config?: Partial<ArchitectureConfig>;
|
|
28
|
+
/** `evidence.globalInputs`: paths, or patterns expanded over the tree. */
|
|
29
|
+
globalInputs: readonly string[];
|
|
30
|
+
/** `evidence.resources`: `exercised` (the default) or `used`; the evidence package's subject reads it from the source this returns. */
|
|
31
|
+
resources?: EvidenceResources;
|
|
32
|
+
/** `evidence.dependencies`: what a dependency slice contributes; the subject reads it from the source this returns. */
|
|
33
|
+
dependencies?: EvidenceDependencies;
|
|
34
|
+
/** The bytes of a repository-relative file, or undefined when it does not exist. */
|
|
35
|
+
readFile: (path: string) => Uint8Array | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Shaped as the evidence package's `SubjectSource`, which this package does not import. */
|
|
39
|
+
export interface ArchitectureSubjectSource {
|
|
40
|
+
readFile: (path: string) => Uint8Array | undefined;
|
|
41
|
+
sliceFiles: Map<string, string[]>;
|
|
42
|
+
resources: Map<string, { record: unknown; files: string[] }>;
|
|
43
|
+
localImports: (path: string) => string[];
|
|
44
|
+
globalInputs: string[];
|
|
45
|
+
resourceMode: EvidenceResources;
|
|
46
|
+
dependencyMode: EvidenceDependencies;
|
|
47
|
+
covers: (path: string) => boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A global input with no `*` is a path, present or not (a missing one moves the subject when it appears); the rest are patterns. */
|
|
51
|
+
function expandGlobalInputs(inputs: readonly string[], paths: Iterable<string>): string[] {
|
|
52
|
+
const all = [...paths];
|
|
53
|
+
const out = new Set<string>();
|
|
54
|
+
for (const input of inputs) {
|
|
55
|
+
if (!input.includes("*")) out.add(input);
|
|
56
|
+
else for (const path of all) if (matchPattern(input, path)) out.add(path);
|
|
57
|
+
}
|
|
58
|
+
return [...out].sort(compareStrings);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function subjectSource(
|
|
62
|
+
graph: Graph,
|
|
63
|
+
registries: Registries,
|
|
64
|
+
tree: { files: ReadonlyMap<string, string> },
|
|
65
|
+
options: SubjectSourceOptions,
|
|
66
|
+
): ArchitectureSubjectSource {
|
|
67
|
+
const configText = tree.files.get(ARCHITECTURE_CONFIG_PATH);
|
|
68
|
+
const fileConfig =
|
|
69
|
+
configText === undefined ? undefined : readArchitectureConfig(configText, ARCHITECTURE_CONFIG_PATH).config;
|
|
70
|
+
const config = resolveConfig(fileConfig, options.config);
|
|
71
|
+
const model = buildModel(graph, registries, tree.files, config);
|
|
72
|
+
|
|
73
|
+
const sliceFiles = new Map<string, string[]>();
|
|
74
|
+
for (const slice of model.slices) {
|
|
75
|
+
sliceFiles.set(slice.id, [...new Set(slice.claimFiles.flat())].sort(compareStrings));
|
|
76
|
+
}
|
|
77
|
+
const resources = new Map<string, { record: unknown; files: string[] }>();
|
|
78
|
+
for (const info of model.resources)
|
|
79
|
+
resources.set(info.resource.id, { record: info.resource, files: [...info.files] });
|
|
80
|
+
|
|
81
|
+
let resolver: Resolver | null = null;
|
|
82
|
+
const closures = new Map<string, string[]>();
|
|
83
|
+
const importsOf = (path: string): string[] => {
|
|
84
|
+
resolver ??= new Resolver(model.files, config);
|
|
85
|
+
const text = model.files.get(path);
|
|
86
|
+
if (text === undefined || !isCode(path)) return [];
|
|
87
|
+
const targets: string[] = [];
|
|
88
|
+
for (const raw of extractImports(path, text).imports) {
|
|
89
|
+
const resolution = resolver.resolve(path, raw.specifier);
|
|
90
|
+
if (resolution.problem === undefined && resolution.to !== null) targets.push(resolution.to);
|
|
91
|
+
}
|
|
92
|
+
return targets;
|
|
93
|
+
};
|
|
94
|
+
const localImports = (start: string): string[] => {
|
|
95
|
+
const cached = closures.get(start);
|
|
96
|
+
if (cached !== undefined) return cached;
|
|
97
|
+
const boundary = model.packageOf(start);
|
|
98
|
+
const seen = new Set<string>([start]);
|
|
99
|
+
const queue = [start];
|
|
100
|
+
while (queue.length > 0) {
|
|
101
|
+
const file = queue.pop() as string;
|
|
102
|
+
for (const target of importsOf(file)) {
|
|
103
|
+
if (seen.has(target) || model.packageOf(target) !== boundary) continue;
|
|
104
|
+
seen.add(target);
|
|
105
|
+
queue.push(target);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
seen.delete(start);
|
|
109
|
+
const list = [...seen].sort(compareStrings);
|
|
110
|
+
closures.set(start, list);
|
|
111
|
+
return list;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const globalInputs = expandGlobalInputs(options.globalInputs, tree.files.keys());
|
|
115
|
+
|
|
116
|
+
const resourceMode = options.resources ?? "exercised";
|
|
117
|
+
let covered: Set<string> | null = null;
|
|
118
|
+
const covers = (path: string): boolean => {
|
|
119
|
+
if (covered === null) {
|
|
120
|
+
covered = new Set<string>(globalInputs);
|
|
121
|
+
for (const files of sliceFiles.values()) for (const file of files) covered.add(file);
|
|
122
|
+
// The resources some subject hashes: under `used`, every one a slice lists; under `exercised`, the ones a slice
|
|
123
|
+
// that implements a behavior lists and also claims a file of (its own), and the ones a verification exercises.
|
|
124
|
+
const used = new Set<string>();
|
|
125
|
+
for (const slice of model.slices) {
|
|
126
|
+
const implementing = (graph.artifacts.get(slice.id)?.meta.links.implements ?? []).length > 0;
|
|
127
|
+
for (const id of slice.meta.usesResources) {
|
|
128
|
+
if (resourceMode === "used") used.add(id);
|
|
129
|
+
else if (implementing) {
|
|
130
|
+
const files = new Set(resources.get(id)?.files ?? []);
|
|
131
|
+
if ((sliceFiles.get(slice.id) ?? []).some((file) => files.has(file))) used.add(id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const artifact of graph.artifacts.values()) {
|
|
136
|
+
covered.add(artifact.path);
|
|
137
|
+
const verification = artifact.meta.verification;
|
|
138
|
+
if (artifact.meta.type !== "verification" || verification === undefined) continue;
|
|
139
|
+
covered.add(verification.locator);
|
|
140
|
+
for (const file of localImports(verification.locator)) covered.add(file);
|
|
141
|
+
for (const id of verification.exercises ?? []) used.add(id);
|
|
142
|
+
}
|
|
143
|
+
for (const id of used) for (const file of resources.get(id)?.files ?? []) covered.add(file);
|
|
144
|
+
}
|
|
145
|
+
return covered.has(path);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const dependencyMode = options.dependencies ?? DEFAULT_EVIDENCE_DEPENDENCIES;
|
|
149
|
+
return {
|
|
150
|
+
readFile: options.readFile,
|
|
151
|
+
sliceFiles,
|
|
152
|
+
resources,
|
|
153
|
+
localImports,
|
|
154
|
+
globalInputs,
|
|
155
|
+
resourceMode,
|
|
156
|
+
dependencyMode,
|
|
157
|
+
covers,
|
|
158
|
+
};
|
|
159
|
+
}
|