@open-mercato/cli 0.10.1-develop.7983.1.fd91207efe → 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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { getEnabledModuleIdentities } from '../module-identity'
|
|
1
2
|
import path from 'node:path'
|
|
2
|
-
import {
|
|
3
|
+
import { resolveInstalledOfficialModulePackage } from '../module-package'
|
|
3
4
|
import type { PackageResolver } from '../resolver'
|
|
4
5
|
import { calculateChecksum, createGeneratorResult, type GeneratorResult, writeGeneratedFile } from '../utils'
|
|
5
6
|
|
|
@@ -24,14 +25,15 @@ export async function generateModulePackageSources(
|
|
|
24
25
|
const sourcePaths = new Set<string>()
|
|
25
26
|
const checksumInputs = new Set<string>()
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const enabled = resolver.loadEnabledModules()
|
|
29
|
+
getEnabledModuleIdentities(resolver, enabled)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
for (const entry of enabled) {
|
|
32
|
+
if (!entry.from || entry.from === '@app' || entry.from === '@open-mercato/core') continue
|
|
31
33
|
|
|
32
34
|
let modulePackage
|
|
33
35
|
try {
|
|
34
|
-
modulePackage =
|
|
36
|
+
modulePackage = resolveInstalledOfficialModulePackage(resolver, entry.from, entry.id)
|
|
35
37
|
} catch {
|
|
36
38
|
continue
|
|
37
39
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { sourceHasDefaultExport, sourceExportedHttpMethods } from './source-exports'
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { pathToFileURL } from 'node:url'
|
|
4
5
|
import { VariableDeclarationKind, type WriterFunction } from 'ts-morph'
|
|
5
6
|
import ts from 'typescript-js'
|
|
6
7
|
import type { ModuleEntry, PackageResolver } from '../resolver'
|
|
8
|
+
import { getEnabledModuleIdentities } from '../module-identity'
|
|
9
|
+
import { getModuleRequirementHint, resolveModuleRequirement } from '@open-mercato/shared/modules/module-locators'
|
|
7
10
|
import {
|
|
8
11
|
DEV_SUPERVISOR_MANIFEST_FILE,
|
|
9
12
|
DEV_SUPERVISOR_MANIFEST_VERSION,
|
|
@@ -93,6 +96,7 @@ export interface ModuleRegistryOptions {
|
|
|
93
96
|
interface ModuleRegistryDiscovery {
|
|
94
97
|
enabled: ModuleEntry[]
|
|
95
98
|
modules: ModuleRegistryDiscoveryEntry[]
|
|
99
|
+
identities: ReturnType<typeof getEnabledModuleIdentities>
|
|
96
100
|
getStructureChecksum: () => string
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -1158,44 +1162,11 @@ function inferScriptKind(filePath: string): ts.ScriptKind {
|
|
|
1158
1162
|
}
|
|
1159
1163
|
|
|
1160
1164
|
function hasDefaultExport(sourceFile: string): boolean {
|
|
1161
|
-
let source = ''
|
|
1162
1165
|
try {
|
|
1163
|
-
|
|
1166
|
+
return sourceHasDefaultExport(sourceFile, fs.readFileSync(sourceFile, 'utf8'))
|
|
1164
1167
|
} catch {
|
|
1165
1168
|
return false
|
|
1166
1169
|
}
|
|
1167
|
-
|
|
1168
|
-
const parsed = ts.createSourceFile(
|
|
1169
|
-
sourceFile,
|
|
1170
|
-
source,
|
|
1171
|
-
ts.ScriptTarget.Latest,
|
|
1172
|
-
true,
|
|
1173
|
-
inferScriptKind(sourceFile),
|
|
1174
|
-
)
|
|
1175
|
-
|
|
1176
|
-
for (const statement of parsed.statements) {
|
|
1177
|
-
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
1178
|
-
return true
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
if (
|
|
1182
|
-
ts.isExportDeclaration(statement)
|
|
1183
|
-
&& statement.exportClause
|
|
1184
|
-
&& ts.isNamedExports(statement.exportClause)
|
|
1185
|
-
&& statement.exportClause.elements.some(
|
|
1186
|
-
(element) => element.name.text === 'default' || element.propertyName?.text === 'default',
|
|
1187
|
-
)
|
|
1188
|
-
) {
|
|
1189
|
-
return true
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined
|
|
1193
|
-
if (modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
1194
|
-
return true
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
return false
|
|
1199
1170
|
}
|
|
1200
1171
|
|
|
1201
1172
|
function requiresRuntimePageMetadataFromSourceFile(sourceFile: string): boolean {
|
|
@@ -1737,66 +1708,11 @@ async function resolveModuleRequiresFromIndexFile(indexFileAbsolutePath: string)
|
|
|
1737
1708
|
}
|
|
1738
1709
|
|
|
1739
1710
|
function detectExportedHttpMethods(sourceFile: string): HttpMethod[] {
|
|
1740
|
-
let source = ''
|
|
1741
1711
|
try {
|
|
1742
|
-
|
|
1712
|
+
return sourceExportedHttpMethods(fs.readFileSync(sourceFile, 'utf8'))
|
|
1743
1713
|
} catch {
|
|
1744
1714
|
return []
|
|
1745
1715
|
}
|
|
1746
|
-
|
|
1747
|
-
const knownMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as HttpMethod[]
|
|
1748
|
-
const exportedMethods = new Set<HttpMethod>()
|
|
1749
|
-
const collectKnownMethods = (rawSpecifiers: string[], opts?: { useExportedAlias?: boolean; useDestructuredProperty?: boolean }) => {
|
|
1750
|
-
for (const specifier of rawSpecifiers) {
|
|
1751
|
-
const trimmed = specifier.trim()
|
|
1752
|
-
if (!trimmed) continue
|
|
1753
|
-
|
|
1754
|
-
let candidate = trimmed
|
|
1755
|
-
if (opts?.useExportedAlias) {
|
|
1756
|
-
candidate = trimmed.split(/\s+as\s+/i).pop()?.trim() ?? trimmed
|
|
1757
|
-
}
|
|
1758
|
-
if (opts?.useDestructuredProperty) {
|
|
1759
|
-
candidate = trimmed.split(':')[0]?.trim() ?? trimmed
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
if (knownMethods.includes(candidate as HttpMethod)) {
|
|
1763
|
-
exportedMethods.add(candidate as HttpMethod)
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
for (const method of knownMethods) {
|
|
1769
|
-
const pattern = new RegExp(`export\\s+(?:async\\s+)?function\\s+${method}\\b|export\\s+(?:const|let|var)\\s+${method}\\b`)
|
|
1770
|
-
if (pattern.test(source)) {
|
|
1771
|
-
exportedMethods.add(method)
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
const reExportPattern = /export\s*{([^}]+)}/g
|
|
1776
|
-
for (const match of source.matchAll(reExportPattern)) {
|
|
1777
|
-
const exportBlock = match[1] ?? ''
|
|
1778
|
-
collectKnownMethods(
|
|
1779
|
-
exportBlock
|
|
1780
|
-
.split(',')
|
|
1781
|
-
.map((part) => part.trim())
|
|
1782
|
-
.filter((part) => part.length > 0),
|
|
1783
|
-
{ useExportedAlias: true },
|
|
1784
|
-
)
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
const destructuredExportPattern = /export\s+(?:const|let|var)\s*{([^}]+)}/g
|
|
1788
|
-
for (const match of source.matchAll(destructuredExportPattern)) {
|
|
1789
|
-
const exportBlock = match[1] ?? ''
|
|
1790
|
-
collectKnownMethods(
|
|
1791
|
-
exportBlock
|
|
1792
|
-
.split(',')
|
|
1793
|
-
.map((part) => part.trim())
|
|
1794
|
-
.filter((part) => part.length > 0),
|
|
1795
|
-
{ useDestructuredProperty: true },
|
|
1796
|
-
)
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
return knownMethods.filter((method) => exportedMethods.has(method))
|
|
1800
1716
|
}
|
|
1801
1717
|
|
|
1802
1718
|
function buildPageRouteProps(metaExpr: string, routePath: string): string {
|
|
@@ -2062,12 +1978,22 @@ async function createModuleRegistryDiscovery(
|
|
|
2062
1978
|
): Promise<ModuleRegistryDiscovery> {
|
|
2063
1979
|
resetConventionWarnings()
|
|
2064
1980
|
const enabled = resolver.loadEnabledModules()
|
|
1981
|
+
const rootsByEntry = new Map<ModuleEntry, ModuleRoots>()
|
|
1982
|
+
const getModulePaths = (entry: ModuleEntry): ModuleRoots => {
|
|
1983
|
+
let roots = rootsByEntry.get(entry)
|
|
1984
|
+
if (!roots) {
|
|
1985
|
+
roots = resolver.getModulePaths(entry)
|
|
1986
|
+
rootsByEntry.set(entry, roots)
|
|
1987
|
+
}
|
|
1988
|
+
return roots
|
|
1989
|
+
}
|
|
1990
|
+
const identities = getEnabledModuleIdentities({ ...resolver, getModulePaths }, enabled)
|
|
2065
1991
|
const modules: ModuleRegistryDiscoveryEntry[] = []
|
|
2066
1992
|
const trackedRoots = new Set<string>()
|
|
2067
1993
|
|
|
2068
1994
|
for (const entry of enabled) {
|
|
2069
1995
|
const modId = entry.id
|
|
2070
|
-
const roots =
|
|
1996
|
+
const roots = getModulePaths(entry)
|
|
2071
1997
|
verifyThirdPartyModuleShape(entry, roots.pkgBase)
|
|
2072
1998
|
const rawImps = resolver.getModuleImportBase(entry)
|
|
2073
1999
|
const appImportBase = entry.from === '@app' ? `../../src/modules/${modId}` : rawImps.appBase
|
|
@@ -2110,6 +2036,7 @@ async function createModuleRegistryDiscovery(
|
|
|
2110
2036
|
return {
|
|
2111
2037
|
enabled,
|
|
2112
2038
|
modules,
|
|
2039
|
+
identities,
|
|
2113
2040
|
getStructureChecksum: () => structureChecksum ??= calculateStructureChecksum(Array.from(trackedRoots)),
|
|
2114
2041
|
}
|
|
2115
2042
|
}
|
|
@@ -4240,10 +4167,12 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
4240
4167
|
})
|
|
4241
4168
|
// Validate module dependencies declared via ModuleInfo.requires
|
|
4242
4169
|
{
|
|
4243
|
-
const
|
|
4170
|
+
const identities = discovery.identities
|
|
4244
4171
|
const problems: string[] = []
|
|
4172
|
+
const missingRequirements = new Set<string>()
|
|
4245
4173
|
for (const [modId, reqs] of requiresByModule.entries()) {
|
|
4246
|
-
const missing = reqs.filter((
|
|
4174
|
+
const missing = reqs.filter((requirement) => !resolveModuleRequirement(requirement, identities))
|
|
4175
|
+
for (const requirement of missing) missingRequirements.add(requirement)
|
|
4247
4176
|
if (missing.length) {
|
|
4248
4177
|
problems.push(`- Module "${modId}" requires: ${missing.join(', ')}`)
|
|
4249
4178
|
}
|
|
@@ -4251,12 +4180,7 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
4251
4180
|
if (problems.length) {
|
|
4252
4181
|
console.error('\nModule dependency check failed:')
|
|
4253
4182
|
for (const p of problems) console.error(p)
|
|
4254
|
-
|
|
4255
|
-
console.error(
|
|
4256
|
-
' export const enabledModules = [ { id: \'' +
|
|
4257
|
-
Array.from(new Set(requiresByModule.values()).values()).join("' }, { id: '") +
|
|
4258
|
-
"' } ]"
|
|
4259
|
-
)
|
|
4183
|
+
for (const requirement of missingRequirements) console.error(`Fix: ${getModuleRequirementHint(requirement)}`)
|
|
4260
4184
|
process.exit(1)
|
|
4261
4185
|
}
|
|
4262
4186
|
}
|
|
@@ -4720,10 +4644,12 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
|
|
|
4720
4644
|
})
|
|
4721
4645
|
|
|
4722
4646
|
{
|
|
4723
|
-
const
|
|
4647
|
+
const identities = discovery.identities
|
|
4724
4648
|
const problems: string[] = []
|
|
4649
|
+
const missingRequirements = new Set<string>()
|
|
4725
4650
|
for (const [modId, reqs] of requiresByModule.entries()) {
|
|
4726
|
-
const missing = reqs.filter((
|
|
4651
|
+
const missing = reqs.filter((requirement) => !resolveModuleRequirement(requirement, identities))
|
|
4652
|
+
for (const requirement of missing) missingRequirements.add(requirement)
|
|
4727
4653
|
if (missing.length) {
|
|
4728
4654
|
problems.push(`- Module "${modId}" requires: ${missing.join(', ')}`)
|
|
4729
4655
|
}
|
|
@@ -4731,12 +4657,7 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
|
|
|
4731
4657
|
if (problems.length) {
|
|
4732
4658
|
console.error('\nModule dependency check failed:')
|
|
4733
4659
|
for (const p of problems) console.error(p)
|
|
4734
|
-
|
|
4735
|
-
console.error(
|
|
4736
|
-
' export const enabledModules = [ { id: \'' +
|
|
4737
|
-
Array.from(new Set(requiresByModule.values()).values()).join("' }, { id: '") +
|
|
4738
|
-
"' } ]"
|
|
4739
|
-
)
|
|
4660
|
+
for (const requirement of missingRequirements) console.error(`Fix: ${getModuleRequirementHint(requirement)}`)
|
|
4740
4661
|
process.exit(1)
|
|
4741
4662
|
}
|
|
4742
4663
|
}
|
|
@@ -5109,10 +5030,12 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
|
|
|
5109
5030
|
|
|
5110
5031
|
// Validate module dependencies declared via ModuleInfo.requires
|
|
5111
5032
|
{
|
|
5112
|
-
const
|
|
5033
|
+
const identities = discovery.identities
|
|
5113
5034
|
const problems: string[] = []
|
|
5035
|
+
const missingRequirements = new Set<string>()
|
|
5114
5036
|
for (const [modId, reqs] of requiresByModule.entries()) {
|
|
5115
|
-
const missing = reqs.filter((
|
|
5037
|
+
const missing = reqs.filter((requirement) => !resolveModuleRequirement(requirement, identities))
|
|
5038
|
+
for (const requirement of missing) missingRequirements.add(requirement)
|
|
5116
5039
|
if (missing.length) {
|
|
5117
5040
|
problems.push(`- Module "${modId}" requires: ${missing.join(', ')}`)
|
|
5118
5041
|
}
|
|
@@ -5120,12 +5043,7 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
|
|
|
5120
5043
|
if (problems.length) {
|
|
5121
5044
|
console.error('\nModule dependency check failed:')
|
|
5122
5045
|
for (const p of problems) console.error(p)
|
|
5123
|
-
|
|
5124
|
-
console.error(
|
|
5125
|
-
' export const enabledModules = [ { id: \'' +
|
|
5126
|
-
Array.from(new Set(requiresByModule.values()).values()).join("' }, { id: '") +
|
|
5127
|
-
"' } ]"
|
|
5128
|
-
)
|
|
5046
|
+
for (const requirement of missingRequirements) console.error(`Fix: ${getModuleRequirementHint(requirement)}`)
|
|
5129
5047
|
process.exit(1)
|
|
5130
5048
|
}
|
|
5131
5049
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as fs from 'node:fs'
|
|
10
10
|
import * as path from 'node:path'
|
|
11
11
|
import type { ModuleEntry, PackageResolver } from '../resolver'
|
|
12
|
+
import { getEnabledModuleIdentities } from '../module-identity'
|
|
12
13
|
import { isModuleRouteFile } from './scanner'
|
|
13
14
|
import {
|
|
14
15
|
calculateChecksum,
|
|
@@ -671,6 +672,8 @@ export async function generateOpenApi(options: GenerateOpenApiOptions): Promise<
|
|
|
671
672
|
const checksumFile = path.join(outputDir, 'openapi.generated.checksum')
|
|
672
673
|
const inputManifestFile = path.join(outputDir, 'openapi.generated.inputs.json')
|
|
673
674
|
|
|
675
|
+
getEnabledModuleIdentities(resolver, resolver.loadEnabledModules())
|
|
676
|
+
|
|
674
677
|
// Ensure output directory exists
|
|
675
678
|
if (!fs.existsSync(outputDir)) {
|
|
676
679
|
fs.mkdirSync(outputDir, { recursive: true })
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import ts from 'typescript-js'
|
|
2
|
+
|
|
3
|
+
export function sourceHasDefaultExport(sourceFile: string, source: string): boolean {
|
|
4
|
+
const parsed = ts.createSourceFile(
|
|
5
|
+
sourceFile,
|
|
6
|
+
source,
|
|
7
|
+
ts.ScriptTarget.Latest,
|
|
8
|
+
true,
|
|
9
|
+
sourceFile.endsWith('.tsx') ? ts.ScriptKind.TSX : sourceFile.endsWith('.jsx') ? ts.ScriptKind.JSX : sourceFile.endsWith('.js') ? ts.ScriptKind.JS : ts.ScriptKind.TS,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
for (const statement of parsed.statements) {
|
|
13
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
ts.isExportDeclaration(statement)
|
|
19
|
+
&& statement.exportClause
|
|
20
|
+
&& ts.isNamedExports(statement.exportClause)
|
|
21
|
+
&& statement.exportClause.elements.some(
|
|
22
|
+
(element) => element.name.text === 'default' || element.propertyName?.text === 'default',
|
|
23
|
+
)
|
|
24
|
+
) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined
|
|
29
|
+
if (modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type SourceHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
38
|
+
|
|
39
|
+
export function sourceExportedHttpMethods(source: string): SourceHttpMethod[] {
|
|
40
|
+
const knownMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as SourceHttpMethod[]
|
|
41
|
+
const exportedMethods = new Set<SourceHttpMethod>()
|
|
42
|
+
const collectKnownMethods = (rawSpecifiers: string[], opts?: { useExportedAlias?: boolean; useDestructuredProperty?: boolean }) => {
|
|
43
|
+
for (const specifier of rawSpecifiers) {
|
|
44
|
+
const trimmed = specifier.trim()
|
|
45
|
+
if (!trimmed) continue
|
|
46
|
+
|
|
47
|
+
let candidate = trimmed
|
|
48
|
+
if (opts?.useExportedAlias) {
|
|
49
|
+
candidate = trimmed.split(/\s+as\s+/i).pop()?.trim() ?? trimmed
|
|
50
|
+
}
|
|
51
|
+
if (opts?.useDestructuredProperty) {
|
|
52
|
+
candidate = trimmed.split(':')[0]?.trim() ?? trimmed
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (knownMethods.includes(candidate as SourceHttpMethod)) {
|
|
56
|
+
exportedMethods.add(candidate as SourceHttpMethod)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const method of knownMethods) {
|
|
62
|
+
const pattern = new RegExp(`export\\s+(?:async\\s+)?function\\s+${method}\\b|export\\s+(?:const|let|var)\\s+${method}\\b`)
|
|
63
|
+
if (pattern.test(source)) {
|
|
64
|
+
exportedMethods.add(method)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const reExportPattern = /export\s*{([^}]+)}/g
|
|
69
|
+
for (const match of source.matchAll(reExportPattern)) {
|
|
70
|
+
const exportBlock = match[1] ?? ''
|
|
71
|
+
collectKnownMethods(
|
|
72
|
+
exportBlock
|
|
73
|
+
.split(',')
|
|
74
|
+
.map((part) => part.trim())
|
|
75
|
+
.filter((part) => part.length > 0),
|
|
76
|
+
{ useExportedAlias: true },
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const destructuredExportPattern = /export\s+(?:const|let|var)\s*{([^}]+)}/g
|
|
81
|
+
for (const match of source.matchAll(destructuredExportPattern)) {
|
|
82
|
+
const exportBlock = match[1] ?? ''
|
|
83
|
+
collectKnownMethods(
|
|
84
|
+
exportBlock
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((part) => part.trim())
|
|
87
|
+
.filter((part) => part.length > 0),
|
|
88
|
+
{ useDestructuredProperty: true },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return knownMethods.filter((method) => exportedMethods.has(method))
|
|
93
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
type InitSummaryUser = { icon: string; label: string; email: string; password: string }
|
|
2
|
+
|
|
3
|
+
type InitSummaryOptions = {
|
|
4
|
+
users: InitSummaryUser[]
|
|
5
|
+
autoStart: boolean
|
|
6
|
+
columns?: number
|
|
7
|
+
color?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
|
|
11
|
+
const panelStyle = '\u001b[48;2;57;62;70m\u001b[38;2;238;238;238m'
|
|
12
|
+
const reset = '\u001b[0m'
|
|
13
|
+
|
|
14
|
+
function cellWidth(value: string): number {
|
|
15
|
+
if (/\p{Extended_Pictographic}|\p{Regional_Indicator}|[\u1100-\u115f\u2329\u232a\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe10-\ufe19\ufe30-\ufe6f\uff01-\uff60\uffe0-\uffe6]/u.test(value)) return 2
|
|
16
|
+
return /^\p{Mark}+$/u.test(value) ? 0 : 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function wrapLine(line: string, width: number): string[] {
|
|
20
|
+
const wrapped: string[] = []
|
|
21
|
+
let current = ''
|
|
22
|
+
let currentWidth = 0
|
|
23
|
+
for (const { segment } of segmenter.segment(line)) {
|
|
24
|
+
const segmentWidth = cellWidth(segment)
|
|
25
|
+
if (currentWidth + segmentWidth > width && current) {
|
|
26
|
+
wrapped.push(current)
|
|
27
|
+
current = ''
|
|
28
|
+
currentWidth = 0
|
|
29
|
+
}
|
|
30
|
+
current += segment
|
|
31
|
+
currentWidth += segmentWidth
|
|
32
|
+
}
|
|
33
|
+
wrapped.push(current)
|
|
34
|
+
return wrapped
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatInitSummary({ users, autoStart, columns = 80, color = false }: InitSummaryOptions): string[] {
|
|
38
|
+
const width = Number.isFinite(columns) && columns >= 8 ? Math.floor(columns) : 80
|
|
39
|
+
const lines = ['', '🔑 DEVELOPMENT ACCOUNTS', '']
|
|
40
|
+
for (const user of users) {
|
|
41
|
+
lines.push(`${user.icon} ${user.label}`, ` Email: ${user.email}`, ` Password: ${user.password}`, '')
|
|
42
|
+
}
|
|
43
|
+
lines.push(...(autoStart
|
|
44
|
+
? ['🚀 The dev server will start automatically.', ' Next time, run: yarn dev']
|
|
45
|
+
: ['🚀 Start the dev server: yarn dev']))
|
|
46
|
+
lines.push('')
|
|
47
|
+
const rule = '━'.repeat(width)
|
|
48
|
+
const content = lines.flatMap((line) => wrapLine(line, width - 4).map((part) => ` ${part}`))
|
|
49
|
+
return [rule, ...content, rule].map((line) => {
|
|
50
|
+
if (!color) return line
|
|
51
|
+
const visibleWidth = [...segmenter.segment(line)].reduce((total, { segment }) => total + cellWidth(segment), 0)
|
|
52
|
+
return `${panelStyle}${line}${' '.repeat(Math.max(0, width - visibleWidth))}${reset}`
|
|
53
|
+
})
|
|
54
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { assertModulePackageExports, parsePackageNameFromSpec } from './module-package'
|
|
3
|
+
|
|
4
|
+
export type GithubModuleTarget = {
|
|
5
|
+
packageName: string
|
|
6
|
+
packageSpec: string
|
|
7
|
+
moduleIds: string[]
|
|
8
|
+
locator: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function githubApi(endpoint: string, cwd: string): unknown {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(execFileSync('gh', ['api', endpoint], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000, maxBuffer: 16 * 1024 * 1024 })) as unknown
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error(`Cannot read GitHub repository metadata at ${endpoint}. Check the locator and access with "gh auth login". No package was installed.`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function record(value: unknown): Record<string, unknown> {
|
|
20
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveGithubModuleTarget(input: string, cwd: string, options: { moduleId?: string; enabledModules?: readonly { id: string; from?: string }[] } = {}): GithubModuleTarget {
|
|
24
|
+
let normalized = input.trim()
|
|
25
|
+
.replace(/^(?:(?:git\+)?https?:\/\/github\.com\/)+/i, '')
|
|
26
|
+
.replace(/^github\.com\//i, '')
|
|
27
|
+
.replace(/^github:/i, '')
|
|
28
|
+
.replace(/^git@github\.com:/i, '')
|
|
29
|
+
.replace(/^(?:git\+)?ssh:\/\/(?:git@)?github\.com\//i, '')
|
|
30
|
+
const hash = normalized.indexOf('#')
|
|
31
|
+
const separator = hash < 0 ? normalized.lastIndexOf('@') : hash
|
|
32
|
+
const requestedRef = separator < 0 ? undefined : normalized.slice(separator + 1)
|
|
33
|
+
if (requestedRef !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$/.test(requestedRef)) {
|
|
34
|
+
throw new Error('Invalid GitHub revision. No package was installed.')
|
|
35
|
+
}
|
|
36
|
+
normalized = (separator < 0 ? normalized : normalized.slice(0, separator)).replace(/\/$/, '').replace(/\.git$/i, '')
|
|
37
|
+
if (/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) {
|
|
38
|
+
const login = record(githubApi('user', cwd)).login
|
|
39
|
+
if (typeof login !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(login)) {
|
|
40
|
+
throw new Error('Cannot determine your GitHub account. Use owner/repository or a full GitHub URL.')
|
|
41
|
+
}
|
|
42
|
+
normalized = `${login}/${normalized}`
|
|
43
|
+
}
|
|
44
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) {
|
|
45
|
+
throw new Error('Unsupported GitHub locator. Use github:owner/repository, a repository URL, or owner/repository.')
|
|
46
|
+
}
|
|
47
|
+
const repository = normalized.toLowerCase()
|
|
48
|
+
const ref = requestedRef ?? record(githubApi(`repos/${repository}`, cwd)).default_branch
|
|
49
|
+
if (typeof ref !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$/.test(ref)) {
|
|
50
|
+
throw new Error('Cannot determine a valid GitHub revision. Use a repository locator with #branch, #tag, or #commit.')
|
|
51
|
+
}
|
|
52
|
+
const sha = record(githubApi(`repos/${repository}/commits/${encodeURIComponent(ref)}`, cwd)).sha
|
|
53
|
+
if (typeof sha !== 'string' || !/^[0-9a-f]{40}$/.test(sha)) {
|
|
54
|
+
throw new Error(`Cannot resolve ${repository} to an immutable commit. No package was installed.`)
|
|
55
|
+
}
|
|
56
|
+
const contents = record(githubApi(`repos/${repository}/contents/package.json?ref=${sha}`, cwd))
|
|
57
|
+
if (contents.encoding !== 'base64' || typeof contents.content !== 'string') {
|
|
58
|
+
throw new Error(`Repository ${repository} has no readable package.json. No package was installed.`)
|
|
59
|
+
}
|
|
60
|
+
let manifest: Record<string, unknown>
|
|
61
|
+
try {
|
|
62
|
+
manifest = record(JSON.parse(Buffer.from(contents.content, 'base64').toString('utf8')))
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error(`Repository ${repository} has invalid package.json. No package was installed.`)
|
|
65
|
+
}
|
|
66
|
+
const packageName = manifest.name
|
|
67
|
+
if (typeof packageName !== 'string' || !/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName) || parsePackageNameFromSpec(packageName) !== packageName) {
|
|
68
|
+
throw new Error(`Repository ${repository} has no valid npm package name. No package was installed.`)
|
|
69
|
+
}
|
|
70
|
+
const tree = record(githubApi(`repos/${repository}/git/trees/${sha}?recursive=1`, cwd))
|
|
71
|
+
if (!Array.isArray(tree.tree) || tree.truncated === true) {
|
|
72
|
+
throw new Error(`Cannot enumerate modules in ${repository}. No package was installed.`)
|
|
73
|
+
}
|
|
74
|
+
const moduleIds = Array.from(new Set(tree.tree.flatMap((entry: unknown) => {
|
|
75
|
+
const filePath = record(entry).path
|
|
76
|
+
const match = typeof filePath === 'string' ? /^src\/modules\/([a-z0-9]+(?:_[a-z0-9]+)*)\/index\.ts$/.exec(filePath) : null
|
|
77
|
+
return match ? [match[1]] : []
|
|
78
|
+
})))
|
|
79
|
+
if (moduleIds.length === 0) throw new Error(`Repository ${repository} has no module descriptors in src/modules/. No package was installed.`)
|
|
80
|
+
const files = tree.tree.flatMap((entry: unknown) => {
|
|
81
|
+
const filePath = record(entry).path
|
|
82
|
+
if (typeof filePath !== 'string') return []
|
|
83
|
+
const compiledPath = /^src\/modules\/.+\.tsx?$/.test(filePath) ? filePath.replace(/^src\//, 'dist/').replace(/\.tsx?$/, '.js') : undefined
|
|
84
|
+
return compiledPath ? [filePath, compiledPath] : [filePath]
|
|
85
|
+
})
|
|
86
|
+
const requiredModuleIds = new Set([
|
|
87
|
+
...(options.moduleId ? [options.moduleId] : moduleIds.length === 1 ? moduleIds : []),
|
|
88
|
+
...(options.enabledModules ?? []).filter((entry) => (entry.from ?? '@open-mercato/core') === packageName).map((entry) => entry.id),
|
|
89
|
+
])
|
|
90
|
+
const sourceCache = new Map<string, string>()
|
|
91
|
+
const readSource = (relativePath: string): string => {
|
|
92
|
+
const cached = sourceCache.get(relativePath)
|
|
93
|
+
if (cached !== undefined) return cached
|
|
94
|
+
const encodedPath = relativePath.split('/').map(encodeURIComponent).join('/')
|
|
95
|
+
const file = record(githubApi(`repos/${repository}/contents/${encodedPath}?ref=${sha}`, cwd))
|
|
96
|
+
if (file.encoding !== 'base64' || typeof file.content !== 'string' || file.content.length > 2 * 1024 * 1024) {
|
|
97
|
+
throw new Error(`Cannot inspect module source in ${repository} at the selected commit. No package was installed.`)
|
|
98
|
+
}
|
|
99
|
+
const source = Buffer.from(file.content, 'base64').toString('utf8')
|
|
100
|
+
if (Buffer.byteLength(source, 'utf8') > 1024 * 1024) throw new Error(`Module source inspection limit exceeded in ${repository}. No package was installed.`)
|
|
101
|
+
sourceCache.set(relativePath, source)
|
|
102
|
+
return source
|
|
103
|
+
}
|
|
104
|
+
for (const moduleId of requiredModuleIds) {
|
|
105
|
+
if (!moduleIds.includes(moduleId)) continue
|
|
106
|
+
assertModulePackageExports(manifest, packageName, moduleId, files, readSource)
|
|
107
|
+
}
|
|
108
|
+
return { packageName, packageSpec: `${packageName}@github:${repository}#${sha}`, moduleIds, locator: `github:${repository}` }
|
|
109
|
+
}
|