@jskit-ai/jskit-catalog 0.1.4

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/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@jskit-ai/jskit-catalog",
3
+ "version": "0.1.4",
4
+ "description": "Published metadata catalog for JSKIT package descriptors.",
5
+ "type": "module",
6
+ "files": [
7
+ "catalog",
8
+ "scripts"
9
+ ],
10
+ "exports": {
11
+ ".": "./catalog/packages.json",
12
+ "./packages": "./catalog/packages.json",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "catalog:build": "node ./scripts/build-catalog.mjs",
17
+ "test": "node --test"
18
+ },
19
+ "engines": {
20
+ "node": "20.x"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "keywords": [
26
+ "jskit",
27
+ "catalog",
28
+ "metadata",
29
+ "descriptors"
30
+ ]
31
+ }
@@ -0,0 +1,151 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import { access, readdir, readFile, writeFile, mkdir } from "node:fs/promises";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+
6
+ const SCRIPT_PATH = fileURLToPath(import.meta.url);
7
+ const PACKAGE_ROOT = path.resolve(path.dirname(SCRIPT_PATH), "..");
8
+ const DEFAULT_REPO_ROOT = path.resolve(PACKAGE_ROOT, "../..");
9
+
10
+ function parseInlineArg(name) {
11
+ const args = process.argv.slice(2);
12
+ const exactPrefix = `${name}=`;
13
+ for (let index = 0; index < args.length; index += 1) {
14
+ const candidate = String(args[index] || "").trim();
15
+ if (!candidate) {
16
+ continue;
17
+ }
18
+ if (candidate === name) {
19
+ const next = String(args[index + 1] || "").trim();
20
+ return next || "";
21
+ }
22
+ if (candidate.startsWith(exactPrefix)) {
23
+ return candidate.slice(exactPrefix.length).trim();
24
+ }
25
+ }
26
+ return "";
27
+ }
28
+
29
+ function toSortedUniqueStrings(values) {
30
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
31
+ }
32
+
33
+ async function fileExists(absolutePath) {
34
+ try {
35
+ await access(absolutePath);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function collectPackageRoots(packagesRoot) {
43
+ const directories = [];
44
+ const levelOne = await readdir(packagesRoot, { withFileTypes: true });
45
+
46
+ for (const entry of levelOne) {
47
+ if (!entry.isDirectory()) {
48
+ continue;
49
+ }
50
+ if (entry.name.startsWith(".") || entry.name.endsWith(".LEGACY")) {
51
+ continue;
52
+ }
53
+
54
+ const absolute = path.join(packagesRoot, entry.name);
55
+ const descriptorPath = path.join(absolute, "package.descriptor.mjs");
56
+ if (await fileExists(descriptorPath)) {
57
+ directories.push(absolute);
58
+ continue;
59
+ }
60
+
61
+ const nested = await readdir(absolute, { withFileTypes: true }).catch(() => []);
62
+ for (const child of nested) {
63
+ if (!child.isDirectory() || child.name.startsWith(".")) {
64
+ continue;
65
+ }
66
+ const nestedAbsolute = path.join(absolute, child.name);
67
+ const nestedDescriptor = path.join(nestedAbsolute, "package.descriptor.mjs");
68
+ if (await fileExists(nestedDescriptor)) {
69
+ directories.push(nestedAbsolute);
70
+ }
71
+ }
72
+ }
73
+
74
+ return toSortedUniqueStrings(directories);
75
+ }
76
+
77
+ async function readJson(absolutePath) {
78
+ const raw = await readFile(absolutePath, "utf8");
79
+ return JSON.parse(raw);
80
+ }
81
+
82
+ async function buildCatalog({ repoRoot, packagesRoot, outputPath }) {
83
+ const packageRoots = await collectPackageRoots(packagesRoot);
84
+ const entries = [];
85
+
86
+ for (const packageRoot of packageRoots) {
87
+ const descriptorPath = path.join(packageRoot, "package.descriptor.mjs");
88
+ const packageJsonPath = path.join(packageRoot, "package.json");
89
+
90
+ const descriptorModule = await import(pathToFileURL(descriptorPath).href + `?t=${Date.now()}_${Math.random()}`);
91
+ const descriptor = descriptorModule?.default && typeof descriptorModule.default === "object" ? descriptorModule.default : null;
92
+ if (!descriptor) {
93
+ throw new Error(`Invalid descriptor at ${descriptorPath}`);
94
+ }
95
+
96
+ const packageJson = await readJson(packageJsonPath);
97
+ const packageId = String(descriptor.packageId || "").trim();
98
+ const descriptorVersion = String(descriptor.version || "").trim();
99
+ const packageJsonName = String(packageJson?.name || "").trim();
100
+ const packageJsonVersion = String(packageJson?.version || "").trim();
101
+
102
+ if (!packageId) {
103
+ throw new Error(`Missing packageId in ${descriptorPath}`);
104
+ }
105
+ if (packageJsonName !== packageId) {
106
+ throw new Error(
107
+ `Package mismatch in ${packageRoot}: descriptor has ${packageId} but package.json has ${packageJsonName || "(empty)"}`
108
+ );
109
+ }
110
+
111
+ const version = descriptorVersion || packageJsonVersion;
112
+ if (!version) {
113
+ throw new Error(`Missing version for ${packageId} in ${packageRoot}`);
114
+ }
115
+
116
+ entries.push({
117
+ packageId,
118
+ version,
119
+ descriptor: {
120
+ ...descriptor,
121
+ version
122
+ }
123
+ });
124
+ }
125
+
126
+ const catalog = {
127
+ schemaVersion: 1,
128
+ source: {
129
+ kind: "packages-directory"
130
+ },
131
+ packages: entries.sort((left, right) => left.packageId.localeCompare(right.packageId))
132
+ };
133
+
134
+ await mkdir(path.dirname(outputPath), { recursive: true });
135
+ await writeFile(outputPath, `${JSON.stringify(catalog, null, 2)}\n`, "utf8");
136
+ }
137
+
138
+ async function main() {
139
+ const repoRootArg = parseInlineArg("--repo-root");
140
+ const packagesRootArg = parseInlineArg("--packages-root");
141
+ const outputArg = parseInlineArg("--output");
142
+
143
+ const repoRoot = path.resolve(repoRootArg || process.env.JSKIT_REPO_ROOT || DEFAULT_REPO_ROOT);
144
+ const packagesRoot = path.resolve(packagesRootArg || path.join(repoRoot, "packages"));
145
+ const outputPath = path.resolve(outputArg || path.join(PACKAGE_ROOT, "catalog", "packages.json"));
146
+
147
+ await buildCatalog({ repoRoot, packagesRoot, outputPath });
148
+ process.stdout.write(`Catalog written: ${outputPath}\n`);
149
+ }
150
+
151
+ await main();