@schemasentry/cli 0.4.0 → 0.5.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 +37 -1
- package/dist/index.js +308 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -81,6 +81,40 @@ pnpm schemasentry collect \
|
|
|
81
81
|
--data ./schema-sentry.data.json
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### `scaffold`
|
|
85
|
+
|
|
86
|
+
Auto-generate schema stubs for routes without schema (dry-run by default):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm schemasentry scaffold --manifest ./schema-sentry.manifest.json --data ./schema-sentry.data.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Preview what would be generated without writing files:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pnpm schemasentry scaffold
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Apply scaffolded schema to your files:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm schemasentry scaffold --write
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Skip confirmation prompts:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pnpm schemasentry scaffold --write --force
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Pattern-based auto-detection** infers schema types from URL patterns:
|
|
111
|
+
- `/blog/*` → BlogPosting
|
|
112
|
+
- `/products/*` → Product
|
|
113
|
+
- `/faq` → FAQPage
|
|
114
|
+
- `/events/*` → Event
|
|
115
|
+
- `/howto/*` → HowTo
|
|
116
|
+
- and more...
|
|
117
|
+
|
|
84
118
|
## Options
|
|
85
119
|
|
|
86
120
|
| Option | Description |
|
|
@@ -88,10 +122,12 @@ pnpm schemasentry collect \
|
|
|
88
122
|
| `--format json\|html` | Output format |
|
|
89
123
|
| `--annotations none\|github` | CI annotations |
|
|
90
124
|
| `-o, --output <path>` | Write output to file |
|
|
91
|
-
| `--root <path>` | Root directory to scan
|
|
125
|
+
| `--root <path>` | Root directory to scan (`collect`, `scaffold`) |
|
|
92
126
|
| `--routes <routes...>` | Collect only specific routes (`collect`) |
|
|
93
127
|
| `--strict-routes` | Fail when any route passed to `--routes` is missing (`collect`) |
|
|
94
128
|
| `--check` | Compare collected output with existing data and fail on drift (`collect`) |
|
|
129
|
+
| `--write` | Apply scaffolded changes to files (`scaffold`) |
|
|
130
|
+
| `--force` | Skip confirmation prompts (`scaffold`) |
|
|
95
131
|
| `--recommended / --no-recommended` | Enable recommended field checks |
|
|
96
132
|
|
|
97
133
|
## Documentation
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
|
-
import
|
|
8
|
-
import { stableStringify as
|
|
7
|
+
import path6 from "path";
|
|
8
|
+
import { stableStringify as stableStringify4 } from "@schemasentry/core";
|
|
9
9
|
|
|
10
10
|
// src/report.ts
|
|
11
11
|
import {
|
|
@@ -877,6 +877,235 @@ var schemaTypeLabel = (node) => {
|
|
|
877
877
|
return typeof type === "string" && type.trim().length > 0 ? type : "(unknown)";
|
|
878
878
|
};
|
|
879
879
|
|
|
880
|
+
// src/scaffold.ts
|
|
881
|
+
import { promises as fs5 } from "fs";
|
|
882
|
+
import path5 from "path";
|
|
883
|
+
import { stableStringify as stableStringify3 } from "@schemasentry/core";
|
|
884
|
+
|
|
885
|
+
// src/patterns.ts
|
|
886
|
+
var DEFAULT_PATTERNS = [
|
|
887
|
+
{ pattern: "/blog/*", schemaType: "BlogPosting", priority: 10 },
|
|
888
|
+
{ pattern: "/blog", schemaType: "WebPage", priority: 5 },
|
|
889
|
+
{ pattern: "/products/*", schemaType: "Product", priority: 10 },
|
|
890
|
+
{ pattern: "/product/*", schemaType: "Product", priority: 10 },
|
|
891
|
+
{ pattern: "/faq", schemaType: "FAQPage", priority: 10 },
|
|
892
|
+
{ pattern: "/faqs", schemaType: "FAQPage", priority: 10 },
|
|
893
|
+
{ pattern: "/how-to/*", schemaType: "HowTo", priority: 10 },
|
|
894
|
+
{ pattern: "/howto/*", schemaType: "HowTo", priority: 10 },
|
|
895
|
+
{ pattern: "/events/*", schemaType: "Event", priority: 10 },
|
|
896
|
+
{ pattern: "/event/*", schemaType: "Event", priority: 10 },
|
|
897
|
+
{ pattern: "/reviews/*", schemaType: "Review", priority: 10 },
|
|
898
|
+
{ pattern: "/review/*", schemaType: "Review", priority: 10 },
|
|
899
|
+
{ pattern: "/videos/*", schemaType: "VideoObject", priority: 10 },
|
|
900
|
+
{ pattern: "/video/*", schemaType: "VideoObject", priority: 10 },
|
|
901
|
+
{ pattern: "/images/*", schemaType: "ImageObject", priority: 10 },
|
|
902
|
+
{ pattern: "/image/*", schemaType: "ImageObject", priority: 10 },
|
|
903
|
+
{ pattern: "/about", schemaType: "WebPage", priority: 10 },
|
|
904
|
+
{ pattern: "/contact", schemaType: "WebPage", priority: 10 },
|
|
905
|
+
{ pattern: "/", schemaType: "WebSite", priority: 1 }
|
|
906
|
+
];
|
|
907
|
+
var matchRouteToPatterns = (route, patterns = DEFAULT_PATTERNS) => {
|
|
908
|
+
const matches = [];
|
|
909
|
+
for (const rule of patterns) {
|
|
910
|
+
if (routeMatchesPattern(route, rule.pattern)) {
|
|
911
|
+
matches.push({
|
|
912
|
+
type: rule.schemaType,
|
|
913
|
+
priority: rule.priority ?? 5
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
matches.sort((a, b) => b.priority - a.priority);
|
|
918
|
+
return [...new Set(matches.map((m) => m.type))];
|
|
919
|
+
};
|
|
920
|
+
var routeMatchesPattern = (route, pattern) => {
|
|
921
|
+
if (pattern === route) {
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
if (pattern.endsWith("/*")) {
|
|
925
|
+
const prefix = pattern.slice(0, -1);
|
|
926
|
+
return route.startsWith(prefix);
|
|
927
|
+
}
|
|
928
|
+
const patternRegex = pattern.replace(/\*/g, "[^/]+").replace(/\?/g, ".");
|
|
929
|
+
const regex = new RegExp(`^${patternRegex}$`);
|
|
930
|
+
return regex.test(route);
|
|
931
|
+
};
|
|
932
|
+
var inferSchemaTypes = (routes, customPatterns) => {
|
|
933
|
+
const patterns = customPatterns ?? DEFAULT_PATTERNS;
|
|
934
|
+
const result = /* @__PURE__ */ new Map();
|
|
935
|
+
for (const route of routes) {
|
|
936
|
+
const types = matchRouteToPatterns(route, patterns);
|
|
937
|
+
if (types.length > 0) {
|
|
938
|
+
result.set(route, types);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return result;
|
|
942
|
+
};
|
|
943
|
+
var generateManifestEntries = (routes, customPatterns) => {
|
|
944
|
+
const inferred = inferSchemaTypes(routes, customPatterns);
|
|
945
|
+
const entries = {};
|
|
946
|
+
for (const [route, types] of inferred) {
|
|
947
|
+
entries[route] = types;
|
|
948
|
+
}
|
|
949
|
+
return entries;
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/scaffold.ts
|
|
953
|
+
var scaffoldSchema = async (options) => {
|
|
954
|
+
const manifest = await loadManifest(options.manifestPath);
|
|
955
|
+
const data = await loadData(options.dataPath);
|
|
956
|
+
const discoveredRoutes = await scanRoutes({ rootDir: options.rootDir });
|
|
957
|
+
const routesNeedingSchema = discoveredRoutes.filter(
|
|
958
|
+
(route) => !data.routes[route] || data.routes[route].length === 0
|
|
959
|
+
);
|
|
960
|
+
const inferredTypes = inferSchemaTypes(routesNeedingSchema, options.customPatterns);
|
|
961
|
+
const manifestEntries = generateManifestEntries(
|
|
962
|
+
routesNeedingSchema,
|
|
963
|
+
options.customPatterns
|
|
964
|
+
);
|
|
965
|
+
const generatedSchemas = /* @__PURE__ */ new Map();
|
|
966
|
+
for (const [route, types] of inferredTypes) {
|
|
967
|
+
const schemas = types.map((type) => generateSchemaStub(type, route));
|
|
968
|
+
generatedSchemas.set(route, schemas);
|
|
969
|
+
}
|
|
970
|
+
const wouldUpdate = routesNeedingSchema.length > 0;
|
|
971
|
+
return {
|
|
972
|
+
routesToScaffold: routesNeedingSchema,
|
|
973
|
+
generatedSchemas,
|
|
974
|
+
manifestUpdates: manifestEntries,
|
|
975
|
+
wouldUpdate
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
var loadManifest = async (manifestPath) => {
|
|
979
|
+
try {
|
|
980
|
+
const raw = await fs5.readFile(manifestPath, "utf8");
|
|
981
|
+
return JSON.parse(raw);
|
|
982
|
+
} catch {
|
|
983
|
+
return { routes: {} };
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
var loadData = async (dataPath) => {
|
|
987
|
+
try {
|
|
988
|
+
const raw = await fs5.readFile(dataPath, "utf8");
|
|
989
|
+
return JSON.parse(raw);
|
|
990
|
+
} catch {
|
|
991
|
+
return { routes: {} };
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
var generateSchemaStub = (type, route) => {
|
|
995
|
+
const base = {
|
|
996
|
+
"@context": "https://schema.org",
|
|
997
|
+
"@type": type
|
|
998
|
+
};
|
|
999
|
+
switch (type) {
|
|
1000
|
+
case "BlogPosting":
|
|
1001
|
+
return {
|
|
1002
|
+
...base,
|
|
1003
|
+
headline: "Blog Post Title",
|
|
1004
|
+
author: {
|
|
1005
|
+
"@type": "Person",
|
|
1006
|
+
name: "Author Name"
|
|
1007
|
+
},
|
|
1008
|
+
datePublished: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1009
|
+
url: route
|
|
1010
|
+
};
|
|
1011
|
+
case "Product":
|
|
1012
|
+
return {
|
|
1013
|
+
...base,
|
|
1014
|
+
name: "Product Name",
|
|
1015
|
+
description: "Product description",
|
|
1016
|
+
offers: {
|
|
1017
|
+
"@type": "Offer",
|
|
1018
|
+
price: "0.00",
|
|
1019
|
+
priceCurrency: "USD"
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
case "FAQPage":
|
|
1023
|
+
return {
|
|
1024
|
+
...base,
|
|
1025
|
+
mainEntity: []
|
|
1026
|
+
};
|
|
1027
|
+
case "HowTo":
|
|
1028
|
+
return {
|
|
1029
|
+
...base,
|
|
1030
|
+
name: "How-To Title",
|
|
1031
|
+
step: []
|
|
1032
|
+
};
|
|
1033
|
+
case "Event":
|
|
1034
|
+
return {
|
|
1035
|
+
...base,
|
|
1036
|
+
name: "Event Name",
|
|
1037
|
+
startDate: (/* @__PURE__ */ new Date()).toISOString()
|
|
1038
|
+
};
|
|
1039
|
+
case "Organization":
|
|
1040
|
+
return {
|
|
1041
|
+
...base,
|
|
1042
|
+
name: "Organization Name",
|
|
1043
|
+
url: route
|
|
1044
|
+
};
|
|
1045
|
+
case "WebSite":
|
|
1046
|
+
return {
|
|
1047
|
+
...base,
|
|
1048
|
+
name: "Website Name",
|
|
1049
|
+
url: route
|
|
1050
|
+
};
|
|
1051
|
+
case "Article":
|
|
1052
|
+
return {
|
|
1053
|
+
...base,
|
|
1054
|
+
headline: "Article Headline",
|
|
1055
|
+
author: {
|
|
1056
|
+
"@type": "Person",
|
|
1057
|
+
name: "Author Name"
|
|
1058
|
+
},
|
|
1059
|
+
datePublished: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1060
|
+
};
|
|
1061
|
+
default:
|
|
1062
|
+
return {
|
|
1063
|
+
...base,
|
|
1064
|
+
name: `${type} Name`
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
var formatScaffoldPreview = (result) => {
|
|
1069
|
+
if (result.routesToScaffold.length === 0) {
|
|
1070
|
+
return "No routes need schema generation.";
|
|
1071
|
+
}
|
|
1072
|
+
const lines = [
|
|
1073
|
+
`Routes to scaffold: ${result.routesToScaffold.length}`,
|
|
1074
|
+
""
|
|
1075
|
+
];
|
|
1076
|
+
for (const route of result.routesToScaffold) {
|
|
1077
|
+
const types = result.manifestUpdates[route] || [];
|
|
1078
|
+
lines.push(` ${route}`);
|
|
1079
|
+
lines.push(` Schema types: ${types.join(", ") || "None detected"}`);
|
|
1080
|
+
}
|
|
1081
|
+
return lines.join("\n");
|
|
1082
|
+
};
|
|
1083
|
+
var applyScaffold = async (result, options) => {
|
|
1084
|
+
if (!result.wouldUpdate) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const manifest = await loadManifest(options.manifestPath);
|
|
1088
|
+
const data = await loadData(options.dataPath);
|
|
1089
|
+
for (const [route, types] of Object.entries(result.manifestUpdates)) {
|
|
1090
|
+
if (!manifest.routes[route]) {
|
|
1091
|
+
manifest.routes[route] = types;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
for (const [route, schemas] of result.generatedSchemas) {
|
|
1095
|
+
if (!data.routes[route]) {
|
|
1096
|
+
data.routes[route] = schemas;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
await fs5.mkdir(path5.dirname(options.manifestPath), { recursive: true });
|
|
1100
|
+
await fs5.mkdir(path5.dirname(options.dataPath), { recursive: true });
|
|
1101
|
+
await fs5.writeFile(
|
|
1102
|
+
options.manifestPath,
|
|
1103
|
+
stableStringify3(manifest),
|
|
1104
|
+
"utf8"
|
|
1105
|
+
);
|
|
1106
|
+
await fs5.writeFile(options.dataPath, stableStringify3(data), "utf8");
|
|
1107
|
+
};
|
|
1108
|
+
|
|
880
1109
|
// src/index.ts
|
|
881
1110
|
import { createInterface } from "readline/promises";
|
|
882
1111
|
import { stdin as input, stdout as output } from "process";
|
|
@@ -895,8 +1124,8 @@ program.command("validate").description("Validate schema coverage and rules").op
|
|
|
895
1124
|
const format = resolveOutputFormat(options.format);
|
|
896
1125
|
const annotationsMode = resolveAnnotationsMode(options.annotations);
|
|
897
1126
|
const recommended = await resolveRecommendedOption(options.config);
|
|
898
|
-
const manifestPath =
|
|
899
|
-
const dataPath =
|
|
1127
|
+
const manifestPath = path6.resolve(process.cwd(), options.manifest);
|
|
1128
|
+
const dataPath = path6.resolve(process.cwd(), options.data);
|
|
900
1129
|
let raw;
|
|
901
1130
|
try {
|
|
902
1131
|
raw = await readFile(manifestPath, "utf8");
|
|
@@ -983,12 +1212,12 @@ program.command("init").description("Interactive setup wizard").option(
|
|
|
983
1212
|
"Path to schema data JSON",
|
|
984
1213
|
"schema-sentry.data.json"
|
|
985
1214
|
).option("-y, --yes", "Use defaults and skip prompts").option("-f, --force", "Overwrite existing files").option("--scan", "Scan the filesystem for routes and add WebPage entries").option("--root <path>", "Project root for scanning", ".").action(async (options) => {
|
|
986
|
-
const manifestPath =
|
|
987
|
-
const dataPath =
|
|
1215
|
+
const manifestPath = path6.resolve(process.cwd(), options.manifest);
|
|
1216
|
+
const dataPath = path6.resolve(process.cwd(), options.data);
|
|
988
1217
|
const force = options.force ?? false;
|
|
989
1218
|
const useDefaults = options.yes ?? false;
|
|
990
1219
|
const answers = useDefaults ? getDefaultAnswers() : await promptAnswers();
|
|
991
|
-
const scannedRoutes = options.scan ? await scanRoutes({ rootDir:
|
|
1220
|
+
const scannedRoutes = options.scan ? await scanRoutes({ rootDir: path6.resolve(process.cwd(), options.root ?? ".") }) : [];
|
|
992
1221
|
if (options.scan && scannedRoutes.length === 0) {
|
|
993
1222
|
console.error("No routes found during scan.");
|
|
994
1223
|
}
|
|
@@ -1017,7 +1246,7 @@ program.command("audit").description("Analyze schema health and report issues").
|
|
|
1017
1246
|
const format = resolveOutputFormat(options.format);
|
|
1018
1247
|
const annotationsMode = resolveAnnotationsMode(options.annotations);
|
|
1019
1248
|
const recommended = await resolveRecommendedOption(options.config);
|
|
1020
|
-
const dataPath =
|
|
1249
|
+
const dataPath = path6.resolve(process.cwd(), options.data);
|
|
1021
1250
|
let dataRaw;
|
|
1022
1251
|
try {
|
|
1023
1252
|
dataRaw = await readFile(dataPath, "utf8");
|
|
@@ -1053,7 +1282,7 @@ program.command("audit").description("Analyze schema health and report issues").
|
|
|
1053
1282
|
}
|
|
1054
1283
|
let manifest;
|
|
1055
1284
|
if (options.manifest) {
|
|
1056
|
-
const manifestPath =
|
|
1285
|
+
const manifestPath = path6.resolve(process.cwd(), options.manifest);
|
|
1057
1286
|
let manifestRaw;
|
|
1058
1287
|
try {
|
|
1059
1288
|
manifestRaw = await readFile(manifestPath, "utf8");
|
|
@@ -1087,7 +1316,7 @@ program.command("audit").description("Analyze schema health and report issues").
|
|
|
1087
1316
|
return;
|
|
1088
1317
|
}
|
|
1089
1318
|
}
|
|
1090
|
-
const requiredRoutes = options.scan ? await scanRoutes({ rootDir:
|
|
1319
|
+
const requiredRoutes = options.scan ? await scanRoutes({ rootDir: path6.resolve(process.cwd(), options.root ?? ".") }) : [];
|
|
1091
1320
|
if (options.scan && requiredRoutes.length === 0) {
|
|
1092
1321
|
console.error("No routes found during scan.");
|
|
1093
1322
|
}
|
|
@@ -1113,7 +1342,7 @@ program.command("collect").description("Collect JSON-LD blocks from built HTML o
|
|
|
1113
1342
|
).action(async (options) => {
|
|
1114
1343
|
const start = Date.now();
|
|
1115
1344
|
const format = resolveCollectOutputFormat(options.format);
|
|
1116
|
-
const rootDir =
|
|
1345
|
+
const rootDir = path6.resolve(process.cwd(), options.root ?? ".");
|
|
1117
1346
|
const check = options.check ?? false;
|
|
1118
1347
|
const requestedRoutes = normalizeRouteFilter(options.routes ?? []);
|
|
1119
1348
|
const strictRoutes = options.strictRoutes ?? false;
|
|
@@ -1150,7 +1379,7 @@ program.command("collect").description("Collect JSON-LD blocks from built HTML o
|
|
|
1150
1379
|
}
|
|
1151
1380
|
let driftDetected = false;
|
|
1152
1381
|
if (check) {
|
|
1153
|
-
const existingPath =
|
|
1382
|
+
const existingPath = path6.resolve(process.cwd(), options.data);
|
|
1154
1383
|
let existingRaw;
|
|
1155
1384
|
try {
|
|
1156
1385
|
existingRaw = await readFile(existingPath, "utf8");
|
|
@@ -1195,9 +1424,9 @@ program.command("collect").description("Collect JSON-LD blocks from built HTML o
|
|
|
1195
1424
|
}
|
|
1196
1425
|
const content = formatCollectOutput(collected.data, format);
|
|
1197
1426
|
if (options.output) {
|
|
1198
|
-
const resolvedPath =
|
|
1427
|
+
const resolvedPath = path6.resolve(process.cwd(), options.output);
|
|
1199
1428
|
try {
|
|
1200
|
-
await mkdir(
|
|
1429
|
+
await mkdir(path6.dirname(resolvedPath), { recursive: true });
|
|
1201
1430
|
await writeFile(resolvedPath, `${content}
|
|
1202
1431
|
`, "utf8");
|
|
1203
1432
|
console.error(`Collected data written to ${resolvedPath}`);
|
|
@@ -1225,6 +1454,65 @@ program.command("collect").description("Collect JSON-LD blocks from built HTML o
|
|
|
1225
1454
|
});
|
|
1226
1455
|
process.exit(driftDetected ? 1 : 0);
|
|
1227
1456
|
});
|
|
1457
|
+
program.command("scaffold").description("Generate schema stubs for routes without schema (dry-run by default)").option(
|
|
1458
|
+
"-m, --manifest <path>",
|
|
1459
|
+
"Path to manifest JSON",
|
|
1460
|
+
"schema-sentry.manifest.json"
|
|
1461
|
+
).option(
|
|
1462
|
+
"-d, --data <path>",
|
|
1463
|
+
"Path to schema data JSON",
|
|
1464
|
+
"schema-sentry.data.json"
|
|
1465
|
+
).option("--root <path>", "Project root for scanning", ".").option("--write", "Apply changes (default is dry-run)").option("-f, --force", "Skip confirmation prompts").action(async (options) => {
|
|
1466
|
+
const start = Date.now();
|
|
1467
|
+
const manifestPath = path6.resolve(process.cwd(), options.manifest);
|
|
1468
|
+
const dataPath = path6.resolve(process.cwd(), options.data);
|
|
1469
|
+
const rootDir = path6.resolve(process.cwd(), options.root ?? ".");
|
|
1470
|
+
const dryRun = !(options.write ?? false);
|
|
1471
|
+
const force = options.force ?? false;
|
|
1472
|
+
const result = await scaffoldSchema({
|
|
1473
|
+
manifestPath,
|
|
1474
|
+
dataPath,
|
|
1475
|
+
rootDir,
|
|
1476
|
+
dryRun,
|
|
1477
|
+
force
|
|
1478
|
+
});
|
|
1479
|
+
console.error(formatScaffoldPreview(result));
|
|
1480
|
+
if (!result.wouldUpdate) {
|
|
1481
|
+
process.exit(0);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (dryRun) {
|
|
1485
|
+
console.error("\nDry run complete. Use --write to apply changes.");
|
|
1486
|
+
process.exit(0);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (!force) {
|
|
1490
|
+
console.error("\nScaffolding will update:");
|
|
1491
|
+
console.error(` - ${manifestPath}`);
|
|
1492
|
+
console.error(` - ${dataPath}`);
|
|
1493
|
+
console.error("\nUse --force to skip this confirmation.");
|
|
1494
|
+
}
|
|
1495
|
+
try {
|
|
1496
|
+
await applyScaffold(result, {
|
|
1497
|
+
manifestPath,
|
|
1498
|
+
dataPath,
|
|
1499
|
+
rootDir,
|
|
1500
|
+
dryRun,
|
|
1501
|
+
force
|
|
1502
|
+
});
|
|
1503
|
+
console.error(`
|
|
1504
|
+
Scaffold complete in ${Date.now() - start}ms`);
|
|
1505
|
+
process.exit(0);
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1508
|
+
printCliError(
|
|
1509
|
+
"scaffold.apply_failed",
|
|
1510
|
+
`Failed to apply scaffold: ${message}`,
|
|
1511
|
+
"Check file permissions or disk space."
|
|
1512
|
+
);
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1228
1516
|
function isManifest(value) {
|
|
1229
1517
|
if (!value || typeof value !== "object") {
|
|
1230
1518
|
return false;
|
|
@@ -1298,13 +1586,13 @@ function formatReportOutput(report, format, title) {
|
|
|
1298
1586
|
if (format === "html") {
|
|
1299
1587
|
return renderHtmlReport(report, { title });
|
|
1300
1588
|
}
|
|
1301
|
-
return
|
|
1589
|
+
return stableStringify4(report);
|
|
1302
1590
|
}
|
|
1303
1591
|
function formatCollectOutput(data, format) {
|
|
1304
1592
|
if (format === "json") {
|
|
1305
|
-
return
|
|
1593
|
+
return stableStringify4(data);
|
|
1306
1594
|
}
|
|
1307
|
-
return
|
|
1595
|
+
return stableStringify4(data);
|
|
1308
1596
|
}
|
|
1309
1597
|
async function emitReport(options) {
|
|
1310
1598
|
const { report, format, outputPath, title } = options;
|
|
@@ -1313,9 +1601,9 @@ async function emitReport(options) {
|
|
|
1313
1601
|
console.log(content);
|
|
1314
1602
|
return;
|
|
1315
1603
|
}
|
|
1316
|
-
const resolvedPath =
|
|
1604
|
+
const resolvedPath = path6.resolve(process.cwd(), outputPath);
|
|
1317
1605
|
try {
|
|
1318
|
-
await mkdir(
|
|
1606
|
+
await mkdir(path6.dirname(resolvedPath), { recursive: true });
|
|
1319
1607
|
await writeFile(resolvedPath, content, "utf8");
|
|
1320
1608
|
console.error(`Report written to ${resolvedPath}`);
|
|
1321
1609
|
} catch (error) {
|
|
@@ -1335,7 +1623,7 @@ function emitAnnotations(report, mode, commandLabel) {
|
|
|
1335
1623
|
}
|
|
1336
1624
|
function printCliError(code, message, suggestion) {
|
|
1337
1625
|
console.error(
|
|
1338
|
-
|
|
1626
|
+
stableStringify4({
|
|
1339
1627
|
ok: false,
|
|
1340
1628
|
errors: [
|
|
1341
1629
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schemasentry/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI for Schema Sentry validation and reporting.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
|
-
"@schemasentry/core": "0.
|
|
36
|
+
"@schemasentry/core": "0.5.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
|