Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
9794b31
chore(builder) refacor subschemas to yield also the key
jkowalleck Oct 15, 2026
73217b8
chore(builder): tree shake
jkowalleck Oct 15, 2026
fc25b89
chore(builder): tree shake
jkowalleck Oct 15, 2026
9b4918e
docs
jkowalleck Oct 15, 2026
65c5b30
wip
jkowalleck Oct 15, 2026
282e80f
wip
jkowalleck Oct 15, 2026
e0f4a79
wip
jkowalleck Oct 15, 2026
1c7e670
wip
jkowalleck Oct 15, 2026
c663900
wip
jkowalleck Oct 19, 2026
813601b
wip
jkowalleck Oct 19, 2026
8a4b27a
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 19, 2026
f18e8ee
wip
jkowalleck Oct 19, 2026
2045af8
wip
jkowalleck Oct 20, 2026
ac17d29
wip
jkowalleck Oct 20, 2026
95ecf7e
wip
jkowalleck Oct 20, 2026
5ce1691
wip
jkowalleck Oct 20, 2026
c52a3cf
docs
jkowalleck Oct 20, 2026
504cb42
docs
jkowalleck Oct 20, 2026
eb29b49
docs
jkowalleck Oct 20, 2026
c6a0f04
docs
jkowalleck Oct 20, 2026
fb2dc02
fix
jkowalleck Oct 20, 2026
1f72695
tests
jkowalleck Oct 20, 2026
f6f97b9
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 20, 2026
3924e37
wip
jkowalleck Oct 20, 2026
de61424
wip
jkowalleck Oct 20, 2026
482e0cc
wip
jkowalleck Oct 20, 2026
3079062
wip
jkowalleck Oct 20, 2026
6494ece
wip
jkowalleck Oct 20, 2026
6e2d9bb
wip
jkowalleck Oct 20, 2026
b35bd3d
wip
jkowalleck Oct 20, 2026
6406ac3
wip
jkowalleck Oct 20, 2026
0c640ff
wip
jkowalleck Oct 20, 2026
aa4d8ed
wip
jkowalleck Oct 20, 2026
044773e
wip
jkowalleck Oct 20, 2026
503f735
wip
jkowalleck Oct 20, 2026
3a3c670
wip
jkowalleck Oct 21, 2026
d48bb4a
wip
jkowalleck Oct 21, 2026
35c8431
wip
jkowalleck Oct 21, 2026
6499eb0
wip
jkowalleck Oct 21, 2026
c5051c3
wip
jkowalleck Oct 21, 2026
f7d7710
wip
jkowalleck Oct 21, 2026
4df9c0f
wip
jkowalleck Oct 21, 2026
c77a3dd
tests
jkowalleck Oct 21, 2026
d944905
wip
jkowalleck Oct 21, 2026
dacf9aa
wip
jkowalleck Oct 21, 2026
2c7315a
wip
jkowalleck Oct 22, 2026
faf03e8
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 22, 2026
8d35450
wip
jkowalleck Oct 22, 2026
df7b277
wip
jkowalleck Oct 22, 2026
a5e402c
wip
jkowalleck Oct 22, 2026
86f1df7
wip
jkowalleck Oct 22, 2026
9d35138
wip
jkowalleck Oct 22, 2026
e84db59
wip
jkowalleck Oct 23, 2026
c190ab0
wip
jkowalleck Oct 23, 2026
45ffbd0
wip
jkowalleck Oct 23, 2026
7d4c208
wip
jkowalleck Oct 23, 2026
762b630
wip
jkowalleck Oct 23, 2026
0192d4a
chore(bundler): fix some test fixtures
jkowalleck Oct 23, 2026
ddddc66
chore(bundler): refactor
jkowalleck Oct 23, 2026
1a6c391
chore(bundler): fix
jkowalleck Oct 23, 2026
33c3eb7
tidy
jkowalleck Oct 23, 2026
209b368
tidy
jkowalleck Oct 23, 2026
fa5a856
tidy
jkowalleck Oct 23, 2026
35d7901
Merge branch '2.0-dev-bundler-tests-fixes' into chore/bundler-rewrite…
jkowalleck Oct 23, 2026
0b072af
wip
jkowalleck Oct 23, 2026
aa3cdf3
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 23, 2026
2e049c3
wip
jkowalleck Oct 23, 2026
848a9e5
wip
jkowalleck Oct 23, 2026
848694e
wip
jkowalleck Oct 23, 2026
16a6748
wip
jkowalleck Oct 23, 2026
f93f710
wip
jkowalleck Oct 23, 2026
0bcc04b
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 23, 2026
c1a67a2
wip
jkowalleck Oct 23, 2026
a99ad09
tidy
jkowalleck Oct 23, 2026
7db75fe
wip
jkowalleck Oct 23, 2026
4777042
wip
jkowalleck Oct 24, 2026
7bdf56d
wip
jkowalleck Oct 24, 2026
d4172c0
wip
jkowalleck Oct 24, 2026
3257c35
wip
jkowalleck Oct 24, 2026
776076b
wip
jkowalleck Oct 24, 2026
2551ec8
wip
jkowalleck Oct 24, 2026
c6cbbec
wip
jkowalleck Oct 24, 2026
a2449f2
wip
jkowalleck Oct 24, 2026
cda2bec
wip
jkowalleck Oct 24, 2026
b989d3b
wip
jkowalleck Oct 24, 2026
8ccffb9
wip
jkowalleck Oct 24, 2026
793d271
wip
jkowalleck Oct 24, 2026
30e190f
tests
jkowalleck Oct 26, 2026
c4aa30e
wip
jkowalleck Oct 26, 2026
ad6f3c1
wip
jkowalleck Oct 26, 2026
af7e5bf
wip
jkowalleck Oct 26, 2026
bf6e04e
wip
jkowalleck Oct 26, 2026
c15ce48
wip
jkowalleck Oct 26, 2026
b4169c9
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 26, 2026
8af0d23
wip
jkowalleck Oct 26, 2026
f1703df
cleanup
jkowalleck Oct 26, 2026
61872f4
cleanup
jkowalleck Oct 26, 2026
f3bfb33
Merge remote-tracking branch 'origin/2.0-dev' into chore/bundler-rewr…
jkowalleck Oct 26, 2026
fcec408
wip
jkowalleck Oct 26, 2026
4afba6b
wip
jkowalleck Oct 26, 2026
694bea6
wip
jkowalleck Oct 26, 2026
855079c
docs
jkowalleck Oct 26, 2026
6dd0754
docs
jkowalleck Oct 26, 2026
77d8020
docs
jkowalleck Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion schema/2.0/cyclonedx-2.0-bundled.min.schema.json

Large diffs are not rendered by default.

169 changes: 102 additions & 67 deletions schema/2.0/cyclonedx-2.0-bundled.schema.json

Large diffs are not rendered by default.

60 changes: 55 additions & 5 deletions tools/src/main/js/bundler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ This writes, next to the root schema:

Exit codes:
* `1` — invalid arguments
* `2` — bundling/validation error
* `2` — bundling/tree-shaking/validation error

This is what the `bundle_2.0_schemas` GitHub workflow runs on every change to
the 2.0 schema sources.
Expand All @@ -58,13 +58,44 @@ The bundler is split into stages (one module each under `src/`):
they describe the module *file*, not the schema, and would be misleading inside the bundle.
* The entry schema's own `$defs` are moved into a *hollow* container `$defs/<entry-name>`
(marked with `"not": {"$comment": …}` so it is never used for validation by accident).
* `$ref`, `$dynamicRef` and `$recursiveRef` are rewired:
* `$ref`, `$dynamicRef` and `$recursiveRef` are rewired
(all treated alike as static pointers, see [Limitations](#limitations)):
* refs to a bundled file → `#/$defs/<module-name>[/<pointer>]`
* refs to a non-bundled file → path made relative to the output file, kept external
* absolute URIs → left untouched, reported as external
* The root `$id` is rewritten to point at the bundled file name.
2. **Tree-shake** (`src/cleanup.js`) — remove definitions that are not reachable from the root.
*Not implemented yet; currently a no-op.*
2. **Tree-shake** (`src/cleanup.js`) — a mark-and-sweep pass over the bundled schema.
* *Mark*: starting at the root schema, walk all subschemas (`properties`, `items`, `allOf`, …)
and follow every local `$ref`/`$dynamicRef`.
`$dynamicRef` is treated exactly like `$ref` — resolved statically as a JSON Pointer,
without dynamic-scope semantics. `$defs` are *not* descended into —
definitions are only reached via refs. Non-standard keywords are not descended into either.
Refs may point at any JSON Pointer, not just `$defs`
entries, e.g. `#/$defs/<module>/properties/address/properties/street`
or `#/$defs/<module>/properties/combined/allOf/1`.
* *Sweep*: everything that was not marked is dropped. Three things can happen:
* **removed** — unreached object members are deleted:
* unreached `$defs` entries — also in schemas that are reached as a whole;
* unreached parts (`properties`, `type`, `examples`, …) of schemas that are not reached
themselves but contain reached parts;
* unreached *trailing* array items (`allOf`, `oneOf`, …).
* **voided** — unreached array items that are followed by a reached item are *not* removed,
so that the indices of the reached items stay stable. They are replaced by a placeholder:
`{"not": {"$comment": …}}` for schemas, an empty value (`{}`, `[]`, `""`, `0`) for data;
booleans and `null` are kept as they are.
* **hollowed** — schemas that are not reached but contain reached parts:
only `title`, `description`, `$comment` and the keys leading to the reached parts are kept,
and `"not": {"$comment": …}` is added so the hollow schema never validates anything.
Typical examples: an embedded module of which only some `$defs` are referenced,
or a `properties` container of which only a nested property is referenced.
Reached schemas themselves are left untouched — only their `$defs` are swept.
Non-standard keywords of reached schemas (e.g. custom definition containers) are kept as a whole.
* Schemas carrying `$anchor`/`$dynamicAnchor` are kept untouched, whether reached or not:
their `$defs` are not swept, and refs inside them are not followed.
* The tree-shaker aborts with an error if any local ref in the schema — reached or not — uses
an anchor fragment instead of a JSON Pointer.
It also aborts if a *reached* local ref's target does not exist, or is neither a schema nor a boolean.
Unresolvable refs in unreached parts are dropped silently along with those parts.
3. **Sanity check** (`src/sanity.js`) — validates the bundled result:
* every local JSON-Pointer ref must resolve in the bundle.
Unresolvable refs are errors; external refs and anchor-based refs are reported as warnings only.
Expand All @@ -73,12 +104,31 @@ The bundler is split into stages (one module each under `src/`):
4. **Write** (`cli.js`) — emits the pretty bundle, then strips `$comment` from all subschemas
(property *names* called `$comment` are preserved) and emits the minified bundle.

A report of what was embedded, rewired and treated as external is printed to the console.
A report of what was embedded, rewired and treated as external, and of what the tree-shaker
hollowed, voided and removed, is printed to the console.

## Limitations

* Refs via `$anchor` / `$dynamicAnchor` / `$recursiveAnchor` into bundled files are **not** supported
and cause an error; only JSON-Pointer fragments are rewired.
* `$dynamicRef` is **not** supported with its dynamic-scope semantics.
The bundler and the tree-shaker treat it as a plain `$ref`: the fragment must be a JSON Pointer and
is resolved statically against the bundle. A `$dynamicRef` with an anchor fragment
(e.g. `"$dynamicRef": "#node"`) aborts the tree-shaker with an `Unsupported ref fragment` error.
Since a statically resolved `$dynamicRef` may keep a different target than a validator would pick
at runtime, avoid `$dynamicRef`/`$dynamicAnchor` in the modular schemas altogether.
* Tree-shaking follows JSON-Pointer refs only; a local ref with an anchor fragment aborts with an
`Unsupported ref fragment` error before the sanity check runs.
Schemas that declare an anchor are kept as they are, since the tree-shaker cannot tell whether
they are referenced. Refs inside them are not followed, so their targets may be removed,
which then fails the sanity check.
* Non-standard keywords of reached schemas are kept, but refs inside them are not followed,
so their targets may be removed, which then fails the sanity check.
* Unresolvable *reached* local refs abort the tree-shaker with a `Missing target for LocalRef` error
before the sanity check gets to report them. Unresolvable refs in unreached parts are removed
without notice.
* Tree-shaking cannot hollow a schema that kept a `not` keyword;
this aborts with a `Collision` error.
* The bundler is not aware of `$id`-based resolution; refs are resolved purely by file path.
* Module names are derived from file basenames (`<name>.schema.json` → `<name>`).
If two schema files — including the entry schema — share a basename, the bundler
Expand Down
7 changes: 5 additions & 2 deletions tools/src/main/js/bundler/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,11 @@ if (import.meta.main) {
console.log(` ${modules} \\`);
console.log(` ${entry}`);
console.log('');
console.log('This will create:');
console.log('This will create a bundled and tree-shaken schema:');
console.log(` ${bundled} (pretty-printed with all $comment)`);
console.log(` ${minified} (minified, root $comment only)`);
console.log('');
console.log('Note: refs via anchors and $dynamicRef dynamic-scope semantics are not supported. See README.');
process.exit(E_INVALID);
}

Expand All @@ -153,8 +155,9 @@ if (import.meta.main) {
console.info(`bundler: embedded (${bundled.embedded.length}):`, bundled.embedded);
console.info(`bundler: external (${bundled.external.length}):`, bundled.external);

console.info(`tree-shake: removed (${shaken.removed.length}):`, shaken.removed);
console.info(`tree-shake: hollowed (${shaken.hollowed.length}):`, shaken.hollowed);
console.info(`tree-shake: voided (${shaken.voided.length}):`, shaken.voided);
console.info(`tree-shake: removed (${shaken.removed.length}):`, shaken.removed);

if (validationWarnings.length) {
console.warn(`validation warnings (${validationWarnings.length}):`, validationWarnings);
Expand Down
Loading
Loading