@jay-framework/data-files 0.24.1 → 0.24.3
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/dist/index.d.ts +1 -4
- package/dist/index.js +3 -221
- package/dist/parse-data-BfLjCqSm.js +107 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +127 -0
- package/package.json +11 -10
package/dist/index.d.ts
CHANGED
|
@@ -123,7 +123,4 @@ declare const generateDataPagesContract: _jay_framework_fullstack_component.Dyna
|
|
|
123
123
|
declare const generateDataListContract: _jay_framework_fullstack_component.DynamicContractGenerator<[]>;
|
|
124
124
|
declare const generateDataItemContract: _jay_framework_fullstack_component.DynamicContractGenerator<[]>;
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
declare function generateSchemaCommand(args: string[]): Promise<void>;
|
|
128
|
-
|
|
129
|
-
export { buildSlugIndex, clearFileCache, clearParseCache, clearSchemaCache, dataItem, dataList, dataPages, generateDataItemContract, generateDataListContract, generateDataPagesContract, generateSchema, generateSchemaCommand, loadSchema, parseDataFile, resolveReferences };
|
|
126
|
+
export { buildSlugIndex, clearFileCache, clearParseCache, clearSchemaCache, dataItem, dataList, dataPages, generateDataItemContract, generateDataListContract, generateDataPagesContract, loadSchema, parseDataFile, resolveReferences };
|
package/dist/index.js
CHANGED
|
@@ -1,105 +1,9 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
1
|
import { makeJayStackComponent, notFound, phaseOutput, makeContractGenerator } from "@jay-framework/fullstack-component";
|
|
2
|
+
import { F as FileCache, p as parseDataFile, b as buildSlugIndex } from "./parse-data-BfLjCqSm.js";
|
|
3
|
+
import { c } from "./parse-data-BfLjCqSm.js";
|
|
5
4
|
import fs from "node:fs/promises";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
import yaml from "js-yaml";
|
|
8
|
-
const CACHE_TTL_MS = 1e4;
|
|
9
|
-
class FileCache {
|
|
10
|
-
constructor() {
|
|
11
|
-
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
12
|
-
}
|
|
13
|
-
async get(filePath, loader) {
|
|
14
|
-
const cached = this.entries.get(filePath);
|
|
15
|
-
if (cached) {
|
|
16
|
-
const now = Date.now();
|
|
17
|
-
if (now - cached.checkedAt < CACHE_TTL_MS) return cached.value;
|
|
18
|
-
try {
|
|
19
|
-
const stat2 = await fs.stat(filePath);
|
|
20
|
-
cached.checkedAt = now;
|
|
21
|
-
if (stat2.mtimeMs === cached.mtime) return cached.value;
|
|
22
|
-
} catch {
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const value = await loader();
|
|
26
|
-
const stat = await fs.stat(filePath);
|
|
27
|
-
this.entries.set(filePath, { value, mtime: stat.mtimeMs, checkedAt: Date.now() });
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
clear() {
|
|
31
|
-
this.entries.clear();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const cache$1 = new FileCache();
|
|
35
|
-
async function parseDataFile(filePath) {
|
|
36
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
37
|
-
const supportedExtensions = [".csv", ".yaml", ".yml", ".json", ".jsonl"];
|
|
38
|
-
if (!supportedExtensions.includes(ext)) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`Unsupported data file format "${ext}". Supported: .csv, .yaml, .yml, .json, .jsonl`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
return cache$1.get(filePath, async () => {
|
|
44
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
45
|
-
switch (ext) {
|
|
46
|
-
case ".csv":
|
|
47
|
-
return parseCsv(content);
|
|
48
|
-
case ".yaml":
|
|
49
|
-
case ".yml":
|
|
50
|
-
return parseYaml(content);
|
|
51
|
-
case ".json":
|
|
52
|
-
return parseJson(content);
|
|
53
|
-
case ".jsonl":
|
|
54
|
-
return parseJsonl(content);
|
|
55
|
-
default:
|
|
56
|
-
throw new Error("Unreachable");
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function parseCsv(content) {
|
|
61
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
62
|
-
if (lines.length === 0) return [];
|
|
63
|
-
const headers = lines[0].split(",").map((h) => h.trim());
|
|
64
|
-
return lines.slice(1).map((line) => {
|
|
65
|
-
const values = line.split(",").map((v) => v.trim());
|
|
66
|
-
const row = {};
|
|
67
|
-
for (let i = 0; i < headers.length; i++) {
|
|
68
|
-
row[headers[i]] = values[i] ?? "";
|
|
69
|
-
}
|
|
70
|
-
return row;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function parseYaml(content) {
|
|
74
|
-
const data = yaml.load(content);
|
|
75
|
-
if (!Array.isArray(data)) {
|
|
76
|
-
throw new Error("YAML data file must be an array of objects");
|
|
77
|
-
}
|
|
78
|
-
return data;
|
|
79
|
-
}
|
|
80
|
-
function parseJson(content) {
|
|
81
|
-
const data = JSON.parse(content);
|
|
82
|
-
if (!Array.isArray(data)) {
|
|
83
|
-
throw new Error("JSON data file must be an array of objects");
|
|
84
|
-
}
|
|
85
|
-
return data;
|
|
86
|
-
}
|
|
87
|
-
function parseJsonl(content) {
|
|
88
|
-
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
89
|
-
}
|
|
90
|
-
function clearParseCache() {
|
|
91
|
-
cache$1.clear();
|
|
92
|
-
}
|
|
93
|
-
function buildSlugIndex(rows, slugField) {
|
|
94
|
-
const index = /* @__PURE__ */ new Map();
|
|
95
|
-
for (const row of rows) {
|
|
96
|
-
const slug = String(row[slugField] ?? "");
|
|
97
|
-
if (slug) {
|
|
98
|
-
index.set(slug, row);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return index;
|
|
102
|
-
}
|
|
103
7
|
const MAX_ROWS = 1e4;
|
|
104
8
|
const cache = new FileCache();
|
|
105
9
|
async function loadSchema(contentDir) {
|
|
@@ -413,130 +317,10 @@ const generateDataItemContract = makeContractGenerator().generateWith(async () =
|
|
|
413
317
|
})
|
|
414
318
|
);
|
|
415
319
|
});
|
|
416
|
-
function inferType(value) {
|
|
417
|
-
if (value === null || value === void 0) return "string";
|
|
418
|
-
if (typeof value === "number") return "number";
|
|
419
|
-
if (typeof value === "boolean") return "boolean";
|
|
420
|
-
if (typeof value === "string") {
|
|
421
|
-
if (value.startsWith("<")) return "html-string";
|
|
422
|
-
return "string";
|
|
423
|
-
}
|
|
424
|
-
return "string";
|
|
425
|
-
}
|
|
426
|
-
function inferTags(rows) {
|
|
427
|
-
if (rows.length === 0) return [];
|
|
428
|
-
const firstRow = rows[0];
|
|
429
|
-
const tags = [];
|
|
430
|
-
for (const [key, value] of Object.entries(firstRow)) {
|
|
431
|
-
if (Array.isArray(value)) {
|
|
432
|
-
const itemTags = value.length > 0 && typeof value[0] === "object" && value[0] !== null ? inferTags(value) : [];
|
|
433
|
-
const trackBy = itemTags.find((t) => t.tag === "id" || t.tag === "slug")?.tag;
|
|
434
|
-
tags.push({
|
|
435
|
-
tag: key,
|
|
436
|
-
type: "sub-contract",
|
|
437
|
-
repeated: true,
|
|
438
|
-
trackBy: trackBy || (itemTags.length > 0 ? itemTags[0].tag : "id"),
|
|
439
|
-
tags: itemTags
|
|
440
|
-
});
|
|
441
|
-
} else if (typeof value === "object" && value !== null) {
|
|
442
|
-
tags.push({
|
|
443
|
-
tag: key,
|
|
444
|
-
type: "sub-contract",
|
|
445
|
-
tags: inferTags([value])
|
|
446
|
-
});
|
|
447
|
-
} else {
|
|
448
|
-
tags.push({
|
|
449
|
-
tag: key,
|
|
450
|
-
type: "data",
|
|
451
|
-
dataType: inferType(value)
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return tags;
|
|
456
|
-
}
|
|
457
|
-
function pickSlugField(tags) {
|
|
458
|
-
const candidates = ["slug", "id", "key", "name"];
|
|
459
|
-
for (const candidate of candidates) {
|
|
460
|
-
if (tags.find((t) => t.tag === candidate && t.type === "data")) {
|
|
461
|
-
return candidate;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return tags.find((t) => t.type === "data")?.tag;
|
|
465
|
-
}
|
|
466
|
-
function tagToYaml(tag, indent) {
|
|
467
|
-
const pad = " ".repeat(indent);
|
|
468
|
-
const lines = [];
|
|
469
|
-
lines.push(`${pad}- tag: ${tag.tag}`);
|
|
470
|
-
lines.push(`${pad} type: ${tag.type}`);
|
|
471
|
-
if (tag.dataType) lines.push(`${pad} dataType: ${tag.dataType}`);
|
|
472
|
-
if (tag.isSlug) {
|
|
473
|
-
lines.push(`${pad} meta:`);
|
|
474
|
-
lines.push(`${pad} slug: "true"`);
|
|
475
|
-
}
|
|
476
|
-
if (tag.repeated) lines.push(`${pad} repeated: true`);
|
|
477
|
-
if (tag.trackBy) lines.push(`${pad} trackBy: ${tag.trackBy}`);
|
|
478
|
-
if (tag.tags && tag.tags.length > 0) {
|
|
479
|
-
lines.push(`${pad} tags:`);
|
|
480
|
-
for (const child of tag.tags) {
|
|
481
|
-
lines.push(tagToYaml(child, indent + 2));
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return lines.join("\n");
|
|
485
|
-
}
|
|
486
|
-
async function generateSchema(contentDir) {
|
|
487
|
-
const files = await fs.readdir(contentDir);
|
|
488
|
-
const dataFile = files.find(
|
|
489
|
-
(f) => f.endsWith(".csv") || f.endsWith(".yaml") || f.endsWith(".yml") || f.endsWith(".json") || f.endsWith(".jsonl")
|
|
490
|
-
);
|
|
491
|
-
if (!dataFile) {
|
|
492
|
-
throw new Error(`No data file found in "${contentDir}"`);
|
|
493
|
-
}
|
|
494
|
-
const rows = await parseDataFile(path.join(contentDir, dataFile));
|
|
495
|
-
if (rows.length === 0) {
|
|
496
|
-
throw new Error(`Data file "${dataFile}" is empty`);
|
|
497
|
-
}
|
|
498
|
-
const tags = inferTags(rows);
|
|
499
|
-
const slugField = pickSlugField(tags);
|
|
500
|
-
if (slugField) {
|
|
501
|
-
const slugTag = tags.find((t) => t.tag === slugField);
|
|
502
|
-
if (slugTag) slugTag.isSlug = true;
|
|
503
|
-
}
|
|
504
|
-
const dirName = path.basename(contentDir);
|
|
505
|
-
const lines = [
|
|
506
|
-
`name: ${dirName}`,
|
|
507
|
-
`description: Auto-generated schema for ${dirName}`,
|
|
508
|
-
"",
|
|
509
|
-
"tags:",
|
|
510
|
-
...tags.map((t) => tagToYaml(t, 1))
|
|
511
|
-
];
|
|
512
|
-
return lines.join("\n") + "\n";
|
|
513
|
-
}
|
|
514
|
-
async function generateSchemaCommand(args) {
|
|
515
|
-
const contentDir = args[0];
|
|
516
|
-
if (!contentDir) {
|
|
517
|
-
console.error(
|
|
518
|
-
"Usage: jay-stack run data-files/generate-schema <content-dir>\nExample: jay-stack run data-files/generate-schema content/team"
|
|
519
|
-
);
|
|
520
|
-
process.exit(1);
|
|
521
|
-
}
|
|
522
|
-
const yaml2 = await generateSchema(contentDir);
|
|
523
|
-
const dirName = path.basename(contentDir);
|
|
524
|
-
const outputPath = path.join(contentDir, `${dirName}.jay-contract`);
|
|
525
|
-
try {
|
|
526
|
-
await fs.access(outputPath);
|
|
527
|
-
console.error(`Schema file already exists: ${outputPath}
|
|
528
|
-
Delete it first to regenerate.`);
|
|
529
|
-
process.exit(1);
|
|
530
|
-
} catch {
|
|
531
|
-
}
|
|
532
|
-
await fs.writeFile(outputPath, yaml2, "utf-8");
|
|
533
|
-
console.log(`Generated schema: ${outputPath}`);
|
|
534
|
-
console.log("Review and refine the schema — check slug field, add descriptions, define links.");
|
|
535
|
-
}
|
|
536
320
|
export {
|
|
537
321
|
buildSlugIndex,
|
|
538
322
|
clearFileCache,
|
|
539
|
-
clearParseCache,
|
|
323
|
+
c as clearParseCache,
|
|
540
324
|
clearSchemaCache,
|
|
541
325
|
dataItem,
|
|
542
326
|
dataList,
|
|
@@ -544,8 +328,6 @@ export {
|
|
|
544
328
|
generateDataItemContract,
|
|
545
329
|
generateDataListContract,
|
|
546
330
|
generateDataPagesContract,
|
|
547
|
-
generateSchema,
|
|
548
|
-
generateSchemaCommand,
|
|
549
331
|
loadSchema,
|
|
550
332
|
parseDataFile,
|
|
551
333
|
resolveReferences
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
const CACHE_TTL_MS = 1e4;
|
|
8
|
+
class FileCache {
|
|
9
|
+
constructor() {
|
|
10
|
+
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
11
|
+
}
|
|
12
|
+
async get(filePath, loader) {
|
|
13
|
+
const cached = this.entries.get(filePath);
|
|
14
|
+
if (cached) {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
if (now - cached.checkedAt < CACHE_TTL_MS) return cached.value;
|
|
17
|
+
try {
|
|
18
|
+
const stat2 = await fs.stat(filePath);
|
|
19
|
+
cached.checkedAt = now;
|
|
20
|
+
if (stat2.mtimeMs === cached.mtime) return cached.value;
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const value = await loader();
|
|
25
|
+
const stat = await fs.stat(filePath);
|
|
26
|
+
this.entries.set(filePath, { value, mtime: stat.mtimeMs, checkedAt: Date.now() });
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
clear() {
|
|
30
|
+
this.entries.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const cache = new FileCache();
|
|
34
|
+
async function parseDataFile(filePath) {
|
|
35
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
36
|
+
const supportedExtensions = [".csv", ".yaml", ".yml", ".json", ".jsonl"];
|
|
37
|
+
if (!supportedExtensions.includes(ext)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Unsupported data file format "${ext}". Supported: .csv, .yaml, .yml, .json, .jsonl`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return cache.get(filePath, async () => {
|
|
43
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
44
|
+
switch (ext) {
|
|
45
|
+
case ".csv":
|
|
46
|
+
return parseCsv(content);
|
|
47
|
+
case ".yaml":
|
|
48
|
+
case ".yml":
|
|
49
|
+
return parseYaml(content);
|
|
50
|
+
case ".json":
|
|
51
|
+
return parseJson(content);
|
|
52
|
+
case ".jsonl":
|
|
53
|
+
return parseJsonl(content);
|
|
54
|
+
default:
|
|
55
|
+
throw new Error("Unreachable");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function parseCsv(content) {
|
|
60
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
61
|
+
if (lines.length === 0) return [];
|
|
62
|
+
const headers = lines[0].split(",").map((h) => h.trim());
|
|
63
|
+
return lines.slice(1).map((line) => {
|
|
64
|
+
const values = line.split(",").map((v) => v.trim());
|
|
65
|
+
const row = {};
|
|
66
|
+
for (let i = 0; i < headers.length; i++) {
|
|
67
|
+
row[headers[i]] = values[i] ?? "";
|
|
68
|
+
}
|
|
69
|
+
return row;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function parseYaml(content) {
|
|
73
|
+
const data = yaml.load(content);
|
|
74
|
+
if (!Array.isArray(data)) {
|
|
75
|
+
throw new Error("YAML data file must be an array of objects");
|
|
76
|
+
}
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
function parseJson(content) {
|
|
80
|
+
const data = JSON.parse(content);
|
|
81
|
+
if (!Array.isArray(data)) {
|
|
82
|
+
throw new Error("JSON data file must be an array of objects");
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
function parseJsonl(content) {
|
|
87
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
88
|
+
}
|
|
89
|
+
function clearParseCache() {
|
|
90
|
+
cache.clear();
|
|
91
|
+
}
|
|
92
|
+
function buildSlugIndex(rows, slugField) {
|
|
93
|
+
const index = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
const slug = String(row[slugField] ?? "");
|
|
96
|
+
if (slug) {
|
|
97
|
+
index.set(slug, row);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return index;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
FileCache as F,
|
|
104
|
+
buildSlugIndex as b,
|
|
105
|
+
clearParseCache as c,
|
|
106
|
+
parseDataFile as p
|
|
107
|
+
};
|
package/dist/tools.d.ts
ADDED
package/dist/tools.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { p as parseDataFile } from "./parse-data-BfLjCqSm.js";
|
|
4
|
+
function inferType(value) {
|
|
5
|
+
if (value === null || value === void 0) return "string";
|
|
6
|
+
if (typeof value === "number") return "number";
|
|
7
|
+
if (typeof value === "boolean") return "boolean";
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
if (value.startsWith("<")) return "html-string";
|
|
10
|
+
return "string";
|
|
11
|
+
}
|
|
12
|
+
return "string";
|
|
13
|
+
}
|
|
14
|
+
function inferTags(rows) {
|
|
15
|
+
if (rows.length === 0) return [];
|
|
16
|
+
const firstRow = rows[0];
|
|
17
|
+
const tags = [];
|
|
18
|
+
for (const [key, value] of Object.entries(firstRow)) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const itemTags = value.length > 0 && typeof value[0] === "object" && value[0] !== null ? inferTags(value) : [];
|
|
21
|
+
const trackBy = itemTags.find((t) => t.tag === "id" || t.tag === "slug")?.tag;
|
|
22
|
+
tags.push({
|
|
23
|
+
tag: key,
|
|
24
|
+
type: "sub-contract",
|
|
25
|
+
repeated: true,
|
|
26
|
+
trackBy: trackBy || (itemTags.length > 0 ? itemTags[0].tag : "id"),
|
|
27
|
+
tags: itemTags
|
|
28
|
+
});
|
|
29
|
+
} else if (typeof value === "object" && value !== null) {
|
|
30
|
+
tags.push({
|
|
31
|
+
tag: key,
|
|
32
|
+
type: "sub-contract",
|
|
33
|
+
tags: inferTags([value])
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
tags.push({
|
|
37
|
+
tag: key,
|
|
38
|
+
type: "data",
|
|
39
|
+
dataType: inferType(value)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return tags;
|
|
44
|
+
}
|
|
45
|
+
function pickSlugField(tags) {
|
|
46
|
+
const candidates = ["slug", "id", "key", "name"];
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
if (tags.find((t) => t.tag === candidate && t.type === "data")) {
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return tags.find((t) => t.type === "data")?.tag;
|
|
53
|
+
}
|
|
54
|
+
function tagToYaml(tag, indent) {
|
|
55
|
+
const pad = " ".repeat(indent);
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`${pad}- tag: ${tag.tag}`);
|
|
58
|
+
lines.push(`${pad} type: ${tag.type}`);
|
|
59
|
+
if (tag.dataType) lines.push(`${pad} dataType: ${tag.dataType}`);
|
|
60
|
+
if (tag.isSlug) {
|
|
61
|
+
lines.push(`${pad} meta:`);
|
|
62
|
+
lines.push(`${pad} slug: "true"`);
|
|
63
|
+
}
|
|
64
|
+
if (tag.repeated) lines.push(`${pad} repeated: true`);
|
|
65
|
+
if (tag.trackBy) lines.push(`${pad} trackBy: ${tag.trackBy}`);
|
|
66
|
+
if (tag.tags && tag.tags.length > 0) {
|
|
67
|
+
lines.push(`${pad} tags:`);
|
|
68
|
+
for (const child of tag.tags) {
|
|
69
|
+
lines.push(tagToYaml(child, indent + 2));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
async function generateSchema(contentDir) {
|
|
75
|
+
const files = await fs.readdir(contentDir);
|
|
76
|
+
const dataFile = files.find(
|
|
77
|
+
(f) => f.endsWith(".csv") || f.endsWith(".yaml") || f.endsWith(".yml") || f.endsWith(".json") || f.endsWith(".jsonl")
|
|
78
|
+
);
|
|
79
|
+
if (!dataFile) {
|
|
80
|
+
throw new Error(`No data file found in "${contentDir}"`);
|
|
81
|
+
}
|
|
82
|
+
const rows = await parseDataFile(path.join(contentDir, dataFile));
|
|
83
|
+
if (rows.length === 0) {
|
|
84
|
+
throw new Error(`Data file "${dataFile}" is empty`);
|
|
85
|
+
}
|
|
86
|
+
const tags = inferTags(rows);
|
|
87
|
+
const slugField = pickSlugField(tags);
|
|
88
|
+
if (slugField) {
|
|
89
|
+
const slugTag = tags.find((t) => t.tag === slugField);
|
|
90
|
+
if (slugTag) slugTag.isSlug = true;
|
|
91
|
+
}
|
|
92
|
+
const dirName = path.basename(contentDir);
|
|
93
|
+
const lines = [
|
|
94
|
+
`name: ${dirName}`,
|
|
95
|
+
`description: Auto-generated schema for ${dirName}`,
|
|
96
|
+
"",
|
|
97
|
+
"tags:",
|
|
98
|
+
...tags.map((t) => tagToYaml(t, 1))
|
|
99
|
+
];
|
|
100
|
+
return lines.join("\n") + "\n";
|
|
101
|
+
}
|
|
102
|
+
async function generateSchemaCommand(args) {
|
|
103
|
+
const contentDir = args[0];
|
|
104
|
+
if (!contentDir) {
|
|
105
|
+
console.error(
|
|
106
|
+
"Usage: jay-stack run data-files/generate-schema <content-dir>\nExample: jay-stack run data-files/generate-schema content/team"
|
|
107
|
+
);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const yaml = await generateSchema(contentDir);
|
|
111
|
+
const dirName = path.basename(contentDir);
|
|
112
|
+
const outputPath = path.join(contentDir, `${dirName}.jay-contract`);
|
|
113
|
+
try {
|
|
114
|
+
await fs.access(outputPath);
|
|
115
|
+
console.error(`Schema file already exists: ${outputPath}
|
|
116
|
+
Delete it first to regenerate.`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
await fs.writeFile(outputPath, yaml, "utf-8");
|
|
121
|
+
console.log(`Generated schema: ${outputPath}`);
|
|
122
|
+
console.log("Review and refine the schema — check slug field, add descriptions, define links.");
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
generateSchema,
|
|
126
|
+
generateSchemaCommand
|
|
127
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/data-files",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Data files plugin for Jay Framework — CSV, YAML, JSON, JSONL data sources with list views, item views, and per-item pages",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": "./dist/index.js",
|
|
15
15
|
"./client": "./dist/index.client.js",
|
|
16
|
+
"./tools": "./dist/tools.js",
|
|
16
17
|
"./plugin.yaml": "./plugin.yaml"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "npm run build:server && npm run build:client && npm run build:types",
|
|
20
21
|
"build:client": "vite build",
|
|
21
22
|
"build:server": "vite build --ssr",
|
|
22
|
-
"build:types": "tsup lib/index.ts --dts-only --format esm",
|
|
23
|
+
"build:types": "tsup lib/index.ts lib/tools.ts --dts-only --format esm",
|
|
23
24
|
"build:check-types": "tsc",
|
|
24
25
|
"validate": "jay-stack-cli validate-plugin",
|
|
25
26
|
"clean": "rimraf dist",
|
|
@@ -27,18 +28,18 @@
|
|
|
27
28
|
"test": "vitest run"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@jay-framework/component": "^0.24.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.24.
|
|
32
|
-
"@jay-framework/reactive": "^0.24.
|
|
33
|
-
"@jay-framework/runtime": "^0.24.
|
|
34
|
-
"@jay-framework/stack-client-runtime": "^0.24.
|
|
35
|
-
"@jay-framework/stack-server-runtime": "^0.24.
|
|
31
|
+
"@jay-framework/component": "^0.24.3",
|
|
32
|
+
"@jay-framework/fullstack-component": "^0.24.3",
|
|
33
|
+
"@jay-framework/reactive": "^0.24.3",
|
|
34
|
+
"@jay-framework/runtime": "^0.24.3",
|
|
35
|
+
"@jay-framework/stack-client-runtime": "^0.24.3",
|
|
36
|
+
"@jay-framework/stack-server-runtime": "^0.24.3",
|
|
36
37
|
"js-yaml": "^4.1.0",
|
|
37
38
|
"papaparse": "^5.4.1"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@jay-framework/dev-environment": "^0.24.
|
|
41
|
-
"@jay-framework/jay-stack-cli": "^0.24.
|
|
41
|
+
"@jay-framework/dev-environment": "^0.24.3",
|
|
42
|
+
"@jay-framework/jay-stack-cli": "^0.24.3",
|
|
42
43
|
"@types/js-yaml": "^4.0.9",
|
|
43
44
|
"@types/node": "^22.15.21",
|
|
44
45
|
"@types/papaparse": "^5.3.14",
|