@effected/schemastore-cli 0.19.0 → 0.21.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/README.md +5 -3
- package/Report.js +12 -6
- package/Runner.js +16 -3
- package/cli/execute.js +12 -7
- package/main.js +23 -11
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ schemastore validate <payload.json> [config] [--schema <path|$id|url>] [--format
|
|
|
119
119
|
|
|
120
120
|
- Before anything is generated, every frozen label is verified — present on disk, and self-identified by the derived `$id`.
|
|
121
121
|
- `build` generates every schema, runs the gates (the structural lint and ajv strict mode), applies the drift policy, and writes what passes — content-compared, so an unchanged file is untouched — plus the config's catalog slice when any entry declares one, and the merged catalog over every slice.
|
|
122
|
-
- `check` is the identical walk with no writes: it reports what `build` would do under the same flags and exits the same way, and also fails (exit `1`) whenever a build would write anything — a stale or missing document is fixed by running `schemastore build` and committing the result. A catalog slice left behind after the config's last `catalog` block was removed is reported `orphaned` and fails `check` the same way, but `build` never deletes it — and the merged catalog keeps advertising its entries until it is gone: delete the file by hand, or restore a `catalog` block; a merged `catalog.json` with no slice left is orphaned the same way. So is a document left behind under an old derived name — an `appendVersion` flip or a `layout` change moved its path, and nothing claims the old file any more: both commands probe the sibling shapes (`<name>.json`, `<name>-<v>.json`, `<v>/<name>.json`, `<v>/<name>-<v>.json`) of every label the config still declares and report each one that exists as an orphaned document, failed by `check`, never deleted by `build`. Nothing else in `outputDir` is looked at, so sharing it with another config, a deploy folder, or the repository root is safe (unless two configs derive the same schema name and version under different layouts into it); a `name` change or a dropped label leaves a file the command cannot know about — delete those by hand. A catalog URL advertised
|
|
122
|
+
- `check` is the identical walk with no writes: it reports what `build` would do under the same flags and exits the same way, and also fails (exit `1`) whenever a build would write anything — a stale or missing document is fixed by running `schemastore build` and committing the result. A catalog slice left behind after the config's last `catalog` block was removed is reported `orphaned` and fails `check` the same way, but `build` never deletes it — and the merged catalog keeps advertising its entries until it is gone: delete the file by hand, or restore a `catalog` block; a merged `catalog.json` with no slice left is orphaned the same way. So is a document left behind under an old derived name — an `appendVersion` flip or a `layout` change moved its path, and nothing claims the old file any more: both commands probe the sibling shapes (`<name>.json`, `<name>-<v>.json`, `<v>/<name>.json`, `<v>/<name>-<v>.json`) of every label the config still declares and report each one that exists as an orphaned document, failed by `check`, never deleted by `build`. Nothing else in `outputDir` is looked at, so sharing it with another config, a deploy folder, or the repository root is safe (unless two configs derive the same schema name and version under different layouts into it); a `name` change or a dropped label leaves a file the command cannot know about — delete those by hand. A catalog URL or entry name advertised more than once across the slices, or a slice that cannot be read or is not a catalog entry array (an undeclared key included), blocks the merged catalog: both commands fail (exit `1`) naming the URL or name and its slices or the invalid slice, and the merged file is left as it is until the configs or slices are fixed.
|
|
123
123
|
- `validate` answers the question the publication story exists for: does THIS payload conform to the published document it names? The reference is the `--schema` flag or the payload's own `$schema`, resolved file-first and then against every identity a config schema derives (a target `$id`, a frozen version's `$id`/`url`, the catalog `url`) — CI validates an action's output against the committed document with no third-party tool and no network fetch. The payload's `$schema` self-reference is the pointer naming the document: it is stripped before validating only when the resolved document does not declare `$schema` as a root property, since a generated document's `additionalProperties: false` would otherwise reject the very self-reference that names it. A document that does declare `$schema` — the `HostedSchema` pattern above, where the source struct carries `$schema: Schema.Literal(...)` and the generated document requires and const-constrains the key — validates the payload verbatim. A non-conforming payload fails (exit `1`) with one finding per problem, each carrying the JSON pointer into the instance and the keyword; `--format=json` writes one report document to stdout and moves the human lines to stderr.
|
|
124
124
|
- `--drift` and `--on-drift` override the config for one run; `--force` is sugar for `--drift=allow` (combined with a different explicit `--drift` it is a usage error).
|
|
125
125
|
- `--format=json` emits one JSON document on stdout (per-schema outcome and effective tolerance, the catalog slice and merged-catalog outcomes, the `orphaned` document paths when any, `drift: { onDrift, policy? }`); human text moves to stderr. When `GITHUB_STEP_SUMMARY` is set, both commands append a markdown table.
|
|
@@ -154,13 +154,15 @@ Findings come back as values — `ValidationFinding` pointers into the document
|
|
|
154
154
|
|
|
155
155
|
## Exit codes
|
|
156
156
|
|
|
157
|
+
A failure is reported on stderr in the `@effected/cli` standard form: a status line naming the error and its message, the message's further lines, and, when the failure happened inside one of the command's spans, an `in: …` line naming them (a missing config, raised before any span, prints none). It is painted at a terminal, plain for an agent, and written for the log under GitHub Actions. Read the exit code, not the line's shape.
|
|
158
|
+
|
|
157
159
|
| code | meaning |
|
|
158
160
|
| ---- | -------------------------------------------------------------------------- |
|
|
159
161
|
| 0 | success, including drift under `onDrift: warn` |
|
|
160
|
-
| 1 | drift under `onDrift: error` (one line per drifting schema: `$id`, change, current and next version), a gate failure, a missing or mis-identified frozen version, a merged catalog blocked by a URL
|
|
162
|
+
| 1 | drift under `onDrift: error` (one line per drifting schema: `$id`, change, current and next version), a gate failure, a missing or mis-identified frozen version, a merged catalog blocked by a URL or name advertised twice or an invalid slice, — for `check` — anything `build` would write or an output nothing claims (an orphaned catalog slice or merged catalog, or an orphaned document at a sibling shape of a derived path), or — for `validate` — a payload that does not conform to the resolved document (one finding per problem, pointer and keyword each) |
|
|
161
163
|
| 2 | config not found, failed to load, failed `defineConfig` validation, a `catalogDir` that is a file or cannot be listed (checked before anything is written), or — for `validate` — a payload that cannot be read or parsed, or a `--schema`/`$schema` reference that is neither an existing file nor an identity any config schema derives, or names a document that cannot be read or parsed |
|
|
162
164
|
| 3 | infrastructure failure (for `validate`, an engine mechanism failure — a document the instance engine cannot compile — included) |
|
|
163
|
-
| 64 | usage error (for `validate`, a payload with no `$schema` and no `--schema` given, included) |
|
|
165
|
+
| 64 | usage error (for `validate`, a payload with no `$schema` and no `--schema` given, included); `--wizard` is the kit's prompt-gated flag, so a run that is not interactive (a pipe, CI, an agent) leaves it out of help and rejects it at `64` (it was accepted at `0` before the command moved onto `CliRuntime.main`) |
|
|
164
166
|
|
|
165
167
|
## License
|
|
166
168
|
|
package/Report.js
CHANGED
|
@@ -29,6 +29,7 @@ const sliceLine = (slice) => {
|
|
|
29
29
|
default: return slice.outcome;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
+
const conflictSubject = (conflict) => conflict.kind === "url" ? `url ${conflict.url}` : `name ${conflict.name}`;
|
|
32
33
|
const mergedLines = (merged) => {
|
|
33
34
|
const counts = `(${merged.entries} entries from ${merged.slices.length} slice(s))`;
|
|
34
35
|
switch (merged.outcome) {
|
|
@@ -39,7 +40,7 @@ const mergedLines = (merged) => {
|
|
|
39
40
|
case "orphaned": return [`orphaned catalog ${merged.path} (no catalog slice remains — delete it by hand; build never will)`];
|
|
40
41
|
case "blocked": return [
|
|
41
42
|
`CATALOG BLOCKED ${merged.path} (not written: fix the slices below)`,
|
|
42
|
-
...merged.conflicts.map((conflict) => `
|
|
43
|
+
...merged.conflicts.map((conflict) => ` ${conflictSubject(conflict)} advertised by ${conflict.slices.join(", ")}`),
|
|
43
44
|
...merged.invalid.map(({ path, reason }) => ` slice ${path} is invalid: ${reason}`)
|
|
44
45
|
];
|
|
45
46
|
default: return merged.outcome;
|
|
@@ -58,10 +59,15 @@ const catalogJson = (catalog) => ({
|
|
|
58
59
|
entries: catalog.merged.entries,
|
|
59
60
|
outcome: catalog.merged.outcome,
|
|
60
61
|
slices: catalog.merged.slices,
|
|
61
|
-
...catalog.merged.conflicts.length > 0 ? { conflicts: catalog.merged.conflicts.map((
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
...catalog.merged.conflicts.length > 0 ? { conflicts: catalog.merged.conflicts.map((conflict) => conflict.kind === "url" ? {
|
|
63
|
+
kind: conflict.kind,
|
|
64
|
+
url: conflict.url,
|
|
65
|
+
slices: conflict.slices
|
|
66
|
+
} : {
|
|
67
|
+
kind: conflict.kind,
|
|
68
|
+
name: conflict.name,
|
|
69
|
+
slices: conflict.slices
|
|
70
|
+
}) } : {},
|
|
65
71
|
...catalog.merged.invalid.length > 0 ? { invalid: catalog.merged.invalid.map(({ path, reason }) => ({
|
|
66
72
|
path,
|
|
67
73
|
reason
|
|
@@ -200,7 +206,7 @@ var Report = class {
|
|
|
200
206
|
String(merged.entries),
|
|
201
207
|
merged.outcome
|
|
202
208
|
])] : []);
|
|
203
|
-
if (merged !== void 0 && (merged.conflicts.length > 0 || merged.invalid.length > 0)) lines.push("", tableRow(["catalog problem", "slices"]), tableRow(["---", "---"]), ...merged.conflicts.map((conflict) => tableRow([
|
|
209
|
+
if (merged !== void 0 && (merged.conflicts.length > 0 || merged.invalid.length > 0)) lines.push("", tableRow(["catalog problem", "slices"]), tableRow(["---", "---"]), ...merged.conflicts.map((conflict) => tableRow([conflictSubject(conflict), conflict.slices.join(", ")])), ...merged.invalid.map(({ path, reason }) => tableRow([`invalid: ${reason}`, path])));
|
|
204
210
|
}
|
|
205
211
|
if (report.orphaned !== void 0) lines.push("", tableRow(["orphaned document", "claimed by"]), tableRow(["---", "---"]), ...report.orphaned.map((orphan) => tableRow([orphan, "nothing — delete by hand"])));
|
|
206
212
|
lines.push("");
|
package/Runner.js
CHANGED
|
@@ -221,18 +221,31 @@ const syncCatalog = Effect.fn("Runner.syncCatalog")(function* (config, names, wr
|
|
|
221
221
|
};
|
|
222
222
|
} else {
|
|
223
223
|
const claims = /* @__PURE__ */ new Map();
|
|
224
|
+
const nameClaims = /* @__PURE__ */ new Map();
|
|
224
225
|
const union = [];
|
|
225
226
|
for (const source of sources) for (const entry of source.entries) {
|
|
226
227
|
const claimants = claims.get(entry.url);
|
|
227
228
|
if (claimants === void 0) {
|
|
228
229
|
claims.set(entry.url, [source.slice]);
|
|
229
230
|
union.push(entry);
|
|
231
|
+
const nameClaimants = nameClaims.get(entry.name);
|
|
232
|
+
if (nameClaimants === void 0) nameClaims.set(entry.name, [source.slice]);
|
|
233
|
+
else nameClaimants.push(source.slice);
|
|
230
234
|
} else claimants.push(source.slice);
|
|
231
235
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
236
|
+
const repeated = (claimed) => [...claimed].filter(([, slices]) => slices.length > 1).sort(([a], [b]) => byCodeUnit(a, b)).map(([value, slices]) => ({
|
|
237
|
+
value,
|
|
234
238
|
slices: [...slices].sort(byCodeUnit)
|
|
235
|
-
}))
|
|
239
|
+
}));
|
|
240
|
+
const conflicts = [...repeated(claims).map(({ value, slices }) => ({
|
|
241
|
+
kind: "url",
|
|
242
|
+
url: value,
|
|
243
|
+
slices
|
|
244
|
+
})), ...repeated(nameClaims).map(({ value, slices }) => ({
|
|
245
|
+
kind: "name",
|
|
246
|
+
name: value,
|
|
247
|
+
slices
|
|
248
|
+
}))];
|
|
236
249
|
union.sort((a, b) => byCodeUnit(a.url, b.url));
|
|
237
250
|
const outcome = conflicts.length > 0 || invalid.length > 0 ? "blocked" : yield* syncFile(mergedPath, yield* encodeEntries(union), writing, refused);
|
|
238
251
|
merged = {
|
package/cli/execute.js
CHANGED
|
@@ -45,8 +45,8 @@ var GateError = class extends Schema.TaggedError()("GateError", { count: Schema.
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
|
-
* The merged catalog could not be assembled: a catalog URL
|
|
49
|
-
* more than
|
|
48
|
+
* The merged catalog could not be assembled: a catalog URL or entry name is
|
|
49
|
+
* advertised more than once across the slices, or a slice in `catalogDir` is not a catalog entry
|
|
50
50
|
* array. Nothing is merged silently, so the merged catalog was left as it
|
|
51
51
|
* is. Exit `1` under both `build` and `check`: only an edit to the slices
|
|
52
52
|
* or the configs clears it.
|
|
@@ -55,18 +55,23 @@ var GateError = class extends Schema.TaggedError()("GateError", { count: Schema.
|
|
|
55
55
|
*/
|
|
56
56
|
var CatalogMergeError = class extends Schema.TaggedError()("CatalogMergeError", {
|
|
57
57
|
path: Schema.String,
|
|
58
|
-
conflicts: Schema.Array(Schema.Struct({
|
|
58
|
+
conflicts: Schema.Array(Schema.Union([Schema.Struct({
|
|
59
|
+
kind: Schema.Literal("url"),
|
|
59
60
|
url: Schema.String,
|
|
60
61
|
slices: Schema.Array(Schema.String)
|
|
61
|
-
})
|
|
62
|
+
}), Schema.Struct({
|
|
63
|
+
kind: Schema.Literal("name"),
|
|
64
|
+
name: Schema.String,
|
|
65
|
+
slices: Schema.Array(Schema.String)
|
|
66
|
+
})])),
|
|
62
67
|
invalid: Schema.Array(Schema.Struct({
|
|
63
68
|
path: Schema.String,
|
|
64
69
|
reason: Schema.String
|
|
65
70
|
}))
|
|
66
71
|
}) {
|
|
67
72
|
get message() {
|
|
68
|
-
const lines = [...this.conflicts.map((conflict) => ` url ${conflict.url} is advertised by ${conflict.slices.join(", ")}`), ...this.invalid.map(({ path, reason }) => ` ${path} is invalid: ${reason}`)];
|
|
69
|
-
return `The merged catalog ${this.path} was not written.\n${lines.join("\n")}\nGive each catalog URL to exactly one config, and fix or delete every invalid slice.`;
|
|
73
|
+
const lines = [...this.conflicts.map((conflict) => ` ${conflict.kind === "url" ? `url ${conflict.url}` : `name ${conflict.name}`} is advertised by ${conflict.slices.join(", ")}`), ...this.invalid.map(({ path, reason }) => ` ${path} is invalid: ${reason}`)];
|
|
74
|
+
return `The merged catalog ${this.path} was not written.\n${lines.join("\n")}\nGive each catalog URL and name to exactly one config, and fix or delete every invalid slice.`;
|
|
70
75
|
}
|
|
71
76
|
};
|
|
72
77
|
/**
|
|
@@ -126,7 +131,7 @@ const emit = Effect.fn("schemastore.emit")(function* (report, format) {
|
|
|
126
131
|
* requested format, appends the step summary, and fails typed —
|
|
127
132
|
* `GateError`, then `DriftError`, then `CatalogMergeError`, then (for
|
|
128
133
|
* `check` only) `StaleError`, each carrying exit `1` — when the report says
|
|
129
|
-
* the run refused to write, the merged catalog was blocked by a URL
|
|
134
|
+
* the run refused to write, the merged catalog was blocked by a URL or name
|
|
130
135
|
* conflict or an invalid slice, or, under `check`, that a build would
|
|
131
136
|
* write. `SchemaFile` is built here
|
|
132
137
|
* over the environment's `FileSystem`; the validator is `deps.validator` or
|
package/main.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import { loggerLayer, program } from "./cli/program.js";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
2
|
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
4
3
|
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
5
4
|
import { CliRuntime } from "@effected/cli";
|
|
6
|
-
import { CliError } from "effect/cli";
|
|
7
5
|
|
|
8
6
|
//#region src/main.ts
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* How `CliRuntime.main` runs the command: the kit's standard failure report, exit `3` for a typed error that carries
|
|
9
|
+
* no code of its own (the infrastructure tier; every other code is marked where it is raised), and this module as the
|
|
10
|
+
* program's own, so the report's span trail keeps the command's spans when it is installed under
|
|
11
|
+
* `node_modules/@effected/` and leaves out the kit's.
|
|
12
|
+
*/
|
|
13
|
+
const mainOptions = {
|
|
14
|
+
exitCode: 3,
|
|
15
|
+
logger: loggerLayer,
|
|
16
|
+
env: { appModule: import.meta.url }
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The command under `CliRuntime.main` over `platform`: what `main` runs with `NodeServices.layer`, and what a test runs
|
|
20
|
+
* with a test `Command.Environment`.
|
|
21
|
+
*/
|
|
22
|
+
const run = (args, deps, platform) => CliRuntime.main(program(args, deps), {
|
|
23
|
+
...mainOptions,
|
|
24
|
+
platform
|
|
25
|
+
});
|
|
10
26
|
const main = () => {
|
|
11
|
-
|
|
27
|
+
NodeRuntime.runMain(run(process.argv.slice(2), {
|
|
12
28
|
cwd: process.cwd(),
|
|
13
|
-
version: "0.
|
|
14
|
-
}
|
|
15
|
-
exitCode: 3,
|
|
16
|
-
render
|
|
17
|
-
}), Effect.provide(loggerLayer));
|
|
18
|
-
NodeRuntime.runMain(run);
|
|
29
|
+
version: "0.21.0"
|
|
30
|
+
}, NodeServices.layer));
|
|
19
31
|
};
|
|
20
32
|
|
|
21
33
|
//#endregion
|
|
22
|
-
export { main };
|
|
34
|
+
export { main, mainOptions, run };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/schemastore-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The schemastore command: build and check SchemaStore-shaped JSON Schema documents from a schemastore.config.ts, with a per-schema published flag and a drift policy.",
|
|
6
6
|
"keywords": [
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@effect/platform-node": "^4.0.0",
|
|
44
|
-
"@effected/cli": "^0.
|
|
44
|
+
"@effected/cli": "^0.12.0",
|
|
45
45
|
"@effected/env": "^0.1.0",
|
|
46
46
|
"@effected/glob": "^0.10.0",
|
|
47
47
|
"@effected/walker": "^0.15.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"jiti": "^2.6.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@effected/schemastore": "0.
|
|
53
|
+
"@effected/schemastore": "0.21.0",
|
|
54
54
|
"effect": "^4.0.0"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|