@open-mercato/cli 0.6.7-develop.6553.1.2ec7246e04 → 0.6.7-develop.6566.1.15385fa179

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 (109) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/AGENTS.md +1 -0
  3. package/dist/agentic/guides/module-facts.json +53 -53
  4. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys.md +1 -1
  7. package/dist/agentic/guides/modules/attachments.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  9. package/dist/agentic/guides/modules/auth.md +1 -1
  10. package/dist/agentic/guides/modules/business_rules.md +1 -1
  11. package/dist/agentic/guides/modules/catalog.md +1 -1
  12. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  13. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  14. package/dist/agentic/guides/modules/checkout.md +1 -1
  15. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  16. package/dist/agentic/guides/modules/configs.md +1 -1
  17. package/dist/agentic/guides/modules/content.md +1 -1
  18. package/dist/agentic/guides/modules/currencies.md +1 -1
  19. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  20. package/dist/agentic/guides/modules/customers.md +1 -1
  21. package/dist/agentic/guides/modules/dashboards.md +1 -1
  22. package/dist/agentic/guides/modules/data_sync.md +1 -1
  23. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  24. package/dist/agentic/guides/modules/directory.md +1 -1
  25. package/dist/agentic/guides/modules/entities.md +1 -1
  26. package/dist/agentic/guides/modules/events.md +1 -1
  27. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  28. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  29. package/dist/agentic/guides/modules/generators.md +1 -1
  30. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  31. package/dist/agentic/guides/modules/integrations.md +1 -1
  32. package/dist/agentic/guides/modules/messages.md +1 -1
  33. package/dist/agentic/guides/modules/notifications.md +1 -1
  34. package/dist/agentic/guides/modules/onboarding.md +1 -1
  35. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  36. package/dist/agentic/guides/modules/perspectives.md +1 -1
  37. package/dist/agentic/guides/modules/planner.md +1 -1
  38. package/dist/agentic/guides/modules/portal.md +1 -1
  39. package/dist/agentic/guides/modules/progress.md +1 -1
  40. package/dist/agentic/guides/modules/query_index.md +1 -1
  41. package/dist/agentic/guides/modules/record_locks.md +1 -1
  42. package/dist/agentic/guides/modules/resources.md +1 -1
  43. package/dist/agentic/guides/modules/sales.md +1 -1
  44. package/dist/agentic/guides/modules/scheduler.md +1 -1
  45. package/dist/agentic/guides/modules/search.md +1 -1
  46. package/dist/agentic/guides/modules/security.md +1 -1
  47. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  48. package/dist/agentic/guides/modules/sso.md +1 -1
  49. package/dist/agentic/guides/modules/staff.md +1 -1
  50. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  51. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  52. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  53. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  54. package/dist/agentic/guides/modules/translations.md +1 -1
  55. package/dist/agentic/guides/modules/webhooks.md +1 -1
  56. package/dist/agentic/guides/modules/workflows.md +1 -1
  57. package/dist/agentic/shared/AGENTS.md.template +1 -1
  58. package/dist/agentic/shared/ai/skills/tiers.schema.json +15 -0
  59. package/dist/agentic/shared/scripts/install-skills.sh +244 -37
  60. package/dist/lib/__integration__/TC-INT-008.spec.js +9 -11
  61. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  62. package/dist/lib/agentic-setup.js +14 -2
  63. package/dist/lib/agentic-setup.js.map +2 -2
  64. package/dist/lib/db/commands.js +1 -1
  65. package/dist/lib/db/commands.js.map +1 -1
  66. package/dist/lib/generate-watch-events.js +106 -0
  67. package/dist/lib/generate-watch-events.js.map +7 -0
  68. package/dist/lib/generators/ast/imports.js +1 -1
  69. package/dist/lib/generators/ast/imports.js.map +1 -1
  70. package/dist/lib/generators/entity-ids.js +21 -14
  71. package/dist/lib/generators/entity-ids.js.map +2 -2
  72. package/dist/lib/generators/extensions/injection-widgets.js +1 -1
  73. package/dist/lib/generators/extensions/injection-widgets.js.map +1 -1
  74. package/dist/lib/generators/module-facts.js +1 -1
  75. package/dist/lib/generators/module-facts.js.map +1 -1
  76. package/dist/lib/generators/module-registry.js +1 -1
  77. package/dist/lib/generators/module-registry.js.map +1 -1
  78. package/dist/lib/generators/openapi.js +114 -12
  79. package/dist/lib/generators/openapi.js.map +2 -2
  80. package/dist/lib/in-process-generate-watcher.js +13 -2
  81. package/dist/lib/in-process-generate-watcher.js.map +2 -2
  82. package/dist/lib/modules-config.js +1 -1
  83. package/dist/lib/modules-config.js.map +1 -1
  84. package/dist/lib/resolver.js +1 -1
  85. package/dist/lib/resolver.js.map +1 -1
  86. package/dist/mercato.js +68 -23
  87. package/dist/mercato.js.map +2 -2
  88. package/package.json +7 -6
  89. package/src/__tests__/mercato.test.ts +53 -2
  90. package/src/lib/__integration__/TC-INT-008.spec.ts +15 -15
  91. package/src/lib/__tests__/generate-watch-events.test.ts +112 -0
  92. package/src/lib/__tests__/generate-watch-structure.test.ts +20 -0
  93. package/src/lib/__tests__/in-process-generate-watcher.test.ts +193 -2
  94. package/src/lib/agentic-setup.ts +23 -4
  95. package/src/lib/db/commands.ts +1 -1
  96. package/src/lib/generate-watch-events.ts +125 -0
  97. package/src/lib/generators/__tests__/entity-ids.test.ts +98 -1
  98. package/src/lib/generators/__tests__/openapi.test.ts +174 -0
  99. package/src/lib/generators/__tests__/structural-contracts.test.ts +1 -1
  100. package/src/lib/generators/ast/imports.ts +1 -1
  101. package/src/lib/generators/entity-ids.ts +34 -14
  102. package/src/lib/generators/extensions/injection-widgets.ts +1 -1
  103. package/src/lib/generators/module-facts.ts +1 -1
  104. package/src/lib/generators/module-registry.ts +1 -1
  105. package/src/lib/generators/openapi.ts +167 -14
  106. package/src/lib/in-process-generate-watcher.ts +35 -6
  107. package/src/lib/modules-config.ts +1 -1
  108. package/src/lib/resolver.ts +1 -1
  109. package/src/mercato.ts +75 -27
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/modules-config.ts"],
4
- "sourcesContent": ["import fs from 'node:fs'\nimport ts from 'typescript'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\nexport type ModuleEntry = {\n id: string\n from?: '@open-mercato/core' | '@app' | string\n}\n\ntype ModuleOccurrence = {\n entry: ModuleEntry\n node: ts.ObjectLiteralExpression\n}\n\ntype ModuleConfigShape = {\n arrayNode: ts.ArrayLiteralExpression\n occurrences: ModuleOccurrence[]\n}\n\nfunction parseModuleEntryFromObjectLiteral(node: ts.ObjectLiteralExpression): ModuleEntry | null {\n let id: string | null = null\n let from: string | null = null\n\n for (const property of node.properties) {\n if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) continue\n\n if (property.name.text === 'id' && ts.isStringLiteralLike(property.initializer)) {\n id = property.initializer.text\n }\n\n if (property.name.text === 'from' && ts.isStringLiteralLike(property.initializer)) {\n from = property.initializer.text\n }\n }\n\n if (!id) return null\n\n return {\n id,\n from: from ?? '@open-mercato/core',\n }\n}\n\nfunction parseProcessEnvAccess(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n): { matched: boolean; value: string | undefined } {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {\n const target = node.expression\n if (\n ts.isPropertyAccessExpression(target)\n && ts.isIdentifier(target.expression)\n && target.expression.text === 'process'\n && target.name.text === 'env'\n ) {\n return { matched: true, value: env[node.name.text] }\n }\n }\n\n if (\n ts.isElementAccessExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && ts.isIdentifier(node.expression.expression)\n && node.expression.expression.text === 'process'\n && node.expression.name.text === 'env'\n && ts.isStringLiteralLike(node.argumentExpression)\n ) {\n return { matched: true, value: env[node.argumentExpression.text] }\n }\n\n return { matched: false, value: undefined }\n}\n\nfunction evaluateStaticExpressionWithScope(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): unknown {\n if (ts.isParenthesizedExpression(node)) {\n return evaluateStaticExpressionWithScope(node.expression, env, scope)\n }\n\n if (ts.isIdentifier(node)) {\n return scope.get(node.text)\n }\n\n if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) {\n return node.text\n }\n\n if (ts.isNumericLiteral(node)) {\n return Number(node.text)\n }\n\n if (node.kind === ts.SyntaxKind.TrueKeyword) return true\n if (node.kind === ts.SyntaxKind.FalseKeyword) return false\n if (node.kind === ts.SyntaxKind.NullKeyword) return null\n\n const envAccess = parseProcessEnvAccess(node, env)\n if (envAccess.matched) {\n return envAccess.value\n }\n\n if (ts.isPropertyAccessExpression(node)) {\n const target = evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (target && typeof target === 'object' && node.name.text in target) {\n return (target as Record<string, unknown>)[node.name.text]\n }\n return undefined\n }\n\n if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {\n return !Boolean(evaluateStaticExpressionWithScope(node.operand, env, scope))\n }\n\n if (ts.isBinaryExpression(node)) {\n if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n && Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n || Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope)\n === evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope)\n !== evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n }\n\n if (\n ts.isCallExpression(node)\n && ts.isIdentifier(node.expression)\n && node.expression.text === 'parseBooleanWithDefault'\n ) {\n const rawValueNode = node.arguments[0]\n const fallbackNode = node.arguments[1]\n const rawValue = rawValueNode\n ? evaluateStaticExpressionWithScope(rawValueNode, env, scope)\n : undefined\n const fallbackValue = fallbackNode\n ? evaluateStaticExpressionWithScope(fallbackNode, env, scope)\n : false\n\n return parseBooleanWithDefault(\n typeof rawValue === 'string' ? rawValue : undefined,\n Boolean(fallbackValue),\n )\n }\n\n if (\n ts.isCallExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && node.expression.name.text === 'some'\n ) {\n const target = evaluateStaticExpressionWithScope(node.expression.expression, env, scope)\n const predicate = node.arguments[0]\n if (!Array.isArray(target) || !predicate) {\n return undefined\n }\n\n return target.some((entry) => {\n if (!ts.isArrowFunction(predicate) && !ts.isFunctionExpression(predicate)) {\n return false\n }\n\n const localScope = new Map(scope)\n const parameter = predicate.parameters[0]\n if (parameter && ts.isIdentifier(parameter.name)) {\n localScope.set(parameter.name.text, entry)\n }\n\n if (ts.isBlock(predicate.body)) {\n const returnStatement = predicate.body.statements.find((statement): statement is ts.ReturnStatement =>\n ts.isReturnStatement(statement),\n )\n if (!returnStatement?.expression) {\n return false\n }\n return Boolean(evaluateStaticExpressionWithScope(returnStatement.expression, env, localScope))\n }\n\n return Boolean(evaluateStaticExpressionWithScope(predicate.body, env, localScope))\n })\n }\n\n return undefined\n}\n\nfunction evaluateStaticCondition(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): boolean {\n return Boolean(evaluateStaticExpressionWithScope(node, env, scope))\n}\n\nfunction collectPushEntriesFromStatement(\n statement: ts.Statement,\n env: NodeJS.ProcessEnv,\n targetVariableName: string,\n scope: Map<string, unknown>,\n): ModuleOccurrence[] {\n if (ts.isBlock(statement)) {\n return statement.statements.flatMap((child) =>\n collectPushEntriesFromStatement(child, env, targetVariableName, scope),\n )\n }\n\n if (ts.isIfStatement(statement)) {\n if (evaluateStaticCondition(statement.expression, env, scope)) {\n return collectPushEntriesFromStatement(statement.thenStatement, env, targetVariableName, scope)\n }\n\n if (statement.elseStatement) {\n return collectPushEntriesFromStatement(statement.elseStatement, env, targetVariableName, scope)\n }\n\n return []\n }\n\n if (!ts.isExpressionStatement(statement)) return []\n\n const expression = statement.expression\n if (!ts.isCallExpression(expression) || !ts.isPropertyAccessExpression(expression.expression)) {\n return []\n }\n\n const pushTarget = expression.expression.expression\n const pushMethod = expression.expression.name\n\n if (!ts.isIdentifier(pushTarget) || pushTarget.text !== targetVariableName || pushMethod.text !== 'push') {\n return []\n }\n\n return expression.arguments.flatMap((argument) => {\n if (!ts.isObjectLiteralExpression(argument)) return []\n const entry = parseModuleEntryFromObjectLiteral(argument)\n return entry ? [{ entry, node: argument }] : []\n })\n}\n\nfunction parseModuleConfigShape(\n source: string,\n env: NodeJS.ProcessEnv = process.env,\n): ModuleConfigShape {\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const variableName = 'enabledModules'\n const occurrences: ModuleOccurrence[] = []\n const scope = new Map<string, unknown>()\n let arrayNode: ts.ArrayLiteralExpression | null = null\n let foundDeclaration = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isVariableStatement(statement)) {\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name)) continue\n\n if (declaration.name.text === variableName) {\n if (!declaration.initializer || !ts.isArrayLiteralExpression(declaration.initializer)) continue\n\n arrayNode = declaration.initializer\n occurrences.push(...declaration.initializer.elements.flatMap((element) => {\n if (!ts.isObjectLiteralExpression(element)) return []\n const entry = parseModuleEntryFromObjectLiteral(element)\n return entry ? [{ entry, node: element }] : []\n }))\n scope.set(variableName, occurrences.map((occurrence) => occurrence.entry))\n foundDeclaration = true\n continue\n }\n\n if (!declaration.initializer) continue\n\n scope.set(\n declaration.name.text,\n evaluateStaticExpressionWithScope(declaration.initializer, env, scope),\n )\n }\n continue\n }\n\n if (!foundDeclaration) continue\n\n const pushedOccurrences = collectPushEntriesFromStatement(statement, env, variableName, scope)\n if (pushedOccurrences.length > 0) {\n occurrences.push(...pushedOccurrences)\n scope.set(variableName, occurrences.map((occurrence) => occurrence.entry))\n }\n }\n\n if (!arrayNode) {\n throw new Error('Could not find enabledModules array declaration in src/modules.ts')\n }\n\n return { arrayNode, occurrences }\n}\n\nfunction normalizeModuleEntry(entry: ModuleEntry): ModuleEntry {\n return {\n id: entry.id,\n from: entry.from ?? '@open-mercato/core',\n }\n}\n\nfunction escapeSingleQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n}\n\nfunction buildModuleEntryLiteral(entry: ModuleEntry): string {\n const normalized = normalizeModuleEntry(entry)\n return `{ id: '${escapeSingleQuotedString(normalized.id)}', from: '${escapeSingleQuotedString(normalized.from ?? '@open-mercato/core')}' }`\n}\n\nfunction lineIndentAt(source: string, position: number): string {\n const lineStart = source.lastIndexOf('\\n', Math.max(0, position - 1)) + 1\n const lineToPosition = source.slice(lineStart, position)\n const match = lineToPosition.match(/^\\s*/)\n return match?.[0] ?? ''\n}\n\nfunction replaceArrayLiteral(\n source: string,\n arrayNode: ts.ArrayLiteralExpression,\n entry: ModuleEntry,\n sourceFile: ts.SourceFile,\n): string {\n const existingElements = arrayNode.elements.map((element) =>\n source.slice(element.getStart(sourceFile), element.end).trim(),\n )\n const closingIndent = lineIndentAt(source, arrayNode.end - 1)\n const elementIndent =\n arrayNode.elements.length > 0\n ? lineIndentAt(source, arrayNode.elements[0].getStart(sourceFile))\n : `${closingIndent} `\n const updatedArray = [\n '[',\n ...[...existingElements, buildModuleEntryLiteral(entry)].map((value) => `${elementIndent}${value},`),\n `${closingIndent}]`,\n ].join('\\n')\n\n return source.slice(0, arrayNode.getStart(sourceFile)) + updatedArray + source.slice(arrayNode.end)\n}\n\nfunction upsertModuleSource(objectLiteral: string, from: string): string {\n if (/from\\s*:\\s*'[^']*'/.test(objectLiteral)) {\n return objectLiteral.replace(/from\\s*:\\s*'[^']*'/, `from: '${escapeSingleQuotedString(from)}'`)\n }\n\n if (/from\\s*:\\s*\"[^\"]*\"/.test(objectLiteral)) {\n return objectLiteral.replace(/from\\s*:\\s*\"[^\"]*\"/, `from: \"${from.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`)\n }\n\n return objectLiteral.replace(/\\}\\s*$/, `, from: '${escapeSingleQuotedString(from)}' }`)\n}\n\nexport function ensureModuleRegistration(\n modulesPath: string,\n entry: ModuleEntry,\n): { changed: boolean; registeredAs: string } {\n if (!fs.existsSync(modulesPath)) {\n throw new Error(`modules.ts not found at ${modulesPath}`)\n }\n\n const source = fs.readFileSync(modulesPath, 'utf8')\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const config = parseModuleConfigShape(source)\n const normalized = normalizeModuleEntry(entry)\n const matches = config.occurrences.filter((occurrence) => occurrence.entry.id === normalized.id)\n\n if (matches.length > 1) {\n throw new Error(\n `Module \"${normalized.id}\" is registered multiple times in ${modulesPath}. Resolve duplicates manually before running this command.`,\n )\n }\n\n if (matches.length === 1) {\n const existing = normalizeModuleEntry(matches[0].entry)\n if (existing.from === normalized.from) {\n return { changed: false, registeredAs: normalized.from ?? '@open-mercato/core' }\n }\n\n throw new Error(\n `Module \"${normalized.id}\" is already registered from \"${existing.from}\". Remove or eject the existing registration first.`,\n )\n }\n\n const updated = replaceArrayLiteral(source, config.arrayNode, normalized, sourceFile)\n fs.writeFileSync(modulesPath, updated)\n\n return { changed: true, registeredAs: normalized.from ?? '@open-mercato/core' }\n}\n\nexport function setModuleRegistrationSource(\n modulesPath: string,\n moduleId: string,\n from: string,\n): { changed: boolean } {\n if (!fs.existsSync(modulesPath)) {\n throw new Error(`modules.ts not found at ${modulesPath}`)\n }\n\n const source = fs.readFileSync(modulesPath, 'utf8')\n const config = parseModuleConfigShape(source)\n const matches = config.occurrences.filter((occurrence) => occurrence.entry.id === moduleId)\n\n if (matches.length === 0) {\n throw new Error(\n `Could not find module entry for \"${moduleId}\" in ${modulesPath}. Expected a pattern like: { id: '${moduleId}', from: '...' } or { id: '${moduleId}' }`,\n )\n }\n\n if (matches.length > 1) {\n throw new Error(\n `Module \"${moduleId}\" is registered multiple times in ${modulesPath}. Resolve duplicates manually before running this command.`,\n )\n }\n\n const match = matches[0]\n const current = normalizeModuleEntry(match.entry)\n if (current.from === from) {\n return { changed: false }\n }\n\n const objectLiteral = source.slice(match.node.getStart(), match.node.end)\n const updatedObjectLiteral = upsertModuleSource(objectLiteral, from)\n const updated =\n source.slice(0, match.node.getStart()) +\n updatedObjectLiteral +\n source.slice(match.node.end)\n\n fs.writeFileSync(modulesPath, updated)\n\n return { changed: true }\n}\n"],
4
+ "sourcesContent": ["import fs from 'node:fs'\nimport ts from 'typescript-js'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\nexport type ModuleEntry = {\n id: string\n from?: '@open-mercato/core' | '@app' | string\n}\n\ntype ModuleOccurrence = {\n entry: ModuleEntry\n node: ts.ObjectLiteralExpression\n}\n\ntype ModuleConfigShape = {\n arrayNode: ts.ArrayLiteralExpression\n occurrences: ModuleOccurrence[]\n}\n\nfunction parseModuleEntryFromObjectLiteral(node: ts.ObjectLiteralExpression): ModuleEntry | null {\n let id: string | null = null\n let from: string | null = null\n\n for (const property of node.properties) {\n if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) continue\n\n if (property.name.text === 'id' && ts.isStringLiteralLike(property.initializer)) {\n id = property.initializer.text\n }\n\n if (property.name.text === 'from' && ts.isStringLiteralLike(property.initializer)) {\n from = property.initializer.text\n }\n }\n\n if (!id) return null\n\n return {\n id,\n from: from ?? '@open-mercato/core',\n }\n}\n\nfunction parseProcessEnvAccess(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n): { matched: boolean; value: string | undefined } {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {\n const target = node.expression\n if (\n ts.isPropertyAccessExpression(target)\n && ts.isIdentifier(target.expression)\n && target.expression.text === 'process'\n && target.name.text === 'env'\n ) {\n return { matched: true, value: env[node.name.text] }\n }\n }\n\n if (\n ts.isElementAccessExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && ts.isIdentifier(node.expression.expression)\n && node.expression.expression.text === 'process'\n && node.expression.name.text === 'env'\n && ts.isStringLiteralLike(node.argumentExpression)\n ) {\n return { matched: true, value: env[node.argumentExpression.text] }\n }\n\n return { matched: false, value: undefined }\n}\n\nfunction evaluateStaticExpressionWithScope(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): unknown {\n if (ts.isParenthesizedExpression(node)) {\n return evaluateStaticExpressionWithScope(node.expression, env, scope)\n }\n\n if (ts.isIdentifier(node)) {\n return scope.get(node.text)\n }\n\n if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) {\n return node.text\n }\n\n if (ts.isNumericLiteral(node)) {\n return Number(node.text)\n }\n\n if (node.kind === ts.SyntaxKind.TrueKeyword) return true\n if (node.kind === ts.SyntaxKind.FalseKeyword) return false\n if (node.kind === ts.SyntaxKind.NullKeyword) return null\n\n const envAccess = parseProcessEnvAccess(node, env)\n if (envAccess.matched) {\n return envAccess.value\n }\n\n if (ts.isPropertyAccessExpression(node)) {\n const target = evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (target && typeof target === 'object' && node.name.text in target) {\n return (target as Record<string, unknown>)[node.name.text]\n }\n return undefined\n }\n\n if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {\n return !Boolean(evaluateStaticExpressionWithScope(node.operand, env, scope))\n }\n\n if (ts.isBinaryExpression(node)) {\n if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n && Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n || Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope)\n === evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n\n if (node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope)\n !== evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n }\n\n if (\n ts.isCallExpression(node)\n && ts.isIdentifier(node.expression)\n && node.expression.text === 'parseBooleanWithDefault'\n ) {\n const rawValueNode = node.arguments[0]\n const fallbackNode = node.arguments[1]\n const rawValue = rawValueNode\n ? evaluateStaticExpressionWithScope(rawValueNode, env, scope)\n : undefined\n const fallbackValue = fallbackNode\n ? evaluateStaticExpressionWithScope(fallbackNode, env, scope)\n : false\n\n return parseBooleanWithDefault(\n typeof rawValue === 'string' ? rawValue : undefined,\n Boolean(fallbackValue),\n )\n }\n\n if (\n ts.isCallExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && node.expression.name.text === 'some'\n ) {\n const target = evaluateStaticExpressionWithScope(node.expression.expression, env, scope)\n const predicate = node.arguments[0]\n if (!Array.isArray(target) || !predicate) {\n return undefined\n }\n\n return target.some((entry) => {\n if (!ts.isArrowFunction(predicate) && !ts.isFunctionExpression(predicate)) {\n return false\n }\n\n const localScope = new Map(scope)\n const parameter = predicate.parameters[0]\n if (parameter && ts.isIdentifier(parameter.name)) {\n localScope.set(parameter.name.text, entry)\n }\n\n if (ts.isBlock(predicate.body)) {\n const returnStatement = predicate.body.statements.find((statement): statement is ts.ReturnStatement =>\n ts.isReturnStatement(statement),\n )\n if (!returnStatement?.expression) {\n return false\n }\n return Boolean(evaluateStaticExpressionWithScope(returnStatement.expression, env, localScope))\n }\n\n return Boolean(evaluateStaticExpressionWithScope(predicate.body, env, localScope))\n })\n }\n\n return undefined\n}\n\nfunction evaluateStaticCondition(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): boolean {\n return Boolean(evaluateStaticExpressionWithScope(node, env, scope))\n}\n\nfunction collectPushEntriesFromStatement(\n statement: ts.Statement,\n env: NodeJS.ProcessEnv,\n targetVariableName: string,\n scope: Map<string, unknown>,\n): ModuleOccurrence[] {\n if (ts.isBlock(statement)) {\n return statement.statements.flatMap((child) =>\n collectPushEntriesFromStatement(child, env, targetVariableName, scope),\n )\n }\n\n if (ts.isIfStatement(statement)) {\n if (evaluateStaticCondition(statement.expression, env, scope)) {\n return collectPushEntriesFromStatement(statement.thenStatement, env, targetVariableName, scope)\n }\n\n if (statement.elseStatement) {\n return collectPushEntriesFromStatement(statement.elseStatement, env, targetVariableName, scope)\n }\n\n return []\n }\n\n if (!ts.isExpressionStatement(statement)) return []\n\n const expression = statement.expression\n if (!ts.isCallExpression(expression) || !ts.isPropertyAccessExpression(expression.expression)) {\n return []\n }\n\n const pushTarget = expression.expression.expression\n const pushMethod = expression.expression.name\n\n if (!ts.isIdentifier(pushTarget) || pushTarget.text !== targetVariableName || pushMethod.text !== 'push') {\n return []\n }\n\n return expression.arguments.flatMap((argument) => {\n if (!ts.isObjectLiteralExpression(argument)) return []\n const entry = parseModuleEntryFromObjectLiteral(argument)\n return entry ? [{ entry, node: argument }] : []\n })\n}\n\nfunction parseModuleConfigShape(\n source: string,\n env: NodeJS.ProcessEnv = process.env,\n): ModuleConfigShape {\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const variableName = 'enabledModules'\n const occurrences: ModuleOccurrence[] = []\n const scope = new Map<string, unknown>()\n let arrayNode: ts.ArrayLiteralExpression | null = null\n let foundDeclaration = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isVariableStatement(statement)) {\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name)) continue\n\n if (declaration.name.text === variableName) {\n if (!declaration.initializer || !ts.isArrayLiteralExpression(declaration.initializer)) continue\n\n arrayNode = declaration.initializer\n occurrences.push(...declaration.initializer.elements.flatMap((element) => {\n if (!ts.isObjectLiteralExpression(element)) return []\n const entry = parseModuleEntryFromObjectLiteral(element)\n return entry ? [{ entry, node: element }] : []\n }))\n scope.set(variableName, occurrences.map((occurrence) => occurrence.entry))\n foundDeclaration = true\n continue\n }\n\n if (!declaration.initializer) continue\n\n scope.set(\n declaration.name.text,\n evaluateStaticExpressionWithScope(declaration.initializer, env, scope),\n )\n }\n continue\n }\n\n if (!foundDeclaration) continue\n\n const pushedOccurrences = collectPushEntriesFromStatement(statement, env, variableName, scope)\n if (pushedOccurrences.length > 0) {\n occurrences.push(...pushedOccurrences)\n scope.set(variableName, occurrences.map((occurrence) => occurrence.entry))\n }\n }\n\n if (!arrayNode) {\n throw new Error('Could not find enabledModules array declaration in src/modules.ts')\n }\n\n return { arrayNode, occurrences }\n}\n\nfunction normalizeModuleEntry(entry: ModuleEntry): ModuleEntry {\n return {\n id: entry.id,\n from: entry.from ?? '@open-mercato/core',\n }\n}\n\nfunction escapeSingleQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n}\n\nfunction buildModuleEntryLiteral(entry: ModuleEntry): string {\n const normalized = normalizeModuleEntry(entry)\n return `{ id: '${escapeSingleQuotedString(normalized.id)}', from: '${escapeSingleQuotedString(normalized.from ?? '@open-mercato/core')}' }`\n}\n\nfunction lineIndentAt(source: string, position: number): string {\n const lineStart = source.lastIndexOf('\\n', Math.max(0, position - 1)) + 1\n const lineToPosition = source.slice(lineStart, position)\n const match = lineToPosition.match(/^\\s*/)\n return match?.[0] ?? ''\n}\n\nfunction replaceArrayLiteral(\n source: string,\n arrayNode: ts.ArrayLiteralExpression,\n entry: ModuleEntry,\n sourceFile: ts.SourceFile,\n): string {\n const existingElements = arrayNode.elements.map((element) =>\n source.slice(element.getStart(sourceFile), element.end).trim(),\n )\n const closingIndent = lineIndentAt(source, arrayNode.end - 1)\n const elementIndent =\n arrayNode.elements.length > 0\n ? lineIndentAt(source, arrayNode.elements[0].getStart(sourceFile))\n : `${closingIndent} `\n const updatedArray = [\n '[',\n ...[...existingElements, buildModuleEntryLiteral(entry)].map((value) => `${elementIndent}${value},`),\n `${closingIndent}]`,\n ].join('\\n')\n\n return source.slice(0, arrayNode.getStart(sourceFile)) + updatedArray + source.slice(arrayNode.end)\n}\n\nfunction upsertModuleSource(objectLiteral: string, from: string): string {\n if (/from\\s*:\\s*'[^']*'/.test(objectLiteral)) {\n return objectLiteral.replace(/from\\s*:\\s*'[^']*'/, `from: '${escapeSingleQuotedString(from)}'`)\n }\n\n if (/from\\s*:\\s*\"[^\"]*\"/.test(objectLiteral)) {\n return objectLiteral.replace(/from\\s*:\\s*\"[^\"]*\"/, `from: \"${from.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`)\n }\n\n return objectLiteral.replace(/\\}\\s*$/, `, from: '${escapeSingleQuotedString(from)}' }`)\n}\n\nexport function ensureModuleRegistration(\n modulesPath: string,\n entry: ModuleEntry,\n): { changed: boolean; registeredAs: string } {\n if (!fs.existsSync(modulesPath)) {\n throw new Error(`modules.ts not found at ${modulesPath}`)\n }\n\n const source = fs.readFileSync(modulesPath, 'utf8')\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const config = parseModuleConfigShape(source)\n const normalized = normalizeModuleEntry(entry)\n const matches = config.occurrences.filter((occurrence) => occurrence.entry.id === normalized.id)\n\n if (matches.length > 1) {\n throw new Error(\n `Module \"${normalized.id}\" is registered multiple times in ${modulesPath}. Resolve duplicates manually before running this command.`,\n )\n }\n\n if (matches.length === 1) {\n const existing = normalizeModuleEntry(matches[0].entry)\n if (existing.from === normalized.from) {\n return { changed: false, registeredAs: normalized.from ?? '@open-mercato/core' }\n }\n\n throw new Error(\n `Module \"${normalized.id}\" is already registered from \"${existing.from}\". Remove or eject the existing registration first.`,\n )\n }\n\n const updated = replaceArrayLiteral(source, config.arrayNode, normalized, sourceFile)\n fs.writeFileSync(modulesPath, updated)\n\n return { changed: true, registeredAs: normalized.from ?? '@open-mercato/core' }\n}\n\nexport function setModuleRegistrationSource(\n modulesPath: string,\n moduleId: string,\n from: string,\n): { changed: boolean } {\n if (!fs.existsSync(modulesPath)) {\n throw new Error(`modules.ts not found at ${modulesPath}`)\n }\n\n const source = fs.readFileSync(modulesPath, 'utf8')\n const config = parseModuleConfigShape(source)\n const matches = config.occurrences.filter((occurrence) => occurrence.entry.id === moduleId)\n\n if (matches.length === 0) {\n throw new Error(\n `Could not find module entry for \"${moduleId}\" in ${modulesPath}. Expected a pattern like: { id: '${moduleId}', from: '...' } or { id: '${moduleId}' }`,\n )\n }\n\n if (matches.length > 1) {\n throw new Error(\n `Module \"${moduleId}\" is registered multiple times in ${modulesPath}. Resolve duplicates manually before running this command.`,\n )\n }\n\n const match = matches[0]\n const current = normalizeModuleEntry(match.entry)\n if (current.from === from) {\n return { changed: false }\n }\n\n const objectLiteral = source.slice(match.node.getStart(), match.node.end)\n const updatedObjectLiteral = upsertModuleSource(objectLiteral, from)\n const updated =\n source.slice(0, match.node.getStart()) +\n updatedObjectLiteral +\n source.slice(match.node.end)\n\n fs.writeFileSync(modulesPath, updated)\n\n return { changed: true }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
- import ts from "typescript";
3
+ import ts from "typescript-js";
4
4
  import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
5
5
  function pkgDirFor(rootDir, from, isMonorepo = true) {
6
6
  if (!isMonorepo) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/resolver.ts"],
4
- "sourcesContent": ["import path from 'node:path'\nimport fs from 'node:fs'\nimport ts from 'typescript'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\n/**\n * Resolved execution environment for the CLI.\n * Produced once by resolveEnvironment() and consumed by all subsystems.\n * Eliminates per-subsystem isMonorepo() branching and hardcoded path segments.\n */\nexport type CliEnvironment = {\n /** Whether the CLI is running inside a Yarn/npm workspace monorepo. */\n mode: 'monorepo' | 'standalone'\n /** Workspace or project root (where package.json / node_modules live). */\n rootDir: string\n /** Next.js application directory (contains src/, package.json, next.config.*). */\n appDir: string\n /**\n * Resolve the root directory of an installed @open-mercato package.\n * Monorepo: packages/<pkg>/ Standalone: node_modules/<pkg>/\n */\n packageRoot: (packageName: string) => string\n}\n\nexport type ModuleEntry = {\n id: string\n from?: '@open-mercato/core' | '@app' | string\n}\n\nexport type PackageInfo = {\n name: string\n path: string\n modulesPath: string\n}\n\nexport interface PackageResolver {\n isMonorepo(): boolean\n getRootDir(): string\n getAppDir(): string\n getOutputDir(): string\n getModulesConfigPath(): string\n discoverPackages(): PackageInfo[]\n loadEnabledModules(): ModuleEntry[]\n getModulePaths(entry: ModuleEntry): { appBase: string; pkgBase: string }\n getModuleImportBase(entry: ModuleEntry): { appBase: string; pkgBase: string }\n getPackageOutputDir(packageName: string): string\n getPackageRoot(from?: string): string\n}\n\nfunction pkgDirFor(rootDir: string, from?: string, isMonorepo = true): string {\n if (!isMonorepo) {\n const pkgName = from || '@open-mercato/core'\n const pkgRoot = path.join(rootDir, 'node_modules', pkgName)\n const distModules = path.join(pkgRoot, 'dist', 'modules')\n if (fs.existsSync(distModules)) return distModules\n return path.join(pkgRoot, 'src', 'modules')\n }\n\n // Monorepo mode - read from src/modules (TypeScript source)\n if (!from || from === '@open-mercato/core') {\n return path.resolve(rootDir, 'packages/core/src/modules')\n }\n // Support other local packages like '@open-mercato/onboarding' => packages/onboarding/src/modules\n const m = from.match(/^@open-mercato\\/(.+)$/)\n if (m) {\n return path.resolve(rootDir, `packages/${m[1]}/src/modules`)\n }\n // Fallback to core modules path\n return path.resolve(rootDir, 'packages/core/src/modules')\n}\n\nfunction pkgRootFor(rootDir: string, from?: string, isMonorepo = true): string {\n if (!isMonorepo) {\n const pkgName = from || '@open-mercato/core'\n return path.join(rootDir, 'node_modules', pkgName)\n }\n\n if (!from || from === '@open-mercato/core') {\n return path.resolve(rootDir, 'packages/core')\n }\n const m = from.match(/^@open-mercato\\/(.+)$/)\n if (m) {\n return path.resolve(rootDir, `packages/${m[1]}`)\n }\n return path.resolve(rootDir, 'packages/core')\n}\n\nfunction parseModuleEntryFromObjectLiteral(node: ts.ObjectLiteralExpression): ModuleEntry | null {\n let id: string | null = null\n let from: string | null = null\n for (const property of node.properties) {\n if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) continue\n const key = property.name.text\n if (key === 'id' && ts.isStringLiteralLike(property.initializer)) {\n id = property.initializer.text\n }\n if (key === 'from' && ts.isStringLiteralLike(property.initializer)) {\n from = property.initializer.text\n }\n }\n if (!id) return null\n return { id, from: from ?? '@open-mercato/core' }\n}\n\nfunction parseProcessEnvAccess(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n): { matched: boolean; value: string | undefined } {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {\n const target = node.expression\n if (\n ts.isPropertyAccessExpression(target)\n && ts.isIdentifier(target.expression)\n && target.expression.text === 'process'\n && target.name.text === 'env'\n ) {\n return { matched: true, value: env[node.name.text] }\n }\n }\n if (\n ts.isElementAccessExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && ts.isIdentifier(node.expression.expression)\n && node.expression.expression.text === 'process'\n && node.expression.name.text === 'env'\n && ts.isStringLiteralLike(node.argumentExpression)\n ) {\n return { matched: true, value: env[node.argumentExpression.text] }\n }\n return { matched: false, value: undefined }\n}\n\nfunction evaluateStaticExpression(node: ts.Expression, env: NodeJS.ProcessEnv): unknown {\n return evaluateStaticExpressionWithScope(node, env, new Map())\n}\n\nfunction evaluateStaticExpressionWithScope(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): unknown {\n if (ts.isParenthesizedExpression(node)) return evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (ts.isIdentifier(node)) {\n return scope.get(node.text)\n }\n if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text\n if (ts.isNumericLiteral(node)) return Number(node.text)\n if (node.kind === ts.SyntaxKind.TrueKeyword) return true\n if (node.kind === ts.SyntaxKind.FalseKeyword) return false\n if (node.kind === ts.SyntaxKind.NullKeyword) return null\n\n const envAccess = parseProcessEnvAccess(node, env)\n if (envAccess.matched) return envAccess.value\n\n if (ts.isPropertyAccessExpression(node)) {\n const target = evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (target && typeof target === 'object' && node.name.text in target) {\n return (target as Record<string, unknown>)[node.name.text]\n }\n return undefined\n }\n\n if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {\n return !Boolean(evaluateStaticExpressionWithScope(node.operand, env, scope))\n }\n\n if (ts.isBinaryExpression(node)) {\n if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n && Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n || Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope) === evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n if (node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope) !== evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n }\n\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'parseBooleanWithDefault') {\n const rawValueNode = node.arguments[0]\n const fallbackNode = node.arguments[1]\n const rawValue = rawValueNode ? evaluateStaticExpressionWithScope(rawValueNode, env, scope) : undefined\n const fallbackValue = fallbackNode ? evaluateStaticExpressionWithScope(fallbackNode, env, scope) : false\n return parseBooleanWithDefault(typeof rawValue === 'string' ? rawValue : undefined, Boolean(fallbackValue))\n }\n\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'some') {\n const target = evaluateStaticExpressionWithScope(node.expression.expression, env, scope)\n const predicate = node.arguments[0]\n if (!Array.isArray(target) || !predicate) return undefined\n\n return target.some((entry) => {\n if (!ts.isArrowFunction(predicate) && !ts.isFunctionExpression(predicate)) return false\n\n const localScope = new Map(scope)\n const parameter = predicate.parameters[0]\n if (parameter && ts.isIdentifier(parameter.name)) {\n localScope.set(parameter.name.text, entry)\n }\n\n if (ts.isBlock(predicate.body)) {\n const returnStatement = predicate.body.statements.find((statement): statement is ts.ReturnStatement =>\n ts.isReturnStatement(statement),\n )\n if (!returnStatement?.expression) return false\n return Boolean(evaluateStaticExpressionWithScope(returnStatement.expression, env, localScope))\n }\n\n return Boolean(evaluateStaticExpressionWithScope(predicate.body, env, localScope))\n })\n }\n\n return undefined\n}\n\nfunction evaluateStaticCondition(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): boolean {\n const evaluated = evaluateStaticExpressionWithScope(node, env, scope)\n return Boolean(evaluated)\n}\n\nfunction collectPushEntriesFromStatement(\n statement: ts.Statement,\n env: NodeJS.ProcessEnv,\n targetVariableName: string,\n scope: Map<string, unknown>,\n): ModuleEntry[] {\n if (ts.isBlock(statement)) {\n return statement.statements.flatMap((child) => collectPushEntriesFromStatement(child, env, targetVariableName, scope))\n }\n\n if (ts.isIfStatement(statement)) {\n if (evaluateStaticCondition(statement.expression, env, scope)) {\n return collectPushEntriesFromStatement(statement.thenStatement, env, targetVariableName, scope)\n }\n if (statement.elseStatement) {\n return collectPushEntriesFromStatement(statement.elseStatement, env, targetVariableName, scope)\n }\n return []\n }\n\n if (!ts.isExpressionStatement(statement)) return []\n const expression = statement.expression\n if (!ts.isCallExpression(expression)) return []\n if (!ts.isPropertyAccessExpression(expression.expression)) return []\n const pushTarget = expression.expression.expression\n const pushMethod = expression.expression.name\n if (!ts.isIdentifier(pushTarget) || pushTarget.text !== targetVariableName) return []\n if (pushMethod.text !== 'push') return []\n\n return expression.arguments.flatMap((argument) => {\n if (!ts.isObjectLiteralExpression(argument)) return []\n const entry = parseModuleEntryFromObjectLiteral(argument)\n return entry ? [entry] : []\n })\n}\n\nfunction parseModulesFromSource(source: string, env: NodeJS.ProcessEnv = process.env): ModuleEntry[] {\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const modules: ModuleEntry[] = []\n const variableName = 'enabledModules'\n const scope = new Map<string, unknown>()\n let foundDeclaration = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isVariableStatement(statement)) {\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name)) continue\n if (declaration.name.text === variableName) {\n if (!declaration.initializer || !ts.isArrayLiteralExpression(declaration.initializer)) continue\n const fromArray = declaration.initializer.elements.flatMap((element) => {\n if (!ts.isObjectLiteralExpression(element)) return []\n const entry = parseModuleEntryFromObjectLiteral(element)\n return entry ? [entry] : []\n })\n modules.push(...fromArray)\n scope.set(variableName, [...modules])\n foundDeclaration = true\n continue\n }\n if (!declaration.initializer) continue\n scope.set(\n declaration.name.text,\n evaluateStaticExpressionWithScope(declaration.initializer, env, scope),\n )\n }\n continue\n }\n if (!foundDeclaration) continue\n const pushedEntries = collectPushEntriesFromStatement(statement, env, variableName, scope)\n if (pushedEntries.length > 0) {\n modules.push(...pushedEntries)\n scope.set(variableName, [...modules])\n }\n }\n\n return modules\n}\n\nfunction readEnabledModulesFromConfig(cfgPath: string): ModuleEntry[] {\n const source = fs.readFileSync(cfgPath, 'utf8')\n return parseModulesFromSource(source)\n}\n\nfunction loadEnabledModulesFromConfig(appDir: string): ModuleEntry[] {\n const cfgPath = path.resolve(appDir, 'src/modules.ts')\n if (fs.existsSync(cfgPath)) {\n try {\n const loadedModules = readEnabledModulesFromConfig(cfgPath)\n if (loadedModules.length > 0) return loadedModules\n } catch (error) {\n console.warn(\n '[resolver] Failed to read enabled modules from src/modules.ts, falling back to src/modules scan:',\n error,\n )\n }\n }\n // Fallback: scan src/modules/* to keep backward compatibility\n const modulesRoot = path.resolve(appDir, 'src/modules')\n if (!fs.existsSync(modulesRoot)) return []\n const scannedModules = fs\n .readdirSync(modulesRoot, { withFileTypes: true })\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((e) => ({ id: e.name, from: '@app' as const }))\n\n return scannedModules\n}\n\nfunction discoverPackagesInMonorepo(rootDir: string): PackageInfo[] {\n const packagesDir = path.join(rootDir, 'packages')\n if (!fs.existsSync(packagesDir)) return []\n\n const packages: PackageInfo[] = []\n const entries = fs.readdirSync(packagesDir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const pkgPath = path.join(packagesDir, entry.name)\n const pkgJsonPath = path.join(pkgPath, 'package.json')\n\n if (!fs.existsSync(pkgJsonPath)) continue\n\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))\n // Read from src/modules (TypeScript source)\n const modulesPath = path.join(pkgPath, 'src', 'modules')\n\n if (fs.existsSync(modulesPath)) {\n packages.push({\n name: pkgJson.name || `@open-mercato/${entry.name}`,\n path: pkgPath,\n modulesPath,\n })\n }\n } catch {\n // Skip invalid packages\n }\n }\n\n return packages\n}\n\nfunction discoverPackagesInNodeModules(rootDir: string): PackageInfo[] {\n const nodeModulesPath = path.join(rootDir, 'node_modules', '@open-mercato')\n if (!fs.existsSync(nodeModulesPath)) return []\n\n const packages: PackageInfo[] = []\n const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const pkgPath = path.join(nodeModulesPath, entry.name)\n const pkgJsonPath = path.join(pkgPath, 'package.json')\n\n if (!fs.existsSync(pkgJsonPath)) continue\n\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))\n const distModulesPath = path.join(pkgPath, 'dist', 'modules')\n const srcModulesPath = path.join(pkgPath, 'src', 'modules')\n const modulesPath = fs.existsSync(distModulesPath) ? distModulesPath : srcModulesPath\n\n if (fs.existsSync(modulesPath)) {\n packages.push({\n name: pkgJson.name || `@open-mercato/${entry.name}`,\n path: pkgPath,\n modulesPath,\n })\n }\n } catch {\n // Skip invalid packages\n }\n }\n\n return packages\n}\n\nfunction detectAppDir(rootDir: string, isMonorepo: boolean): string {\n if (!isMonorepo) {\n // Production mode: app is at root\n return rootDir\n }\n\n // Monorepo mode: look for app in apps/mercato/ or apps/app/\n const mercatoApp = path.join(rootDir, 'apps', 'mercato')\n if (fs.existsSync(mercatoApp)) {\n return mercatoApp\n }\n\n const defaultApp = path.join(rootDir, 'apps', 'app')\n if (fs.existsSync(defaultApp)) {\n return defaultApp\n }\n\n // Fallback: check if apps directory exists and has any app\n const appsDir = path.join(rootDir, 'apps')\n if (fs.existsSync(appsDir)) {\n const entries = fs.readdirSync(appsDir, { withFileTypes: true })\n const appEntry = entries.find(\n (e) => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'docs'\n )\n if (appEntry) {\n return path.join(appsDir, appEntry.name)\n }\n }\n\n // Final fallback for legacy structure: root is the app\n return rootDir\n}\n\nfunction findNodeModulesRoot(startDir: string): string | null {\n // Walk up to find node_modules/@open-mercato/core\n let dir = startDir\n while (dir !== path.dirname(dir)) {\n const corePkgPath = path.join(dir, 'node_modules', '@open-mercato', 'core')\n if (fs.existsSync(corePkgPath)) {\n return dir\n }\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction detectMonorepoFromNodeModules(appDir: string): { isMonorepo: boolean; monorepoRoot: string | null; nodeModulesRoot: string | null } {\n // Find where node_modules/@open-mercato/core is located (may be hoisted)\n const nodeModulesRoot = findNodeModulesRoot(appDir)\n if (!nodeModulesRoot) {\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot: null }\n }\n\n const corePkgPath = path.join(nodeModulesRoot, 'node_modules', '@open-mercato', 'core')\n\n try {\n const stat = fs.lstatSync(corePkgPath)\n if (stat.isSymbolicLink()) {\n // It's a symlink - we're in monorepo dev mode\n // Resolve the symlink to find the monorepo root\n const realPath = fs.realpathSync(corePkgPath)\n // realPath is something like /path/to/monorepo/packages/core\n // monorepo root is 2 levels up\n const monorepoRoot = path.dirname(path.dirname(realPath))\n return { isMonorepo: true, monorepoRoot, nodeModulesRoot }\n }\n // It's a real directory - production mode\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot }\n } catch {\n // Package doesn't exist yet or error reading - assume production mode\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot }\n }\n}\n\nexport function createResolver(cwd: string = process.cwd()): PackageResolver {\n // First detect if we're in a monorepo by checking if node_modules packages are symlinks\n const { isMonorepo: _isMonorepo, monorepoRoot, nodeModulesRoot } = detectMonorepoFromNodeModules(cwd)\n // In workspaces with hoisted real directories, package sources live under the discovered node_modules root.\n const rootDir = monorepoRoot ?? nodeModulesRoot ?? cwd\n\n // shouldResolveAppFromRoot: true when we have a workspace/install root that differs from cwd\n // (monorepo symlinks detected, or node_modules root found above cwd)\n const shouldResolveAppFromRoot =\n _isMonorepo || (nodeModulesRoot !== null && path.resolve(nodeModulesRoot) !== path.resolve(cwd))\n\n // The app directory depends on context:\n // - In monorepo: use detectAppDir to find apps/mercato or similar\n // - When symlinks not detected (e.g. Docker volume node_modules): still use apps/mercato if present at rootDir\n // - Otherwise: app is at cwd\n const candidateAppDir = shouldResolveAppFromRoot ? detectAppDir(rootDir, true) : rootDir\n const appDir =\n _isMonorepo\n ? candidateAppDir\n : shouldResolveAppFromRoot && candidateAppDir !== rootDir && fs.existsSync(candidateAppDir)\n ? candidateAppDir\n : cwd\n\n return {\n isMonorepo: () => _isMonorepo,\n\n getRootDir: () => rootDir,\n\n getAppDir: () => appDir,\n\n getOutputDir: () => {\n // Output is ALWAYS .mercato/generated relative to app directory\n return path.join(appDir, '.mercato', 'generated')\n },\n\n getModulesConfigPath: () => path.join(appDir, 'src', 'modules.ts'),\n\n discoverPackages: () => {\n return _isMonorepo\n ? discoverPackagesInMonorepo(rootDir)\n : discoverPackagesInNodeModules(rootDir)\n },\n\n loadEnabledModules: () => loadEnabledModulesFromConfig(appDir),\n\n getModulePaths: (entry: ModuleEntry) => {\n const appBase = path.resolve(appDir, 'src/modules', entry.id)\n const pkgModulesRoot = pkgDirFor(rootDir, entry.from, _isMonorepo)\n const pkgBase = path.join(pkgModulesRoot, entry.id)\n return { appBase, pkgBase }\n },\n\n getModuleImportBase: (entry: ModuleEntry) => {\n // Prefer @app overrides at import-time; fall back to provided package alias\n const from = entry.from || '@open-mercato/core'\n return {\n appBase: `@/modules/${entry.id}`,\n pkgBase: `${from}/modules/${entry.id}`,\n }\n },\n\n getPackageOutputDir: (packageName: string) => {\n if (packageName === '@app') {\n // App output goes to .mercato/generated\n return path.join(appDir, '.mercato', 'generated')\n }\n const pkgRoot = pkgRootFor(rootDir, packageName, _isMonorepo)\n return path.join(pkgRoot, 'generated')\n },\n\n getPackageRoot: (from?: string) => {\n return pkgRootFor(rootDir, from, _isMonorepo)\n },\n }\n}\n\n/**\n * Resolve the CLI execution environment as a plain value-object.\n * Call once at subsystem init; use the returned object for all path decisions.\n */\nexport function resolveEnvironment(cwd: string = process.cwd()): CliEnvironment {\n const resolver = createResolver(cwd)\n const _isMonorepo = resolver.isMonorepo()\n const rootDir = resolver.getRootDir()\n const appDir = resolver.getAppDir()\n return {\n mode: _isMonorepo ? 'monorepo' : 'standalone',\n rootDir,\n appDir,\n packageRoot: (packageName: string) => pkgRootFor(rootDir, packageName, _isMonorepo),\n }\n}\n"],
4
+ "sourcesContent": ["import path from 'node:path'\nimport fs from 'node:fs'\nimport ts from 'typescript-js'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\n/**\n * Resolved execution environment for the CLI.\n * Produced once by resolveEnvironment() and consumed by all subsystems.\n * Eliminates per-subsystem isMonorepo() branching and hardcoded path segments.\n */\nexport type CliEnvironment = {\n /** Whether the CLI is running inside a Yarn/npm workspace monorepo. */\n mode: 'monorepo' | 'standalone'\n /** Workspace or project root (where package.json / node_modules live). */\n rootDir: string\n /** Next.js application directory (contains src/, package.json, next.config.*). */\n appDir: string\n /**\n * Resolve the root directory of an installed @open-mercato package.\n * Monorepo: packages/<pkg>/ Standalone: node_modules/<pkg>/\n */\n packageRoot: (packageName: string) => string\n}\n\nexport type ModuleEntry = {\n id: string\n from?: '@open-mercato/core' | '@app' | string\n}\n\nexport type PackageInfo = {\n name: string\n path: string\n modulesPath: string\n}\n\nexport interface PackageResolver {\n isMonorepo(): boolean\n getRootDir(): string\n getAppDir(): string\n getOutputDir(): string\n getModulesConfigPath(): string\n discoverPackages(): PackageInfo[]\n loadEnabledModules(): ModuleEntry[]\n getModulePaths(entry: ModuleEntry): { appBase: string; pkgBase: string }\n getModuleImportBase(entry: ModuleEntry): { appBase: string; pkgBase: string }\n getPackageOutputDir(packageName: string): string\n getPackageRoot(from?: string): string\n}\n\nfunction pkgDirFor(rootDir: string, from?: string, isMonorepo = true): string {\n if (!isMonorepo) {\n const pkgName = from || '@open-mercato/core'\n const pkgRoot = path.join(rootDir, 'node_modules', pkgName)\n const distModules = path.join(pkgRoot, 'dist', 'modules')\n if (fs.existsSync(distModules)) return distModules\n return path.join(pkgRoot, 'src', 'modules')\n }\n\n // Monorepo mode - read from src/modules (TypeScript source)\n if (!from || from === '@open-mercato/core') {\n return path.resolve(rootDir, 'packages/core/src/modules')\n }\n // Support other local packages like '@open-mercato/onboarding' => packages/onboarding/src/modules\n const m = from.match(/^@open-mercato\\/(.+)$/)\n if (m) {\n return path.resolve(rootDir, `packages/${m[1]}/src/modules`)\n }\n // Fallback to core modules path\n return path.resolve(rootDir, 'packages/core/src/modules')\n}\n\nfunction pkgRootFor(rootDir: string, from?: string, isMonorepo = true): string {\n if (!isMonorepo) {\n const pkgName = from || '@open-mercato/core'\n return path.join(rootDir, 'node_modules', pkgName)\n }\n\n if (!from || from === '@open-mercato/core') {\n return path.resolve(rootDir, 'packages/core')\n }\n const m = from.match(/^@open-mercato\\/(.+)$/)\n if (m) {\n return path.resolve(rootDir, `packages/${m[1]}`)\n }\n return path.resolve(rootDir, 'packages/core')\n}\n\nfunction parseModuleEntryFromObjectLiteral(node: ts.ObjectLiteralExpression): ModuleEntry | null {\n let id: string | null = null\n let from: string | null = null\n for (const property of node.properties) {\n if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) continue\n const key = property.name.text\n if (key === 'id' && ts.isStringLiteralLike(property.initializer)) {\n id = property.initializer.text\n }\n if (key === 'from' && ts.isStringLiteralLike(property.initializer)) {\n from = property.initializer.text\n }\n }\n if (!id) return null\n return { id, from: from ?? '@open-mercato/core' }\n}\n\nfunction parseProcessEnvAccess(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n): { matched: boolean; value: string | undefined } {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {\n const target = node.expression\n if (\n ts.isPropertyAccessExpression(target)\n && ts.isIdentifier(target.expression)\n && target.expression.text === 'process'\n && target.name.text === 'env'\n ) {\n return { matched: true, value: env[node.name.text] }\n }\n }\n if (\n ts.isElementAccessExpression(node)\n && ts.isPropertyAccessExpression(node.expression)\n && ts.isIdentifier(node.expression.expression)\n && node.expression.expression.text === 'process'\n && node.expression.name.text === 'env'\n && ts.isStringLiteralLike(node.argumentExpression)\n ) {\n return { matched: true, value: env[node.argumentExpression.text] }\n }\n return { matched: false, value: undefined }\n}\n\nfunction evaluateStaticExpression(node: ts.Expression, env: NodeJS.ProcessEnv): unknown {\n return evaluateStaticExpressionWithScope(node, env, new Map())\n}\n\nfunction evaluateStaticExpressionWithScope(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): unknown {\n if (ts.isParenthesizedExpression(node)) return evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (ts.isIdentifier(node)) {\n return scope.get(node.text)\n }\n if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text\n if (ts.isNumericLiteral(node)) return Number(node.text)\n if (node.kind === ts.SyntaxKind.TrueKeyword) return true\n if (node.kind === ts.SyntaxKind.FalseKeyword) return false\n if (node.kind === ts.SyntaxKind.NullKeyword) return null\n\n const envAccess = parseProcessEnvAccess(node, env)\n if (envAccess.matched) return envAccess.value\n\n if (ts.isPropertyAccessExpression(node)) {\n const target = evaluateStaticExpressionWithScope(node.expression, env, scope)\n if (target && typeof target === 'object' && node.name.text in target) {\n return (target as Record<string, unknown>)[node.name.text]\n }\n return undefined\n }\n\n if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {\n return !Boolean(evaluateStaticExpressionWithScope(node.operand, env, scope))\n }\n\n if (ts.isBinaryExpression(node)) {\n if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n && Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {\n return Boolean(evaluateStaticExpressionWithScope(node.left, env, scope))\n || Boolean(evaluateStaticExpressionWithScope(node.right, env, scope))\n }\n if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope) === evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n if (node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {\n return evaluateStaticExpressionWithScope(node.left, env, scope) !== evaluateStaticExpressionWithScope(node.right, env, scope)\n }\n }\n\n if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'parseBooleanWithDefault') {\n const rawValueNode = node.arguments[0]\n const fallbackNode = node.arguments[1]\n const rawValue = rawValueNode ? evaluateStaticExpressionWithScope(rawValueNode, env, scope) : undefined\n const fallbackValue = fallbackNode ? evaluateStaticExpressionWithScope(fallbackNode, env, scope) : false\n return parseBooleanWithDefault(typeof rawValue === 'string' ? rawValue : undefined, Boolean(fallbackValue))\n }\n\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'some') {\n const target = evaluateStaticExpressionWithScope(node.expression.expression, env, scope)\n const predicate = node.arguments[0]\n if (!Array.isArray(target) || !predicate) return undefined\n\n return target.some((entry) => {\n if (!ts.isArrowFunction(predicate) && !ts.isFunctionExpression(predicate)) return false\n\n const localScope = new Map(scope)\n const parameter = predicate.parameters[0]\n if (parameter && ts.isIdentifier(parameter.name)) {\n localScope.set(parameter.name.text, entry)\n }\n\n if (ts.isBlock(predicate.body)) {\n const returnStatement = predicate.body.statements.find((statement): statement is ts.ReturnStatement =>\n ts.isReturnStatement(statement),\n )\n if (!returnStatement?.expression) return false\n return Boolean(evaluateStaticExpressionWithScope(returnStatement.expression, env, localScope))\n }\n\n return Boolean(evaluateStaticExpressionWithScope(predicate.body, env, localScope))\n })\n }\n\n return undefined\n}\n\nfunction evaluateStaticCondition(\n node: ts.Expression,\n env: NodeJS.ProcessEnv,\n scope: Map<string, unknown>,\n): boolean {\n const evaluated = evaluateStaticExpressionWithScope(node, env, scope)\n return Boolean(evaluated)\n}\n\nfunction collectPushEntriesFromStatement(\n statement: ts.Statement,\n env: NodeJS.ProcessEnv,\n targetVariableName: string,\n scope: Map<string, unknown>,\n): ModuleEntry[] {\n if (ts.isBlock(statement)) {\n return statement.statements.flatMap((child) => collectPushEntriesFromStatement(child, env, targetVariableName, scope))\n }\n\n if (ts.isIfStatement(statement)) {\n if (evaluateStaticCondition(statement.expression, env, scope)) {\n return collectPushEntriesFromStatement(statement.thenStatement, env, targetVariableName, scope)\n }\n if (statement.elseStatement) {\n return collectPushEntriesFromStatement(statement.elseStatement, env, targetVariableName, scope)\n }\n return []\n }\n\n if (!ts.isExpressionStatement(statement)) return []\n const expression = statement.expression\n if (!ts.isCallExpression(expression)) return []\n if (!ts.isPropertyAccessExpression(expression.expression)) return []\n const pushTarget = expression.expression.expression\n const pushMethod = expression.expression.name\n if (!ts.isIdentifier(pushTarget) || pushTarget.text !== targetVariableName) return []\n if (pushMethod.text !== 'push') return []\n\n return expression.arguments.flatMap((argument) => {\n if (!ts.isObjectLiteralExpression(argument)) return []\n const entry = parseModuleEntryFromObjectLiteral(argument)\n return entry ? [entry] : []\n })\n}\n\nfunction parseModulesFromSource(source: string, env: NodeJS.ProcessEnv = process.env): ModuleEntry[] {\n const sourceFile = ts.createSourceFile('modules.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)\n const modules: ModuleEntry[] = []\n const variableName = 'enabledModules'\n const scope = new Map<string, unknown>()\n let foundDeclaration = false\n\n for (const statement of sourceFile.statements) {\n if (ts.isVariableStatement(statement)) {\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name)) continue\n if (declaration.name.text === variableName) {\n if (!declaration.initializer || !ts.isArrayLiteralExpression(declaration.initializer)) continue\n const fromArray = declaration.initializer.elements.flatMap((element) => {\n if (!ts.isObjectLiteralExpression(element)) return []\n const entry = parseModuleEntryFromObjectLiteral(element)\n return entry ? [entry] : []\n })\n modules.push(...fromArray)\n scope.set(variableName, [...modules])\n foundDeclaration = true\n continue\n }\n if (!declaration.initializer) continue\n scope.set(\n declaration.name.text,\n evaluateStaticExpressionWithScope(declaration.initializer, env, scope),\n )\n }\n continue\n }\n if (!foundDeclaration) continue\n const pushedEntries = collectPushEntriesFromStatement(statement, env, variableName, scope)\n if (pushedEntries.length > 0) {\n modules.push(...pushedEntries)\n scope.set(variableName, [...modules])\n }\n }\n\n return modules\n}\n\nfunction readEnabledModulesFromConfig(cfgPath: string): ModuleEntry[] {\n const source = fs.readFileSync(cfgPath, 'utf8')\n return parseModulesFromSource(source)\n}\n\nfunction loadEnabledModulesFromConfig(appDir: string): ModuleEntry[] {\n const cfgPath = path.resolve(appDir, 'src/modules.ts')\n if (fs.existsSync(cfgPath)) {\n try {\n const loadedModules = readEnabledModulesFromConfig(cfgPath)\n if (loadedModules.length > 0) return loadedModules\n } catch (error) {\n console.warn(\n '[resolver] Failed to read enabled modules from src/modules.ts, falling back to src/modules scan:',\n error,\n )\n }\n }\n // Fallback: scan src/modules/* to keep backward compatibility\n const modulesRoot = path.resolve(appDir, 'src/modules')\n if (!fs.existsSync(modulesRoot)) return []\n const scannedModules = fs\n .readdirSync(modulesRoot, { withFileTypes: true })\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((e) => ({ id: e.name, from: '@app' as const }))\n\n return scannedModules\n}\n\nfunction discoverPackagesInMonorepo(rootDir: string): PackageInfo[] {\n const packagesDir = path.join(rootDir, 'packages')\n if (!fs.existsSync(packagesDir)) return []\n\n const packages: PackageInfo[] = []\n const entries = fs.readdirSync(packagesDir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const pkgPath = path.join(packagesDir, entry.name)\n const pkgJsonPath = path.join(pkgPath, 'package.json')\n\n if (!fs.existsSync(pkgJsonPath)) continue\n\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))\n // Read from src/modules (TypeScript source)\n const modulesPath = path.join(pkgPath, 'src', 'modules')\n\n if (fs.existsSync(modulesPath)) {\n packages.push({\n name: pkgJson.name || `@open-mercato/${entry.name}`,\n path: pkgPath,\n modulesPath,\n })\n }\n } catch {\n // Skip invalid packages\n }\n }\n\n return packages\n}\n\nfunction discoverPackagesInNodeModules(rootDir: string): PackageInfo[] {\n const nodeModulesPath = path.join(rootDir, 'node_modules', '@open-mercato')\n if (!fs.existsSync(nodeModulesPath)) return []\n\n const packages: PackageInfo[] = []\n const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true })\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const pkgPath = path.join(nodeModulesPath, entry.name)\n const pkgJsonPath = path.join(pkgPath, 'package.json')\n\n if (!fs.existsSync(pkgJsonPath)) continue\n\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))\n const distModulesPath = path.join(pkgPath, 'dist', 'modules')\n const srcModulesPath = path.join(pkgPath, 'src', 'modules')\n const modulesPath = fs.existsSync(distModulesPath) ? distModulesPath : srcModulesPath\n\n if (fs.existsSync(modulesPath)) {\n packages.push({\n name: pkgJson.name || `@open-mercato/${entry.name}`,\n path: pkgPath,\n modulesPath,\n })\n }\n } catch {\n // Skip invalid packages\n }\n }\n\n return packages\n}\n\nfunction detectAppDir(rootDir: string, isMonorepo: boolean): string {\n if (!isMonorepo) {\n // Production mode: app is at root\n return rootDir\n }\n\n // Monorepo mode: look for app in apps/mercato/ or apps/app/\n const mercatoApp = path.join(rootDir, 'apps', 'mercato')\n if (fs.existsSync(mercatoApp)) {\n return mercatoApp\n }\n\n const defaultApp = path.join(rootDir, 'apps', 'app')\n if (fs.existsSync(defaultApp)) {\n return defaultApp\n }\n\n // Fallback: check if apps directory exists and has any app\n const appsDir = path.join(rootDir, 'apps')\n if (fs.existsSync(appsDir)) {\n const entries = fs.readdirSync(appsDir, { withFileTypes: true })\n const appEntry = entries.find(\n (e) => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'docs'\n )\n if (appEntry) {\n return path.join(appsDir, appEntry.name)\n }\n }\n\n // Final fallback for legacy structure: root is the app\n return rootDir\n}\n\nfunction findNodeModulesRoot(startDir: string): string | null {\n // Walk up to find node_modules/@open-mercato/core\n let dir = startDir\n while (dir !== path.dirname(dir)) {\n const corePkgPath = path.join(dir, 'node_modules', '@open-mercato', 'core')\n if (fs.existsSync(corePkgPath)) {\n return dir\n }\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction detectMonorepoFromNodeModules(appDir: string): { isMonorepo: boolean; monorepoRoot: string | null; nodeModulesRoot: string | null } {\n // Find where node_modules/@open-mercato/core is located (may be hoisted)\n const nodeModulesRoot = findNodeModulesRoot(appDir)\n if (!nodeModulesRoot) {\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot: null }\n }\n\n const corePkgPath = path.join(nodeModulesRoot, 'node_modules', '@open-mercato', 'core')\n\n try {\n const stat = fs.lstatSync(corePkgPath)\n if (stat.isSymbolicLink()) {\n // It's a symlink - we're in monorepo dev mode\n // Resolve the symlink to find the monorepo root\n const realPath = fs.realpathSync(corePkgPath)\n // realPath is something like /path/to/monorepo/packages/core\n // monorepo root is 2 levels up\n const monorepoRoot = path.dirname(path.dirname(realPath))\n return { isMonorepo: true, monorepoRoot, nodeModulesRoot }\n }\n // It's a real directory - production mode\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot }\n } catch {\n // Package doesn't exist yet or error reading - assume production mode\n return { isMonorepo: false, monorepoRoot: null, nodeModulesRoot }\n }\n}\n\nexport function createResolver(cwd: string = process.cwd()): PackageResolver {\n // First detect if we're in a monorepo by checking if node_modules packages are symlinks\n const { isMonorepo: _isMonorepo, monorepoRoot, nodeModulesRoot } = detectMonorepoFromNodeModules(cwd)\n // In workspaces with hoisted real directories, package sources live under the discovered node_modules root.\n const rootDir = monorepoRoot ?? nodeModulesRoot ?? cwd\n\n // shouldResolveAppFromRoot: true when we have a workspace/install root that differs from cwd\n // (monorepo symlinks detected, or node_modules root found above cwd)\n const shouldResolveAppFromRoot =\n _isMonorepo || (nodeModulesRoot !== null && path.resolve(nodeModulesRoot) !== path.resolve(cwd))\n\n // The app directory depends on context:\n // - In monorepo: use detectAppDir to find apps/mercato or similar\n // - When symlinks not detected (e.g. Docker volume node_modules): still use apps/mercato if present at rootDir\n // - Otherwise: app is at cwd\n const candidateAppDir = shouldResolveAppFromRoot ? detectAppDir(rootDir, true) : rootDir\n const appDir =\n _isMonorepo\n ? candidateAppDir\n : shouldResolveAppFromRoot && candidateAppDir !== rootDir && fs.existsSync(candidateAppDir)\n ? candidateAppDir\n : cwd\n\n return {\n isMonorepo: () => _isMonorepo,\n\n getRootDir: () => rootDir,\n\n getAppDir: () => appDir,\n\n getOutputDir: () => {\n // Output is ALWAYS .mercato/generated relative to app directory\n return path.join(appDir, '.mercato', 'generated')\n },\n\n getModulesConfigPath: () => path.join(appDir, 'src', 'modules.ts'),\n\n discoverPackages: () => {\n return _isMonorepo\n ? discoverPackagesInMonorepo(rootDir)\n : discoverPackagesInNodeModules(rootDir)\n },\n\n loadEnabledModules: () => loadEnabledModulesFromConfig(appDir),\n\n getModulePaths: (entry: ModuleEntry) => {\n const appBase = path.resolve(appDir, 'src/modules', entry.id)\n const pkgModulesRoot = pkgDirFor(rootDir, entry.from, _isMonorepo)\n const pkgBase = path.join(pkgModulesRoot, entry.id)\n return { appBase, pkgBase }\n },\n\n getModuleImportBase: (entry: ModuleEntry) => {\n // Prefer @app overrides at import-time; fall back to provided package alias\n const from = entry.from || '@open-mercato/core'\n return {\n appBase: `@/modules/${entry.id}`,\n pkgBase: `${from}/modules/${entry.id}`,\n }\n },\n\n getPackageOutputDir: (packageName: string) => {\n if (packageName === '@app') {\n // App output goes to .mercato/generated\n return path.join(appDir, '.mercato', 'generated')\n }\n const pkgRoot = pkgRootFor(rootDir, packageName, _isMonorepo)\n return path.join(pkgRoot, 'generated')\n },\n\n getPackageRoot: (from?: string) => {\n return pkgRootFor(rootDir, from, _isMonorepo)\n },\n }\n}\n\n/**\n * Resolve the CLI execution environment as a plain value-object.\n * Call once at subsystem init; use the returned object for all path decisions.\n */\nexport function resolveEnvironment(cwd: string = process.cwd()): CliEnvironment {\n const resolver = createResolver(cwd)\n const _isMonorepo = resolver.isMonorepo()\n const rootDir = resolver.getRootDir()\n const appDir = resolver.getAppDir()\n return {\n mode: _isMonorepo ? 'monorepo' : 'standalone',\n rootDir,\n appDir,\n packageRoot: (packageName: string) => pkgRootFor(rootDir, packageName, _isMonorepo),\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/mercato.js CHANGED
@@ -565,29 +565,75 @@ async function runGeneratorSuite(quiet) {
565
565
  generateOpenApi
566
566
  } = await import("./lib/generators/index.js");
567
567
  const resolver = createResolver();
568
- await generateEntityIds({ resolver, quiet });
569
- await generateModuleRegistry({ resolver, quiet });
570
- await generateModuleRegistryApp({ resolver, quiet });
571
- await generateModuleRegistryCli({ resolver, quiet });
572
- await generateModuleEntities({ resolver, quiet });
573
- await generateModuleDi({ resolver, quiet });
574
- await generateModulePackageSources({ resolver, quiet });
575
- await generateOpenApi({ resolver, quiet });
568
+ const results = [
569
+ await generateEntityIds({ resolver, quiet }),
570
+ await generateModuleRegistry({ resolver, quiet }),
571
+ await generateModuleRegistryApp({ resolver, quiet }),
572
+ await generateModuleRegistryCli({ resolver, quiet }),
573
+ await generateModuleEntities({ resolver, quiet }),
574
+ await generateModuleDi({ resolver, quiet }),
575
+ await generateModulePackageSources({ resolver, quiet }),
576
+ await generateOpenApi({ resolver, quiet })
577
+ ];
578
+ return results.some((result) => (result?.filesWritten.length ?? 0) > 0);
576
579
  }
577
- function createGenerateWatchChecksumFn() {
578
- return async () => {
579
- const { createResolver } = await import("./lib/resolver.js");
580
- const { calculateGenerateWatchStructureChecksum } = await import("./lib/generate-watch-structure.js");
580
+ async function runGeneratorSuiteWithStructuralInvalidation(quiet) {
581
+ const generatedFilesChanged = await runGeneratorSuite(quiet);
582
+ if (!generatedFilesChanged) {
583
+ if (!quiet) {
584
+ console.log("[generate] Generated outputs unchanged; skipping structural cache purge.");
585
+ }
586
+ return;
587
+ }
588
+ await runPostGenerateStructuralCachePurge(quiet);
589
+ }
590
+ async function createGenerateWatchRuntime() {
591
+ const [
592
+ { createResolver },
593
+ { calculateGenerateWatchStructureChecksum },
594
+ { createGenerateWatchChangeSignal },
595
+ { resolveStandaloneSourceMirrorBase }
596
+ ] = await Promise.all([
597
+ import("./lib/resolver.js"),
598
+ import("./lib/generate-watch-structure.js"),
599
+ import("./lib/generate-watch-events.js"),
600
+ import("./lib/generators/scanner.js")
601
+ ]);
602
+ const collectWatchState = () => {
581
603
  const resolver = createResolver();
582
604
  const moduleRoots = [];
583
605
  for (const entry of resolver.loadEnabledModules()) {
584
606
  const roots = resolver.getModulePaths(entry);
585
607
  moduleRoots.push({ appBase: roots.appBase, pkgBase: roots.pkgBase });
586
608
  }
587
- return calculateGenerateWatchStructureChecksum({
588
- modulesFile: path.join(resolver.getAppDir(), "src", "modules.ts"),
609
+ return {
610
+ modulesFile: resolver.getModulesConfigPath(),
589
611
  moduleRoots
590
- });
612
+ };
613
+ };
614
+ return {
615
+ computeStructureChecksum: async () => {
616
+ return calculateGenerateWatchStructureChecksum(collectWatchState());
617
+ },
618
+ changeSignal: createGenerateWatchChangeSignal({
619
+ getWatchTargets: () => {
620
+ const state = collectWatchState();
621
+ const targets = [{
622
+ directory: path.dirname(state.modulesFile),
623
+ recursive: false,
624
+ fileName: path.basename(state.modulesFile)
625
+ }];
626
+ for (const roots of state.moduleRoots) {
627
+ targets.push({ directory: path.dirname(roots.appBase), recursive: true });
628
+ targets.push({ directory: path.dirname(roots.pkgBase), recursive: true });
629
+ const sourceMirror = resolveStandaloneSourceMirrorBase(roots.pkgBase);
630
+ if (sourceMirror) {
631
+ targets.push({ directory: path.dirname(sourceMirror), recursive: true });
632
+ }
633
+ }
634
+ return targets;
635
+ }
636
+ })
591
637
  };
592
638
  }
593
639
  async function buildAllModules() {
@@ -1425,8 +1471,7 @@ async function run(argv = process.argv) {
1425
1471
  run: async (args) => {
1426
1472
  const quiet = args.includes("--quiet") || args.includes("-q");
1427
1473
  console.log("Running all generators...");
1428
- await runGeneratorSuite(quiet);
1429
- await runPostGenerateStructuralCachePurge(quiet);
1474
+ await runGeneratorSuiteWithStructuralInvalidation(quiet);
1430
1475
  console.log("All generators completed.");
1431
1476
  }
1432
1477
  },
@@ -1438,14 +1483,14 @@ async function run(argv = process.argv) {
1438
1483
  const intervalArg = args.find((arg) => arg.startsWith("--interval="));
1439
1484
  const parsedInterval = intervalArg ? Number.parseInt(intervalArg.split("=")[1] ?? "", 10) : NaN;
1440
1485
  const intervalMs = Number.isFinite(parsedInterval) && parsedInterval >= 250 ? parsedInterval : 1e3;
1486
+ const generateWatchRuntime = await createGenerateWatchRuntime();
1441
1487
  const watcher = startInProcessGenerateWatcher({
1442
1488
  pollMs: intervalMs,
1443
1489
  skipInitial,
1444
1490
  quiet,
1445
- computeStructureChecksum: createGenerateWatchChecksumFn(),
1491
+ ...generateWatchRuntime,
1446
1492
  runGenerators: async () => {
1447
- await runGeneratorSuite(true);
1448
- await runPostGenerateStructuralCachePurge(true);
1493
+ await runGeneratorSuiteWithStructuralInvalidation(true);
1449
1494
  }
1450
1495
  });
1451
1496
  const shutdownSignals = ["SIGINT", "SIGTERM"];
@@ -1813,6 +1858,7 @@ async function run(argv = process.argv) {
1813
1858
  });
1814
1859
  if (generateWatcherMode === "in-process") {
1815
1860
  console.log("[server] In-process generate watcher enabled \u2014 structural changes will regenerate without a sidecar process.");
1861
+ const generateWatchRuntime = await createGenerateWatchRuntime();
1816
1862
  activeGenerateWatcher = startInProcessGenerateWatcher({
1817
1863
  // `--skip-initial` equivalent: `yarn dev` always runs an
1818
1864
  // initial `mercato generate` before reaching the server
@@ -1821,10 +1867,9 @@ async function run(argv = process.argv) {
1821
1867
  // twice in a row.
1822
1868
  skipInitial: true,
1823
1869
  quiet: false,
1824
- computeStructureChecksum: createGenerateWatchChecksumFn(),
1870
+ ...generateWatchRuntime,
1825
1871
  runGenerators: async () => {
1826
- await runGeneratorSuite(true);
1827
- await runPostGenerateStructuralCachePurge(true);
1872
+ await runGeneratorSuiteWithStructuralInvalidation(true);
1828
1873
  }
1829
1874
  });
1830
1875
  } else {