@effected/schemastore-cli 0.12.0 → 0.13.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 CHANGED
@@ -114,9 +114,9 @@ schemastore check [config] [--drift=strict|semantic|allow] [--on-drift=error|war
114
114
 
115
115
  - Before anything is generated, every frozen label is verified — present on disk, and self-identified by the derived `$id`.
116
116
  - `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 catalog file when any entry declares one.
117
- - `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.json` left behind after the last `catalog` block was removed is reported `orphaned` and fails `check` the same way, but `build` never deletes it: delete the file by hand, or restore a `catalog` block.
117
+ - `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.json` left behind after the last `catalog` block was removed is reported `orphaned` and fails `check` the same way, but `build` never deletes it: delete the file by hand, or restore a `catalog` block. 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.
118
118
  - `--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).
119
- - `--format=json` emits one JSON document on stdout (per-schema outcome and effective tolerance, the catalog outcome, `drift: { onDrift, policy? }`); human text moves to stderr. When `GITHUB_STEP_SUMMARY` is set, both commands append a markdown table.
119
+ - `--format=json` emits one JSON document on stdout (per-schema outcome and effective tolerance, the catalog outcome, 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.
120
120
 
121
121
  ## The engine, as a library export
122
122
 
@@ -140,7 +140,7 @@ Findings come back as values; the error channel carries `SchemaValidatorError` o
140
140
  | code | meaning |
141
141
  | ---- | -------------------------------------------------------------------------- |
142
142
  | 0 | success, including drift under `onDrift: warn` |
143
- | 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, or — for `check` — anything `build` would write |
143
+ | 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, or — for `check` — anything `build` would write or an output nothing claims (an orphaned catalog file, or an orphaned document at a sibling shape of a derived path) |
144
144
  | 2 | config not found, failed to load, or failed `defineConfig` validation |
145
145
  | 3 | infrastructure failure |
146
146
  | 64 | usage error |
package/Report.js CHANGED
@@ -27,6 +27,7 @@ const catalogLine = (entry) => {
27
27
  default: return entry.outcome;
28
28
  }
29
29
  };
30
+ const orphanedLine = (orphan) => `orphaned document ${orphan} (no target, frozen version, or catalog entry claims it — delete it by hand; build never will)`;
30
31
  const driftClause = (report) => `drift ${report.policy !== void 0 ? `${report.policy} (flag)` : "per schema (config)"}, on-drift ${report.onDrift}`;
31
32
  const summaryLine = (report) => {
32
33
  const written = report.schemas.filter((schema) => schema.outcome === "written").length;
@@ -58,6 +59,7 @@ var Report = class {
58
59
  const lines = [];
59
60
  for (const schema of report.schemas) lines.push(...schemaLines(schema, report));
60
61
  if (report.catalog !== void 0) lines.push(catalogLine(report.catalog));
62
+ for (const orphan of report.orphaned ?? []) lines.push(orphanedLine(orphan));
61
63
  lines.push(summaryLine(report));
62
64
  return lines;
63
65
  }
@@ -104,6 +106,7 @@ var Report = class {
104
106
  entries: report.catalog.entries,
105
107
  outcome: report.catalog.outcome
106
108
  } } : {},
109
+ ...report.orphaned !== void 0 ? { orphaned: report.orphaned } : {},
107
110
  drifted: report.drifted,
108
111
  gateFailed: report.gateFailed,
109
112
  wrote: report.wrote
@@ -149,6 +152,7 @@ var Report = class {
149
152
  String(report.catalog.entries),
150
153
  report.catalog.outcome
151
154
  ]));
155
+ if (report.orphaned !== void 0) lines.push("", tableRow(["orphaned document", "claimed by"]), tableRow(["---", "---"]), ...report.orphaned.map((orphan) => tableRow([orphan, "nothing — delete by hand"])));
152
156
  lines.push("");
153
157
  if (report.gateFailed) {
154
158
  const failed = report.schemas.filter((schema) => schema.outcome === "gate-failed").length;
package/Runner.js CHANGED
@@ -127,6 +127,22 @@ const parsesEqual = (existing, text) => {
127
127
  * `orphaned` (stale under `check`) and left in place, and the report omits
128
128
  * `catalog` entirely only when there is no such file either.
129
129
  *
130
+ * **A moved path leaves an orphan the derivation cannot see**: an
131
+ * `appendVersion` flip or a `layout` change renames a document's derived
132
+ * path, and the previously written file stays on disk under the old name —
133
+ * for a `published` label, its advertised URL keeps serving a stale
134
+ * document with no report. The derivation has exactly four shapes for a
135
+ * name and label (`<name>.json`, `<name>-<v>.json`, `<v>/<name>.json`,
136
+ * `<v>/<name>-<v>.json`), so both modes probe the sibling shapes of every
137
+ * label the config still knows and report each one that exists as a FILE
138
+ * and that no target, frozen version, or `catalogPath` claims in
139
+ * {@link RunReport.orphaned}. Nothing else on disk is looked at: an
140
+ * `outputDir` shared with another config, a deploy folder, or the
141
+ * repository root holds documents this config cannot tell from its own
142
+ * leftovers, so they are never reported. A `name` change is therefore not
143
+ * caught either — the old name is unknowable. Orphans are reported, never
144
+ * deleted: the CLI may not have written them.
145
+ *
130
146
  * @public
131
147
  */
132
148
  var Runner = class {
@@ -233,6 +249,26 @@ var Runner = class {
233
249
  outcome
234
250
  };
235
251
  }
252
+ const claimed = /* @__PURE__ */ new Set([path.normalize(config.catalogPath)]);
253
+ for (const schema of config.schemas) {
254
+ claimed.add(path.normalize(schema.target.path));
255
+ for (const frozen of schema.frozen) claimed.add(path.normalize(frozen.path));
256
+ }
257
+ const orphaned = [];
258
+ for (const schema of config.schemas) {
259
+ const labels = [schema.target.version, ...schema.frozen.map((frozen) => frozen.version)];
260
+ const shapes = [SchemaVersioning.fileName(schema.name), ...labels.flatMap((version) => version === void 0 ? [] : [
261
+ SchemaVersioning.fileName(schema.name, version, "flat"),
262
+ SchemaVersioning.fileName(schema.name, version, "versioned"),
263
+ SchemaVersioning.fileName(schema.name, version, "versioned", false)
264
+ ])];
265
+ for (const shape of shapes) {
266
+ const file = path.normalize(path.join(config.outputDir, shape));
267
+ if (claimed.has(file)) continue;
268
+ const info = yield* orNone(fs.stat(file));
269
+ if (Option.isSome(info) && info.value.type === "File") orphaned.push(file);
270
+ }
271
+ }
236
272
  return {
237
273
  mode: options.mode,
238
274
  configPath: options.configPath,
@@ -240,6 +276,7 @@ var Runner = class {
240
276
  ...options.policy !== void 0 ? { policy: options.policy } : {},
241
277
  schemas,
242
278
  ...catalog !== void 0 ? { catalog } : {},
279
+ ...orphaned.length > 0 ? { orphaned } : {},
243
280
  drifted,
244
281
  gateFailed,
245
282
  wrote: schemas.some((s) => s.outcome === "written") || catalog?.outcome === "written"
@@ -6,11 +6,14 @@ import { Command } from "effect/unstable/cli";
6
6
  /**
7
7
  * `schemastore check`: the same walk as `build`, reported and never written.
8
8
  * The CI drift gate: it also fails when the committed documents are stale,
9
- * i.e. whenever `build` would write anything.
9
+ * i.e. whenever `build` would write anything — and on the outputs nothing
10
+ * claims (an orphaned catalog file, or a document left behind at a sibling
11
+ * shape of a derived path), which `build` never deletes: remove them by
12
+ * hand.
10
13
  *
11
14
  * @public
12
15
  */
13
- const makeCheckCommand = (deps) => Command.make("check", commandFlags, (input) => execute("check", input, deps)).pipe(Command.withDescription("Report what build would do, fail when it would write anything or refuse to, and write nothing"));
16
+ const makeCheckCommand = (deps) => Command.make("check", commandFlags, (input) => execute("check", input, deps)).pipe(Command.withDescription("Report what build would do, fail when it would write anything or refuse to, or when a catalog file or document is orphaned, and write nothing"));
14
17
 
15
18
  //#endregion
16
19
  export { makeCheckCommand };
package/cli/execute.js CHANGED
@@ -46,14 +46,22 @@ var GateError = class extends Schema.TaggedError()("GateError", { count: Schema.
46
46
  };
47
47
  /**
48
48
  * `check` found committed documents that differ from what the config
49
- * generates (or are missing), so a `build` would write. `check` is the CI
50
- * drift gate, so a stale tree fails it. Exit `1`.
49
+ * generates (or are missing), so a `build` would write — or outputs
50
+ * nothing claims (an orphaned catalog file, an orphaned document), which
51
+ * `build` never deletes. `check` is the CI drift gate, so a stale tree
52
+ * fails it. Exit `1`. `count` is every finding; `orphaned` the part of it
53
+ * a build cannot clear, so the message names both remedies.
51
54
  *
52
55
  * @public
53
56
  */
54
- var StaleError = class extends Schema.TaggedError()("StaleError", { count: Schema.Number }) {
57
+ var StaleError = class extends Schema.TaggedError()("StaleError", {
58
+ count: Schema.Number,
59
+ orphaned: Schema.optionalKey(Schema.Number)
60
+ }) {
55
61
  get message() {
56
- return `${this.count} document(s) are stale; run \`schemastore build\` and commit the result.`;
62
+ const orphaned = this.orphaned ?? 0;
63
+ const stale = this.count - orphaned;
64
+ return [...stale > 0 ? [`${stale} document(s) are stale; run \`schemastore build\` and commit the result.`] : [], ...orphaned > 0 ? [`${orphaned} orphaned output(s) must be deleted by hand; build never will.`] : []].join(" ");
57
65
  }
58
66
  };
59
67
  /**
@@ -132,8 +140,12 @@ const execute = Effect.fn("schemastore.execute")(function* (mode, input, deps) {
132
140
  return yield* Effect.fail(CliRuntime.reported(new DriftError({ drifted }), 1));
133
141
  }
134
142
  if (mode === "check") {
135
- const count = report.schemas.filter((schema) => schema.outcome === "would-write").length + (report.catalog?.outcome === "would-write" || report.catalog?.outcome === "orphaned" ? 1 : 0);
136
- if (count > 0) return yield* Effect.fail(CliRuntime.reported(new StaleError({ count }), 1));
143
+ const orphaned = (report.catalog?.outcome === "orphaned" ? 1 : 0) + (report.orphaned?.length ?? 0);
144
+ const count = report.schemas.filter((schema) => schema.outcome === "would-write").length + (report.catalog?.outcome === "would-write" ? 1 : 0) + orphaned;
145
+ if (count > 0) return yield* Effect.fail(CliRuntime.reported(new StaleError({
146
+ count,
147
+ ...orphaned > 0 ? { orphaned } : {}
148
+ }), 1));
137
149
  }
138
150
  });
139
151
 
package/main.js CHANGED
@@ -15,7 +15,7 @@ const render = (error) => CliError.isCliError(error) && error._tag === "ShowHelp
15
15
  const main = () => {
16
16
  const run = program(process.argv.slice(2), {
17
17
  cwd: process.cwd(),
18
- version: "0.12.0"
18
+ version: "0.13.1"
19
19
  }).pipe(Effect.provide(NodeServices.layer), CliRuntime.reportFailures({
20
20
  exitCode: 3,
21
21
  render
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/schemastore-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
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,13 +41,13 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@effect/platform-node": "4.0.0-rc.115",
44
- "@effected/cli": "^0.5.1",
44
+ "@effected/cli": "^0.5.2",
45
45
  "ajv": "^8.20.0",
46
46
  "ajv-formats": "^3.0.1",
47
47
  "jiti": "^2.6.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@effected/schemastore": "0.12.0",
50
+ "@effected/schemastore": "0.13.1",
51
51
  "effect": "4.0.0-rc.115"
52
52
  },
53
53
  "engines": {