@ui-manifest-json/core 0.1.0 → 0.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/full-path.d.ts +24 -0
- package/dist/full-path.d.ts.map +1 -0
- package/dist/full-path.js +38 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/provenance.d.ts +17 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +104 -0
- package/dist/types/manifest.d.ts +16 -1
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +6 -1
- package/dist/types/provenance.d.ts +93 -0
- package/dist/types/provenance.d.ts.map +1 -0
- package/dist/types/provenance.js +14 -0
- package/dist/types/route.d.ts +14 -0
- package/dist/types/route.d.ts.map +1 -1
- package/package.json +18 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BrainRidge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @ui-manifest-json/core
|
|
2
|
+
|
|
3
|
+
The shared `UiManifest` schema — as TypeScript types — plus the framework-agnostic helpers every
|
|
4
|
+
extractor builds on.
|
|
5
|
+
|
|
6
|
+
**You don't usually install this directly.** [`@ui-manifest-json/angular`][ng] and
|
|
7
|
+
[`@ui-manifest-json/react`][react] depend on it. Install it on its own when you're *consuming*
|
|
8
|
+
manifests and want the types, or writing an extractor for another framework.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @ui-manifest-json/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What's in it
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { UiManifest, RouteNode, ComponentNode, DomNode } from '@ui-manifest-json/core';
|
|
18
|
+
|
|
19
|
+
const manifest: UiManifest = JSON.parse(await readFile('ui-manifest.json', 'utf8'));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The full shape is documented field by field in [docs/schema.md][schema]. The types are the source of
|
|
23
|
+
truth; that page is the tour.
|
|
24
|
+
|
|
25
|
+
Three runtime helpers ship alongside the types:
|
|
26
|
+
|
|
27
|
+
| Export | What it does |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `resolveRouteDependencyTree` | Splices each component's template into its parent's where the child's tag appears, recursively, with component-boundary and cycle markers. Framework-agnostic: you supply a `matchFn` that decides which tag maps to which component. |
|
|
30
|
+
| `resolveFullPaths` | Walks a nested route tree and annotates each node with the full path a URL must have to reach it, `baseHref` applied. |
|
|
31
|
+
| `collectRepoProvenance` / `generatorProvenance` | Reads the commit, remote, branch, dirty state and app root out of the git working tree, and the build id out of CI. Best-effort: outside a git tree every field is simply absent, which means the manifest is unpinned rather than that anything failed. |
|
|
32
|
+
|
|
33
|
+
## Schema version
|
|
34
|
+
|
|
35
|
+
`SCHEMA_VERSION` is `"2.0"`. A manifest carries it as `schemaVersion`, and it is the one field to
|
|
36
|
+
check before trusting the rest — v2 made `app.baseHref` and `app.routerMode` required, and without
|
|
37
|
+
those a route path cannot be matched against a real URL at all.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT — see [LICENSE](./LICENSE).
|
|
42
|
+
|
|
43
|
+
[ng]: https://www.npmjs.com/package/@ui-manifest-json/angular
|
|
44
|
+
[react]: https://www.npmjs.com/package/@ui-manifest-json/react
|
|
45
|
+
[schema]: https://github.com/BrainRidge/ui-manifest/blob/main/docs/schema.md
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve every route's `fullPath` from the nested route tree.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic on purpose: Angular's `children`/`loadChildren` and React Router's nested
|
|
5
|
+
* `<Route>`/`children` produce the same `RouteNode` tree, so the join rule is the same and lives
|
|
6
|
+
* once. Both extractors call this after their own parsing.
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteNode } from './types/route.js';
|
|
9
|
+
/**
|
|
10
|
+
* Annotate `routes` (in place, recursively) with `fullPath`.
|
|
11
|
+
*
|
|
12
|
+
* `baseHref` is prepended so the result is what a URL actually looks like, not what the route
|
|
13
|
+
* config says — those differ for every app not served from the root, and the difference is
|
|
14
|
+
* invisible until a consumer tries to match a real URL and matches nothing.
|
|
15
|
+
*
|
|
16
|
+
* A wildcard gets no `fullPath` at all rather than an empty or synthesised one: it is a fallback,
|
|
17
|
+
* not a screen, and a consumer walking `fullPath` should skip it without having to know the
|
|
18
|
+
* convention.
|
|
19
|
+
*
|
|
20
|
+
* Note a wildcard's CHILDREN are still resolved. A `**` with children is unusual but legal, and the
|
|
21
|
+
* children are reachable even though the parent segment is not a location.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveFullPaths(routes: RouteNode[], baseHref?: string): RouteNode[];
|
|
24
|
+
//# sourceMappingURL=full-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"full-path.d.ts","sourceRoot":"","sources":["../src/full-path.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAYlD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,SAAM,GAAG,SAAS,EAAE,CAYjF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Route paths that match anything and therefore identify nothing. */
|
|
2
|
+
const WILDCARDS = new Set(['**', '*']);
|
|
3
|
+
function join(parent, segment) {
|
|
4
|
+
const left = parent.replace(/\/+$/, '');
|
|
5
|
+
const right = segment.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
6
|
+
if (!right)
|
|
7
|
+
return left || '/';
|
|
8
|
+
return `${left}/${right}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Annotate `routes` (in place, recursively) with `fullPath`.
|
|
12
|
+
*
|
|
13
|
+
* `baseHref` is prepended so the result is what a URL actually looks like, not what the route
|
|
14
|
+
* config says — those differ for every app not served from the root, and the difference is
|
|
15
|
+
* invisible until a consumer tries to match a real URL and matches nothing.
|
|
16
|
+
*
|
|
17
|
+
* A wildcard gets no `fullPath` at all rather than an empty or synthesised one: it is a fallback,
|
|
18
|
+
* not a screen, and a consumer walking `fullPath` should skip it without having to know the
|
|
19
|
+
* convention.
|
|
20
|
+
*
|
|
21
|
+
* Note a wildcard's CHILDREN are still resolved. A `**` with children is unusual but legal, and the
|
|
22
|
+
* children are reachable even though the parent segment is not a location.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveFullPaths(routes, baseHref = '/') {
|
|
25
|
+
const base = `/${baseHref.replace(/^\/+/, '').replace(/\/+$/, '')}`.replace(/^\/$/, '');
|
|
26
|
+
const walk = (nodes, parent) => {
|
|
27
|
+
for (const node of nodes) {
|
|
28
|
+
const segment = node.path ?? '';
|
|
29
|
+
const here = WILDCARDS.has(segment.trim()) ? parent : join(parent, segment);
|
|
30
|
+
if (!WILDCARDS.has(segment.trim()))
|
|
31
|
+
node.fullPath = here || '/';
|
|
32
|
+
if (node.children?.length)
|
|
33
|
+
walk(node.children, here);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
walk(routes, base);
|
|
37
|
+
return routes;
|
|
38
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export * from './types/dom.js';
|
|
2
2
|
export * from './types/component.js';
|
|
3
3
|
export * from './types/route.js';
|
|
4
|
+
export * from './types/provenance.js';
|
|
4
5
|
export * from './types/dependency-graph.js';
|
|
5
6
|
export * from './types/manifest.js';
|
|
7
|
+
export { resolveFullPaths } from './full-path.js';
|
|
8
|
+
export { collectRepoProvenance, generatorProvenance } from './provenance.js';
|
|
9
|
+
export type { CollectProvenanceOptions } from './provenance.js';
|
|
6
10
|
export { resolveRouteDependencyTree } from './resolve-tree.js';
|
|
7
11
|
export type { MatchFn } from './resolve-tree.js';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,YAAY,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from './types/dom.js';
|
|
2
2
|
export * from './types/component.js';
|
|
3
3
|
export * from './types/route.js';
|
|
4
|
+
export * from './types/provenance.js';
|
|
4
5
|
export * from './types/dependency-graph.js';
|
|
5
6
|
export * from './types/manifest.js';
|
|
7
|
+
export { resolveFullPaths } from './full-path.js';
|
|
8
|
+
export { collectRepoProvenance, generatorProvenance } from './provenance.js';
|
|
6
9
|
export { resolveRouteDependencyTree } from './resolve-tree.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GeneratorProvenance, RepoProvenance } from './types/provenance.js';
|
|
2
|
+
export interface CollectProvenanceOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Where the extractor is scanning. Becomes `appRoot`, relative to the repository root — and it
|
|
5
|
+
* is also the directory git is asked FROM, which matters more than it looks: `--dir` can point
|
|
6
|
+
* at a checkout that is not the one the command was run in, and asking git about the current
|
|
7
|
+
* directory would then pin the manifest to a completely unrelated repository's HEAD. That is the
|
|
8
|
+
* worst failure available here, because the result looks exactly like a correct pin.
|
|
9
|
+
*/
|
|
10
|
+
targetDir: string;
|
|
11
|
+
/** Only a fallback origin for `appRoot` when the target is not in a git tree at all. */
|
|
12
|
+
cwd: string;
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
}
|
|
15
|
+
export declare function collectRepoProvenance(options: CollectProvenanceOptions): RepoProvenance;
|
|
16
|
+
export declare function generatorProvenance(name: string, version: string, passes: string[], env?: NodeJS.ProcessEnv): GeneratorProvenance;
|
|
17
|
+
//# sourceMappingURL=provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../src/provenance.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAoDjF,MAAM,WAAW,wBAAwB;IACvC;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,cAAc,CAsCvF;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,mBAAmB,CAGrB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect `RepoProvenance` from the git working tree, and `buildId` from CI.
|
|
3
|
+
*
|
|
4
|
+
* Every field is best-effort and every failure is silent-but-absent. This runs against arbitrary
|
|
5
|
+
* checkouts — a tarball with no `.git`, a shallow CI clone, a machine with no `git` on PATH — and
|
|
6
|
+
* none of those is an error: they mean the manifest is unpinned, which is a fact about the output
|
|
7
|
+
* rather than a failure to produce it. The one thing never done here is substituting a plausible
|
|
8
|
+
* value for a missing one: a branch name in place of a commit would look like a pin and move.
|
|
9
|
+
*
|
|
10
|
+
* `git` is invoked directly rather than via a dependency. It is one process per field, already on
|
|
11
|
+
* every machine that has a checkout to read, and the alternative is a dependency in a package whose
|
|
12
|
+
* whole appeal is that it has almost none.
|
|
13
|
+
*/
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { realpathSync } from 'node:fs';
|
|
16
|
+
import { relative } from 'node:path';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve symlinks, or return the path unchanged.
|
|
19
|
+
*
|
|
20
|
+
* `git rev-parse --show-toplevel` reports a REAL path, so on any platform where the checkout sits
|
|
21
|
+
* under a symlink the two disagree and `relative()` produces an escape-hatch path full of `..`.
|
|
22
|
+
* macOS makes this the common case rather than an edge one — `/tmp` and `/var` are both symlinks
|
|
23
|
+
* into `/private` — so a manifest generated in a temp checkout would report an `appRoot` naming
|
|
24
|
+
* the developer's filesystem instead of a subtree of the repo.
|
|
25
|
+
*/
|
|
26
|
+
function realpath(path) {
|
|
27
|
+
try {
|
|
28
|
+
return realpathSync(path);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return path; // the directory may not exist yet; a non-resolvable path is not a failure here
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Run one git command, or return undefined. Never throws, never prints. */
|
|
35
|
+
function git(args, cwd) {
|
|
36
|
+
try {
|
|
37
|
+
const out = execFileSync('git', args, {
|
|
38
|
+
cwd,
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
});
|
|
43
|
+
const value = out.trim();
|
|
44
|
+
return value || undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The build identifier of the CI run, if this is one.
|
|
52
|
+
*
|
|
53
|
+
* Ordered by specificity, not popularity: a run id identifies one execution, where a build number
|
|
54
|
+
* can repeat across re-runs. Absent locally, which is correct — a developer's laptop has no build.
|
|
55
|
+
*/
|
|
56
|
+
function detectBuildId(env) {
|
|
57
|
+
return (env.GITHUB_RUN_ID ??
|
|
58
|
+
env.BUILD_BUILDID ?? // Azure Pipelines
|
|
59
|
+
env.CI_PIPELINE_ID ?? // GitLab
|
|
60
|
+
env.BUILDKITE_BUILD_ID ??
|
|
61
|
+
env.CIRCLE_WORKFLOW_ID ??
|
|
62
|
+
undefined);
|
|
63
|
+
}
|
|
64
|
+
export function collectRepoProvenance(options) {
|
|
65
|
+
const cwd = realpath(options.cwd);
|
|
66
|
+
const targetDir = realpath(options.targetDir);
|
|
67
|
+
// Every git question is asked from the SCANNED tree, not from the process's cwd — see
|
|
68
|
+
// `targetDir` above.
|
|
69
|
+
const root = git(['rev-parse', '--show-toplevel'], targetDir);
|
|
70
|
+
if (!root) {
|
|
71
|
+
// Not a git working tree. `appRoot` is still worth reporting relative to cwd — a consumer
|
|
72
|
+
// knowing which directory was scanned is useful even when it cannot know which commit.
|
|
73
|
+
const appRoot = relative(cwd, targetDir) || '.';
|
|
74
|
+
return { appRoot };
|
|
75
|
+
}
|
|
76
|
+
const commit = git(['rev-parse', 'HEAD'], targetDir);
|
|
77
|
+
// `--porcelain` is empty for a clean tree. Only meaningful once we know we have a commit:
|
|
78
|
+
// "dirty" without one says nothing a consumer can use.
|
|
79
|
+
const status = commit ? git(['status', '--porcelain'], targetDir) : undefined;
|
|
80
|
+
const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], targetDir);
|
|
81
|
+
const provenance = {
|
|
82
|
+
remoteUrl: git(['remote', 'get-url', 'origin'], targetDir),
|
|
83
|
+
commit,
|
|
84
|
+
// Committer date, ISO 8601 strict. Not the author date: two commits can share an author date
|
|
85
|
+
// after a rebase, and what orders two manifests is when the code landed.
|
|
86
|
+
commitTime: commit ? git(['show', '-s', '--format=%cI', 'HEAD'], targetDir) : undefined,
|
|
87
|
+
// A detached HEAD reports "HEAD", which is not a branch name and should not be recorded as one.
|
|
88
|
+
branch: branch && branch !== 'HEAD' ? branch : undefined,
|
|
89
|
+
dirty: commit ? Boolean(status) : undefined,
|
|
90
|
+
appRoot: relative(root, targetDir) || '.',
|
|
91
|
+
};
|
|
92
|
+
// Absent, not null/empty: a consumer testing `if (provenance.commit)` should not have to also
|
|
93
|
+
// test for the empty string, and JSON with explicit nulls everywhere reads as though something
|
|
94
|
+
// failed rather than as though it was never available.
|
|
95
|
+
for (const key of Object.keys(provenance)) {
|
|
96
|
+
if (provenance[key] === undefined)
|
|
97
|
+
delete provenance[key];
|
|
98
|
+
}
|
|
99
|
+
return provenance;
|
|
100
|
+
}
|
|
101
|
+
export function generatorProvenance(name, version, passes, env = process.env) {
|
|
102
|
+
const buildId = detectBuildId(env);
|
|
103
|
+
return buildId ? { name, version, buildId, passes } : { name, version, passes };
|
|
104
|
+
}
|
package/dist/types/manifest.d.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import type { ComponentNode } from './component.js';
|
|
2
2
|
import type { RouteNode } from './route.js';
|
|
3
3
|
import type { RouteDependencyTree } from './dependency-graph.js';
|
|
4
|
-
|
|
4
|
+
import type { AppIdentity, Coverage, CoverageScope, Provenance } from './provenance.js';
|
|
5
|
+
/**
|
|
6
|
+
* Bumped to "2.0" for the `app` block, whose two fields are REQUIRED — see `AppIdentity`. A
|
|
7
|
+
* consumer needs exactly one field to test to know whether the routes it is about to read can be
|
|
8
|
+
* matched against real URLs at all, and that field is this one.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SCHEMA_VERSION = "2.0";
|
|
5
11
|
export type Framework = 'angular' | 'react';
|
|
6
12
|
export interface UiManifest {
|
|
7
13
|
schemaVersion: typeof SCHEMA_VERSION;
|
|
8
14
|
framework: Framework;
|
|
15
|
+
/** Where the app is served from. Required: without it every route in this file is unmatchable
|
|
16
|
+
* against a real URL, silently. See {@link AppIdentity}. */
|
|
17
|
+
app: AppIdentity;
|
|
18
|
+
/** Which commit, which extractor, which passes. Non-diffable — ignore it when diffing, the same
|
|
19
|
+
* as `generatedAt`. */
|
|
20
|
+
provenance: Provenance;
|
|
21
|
+
/** Whether a missing route means "deleted" or "not looked at". See {@link Coverage}. */
|
|
22
|
+
coverage: Coverage;
|
|
23
|
+
coverageScope?: CoverageScope;
|
|
9
24
|
/** ISO timestamp of generation. Not diff-relevant on its own — consumers diffing two
|
|
10
25
|
* manifests should ignore this field, since it changes on every run even with no UI change. */
|
|
11
26
|
generatedAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/types/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/types/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5C,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,cAAc,CAAC;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB;iEAC6D;IAC7D,GAAG,EAAE,WAAW,CAAC;IACjB;4BACwB;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,wFAAwF;IACxF,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;oGACgG;IAChG,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,wFAAwF;IACxF,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACxC;mGAC+F;IAC/F,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB"}
|
package/dist/types/manifest.js
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Bumped to "2.0" for the `app` block, whose two fields are REQUIRED — see `AppIdentity`. A
|
|
3
|
+
* consumer needs exactly one field to test to know whether the routes it is about to read can be
|
|
4
|
+
* matched against real URLs at all, and that field is this one.
|
|
5
|
+
*/
|
|
6
|
+
export const SCHEMA_VERSION = '2.0';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a manifest came from: which app, which commit, which extractor run.
|
|
3
|
+
*
|
|
4
|
+
* v1 could answer "what does this UI contain" but not "which build is this" — so two manifests
|
|
5
|
+
* could only ever be compared against each other, never against a deployed thing. Everything here
|
|
6
|
+
* exists to make a manifest self-locating.
|
|
7
|
+
*
|
|
8
|
+
* **This whole block is non-diffable**, in the same sense `generatedAt` already is: `commit` and
|
|
9
|
+
* `buildId` change on every run even when the UI does not. `jq 'del(.generatedAt, .provenance)'`
|
|
10
|
+
* restores a document whose every remaining field changes only when the UI's structure does. That
|
|
11
|
+
* is why it is a segregated top-level block rather than fields sprinkled through `routes` and
|
|
12
|
+
* `components`.
|
|
13
|
+
*/
|
|
14
|
+
/** How the app's router turns a route path into a URL. */
|
|
15
|
+
export type RouterMode = 'path' | 'hash';
|
|
16
|
+
/**
|
|
17
|
+
* Where the app is served from, which is what decides whether a route path matches a real URL.
|
|
18
|
+
*
|
|
19
|
+
* Both fields are REQUIRED, and that is deliberate. Omitting either does not produce an error in
|
|
20
|
+
* any consumer — it produces a silent, total miss. An app served under `/portal/` renders
|
|
21
|
+
* `/portal/dashboard`; a manifest that says `dashboard` matches nothing, on every route, and looks
|
|
22
|
+
* exactly like a manifest for an app that simply has little in it. `useHash: true` fails the same
|
|
23
|
+
* way. A field whose absence is indistinguishable from a wrong answer has to be required.
|
|
24
|
+
*/
|
|
25
|
+
export interface AppIdentity {
|
|
26
|
+
/**
|
|
27
|
+
* The app's base path, from Angular's `<base href>` / `APP_BASE_HREF`, or React Router's
|
|
28
|
+
* `basename`. `"/"` when the app is served from the root — which is the common case, and is a
|
|
29
|
+
* real answer rather than a default standing in for "unknown".
|
|
30
|
+
*/
|
|
31
|
+
baseHref: string;
|
|
32
|
+
/** `"hash"` for `useHash: true` / `HashRouter`, which puts the entire route after a `#`. */
|
|
33
|
+
routerMode: RouterMode;
|
|
34
|
+
/** How `baseHref`/`routerMode` were established. `"detected"` means read out of the source;
|
|
35
|
+
* `"configured"` means the caller supplied them; `"default"` means neither, and the values are
|
|
36
|
+
* the conventional ones — which a consumer should treat as a weaker claim. */
|
|
37
|
+
confidence: 'detected' | 'configured' | 'default';
|
|
38
|
+
}
|
|
39
|
+
/** The commit the source was in when the manifest was generated. */
|
|
40
|
+
export interface RepoProvenance {
|
|
41
|
+
/** `origin`'s URL, as git reports it. Absent outside a git working tree. */
|
|
42
|
+
remoteUrl?: string;
|
|
43
|
+
/** Full commit sha. Absent outside a git working tree, and deliberately NOT defaulted to a
|
|
44
|
+
* branch name: a branch moves, so a manifest pinned to one is not pinned at all. Its absence
|
|
45
|
+
* marks the output as unpinned, which is information a consumer can act on. */
|
|
46
|
+
commit?: string;
|
|
47
|
+
/** Committer timestamp, ISO 8601. From git, never from a clock: it is what orders two manifests
|
|
48
|
+
* that arrive out of sequence, and a generation timestamp would order them by when they were
|
|
49
|
+
* uploaded rather than by which describes newer code. */
|
|
50
|
+
commitTime?: string;
|
|
51
|
+
branch?: string;
|
|
52
|
+
/** True when the working tree had uncommitted changes — so `commit` describes *most* of what was
|
|
53
|
+
* extracted, not all of it. Silently reporting a commit for a dirty tree is how a manifest comes
|
|
54
|
+
* to describe code that was never committed anywhere. */
|
|
55
|
+
dirty?: boolean;
|
|
56
|
+
/** The scanned directory, relative to the repository root. Every `filePath` in the manifest is
|
|
57
|
+
* relative to this, so a consumer can reconstruct a repo-relative path without guessing. */
|
|
58
|
+
appRoot?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Which extractor produced this, and what it actually ran. */
|
|
61
|
+
export interface GeneratorProvenance {
|
|
62
|
+
name: string;
|
|
63
|
+
version: string;
|
|
64
|
+
/** The CI run that produced it, when one did (`GITHUB_RUN_ID` and friends). */
|
|
65
|
+
buildId?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Analysis passes that ran, e.g. `["routes", "components", "dom", "dependency-graph"]`.
|
|
68
|
+
*
|
|
69
|
+
* The point is negative information: a consumer must be able to tell "this component has no DOM
|
|
70
|
+
* tree" from "the DOM pass never ran". Without it, every optional field is ambiguous between
|
|
71
|
+
* "absent" and "not looked for", and a consumer either re-runs unnecessarily or trusts a gap.
|
|
72
|
+
*/
|
|
73
|
+
passes: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface Provenance {
|
|
76
|
+
repo: RepoProvenance;
|
|
77
|
+
generator: GeneratorProvenance;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether this manifest describes the whole app or a named part of it.
|
|
81
|
+
*
|
|
82
|
+
* Load-bearing for anything that merges manifests over time. Given only a manifest that lacks a
|
|
83
|
+
* route, a consumer cannot tell whether the route was DELETED or merely not covered by this run —
|
|
84
|
+
* and guessing "not covered" means a deleted route lives forever, while guessing "deleted" throws
|
|
85
|
+
* away a real one. `"full"` is a claim that absence means deletion; `"partial"` is a claim that it
|
|
86
|
+
* does not, and `coverageScope` says what was actually looked at.
|
|
87
|
+
*/
|
|
88
|
+
export type Coverage = 'full' | 'partial';
|
|
89
|
+
export interface CoverageScope {
|
|
90
|
+
routes?: string[];
|
|
91
|
+
paths?: string[];
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/types/provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,UAAU,EAAE,UAAU,CAAC;IACvB;;mFAE+E;IAC/E,UAAU,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;CACnD;AAED,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;oFAEgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;8DAE0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;8DAE0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;iGAC6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+DAA+D;AAC/D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a manifest came from: which app, which commit, which extractor run.
|
|
3
|
+
*
|
|
4
|
+
* v1 could answer "what does this UI contain" but not "which build is this" — so two manifests
|
|
5
|
+
* could only ever be compared against each other, never against a deployed thing. Everything here
|
|
6
|
+
* exists to make a manifest self-locating.
|
|
7
|
+
*
|
|
8
|
+
* **This whole block is non-diffable**, in the same sense `generatedAt` already is: `commit` and
|
|
9
|
+
* `buildId` change on every run even when the UI does not. `jq 'del(.generatedAt, .provenance)'`
|
|
10
|
+
* restores a document whose every remaining field changes only when the UI's structure does. That
|
|
11
|
+
* is why it is a segregated top-level block rather than fields sprinkled through `routes` and
|
|
12
|
+
* `components`.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
package/dist/types/route.d.ts
CHANGED
|
@@ -4,7 +4,21 @@ export interface RouteGuards {
|
|
|
4
4
|
}
|
|
5
5
|
export type ReactRoutingPattern = 'jsx-routes' | 'router-config' | 'file-based';
|
|
6
6
|
export interface RouteNode {
|
|
7
|
+
/** This route's own segment, exactly as written in the source. */
|
|
7
8
|
path: string;
|
|
9
|
+
/**
|
|
10
|
+
* The full path a URL must have to reach this route: every ancestor's `path` joined with this
|
|
11
|
+
* one, `baseHref` applied, and leading/trailing slashes normalised.
|
|
12
|
+
*
|
|
13
|
+
* Derivable by a consumer from the tree — but doing it here means one implementation instead of
|
|
14
|
+
* one per consumer, and it removes a specific way to get it wrong: two `''` children under
|
|
15
|
+
* different parents are the same `path` and different `fullPath`s, so a consumer keying on
|
|
16
|
+
* `path` silently collides them.
|
|
17
|
+
*
|
|
18
|
+
* Absent for a route that cannot BE a URL — a bare `**` wildcard matches every path and so
|
|
19
|
+
* identifies none, and giving it a `fullPath` would invite a consumer to treat it as a screen.
|
|
20
|
+
*/
|
|
21
|
+
fullPath?: string;
|
|
8
22
|
/** The resolved lazy-loaded target, e.g. Angular's loadComponent or a React <Route element>. */
|
|
9
23
|
component?: {
|
|
10
24
|
module: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/types/route.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,eAAe,GAAG,YAAY,CAAC;AAEhF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,qFAAqF;IACrF,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC"}
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/types/route.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,eAAe,GAAG,YAAY,CAAC;AAEhF,MAAM,WAAW,SAAS;IACxB,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,qFAAqF;IACrF,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ui-manifest-json/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared JSON schema types and
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared JSON schema types and framework-agnostic helpers for ui-manifest extractors.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ui-manifest",
|
|
7
|
+
"schema",
|
|
8
|
+
"types",
|
|
9
|
+
"ast",
|
|
10
|
+
"static-analysis"
|
|
11
|
+
],
|
|
5
12
|
"type": "module",
|
|
6
13
|
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/BrainRidge/ui-manifest.git",
|
|
17
|
+
"directory": "packages/core"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/BrainRidge/ui-manifest/tree/main/packages/core#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/BrainRidge/ui-manifest/issues"
|
|
22
|
+
},
|
|
7
23
|
"engines": {
|
|
8
24
|
"node": ">=20.6.0"
|
|
9
25
|
},
|