@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.
Files changed (162) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/agentic/guides/module-facts.json +162 -162
  3. package/dist/agentic/guides/module-facts.v2.json +162 -162
  4. package/dist/agentic/guides/module-tool.md +21 -0
  5. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  6. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  7. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  8. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  9. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  10. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  11. package/dist/agentic/guides/modules/auth/index.md +1 -1
  12. package/dist/agentic/guides/modules/availability/index.md +1 -1
  13. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  14. package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
  15. package/dist/agentic/guides/modules/cart/index.md +1 -1
  16. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  19. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  20. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  24. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  25. package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
  26. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  27. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  28. package/dist/agentic/guides/modules/configs/index.md +1 -1
  29. package/dist/agentic/guides/modules/content/index.md +1 -1
  30. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  31. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  32. package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
  33. package/dist/agentic/guides/modules/customers/index.md +1 -1
  34. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  35. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  36. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  37. package/dist/agentic/guides/modules/devices/index.md +1 -1
  38. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  39. package/dist/agentic/guides/modules/directory/index.md +1 -1
  40. package/dist/agentic/guides/modules/documents/index.md +1 -1
  41. package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
  42. package/dist/agentic/guides/modules/entities/index.md +1 -1
  43. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  44. package/dist/agentic/guides/modules/events/index.md +1 -1
  45. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  46. package/dist/agentic/guides/modules/forms/index.md +1 -1
  47. package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
  48. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  49. package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
  50. package/dist/agentic/guides/modules/generators/index.md +1 -1
  51. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  52. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  53. package/dist/agentic/guides/modules/messages/index.md +1 -1
  54. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  55. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  56. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  57. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  58. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  59. package/dist/agentic/guides/modules/planner/index.md +1 -1
  60. package/dist/agentic/guides/modules/portal/index.md +1 -1
  61. package/dist/agentic/guides/modules/progress/index.md +1 -1
  62. package/dist/agentic/guides/modules/promotions/index.md +1 -1
  63. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  64. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  65. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  66. package/dist/agentic/guides/modules/resources/index.md +1 -1
  67. package/dist/agentic/guides/modules/sales/index.md +1 -1
  68. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  69. package/dist/agentic/guides/modules/search/index.md +1 -1
  70. package/dist/agentic/guides/modules/security/index.md +1 -1
  71. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  72. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  73. package/dist/agentic/guides/modules/sso/index.md +1 -1
  74. package/dist/agentic/guides/modules/staff/index.md +1 -1
  75. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  76. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  77. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  78. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  79. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  80. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  81. package/dist/agentic/guides/modules/translations/index.md +1 -1
  82. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  83. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  84. package/dist/agentic/guides/modules/wms/index.md +1 -1
  85. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  86. package/dist/agentic/guides/reference-module-facts.json +1 -1
  87. package/dist/agentic/guides/upstream/manifest.json +1 -1
  88. package/dist/agentic/shared/ai/harness/cases.json +23 -9
  89. package/dist/agentic/shared/ai/harness/source-link-inventory.json +90 -4
  90. package/dist/lib/__integration__/TC-INT-007.spec.js +207 -19
  91. package/dist/lib/__integration__/TC-INT-007.spec.js.map +2 -2
  92. package/dist/lib/__integration__/TC-INT-009.spec.js +267 -0
  93. package/dist/lib/__integration__/TC-INT-009.spec.js.map +7 -0
  94. package/dist/lib/eject.js +25 -9
  95. package/dist/lib/eject.js.map +2 -2
  96. package/dist/lib/generators/entity-ids.js +2 -0
  97. package/dist/lib/generators/entity-ids.js.map +2 -2
  98. package/dist/lib/generators/module-di.js +2 -0
  99. package/dist/lib/generators/module-di.js.map +2 -2
  100. package/dist/lib/generators/module-entities.js +2 -0
  101. package/dist/lib/generators/module-entities.js.map +2 -2
  102. package/dist/lib/generators/module-package-sources.js +6 -4
  103. package/dist/lib/generators/module-package-sources.js.map +2 -2
  104. package/dist/lib/generators/module-registry.js +32 -86
  105. package/dist/lib/generators/module-registry.js.map +2 -2
  106. package/dist/lib/generators/openapi.js +2 -0
  107. package/dist/lib/generators/openapi.js.map +2 -2
  108. package/dist/lib/generators/source-exports.js +73 -0
  109. package/dist/lib/generators/source-exports.js.map +7 -0
  110. package/dist/lib/init-summary.js +44 -0
  111. package/dist/lib/init-summary.js.map +7 -0
  112. package/dist/lib/module-github.js +98 -0
  113. package/dist/lib/module-github.js.map +7 -0
  114. package/dist/lib/module-identity.js +131 -0
  115. package/dist/lib/module-identity.js.map +7 -0
  116. package/dist/lib/module-inspection.js +124 -0
  117. package/dist/lib/module-inspection.js.map +7 -0
  118. package/dist/lib/module-install.js +150 -21
  119. package/dist/lib/module-install.js.map +3 -3
  120. package/dist/lib/module-package.js +205 -15
  121. package/dist/lib/module-package.js.map +2 -2
  122. package/dist/lib/modules-config.js +69 -30
  123. package/dist/lib/modules-config.js.map +2 -2
  124. package/dist/lib/resolver.js +33 -34
  125. package/dist/lib/resolver.js.map +2 -2
  126. package/dist/mercato.js +14 -23
  127. package/dist/mercato.js.map +2 -2
  128. package/package.json +7 -7
  129. package/src/__tests__/mercato.test.ts +14 -4
  130. package/src/lib/__fixtures__/official-module-package/dist/modules/test_package/widgets/injection/test/widget.js +1 -0
  131. package/src/lib/__integration__/TC-INT-007.spec.ts +219 -19
  132. package/src/lib/__integration__/TC-INT-009.spec.ts +279 -0
  133. package/src/lib/__tests__/eject.test.ts +98 -4
  134. package/src/lib/__tests__/init-summary.test.ts +50 -0
  135. package/src/lib/__tests__/module-github.test.ts +108 -0
  136. package/src/lib/__tests__/module-identity.test.ts +144 -0
  137. package/src/lib/__tests__/module-inspection.test.ts +195 -0
  138. package/src/lib/__tests__/module-install.test.ts +399 -10
  139. package/src/lib/__tests__/module-package.test.ts +154 -0
  140. package/src/lib/__tests__/modules-config.test.ts +55 -0
  141. package/src/lib/__tests__/resolver.packages.test.ts +29 -0
  142. package/src/lib/eject.ts +28 -10
  143. package/src/lib/generators/__tests__/module-id-collisions.test.ts +146 -0
  144. package/src/lib/generators/__tests__/module-package-sources.test.ts +19 -2
  145. package/src/lib/generators/__tests__/module-requirements.test.ts +56 -0
  146. package/src/lib/generators/entity-ids.ts +2 -0
  147. package/src/lib/generators/module-di.ts +2 -0
  148. package/src/lib/generators/module-entities.ts +2 -0
  149. package/src/lib/generators/module-package-sources.ts +7 -5
  150. package/src/lib/generators/module-registry.ts +33 -115
  151. package/src/lib/generators/openapi.ts +3 -0
  152. package/src/lib/generators/source-exports.ts +93 -0
  153. package/src/lib/init-summary.ts +54 -0
  154. package/src/lib/module-github.ts +109 -0
  155. package/src/lib/module-identity.ts +145 -0
  156. package/src/lib/module-inspection.ts +133 -0
  157. package/src/lib/module-install.ts +170 -22
  158. package/src/lib/module-package.ts +243 -16
  159. package/src/lib/modules-config.ts +80 -32
  160. package/src/lib/resolver.ts +35 -39
  161. package/src/mercato.ts +14 -24
  162. 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 { readOfficialModulePackageFromRoot, resolveInstalledPackageRoot } from '../module-package'
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
- for (const entry of resolver.loadEnabledModules()) {
28
- if (!entry.from || entry.from === '@app' || entry.from === '@open-mercato/core') continue
28
+ const enabled = resolver.loadEnabledModules()
29
+ getEnabledModuleIdentities(resolver, enabled)
29
30
 
30
- const packageRoot = resolveInstalledPackageRoot(resolver, entry.from)
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 = readOfficialModulePackageFromRoot(packageRoot, entry.from, entry.id)
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
- source = fs.readFileSync(sourceFile, 'utf8')
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
- source = fs.readFileSync(sourceFile, 'utf8')
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 = resolver.getModulePaths(entry)
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 enabledIds = new Set(enabled.map((e) => e.id))
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((r) => !enabledIds.has(r))
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
- console.error('\nFix: Enable required module(s) in src/modules.ts. Example:')
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 enabledIds = new Set(enabled.map((e) => e.id))
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((r) => !enabledIds.has(r))
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
- console.error('\nFix: Enable required module(s) in src/modules.ts. Example:')
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 enabledIds = new Set(enabled.map((e) => e.id))
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((r) => !enabledIds.has(r))
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
- console.error('\nFix: Enable required module(s) in src/modules.ts. Example:')
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
+ }