@jskit-ai/jskit-cli 0.2.26 → 0.2.28
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 +3 -2
- package/src/server/cliRuntime/appState.js +226 -0
- package/src/server/cliRuntime/capabilitySupport.js +194 -0
- package/src/server/cliRuntime/descriptorValidation.js +150 -0
- package/src/server/cliRuntime/ioAndMigrations.js +381 -0
- package/src/server/cliRuntime/localPackageSupport.js +390 -0
- package/src/server/cliRuntime/mutationApplication.js +9 -0
- package/src/server/cliRuntime/mutationWhen.js +285 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
- package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
- package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
- package/src/server/cliRuntime/mutations/templateContext.js +171 -0
- package/src/server/cliRuntime/mutations/textMutations.js +250 -0
- package/src/server/cliRuntime/packageInstallFlow.js +489 -0
- package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
- package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
- package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
- package/src/server/cliRuntime/packageIntrospection.js +137 -0
- package/src/server/cliRuntime/packageOptions.js +299 -0
- package/src/server/cliRuntime/packageRegistries.js +343 -0
- package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
- package/src/server/cliRuntime/viteProxy.js +356 -0
- package/src/server/commandHandlers/health.js +292 -0
- package/src/server/commandHandlers/list.js +292 -0
- package/src/server/commandHandlers/package.js +23 -0
- package/src/server/commandHandlers/packageCommands/add.js +282 -0
- package/src/server/commandHandlers/packageCommands/create.js +155 -0
- package/src/server/commandHandlers/packageCommands/generate.js +116 -0
- package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
- package/src/server/commandHandlers/packageCommands/position.js +103 -0
- package/src/server/commandHandlers/packageCommands/remove.js +181 -0
- package/src/server/commandHandlers/packageCommands/update.js +40 -0
- package/src/server/commandHandlers/shared.js +314 -0
- package/src/server/commandHandlers/show/payloads.js +92 -0
- package/src/server/commandHandlers/show/renderBundleText.js +16 -0
- package/src/server/commandHandlers/show/renderHelpers.js +82 -0
- package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
- package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
- package/src/server/commandHandlers/show/renderPackageText.js +332 -0
- package/src/server/commandHandlers/show.js +114 -0
- package/src/server/core/argParser.js +144 -0
- package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
- package/src/server/core/commandCatalog.js +47 -0
- package/src/server/core/createCliRunner.js +150 -0
- package/src/server/core/createCommandHandlers.js +43 -0
- package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
- package/src/server/core/usageHelp.js +344 -0
- package/src/server/index.js +1 -1
- package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
- package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
- package/src/server/argParser.js +0 -206
- package/src/server/cliRuntime.js +0 -4853
- package/src/server/commandHandlers.js +0 -2109
- /package/src/server/{cliError.js → shared/cliError.js} +0 -0
- /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
- /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
- /package/src/server/{packageIdHelpers.js → shared/packageIdHelpers.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.28",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.20"
|
|
24
25
|
},
|
|
25
26
|
"engines": {
|
|
26
27
|
"node": "20.x"
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { createCliError } from "../shared/cliError.js";
|
|
4
|
+
import { ensureObject } from "../shared/collectionUtils.js";
|
|
5
|
+
import { escapeRegExp } from "../shared/optionInterpolation.js";
|
|
6
|
+
import {
|
|
7
|
+
fileExists,
|
|
8
|
+
readJsonFile,
|
|
9
|
+
writeJsonFile
|
|
10
|
+
} from "./ioAndMigrations.js";
|
|
11
|
+
|
|
12
|
+
const LOCK_RELATIVE_PATH = ".jskit/lock.json";
|
|
13
|
+
const LOCK_VERSION = 1;
|
|
14
|
+
|
|
15
|
+
async function resolveAppRootFromCwd(cwd) {
|
|
16
|
+
const startDirectory = path.resolve(String(cwd || process.cwd()));
|
|
17
|
+
let currentDirectory = startDirectory;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
const packageJsonPath = path.join(currentDirectory, "package.json");
|
|
21
|
+
if (await fileExists(packageJsonPath)) {
|
|
22
|
+
return currentDirectory;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
26
|
+
if (parentDirectory === currentDirectory) {
|
|
27
|
+
throw createCliError(
|
|
28
|
+
`Could not locate package.json starting from ${startDirectory}. Run jskit from an app directory (or a child directory of one).`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
currentDirectory = parentDirectory;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadAppPackageJson(appRoot) {
|
|
36
|
+
const packageJsonPath = path.join(appRoot, "package.json");
|
|
37
|
+
const packageJson = await readJsonFile(packageJsonPath);
|
|
38
|
+
return {
|
|
39
|
+
packageJsonPath,
|
|
40
|
+
packageJson
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createDefaultLock() {
|
|
45
|
+
return {
|
|
46
|
+
lockVersion: LOCK_VERSION,
|
|
47
|
+
installedPackages: {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function loadLockFile(appRoot) {
|
|
52
|
+
const lockPath = path.join(appRoot, LOCK_RELATIVE_PATH);
|
|
53
|
+
if (!(await fileExists(lockPath))) {
|
|
54
|
+
return {
|
|
55
|
+
lockPath,
|
|
56
|
+
lock: createDefaultLock()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const lock = await readJsonFile(lockPath);
|
|
61
|
+
const installedPackages = ensureObject(lock?.installedPackages);
|
|
62
|
+
const lockVersion = Number(lock?.lockVersion);
|
|
63
|
+
return {
|
|
64
|
+
lockPath,
|
|
65
|
+
lock: {
|
|
66
|
+
lockVersion: Number.isFinite(lockVersion) && lockVersion > 0 ? lockVersion : LOCK_VERSION,
|
|
67
|
+
installedPackages
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createManagedPackageJsonChange(hadPrevious, previousValue, value) {
|
|
73
|
+
return {
|
|
74
|
+
hadPrevious: Boolean(hadPrevious),
|
|
75
|
+
previousValue: hadPrevious ? String(previousValue) : "",
|
|
76
|
+
value: String(value)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensurePackageJsonSection(packageJson, sectionName) {
|
|
81
|
+
const sectionValue = ensureObject(packageJson[sectionName]);
|
|
82
|
+
packageJson[sectionName] = sectionValue;
|
|
83
|
+
return sectionValue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function applyPackageJsonField(packageJson, sectionName, key, value) {
|
|
87
|
+
const section = ensurePackageJsonSection(packageJson, sectionName);
|
|
88
|
+
const nextValue = String(value);
|
|
89
|
+
const hadPrevious = Object.prototype.hasOwnProperty.call(section, key);
|
|
90
|
+
const previousValue = hadPrevious ? String(section[key]) : "";
|
|
91
|
+
const changed = !hadPrevious || previousValue !== nextValue;
|
|
92
|
+
section[key] = nextValue;
|
|
93
|
+
return {
|
|
94
|
+
changed,
|
|
95
|
+
managed: createManagedPackageJsonChange(hadPrevious, previousValue, nextValue)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function removePackageJsonField(packageJson, sectionName, key) {
|
|
100
|
+
const section = ensureObject(packageJson[sectionName]);
|
|
101
|
+
if (!Object.prototype.hasOwnProperty.call(section, key)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
delete section[key];
|
|
105
|
+
if (Object.keys(section).length < 1) {
|
|
106
|
+
delete packageJson[sectionName];
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function restorePackageJsonField(packageJson, sectionName, key, managedChange) {
|
|
112
|
+
const section = ensurePackageJsonSection(packageJson, sectionName);
|
|
113
|
+
const currentValue = Object.prototype.hasOwnProperty.call(section, key) ? String(section[key]) : "";
|
|
114
|
+
if (currentValue !== String(managedChange?.value || "")) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (managedChange?.hadPrevious) {
|
|
119
|
+
section[key] = String(managedChange.previousValue || "");
|
|
120
|
+
} else {
|
|
121
|
+
delete section[key];
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseEnvLineValue(line, key) {
|
|
127
|
+
const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
128
|
+
if (!pattern.test(line)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const index = line.indexOf("=");
|
|
132
|
+
if (index === -1) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
return line.slice(index + 1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function upsertEnvValue(content, key, value) {
|
|
139
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
140
|
+
const lookupPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
141
|
+
let index = -1;
|
|
142
|
+
|
|
143
|
+
for (let cursor = 0; cursor < lines.length; cursor += 1) {
|
|
144
|
+
if (lookupPattern.test(lines[cursor])) {
|
|
145
|
+
index = cursor;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const hadPrevious = index >= 0;
|
|
151
|
+
const previousValue = hadPrevious ? String(parseEnvLineValue(lines[index], key) || "") : "";
|
|
152
|
+
const nextLine = `${key}=${value}`;
|
|
153
|
+
|
|
154
|
+
if (hadPrevious) {
|
|
155
|
+
lines[index] = nextLine;
|
|
156
|
+
} else {
|
|
157
|
+
if (lines.length === 1 && lines[0] === "") {
|
|
158
|
+
lines[0] = nextLine;
|
|
159
|
+
} else {
|
|
160
|
+
lines.push(nextLine);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const normalized = `${lines.join("\n").replace(/\n+$/, "")}\n`;
|
|
165
|
+
return {
|
|
166
|
+
hadPrevious,
|
|
167
|
+
previousValue,
|
|
168
|
+
content: normalized
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function removeEnvValue(content, key, expectedValue, previous) {
|
|
173
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
174
|
+
const lookupPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
175
|
+
let index = -1;
|
|
176
|
+
|
|
177
|
+
for (let cursor = 0; cursor < lines.length; cursor += 1) {
|
|
178
|
+
if (lookupPattern.test(lines[cursor])) {
|
|
179
|
+
index = cursor;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (index < 0) {
|
|
185
|
+
return {
|
|
186
|
+
changed: false,
|
|
187
|
+
content: content
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const currentValue = String(parseEnvLineValue(lines[index], key) || "");
|
|
192
|
+
if (currentValue !== String(expectedValue || "")) {
|
|
193
|
+
return {
|
|
194
|
+
changed: false,
|
|
195
|
+
content: content
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (previous?.hadPrevious) {
|
|
200
|
+
lines[index] = `${key}=${String(previous.previousValue || "")}`;
|
|
201
|
+
} else {
|
|
202
|
+
lines.splice(index, 1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const normalized = `${lines.join("\n").replace(/\n+$/, "")}\n`;
|
|
206
|
+
return {
|
|
207
|
+
changed: true,
|
|
208
|
+
content: normalized
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
resolveAppRootFromCwd,
|
|
214
|
+
loadAppPackageJson,
|
|
215
|
+
createDefaultLock,
|
|
216
|
+
loadLockFile,
|
|
217
|
+
createManagedPackageJsonChange,
|
|
218
|
+
ensurePackageJsonSection,
|
|
219
|
+
applyPackageJsonField,
|
|
220
|
+
removePackageJsonField,
|
|
221
|
+
restorePackageJsonField,
|
|
222
|
+
parseEnvLineValue,
|
|
223
|
+
upsertEnvValue,
|
|
224
|
+
removeEnvValue,
|
|
225
|
+
writeJsonFile
|
|
226
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createCliError } from "../shared/cliError.js";
|
|
2
|
+
import {
|
|
3
|
+
ensureArray,
|
|
4
|
+
ensureObject,
|
|
5
|
+
sortStrings
|
|
6
|
+
} from "../shared/collectionUtils.js";
|
|
7
|
+
|
|
8
|
+
const BUILTIN_CAPABILITY_PROVIDERS = Object.freeze({
|
|
9
|
+
"runtime.actions": Object.freeze(["@jskit-ai/kernel"])
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function listDeclaredCapabilities(capabilitiesSection, fieldName) {
|
|
13
|
+
const section = ensureObject(capabilitiesSection);
|
|
14
|
+
const source = ensureArray(section[fieldName]);
|
|
15
|
+
const normalized = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const value of source) {
|
|
18
|
+
const capabilityId = String(value || "").trim();
|
|
19
|
+
if (!capabilityId || seen.has(capabilityId)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
seen.add(capabilityId);
|
|
23
|
+
normalized.push(capabilityId);
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildCapabilityGraph(packageRegistry) {
|
|
29
|
+
const graph = new Map();
|
|
30
|
+
const ensureNode = (capabilityId) => {
|
|
31
|
+
if (!graph.has(capabilityId)) {
|
|
32
|
+
graph.set(capabilityId, {
|
|
33
|
+
providers: new Set(),
|
|
34
|
+
requirers: new Set()
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return graph.get(capabilityId);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (const [packageId, packageEntry] of packageRegistry.entries()) {
|
|
41
|
+
const capabilities = ensureObject(packageEntry?.descriptor?.capabilities);
|
|
42
|
+
for (const capabilityId of listDeclaredCapabilities(capabilities, "provides")) {
|
|
43
|
+
ensureNode(capabilityId).providers.add(packageId);
|
|
44
|
+
}
|
|
45
|
+
for (const capabilityId of listDeclaredCapabilities(capabilities, "requires")) {
|
|
46
|
+
ensureNode(capabilityId).requirers.add(packageId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const [capabilityId, providers] of Object.entries(BUILTIN_CAPABILITY_PROVIDERS)) {
|
|
51
|
+
const node = ensureNode(capabilityId);
|
|
52
|
+
for (const providerId of ensureArray(providers).map((value) => String(value || "").trim()).filter(Boolean)) {
|
|
53
|
+
node.providers.add(providerId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const normalizedGraph = new Map();
|
|
58
|
+
for (const [capabilityId, node] of graph.entries()) {
|
|
59
|
+
normalizedGraph.set(capabilityId, {
|
|
60
|
+
providers: sortStrings([...node.providers]),
|
|
61
|
+
requirers: sortStrings([...node.requirers])
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return normalizedGraph;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createCapabilityPackageDetail(packageId, packageRegistry) {
|
|
68
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
69
|
+
return {
|
|
70
|
+
packageId,
|
|
71
|
+
version: String(packageEntry?.version || packageEntry?.descriptor?.version || "").trim(),
|
|
72
|
+
descriptorPath: String(packageEntry?.descriptorRelativePath || "").trim()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildCapabilityDetailsForPackage({ packageRegistry, packageId, dependsOn = [], provides = [], requires = [] }) {
|
|
77
|
+
const graph = buildCapabilityGraph(packageRegistry);
|
|
78
|
+
const dependsOnSet = new Set(ensureArray(dependsOn).map((value) => String(value || "").trim()).filter(Boolean));
|
|
79
|
+
|
|
80
|
+
function buildCapabilityRecord(capabilityId) {
|
|
81
|
+
const node = graph.get(capabilityId) || {
|
|
82
|
+
providers: [],
|
|
83
|
+
requirers: []
|
|
84
|
+
};
|
|
85
|
+
const providers = sortStrings(ensureArray(node.providers));
|
|
86
|
+
const requirers = sortStrings(ensureArray(node.requirers));
|
|
87
|
+
const providersInDependsOn = providers.filter((providerId) => dependsOnSet.has(providerId));
|
|
88
|
+
return {
|
|
89
|
+
capabilityId,
|
|
90
|
+
providers,
|
|
91
|
+
requirers,
|
|
92
|
+
providersInDependsOn,
|
|
93
|
+
providerDetails: providers.map((providerId) => createCapabilityPackageDetail(providerId, packageRegistry)),
|
|
94
|
+
requirerDetails: requirers.map((requirerId) => createCapabilityPackageDetail(requirerId, packageRegistry)),
|
|
95
|
+
isProvidedByCurrentPackage: providers.includes(packageId),
|
|
96
|
+
isRequiredByCurrentPackage: requirers.includes(packageId)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
provides: ensureArray(provides).map((capabilityId) => buildCapabilityRecord(capabilityId)),
|
|
102
|
+
requires: ensureArray(requires).map((capabilityId) => buildCapabilityRecord(capabilityId))
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function collectPlannedCapabilityIssues(plannedPackageIds, packageRegistry) {
|
|
107
|
+
const selectedPackageIds = sortStrings(
|
|
108
|
+
[...new Set(ensureArray(plannedPackageIds).map((value) => String(value || "").trim()).filter(Boolean))]
|
|
109
|
+
);
|
|
110
|
+
const selectedPackageSet = new Set(selectedPackageIds);
|
|
111
|
+
const providersByCapability = new Map();
|
|
112
|
+
|
|
113
|
+
for (const [capabilityId, providers] of Object.entries(BUILTIN_CAPABILITY_PROVIDERS)) {
|
|
114
|
+
if (!providersByCapability.has(capabilityId)) {
|
|
115
|
+
providersByCapability.set(capabilityId, new Set());
|
|
116
|
+
}
|
|
117
|
+
for (const providerId of ensureArray(providers).map((value) => String(value || "").trim()).filter(Boolean)) {
|
|
118
|
+
providersByCapability.get(capabilityId).add(providerId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const packageId of selectedPackageIds) {
|
|
123
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
124
|
+
if (!packageEntry) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const provides = listDeclaredCapabilities(packageEntry.descriptor.capabilities, "provides");
|
|
128
|
+
for (const capabilityId of provides) {
|
|
129
|
+
if (!providersByCapability.has(capabilityId)) {
|
|
130
|
+
providersByCapability.set(capabilityId, new Set());
|
|
131
|
+
}
|
|
132
|
+
providersByCapability.get(capabilityId).add(packageId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const issues = [];
|
|
137
|
+
for (const packageId of selectedPackageIds) {
|
|
138
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
139
|
+
if (!packageEntry) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const requires = listDeclaredCapabilities(packageEntry.descriptor.capabilities, "requires");
|
|
143
|
+
for (const capabilityId of requires) {
|
|
144
|
+
const selectedProviders = providersByCapability.get(capabilityId);
|
|
145
|
+
if (selectedProviders && selectedProviders.size > 0) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const availableProviders = [];
|
|
150
|
+
for (const [candidatePackageId, candidatePackageEntry] of packageRegistry.entries()) {
|
|
151
|
+
if (selectedPackageSet.has(candidatePackageId)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const candidateProvides = listDeclaredCapabilities(candidatePackageEntry.descriptor.capabilities, "provides");
|
|
155
|
+
if (candidateProvides.includes(capabilityId)) {
|
|
156
|
+
availableProviders.push(candidatePackageId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
issues.push({
|
|
161
|
+
packageId,
|
|
162
|
+
capabilityId,
|
|
163
|
+
availableProviders: sortStrings(availableProviders)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return issues;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function validatePlannedCapabilityClosure(plannedPackageIds, packageRegistry, actionLabel) {
|
|
172
|
+
const issues = collectPlannedCapabilityIssues(plannedPackageIds, packageRegistry);
|
|
173
|
+
if (issues.length === 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const lines = [`Cannot ${actionLabel}: capability requirements are not satisfied.`];
|
|
178
|
+
for (const issue of issues) {
|
|
179
|
+
const providersHint = issue.availableProviders.length > 0
|
|
180
|
+
? ` Available providers: ${issue.availableProviders.join(", ")}.`
|
|
181
|
+
: "";
|
|
182
|
+
lines.push(
|
|
183
|
+
`- ${issue.packageId} requires capability ${issue.capabilityId}, but no selected package provides it.${providersHint}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw createCliError(lines.join("\n"));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
listDeclaredCapabilities,
|
|
192
|
+
buildCapabilityDetailsForPackage,
|
|
193
|
+
validatePlannedCapabilityClosure
|
|
194
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createCliError } from "../shared/cliError.js";
|
|
2
|
+
import {
|
|
3
|
+
ensureArray,
|
|
4
|
+
ensureObject
|
|
5
|
+
} from "../shared/collectionUtils.js";
|
|
6
|
+
import { normalizeFileMutationRecord } from "./mutationWhen.js";
|
|
7
|
+
|
|
8
|
+
const PACKAGE_KIND_RUNTIME = "runtime";
|
|
9
|
+
const PACKAGE_KIND_GENERATOR = "generator";
|
|
10
|
+
const PACKAGE_KINDS = Object.freeze([PACKAGE_KIND_RUNTIME, PACKAGE_KIND_GENERATOR]);
|
|
11
|
+
|
|
12
|
+
function normalizePackageKind(rawValue, descriptorPath) {
|
|
13
|
+
const normalized = String(rawValue || "").trim().toLowerCase();
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
throw createCliError(
|
|
16
|
+
`Invalid package descriptor at ${descriptorPath}: missing kind (expected ${PACKAGE_KINDS.join(" | ")}).`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (!PACKAGE_KINDS.includes(normalized)) {
|
|
20
|
+
throw createCliError(
|
|
21
|
+
`Invalid package descriptor at ${descriptorPath}: kind must be one of: ${PACKAGE_KINDS.join(", ")}.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateInstallMigrationMutationShape(descriptor, descriptorPath) {
|
|
28
|
+
const packageId = String(ensureObject(descriptor).packageId || "").trim() || "unknown-package";
|
|
29
|
+
const mutations = ensureObject(ensureObject(descriptor).mutations);
|
|
30
|
+
const files = ensureArray(mutations.files);
|
|
31
|
+
for (const fileMutation of files) {
|
|
32
|
+
const normalized = normalizeFileMutationRecord(fileMutation);
|
|
33
|
+
if (normalized.op !== "install-migration") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!normalized.from) {
|
|
37
|
+
throw createCliError(
|
|
38
|
+
`Invalid package descriptor at ${descriptorPath}: install-migration in ${packageId} requires "from".`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!normalized.id) {
|
|
42
|
+
throw createCliError(
|
|
43
|
+
`Invalid package descriptor at ${descriptorPath}: install-migration in ${packageId} requires "id".`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validatePackageDescriptorShape(descriptor, descriptorPath) {
|
|
50
|
+
const normalized = ensureObject(descriptor);
|
|
51
|
+
const packageId = String(normalized.packageId || "").trim();
|
|
52
|
+
const version = String(normalized.version || "").trim();
|
|
53
|
+
|
|
54
|
+
if (!packageId.startsWith("@jskit-ai/")) {
|
|
55
|
+
throw createCliError(`Invalid package descriptor at ${descriptorPath}: packageId must start with @jskit-ai/.`);
|
|
56
|
+
}
|
|
57
|
+
if (!version) {
|
|
58
|
+
throw createCliError(`Invalid package descriptor at ${descriptorPath}: missing version.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const runtime = ensureObject(normalized.runtime);
|
|
62
|
+
const server = ensureObject(runtime.server);
|
|
63
|
+
const client = ensureObject(runtime.client);
|
|
64
|
+
const hasServerProviders = Array.isArray(server.providers);
|
|
65
|
+
const hasClientProviders = Array.isArray(client.providers);
|
|
66
|
+
if (!hasServerProviders && !hasClientProviders) {
|
|
67
|
+
throw createCliError(
|
|
68
|
+
`Invalid package descriptor at ${descriptorPath}: runtime.server.providers or runtime.client.providers must be declared.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
validateInstallMigrationMutationShape(normalized, descriptorPath);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...normalized,
|
|
76
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isGeneratorPackageEntry(packageEntry) {
|
|
81
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
82
|
+
return String(descriptor.kind || "").trim().toLowerCase() === PACKAGE_KIND_GENERATOR;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateAppLocalPackageDescriptorShape(descriptor, descriptorPath, { expectedPackageId = "", fallbackVersion = "" } = {}) {
|
|
86
|
+
const normalized = ensureObject(descriptor);
|
|
87
|
+
const packageId = String(normalized.packageId || "").trim();
|
|
88
|
+
const version = String(normalized.version || "").trim() || String(fallbackVersion || "").trim();
|
|
89
|
+
|
|
90
|
+
if (!packageId) {
|
|
91
|
+
throw createCliError(`Invalid app-local package descriptor at ${descriptorPath}: missing packageId.`);
|
|
92
|
+
}
|
|
93
|
+
if (expectedPackageId && packageId !== expectedPackageId) {
|
|
94
|
+
throw createCliError(
|
|
95
|
+
`Descriptor/package mismatch at ${descriptorPath}: package.descriptor.mjs has ${packageId} but package.json has ${expectedPackageId}.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (!version) {
|
|
99
|
+
throw createCliError(`Invalid app-local package descriptor at ${descriptorPath}: missing version.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
validateInstallMigrationMutationShape(normalized, descriptorPath);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...normalized,
|
|
106
|
+
packageId,
|
|
107
|
+
version,
|
|
108
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function createPackageEntry({
|
|
113
|
+
packageId,
|
|
114
|
+
version,
|
|
115
|
+
descriptor,
|
|
116
|
+
rootDir = "",
|
|
117
|
+
relativeDir = "",
|
|
118
|
+
descriptorRelativePath = "",
|
|
119
|
+
packageJson = {},
|
|
120
|
+
sourceType = "",
|
|
121
|
+
source = {}
|
|
122
|
+
}) {
|
|
123
|
+
const normalizedSourceType = String(sourceType || "").trim() || "package";
|
|
124
|
+
const normalizedDescriptorPath = String(descriptorRelativePath || "").trim();
|
|
125
|
+
const normalizedSource = {
|
|
126
|
+
type: normalizedSourceType,
|
|
127
|
+
...ensureObject(source)
|
|
128
|
+
};
|
|
129
|
+
if (!normalizedSource.descriptorPath && normalizedDescriptorPath) {
|
|
130
|
+
normalizedSource.descriptorPath = normalizedDescriptorPath;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
packageId: String(packageId || "").trim(),
|
|
134
|
+
version: String(version || "").trim(),
|
|
135
|
+
descriptor: ensureObject(descriptor),
|
|
136
|
+
rootDir: String(rootDir || "").trim(),
|
|
137
|
+
relativeDir: String(relativeDir || "").trim(),
|
|
138
|
+
descriptorRelativePath: normalizedDescriptorPath,
|
|
139
|
+
packageJson: ensureObject(packageJson),
|
|
140
|
+
sourceType: normalizedSourceType,
|
|
141
|
+
source: normalizedSource
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
validatePackageDescriptorShape,
|
|
147
|
+
validateAppLocalPackageDescriptorShape,
|
|
148
|
+
createPackageEntry,
|
|
149
|
+
isGeneratorPackageEntry
|
|
150
|
+
};
|