@jskit-ai/jskit-catalog 0.2.9 → 0.2.11
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/catalog/packages.json +492 -272
- package/package.json +1 -1
- package/scripts/build-catalog.mjs +1 -1
- package/scripts/files.mjs +17 -0
- package/scripts/framework-packages.mjs +2 -10
- package/scripts/i18n.mjs +2 -1
- package/scripts/pattern-assets.mjs +4 -7
- package/scripts/project-install.mjs +156 -0
- package/scripts/project-manifests.mjs +133 -0
- package/scripts/project-packages.mjs +14 -298
- package/scripts/verify-packages.mjs +69 -38
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
assertUniquePatternIds,
|
|
8
8
|
discoverPackagePatterns
|
|
9
9
|
} from "./pattern-assets.mjs";
|
|
10
|
-
import { discoverProjectManifests } from "./project-
|
|
10
|
+
import { discoverProjectManifests } from "./project-manifests.mjs";
|
|
11
11
|
|
|
12
12
|
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
13
13
|
const PACKAGE_ROOT = path.resolve(path.dirname(SCRIPT_PATH), "..");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
async function fileExists(absolutePath) {
|
|
5
|
+
try {
|
|
6
|
+
await access(absolutePath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function toPosixPath(value = "") {
|
|
14
|
+
return String(value || "").split(path.sep).join("/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { fileExists, toPosixPath };
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileExists } from "./files.mjs";
|
|
3
4
|
|
|
4
5
|
const BUILTIN_SINGLETON_PACKAGE_IDS = Object.freeze(["@jskit-ai/kernel"]);
|
|
5
6
|
|
|
6
|
-
async function fileExists(filePath) {
|
|
7
|
-
try {
|
|
8
|
-
await access(filePath);
|
|
9
|
-
return true;
|
|
10
|
-
} catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
7
|
// The JSKIT framework packages are the direct children of packagesRoot whose
|
|
16
8
|
// package.json declares `jskit` metadata. The catalog builder and the package
|
|
17
9
|
// verifier both read this one set.
|
package/scripts/i18n.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { toPosixPath } from "./files.mjs";
|
|
3
4
|
import { resolveInstalledPackage } from "./project-packages.mjs";
|
|
4
5
|
|
|
5
6
|
// `jskit i18n export|check <locale>`: an app translates JSKIT by supplying
|
|
@@ -225,7 +226,7 @@ function nestCatalog(flat, file) {
|
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
function relativeFile(projectRoot, file) {
|
|
228
|
-
return path.relative(path.resolve(projectRoot), file)
|
|
229
|
+
return toPosixPath(path.relative(path.resolve(projectRoot), file));
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
// --- export ------------------------------------------------------------------------
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { toPosixPath } from "./files.mjs";
|
|
3
4
|
|
|
4
5
|
const PATTERN_DOCUMENT_NAME = "PATTERN.md";
|
|
5
6
|
const PATTERN_EXAMPLE_DIRECTORY = "example";
|
|
@@ -27,10 +28,6 @@ function normalizeText(value = "") {
|
|
|
27
28
|
return String(value || "").trim();
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function normalizePath(value = "") {
|
|
31
|
-
return String(value || "").split(path.sep).join("/");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
31
|
function sortedUniqueStrings(values = []) {
|
|
35
32
|
return [...new Set(values.map(normalizeText).filter(Boolean))]
|
|
36
33
|
.sort((left, right) => left.localeCompare(right));
|
|
@@ -156,7 +153,7 @@ async function collectFiles(rootDirectory) {
|
|
|
156
153
|
continue;
|
|
157
154
|
}
|
|
158
155
|
if (entry.isFile()) {
|
|
159
|
-
files.push(
|
|
156
|
+
files.push(toPosixPath(path.relative(rootDirectory, absolutePath)));
|
|
160
157
|
}
|
|
161
158
|
}
|
|
162
159
|
}
|
|
@@ -186,7 +183,7 @@ async function discoverPackagePatterns({ packageRoot, packageJson } = {}) {
|
|
|
186
183
|
for (const entry of patternDirectories) {
|
|
187
184
|
const patternRoot = path.join(patternsRoot, entry.name);
|
|
188
185
|
const documentPath = path.join(patternRoot, PATTERN_DOCUMENT_NAME);
|
|
189
|
-
const relativeDocumentPath =
|
|
186
|
+
const relativeDocumentPath = toPosixPath(path.relative(resolvedPackageRoot, documentPath));
|
|
190
187
|
let source;
|
|
191
188
|
try {
|
|
192
189
|
source = await readFile(documentPath, "utf8");
|
|
@@ -220,7 +217,7 @@ async function discoverPackagePatterns({ packageRoot, packageJson } = {}) {
|
|
|
220
217
|
packageId,
|
|
221
218
|
packageVersion,
|
|
222
219
|
documentPath: relativeDocumentPath,
|
|
223
|
-
examplePath:
|
|
220
|
+
examplePath: toPosixPath(path.relative(resolvedPackageRoot, exampleRoot)),
|
|
224
221
|
files: Object.freeze(files)
|
|
225
222
|
}));
|
|
226
223
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileExists } from "./files.mjs";
|
|
5
|
+
|
|
6
|
+
// Writing an update's manifests and installing it, each undoable: manifest
|
|
7
|
+
// writes are atomic per file and restorable, and `npm install` runs against a
|
|
8
|
+
// moved-aside node_modules that a rollback puts back.
|
|
9
|
+
|
|
10
|
+
async function writeManifestUpdates(updates = []) {
|
|
11
|
+
const temporaryPaths = [];
|
|
12
|
+
const committed = [];
|
|
13
|
+
try {
|
|
14
|
+
for (const [index, update] of updates.entries()) {
|
|
15
|
+
const temporaryPath = `${update.manifest.absolutePath}.jskit-${process.pid}-${index}.tmp`;
|
|
16
|
+
await writeFile(temporaryPath, update.contents, "utf8");
|
|
17
|
+
temporaryPaths.push(temporaryPath);
|
|
18
|
+
}
|
|
19
|
+
for (const [index, update] of updates.entries()) {
|
|
20
|
+
await rename(temporaryPaths[index], update.manifest.absolutePath);
|
|
21
|
+
committed.push(update);
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
for (const update of committed) {
|
|
25
|
+
await writeFile(update.manifest.absolutePath, update.manifest.contents, "utf8");
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
} finally {
|
|
29
|
+
await Promise.all(temporaryPaths.map((temporaryPath) => rm(temporaryPath, { force: true })));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readOptionalFile(absolutePath) {
|
|
34
|
+
try {
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
contents: await readFile(absolutePath, "utf8"),
|
|
37
|
+
exists: true
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error?.code === "ENOENT") {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
contents: "",
|
|
43
|
+
exists: false
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function restoreProjectFiles(updates, packageLockPath, packageLockSnapshot) {
|
|
51
|
+
const rollbackUpdates = updates.map((update) => Object.freeze({
|
|
52
|
+
contents: update.manifest.contents,
|
|
53
|
+
manifest: Object.freeze({
|
|
54
|
+
...update.manifest,
|
|
55
|
+
contents: update.contents
|
|
56
|
+
})
|
|
57
|
+
}));
|
|
58
|
+
await writeManifestUpdates(rollbackUpdates);
|
|
59
|
+
if (packageLockSnapshot.exists) {
|
|
60
|
+
const temporaryPath = `${packageLockPath}.jskit-${process.pid}-rollback.tmp`;
|
|
61
|
+
try {
|
|
62
|
+
await writeFile(temporaryPath, packageLockSnapshot.contents, "utf8");
|
|
63
|
+
await rename(temporaryPath, packageLockPath);
|
|
64
|
+
} finally {
|
|
65
|
+
await rm(temporaryPath, { force: true });
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await rm(packageLockPath, { force: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function spawnNpmInstall(projectRoot) {
|
|
73
|
+
await new Promise((resolve, reject) => {
|
|
74
|
+
const child = spawn("npm", ["install"], {
|
|
75
|
+
cwd: projectRoot,
|
|
76
|
+
env: process.env,
|
|
77
|
+
stdio: "inherit"
|
|
78
|
+
});
|
|
79
|
+
child.on("error", reject);
|
|
80
|
+
child.on("close", (code, signal) => {
|
|
81
|
+
if (code === 0) {
|
|
82
|
+
resolve();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
reject(new Error(`npm install failed (code=${code}, signal=${signal || "none"}).`));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function runNpmInstall(projectRoot) {
|
|
91
|
+
const nodeModulesPath = path.join(projectRoot, "node_modules");
|
|
92
|
+
const hadNodeModules = await fileExists(nodeModulesPath);
|
|
93
|
+
const backupRoot = hadNodeModules
|
|
94
|
+
? await mkdtemp(path.join(projectRoot, ".jskit-update-"))
|
|
95
|
+
: null;
|
|
96
|
+
// Preserve the dependency directory name so development watchers keep excluding it.
|
|
97
|
+
const backupPath = backupRoot ? path.join(backupRoot, "node_modules") : null;
|
|
98
|
+
if (hadNodeModules) {
|
|
99
|
+
try {
|
|
100
|
+
await rename(nodeModulesPath, backupPath);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
await rm(backupRoot, { force: true, recursive: true });
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let settled = false;
|
|
108
|
+
const transaction = Object.freeze({
|
|
109
|
+
async commit() {
|
|
110
|
+
if (settled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (backupRoot) {
|
|
114
|
+
await rm(backupRoot, { force: true, recursive: true });
|
|
115
|
+
}
|
|
116
|
+
settled = true;
|
|
117
|
+
},
|
|
118
|
+
async rollback() {
|
|
119
|
+
if (settled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
await rm(nodeModulesPath, { force: true, recursive: true });
|
|
124
|
+
if (backupPath) {
|
|
125
|
+
await rename(backupPath, nodeModulesPath);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (backupPath) {
|
|
129
|
+
throw new Error(`Could not restore node_modules; backup retained at ${backupPath}.`, { cause: error });
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
if (backupRoot) {
|
|
134
|
+
await rm(backupRoot, { force: true, recursive: true });
|
|
135
|
+
}
|
|
136
|
+
settled = true;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await spawnNpmInstall(projectRoot);
|
|
142
|
+
return transaction;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
try {
|
|
145
|
+
await transaction.rollback();
|
|
146
|
+
} catch (rollbackError) {
|
|
147
|
+
throw new AggregateError(
|
|
148
|
+
[error, rollbackError],
|
|
149
|
+
`npm install failed and the previous node_modules directory could not be restored. ${rollbackError.message}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { readOptionalFile, restoreProjectFiles, runNpmInstall, writeManifestUpdates };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileExists, toPosixPath } from "./files.mjs";
|
|
4
|
+
|
|
5
|
+
// A JSKIT project's manifests: its root package.json and every workspace
|
|
6
|
+
// package.json it declares, read as records that keep their original text.
|
|
7
|
+
const SKIPPED_DIRECTORY_NAMES = new Set([
|
|
8
|
+
".git",
|
|
9
|
+
".cache",
|
|
10
|
+
".next",
|
|
11
|
+
".nuxt",
|
|
12
|
+
".output",
|
|
13
|
+
".turbo",
|
|
14
|
+
"build",
|
|
15
|
+
"coverage",
|
|
16
|
+
"dist",
|
|
17
|
+
"node_modules",
|
|
18
|
+
"tmp"
|
|
19
|
+
]);
|
|
20
|
+
function normalizeWorkspacePatterns(value) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
if (value && typeof value === "object" && Array.isArray(value.packages)) {
|
|
25
|
+
return value.packages;
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function globToRegExp(pattern = "") {
|
|
31
|
+
const source = toPosixPath(pattern).replace(/^\.\//u, "").replace(/\/$/u, "");
|
|
32
|
+
let expression = "^";
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
35
|
+
const character = source[index];
|
|
36
|
+
if (character === "*") {
|
|
37
|
+
if (source[index + 1] === "*") {
|
|
38
|
+
index += 1;
|
|
39
|
+
if (source[index + 1] === "/") {
|
|
40
|
+
index += 1;
|
|
41
|
+
expression += "(?:.*/)?";
|
|
42
|
+
} else {
|
|
43
|
+
expression += ".*";
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
expression += "[^/]*";
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (character === "?") {
|
|
51
|
+
expression += "[^/]";
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
expression += /[.+^${}()|[\]\\]/u.test(character) ? `\\${character}` : character;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new RegExp(`${expression}$`, "u");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createWorkspaceMatcher(workspaces) {
|
|
61
|
+
const patterns = normalizeWorkspacePatterns(workspaces)
|
|
62
|
+
.map((entry) => String(entry || "").trim())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
const includes = patterns
|
|
65
|
+
.filter((entry) => !entry.startsWith("!"))
|
|
66
|
+
.map(globToRegExp);
|
|
67
|
+
const excludes = patterns
|
|
68
|
+
.filter((entry) => entry.startsWith("!"))
|
|
69
|
+
.map((entry) => globToRegExp(entry.slice(1)));
|
|
70
|
+
|
|
71
|
+
return function matchesWorkspace(relativeDirectory = "") {
|
|
72
|
+
const normalized = toPosixPath(relativeDirectory).replace(/^\.\//u, "").replace(/\/$/u, "");
|
|
73
|
+
return includes.some((matcher) => matcher.test(normalized)) &&
|
|
74
|
+
!excludes.some((matcher) => matcher.test(normalized));
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readJsonRecord(absolutePath) {
|
|
79
|
+
const contents = await readFile(absolutePath, "utf8");
|
|
80
|
+
let value;
|
|
81
|
+
try {
|
|
82
|
+
value = JSON.parse(contents);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`${absolutePath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
}
|
|
86
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
87
|
+
throw new TypeError(`${absolutePath} must contain a JSON object.`);
|
|
88
|
+
}
|
|
89
|
+
return { absolutePath, contents, value };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function collectNestedPackageJsonPaths(directory, results = []) {
|
|
93
|
+
for (const entry of await readdir(directory, { withFileTypes: true }).catch(() => [])) {
|
|
94
|
+
if (!entry.isDirectory() || SKIPPED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".")) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const childDirectory = path.join(directory, entry.name);
|
|
98
|
+
const manifestPath = path.join(childDirectory, "package.json");
|
|
99
|
+
if (await fileExists(manifestPath)) {
|
|
100
|
+
results.push(manifestPath);
|
|
101
|
+
}
|
|
102
|
+
await collectNestedPackageJsonPaths(childDirectory, results);
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function discoverProjectManifests(projectRoot, additionalManifests = []) {
|
|
108
|
+
const root = path.resolve(projectRoot);
|
|
109
|
+
const rootManifestPath = path.join(root, "package.json");
|
|
110
|
+
if (!(await fileExists(rootManifestPath))) {
|
|
111
|
+
throw new Error(`JSKIT project root has no package.json: ${root}`);
|
|
112
|
+
}
|
|
113
|
+
const rootManifest = await readJsonRecord(rootManifestPath);
|
|
114
|
+
const matchesWorkspace = createWorkspaceMatcher(rootManifest.value.workspaces);
|
|
115
|
+
const nestedPaths = await collectNestedPackageJsonPaths(root);
|
|
116
|
+
const workspacePaths = nestedPaths
|
|
117
|
+
.filter((manifestPath) => matchesWorkspace(path.relative(root, path.dirname(manifestPath))))
|
|
118
|
+
.sort((left, right) => left.localeCompare(right));
|
|
119
|
+
const manifests = [rootManifest];
|
|
120
|
+
for (const manifestPath of new Set([...workspacePaths, ...additionalManifests.map((file) => path.resolve(root, file))])) {
|
|
121
|
+
if (manifestPath === rootManifestPath) continue;
|
|
122
|
+
manifests.push(await readJsonRecord(manifestPath));
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze(manifests);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function serializePackageJson(value, originalContents = "") {
|
|
128
|
+
const indentMatch = /\n([ \t]+)"/u.exec(String(originalContents || ""));
|
|
129
|
+
const indentation = indentMatch?.[1] || " ";
|
|
130
|
+
return `${JSON.stringify(value, null, indentation)}\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { discoverProjectManifests, readJsonRecord, serializePackageJson };
|