@open-mercato/cli 0.10.1-develop.7990.1.82d0a183c5 → 0.10.1-develop.7994.1.c9f31972aa
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/.turbo/turbo-build.log +1 -1
- package/dist/agentic/guides/module-facts.json +162 -162
- package/dist/agentic/guides/module-facts.v2.json +162 -162
- package/dist/agentic/guides/module-tool.md +21 -0
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/availability/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
- package/dist/agentic/guides/modules/cart/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/forms/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/promotions/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/seeds/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/agentic/shared/ai/harness/cases.json +23 -9
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +90 -4
- package/dist/lib/__integration__/TC-INT-007.spec.js +207 -19
- package/dist/lib/__integration__/TC-INT-007.spec.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-009.spec.js +267 -0
- package/dist/lib/__integration__/TC-INT-009.spec.js.map +7 -0
- package/dist/lib/eject.js +25 -9
- package/dist/lib/eject.js.map +2 -2
- package/dist/lib/generators/entity-ids.js +2 -0
- package/dist/lib/generators/entity-ids.js.map +2 -2
- package/dist/lib/generators/module-di.js +2 -0
- package/dist/lib/generators/module-di.js.map +2 -2
- package/dist/lib/generators/module-entities.js +2 -0
- package/dist/lib/generators/module-entities.js.map +2 -2
- package/dist/lib/generators/module-package-sources.js +6 -4
- package/dist/lib/generators/module-package-sources.js.map +2 -2
- package/dist/lib/generators/module-registry.js +32 -86
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/openapi.js +2 -0
- package/dist/lib/generators/openapi.js.map +2 -2
- package/dist/lib/generators/source-exports.js +73 -0
- package/dist/lib/generators/source-exports.js.map +7 -0
- package/dist/lib/init-summary.js +44 -0
- package/dist/lib/init-summary.js.map +7 -0
- package/dist/lib/module-github.js +98 -0
- package/dist/lib/module-github.js.map +7 -0
- package/dist/lib/module-identity.js +131 -0
- package/dist/lib/module-identity.js.map +7 -0
- package/dist/lib/module-inspection.js +124 -0
- package/dist/lib/module-inspection.js.map +7 -0
- package/dist/lib/module-install.js +150 -21
- package/dist/lib/module-install.js.map +3 -3
- package/dist/lib/module-package.js +205 -15
- package/dist/lib/module-package.js.map +2 -2
- package/dist/lib/modules-config.js +69 -30
- package/dist/lib/modules-config.js.map +2 -2
- package/dist/lib/resolver.js +33 -34
- package/dist/lib/resolver.js.map +2 -2
- package/dist/mercato.js +14 -23
- package/dist/mercato.js.map +2 -2
- package/package.json +7 -7
- package/src/__tests__/mercato.test.ts +14 -4
- package/src/lib/__fixtures__/official-module-package/dist/modules/test_package/widgets/injection/test/widget.js +1 -0
- package/src/lib/__integration__/TC-INT-007.spec.ts +219 -19
- package/src/lib/__integration__/TC-INT-009.spec.ts +279 -0
- package/src/lib/__tests__/eject.test.ts +98 -4
- package/src/lib/__tests__/init-summary.test.ts +50 -0
- package/src/lib/__tests__/module-github.test.ts +108 -0
- package/src/lib/__tests__/module-identity.test.ts +144 -0
- package/src/lib/__tests__/module-inspection.test.ts +195 -0
- package/src/lib/__tests__/module-install.test.ts +399 -10
- package/src/lib/__tests__/module-package.test.ts +154 -0
- package/src/lib/__tests__/modules-config.test.ts +55 -0
- package/src/lib/__tests__/resolver.packages.test.ts +29 -0
- package/src/lib/eject.ts +28 -10
- package/src/lib/generators/__tests__/module-id-collisions.test.ts +146 -0
- package/src/lib/generators/__tests__/module-package-sources.test.ts +19 -2
- package/src/lib/generators/__tests__/module-requirements.test.ts +56 -0
- package/src/lib/generators/entity-ids.ts +2 -0
- package/src/lib/generators/module-di.ts +2 -0
- package/src/lib/generators/module-entities.ts +2 -0
- package/src/lib/generators/module-package-sources.ts +7 -5
- package/src/lib/generators/module-registry.ts +33 -115
- package/src/lib/generators/openapi.ts +3 -0
- package/src/lib/generators/source-exports.ts +93 -0
- package/src/lib/init-summary.ts +54 -0
- package/src/lib/module-github.ts +109 -0
- package/src/lib/module-identity.ts +145 -0
- package/src/lib/module-inspection.ts +133 -0
- package/src/lib/module-install.ts +170 -22
- package/src/lib/module-package.ts +243 -16
- package/src/lib/modules-config.ts +80 -32
- package/src/lib/resolver.ts +35 -39
- package/src/mercato.ts +14 -24
- package/src/types/cross-spawn.d.ts +2 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript-js";
|
|
4
|
+
import { normalizeModuleLocator } from "@open-mercato/shared/modules/module-locators";
|
|
5
|
+
import { readEnabledModules } from "./modules-config.js";
|
|
6
|
+
function readJson(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
9
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10
|
+
} catch {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function readMetadataLocator(moduleBase) {
|
|
15
|
+
const indexPath = ["index.ts", "index.js"].map((name) => path.join(moduleBase, name)).find((candidate) => fs.existsSync(candidate));
|
|
16
|
+
if (!indexPath) return void 0;
|
|
17
|
+
const source = ts.createSourceFile(indexPath, fs.readFileSync(indexPath, "utf8"), ts.ScriptTarget.Latest, true);
|
|
18
|
+
let locator;
|
|
19
|
+
function visit(node) {
|
|
20
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === "metadata" && node.initializer) {
|
|
21
|
+
let initializer = node.initializer;
|
|
22
|
+
while (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer) || ts.isParenthesizedExpression(initializer)) initializer = initializer.expression;
|
|
23
|
+
if (ts.isObjectLiteralExpression(initializer)) {
|
|
24
|
+
for (const property of initializer.properties) {
|
|
25
|
+
if (ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) && property.name.text === "locator" && ts.isStringLiteralLike(property.initializer)) locator = property.initializer.text;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
ts.forEachChild(node, visit);
|
|
30
|
+
}
|
|
31
|
+
visit(source);
|
|
32
|
+
return locator;
|
|
33
|
+
}
|
|
34
|
+
function sourceLocator(value) {
|
|
35
|
+
if (typeof value !== "string") return void 0;
|
|
36
|
+
const locator = normalizeModuleLocator(value);
|
|
37
|
+
return locator?.startsWith("npm:") || locator?.startsWith("github:") ? locator : void 0;
|
|
38
|
+
}
|
|
39
|
+
function installedDependencyLocator(specifier, canonicalName) {
|
|
40
|
+
if (typeof specifier !== "string") return void 0;
|
|
41
|
+
if (/^(?:github:|github\.com\/|(?:git\+)?https?:\/\/github\.com\/|git@github\.com:|(?:git\+)?ssh:\/\/|[a-z0-9][a-z0-9-]*\/[a-z0-9._-]+(?:[#@]|$))/i.test(specifier)) {
|
|
42
|
+
const locator = sourceLocator(specifier);
|
|
43
|
+
return locator?.startsWith("github:") ? locator : void 0;
|
|
44
|
+
}
|
|
45
|
+
if (specifier.startsWith("npm:")) {
|
|
46
|
+
const aliasTarget = specifier.match(/^npm:(@[^/]+\/[^@]+|[^@]+)(?:@.+)?$/)?.[1];
|
|
47
|
+
return aliasTarget ? sourceLocator(`npm:${aliasTarget}`) : void 0;
|
|
48
|
+
}
|
|
49
|
+
if (!canonicalName || /^(?:[a-z][a-z0-9+.-]*:|[./]|https?:)/i.test(specifier)) return void 0;
|
|
50
|
+
return sourceLocator(`npm:${canonicalName}`);
|
|
51
|
+
}
|
|
52
|
+
function assertDeclaredIdentity(entry, observedLocator, canonicalName) {
|
|
53
|
+
const binding = entry.from ?? "@open-mercato/core";
|
|
54
|
+
if (entry.packageName && canonicalName && entry.packageName !== canonicalName) {
|
|
55
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared package '${entry.packageName}' disagrees with installed package '${canonicalName}'. Restore the intended binding or correct src/modules.ts, then regenerate.`);
|
|
56
|
+
}
|
|
57
|
+
const declaredLocator = sourceLocator(entry.locator);
|
|
58
|
+
if (declaredLocator && observedLocator && declaredLocator !== observedLocator) {
|
|
59
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared origin '${declaredLocator}' disagrees with observed origin '${observedLocator}'. Disable the old provider before replacing it, then regenerate.`);
|
|
60
|
+
}
|
|
61
|
+
if (declaredLocator?.startsWith("npm:") && canonicalName && declaredLocator !== sourceLocator(`npm:${canonicalName}`)) {
|
|
62
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared origin '${declaredLocator}' disagrees with installed package '${canonicalName}'. Correct src/modules.ts to select the intended provider, then regenerate.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readModuleLocators(entry, resolver, appManifest, packageManifests) {
|
|
66
|
+
const from = entry.from ?? "@open-mercato/core";
|
|
67
|
+
if (from === "@app") {
|
|
68
|
+
const retainedLocator = sourceLocator(entry.locator ?? readMetadataLocator(resolver.getModulePaths(entry).appBase));
|
|
69
|
+
assertDeclaredIdentity(entry, retainedLocator, entry.packageName);
|
|
70
|
+
return retainedLocator ? [retainedLocator] : [];
|
|
71
|
+
}
|
|
72
|
+
let manifest = packageManifests.get(from);
|
|
73
|
+
if (!manifest) {
|
|
74
|
+
manifest = readJson(path.join(resolver.getPackageRoot(from), "package.json"));
|
|
75
|
+
packageManifests.set(from, manifest);
|
|
76
|
+
}
|
|
77
|
+
const canonicalName = typeof manifest.name === "string" ? manifest.name : void 0;
|
|
78
|
+
const observedLocators = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
80
|
+
const dependencies = appManifest[section];
|
|
81
|
+
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
|
|
82
|
+
const specifier = dependencies[from];
|
|
83
|
+
const locator2 = installedDependencyLocator(specifier, canonicalName);
|
|
84
|
+
if (locator2?.startsWith("npm:") && canonicalName !== from && typeof specifier === "string" && !specifier.startsWith("npm:")) {
|
|
85
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': installed package '${canonicalName}' does not match the dependency binding. Restore the intended package or declare an explicit npm alias, then regenerate.`);
|
|
86
|
+
}
|
|
87
|
+
if (locator2) observedLocators.add(locator2);
|
|
88
|
+
}
|
|
89
|
+
if (observedLocators.size > 1) {
|
|
90
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': dependency sections declare conflicting origins. Keep one provider for this binding in package.json, then regenerate.`);
|
|
91
|
+
}
|
|
92
|
+
const observedLocator = [...observedLocators][0];
|
|
93
|
+
if (observedLocator?.startsWith("npm:") && canonicalName && observedLocator !== sourceLocator(`npm:${canonicalName}`)) {
|
|
94
|
+
throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': dependency origin '${observedLocator}' disagrees with installed package '${canonicalName}'. Restore the intended package through the package manager, then regenerate.`);
|
|
95
|
+
}
|
|
96
|
+
assertDeclaredIdentity(entry, observedLocator, canonicalName);
|
|
97
|
+
const locator = observedLocator ?? sourceLocator(entry.locator);
|
|
98
|
+
return locator ? [locator] : [];
|
|
99
|
+
}
|
|
100
|
+
function getModuleLocators(entry, resolver) {
|
|
101
|
+
return readModuleLocators(entry, resolver, readJson(path.join(resolver.getAppDir(), "package.json")), /* @__PURE__ */ new Map());
|
|
102
|
+
}
|
|
103
|
+
function assertUniqueEnabledModuleIds(enabledModules, resolver) {
|
|
104
|
+
const byId = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const entry of enabledModules) {
|
|
106
|
+
const existing = byId.get(entry.id);
|
|
107
|
+
if (existing) {
|
|
108
|
+
const describe = (provider) => {
|
|
109
|
+
const binding = provider.from ?? "@open-mercato/core";
|
|
110
|
+
const source = sourceLocator(provider.locator);
|
|
111
|
+
return binding === "@app" ? `@app (${resolver.getModulePaths(provider).appBase})${source ? `, origin ${source}` : ""}` : `binding '${binding}'${source ? `, origin ${source}` : ""}`;
|
|
112
|
+
};
|
|
113
|
+
throw new Error(`[generate] Duplicate enabled module ID '${entry.id}': ${describe(existing)} and ${describe(entry)}. Keep one provider in src/modules.ts, then run yarn mercato generate. Installing under another package alias does not allow both module IDs to run.`);
|
|
114
|
+
}
|
|
115
|
+
byId.set(entry.id, entry);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function getEnabledModuleIdentities(resolver, enabledModules = resolver.loadEnabledModules()) {
|
|
119
|
+
const modulesPath = typeof resolver.getModulesConfigPath === "function" ? resolver.getModulesConfigPath() : path.join(resolver.getAppDir(), "src", "modules.ts");
|
|
120
|
+
if (fs.existsSync(modulesPath)) assertUniqueEnabledModuleIds(readEnabledModules(modulesPath), resolver);
|
|
121
|
+
assertUniqueEnabledModuleIds(enabledModules, resolver);
|
|
122
|
+
const appManifest = readJson(path.join(resolver.getAppDir(), "package.json"));
|
|
123
|
+
const packageManifests = /* @__PURE__ */ new Map();
|
|
124
|
+
return enabledModules.map((entry) => ({ id: entry.id, locators: readModuleLocators(entry, resolver, appManifest, packageManifests) }));
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
assertUniqueEnabledModuleIds,
|
|
128
|
+
getEnabledModuleIdentities,
|
|
129
|
+
getModuleLocators
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=module-identity.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/module-identity.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport ts from 'typescript-js'\nimport { normalizeModuleLocator, type ModuleIdentity } from '@open-mercato/shared/modules/module-locators'\nimport type { ModuleEntry, PackageResolver } from './resolver'\nimport { readEnabledModules } from './modules-config'\n\nfunction readJson(filePath: string): Record<string, unknown> {\n try {\n const value: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'))\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n } catch {\n return {}\n }\n}\n\nfunction readMetadataLocator(moduleBase: string): string | undefined {\n const indexPath = ['index.ts', 'index.js'].map((name) => path.join(moduleBase, name)).find((candidate) => fs.existsSync(candidate))\n if (!indexPath) return undefined\n const source = ts.createSourceFile(indexPath, fs.readFileSync(indexPath, 'utf8'), ts.ScriptTarget.Latest, true)\n let locator: string | undefined\n function visit(node: ts.Node): void {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'metadata' && node.initializer) {\n let initializer = node.initializer\n while (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer) || ts.isParenthesizedExpression(initializer)) initializer = initializer.expression\n if (ts.isObjectLiteralExpression(initializer)) {\n for (const property of initializer.properties) {\n if (ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) && property.name.text === 'locator' && ts.isStringLiteralLike(property.initializer)) locator = property.initializer.text\n }\n }\n }\n ts.forEachChild(node, visit)\n }\n visit(source)\n return locator\n}\n\nfunction sourceLocator(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const locator = normalizeModuleLocator(value)\n return locator?.startsWith('npm:') || locator?.startsWith('github:') ? locator : undefined\n}\n\nfunction installedDependencyLocator(specifier: unknown, canonicalName: string | undefined): string | undefined {\n if (typeof specifier !== 'string') return undefined\n if (/^(?:github:|github\\.com\\/|(?:git\\+)?https?:\\/\\/github\\.com\\/|git@github\\.com:|(?:git\\+)?ssh:\\/\\/|[a-z0-9][a-z0-9-]*\\/[a-z0-9._-]+(?:[#@]|$))/i.test(specifier)) {\n const locator = sourceLocator(specifier)\n return locator?.startsWith('github:') ? locator : undefined\n }\n if (specifier.startsWith('npm:')) {\n const aliasTarget = specifier.match(/^npm:(@[^/]+\\/[^@]+|[^@]+)(?:@.+)?$/)?.[1]\n return aliasTarget ? sourceLocator(`npm:${aliasTarget}`) : undefined\n }\n if (!canonicalName || /^(?:[a-z][a-z0-9+.-]*:|[./]|https?:)/i.test(specifier)) return undefined\n return sourceLocator(`npm:${canonicalName}`)\n}\n\nfunction assertDeclaredIdentity(entry: ModuleEntry, observedLocator: string | undefined, canonicalName: string | undefined): void {\n const binding = entry.from ?? '@open-mercato/core'\n if (entry.packageName && canonicalName && entry.packageName !== canonicalName) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared package '${entry.packageName}' disagrees with installed package '${canonicalName}'. Restore the intended binding or correct src/modules.ts, then regenerate.`)\n }\n const declaredLocator = sourceLocator(entry.locator)\n if (declaredLocator && observedLocator && declaredLocator !== observedLocator) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared origin '${declaredLocator}' disagrees with observed origin '${observedLocator}'. Disable the old provider before replacing it, then regenerate.`)\n }\n if (declaredLocator?.startsWith('npm:') && canonicalName && declaredLocator !== sourceLocator(`npm:${canonicalName}`)) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${binding}': declared origin '${declaredLocator}' disagrees with installed package '${canonicalName}'. Correct src/modules.ts to select the intended provider, then regenerate.`)\n }\n}\n\nfunction readModuleLocators(\n entry: ModuleEntry,\n resolver: PackageResolver,\n appManifest: Record<string, unknown>,\n packageManifests: Map<string, Record<string, unknown>>,\n): string[] {\n const from = entry.from ?? '@open-mercato/core'\n if (from === '@app') {\n const retainedLocator = sourceLocator(entry.locator ?? readMetadataLocator(resolver.getModulePaths(entry).appBase))\n assertDeclaredIdentity(entry, retainedLocator, entry.packageName)\n return retainedLocator ? [retainedLocator] : []\n }\n\n let manifest = packageManifests.get(from)\n if (!manifest) {\n manifest = readJson(path.join(resolver.getPackageRoot(from), 'package.json'))\n packageManifests.set(from, manifest)\n }\n const canonicalName = typeof manifest.name === 'string' ? manifest.name : undefined\n const observedLocators = new Set<string>()\n for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n const dependencies = appManifest[section]\n if (!dependencies || typeof dependencies !== 'object' || Array.isArray(dependencies)) continue\n const specifier = (dependencies as Record<string, unknown>)[from]\n const locator = installedDependencyLocator(specifier, canonicalName)\n if (locator?.startsWith('npm:') && canonicalName !== from && typeof specifier === 'string' && !specifier.startsWith('npm:')) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': installed package '${canonicalName}' does not match the dependency binding. Restore the intended package or declare an explicit npm alias, then regenerate.`)\n }\n if (locator) observedLocators.add(locator)\n }\n if (observedLocators.size > 1) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': dependency sections declare conflicting origins. Keep one provider for this binding in package.json, then regenerate.`)\n }\n const observedLocator = [...observedLocators][0]\n if (observedLocator?.startsWith('npm:') && canonicalName && observedLocator !== sourceLocator(`npm:${canonicalName}`)) {\n throw new Error(`[modules] Cannot resolve module '${entry.id}' from binding '${from}': dependency origin '${observedLocator}' disagrees with installed package '${canonicalName}'. Restore the intended package through the package manager, then regenerate.`)\n }\n assertDeclaredIdentity(entry, observedLocator, canonicalName)\n const locator = observedLocator ?? sourceLocator(entry.locator)\n return locator ? [locator] : []\n}\n\nexport function getModuleLocators(entry: ModuleEntry, resolver: PackageResolver): string[] {\n return readModuleLocators(entry, resolver, readJson(path.join(resolver.getAppDir(), 'package.json')), new Map())\n}\n\nexport function assertUniqueEnabledModuleIds(enabledModules: readonly ModuleEntry[], resolver: PackageResolver): void {\n const byId = new Map<string, ModuleEntry>()\n for (const entry of enabledModules) {\n const existing = byId.get(entry.id)\n if (existing) {\n const describe = (provider: ModuleEntry): string => {\n const binding = provider.from ?? '@open-mercato/core'\n const source = sourceLocator(provider.locator)\n return binding === '@app'\n ? `@app (${resolver.getModulePaths(provider).appBase})${source ? `, origin ${source}` : ''}`\n : `binding '${binding}'${source ? `, origin ${source}` : ''}`\n }\n throw new Error(`[generate] Duplicate enabled module ID '${entry.id}': ${describe(existing)} and ${describe(entry)}. Keep one provider in src/modules.ts, then run yarn mercato generate. Installing under another package alias does not allow both module IDs to run.`)\n }\n byId.set(entry.id, entry)\n }\n}\n\nexport function getEnabledModuleIdentities(resolver: PackageResolver, enabledModules: readonly ModuleEntry[] = resolver.loadEnabledModules()): ModuleIdentity[] {\n const modulesPath = typeof resolver.getModulesConfigPath === 'function'\n ? resolver.getModulesConfigPath()\n : path.join(resolver.getAppDir(), 'src', 'modules.ts')\n if (fs.existsSync(modulesPath)) assertUniqueEnabledModuleIds(readEnabledModules(modulesPath), resolver)\n assertUniqueEnabledModuleIds(enabledModules, resolver)\n const appManifest = readJson(path.join(resolver.getAppDir(), 'package.json'))\n const packageManifests = new Map<string, Record<string, unknown>>()\n return enabledModules.map((entry) => ({ id: entry.id, locators: readModuleLocators(entry, resolver, appManifest, packageManifests) }))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["locator"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import spawn from "cross-spawn";
|
|
6
|
+
import { assertModulePackageExports, isModulePackageExportError } from "./module-package.js";
|
|
7
|
+
function record(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9
|
+
}
|
|
10
|
+
function inspectFiles(manifest, packageName, files, requiredModuleIds, readSource) {
|
|
11
|
+
if (manifest.name !== packageName) throw new Error(`Inspected package does not match "${packageName}". No package was installed.`);
|
|
12
|
+
const moduleIds = [...new Set(files.flatMap((file) => {
|
|
13
|
+
const match = /^src\/modules\/([a-z0-9]+(?:_[a-z0-9]+)*)\/index\.ts$/.exec(file);
|
|
14
|
+
return match && files.includes(`dist/modules/${match[1]}/index.js`) ? [match[1]] : [];
|
|
15
|
+
}))];
|
|
16
|
+
const checkedModuleIds = requiredModuleIds.length ? requiredModuleIds : moduleIds.length === 1 ? moduleIds : [];
|
|
17
|
+
for (const moduleId of checkedModuleIds) {
|
|
18
|
+
if (!moduleIds.includes(moduleId)) throw new Error(`Package "${packageName}" is missing source or compiled descriptors for "${moduleId}". No package was installed.`);
|
|
19
|
+
assertModulePackageExports(manifest, packageName, moduleId, files, readSource);
|
|
20
|
+
}
|
|
21
|
+
if (moduleIds.length === 0) throw new Error(`Package "${packageName}" is missing source or compiled module descriptors. Build the package before retrying. No package was installed.`);
|
|
22
|
+
return { moduleIds, ...typeof manifest.version === "string" ? { version: manifest.version } : {} };
|
|
23
|
+
}
|
|
24
|
+
function inspectArchive(archive, packageName, requiredModuleIds) {
|
|
25
|
+
const manifest = record(JSON.parse(execFileSync("tar", ["-xOf", archive, "package/package.json"], { encoding: "utf8", timeout: 15e3 })));
|
|
26
|
+
const files = execFileSync("tar", ["-tf", archive], { encoding: "utf8", timeout: 15e3, maxBuffer: 16 * 1024 * 1024 }).split("\n").filter((file) => file.startsWith("package/")).map((file) => file.slice("package/".length));
|
|
27
|
+
return inspectFiles(manifest, packageName, files, requiredModuleIds, (relativePath) => execFileSync("tar", ["-xOf", archive, `package/${relativePath}`], { encoding: "utf8", timeout: 15e3, maxBuffer: 1024 * 1024 }));
|
|
28
|
+
}
|
|
29
|
+
function packageFiles(packageRoot, relativeDir = "") {
|
|
30
|
+
return fs.readdirSync(path.join(packageRoot, relativeDir), { withFileTypes: true }).flatMap((entry) => {
|
|
31
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) return [];
|
|
32
|
+
const relativePath = path.posix.join(relativeDir, entry.name);
|
|
33
|
+
return entry.isDirectory() ? packageFiles(packageRoot, relativePath) : entry.isFile() ? [relativePath] : [];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function isModernYarnProject(appDir) {
|
|
37
|
+
let directory = appDir;
|
|
38
|
+
while (true) {
|
|
39
|
+
if (fs.existsSync(path.join(directory, ".yarnrc.yml"))) return true;
|
|
40
|
+
const manifestPath = path.join(directory, "package.json");
|
|
41
|
+
if (fs.existsSync(manifestPath)) {
|
|
42
|
+
const manifest = record(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
43
|
+
if (typeof manifest.packageManager === "string" && /^yarn@(?:[2-9]|[1-9][0-9]+)\./.test(manifest.packageManager)) return true;
|
|
44
|
+
}
|
|
45
|
+
const parent = path.dirname(directory);
|
|
46
|
+
if (parent === directory) return false;
|
|
47
|
+
directory = parent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function yarnNpmConfiguration(appDir, packageName, temporaryDir) {
|
|
51
|
+
if (!isModernYarnProject(appDir)) return [];
|
|
52
|
+
const settings = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const setting of ["npmRegistryServer", "npmScopes", "npmRegistries", "npmAuthToken", "npmAuthIdent"]) {
|
|
54
|
+
const result = spawn.sync(process.platform === "win32" ? "yarn.cmd" : "yarn", ["config", "get", setting, "--json", "--no-redacted"], {
|
|
55
|
+
cwd: appDir,
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
58
|
+
timeout: 15e3
|
|
59
|
+
});
|
|
60
|
+
if (result.error || result.status !== 0) throw new Error("Cannot inspect project package registry configuration");
|
|
61
|
+
settings.set(setting, JSON.parse(result.stdout));
|
|
62
|
+
}
|
|
63
|
+
const scope = packageName.startsWith("@") ? packageName.slice(1).split("/")[0] : void 0;
|
|
64
|
+
const scopeSettings = scope ? record(record(settings.get("npmScopes"))[scope]) : {};
|
|
65
|
+
const registry = typeof scopeSettings.npmRegistryServer === "string" ? scopeSettings.npmRegistryServer : settings.get("npmRegistryServer");
|
|
66
|
+
if (typeof registry !== "string") throw new Error("Missing project package registry");
|
|
67
|
+
const registryUrl = new URL(registry);
|
|
68
|
+
if (!["http:", "https:"].includes(registryUrl.protocol) || registryUrl.username || registryUrl.password) throw new Error("Invalid project package registry");
|
|
69
|
+
const registryKey = `//${registryUrl.host}${registryUrl.pathname}`.replace(/\/$/, "");
|
|
70
|
+
const registries = record(settings.get("npmRegistries"));
|
|
71
|
+
const registrySettings = record(registries[registry.replace(/\/$/, "")] ?? registries[registryKey] ?? registries[`${registryKey}/`]);
|
|
72
|
+
const auth = scopeSettings.npmAuthToken || scopeSettings.npmAuthIdent ? scopeSettings : registrySettings.npmAuthToken || registrySettings.npmAuthIdent ? registrySettings : { npmAuthToken: settings.get("npmAuthToken"), npmAuthIdent: settings.get("npmAuthIdent") };
|
|
73
|
+
const userConfig = process.env.NPM_CONFIG_USERCONFIG ?? process.env.npm_config_userconfig ?? path.join(os.homedir(), ".npmrc");
|
|
74
|
+
const existingConfig = fs.existsSync(userConfig) ? fs.readFileSync(userConfig, "utf8") : "";
|
|
75
|
+
const lines = [existingConfig, `registry=${JSON.stringify(registry)}`];
|
|
76
|
+
if (scope) lines.push(`@${scope}:registry=${JSON.stringify(registry)}`);
|
|
77
|
+
if (typeof auth.npmAuthToken === "string" && auth.npmAuthToken) lines.push(`${registryKey}/:_authToken=${JSON.stringify(auth.npmAuthToken)}`);
|
|
78
|
+
else if (typeof auth.npmAuthIdent === "string" && auth.npmAuthIdent) lines.push(`${registryKey}/:_auth=${Buffer.from(auth.npmAuthIdent).toString("base64")}`);
|
|
79
|
+
const configPath = path.join(temporaryDir, "inspection.npmrc");
|
|
80
|
+
fs.writeFileSync(configPath, `${lines.join("\n")}
|
|
81
|
+
`, { mode: 384 });
|
|
82
|
+
return ["--userconfig", configPath, "--registry", registry, ...scope ? [`--@${scope}:registry=${registry}`] : []];
|
|
83
|
+
}
|
|
84
|
+
function inspectNpmModuleTarget(packageSpec, packageName, appDir, requiredModuleIds = []) {
|
|
85
|
+
const filePrefix = `${packageName}@file:`;
|
|
86
|
+
if (packageSpec.startsWith(filePrefix)) {
|
|
87
|
+
const packageRoot = path.resolve(appDir, packageSpec.slice(filePrefix.length));
|
|
88
|
+
if (fs.statSync(packageRoot).isFile()) return { packageName, packageSpec, ...inspectArchive(packageRoot, packageName, requiredModuleIds) };
|
|
89
|
+
const manifest = record(JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")));
|
|
90
|
+
const inspected = inspectFiles(manifest, packageName, packageFiles(packageRoot), requiredModuleIds, (relativePath) => fs.readFileSync(path.join(packageRoot, relativePath), "utf8"));
|
|
91
|
+
return { packageName, packageSpec, ...inspected };
|
|
92
|
+
}
|
|
93
|
+
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), "mercato-module-inspection-"));
|
|
94
|
+
let inspectionStep = "read the project registry configuration";
|
|
95
|
+
try {
|
|
96
|
+
const configurationArgs = yarnNpmConfiguration(appDir, packageName, temporaryDir);
|
|
97
|
+
inspectionStep = "fetch the exact npm artifact";
|
|
98
|
+
const packedProcess = spawn.sync(process.platform === "win32" ? "npm.cmd" : "npm", ["pack", packageSpec, "--ignore-scripts", "--json", "--pack-destination", temporaryDir, ...configurationArgs], {
|
|
99
|
+
cwd: appDir,
|
|
100
|
+
encoding: "utf8",
|
|
101
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
+
timeout: 6e4,
|
|
103
|
+
maxBuffer: 16 * 1024 * 1024
|
|
104
|
+
});
|
|
105
|
+
if (packedProcess.error || packedProcess.status !== 0) throw new Error("Artifact inspection failed");
|
|
106
|
+
inspectionStep = "parse the npm artifact response";
|
|
107
|
+
const packages = JSON.parse(packedProcess.stdout);
|
|
108
|
+
const packed = record(Array.isArray(packages) ? packages[0] : void 0);
|
|
109
|
+
if (packed.name !== packageName || typeof packed.version !== "string" || !/^[0-9A-Za-z.+-]+$/.test(packed.version) || typeof packed.filename !== "string" || path.basename(packed.filename) !== packed.filename) throw new Error("Invalid packed manifest");
|
|
110
|
+
inspectionStep = "validate the packed module artifact";
|
|
111
|
+
const inspected = inspectArchive(path.join(temporaryDir, packed.filename), packageName, requiredModuleIds);
|
|
112
|
+
if (inspected.version !== packed.version) throw new Error("Packed artifact version disagrees with resolution");
|
|
113
|
+
return { packageName, packageSpec, ...inspected, locator: `npm:${packageName}` };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const safeDetail = isModulePackageExportError(error) && error instanceof Error ? ` ${error.message}` : "";
|
|
116
|
+
throw new Error(`Cannot inspect the exact module artifact for "${packageName}" while trying to ${inspectionStep}.${safeDetail} Check project registry credentials, package access and shipped src/dist module descriptors before retrying. No package was installed.`);
|
|
117
|
+
} finally {
|
|
118
|
+
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
inspectNpmModuleTarget
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=module-inspection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/module-inspection.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { execFileSync } from 'node:child_process'\nimport spawn from 'cross-spawn'\nimport { assertModulePackageExports, isModulePackageExportError } from './module-package'\n\nexport type InspectedModuleTarget = {\n packageName: string\n packageSpec: string\n moduleIds: string[]\n locator?: string\n version?: string\n}\n\ntype ArtifactInspection = { moduleIds: string[]; version?: string }\n\nfunction record(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction inspectFiles(manifest: Record<string, unknown>, packageName: string, files: string[], requiredModuleIds: readonly string[], readSource: (relativePath: string) => string): ArtifactInspection {\n if (manifest.name !== packageName) throw new Error(`Inspected package does not match \"${packageName}\". No package was installed.`)\n const moduleIds = [...new Set(files.flatMap((file) => {\n const match = /^src\\/modules\\/([a-z0-9]+(?:_[a-z0-9]+)*)\\/index\\.ts$/.exec(file)\n return match && files.includes(`dist/modules/${match[1]}/index.js`) ? [match[1]] : []\n }))]\n const checkedModuleIds = requiredModuleIds.length ? requiredModuleIds : moduleIds.length === 1 ? moduleIds : []\n for (const moduleId of checkedModuleIds) {\n if (!moduleIds.includes(moduleId)) throw new Error(`Package \"${packageName}\" is missing source or compiled descriptors for \"${moduleId}\". No package was installed.`)\n assertModulePackageExports(manifest, packageName, moduleId, files, readSource)\n }\n if (moduleIds.length === 0) throw new Error(`Package \"${packageName}\" is missing source or compiled module descriptors. Build the package before retrying. No package was installed.`)\n return { moduleIds, ...(typeof manifest.version === 'string' ? { version: manifest.version } : {}) }\n}\n\nfunction inspectArchive(archive: string, packageName: string, requiredModuleIds: readonly string[]): ArtifactInspection {\n const manifest = record(JSON.parse(execFileSync('tar', ['-xOf', archive, 'package/package.json'], { encoding: 'utf8', timeout: 15_000 })))\n const files = execFileSync('tar', ['-tf', archive], { encoding: 'utf8', timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }).split('\\n').filter((file) => file.startsWith('package/')).map((file) => file.slice('package/'.length))\n return inspectFiles(manifest, packageName, files, requiredModuleIds, (relativePath) => execFileSync('tar', ['-xOf', archive, `package/${relativePath}`], { encoding: 'utf8', timeout: 15_000, maxBuffer: 1024 * 1024 }))\n}\n\nfunction packageFiles(packageRoot: string, relativeDir = ''): string[] {\n return fs.readdirSync(path.join(packageRoot, relativeDir), { withFileTypes: true }).flatMap((entry) => {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) return []\n const relativePath = path.posix.join(relativeDir, entry.name)\n return entry.isDirectory() ? packageFiles(packageRoot, relativePath) : entry.isFile() ? [relativePath] : []\n })\n}\n\nfunction isModernYarnProject(appDir: string): boolean {\n let directory = appDir\n while (true) {\n if (fs.existsSync(path.join(directory, '.yarnrc.yml'))) return true\n const manifestPath = path.join(directory, 'package.json')\n if (fs.existsSync(manifestPath)) {\n const manifest = record(JSON.parse(fs.readFileSync(manifestPath, 'utf8')))\n if (typeof manifest.packageManager === 'string' && /^yarn@(?:[2-9]|[1-9][0-9]+)\\./.test(manifest.packageManager)) return true\n }\n const parent = path.dirname(directory)\n if (parent === directory) return false\n directory = parent\n }\n}\n\nfunction yarnNpmConfiguration(appDir: string, packageName: string, temporaryDir: string): string[] {\n if (!isModernYarnProject(appDir)) return []\n const settings = new Map<string, unknown>()\n for (const setting of ['npmRegistryServer', 'npmScopes', 'npmRegistries', 'npmAuthToken', 'npmAuthIdent']) {\n const result = spawn.sync(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', ['config', 'get', setting, '--json', '--no-redacted'], {\n cwd: appDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000,\n })\n if (result.error || result.status !== 0) throw new Error('Cannot inspect project package registry configuration')\n settings.set(setting, JSON.parse(result.stdout))\n }\n const scope = packageName.startsWith('@') ? packageName.slice(1).split('/')[0] : undefined\n const scopeSettings = scope ? record(record(settings.get('npmScopes'))[scope]) : {}\n const registry = typeof scopeSettings.npmRegistryServer === 'string' ? scopeSettings.npmRegistryServer : settings.get('npmRegistryServer')\n if (typeof registry !== 'string') throw new Error('Missing project package registry')\n const registryUrl = new URL(registry)\n if (!['http:', 'https:'].includes(registryUrl.protocol) || registryUrl.username || registryUrl.password) throw new Error('Invalid project package registry')\n const registryKey = `//${registryUrl.host}${registryUrl.pathname}`.replace(/\\/$/, '')\n const registries = record(settings.get('npmRegistries'))\n const registrySettings = record(registries[registry.replace(/\\/$/, '')] ?? registries[registryKey] ?? registries[`${registryKey}/`])\n const auth = scopeSettings.npmAuthToken || scopeSettings.npmAuthIdent ? scopeSettings\n : registrySettings.npmAuthToken || registrySettings.npmAuthIdent ? registrySettings\n : { npmAuthToken: settings.get('npmAuthToken'), npmAuthIdent: settings.get('npmAuthIdent') }\n const userConfig = process.env.NPM_CONFIG_USERCONFIG ?? process.env.npm_config_userconfig ?? path.join(os.homedir(), '.npmrc')\n const existingConfig = fs.existsSync(userConfig) ? fs.readFileSync(userConfig, 'utf8') : ''\n const lines = [existingConfig, `registry=${JSON.stringify(registry)}`]\n if (scope) lines.push(`@${scope}:registry=${JSON.stringify(registry)}`)\n if (typeof auth.npmAuthToken === 'string' && auth.npmAuthToken) lines.push(`${registryKey}/:_authToken=${JSON.stringify(auth.npmAuthToken)}`)\n else if (typeof auth.npmAuthIdent === 'string' && auth.npmAuthIdent) lines.push(`${registryKey}/:_auth=${Buffer.from(auth.npmAuthIdent).toString('base64')}`)\n const configPath = path.join(temporaryDir, 'inspection.npmrc')\n fs.writeFileSync(configPath, `${lines.join('\\n')}\\n`, { mode: 0o600 })\n return ['--userconfig', configPath, '--registry', registry, ...(scope ? [`--@${scope}:registry=${registry}`] : [])]\n}\n\nexport function inspectNpmModuleTarget(packageSpec: string, packageName: string, appDir: string, requiredModuleIds: readonly string[] = []): InspectedModuleTarget {\n const filePrefix = `${packageName}@file:`\n if (packageSpec.startsWith(filePrefix)) {\n const packageRoot = path.resolve(appDir, packageSpec.slice(filePrefix.length))\n if (fs.statSync(packageRoot).isFile()) return { packageName, packageSpec, ...inspectArchive(packageRoot, packageName, requiredModuleIds) }\n const manifest = record(JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')))\n const inspected = inspectFiles(manifest, packageName, packageFiles(packageRoot), requiredModuleIds, (relativePath) => fs.readFileSync(path.join(packageRoot, relativePath), 'utf8'))\n return { packageName, packageSpec, ...inspected }\n }\n\n const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-module-inspection-'))\n let inspectionStep = 'read the project registry configuration'\n try {\n const configurationArgs = yarnNpmConfiguration(appDir, packageName, temporaryDir)\n inspectionStep = 'fetch the exact npm artifact'\n const packedProcess = spawn.sync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['pack', packageSpec, '--ignore-scripts', '--json', '--pack-destination', temporaryDir, ...configurationArgs], {\n cwd: appDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000, maxBuffer: 16 * 1024 * 1024,\n })\n if (packedProcess.error || packedProcess.status !== 0) throw new Error('Artifact inspection failed')\n inspectionStep = 'parse the npm artifact response'\n const packages: unknown = JSON.parse(packedProcess.stdout)\n const packed = record(Array.isArray(packages) ? packages[0] : undefined)\n if (packed.name !== packageName || typeof packed.version !== 'string' || !/^[0-9A-Za-z.+-]+$/.test(packed.version)\n || typeof packed.filename !== 'string' || path.basename(packed.filename) !== packed.filename) throw new Error('Invalid packed manifest')\n inspectionStep = 'validate the packed module artifact'\n const inspected = inspectArchive(path.join(temporaryDir, packed.filename), packageName, requiredModuleIds)\n if (inspected.version !== packed.version) throw new Error('Packed artifact version disagrees with resolution')\n return { packageName, packageSpec, ...inspected, locator: `npm:${packageName}` }\n } catch (error) {\n const safeDetail = isModulePackageExportError(error) && error instanceof Error ? ` ${error.message}` : ''\n throw new Error(`Cannot inspect the exact module artifact for \"${packageName}\" while trying to ${inspectionStep}.${safeDetail} Check project registry credentials, package access and shipped src/dist module descriptors before retrying. No package was installed.`)\n } finally {\n fs.rmSync(temporaryDir, { recursive: true, force: true })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -7,8 +7,12 @@ import {
|
|
|
7
7
|
resolveInstalledOfficialModulePackage,
|
|
8
8
|
validateEjectBoundaries
|
|
9
9
|
} from "./module-package.js";
|
|
10
|
-
import { assertModuleNotGatedOff,
|
|
10
|
+
import { assertModuleNotGatedOff, prepareModuleRegistration, prepareModuleRegistrationSource } from "./modules-config.js";
|
|
11
11
|
import { resolveSpawnCommand } from "./spawn.js";
|
|
12
|
+
import { parseModuleLocator, resolveModuleRequirement } from "@open-mercato/shared/modules/module-locators";
|
|
13
|
+
import { getEnabledModuleIdentities, getModuleLocators } from "./module-identity.js";
|
|
14
|
+
import { resolveGithubModuleTarget } from "./module-github.js";
|
|
15
|
+
import { inspectNpmModuleTarget } from "./module-inspection.js";
|
|
12
16
|
const OFFICIAL_PACKAGE_SCOPE = "@open-mercato/";
|
|
13
17
|
const SAFE_PACKAGE_SPEC_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@.+)?$/i;
|
|
14
18
|
const SAFE_PACKAGE_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
@@ -61,7 +65,7 @@ function runCommand(command, args, cwd) {
|
|
|
61
65
|
function readFileIfExists(filePath) {
|
|
62
66
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : null;
|
|
63
67
|
}
|
|
64
|
-
function isDeclaredAppDependency(appDir, packageName) {
|
|
68
|
+
function isDeclaredAppDependency(appDir, packageName, packageSpecifier) {
|
|
65
69
|
const packageJsonContent = readFileIfExists(path.join(appDir, "package.json"));
|
|
66
70
|
if (!packageJsonContent) {
|
|
67
71
|
return false;
|
|
@@ -73,10 +77,11 @@ function isDeclaredAppDependency(appDir, packageName) {
|
|
|
73
77
|
const message = error instanceof Error ? error.message : String(error);
|
|
74
78
|
throw new Error(`App package.json at ${path.join(appDir, "package.json")} is not valid JSON. ${message}`);
|
|
75
79
|
}
|
|
76
|
-
|
|
80
|
+
const declarations = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].flatMap((field) => {
|
|
77
81
|
const dependencies = packageJson[field];
|
|
78
|
-
return typeof dependencies === "object" && dependencies !== null && packageName in dependencies;
|
|
82
|
+
return typeof dependencies === "object" && dependencies !== null && packageName in dependencies ? [dependencies[packageName]] : [];
|
|
79
83
|
});
|
|
84
|
+
return declarations.length > 0 && (packageSpecifier === void 0 || declarations.every((declaration) => declaration === packageSpecifier));
|
|
80
85
|
}
|
|
81
86
|
function isGatedOffError(error) {
|
|
82
87
|
return error instanceof Error && error.code === "MODULE_GATED_OFF";
|
|
@@ -135,7 +140,7 @@ function assertThirdPartyAllowed(packageName, allowThirdParty) {
|
|
|
135
140
|
);
|
|
136
141
|
}
|
|
137
142
|
function assertSupportedPackageSpec(packageSpec, allowThirdParty) {
|
|
138
|
-
const trimmed = packageSpec.trim();
|
|
143
|
+
const trimmed = packageSpec.trim().replace(/^npm:/, "");
|
|
139
144
|
if (!SAFE_PACKAGE_SPEC_PATTERN.test(trimmed)) {
|
|
140
145
|
throw new Error("Unsupported package spec. Provide a valid npm package name with an optional tag/version or file: locator.");
|
|
141
146
|
}
|
|
@@ -154,10 +159,56 @@ function assertSupportedPackageSpec(packageSpec, allowThirdParty) {
|
|
|
154
159
|
function validateBeforeRegistration(modulePackage, resolver, eject) {
|
|
155
160
|
const appModuleDir = path.join(resolver.getAppDir(), "src", "modules", modulePackage.metadata.moduleId);
|
|
156
161
|
if (eject && fs.existsSync(appModuleDir)) {
|
|
157
|
-
throw new Error(`Destination directory already exists: ${appModuleDir}.
|
|
162
|
+
throw new Error(`Destination directory already exists: ${appModuleDir}. Preserve local source and choose a different destination or resolve the conflict manually.`);
|
|
158
163
|
}
|
|
159
164
|
return appModuleDir;
|
|
160
165
|
}
|
|
166
|
+
function assertRequiredModulesEnabled(modulePackage, resolver) {
|
|
167
|
+
const enabledModules = getEnabledModuleIdentities(resolver);
|
|
168
|
+
const missingModuleIds = Array.from(new Set(modulePackage.moduleInfo.requires ?? [])).filter(
|
|
169
|
+
(requirement) => !resolveModuleRequirement(requirement, enabledModules)
|
|
170
|
+
);
|
|
171
|
+
if (missingModuleIds.length === 0) return;
|
|
172
|
+
throw new Error(
|
|
173
|
+
[
|
|
174
|
+
`Module "${modulePackage.metadata.moduleId}" requires module(s) that are not enabled: ${missingModuleIds.join(", ")}.`,
|
|
175
|
+
"Enable the missing module(s) in src/modules.ts first, then rerun this command.",
|
|
176
|
+
...missingModuleIds.filter((requirement) => parseModuleLocator(requirement)?.kind !== "module").map(
|
|
177
|
+
(requirement) => `Install ${requirement}: npx create-mercato-module install ${shellQuote(requirement)}`
|
|
178
|
+
)
|
|
179
|
+
].join("\n")
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function shellQuote(value) {
|
|
183
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
184
|
+
}
|
|
185
|
+
function assertNoModuleCollision(resolver, moduleId, packageName, locator) {
|
|
186
|
+
const registrations = resolver.loadEnabledModules().filter((entry) => entry.id === moduleId);
|
|
187
|
+
if (registrations.length > 1) {
|
|
188
|
+
throw new Error(`Module name collision: "${moduleId}" is registered multiple times from ${registrations.map((entry) => entry.locator ?? entry.from ?? "@open-mercato/core").join(", ")}. Resolve duplicate registrations before retrying.`);
|
|
189
|
+
}
|
|
190
|
+
const existing = registrations[0];
|
|
191
|
+
if (existing && (existing.from ?? "@open-mercato/core") !== packageName) {
|
|
192
|
+
throw new Error(`Module name collision: "${moduleId}" is already registered from "${existing.locator ?? existing.from ?? "@open-mercato/core"}"; requested source is "${locator ?? packageName}". Two sources cannot share one module id. Remove or rename the conflicting module explicitly before retrying.`);
|
|
193
|
+
}
|
|
194
|
+
const existingGithub = existing ? getModuleLocators(existing, resolver).find((value) => value.startsWith("github:")) : void 0;
|
|
195
|
+
const requestedIdentity = locator ? parseModuleLocator(locator)?.identity : void 0;
|
|
196
|
+
if (existingGithub && requestedIdentity && existingGithub !== requestedIdentity) {
|
|
197
|
+
throw new Error(`Module name collision: "${moduleId}" is already registered from "${existingGithub}"; requested source is "${locator}". Remove or rename the conflicting module explicitly before retrying.`);
|
|
198
|
+
}
|
|
199
|
+
if (!existing && fs.existsSync(path.join(resolver.getAppDir(), "src", "modules", moduleId))) {
|
|
200
|
+
throw new Error(`Module name collision: local src/modules/${moduleId} already exists; requested source is "${locator ?? packageName}". Remove or rename the conflicting module explicitly before retrying.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function assertBindingPreserved(resolver, target) {
|
|
204
|
+
const consumers = resolver.loadEnabledModules().filter((entry) => (entry.from ?? "@open-mercato/core") === target.packageName);
|
|
205
|
+
for (const entry of consumers) {
|
|
206
|
+
const locators = getModuleLocators(entry, resolver);
|
|
207
|
+
if (!target.locator || !locators.includes(target.locator) || !target.moduleIds.includes(entry.id)) {
|
|
208
|
+
throw new Error(`Package binding collision: "${target.packageName}" supplies enabled module "${entry.id}" from "${locators.join(", ") || "unknown origin"}". Requested "${target.locator ?? "unknown origin"}" must preserve its source and module layout. Disable affected entries explicitly before replacing this binding. No package was installed.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
161
212
|
function prepareRegistrationSource(modulePackage, resolver, packageName, eject) {
|
|
162
213
|
const appModuleDir = validateBeforeRegistration(modulePackage, resolver, eject);
|
|
163
214
|
if (!eject) {
|
|
@@ -176,17 +227,34 @@ function prepareRegistrationSource(modulePackage, resolver, packageName, eject)
|
|
|
176
227
|
);
|
|
177
228
|
return "@app";
|
|
178
229
|
}
|
|
179
|
-
async function registerResolvedOfficialModule(resolver, modulePackage, packageName, eject) {
|
|
230
|
+
async function registerResolvedOfficialModule(resolver, modulePackage, packageName, eject, requestedLocator) {
|
|
180
231
|
assertModuleNotGatedOff(resolver.getModulesConfigPath(), modulePackage.metadata.moduleId);
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
232
|
+
const locators = getModuleLocators({ id: modulePackage.metadata.moduleId, from: packageName }, resolver);
|
|
233
|
+
if (requestedLocator && locators.length > 0 && !locators.includes(requestedLocator)) {
|
|
234
|
+
throw new Error(`Installed package "${packageName}" disagrees with inspected origin "${requestedLocator}". It is installed but not enabled. Restore the binding and lockfile through your package manager before retrying.`);
|
|
235
|
+
}
|
|
236
|
+
const locator = requestedLocator ?? locators[0];
|
|
237
|
+
assertNoModuleCollision(resolver, modulePackage.metadata.moduleId, packageName, locator);
|
|
238
|
+
assertRequiredModulesEnabled(modulePackage, resolver);
|
|
239
|
+
const existing = resolver.loadEnabledModules().find((entry) => entry.id === modulePackage.metadata.moduleId);
|
|
240
|
+
const from = eject ? "@app" : packageName;
|
|
241
|
+
const provenance = {
|
|
242
|
+
...locator ? { locator } : {},
|
|
243
|
+
...eject || modulePackage.packageName !== packageName ? { packageName: modulePackage.packageName } : {}
|
|
244
|
+
};
|
|
245
|
+
const modulesPath = resolver.getModulesConfigPath();
|
|
246
|
+
const modulesSource = fs.readFileSync(modulesPath, "utf8");
|
|
247
|
+
const registration = existing && eject ? { changed: true, source: prepareModuleRegistrationSource(modulesSource, modulePackage.metadata.moduleId, from, provenance) } : prepareModuleRegistration(
|
|
248
|
+
modulesSource,
|
|
184
249
|
{
|
|
185
250
|
id: modulePackage.metadata.moduleId,
|
|
186
|
-
from
|
|
251
|
+
from,
|
|
252
|
+
...locator || eject || modulePackage.packageName !== packageName ? provenance : {}
|
|
187
253
|
}
|
|
188
254
|
);
|
|
255
|
+
prepareRegistrationSource(modulePackage, resolver, packageName, eject);
|
|
189
256
|
if (registration.changed) {
|
|
257
|
+
fs.writeFileSync(modulesPath, registration.source);
|
|
190
258
|
await runGeneratorsWithRegistrationNotice(resolver, modulePackage.metadata.moduleId);
|
|
191
259
|
}
|
|
192
260
|
return {
|
|
@@ -197,26 +265,87 @@ async function registerResolvedOfficialModule(resolver, modulePackage, packageNa
|
|
|
197
265
|
};
|
|
198
266
|
}
|
|
199
267
|
async function addOfficialModule(resolver, packageSpec, eject, moduleId, allowThirdParty = false) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
268
|
+
const input = packageSpec.trim();
|
|
269
|
+
getEnabledModuleIdentities(resolver);
|
|
270
|
+
const isGithub = !input.startsWith("@") && !input.startsWith("npm:") && (input.includes("/") && !input.includes("@file:") || /^(?:github:|git@|(?:git\+)?ssh:)/i.test(input));
|
|
271
|
+
let safePackageSpec;
|
|
272
|
+
let packageName;
|
|
273
|
+
let target;
|
|
274
|
+
if (isGithub) {
|
|
275
|
+
if (!allowThirdParty) {
|
|
276
|
+
throw new Error("GitHub repository installs require --allow-third-party, independently of the declared npm package scope.");
|
|
277
|
+
}
|
|
278
|
+
target = resolveGithubModuleTarget(input, resolver.getAppDir(), { moduleId, enabledModules: resolver.loadEnabledModules() });
|
|
279
|
+
assertThirdPartyAllowed(target.packageName, allowThirdParty);
|
|
280
|
+
const selectedModuleId2 = moduleId ?? (target.moduleIds.length === 1 ? target.moduleIds[0] : void 0);
|
|
281
|
+
if (!selectedModuleId2 || !target.moduleIds.includes(selectedModuleId2)) {
|
|
282
|
+
throw new Error(`Select a module with --module <moduleId>. Available in ${target.locator ?? target.packageName}: ${target.moduleIds.join(", ")}. No package was installed.`);
|
|
283
|
+
}
|
|
284
|
+
assertNoModuleCollision(resolver, selectedModuleId2, target.packageName, target.locator);
|
|
285
|
+
safePackageSpec = target.packageSpec;
|
|
286
|
+
packageName = target.packageName;
|
|
287
|
+
} else {
|
|
288
|
+
safePackageSpec = assertSupportedPackageSpec(input, allowThirdParty);
|
|
289
|
+
const parsedName = parsePackageNameFromSpec(safePackageSpec);
|
|
290
|
+
assertPackageName(parsedName);
|
|
291
|
+
packageName = parsedName;
|
|
292
|
+
if (moduleId) assertNoModuleCollision(resolver, moduleId, packageName, safePackageSpec.startsWith(`${packageName}@file:`) ? void 0 : `npm:${packageName}`);
|
|
293
|
+
const requiredModuleIds = resolver.loadEnabledModules().filter((entry) => (entry.from ?? "@open-mercato/core") === packageName).map((entry) => entry.id);
|
|
294
|
+
target = inspectNpmModuleTarget(safePackageSpec, packageName, resolver.getAppDir(), [.../* @__PURE__ */ new Set([...moduleId ? [moduleId] : [], ...requiredModuleIds])]);
|
|
295
|
+
}
|
|
296
|
+
const selectedModuleId = moduleId ?? (target.moduleIds.length === 1 ? target.moduleIds[0] : void 0);
|
|
297
|
+
if (!selectedModuleId || !target.moduleIds.includes(selectedModuleId)) {
|
|
298
|
+
throw new Error(`Select a module with --module <moduleId>. Available in ${target.locator ?? target.packageName}: ${target.moduleIds.join(", ")}. No package was installed.`);
|
|
299
|
+
}
|
|
300
|
+
assertNoModuleCollision(resolver, selectedModuleId, packageName, target.locator);
|
|
301
|
+
const existing = resolver.loadEnabledModules().find((entry) => entry.id === selectedModuleId);
|
|
302
|
+
if (!eject && existing && !target.locator && target.packageSpec.startsWith(`${packageName}@file:`) && getModuleLocators(existing, resolver).length === 0 && isDeclaredAppDependency(resolver.getAppDir(), packageName, target.packageSpec.slice(packageName.length + 1))) {
|
|
303
|
+
return enableOfficialModule(resolver, packageName, selectedModuleId, false, allowThirdParty);
|
|
304
|
+
}
|
|
305
|
+
assertBindingPreserved(resolver, target);
|
|
306
|
+
const modulesSource = fs.readFileSync(resolver.getModulesConfigPath(), "utf8");
|
|
307
|
+
const from = eject ? "@app" : packageName;
|
|
308
|
+
if (existing && eject) prepareModuleRegistrationSource(modulesSource, selectedModuleId, from, { locator: target.locator, packageName });
|
|
309
|
+
else prepareModuleRegistration(modulesSource, { id: selectedModuleId, from, locator: target.locator });
|
|
310
|
+
if (eject && fs.existsSync(path.join(resolver.getAppDir(), "src", "modules", selectedModuleId))) {
|
|
311
|
+
throw new Error(`Destination directory already exists for "${selectedModuleId}". Preserve local source and resolve the conflict manually. No package was installed.`);
|
|
312
|
+
}
|
|
313
|
+
safePackageSpec = target.packageSpec;
|
|
314
|
+
assertModuleNotGatedOff(resolver.getModulesConfigPath(), selectedModuleId);
|
|
203
315
|
const wasDeclaredBefore = isDeclaredAppDependency(resolver.getAppDir(), packageName);
|
|
204
316
|
const modulesConfigBefore = readFileIfExists(resolver.getModulesConfigPath());
|
|
205
317
|
await installPackageSpec(resolver, safePackageSpec);
|
|
206
318
|
try {
|
|
207
|
-
const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName,
|
|
208
|
-
|
|
319
|
+
const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, selectedModuleId);
|
|
320
|
+
if (modulePackage.packageName !== packageName) throw new Error("Installed manifest differs from inspected package name.");
|
|
321
|
+
if (target.version && modulePackage.packageJson.version !== target.version) {
|
|
322
|
+
throw new Error(`Installed version "${modulePackage.packageJson.version ?? "unknown"}" differs from inspected version "${target.version}". No registration or source edits were made.`);
|
|
323
|
+
}
|
|
324
|
+
for (const entry of resolver.loadEnabledModules().filter((entry2) => (entry2.from ?? "@open-mercato/core") === packageName)) {
|
|
325
|
+
resolveInstalledOfficialModulePackage(resolver, packageName, entry.id);
|
|
326
|
+
}
|
|
327
|
+
return await registerResolvedOfficialModule(resolver, modulePackage, packageName, eject, target.locator);
|
|
209
328
|
} catch (error) {
|
|
210
329
|
const moduleWasRegistered = readFileIfExists(resolver.getModulesConfigPath()) !== modulesConfigBefore;
|
|
211
|
-
if (wasDeclaredBefore || moduleWasRegistered || isGatedOffError(error))
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
330
|
+
if (wasDeclaredBefore || moduleWasRegistered || isGatedOffError(error)) throw error;
|
|
331
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
332
|
+
throw withLeftoverPackageHint(
|
|
333
|
+
new Error(`Package "${packageName}" is installed; enablement did not complete. Review src/modules.ts and restore the package binding/lockfile through the package manager if needed. ${message}`),
|
|
334
|
+
resolver,
|
|
335
|
+
packageName
|
|
336
|
+
);
|
|
215
337
|
}
|
|
216
338
|
}
|
|
217
339
|
async function enableOfficialModule(resolver, packageName, moduleId, eject = false, allowThirdParty = false) {
|
|
218
|
-
|
|
340
|
+
if (!SAFE_PACKAGE_SPEC_PATTERN.test(packageName) || parsePackageNameFromSpec(packageName) !== packageName) {
|
|
341
|
+
throw new Error("Unsupported installed package binding. Use the installed npm package name or alias, without a locator, path or version selector.");
|
|
342
|
+
}
|
|
343
|
+
getEnabledModuleIdentities(resolver);
|
|
219
344
|
const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId);
|
|
345
|
+
assertThirdPartyAllowed(modulePackage.packageName, allowThirdParty);
|
|
346
|
+
if (!allowThirdParty && getModuleLocators({ id: modulePackage.metadata.moduleId, from: packageName }, resolver).some((locator) => locator.startsWith("github:"))) {
|
|
347
|
+
throw new Error("GitHub repository installs require --allow-third-party, independently of the declared npm package scope.");
|
|
348
|
+
}
|
|
220
349
|
return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject);
|
|
221
350
|
}
|
|
222
351
|
export {
|