@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
@@ -0,0 +1,144 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { getEnabledModuleIdentities, getModuleLocators } from '../module-identity'
5
+ import { resolveModuleRequirement } from '@open-mercato/shared/modules/module-locators'
6
+ import type { PackageResolver } from '../resolver'
7
+
8
+ describe('module source identities', () => {
9
+ let root: string
10
+ let resolver: PackageResolver
11
+ beforeEach(() => {
12
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'module-identities-'))
13
+ fs.mkdirSync(path.join(root, 'package'), { recursive: true })
14
+ fs.mkdirSync(path.join(root, 'module'), { recursive: true })
15
+ resolver = {
16
+ getPackageRoot: () => path.join(root, 'package'),
17
+ getAppDir: () => root,
18
+ getModulePaths: () => ({ appBase: path.join(root, 'module'), pkgBase: path.join(root, 'package') }),
19
+ } as PackageResolver
20
+ })
21
+ afterEach(() => fs.rmSync(root, { recursive: true, force: true }))
22
+
23
+ function packageManifest(repository: unknown = 'https://github.com/pkarw/consents.git', name = '@pkarw/marketing_consents'): void {
24
+ fs.writeFileSync(path.join(root, 'package', 'package.json'), JSON.stringify({ name, repository }))
25
+ }
26
+
27
+ function dependency(specifier: string, binding = '@pkarw/marketing_consents'): void {
28
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ dependencies: { [binding]: specifier } }))
29
+ }
30
+
31
+ it.each(['1.2.3', 'beta-tag', '^1.0.0', '~1.2.0'])('keeps npm dependency %s distinct from its repository', (version) => {
32
+ packageManifest()
33
+ dependency(version)
34
+ const locators = getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents' }, resolver)
35
+ expect(locators).toEqual(['npm:@pkarw/marketing_consents'])
36
+ expect(resolveModuleRequirement('github:pkarw/consents', [{ id: 'marketing_consents', locators }])).toBeUndefined()
37
+ })
38
+
39
+ it.each(['github:pkarw/consents#commit', 'pkarw/consents#main', 'https://github.com/pkarw/consents.git#commit'])('observes GitHub source %s without treating the package name as npm origin', (specifier) => {
40
+ packageManifest('https://github.com/other/consents')
41
+ dependency(specifier)
42
+ const locators = getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents' }, resolver)
43
+ expect(locators).toEqual(['github:pkarw/consents'])
44
+ expect(resolveModuleRequirement('npm:@pkarw/marketing_consents', [{ id: 'marketing_consents', locators }])).toBeUndefined()
45
+ })
46
+
47
+ it.each(['github:stale/consents', 'npm:@pkarw/marketing_consents'])('rejects declaration %s that contradicts observed GitHub source', (locator) => {
48
+ packageManifest()
49
+ dependency('github:pkarw/consents#commit')
50
+ expect(() => getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents', locator }, resolver)).toThrow(/declared origin.*disagrees with observed origin/)
51
+ })
52
+
53
+ it('rejects a GitHub declaration over an observed npm dependency', () => {
54
+ packageManifest()
55
+ dependency('1.2.3')
56
+ expect(() => getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents', locator: 'github:pkarw/consents' }, resolver)).toThrow(/disagrees with observed origin/)
57
+ })
58
+
59
+ it.each(['file:../consents', 'workspace:*', 'link:../consents'])('keeps unobserved source unknown for %s', (specifier) => {
60
+ packageManifest()
61
+ dependency(specifier)
62
+ fs.writeFileSync(path.join(root, 'package', 'index.ts'), "export const metadata = { locator: 'github:pkarw/consents' }")
63
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents' }, resolver)).toEqual([])
64
+ })
65
+
66
+ it('does not invent install provenance from package repository or metadata alone', () => {
67
+ packageManifest()
68
+ fs.writeFileSync(path.join(root, 'package', 'index.ts'), "export const metadata = { locator: 'github:pkarw/consents' }")
69
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents' }, resolver)).toEqual([])
70
+ })
71
+
72
+ it('keeps retained registration provenance when dependency evidence is absent', () => {
73
+ packageManifest()
74
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents', locator: 'github:pkarw/consents' }, resolver)).toEqual(['github:pkarw/consents'])
75
+ })
76
+
77
+ it.each(['1.2.3', '^1.0.0'])('uses the canonical manifest identity through an npm alias binding at %s', (version) => {
78
+ packageManifest(undefined, '@other/consents')
79
+ dependency(`npm:@other/consents@${version}`, '@app/consents_alias')
80
+ const locators = getModuleLocators({ id: 'marketing_consents', from: '@app/consents_alias', packageName: '@other/consents' }, resolver)
81
+ expect(locators).toEqual(['npm:@other/consents'])
82
+ expect(resolveModuleRequirement('npm:@app/consents_alias', [{ id: 'marketing_consents', locators }])).toBeUndefined()
83
+ })
84
+
85
+ it('observes a GitHub source through a binding whose name differs from the manifest', () => {
86
+ packageManifest(undefined, '@other/consents')
87
+ dependency('github:pkarw/consents#commit', '@app/consents_alias')
88
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@app/consents_alias' }, resolver)).toEqual(['github:pkarw/consents'])
89
+ })
90
+
91
+ it('rejects an installed artifact that contradicts the npm alias target', () => {
92
+ packageManifest(undefined, '@wrong/consents')
93
+ dependency('npm:@other/consents@1.2.3', '@app/consents_alias')
94
+ expect(() => getModuleLocators({ id: 'marketing_consents', from: '@app/consents_alias' }, resolver)).toThrow(/dependency origin.*disagrees with installed package/)
95
+ })
96
+
97
+ it('rejects a mismatching canonical name without an alias declaration', () => {
98
+ packageManifest(undefined, '@wrong/consents')
99
+ dependency('1.2.3')
100
+ expect(() => getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents' }, resolver)).toThrow(/does not match the dependency binding/)
101
+ })
102
+
103
+ it('rejects declared canonical package identity that disagrees with the artifact', () => {
104
+ packageManifest()
105
+ dependency('1.2.3')
106
+ expect(() => getModuleLocators({ id: 'marketing_consents', from: '@pkarw/marketing_consents', packageName: '@other/consents' }, resolver)).toThrow(/declared package.*disagrees with installed package/)
107
+ })
108
+
109
+ it('reads ejected metadata without evaluating arbitrary module code', () => {
110
+ fs.writeFileSync(path.join(root, 'module', 'index.ts'), "throw new Error('must not execute'); export const metadata = { locator: 'github:pkarw/consents' } as const")
111
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@app' }, resolver)).toEqual(['github:pkarw/consents'])
112
+ })
113
+
114
+ it('retains GitHub origin after ejection without assigning npm origin from package attribution', () => {
115
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@app', packageName: '@pkarw/marketing_consents', locator: 'github:pkarw/consents' }, resolver)).toEqual(['github:pkarw/consents'])
116
+ })
117
+
118
+ it('keeps legacy local modules with no provenance unknown', () => {
119
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@app', packageName: '@pkarw/marketing_consents' }, resolver)).toEqual([])
120
+ })
121
+
122
+ it('reuses the app and binding manifests across active siblings', () => {
123
+ packageManifest()
124
+ dependency('1.2.3')
125
+ const reads = jest.spyOn(fs, 'readFileSync')
126
+ try {
127
+ expect(getEnabledModuleIdentities(resolver, [
128
+ { id: 'consents', from: '@pkarw/marketing_consents' },
129
+ { id: 'preferences', from: '@pkarw/marketing_consents' },
130
+ ])).toEqual([
131
+ { id: 'consents', locators: ['npm:@pkarw/marketing_consents'] },
132
+ { id: 'preferences', locators: ['npm:@pkarw/marketing_consents'] },
133
+ ])
134
+ expect(reads.mock.calls.filter(([file]) => file === path.join(root, 'package.json'))).toHaveLength(1)
135
+ expect(reads.mock.calls.filter(([file]) => file === path.join(root, 'package', 'package.json'))).toHaveLength(1)
136
+ } finally {
137
+ reads.mockRestore()
138
+ }
139
+ })
140
+
141
+ it('preserves npm registration source after ejection', () => {
142
+ expect(getModuleLocators({ id: 'marketing_consents', from: '@app', locator: 'npm:@pkarw/marketing_consents@next' }, resolver)).toEqual(['npm:@pkarw/marketing_consents'])
143
+ })
144
+ })
@@ -0,0 +1,195 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import childProcess from 'node:child_process'
5
+ import spawn from 'cross-spawn'
6
+ import { inspectNpmModuleTarget } from '../module-inspection'
7
+
8
+ describe('module artifact inspection', () => {
9
+ let root: string
10
+ beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'module-inspection-test-')) })
11
+ afterEach(() => { jest.restoreAllMocks(); fs.rmSync(root, { recursive: true, force: true }) })
12
+
13
+ it('inspects large packages whose artifact indexes exceed the subprocess default buffer', () => {
14
+ const files = Array.from({ length: 20_000 }, (_, index) => `package/assets/${'fixture'.repeat(10)}-${index}.txt`)
15
+ const archiveIndex = ['package/src/modules/example/index.ts', 'package/dist/modules/example/index.js', ...files].join('\n')
16
+ const packOutput = JSON.stringify([{ name: '@vendor/example', version: '1.2.3', filename: 'candidate.tgz', files: files.map((filePath) => ({ path: filePath, size: 1, mode: 420 })) }])
17
+ jest.spyOn(spawn, 'sync').mockImplementation((_command, _args, options) => {
18
+ if ((options?.maxBuffer ?? 1024 * 1024) < Buffer.byteLength(packOutput)) throw new Error('maxBuffer exceeded')
19
+ return { status: 0, stdout: packOutput, stderr: '', pid: 1, output: [], signal: null }
20
+ })
21
+ jest.spyOn(childProcess, 'execFileSync').mockImplementation((_command, args, options) => {
22
+ if (args?.[0] === '-xOf') return JSON.stringify({ name: '@vendor/example', version: '1.2.3' })
23
+ if ((options?.maxBuffer ?? 1024 * 1024) < Buffer.byteLength(archiveIndex)) throw new Error('maxBuffer exceeded')
24
+ return archiveIndex
25
+ })
26
+ expect(inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root).moduleIds).toEqual(['example'])
27
+ })
28
+
29
+ it('validates selected and active modules without requiring exports for parked modules', () => {
30
+ const packageRoot = path.join(root, 'candidate')
31
+ for (const moduleId of ['alpha', 'beta']) {
32
+ for (const directory of ['src', 'dist']) {
33
+ const moduleRoot = path.join(packageRoot, directory, 'modules', moduleId)
34
+ fs.mkdirSync(moduleRoot, { recursive: true })
35
+ fs.writeFileSync(path.join(moduleRoot, directory === 'src' ? 'index.ts' : 'index.js'), 'export const metadata = {}')
36
+ }
37
+ }
38
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({
39
+ name: '@vendor/example', exports: { './modules/alpha/index': null, './modules/beta/index': './dist/modules/beta/index.js' },
40
+ }))
41
+ expect(inspectNpmModuleTarget('@vendor/example@file:./candidate', '@vendor/example', root, ['beta']).moduleIds).toEqual(['alpha', 'beta'])
42
+ expect(() => inspectNpmModuleTarget('@vendor/example@file:./candidate', '@vendor/example', root, ['alpha', 'beta'])).toThrow('@vendor/example/modules/alpha/index')
43
+ })
44
+
45
+ it.each(['directory', 'archive'])('keeps private lowercase helpers private during %s inspection', (kind) => {
46
+ const packageRoot = path.join(root, 'package')
47
+ const sources = { 'index.ts': 'export const metadata = {}', 'backend/form-utils.tsx': "throw new Error('must not execute'); export const renderLabel = () => null", 'backend/orders/page.tsx': 'export default function Page() {}' }
48
+ const exports = { './modules/example/index': './dist/modules/example/index.js', './modules/example/backend/orders/page': './dist/modules/example/backend/orders/page.js' }
49
+ fs.mkdirSync(packageRoot, { recursive: true })
50
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@vendor/example', version: '1.0.0', exports }))
51
+ for (const [file, content] of Object.entries(sources)) {
52
+ for (const relativePath of [`src/modules/example/${file}`, `dist/modules/example/${file.replace(/\.tsx?$/, '.js')}`]) {
53
+ fs.mkdirSync(path.dirname(path.join(packageRoot, relativePath)), { recursive: true })
54
+ fs.writeFileSync(path.join(packageRoot, relativePath), content)
55
+ }
56
+ }
57
+ if (kind === 'archive') childProcess.execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
58
+ const specifier = '@vendor/example@file:./' + (kind === 'archive' ? 'candidate.tgz' : 'package')
59
+ expect(inspectNpmModuleTarget(specifier, '@vendor/example', root).moduleIds).toEqual(['example'])
60
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@vendor/example', exports: { ...exports, './modules/example/backend/orders/page': null } }))
61
+ if (kind === 'archive') childProcess.execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
62
+ expect(() => inspectNpmModuleTarget(specifier, '@vendor/example', root)).toThrow('backend/orders/page')
63
+ })
64
+
65
+ it.each(['directory', 'archive'])('rejects a %s artifact missing a generated runtime entrypoint', (kind) => {
66
+ const packageRoot = path.join(root, 'package')
67
+ for (const relativePath of ['src/modules/example/index.ts', 'src/modules/example/di.ts', 'dist/modules/example/index.js']) {
68
+ fs.mkdirSync(path.dirname(path.join(packageRoot, relativePath)), { recursive: true })
69
+ fs.writeFileSync(path.join(packageRoot, relativePath), 'export const value = true\n')
70
+ }
71
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({
72
+ name: '@vendor/example',
73
+ version: '1.0.0',
74
+ exports: { './modules/example/*': './dist/modules/example/*.js' },
75
+ }))
76
+ if (kind === 'archive') childProcess.execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
77
+ const specifier = '@vendor/example@file:./' + (kind === 'archive' ? 'candidate.tgz' : 'package')
78
+
79
+ expect(() => inspectNpmModuleTarget(specifier, '@vendor/example', root)).toThrow('missing the compiled runtime entrypoint')
80
+ })
81
+
82
+ it('inspects file packages without executing package scripts or writing app files', () => {
83
+ const packageRoot = path.join(root, 'candidate')
84
+ fs.mkdirSync(path.join(packageRoot, 'src/modules/example'), { recursive: true })
85
+ fs.mkdirSync(path.join(packageRoot, 'dist/modules/example'), { recursive: true })
86
+ fs.writeFileSync(path.join(packageRoot, 'dist/modules/example/index.js'), 'export const metadata = {}')
87
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@vendor/example', scripts: { prepare: 'exit 1' } }))
88
+ fs.writeFileSync(path.join(packageRoot, 'src/modules/example/index.ts'), 'export const metadata = {}')
89
+ const execute = jest.spyOn(childProcess, 'execFileSync')
90
+ const target = inspectNpmModuleTarget('@vendor/example@file:./candidate', '@vendor/example', root)
91
+ expect(target.moduleIds).toEqual(['example'])
92
+ expect(target.locator).toBeUndefined()
93
+ expect(execute).not.toHaveBeenCalled()
94
+ expect(fs.readdirSync(root)).toEqual(['candidate'])
95
+ })
96
+
97
+ it('preserves npm tags while recording the inspected version and disables lifecycle scripts', () => {
98
+ const execute = jest.spyOn(childProcess, 'execFileSync').mockImplementation((_command, args) => args?.[0] === '-xOf'
99
+ ? JSON.stringify({ name: '@vendor/example', version: '1.2.3' })
100
+ : 'package/src/modules/example/index.ts\npackage/dist/modules/example/index.js\n')
101
+ jest.spyOn(spawn, 'sync').mockImplementation((_command, args) => {
102
+ expect(args).toEqual(expect.arrayContaining(['pack', '@vendor/example@next', '--ignore-scripts', '--json', '--pack-destination']))
103
+ return { status: 0, stdout: JSON.stringify([{ name: '@vendor/example', version: '1.2.3', filename: 'candidate.tgz' }]), stderr: '', pid: 1, output: [], signal: null }
104
+ })
105
+ expect(inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root)).toEqual({ packageName: '@vendor/example', packageSpec: '@vendor/example@next', version: '1.2.3', moduleIds: ['example'], locator: 'npm:@vendor/example' })
106
+ expect(execute).toHaveBeenCalledTimes(2)
107
+ expect(fs.readdirSync(root)).toEqual([])
108
+ })
109
+
110
+ it('inspects a real file tarball without extracting it or executing entrypoints', () => {
111
+ const packageRoot = path.join(root, 'package')
112
+ for (const directory of ['src/modules/example', 'dist/modules/example']) fs.mkdirSync(path.join(packageRoot, directory), { recursive: true })
113
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@vendor/example', version: '2.3.4' }))
114
+ fs.writeFileSync(path.join(packageRoot, 'src/modules/example/index.ts'), "throw new Error('must not execute')")
115
+ fs.writeFileSync(path.join(packageRoot, 'dist/modules/example/index.js'), "throw new Error('must not execute')")
116
+ const archive = path.join(root, 'candidate.tgz')
117
+ childProcess.execFileSync('tar', ['-czf', archive, '-C', root, 'package'])
118
+ fs.rmSync(packageRoot, { recursive: true })
119
+ const target = inspectNpmModuleTarget('@vendor/example@file:./candidate.tgz', '@vendor/example', root)
120
+ expect(target).toMatchObject({ packageName: '@vendor/example', version: '2.3.4', moduleIds: ['example'] })
121
+ expect(target.locator).toBeUndefined()
122
+ expect(fs.readdirSync(root)).toEqual(['candidate.tgz'])
123
+ })
124
+
125
+ it('rejects file directories missing compiled descriptors before package installation', () => {
126
+ const packageRoot = path.join(root, 'candidate')
127
+ fs.mkdirSync(path.join(packageRoot, 'src/modules/example'), { recursive: true })
128
+ fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@vendor/example' }))
129
+ fs.writeFileSync(path.join(packageRoot, 'src/modules/example/index.ts'), 'export const metadata = {}')
130
+ expect(() => inspectNpmModuleTarget('@vendor/example@file:./candidate', '@vendor/example', root)).toThrow('Build the package')
131
+ })
132
+
133
+ it('rejects packed artifact version disagreement without exposing output', () => {
134
+ jest.spyOn(childProcess, 'execFileSync').mockImplementation((_command, args) => args?.[0] === '-xOf'
135
+ ? JSON.stringify({ name: '@vendor/example', version: '9.9.9' })
136
+ : 'package/src/modules/example/index.ts\npackage/dist/modules/example/index.js\n')
137
+ jest.spyOn(spawn, 'sync').mockReturnValue({ status: 0, stdout: JSON.stringify([{ name: '@vendor/example', version: '1.2.3', filename: 'candidate.tgz' }]), stderr: '', pid: 1, output: [], signal: null })
138
+ expect(() => inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root)).toThrow('No package was installed')
139
+ })
140
+
141
+ it('reports the safe export validation cause without exposing npm output', () => {
142
+ jest.spyOn(childProcess, 'execFileSync').mockImplementation((_command, args) => {
143
+ if (args?.[0] === '-xOf' && args?.[2] === 'package/package.json') {
144
+ return JSON.stringify({ name: '@vendor/example', version: '1.2.3', exports: { './modules/example/index': null } })
145
+ }
146
+ if (args?.[0] === '-tf') return 'package/src/modules/example/index.ts\npackage/dist/modules/example/index.js\n'
147
+ return 'export const metadata = {}'
148
+ })
149
+ jest.spyOn(spawn, 'sync').mockReturnValue({ status: 0, stdout: JSON.stringify([{ name: '@vendor/example', version: '1.2.3', filename: 'candidate.tgz' }]), stderr: 'private registry token', pid: 1, output: [], signal: null })
150
+
151
+ expect(() => inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root)).toThrow('while trying to validate the packed module artifact')
152
+ try { inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root) } catch (error) {
153
+ expect(String(error)).toContain('@vendor/example/modules/example/index')
154
+ expect(String(error)).not.toContain('private registry token')
155
+ }
156
+ })
157
+
158
+ it.each([0, 1])('mirrors scoped Yarn authentication and removes the temporary npmrc after status %s', (status) => {
159
+ fs.writeFileSync(path.join(root, '.yarnrc.yml'), 'npmScopes: {}')
160
+ const config: Record<string, unknown> = {
161
+ npmRegistryServer: 'https://registry.npmjs.org',
162
+ npmScopes: { vendor: { npmRegistryServer: 'https://private.example.test/npm/' } },
163
+ npmRegistries: { '//private.example.test/npm': { npmAuthToken: 'test-token-do-not-print' } },
164
+ npmAuthToken: null,
165
+ npmAuthIdent: null,
166
+ }
167
+ jest.spyOn(childProcess, 'execFileSync').mockImplementation((_command, args) => args?.[0] === '-xOf'
168
+ ? JSON.stringify({ name: '@vendor/example', version: '1.2.3' })
169
+ : 'package/src/modules/example/index.ts\npackage/dist/modules/example/index.js\n')
170
+ let temporaryConfig: string | undefined
171
+ jest.spyOn(spawn, 'sync').mockImplementation((command, args) => {
172
+ if (command === 'yarn' || command === 'yarn.cmd') return { status: 0, stdout: JSON.stringify(config[String(args?.[2])]), stderr: '', pid: 1, output: [], signal: null }
173
+ const values = args ?? []
174
+ temporaryConfig = String(values[values.indexOf('--userconfig') + 1])
175
+ expect(fs.statSync(temporaryConfig).mode & 0o777).toBe(0o600)
176
+ expect(fs.readFileSync(temporaryConfig, 'utf8')).toContain('//private.example.test/npm/:_authToken="test-token-do-not-print"')
177
+ expect(values).toContain('--@vendor:registry=https://private.example.test/npm/')
178
+ expect(values.join(' ')).not.toContain('test-token-do-not-print')
179
+ return { status, stdout: JSON.stringify([{ name: '@vendor/example', version: '1.2.3', filename: 'candidate.tgz' }]), stderr: 'test-token-do-not-print', pid: 1, output: [], signal: null }
180
+ })
181
+ if (status === 0) expect(inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root).version).toBe('1.2.3')
182
+ else expect(() => inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root)).toThrow('No package was installed')
183
+ expect(temporaryConfig).toBeDefined()
184
+ expect(fs.existsSync(temporaryConfig!)).toBe(false)
185
+ })
186
+
187
+ it('fails closed on unavailable inspection without exposing private tool output', () => {
188
+ jest.spyOn(spawn, 'sync').mockImplementation(() => { throw new Error('private registry token') })
189
+ expect(() => inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root)).toThrow('No package was installed')
190
+ try { inspectNpmModuleTarget('@vendor/example@next', '@vendor/example', root) } catch (error) {
191
+ expect(String(error)).not.toContain('private registry token')
192
+ }
193
+ expect(fs.readdirSync(root)).toEqual([])
194
+ })
195
+ })