@glossarist/concept-browser 0.7.114 → 0.7.116

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.
Files changed (103) hide show
  1. package/README.md +45 -2
  2. package/cli/index.mjs +10 -13
  3. package/cli/index.ts +9 -11
  4. package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
  5. package/package.json +18 -12
  6. package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
  7. package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
  8. package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
  9. package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
  10. package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
  11. package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
  12. package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
  13. package/scripts/bridge-to-astro.js +132 -0
  14. package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
  15. package/scripts/build-edges.js +242 -336
  16. package/scripts/build-edges.ts +572 -0
  17. package/scripts/build-scripts.mjs +22 -0
  18. package/scripts/check-syntax.js +81 -0
  19. package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
  20. package/scripts/doctor.js +278 -0
  21. package/scripts/extract-source-refs.js +16 -24
  22. package/scripts/extract-source-refs.ts +32 -0
  23. package/scripts/fetch-datasets.js +278 -0
  24. package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
  25. package/scripts/generate-404.js +10 -11
  26. package/scripts/generate-404.ts +15 -0
  27. package/scripts/generate-data.js +2220 -0
  28. package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
  29. package/scripts/generate-ontology-data.js +139 -0
  30. package/scripts/generate-ontology-schema.js +472 -0
  31. package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
  32. package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
  33. package/scripts/lib/concept-formats.ts +206 -0
  34. package/scripts/lib/concept-uri.ts +14 -0
  35. package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
  36. package/scripts/lib/harmonize-concept.ts +86 -0
  37. package/scripts/lib/jsonld-emitter.ts +169 -0
  38. package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
  39. package/scripts/lib/yaml-types.ts +262 -0
  40. package/scripts/load-site-config.js +83 -0
  41. package/scripts/migrate-v1-to-v3.js +113 -0
  42. package/scripts/normalize-yaml.js +96 -0
  43. package/scripts/process-about-pages.js +241 -0
  44. package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
  45. package/scripts/smoke.js +211 -0
  46. package/scripts/{smoke.mjs → smoke.ts} +4 -3
  47. package/scripts/sync-concept-model.js +63 -0
  48. package/scripts/validate-shacl.js +152 -0
  49. package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
  50. package/scripts/validate-yaml.js +132 -0
  51. package/scripts/validate-yaml.ts +144 -0
  52. package/src/__tests__/extract-source-refs.test.ts +2 -3
  53. package/src/__tests__/perf/serialization-perf.test.ts +5 -5
  54. package/src/__tests__/published-package-contract.test.ts +84 -0
  55. package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
  56. package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
  57. package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
  58. package/src/__tests__/scripts/build-cache.test.ts +1 -1
  59. package/src/__tests__/scripts/build-integration.test.ts +19 -19
  60. package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
  61. package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
  62. package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
  63. package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
  64. package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
  65. package/src/__tests__/scripts/version-turtle.test.ts +1 -1
  66. package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
  67. package/src/__tests__/scripts-syntax.test.ts +4 -4
  68. package/src/__tests__/use-rdf-document.test.ts +2 -3
  69. package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
  70. package/src/__tests__/validate-shacl.test.ts +3 -3
  71. package/src/adapters/wire/index.ts +69 -0
  72. package/src/data/ontology-schema.json +1336 -183
  73. package/src/utils/content-renderer.ts +7 -3
  74. package/src/utils/url-safety.ts +1 -5
  75. package/tsconfig.json +2 -2
  76. package/vite.config.ts +1 -1
  77. package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
  78. package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
  79. package/src/types/agents-version-turtle.d.ts +0 -27
  80. package/src/types/bibliography-turtle.d.ts +0 -12
  81. package/src/types/build-activity-turtle.d.ts +0 -16
  82. package/src/types/build-cache.d.ts +0 -20
  83. package/src/types/dataset-turtle.d.ts +0 -32
  84. package/src/types/normalize-yaml.d.ts +0 -16
  85. package/src/types/turtle-escape.d.ts +0 -6
  86. package/src/types/vocab-turtle.d.ts +0 -13
  87. /package/scripts/{doctor.mjs → doctor.ts} +0 -0
  88. /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
  89. /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
  90. /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
  91. /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
  92. /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
  93. /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
  94. /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
  95. /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
  96. /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
  97. /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
  98. /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
  99. /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
  100. /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
  101. /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
  102. /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
  103. /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * YAML author-format TypeScript interfaces.
3
+ *
4
+ * These types describe the YAML wire format that dataset authors write
5
+ * and that `scripts/generate-data.ts` reads. They are the author-facing
6
+ * contract — the complement to `src/adapters/jsonld-types.ts` (the
7
+ * browser-facing JSON-LD wire format).
8
+ *
9
+ * MECE boundary:
10
+ * - yaml-types.ts → INPUT to the build pipeline (what authors write)
11
+ * - jsonld-types.ts → OUTPUT of the build pipeline (what the browser reads)
12
+ * - model-bridge.ts → converts JSON-LD → glossarist-js Concept instances
13
+ *
14
+ * Model-driven: field names match the YAML keys authors actually write
15
+ * (snake_case for author-facing, camelCase for internal normalized form).
16
+ */
17
+
18
+ // ── Content blocks ─────────────────────────────────────────────────────────
19
+
20
+ export interface YamlContent {
21
+ content?: string;
22
+ }
23
+
24
+ // ── Terms / designations ──────────────────────────────────────────────────
25
+
26
+ export interface YamlTerm {
27
+ type?: string;
28
+ designation?: string;
29
+ normative_status?: string;
30
+ origin?: YamlOrigin;
31
+ grammar_info?: YamlGrammarInfo[];
32
+ english?: string[];
33
+ }
34
+
35
+ export interface YamlOrigin {
36
+ source?: string;
37
+ ref?: string;
38
+ version?: string;
39
+ }
40
+
41
+ export interface YamlGrammarInfo {
42
+ gender?: string;
43
+ number?: string;
44
+ partOfSpeech?: string;
45
+ noun?: boolean;
46
+ verb?: boolean;
47
+ adj?: boolean;
48
+ adverb?: boolean;
49
+ preposition?: boolean;
50
+ participle?: boolean;
51
+ }
52
+
53
+ // ── Sources ───────────────────────────────────────────────────────────────
54
+
55
+ export interface YamlSource {
56
+ origin?: YamlOrigin;
57
+ ref?: string;
58
+ link?: string;
59
+ relationship_type?: string;
60
+ status?: string;
61
+ title?: string;
62
+ type?: string;
63
+ }
64
+
65
+ // ── Dates ─────────────────────────────────────────────────────────────────
66
+
67
+ export interface YamlDate {
68
+ type?: string;
69
+ date?: string;
70
+ }
71
+
72
+ // ── Relations ─────────────────────────────────────────────────────────────
73
+
74
+ export interface YamlRelation {
75
+ type?: string;
76
+ target?: string | { ref?: string; uri?: string };
77
+ content?: string;
78
+ label?: string;
79
+ }
80
+
81
+ export interface YamlPartitiveMember {
82
+ ref?: string;
83
+ uri?: string;
84
+ presence?: string;
85
+ count?: string;
86
+ delimiting?: boolean;
87
+ multiplicity?: string;
88
+ certainty?: string;
89
+ designation?: string;
90
+ content?: string;
91
+ }
92
+
93
+ export interface YamlPartitiveRelation {
94
+ type?: string;
95
+ target?: string;
96
+ members?: YamlPartitiveMember[];
97
+ criterion?: string;
98
+ multiplicity?: string;
99
+ certainty?: string;
100
+ }
101
+
102
+ export interface YamlGenericRelation {
103
+ type?: string;
104
+ target?: string;
105
+ members?: (string | { ref?: string; uri?: string })[];
106
+ criterion?: string;
107
+ }
108
+
109
+ // ── Localization (per-language concept data) ──────────────────────────────
110
+
111
+ export interface YamlLocalization {
112
+ language_code?: string;
113
+ terms?: YamlTerm[];
114
+ definition?: YamlContent[];
115
+ notes?: YamlContent[];
116
+ examples?: YamlContent[];
117
+ annotations?: YamlContent[];
118
+ sources?: YamlSource[];
119
+ dates?: YamlDate[];
120
+ domain?: string[];
121
+ references?: YamlSource[];
122
+ entry_status?: string;
123
+ classification?: string;
124
+ review_type?: string;
125
+ review_date?: string;
126
+ review_decision_date?: string;
127
+ review_decision_event?: string;
128
+ review_status?: string;
129
+ review_decision?: string;
130
+ review_decision_notes?: string;
131
+ lineage_source_similarity?: number;
132
+ release?: string;
133
+ script?: string;
134
+ system?: string;
135
+ [key: string]: unknown;
136
+ }
137
+
138
+ // ── Harmonized concept (output of loadConceptFile) ────────────────────────
139
+ //
140
+ // After loadConceptFile(), the concept is normalized to this shape
141
+ // regardless of whether the source was simple or managed format.
142
+ // The `_`-prefixed fields are managed-concept metadata hoisted to top level.
143
+
144
+ export interface HarmonizedConcept {
145
+ termid: string;
146
+ id?: string;
147
+ uri?: string;
148
+ status?: string;
149
+ _related?: YamlRelation[];
150
+ _partitiveRelations?: YamlPartitiveRelation[];
151
+ _genericRelations?: YamlGenericRelation[];
152
+ _domains?: string[];
153
+ _dates?: YamlDate[];
154
+ _sources?: YamlSource[];
155
+ _status?: string;
156
+ _schemaVersion?: string;
157
+ _dateAccepted?: string;
158
+ [lang: string]: unknown;
159
+ }
160
+
161
+ // ── Managed concept YAML doc (multi-doc format) ───────────────────────────
162
+
163
+ export interface YamlManagedConceptDoc {
164
+ data?: {
165
+ identifier?: string | number;
166
+ language_code?: string;
167
+ domains?: string[];
168
+ [key: string]: unknown;
169
+ };
170
+ related?: YamlRelation[];
171
+ partitive_relations?: YamlPartitiveRelation[];
172
+ generic_relations?: YamlGenericRelation[];
173
+ dates?: YamlDate[];
174
+ sources?: YamlSource[];
175
+ status?: string;
176
+ schema_version?: string;
177
+ date_accepted?: string;
178
+ terms?: YamlTerm[];
179
+ definition?: YamlContent[];
180
+ notes?: YamlContent[];
181
+ examples?: YamlContent[];
182
+ [key: string]: unknown;
183
+ }
184
+
185
+ // ── Dataset manifest ──────────────────────────────────────────────────────
186
+
187
+ export interface YamlManifestSection {
188
+ id?: string;
189
+ slug?: string;
190
+ title?: string;
191
+ name?: string;
192
+ members?: string[];
193
+ children?: YamlManifestSection[];
194
+ ordering?: string;
195
+ }
196
+
197
+ export interface YamlManifest {
198
+ id?: string;
199
+ title?: string;
200
+ description?: string;
201
+ lastUpdated?: string;
202
+ sourceRepoUrl?: string;
203
+ publisher?: string;
204
+ contactPoint?: string;
205
+ sections?: YamlManifestSection[];
206
+ languageOrder?: string[];
207
+ ref?: Record<string, string>;
208
+ refAliases?: string[];
209
+ editionStatus?: string;
210
+ ordering?: string;
211
+ [key: string]: unknown;
212
+ }
213
+
214
+ // ── Bibliography ──────────────────────────────────────────────────────────
215
+
216
+ export interface YamlBibliographyEntry {
217
+ reference?: string;
218
+ title?: string;
219
+ link?: string;
220
+ author?: string;
221
+ date?: string;
222
+ publisher?: string;
223
+ type?: string;
224
+ [key: string]: unknown;
225
+ }
226
+
227
+ // ── News/content frontmatter ──────────────────────────────────────────────
228
+
229
+ export interface YamlNewsFrontmatter {
230
+ title?: string;
231
+ date?: string;
232
+ categories?: string;
233
+ [key: string]: unknown;
234
+ }
235
+
236
+ // ── Content page config ───────────────────────────────────────────────────
237
+
238
+ export interface YamlContentPage {
239
+ route?: string;
240
+ source?: string;
241
+ title?: string;
242
+ translations?: Record<string, { source?: string; title?: string }>;
243
+ [key: string]: unknown;
244
+ }
245
+
246
+ // ── SHACL constraint (ontology schema) ────────────────────────────────────
247
+
248
+ export interface ShaclConstraint {
249
+ path?: string | null;
250
+ datatype?: string | null;
251
+ class?: string | null;
252
+ valuesFrom?: string | null;
253
+ nodeKind?: string | null;
254
+ minCount?: number | null;
255
+ maxCount?: number | null;
256
+ in?: string[];
257
+ [key: string]: unknown;
258
+ }
259
+
260
+ // ── Prefix map (Turtle emission) ──────────────────────────────────────────
261
+
262
+ export type PrefixMap = Record<string, string>;
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/load-site-config.ts
4
+ import { readFileSync, existsSync } from "fs";
5
+ import { resolve, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import yaml from "js-yaml";
8
+ var __dirname = dirname(fileURLToPath(import.meta.url));
9
+ var projectRoot = resolve(__dirname, "..");
10
+ var SNAKE_TO_CAMEL = {
11
+ // Site-level
12
+ uri_base: "uriBase",
13
+ base_path: "basePath",
14
+ ui_languages: "uiLanguages",
15
+ dataset_groups: "datasetGroups",
16
+ dataset_translations: "datasetTranslations",
17
+ primary_color: "primaryColor",
18
+ dark_color: "darkColor",
19
+ owner_name: "ownerName",
20
+ owner_url: "ownerUrl",
21
+ powered_by: "poweredBy",
22
+ footer_nav: "footerNav",
23
+ footer_logo: "footerLogo",
24
+ news_dir: "newsDir",
25
+ // Dataset-level
26
+ gcr_package: "gcrPackage",
27
+ source_repo: "sourceRepo",
28
+ local_path: "localPath",
29
+ local_light: "localLight",
30
+ local_dark: "localDark",
31
+ uri_aliases: "uriAliases",
32
+ language_order: "languageOrder",
33
+ ref_aliases: "refAliases",
34
+ urn_aliases: "urnAliases",
35
+ existing_site_url: "existingSiteUrl",
36
+ edition_status: "editionStatus",
37
+ dataset_uri: "datasetUri",
38
+ schema_version: "schemaVersion"
39
+ };
40
+ function normalizeKey(key) {
41
+ if (typeof key !== "string") return key;
42
+ if (Object.prototype.hasOwnProperty.call(SNAKE_TO_CAMEL, key)) {
43
+ return SNAKE_TO_CAMEL[key];
44
+ }
45
+ if (key.includes("_") && /^[a-z][a-z0-9_]*$/.test(key)) {
46
+ return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
47
+ }
48
+ return key;
49
+ }
50
+ function normalizeObject(obj) {
51
+ if (Array.isArray(obj)) return obj.map(normalizeObject);
52
+ if (obj && typeof obj === "object") {
53
+ const out = {};
54
+ for (const [k, v] of Object.entries(obj)) {
55
+ out[normalizeKey(k)] = normalizeObject(v);
56
+ }
57
+ return out;
58
+ }
59
+ return obj;
60
+ }
61
+ function findConfigFile(args = []) {
62
+ if (process.env.SITE_CONFIG) {
63
+ return resolve(process.env.SITE_CONFIG);
64
+ }
65
+ for (const dir of [process.cwd(), projectRoot]) {
66
+ const p = resolve(dir, "site-config.yml");
67
+ if (existsSync(p)) return p;
68
+ }
69
+ throw new Error("No site config found. Set SITE_CONFIG or create site-config.yml");
70
+ }
71
+ function loadSiteConfig(args = []) {
72
+ const configPath = findConfigFile(args);
73
+ const raw = yaml.load(readFileSync(configPath, "utf-8"));
74
+ const config = normalizeObject(raw);
75
+ return { config, configPath };
76
+ }
77
+ function getProjectRoot() {
78
+ return projectRoot;
79
+ }
80
+ export {
81
+ getProjectRoot,
82
+ loadSiteConfig
83
+ };
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/migrate-v1-to-v3.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+ function isV1PartitiveRelation(rel) {
7
+ return rel && Array.isArray(rel.parts);
8
+ }
9
+ function migrateRelation(rel) {
10
+ if (!isV1PartitiveRelation(rel)) return null;
11
+ return {
12
+ comprehensive: rel.comprehensive,
13
+ completeness: rel.enumeration === "open" ? "partial" : rel.enumeration === "closed" || rel.enumeration == null ? "complete" : rel.enumeration,
14
+ partitives: (rel.parts ?? []).map((p) => ({
15
+ ref: p,
16
+ certainty: "confirmed"
17
+ })),
18
+ ...rel.content ? { content: rel.content } : {}
19
+ };
20
+ }
21
+ function migrateConceptData(data) {
22
+ if (!data?.partitive_relations?.length) return null;
23
+ const migrated = data.partitive_relations.map(migrateRelation).filter((r) => r !== null);
24
+ if (migrated.length === 0) return null;
25
+ return { ...data, partitive_relations: migrated };
26
+ }
27
+ function isAlreadyMigrated(content) {
28
+ return Boolean(content?.data?.partitive_relations?.some?.((rel) => Array.isArray(rel.partitives)));
29
+ }
30
+ function walkFiles(input) {
31
+ const stat = fs.statSync(input);
32
+ if (stat.isFile()) return [input];
33
+ const out = [];
34
+ for (const entry of fs.readdirSync(input, { withFileTypes: true })) {
35
+ const full = path.join(input, entry.name);
36
+ if (entry.isDirectory()) out.push(...walkFiles(full));
37
+ else if (entry.isFile() && /\.(ya?ml)$/i.test(entry.name)) out.push(full);
38
+ }
39
+ return out;
40
+ }
41
+ async function loadYamlModule() {
42
+ try {
43
+ return await import("js-yaml");
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ function outputPath(input, file, outRoot) {
49
+ const stat = fs.statSync(input);
50
+ if (stat.isFile()) return outRoot;
51
+ const rel = path.relative(input, file);
52
+ return path.join(outRoot, rel);
53
+ }
54
+ async function migrateFile(input, file, options, yaml) {
55
+ const raw = fs.readFileSync(file, "utf8");
56
+ const parsed = yaml ? yaml.load(raw) : JSON.parse(raw);
57
+ if (!parsed?.data) return { changed: false };
58
+ if (isAlreadyMigrated(parsed)) return { changed: false, skipped: true };
59
+ const migrated = migrateConceptData(parsed.data);
60
+ if (!migrated) return { changed: false };
61
+ parsed.data = { ...parsed.data, ...migrated };
62
+ const out = outputPath(input, file, options.out);
63
+ fs.mkdirSync(path.dirname(out), { recursive: true });
64
+ const outText = yaml ? yaml.dump(parsed) : JSON.stringify(parsed, null, 2);
65
+ if (!options.dryRun) fs.writeFileSync(out, outText);
66
+ return { changed: true, out, in: file };
67
+ }
68
+ async function main() {
69
+ const args = process.argv.slice(2);
70
+ const opts = { in: null, out: null, dryRun: false };
71
+ for (let i = 0; i < args.length; i++) {
72
+ const a = args[i];
73
+ if (a === "--in") opts.in = args[++i];
74
+ else if (a === "--out") opts.out = args[++i];
75
+ else if (a === "--dry-run") opts.dryRun = true;
76
+ else if (!opts.in) opts.in = a;
77
+ else if (!opts.out) opts.out = a;
78
+ }
79
+ if (!opts.in) {
80
+ console.error("Usage: node scripts/migrate-v1-to-v3.mjs --in <dir|file> [--out <dir|file>] [--dry-run]");
81
+ process.exit(1);
82
+ }
83
+ if (!opts.out) opts.out = opts.dryRun ? opts.in : `${opts.in}.v3`;
84
+ const yaml = await loadYamlModule();
85
+ if (!yaml) {
86
+ console.error("js-yaml is required; install via npm install js-yaml");
87
+ process.exit(1);
88
+ }
89
+ const files = walkFiles(opts.in);
90
+ let changed = 0, skipped = 0, total = 0;
91
+ for (const f of files) {
92
+ total++;
93
+ const res = await migrateFile(opts.in, f, opts, yaml);
94
+ if (res.changed) {
95
+ changed++;
96
+ console.log(`\u2713 ${res.in} \u2192 ${res.out}`);
97
+ } else if (res.skipped) {
98
+ skipped++;
99
+ }
100
+ }
101
+ console.log(`
102
+ ${changed} migrated, ${skipped} skipped (already v3), ${total - changed - skipped} unchanged.`);
103
+ if (opts.dryRun) console.log("(dry-run \u2014 no files written)");
104
+ }
105
+ if (import.meta.url === `file://${process.argv[1]}`) {
106
+ main();
107
+ }
108
+ export {
109
+ isAlreadyMigrated,
110
+ isV1PartitiveRelation,
111
+ migrateConceptData,
112
+ migrateRelation
113
+ };
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/normalize-yaml.ts
4
+ import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
5
+ import { join, relative, extname, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ var ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
8
+ var YAML_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
9
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
10
+ function walk(dir) {
11
+ const out = [];
12
+ for (const entry of readdirSync(dir)) {
13
+ if (SKIP_DIRS.has(entry)) continue;
14
+ const full = join(dir, entry);
15
+ const st = statSync(full);
16
+ if (st.isDirectory()) {
17
+ out.push(...walk(full));
18
+ } else if (YAML_EXTS.has(extname(entry))) {
19
+ out.push(full);
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+ function normalizeYaml({ root = ROOT, check = false, paths = [] } = {}) {
25
+ const searchDirs = paths.length > 0 ? paths.map((p) => join(root, p)) : [join(root, ".datasets")];
26
+ const files = [];
27
+ for (const dir of searchDirs) {
28
+ try {
29
+ statSync(dir);
30
+ } catch {
31
+ continue;
32
+ }
33
+ files.push(...walk(dir));
34
+ }
35
+ files.sort();
36
+ let checked = 0;
37
+ let nonNfc = 0;
38
+ const fixed = [];
39
+ for (const file of files) {
40
+ const original = readFileSync(file, "utf8");
41
+ checked++;
42
+ if (!isNfc(original)) {
43
+ nonNfc++;
44
+ if (!check) {
45
+ writeFileSync(file, original.normalize("NFC"), "utf8");
46
+ fixed.push(relative(root, file));
47
+ } else {
48
+ fixed.push(relative(root, file));
49
+ }
50
+ }
51
+ }
52
+ return { checked, nonNfc, fixed, check };
53
+ }
54
+ function isNfc(s) {
55
+ return s === s.normalize("NFC");
56
+ }
57
+ function main() {
58
+ const args = process.argv.slice(2);
59
+ const check = args.includes("--check");
60
+ const paths = args.filter((a) => !a.startsWith("-") && a !== "normalize");
61
+ const { checked, nonNfc, fixed } = normalizeYaml({ check, paths });
62
+ if (check) {
63
+ if (nonNfc === 0) {
64
+ process.stdout.write(`NFC OK: ${checked} file(s) checked, all normalized
65
+ `);
66
+ return;
67
+ }
68
+ process.stderr.write(`NFC check failed: ${nonNfc} of ${checked} file(s) are not NFC-normalized
69
+
70
+ `);
71
+ for (const f of fixed) {
72
+ process.stderr.write(` ${f}
73
+ `);
74
+ }
75
+ process.exit(1);
76
+ } else {
77
+ if (nonNfc === 0) {
78
+ process.stdout.write(`NFC OK: ${checked} file(s) checked, all already normalized
79
+ `);
80
+ return;
81
+ }
82
+ process.stdout.write(`Normalized ${nonNfc} of ${checked} file(s)
83
+ `);
84
+ for (const f of fixed) {
85
+ process.stdout.write(` ${f}
86
+ `);
87
+ }
88
+ }
89
+ }
90
+ var isDirectEntry = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
91
+ if (isDirectEntry) {
92
+ main();
93
+ }
94
+ export {
95
+ normalizeYaml
96
+ };