@jskit-ai/jskit-cli 0.2.27 → 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.
Files changed (59) hide show
  1. package/package.json +3 -2
  2. package/src/server/cliRuntime/appState.js +226 -0
  3. package/src/server/cliRuntime/capabilitySupport.js +194 -0
  4. package/src/server/cliRuntime/descriptorValidation.js +150 -0
  5. package/src/server/cliRuntime/ioAndMigrations.js +381 -0
  6. package/src/server/cliRuntime/localPackageSupport.js +390 -0
  7. package/src/server/cliRuntime/mutationApplication.js +9 -0
  8. package/src/server/cliRuntime/mutationWhen.js +285 -0
  9. package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
  10. package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
  11. package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
  12. package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
  13. package/src/server/cliRuntime/mutations/templateContext.js +171 -0
  14. package/src/server/cliRuntime/mutations/textMutations.js +250 -0
  15. package/src/server/cliRuntime/packageInstallFlow.js +489 -0
  16. package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
  17. package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
  18. package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
  19. package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
  20. package/src/server/cliRuntime/packageIntrospection.js +137 -0
  21. package/src/server/cliRuntime/packageOptions.js +299 -0
  22. package/src/server/cliRuntime/packageRegistries.js +343 -0
  23. package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
  24. package/src/server/cliRuntime/viteProxy.js +356 -0
  25. package/src/server/commandHandlers/health.js +292 -0
  26. package/src/server/commandHandlers/list.js +292 -0
  27. package/src/server/commandHandlers/package.js +23 -0
  28. package/src/server/commandHandlers/packageCommands/add.js +282 -0
  29. package/src/server/commandHandlers/packageCommands/create.js +155 -0
  30. package/src/server/commandHandlers/packageCommands/generate.js +116 -0
  31. package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
  32. package/src/server/commandHandlers/packageCommands/position.js +103 -0
  33. package/src/server/commandHandlers/packageCommands/remove.js +181 -0
  34. package/src/server/commandHandlers/packageCommands/update.js +40 -0
  35. package/src/server/commandHandlers/shared.js +314 -0
  36. package/src/server/commandHandlers/show/payloads.js +92 -0
  37. package/src/server/commandHandlers/show/renderBundleText.js +16 -0
  38. package/src/server/commandHandlers/show/renderHelpers.js +82 -0
  39. package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
  40. package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
  41. package/src/server/commandHandlers/show/renderPackageText.js +332 -0
  42. package/src/server/commandHandlers/show.js +114 -0
  43. package/src/server/core/argParser.js +144 -0
  44. package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
  45. package/src/server/core/commandCatalog.js +47 -0
  46. package/src/server/core/createCliRunner.js +150 -0
  47. package/src/server/core/createCommandHandlers.js +43 -0
  48. package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
  49. package/src/server/core/usageHelp.js +344 -0
  50. package/src/server/index.js +1 -1
  51. package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
  52. package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
  53. package/src/server/argParser.js +0 -206
  54. package/src/server/cliRuntime.js +0 -4956
  55. package/src/server/commandHandlers.js +0 -2109
  56. /package/src/server/{cliError.js → shared/cliError.js} +0 -0
  57. /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
  58. /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
  59. /package/src/server/{packageIdHelpers.js → shared/packageIdHelpers.js} +0 -0
@@ -0,0 +1,299 @@
1
+ import { createCliError } from "../shared/cliError.js";
2
+ import {
3
+ ensureObject,
4
+ sortStrings
5
+ } from "../shared/collectionUtils.js";
6
+ import {
7
+ interpolateOptionValue,
8
+ promptForRequiredOption
9
+ } from "../shared/optionInterpolation.js";
10
+ import {
11
+ normalizeWhenSourceValue,
12
+ resolveWhenConfigValue
13
+ } from "./mutationWhen.js";
14
+ import { loadMutationWhenConfigContext } from "./ioAndMigrations.js";
15
+
16
+ const WORKSPACE_VISIBILITY_LEVELS = Object.freeze(["workspace", "workspace_user"]);
17
+ const WORKSPACE_VISIBILITY_SET = new Set(WORKSPACE_VISIBILITY_LEVELS);
18
+
19
+ function normalizeSurfaceIdForMutation(value = "") {
20
+ return String(value || "").trim().toLowerCase();
21
+ }
22
+
23
+ function parseSurfaceIdListForMutation(value = "") {
24
+ const normalized = String(value || "");
25
+ if (!normalized.trim()) {
26
+ return [];
27
+ }
28
+
29
+ return [
30
+ ...new Set(
31
+ normalized
32
+ .split(",")
33
+ .map((entry) => normalizeSurfaceIdForMutation(entry))
34
+ .filter(Boolean)
35
+ )
36
+ ];
37
+ }
38
+
39
+ function resolveSurfaceVisibilityOptionPolicy(packageEntry = {}) {
40
+ const descriptor = ensureObject(packageEntry?.descriptor);
41
+ const optionPolicies = ensureObject(descriptor.optionPolicies);
42
+ const surfaceVisibilityPolicy = optionPolicies.surfaceVisibility;
43
+ if (!surfaceVisibilityPolicy || surfaceVisibilityPolicy === false) {
44
+ return null;
45
+ }
46
+
47
+ let policy = {};
48
+ if (surfaceVisibilityPolicy === true) {
49
+ policy = {};
50
+ } else if (typeof surfaceVisibilityPolicy === "object" && !Array.isArray(surfaceVisibilityPolicy)) {
51
+ policy = ensureObject(surfaceVisibilityPolicy);
52
+ } else {
53
+ throw createCliError(
54
+ `Invalid option policy in package ${packageEntry.packageId}: surfaceVisibility must be true or an object.`
55
+ );
56
+ }
57
+
58
+ const surfaceOption = String(policy.surfaceOption || "surface").trim();
59
+ const visibilityOption = String(policy.visibilityOption || "visibility").trim();
60
+ if (!surfaceOption || !visibilityOption) {
61
+ throw createCliError(
62
+ `Invalid option policy in package ${packageEntry.packageId}: surfaceVisibility requires non-empty surfaceOption and visibilityOption.`
63
+ );
64
+ }
65
+
66
+ return Object.freeze({
67
+ surfaceOption,
68
+ visibilityOption,
69
+ allowAuto: policy.allowAuto !== false
70
+ });
71
+ }
72
+
73
+ function resolveSurfaceDefinitionsForOptionPolicy(configContext = {}) {
74
+ const publicConfig = ensureObject(configContext.public);
75
+ const mergedConfig = ensureObject(configContext.merged);
76
+ const sourceDefinitions = ensureObject(publicConfig.surfaceDefinitions);
77
+ const fallbackDefinitions = ensureObject(mergedConfig.surfaceDefinitions);
78
+ const surfaceDefinitions =
79
+ Object.keys(sourceDefinitions).length > 0 ? sourceDefinitions : fallbackDefinitions;
80
+
81
+ const normalizedDefinitions = {};
82
+ for (const [key, value] of Object.entries(surfaceDefinitions)) {
83
+ const definition = ensureObject(value);
84
+ const definitionId = normalizeSurfaceIdForMutation(definition.id || key);
85
+ if (!definitionId) {
86
+ continue;
87
+ }
88
+
89
+ normalizedDefinitions[definitionId] = Object.freeze({
90
+ id: definitionId,
91
+ enabled: definition.enabled !== false,
92
+ requiresWorkspace: definition.requiresWorkspace === true
93
+ });
94
+ }
95
+
96
+ return Object.freeze(normalizedDefinitions);
97
+ }
98
+
99
+ function normalizeResolvedOptionValue(value = "") {
100
+ return String(value || "").trim().toLowerCase();
101
+ }
102
+
103
+ function validateSurfaceVisibilityOptionPolicy({
104
+ packageEntry,
105
+ resolvedOptions = {},
106
+ policy,
107
+ configContext = {}
108
+ } = {}) {
109
+ const packageId = String(packageEntry?.packageId || "").trim() || "unknown-package";
110
+ const surfaceIds = parseSurfaceIdListForMutation(resolvedOptions?.[policy.surfaceOption]);
111
+ const visibility = normalizeResolvedOptionValue(resolvedOptions?.[policy.visibilityOption]);
112
+ if (surfaceIds.length < 1 || !visibility) {
113
+ return;
114
+ }
115
+ const skipWorkspaceRequirement = policy.allowAuto && visibility === "auto";
116
+
117
+ const surfaceDefinitions = resolveSurfaceDefinitionsForOptionPolicy(configContext);
118
+ for (const surfaceId of surfaceIds) {
119
+ const surfaceDefinition = surfaceDefinitions[surfaceId];
120
+ if (!surfaceDefinition) {
121
+ throw createCliError(
122
+ `Invalid option combination for package ${packageId}: --${policy.surfaceOption} includes unknown surface "${surfaceId}" in config/public.js.`
123
+ );
124
+ }
125
+ if (surfaceDefinition.enabled !== true) {
126
+ throw createCliError(
127
+ `Invalid option combination for package ${packageId}: surface "${surfaceId}" is disabled in config/public.js.`
128
+ );
129
+ }
130
+
131
+ if (!skipWorkspaceRequirement && WORKSPACE_VISIBILITY_SET.has(visibility) && surfaceDefinition.requiresWorkspace !== true) {
132
+ throw createCliError(
133
+ `Invalid option combination for package ${packageId}: --${policy.visibilityOption} "${visibility}" requires surfaces with requiresWorkspace=true, but "${surfaceId}" has requiresWorkspace=false.`
134
+ );
135
+ }
136
+ }
137
+ }
138
+
139
+ async function validateResolvedOptionPolicies({
140
+ packageEntry,
141
+ resolvedOptions = {},
142
+ appRoot = "",
143
+ resolveConfigContext
144
+ } = {}) {
145
+ const policy = resolveSurfaceVisibilityOptionPolicy(packageEntry);
146
+ if (!policy) {
147
+ return;
148
+ }
149
+ if (!appRoot) {
150
+ return;
151
+ }
152
+
153
+ const configContext = await resolveConfigContext();
154
+ validateSurfaceVisibilityOptionPolicy({
155
+ packageEntry,
156
+ resolvedOptions,
157
+ policy,
158
+ configContext
159
+ });
160
+ }
161
+
162
+ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot = "" } = {}) {
163
+ const optionSchemas = ensureObject(packageEntry.descriptor.options);
164
+ const optionNames = Object.keys(optionSchemas);
165
+ const resolved = {};
166
+ const inlineOptionValues = ensureObject(inlineOptions);
167
+ const hasInlineOption = (name) => Object.prototype.hasOwnProperty.call(inlineOptionValues, name);
168
+ let configContext = null;
169
+
170
+ async function loadConfigContext() {
171
+ if (!configContext) {
172
+ configContext = await loadMutationWhenConfigContext(appRoot);
173
+ }
174
+ return configContext;
175
+ }
176
+
177
+ async function resolveOptionDefaultFromConfig(configPath = "") {
178
+ const normalizedConfigPath = String(configPath || "").trim();
179
+ if (!normalizedConfigPath || !appRoot) {
180
+ return "";
181
+ }
182
+
183
+ await loadConfigContext();
184
+ return normalizeWhenSourceValue(resolveWhenConfigValue(configContext, normalizedConfigPath));
185
+ }
186
+
187
+ function resolveOptionDefaultFromTemplate(template = "", optionName = "") {
188
+ const normalizedTemplate = String(template || "").trim();
189
+ if (!normalizedTemplate) {
190
+ return "";
191
+ }
192
+
193
+ try {
194
+ return String(
195
+ interpolateOptionValue(
196
+ normalizedTemplate,
197
+ resolved,
198
+ packageEntry.packageId,
199
+ `option-default:${String(optionName || "").trim()}`
200
+ )
201
+ );
202
+ } catch (error) {
203
+ const message = String(error?.message || error || "");
204
+ if (message.includes("Missing required option")) {
205
+ return "";
206
+ }
207
+ throw error;
208
+ }
209
+ }
210
+
211
+ for (const optionName of optionNames) {
212
+ const schema = ensureObject(optionSchemas[optionName]);
213
+ const allowEmpty = schema.allowEmpty === true;
214
+ if (hasInlineOption(optionName)) {
215
+ const inlineValue = String(inlineOptionValues[optionName] || "").trim();
216
+ if (inlineValue || allowEmpty) {
217
+ resolved[optionName] = inlineValue;
218
+ continue;
219
+ }
220
+ if (schema.required) {
221
+ throw createCliError(`Package ${packageEntry.packageId} option ${optionName} requires a non-empty value.`);
222
+ }
223
+ }
224
+
225
+ const defaultFromConfigPath = String(schema.defaultFromConfig || "").trim();
226
+ if (defaultFromConfigPath) {
227
+ const defaultFromConfigValue = await resolveOptionDefaultFromConfig(defaultFromConfigPath);
228
+ if (defaultFromConfigValue || allowEmpty) {
229
+ resolved[optionName] = defaultFromConfigValue;
230
+ continue;
231
+ }
232
+ }
233
+
234
+ const defaultFromOptionTemplate = String(schema.defaultFromOptionTemplate || "").trim();
235
+ if (defaultFromOptionTemplate) {
236
+ const derivedOptionValue = resolveOptionDefaultFromTemplate(defaultFromOptionTemplate, optionName);
237
+ if (derivedOptionValue || allowEmpty) {
238
+ resolved[optionName] = derivedOptionValue;
239
+ continue;
240
+ }
241
+ }
242
+
243
+ if (typeof schema.defaultValue === "string" && schema.defaultValue.trim()) {
244
+ resolved[optionName] = schema.defaultValue.trim();
245
+ continue;
246
+ }
247
+
248
+ if (schema.required) {
249
+ resolved[optionName] = await promptForRequiredOption({
250
+ ownerType: "package",
251
+ ownerId: packageEntry.packageId,
252
+ optionName,
253
+ optionSchema: schema,
254
+ stdin: io.stdin,
255
+ stdout: io.stdout
256
+ });
257
+ continue;
258
+ }
259
+
260
+ resolved[optionName] = "";
261
+ }
262
+
263
+ await validateResolvedOptionPolicies({
264
+ packageEntry,
265
+ resolvedOptions: resolved,
266
+ appRoot,
267
+ resolveConfigContext: loadConfigContext
268
+ });
269
+
270
+ return resolved;
271
+ }
272
+
273
+ function validateInlineOptionsForPackage(packageEntry, inlineOptions) {
274
+ const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
275
+ const allowedOptionNames = Object.keys(optionSchemas);
276
+ const allowed = new Set(allowedOptionNames);
277
+ const providedOptionNames = Object.keys(ensureObject(inlineOptions));
278
+ const unknownOptionNames = providedOptionNames.filter((optionName) => !allowed.has(optionName));
279
+
280
+ if (unknownOptionNames.length < 1) {
281
+ return;
282
+ }
283
+
284
+ const sortedUnknown = sortStrings(unknownOptionNames);
285
+ const suffix = allowedOptionNames.length > 0
286
+ ? ` Allowed options: ${sortStrings(allowedOptionNames).join(", ")}.`
287
+ : " This package does not accept inline options.";
288
+
289
+ throw createCliError(
290
+ `Unknown option(s) for package ${packageEntry.packageId}: ${sortedUnknown.join(", ")}.${suffix}`
291
+ );
292
+ }
293
+
294
+ export {
295
+ normalizeSurfaceIdForMutation,
296
+ parseSurfaceIdListForMutation,
297
+ resolvePackageOptions,
298
+ validateInlineOptionsForPackage
299
+ };
@@ -0,0 +1,343 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { createCliError } from "../shared/cliError.js";
5
+ import {
6
+ ensureArray,
7
+ ensureObject
8
+ } from "../shared/collectionUtils.js";
9
+ import {
10
+ BUNDLES_ROOT,
11
+ CATALOG_PACKAGES_PATH,
12
+ CLI_PACKAGE_ROOT
13
+ } from "../shared/pathResolution.js";
14
+ import {
15
+ toScopedPackageId
16
+ } from "../shared/packageIdHelpers.js";
17
+ import {
18
+ fileExists,
19
+ readJsonFile,
20
+ normalizeRelativePath
21
+ } from "./ioAndMigrations.js";
22
+ import {
23
+ validatePackageDescriptorShape,
24
+ validateAppLocalPackageDescriptorShape,
25
+ createPackageEntry,
26
+ isGeneratorPackageEntry
27
+ } from "./descriptorValidation.js";
28
+
29
+ function normalizeRelativePosixPath(pathValue) {
30
+ return String(pathValue || "")
31
+ .trim()
32
+ .replace(/\\/g, "/")
33
+ .replace(/^\/+|\/+$/g, "")
34
+ .replace(/\/{2,}/g, "/");
35
+ }
36
+
37
+ function mergePackageRegistries(...registries) {
38
+ const merged = new Map();
39
+ for (const registry of registries) {
40
+ if (!(registry instanceof Map)) {
41
+ continue;
42
+ }
43
+ for (const [packageId, packageEntry] of registry.entries()) {
44
+ merged.set(packageId, packageEntry);
45
+ }
46
+ }
47
+ return merged;
48
+ }
49
+
50
+ function validateBundleDescriptorShape(descriptor, descriptorPath) {
51
+ const normalized = ensureObject(descriptor);
52
+ const bundleId = String(normalized.bundleId || "").trim();
53
+ const version = String(normalized.version || "").trim();
54
+ const packages = ensureArray(normalized.packages).map((value) => String(value));
55
+
56
+ if (!bundleId) {
57
+ throw createCliError(`Invalid bundle descriptor at ${descriptorPath}: missing bundleId.`);
58
+ }
59
+ if (!version) {
60
+ throw createCliError(`Invalid bundle descriptor at ${descriptorPath}: missing version.`);
61
+ }
62
+ if (packages.length < 2) {
63
+ throw createCliError(`Invalid bundle descriptor at ${descriptorPath}: bundles must contain at least two packages.`);
64
+ }
65
+
66
+ return normalized;
67
+ }
68
+
69
+ async function loadAppLocalPackageRegistry(appRoot) {
70
+ const localPackagesRoot = path.join(appRoot, "packages");
71
+ if (!(await fileExists(localPackagesRoot))) {
72
+ return new Map();
73
+ }
74
+
75
+ const registry = new Map();
76
+ const entries = await readdir(localPackagesRoot, { withFileTypes: true });
77
+ for (const entry of entries) {
78
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
79
+ continue;
80
+ }
81
+
82
+ const packageRoot = path.join(localPackagesRoot, entry.name);
83
+ const packageJsonPath = path.join(packageRoot, "package.json");
84
+ const descriptorPath = path.join(packageRoot, "package.descriptor.mjs");
85
+ if (!(await fileExists(packageJsonPath)) || !(await fileExists(descriptorPath))) {
86
+ continue;
87
+ }
88
+
89
+ const packageJson = await readJsonFile(packageJsonPath);
90
+ const packageId = String(packageJson?.name || "").trim();
91
+ if (!packageId) {
92
+ throw createCliError(`Invalid app-local package at ${normalizeRelativePath(appRoot, packageRoot)}: package.json missing name.`);
93
+ }
94
+
95
+ const descriptorModule = await import(pathToFileURL(descriptorPath).href + `?t=${Date.now()}_${Math.random()}`);
96
+ const descriptor = validateAppLocalPackageDescriptorShape(descriptorModule?.default, descriptorPath, {
97
+ expectedPackageId: packageId,
98
+ fallbackVersion: String(packageJson?.version || "").trim()
99
+ });
100
+
101
+ const relativeDir = normalizeRelativePath(appRoot, packageRoot);
102
+ const descriptorRelativePath = normalizeRelativePath(appRoot, descriptorPath);
103
+ registry.set(
104
+ packageId,
105
+ createPackageEntry({
106
+ packageId: descriptor.packageId,
107
+ version: descriptor.version,
108
+ descriptor,
109
+ rootDir: packageRoot,
110
+ relativeDir,
111
+ descriptorRelativePath,
112
+ packageJson,
113
+ sourceType: "app-local-package",
114
+ source: {
115
+ packagePath: normalizeRelativePosixPath(relativeDir),
116
+ descriptorPath: descriptorRelativePath
117
+ }
118
+ })
119
+ );
120
+ }
121
+
122
+ return registry;
123
+ }
124
+
125
+ async function loadCatalogPackageRegistry() {
126
+ if (!(await fileExists(CATALOG_PACKAGES_PATH))) {
127
+ return new Map();
128
+ }
129
+
130
+ const catalog = await readJsonFile(CATALOG_PACKAGES_PATH);
131
+ const packageRecords = ensureArray(catalog?.packages);
132
+ const registry = new Map();
133
+
134
+ for (const packageRecord of packageRecords) {
135
+ const record = ensureObject(packageRecord);
136
+ const packageId = String(record.packageId || "").trim();
137
+ const descriptorPath = `${normalizeRelativePath(CLI_PACKAGE_ROOT, CATALOG_PACKAGES_PATH)}#${packageId || "unknown"}`;
138
+ const descriptor = validatePackageDescriptorShape(record.descriptor, descriptorPath);
139
+ if (!packageId) {
140
+ throw createCliError(`Invalid catalog package entry at ${descriptorPath}: missing packageId.`);
141
+ }
142
+ if (descriptor.packageId !== packageId) {
143
+ throw createCliError(
144
+ `Invalid catalog package entry at ${descriptorPath}: descriptor packageId ${descriptor.packageId} does not match catalog packageId ${packageId}.`
145
+ );
146
+ }
147
+
148
+ const version = String(record.version || descriptor.version || "").trim();
149
+ if (!version) {
150
+ throw createCliError(`Invalid catalog package entry at ${descriptorPath}: missing version.`);
151
+ }
152
+
153
+ registry.set(
154
+ packageId,
155
+ createPackageEntry({
156
+ packageId,
157
+ version,
158
+ descriptor: {
159
+ ...descriptor,
160
+ version
161
+ },
162
+ rootDir: "",
163
+ relativeDir: "",
164
+ descriptorRelativePath: descriptorPath,
165
+ packageJson: {
166
+ name: packageId,
167
+ version
168
+ },
169
+ sourceType: "catalog",
170
+ source: {
171
+ descriptorPath
172
+ }
173
+ })
174
+ );
175
+ }
176
+
177
+ return registry;
178
+ }
179
+
180
+ async function loadPackageRegistry() {
181
+ const catalogRegistry = await loadCatalogPackageRegistry();
182
+ if (catalogRegistry.size === 0) {
183
+ throw createCliError(
184
+ "Unable to load package registry from @jskit-ai/jskit-catalog. Install it alongside @jskit-ai/jskit-cli or set JSKIT_CATALOG_PACKAGES_PATH."
185
+ );
186
+ }
187
+
188
+ return catalogRegistry;
189
+ }
190
+
191
+ async function loadInstalledNodeModulePackageEntry({ appRoot, packageId }) {
192
+ const normalizedPackageId = String(packageId || "").trim();
193
+ if (!normalizedPackageId) {
194
+ return null;
195
+ }
196
+
197
+ const packageRoot = path.resolve(appRoot, "node_modules", ...normalizedPackageId.split("/"));
198
+ const packageJsonPath = path.join(packageRoot, "package.json");
199
+ if (!(await fileExists(packageJsonPath))) {
200
+ return null;
201
+ }
202
+
203
+ const packageJson = await readJsonFile(packageJsonPath);
204
+ const resolvedPackageId = String(packageJson?.name || "").trim() || normalizedPackageId;
205
+ const descriptorPath = path.join(packageRoot, "package.descriptor.mjs");
206
+ if (!(await fileExists(descriptorPath))) {
207
+ return null;
208
+ }
209
+
210
+ const descriptorModule = await import(pathToFileURL(descriptorPath).href + `?t=${Date.now()}_${Math.random()}`);
211
+ const descriptor = validateAppLocalPackageDescriptorShape(descriptorModule?.default, descriptorPath, {
212
+ expectedPackageId: resolvedPackageId,
213
+ fallbackVersion: String(packageJson?.version || "").trim()
214
+ });
215
+ const relativeDir = normalizeRelativePath(appRoot, packageRoot);
216
+ const descriptorRelativePath = normalizeRelativePath(appRoot, descriptorPath);
217
+
218
+ return createPackageEntry({
219
+ packageId: descriptor.packageId,
220
+ version: descriptor.version,
221
+ descriptor,
222
+ rootDir: packageRoot,
223
+ relativeDir,
224
+ descriptorRelativePath,
225
+ packageJson,
226
+ sourceType: "npm-installed-package",
227
+ source: {
228
+ packagePath: normalizeRelativePosixPath(relativeDir),
229
+ descriptorPath: descriptorRelativePath
230
+ }
231
+ });
232
+ }
233
+
234
+ async function resolveInstalledNodeModulePackageEntry({ appRoot, packageId }) {
235
+ const raw = String(packageId || "").trim();
236
+ if (!raw) {
237
+ return null;
238
+ }
239
+
240
+ const candidates = [];
241
+ const seen = new Set();
242
+ const appendCandidate = (value) => {
243
+ const candidate = String(value || "").trim();
244
+ if (!candidate || seen.has(candidate)) {
245
+ return;
246
+ }
247
+ seen.add(candidate);
248
+ candidates.push(candidate);
249
+ };
250
+
251
+ appendCandidate(raw);
252
+ appendCandidate(toScopedPackageId(raw));
253
+
254
+ for (const candidateId of candidates) {
255
+ const entry = await loadInstalledNodeModulePackageEntry({
256
+ appRoot,
257
+ packageId: candidateId
258
+ });
259
+ if (entry) {
260
+ return entry;
261
+ }
262
+ }
263
+
264
+ return null;
265
+ }
266
+
267
+ async function hydratePackageRegistryFromInstalledNodeModules({
268
+ appRoot,
269
+ packageRegistry,
270
+ seedPackageIds = []
271
+ }) {
272
+ const queue = ensureArray(seedPackageIds)
273
+ .map((value) => String(value || "").trim())
274
+ .filter(Boolean);
275
+ const visited = new Set();
276
+
277
+ while (queue.length > 0) {
278
+ const packageId = queue.shift();
279
+ if (!packageId || visited.has(packageId)) {
280
+ continue;
281
+ }
282
+ visited.add(packageId);
283
+
284
+ let packageEntry = packageRegistry.get(packageId);
285
+ if (!packageEntry) {
286
+ const resolvedEntry = await resolveInstalledNodeModulePackageEntry({
287
+ appRoot,
288
+ packageId
289
+ });
290
+ if (!resolvedEntry) {
291
+ continue;
292
+ }
293
+
294
+ packageRegistry.set(resolvedEntry.packageId, resolvedEntry);
295
+ packageEntry = resolvedEntry;
296
+ if (resolvedEntry.packageId !== packageId && !visited.has(resolvedEntry.packageId)) {
297
+ queue.push(resolvedEntry.packageId);
298
+ }
299
+ }
300
+
301
+ const dependsOn = ensureArray(packageEntry?.descriptor?.dependsOn).map((value) => String(value || "").trim()).filter(Boolean);
302
+ for (const dependencyId of dependsOn) {
303
+ if (!visited.has(dependencyId)) {
304
+ queue.push(dependencyId);
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ async function loadBundleRegistry() {
311
+ if (!(await fileExists(BUNDLES_ROOT))) {
312
+ return new Map();
313
+ }
314
+
315
+ const bundles = new Map();
316
+ const entries = await readdir(BUNDLES_ROOT, { withFileTypes: true });
317
+ for (const entry of entries) {
318
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
319
+ continue;
320
+ }
321
+
322
+ const descriptorPath = path.join(BUNDLES_ROOT, entry.name, "bundle.descriptor.mjs");
323
+ if (!(await fileExists(descriptorPath))) {
324
+ continue;
325
+ }
326
+
327
+ const descriptorModule = await import(pathToFileURL(descriptorPath).href);
328
+ const descriptor = validateBundleDescriptorShape(descriptorModule?.default, descriptorPath);
329
+ bundles.set(descriptor.bundleId, descriptor);
330
+ }
331
+
332
+ return bundles;
333
+ }
334
+
335
+ export {
336
+ isGeneratorPackageEntry,
337
+ mergePackageRegistries,
338
+ loadAppLocalPackageRegistry,
339
+ loadPackageRegistry,
340
+ resolveInstalledNodeModulePackageEntry,
341
+ hydratePackageRegistryFromInstalledNodeModules,
342
+ loadBundleRegistry
343
+ };