@open-mercato/cli 0.10.1-develop.7990.1.82d0a183c5 → 0.10.1-develop.8004.1.a8a79ee43f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/build.mjs +1 -0
- package/dist/agentic/guides/module-facts.json +162 -162
- package/dist/agentic/guides/module-facts.v2.json +162 -162
- package/dist/agentic/guides/module-tool.md +21 -0
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/availability/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
- package/dist/agentic/guides/modules/cart/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/forms/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/promotions/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/seeds/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +4 -0
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/ai/harness/cases.json +23 -9
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +90 -4
- package/dist/i18n/de.json +4 -0
- package/dist/i18n/en.json +4 -0
- package/dist/i18n/es.json +4 -0
- package/dist/i18n/ko.json +4 -0
- package/dist/i18n/pl.json +4 -0
- package/dist/lib/__fixtures__/module-search/built-in.json +235 -0
- package/dist/lib/__fixtures__/module-search/catalog-parity.json +4076 -0
- package/dist/lib/__fixtures__/module-search/modules-index.json +1980 -0
- package/dist/lib/__fixtures__/official-module-package/package.json +33 -0
- package/dist/lib/__integration__/TC-INT-007.spec.js +207 -19
- package/dist/lib/__integration__/TC-INT-007.spec.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-009.spec.js +267 -0
- package/dist/lib/__integration__/TC-INT-009.spec.js.map +7 -0
- package/dist/lib/eject.js +25 -9
- package/dist/lib/eject.js.map +2 -2
- package/dist/lib/generators/entity-ids.js +2 -0
- package/dist/lib/generators/entity-ids.js.map +2 -2
- package/dist/lib/generators/module-di.js +2 -0
- package/dist/lib/generators/module-di.js.map +2 -2
- package/dist/lib/generators/module-entities.js +2 -0
- package/dist/lib/generators/module-entities.js.map +2 -2
- package/dist/lib/generators/module-package-sources.js +6 -4
- package/dist/lib/generators/module-package-sources.js.map +2 -2
- package/dist/lib/generators/module-registry.js +32 -86
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/openapi.js +2 -0
- package/dist/lib/generators/openapi.js.map +2 -2
- package/dist/lib/generators/source-exports.js +73 -0
- package/dist/lib/generators/source-exports.js.map +7 -0
- package/dist/lib/init-secrets.js +33 -1
- package/dist/lib/init-secrets.js.map +2 -2
- package/dist/lib/init-summary.js +44 -0
- package/dist/lib/init-summary.js.map +7 -0
- package/dist/lib/module-github.js +98 -0
- package/dist/lib/module-github.js.map +7 -0
- package/dist/lib/module-identity.js +131 -0
- package/dist/lib/module-identity.js.map +7 -0
- package/dist/lib/module-inspection.js +124 -0
- package/dist/lib/module-inspection.js.map +7 -0
- package/dist/lib/module-install.js +150 -21
- package/dist/lib/module-install.js.map +3 -3
- package/dist/lib/module-package.js +205 -15
- package/dist/lib/module-package.js.map +2 -2
- package/dist/lib/modules-config.js +69 -30
- package/dist/lib/modules-config.js.map +2 -2
- package/dist/lib/resolver.js +33 -34
- package/dist/lib/resolver.js.map +2 -2
- package/dist/mercato.js +17 -37
- package/dist/mercato.js.map +2 -2
- package/package.json +7 -7
- package/src/__tests__/mercato.test.ts +71 -4
- package/src/i18n/de.json +4 -0
- package/src/i18n/en.json +4 -0
- package/src/i18n/es.json +4 -0
- package/src/i18n/ko.json +4 -0
- package/src/i18n/pl.json +4 -0
- package/src/lib/__fixtures__/official-module-package/dist/modules/test_package/widgets/injection/test/widget.js +1 -0
- package/src/lib/__integration__/TC-INT-007.spec.ts +219 -19
- package/src/lib/__integration__/TC-INT-009.spec.ts +279 -0
- package/src/lib/__tests__/eject.test.ts +98 -4
- package/src/lib/__tests__/init-secrets.test.ts +85 -1
- package/src/lib/__tests__/init-summary.test.ts +50 -0
- package/src/lib/__tests__/module-github.test.ts +108 -0
- package/src/lib/__tests__/module-identity.test.ts +144 -0
- package/src/lib/__tests__/module-inspection.test.ts +195 -0
- package/src/lib/__tests__/module-install.test.ts +399 -10
- package/src/lib/__tests__/module-package.test.ts +154 -0
- package/src/lib/__tests__/modules-config.test.ts +55 -0
- package/src/lib/__tests__/resolver.packages.test.ts +29 -0
- package/src/lib/eject.ts +28 -10
- package/src/lib/generators/__tests__/module-id-collisions.test.ts +146 -0
- package/src/lib/generators/__tests__/module-package-sources.test.ts +19 -2
- package/src/lib/generators/__tests__/module-requirements.test.ts +56 -0
- package/src/lib/generators/entity-ids.ts +2 -0
- package/src/lib/generators/module-di.ts +2 -0
- package/src/lib/generators/module-entities.ts +2 -0
- package/src/lib/generators/module-package-sources.ts +7 -5
- package/src/lib/generators/module-registry.ts +33 -115
- package/src/lib/generators/openapi.ts +3 -0
- package/src/lib/generators/source-exports.ts +93 -0
- package/src/lib/init-secrets.ts +37 -0
- package/src/lib/init-summary.ts +54 -0
- package/src/lib/module-github.ts +109 -0
- package/src/lib/module-identity.ts +145 -0
- package/src/lib/module-inspection.ts +133 -0
- package/src/lib/module-install.ts +170 -22
- package/src/lib/module-package.ts +243 -16
- package/src/lib/modules-config.ts +80 -32
- package/src/lib/resolver.ts +35 -39
- package/src/mercato.ts +18 -44
- package/src/types/cross-spawn.d.ts +2 -2
|
@@ -2,10 +2,16 @@ import { EventEmitter } from 'node:events'
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import os from 'node:os'
|
|
4
4
|
import path from 'node:path'
|
|
5
|
-
import type
|
|
5
|
+
import { readEnabledModulesFromConfig, type PackageResolver } from '../resolver'
|
|
6
6
|
import { addOfficialModule, enableOfficialModule } from '../module-install'
|
|
7
|
-
import * as
|
|
7
|
+
import * as github from '../module-github'
|
|
8
|
+
import * as inspection from '../module-inspection'
|
|
8
9
|
import spawn from 'cross-spawn'
|
|
10
|
+
import { execFileSync } from 'node:child_process'
|
|
11
|
+
import { getModuleLocators } from '../module-identity'
|
|
12
|
+
import { resolveModuleRequirement } from '@open-mercato/shared/modules/module-locators'
|
|
13
|
+
|
|
14
|
+
import * as modulesConfig from '../modules-config'
|
|
9
15
|
|
|
10
16
|
jest.mock('cross-spawn', () => ({ __esModule: true, default: jest.fn() }))
|
|
11
17
|
|
|
@@ -18,10 +24,12 @@ function buildPackageFixture(
|
|
|
18
24
|
ejectable?: boolean
|
|
19
25
|
extraSourceFiles?: Array<{ relativePath: string; content: string }>
|
|
20
26
|
packageName?: string
|
|
27
|
+
requires?: string[]
|
|
21
28
|
},
|
|
22
29
|
): void {
|
|
23
30
|
const ejectable = options?.ejectable ?? false
|
|
24
31
|
const packageName = options?.packageName ?? '@open-mercato/test-package'
|
|
32
|
+
const requires = options?.requires ?? []
|
|
25
33
|
for (const base of ['src', 'dist']) {
|
|
26
34
|
fs.mkdirSync(path.join(packageRoot, base, 'modules', moduleId), { recursive: true })
|
|
27
35
|
}
|
|
@@ -31,7 +39,7 @@ function buildPackageFixture(
|
|
|
31
39
|
)
|
|
32
40
|
fs.writeFileSync(
|
|
33
41
|
path.join(packageRoot, 'src', 'modules', moduleId, 'index.ts'),
|
|
34
|
-
`export const metadata = { title: 'Test', ejectable: ${ejectable ? 'true' : 'false'} }\n`,
|
|
42
|
+
`export const metadata = { title: 'Test', ejectable: ${ejectable ? 'true' : 'false'}, requires: ${JSON.stringify(requires)} }\n`,
|
|
35
43
|
)
|
|
36
44
|
fs.writeFileSync(
|
|
37
45
|
path.join(packageRoot, 'dist', 'modules', moduleId, 'index.js'),
|
|
@@ -42,6 +50,18 @@ function buildPackageFixture(
|
|
|
42
50
|
const targetPath = path.join(packageRoot, 'src', 'modules', moduleId, file.relativePath)
|
|
43
51
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true })
|
|
44
52
|
fs.writeFileSync(targetPath, file.content)
|
|
53
|
+
|
|
54
|
+
if (/\.[jt]sx?$/.test(file.relativePath)) {
|
|
55
|
+
const runtimePath = path.join(
|
|
56
|
+
packageRoot,
|
|
57
|
+
'dist',
|
|
58
|
+
'modules',
|
|
59
|
+
moduleId,
|
|
60
|
+
file.relativePath.replace(/\.[jt]sx?$/, '.js'),
|
|
61
|
+
)
|
|
62
|
+
fs.mkdirSync(path.dirname(runtimePath), { recursive: true })
|
|
63
|
+
fs.writeFileSync(runtimePath, 'module.exports = {}\n')
|
|
64
|
+
}
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
67
|
|
|
@@ -53,6 +73,7 @@ function buildResolver(
|
|
|
53
73
|
const modulesTsPath = path.join(appDir, 'src', 'modules.ts')
|
|
54
74
|
fs.mkdirSync(path.join(appDir, 'src'), { recursive: true })
|
|
55
75
|
fs.writeFileSync(modulesTsPath, modulesTsContent)
|
|
76
|
+
const initiallyEnabledModules = readEnabledModulesFromConfig(modulesTsPath)
|
|
56
77
|
|
|
57
78
|
return {
|
|
58
79
|
getAppDir: () => appDir,
|
|
@@ -60,8 +81,17 @@ function buildResolver(
|
|
|
60
81
|
getPackageRoot: () => packageRoot,
|
|
61
82
|
isMonorepo: () => false,
|
|
62
83
|
getRootDir: () => appDir,
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
discoverPackages: () => [],
|
|
85
|
+
loadEnabledModules: () => initiallyEnabledModules,
|
|
86
|
+
getModulePaths: (entry) => ({
|
|
87
|
+
appBase: path.join(appDir, 'src', 'modules', entry.id),
|
|
88
|
+
pkgBase: path.join(packageRoot, 'src', 'modules', entry.id),
|
|
89
|
+
}),
|
|
90
|
+
getModuleImportBase: (entry) => ({
|
|
91
|
+
appBase: `@/modules/${entry.id}`,
|
|
92
|
+
pkgBase: `${entry.from ?? '@open-mercato/core'}/modules/${entry.id}`,
|
|
93
|
+
}),
|
|
94
|
+
getPackageOutputDir: () => path.join(packageRoot, 'generated'),
|
|
65
95
|
getOutputDir: () => path.join(appDir, '.mercato', 'generated'),
|
|
66
96
|
} as unknown as PackageResolver
|
|
67
97
|
}
|
|
@@ -77,6 +107,14 @@ describe('enableOfficialModule', () => {
|
|
|
77
107
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
78
108
|
})
|
|
79
109
|
|
|
110
|
+
it.each(['../outside-package', '/absolute/package', 'npm:package', '@vendor/package@next'])('rejects %s as an installed binding before package resolution', async (binding) => {
|
|
111
|
+
const resolver = buildResolver(path.join(tmpDir, 'app'), path.join(tmpDir, 'package'), 'export const enabledModules = []\n')
|
|
112
|
+
const lookup = jest.spyOn(resolver, 'getPackageRoot')
|
|
113
|
+
await expect(enableOfficialModule(resolver, binding, undefined, false, true)).rejects.toThrow('Unsupported installed package binding')
|
|
114
|
+
expect(lookup).not.toHaveBeenCalled()
|
|
115
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe('export const enabledModules = []\n')
|
|
116
|
+
})
|
|
117
|
+
|
|
80
118
|
it('succeeds without changes when the module is already enabled with the same source', async () => {
|
|
81
119
|
const packageRoot = path.join(tmpDir, 'node_modules', '@open-mercato', 'test-package')
|
|
82
120
|
const appDir = path.join(tmpDir, 'app')
|
|
@@ -197,6 +235,7 @@ describe('enableOfficialModule', () => {
|
|
|
197
235
|
packageRoot,
|
|
198
236
|
"export const enabledModules = []\n",
|
|
199
237
|
)
|
|
238
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ dependencies: { '@open-mercato/test-package': '0.1.0' } }))
|
|
200
239
|
|
|
201
240
|
await expect(
|
|
202
241
|
enableOfficialModule(
|
|
@@ -215,8 +254,46 @@ describe('enableOfficialModule', () => {
|
|
|
215
254
|
expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'))).toBe(true)
|
|
216
255
|
expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package', 'backend', 'page.tsx'))).toBe(true)
|
|
217
256
|
expect(fs.readFileSync(path.join(appDir, 'src', 'modules.ts'), 'utf8')).toContain(
|
|
218
|
-
"{ id: 'test_package', from: '@app' }",
|
|
257
|
+
"{ id: 'test_package', from: '@app', locator: 'npm:@open-mercato/test-package', packageName: '@open-mercato/test-package' }",
|
|
258
|
+
)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('rejects missing descriptor dependencies before registration or ejection', async () => {
|
|
262
|
+
const packageRoot = path.join(tmpDir, 'node_modules', '@open-mercato', 'test-package')
|
|
263
|
+
const appDir = path.join(tmpDir, 'app')
|
|
264
|
+
const initialModulesSource = "export const enabledModules = [{ id: 'customers' }]\n"
|
|
265
|
+
buildPackageFixture(packageRoot, 'test_package', {
|
|
266
|
+
ejectable: true,
|
|
267
|
+
requires: ['customers', 'catalog'],
|
|
268
|
+
})
|
|
269
|
+
const resolver = buildResolver(appDir, packageRoot, initialModulesSource)
|
|
270
|
+
|
|
271
|
+
await expect(
|
|
272
|
+
enableOfficialModule(resolver, '@open-mercato/test-package', undefined, true),
|
|
273
|
+
).rejects.toThrow(
|
|
274
|
+
'Module "test_package" requires module(s) that are not enabled: catalog.\n'
|
|
275
|
+
+ 'Enable the missing module(s) in src/modules.ts first, then rerun this command.',
|
|
219
276
|
)
|
|
277
|
+
|
|
278
|
+
expect(fs.readFileSync(path.join(appDir, 'src', 'modules.ts'), 'utf8')).toBe(initialModulesSource)
|
|
279
|
+
expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package'))).toBe(false)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('allows registration when every descriptor dependency is enabled', async () => {
|
|
283
|
+
const packageRoot = path.join(tmpDir, 'node_modules', '@open-mercato', 'test-package')
|
|
284
|
+
const appDir = path.join(tmpDir, 'app')
|
|
285
|
+
buildPackageFixture(packageRoot, 'test_package', {
|
|
286
|
+
requires: ['customers', 'catalog'],
|
|
287
|
+
})
|
|
288
|
+
const resolver = buildResolver(
|
|
289
|
+
appDir,
|
|
290
|
+
packageRoot,
|
|
291
|
+
"export const enabledModules = [{ id: 'customers' }, { id: 'catalog' }]\n",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
await expect(
|
|
295
|
+
enableOfficialModule(resolver, '@open-mercato/test-package'),
|
|
296
|
+
).resolves.toMatchObject({ moduleId: 'test_package', registrationChanged: true })
|
|
220
297
|
})
|
|
221
298
|
|
|
222
299
|
it('rejects --eject for a module gated behind a disabled env flag before copying any source', async () => {
|
|
@@ -344,15 +421,323 @@ describe('third-party module packages', () => {
|
|
|
344
421
|
})
|
|
345
422
|
})
|
|
346
423
|
|
|
424
|
+
|
|
425
|
+
describe('canonical module preflight', () => {
|
|
426
|
+
let root: string
|
|
427
|
+
beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'module-canonical-')) })
|
|
428
|
+
afterEach(() => fs.rmSync(root, { recursive: true, force: true }))
|
|
429
|
+
|
|
430
|
+
it.each(['directory', 'archive'] as const)('keeps an already-enabled file %s a no-op without reinstalling', async (kind) => {
|
|
431
|
+
const packageRoot = path.join(root, 'package')
|
|
432
|
+
const appDir = path.join(root, 'app')
|
|
433
|
+
const packageName = '@vendor/consents'
|
|
434
|
+
buildPackageFixture(packageRoot, 'consents', { packageName })
|
|
435
|
+
if (kind === 'archive') execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
|
|
436
|
+
const fileLocator = kind === 'archive' ? 'file:../candidate.tgz' : 'file:../package'
|
|
437
|
+
const source = `export const enabledModules = [{ id: 'consents', from: '${packageName}' }]\n`
|
|
438
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
439
|
+
const manifest = JSON.stringify({ dependencies: { [packageName]: fileLocator } })
|
|
440
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), manifest)
|
|
441
|
+
jest.mocked(spawn).mockClear()
|
|
442
|
+
|
|
443
|
+
await expect(addOfficialModule(resolver, `${packageName}@${fileLocator}`, false, undefined, true)).resolves.toMatchObject({ moduleId: 'consents', registrationChanged: false })
|
|
444
|
+
expect(spawn).not.toHaveBeenCalled()
|
|
445
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
446
|
+
expect(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8')).toBe(manifest)
|
|
447
|
+
expect(fs.existsSync(resolver.getOutputDir())).toBe(false)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it.each(['directory', 'archive'] as const)('rejects a different unknown file %s even when its module is already enabled', async (kind) => {
|
|
451
|
+
const packageRoot = path.join(root, 'package')
|
|
452
|
+
const appDir = path.join(root, 'app')
|
|
453
|
+
const packageName = '@vendor/consents'
|
|
454
|
+
buildPackageFixture(packageRoot, 'consents', { packageName })
|
|
455
|
+
if (kind === 'archive') execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
|
|
456
|
+
const fileLocator = kind === 'archive' ? 'file:../candidate.tgz' : 'file:../package'
|
|
457
|
+
const source = `export const enabledModules = [{ id: 'consents', from: '${packageName}' }]\n`
|
|
458
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
459
|
+
const manifest = JSON.stringify({ dependencies: { [packageName]: 'file:../other-package' } })
|
|
460
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), manifest)
|
|
461
|
+
jest.mocked(spawn).mockClear()
|
|
462
|
+
|
|
463
|
+
await expect(addOfficialModule(resolver, `${packageName}@${fileLocator}`, false, undefined, true)).rejects.toThrow('Package binding collision')
|
|
464
|
+
expect(spawn).not.toHaveBeenCalled()
|
|
465
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
466
|
+
expect(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8')).toBe(manifest)
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it.each([
|
|
470
|
+
['directory', false], ['archive', false], ['directory', true], ['archive', true],
|
|
471
|
+
] as const)('keeps a new file %s install provenance unknown with eject=%s', async (kind, eject) => {
|
|
472
|
+
const packageRoot = path.join(root, 'package')
|
|
473
|
+
const appDir = path.join(root, 'app')
|
|
474
|
+
const packageName = '@vendor/consents'
|
|
475
|
+
buildPackageFixture(packageRoot, 'consents', { packageName, ejectable: true })
|
|
476
|
+
if (kind === 'archive') execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
|
|
477
|
+
const fileLocator = kind === 'archive' ? 'file:../candidate.tgz' : 'file:../package'
|
|
478
|
+
const resolver = buildResolver(appDir, packageRoot, 'export const enabledModules = []\n')
|
|
479
|
+
resolver.loadEnabledModules = () => readEnabledModulesFromConfig(resolver.getModulesConfigPath())
|
|
480
|
+
jest.mocked(spawn).mockImplementation(() => {
|
|
481
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ dependencies: { [packageName]: fileLocator } }))
|
|
482
|
+
const child = new EventEmitter()
|
|
483
|
+
queueMicrotask(() => child.emit('exit', 0))
|
|
484
|
+
return child as ReturnType<typeof spawn>
|
|
485
|
+
})
|
|
486
|
+
try {
|
|
487
|
+
await addOfficialModule(resolver, `${packageName}@${fileLocator}`, eject, undefined, true)
|
|
488
|
+
const [entry] = resolver.loadEnabledModules()
|
|
489
|
+
expect(entry).toMatchObject({ id: 'consents', from: eject ? '@app' : packageName })
|
|
490
|
+
expect(entry.locator).toBeUndefined()
|
|
491
|
+
if (eject) {
|
|
492
|
+
expect(entry.packageName).toBe(packageName)
|
|
493
|
+
expect(fs.readFileSync(path.join(appDir, 'src/modules/consents/index.ts'), 'utf8')).toBe(fs.readFileSync(path.join(packageRoot, 'src/modules/consents/index.ts'), 'utf8'))
|
|
494
|
+
}
|
|
495
|
+
const locators = getModuleLocators(entry, resolver)
|
|
496
|
+
expect(locators).toEqual([])
|
|
497
|
+
expect(resolveModuleRequirement(`npm:${packageName}`, [{ id: entry.id, locators }])).toBeUndefined()
|
|
498
|
+
expect(resolveModuleRequirement('consents', [{ id: entry.id, locators }])).toBe('consents')
|
|
499
|
+
|
|
500
|
+
const dependentRoot = path.join(root, 'dependent')
|
|
501
|
+
buildPackageFixture(dependentRoot, 'dependent', { packageName: '@vendor/dependent', requires: [`npm:${packageName}`], ejectable: true })
|
|
502
|
+
resolver.getPackageRoot = (binding) => binding === '@vendor/dependent' ? dependentRoot : packageRoot
|
|
503
|
+
const sourceBefore = fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')
|
|
504
|
+
await expect(enableOfficialModule(resolver, '@vendor/dependent', undefined, true, true)).rejects.toThrow('requires module(s) that are not enabled: npm:@vendor/consents')
|
|
505
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(sourceBefore)
|
|
506
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/dependent'))).toBe(false)
|
|
507
|
+
} finally { jest.mocked(spawn).mockReset() }
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it.each([
|
|
511
|
+
['directory', false], ['archive', false], ['directory', true], ['archive', true],
|
|
512
|
+
] as const)('rejects an unknown file %s origin before replacing an enabled binding with implicit core=%s', async (kind, implicitCore) => {
|
|
513
|
+
const packageRoot = path.join(root, 'package')
|
|
514
|
+
const appDir = path.join(root, 'app')
|
|
515
|
+
const packageName = implicitCore ? '@open-mercato/core' : '@vendor/consents'
|
|
516
|
+
buildPackageFixture(packageRoot, 'consents', { packageName, ejectable: true })
|
|
517
|
+
if (kind === 'archive') execFileSync('tar', ['-czf', path.join(root, 'candidate.tgz'), '-C', root, 'package'])
|
|
518
|
+
const source = `export const enabledModules = [{ id: 'consents', ${implicitCore ? '' : `from: '${packageName}', `}locator: 'npm:${packageName}' }]\n`
|
|
519
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
520
|
+
if (implicitCore) resolver.loadEnabledModules = () => [{ id: 'consents', locator: `npm:${packageName}` }]
|
|
521
|
+
const manifest = JSON.stringify({ dependencies: { [packageName]: '1.0.0' } })
|
|
522
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), manifest)
|
|
523
|
+
jest.mocked(spawn).mockClear()
|
|
524
|
+
const fileLocator = kind === 'archive' ? 'file:../candidate.tgz' : 'file:../package'
|
|
525
|
+
await expect(addOfficialModule(resolver, `${packageName}@${fileLocator}`, true, undefined, true)).rejects.toThrow('Package binding collision')
|
|
526
|
+
expect(spawn).not.toHaveBeenCalled()
|
|
527
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
528
|
+
expect(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8')).toBe(manifest)
|
|
529
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/consents'))).toBe(false)
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('revalidates implicit core binding consumers after package installation before any registration or eject copy', async () => {
|
|
533
|
+
const appDir = path.join(root, 'app')
|
|
534
|
+
const packageRoot = path.join(appDir, 'node_modules', '@open-mercato', 'core')
|
|
535
|
+
const packageName = '@open-mercato/core'
|
|
536
|
+
buildPackageFixture(packageRoot, 'active', { packageName })
|
|
537
|
+
buildPackageFixture(packageRoot, 'candidate', { packageName, ejectable: true })
|
|
538
|
+
const source = "export const enabledModules = [{ id: 'active' }]\n"
|
|
539
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
540
|
+
resolver.loadEnabledModules = () => [{ id: 'active' }]
|
|
541
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ dependencies: { [packageName]: '0.1.0' } }))
|
|
542
|
+
const inspect = jest.spyOn(inspection, 'inspectNpmModuleTarget').mockReturnValue({ packageName, packageSpec: `${packageName}@next`, locator: `npm:${packageName}`, moduleIds: ['active', 'candidate'] })
|
|
543
|
+
jest.mocked(spawn).mockImplementation(() => {
|
|
544
|
+
for (const base of ['src', 'dist']) fs.rmSync(path.join(packageRoot, base, 'modules/active'), { recursive: true })
|
|
545
|
+
const child = new EventEmitter()
|
|
546
|
+
queueMicrotask(() => child.emit('exit', 0))
|
|
547
|
+
return child as ReturnType<typeof spawn>
|
|
548
|
+
})
|
|
549
|
+
try {
|
|
550
|
+
await expect(addOfficialModule(resolver, `${packageName}@next`, true, 'candidate')).rejects.toThrow('does not contain module "active"')
|
|
551
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
552
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/candidate'))).toBe(false)
|
|
553
|
+
} finally { inspect.mockRestore(); jest.mocked(spawn).mockReset() }
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
it('does not satisfy a required scoped package with a same-name module from another scope', async () => {
|
|
557
|
+
const packageRoot = path.join(root, 'package')
|
|
558
|
+
const appDir = path.join(root, 'app')
|
|
559
|
+
const source = "export const enabledModules = [{ id: 'marketing_consents', from: '@other/marketing_consents' }]\n"
|
|
560
|
+
buildPackageFixture(packageRoot, 'test_package', { requires: ['@pkarw/marketing_consents@latest'] })
|
|
561
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
562
|
+
await expect(enableOfficialModule(resolver, '@open-mercato/test-package')).rejects.toThrow("npx create-mercato-module install '@pkarw/marketing_consents@latest'")
|
|
563
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
it('matches canonical npm requirements to the enabled package source', async () => {
|
|
567
|
+
const packageRoot = path.join(root, 'package')
|
|
568
|
+
const appDir = path.join(root, 'app')
|
|
569
|
+
buildPackageFixture(packageRoot, 'test_package', { requires: ['npm:@pkarw/marketing_consents@1.2.0'] })
|
|
570
|
+
for (const base of ['src', 'dist']) {
|
|
571
|
+
fs.mkdirSync(path.join(packageRoot, base, 'modules', 'marketing_consents'), { recursive: true })
|
|
572
|
+
fs.writeFileSync(path.join(packageRoot, base, 'modules', 'marketing_consents', base === 'src' ? 'index.ts' : 'index.js'), 'export const metadata = {}')
|
|
573
|
+
}
|
|
574
|
+
const resolver = buildResolver(appDir, packageRoot, "export const enabledModules = [{ id: 'marketing_consents', from: '@pkarw/marketing_consents' }]\n")
|
|
575
|
+
const requiredPackageRoot = path.join(root, 'required-package')
|
|
576
|
+
fs.mkdirSync(requiredPackageRoot, { recursive: true })
|
|
577
|
+
fs.writeFileSync(path.join(requiredPackageRoot, 'package.json'), JSON.stringify({ name: '@pkarw/marketing_consents' }))
|
|
578
|
+
fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ dependencies: { '@pkarw/marketing_consents': '1.2.0' } }))
|
|
579
|
+
resolver.getPackageRoot = (packageName) => packageName === '@pkarw/marketing_consents' ? requiredPackageRoot : packageRoot
|
|
580
|
+
await expect(enableOfficialModule(resolver, '@open-mercato/test-package', 'test_package')).resolves.toMatchObject({ moduleId: 'test_package' })
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('rejects name collisions before copying ejected sources', async () => {
|
|
584
|
+
const packageRoot = path.join(root, 'package')
|
|
585
|
+
const appDir = path.join(root, 'app')
|
|
586
|
+
buildPackageFixture(packageRoot, 'marketing_consents', { ejectable: true })
|
|
587
|
+
const source = "export const enabledModules = [{ id: 'marketing_consents', from: '@other/marketing_consents' }]\n"
|
|
588
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
589
|
+
await expect(enableOfficialModule(resolver, '@open-mercato/test-package', undefined, true)).rejects.toThrow('Module name collision')
|
|
590
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/marketing_consents'))).toBe(false)
|
|
591
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('rejects a GitHub same-name collision before package installation', async () => {
|
|
595
|
+
const packageRoot = path.join(root, 'package')
|
|
596
|
+
const appDir = path.join(root, 'app')
|
|
597
|
+
const source = "export const enabledModules = [{ id: 'marketing_consents', from: '@other/marketing_consents' }]\n"
|
|
598
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
599
|
+
const lookup = jest.spyOn(github, 'resolveGithubModuleTarget').mockReturnValue({
|
|
600
|
+
packageName: '@pkarw/marketing_consents', moduleIds: ['marketing_consents'],
|
|
601
|
+
packageSpec: '@pkarw/marketing_consents@github:pkarw/consents#' + 'a'.repeat(40), locator: 'github:pkarw/consents',
|
|
602
|
+
})
|
|
603
|
+
try {
|
|
604
|
+
await expect(addOfficialModule(resolver, 'github:pkarw/consents', false, undefined, true)).rejects.toThrow('Module name collision')
|
|
605
|
+
expect(fs.existsSync(path.join(appDir, 'package.json'))).toBe(false)
|
|
606
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
607
|
+
} finally {
|
|
608
|
+
lookup.mockRestore()
|
|
609
|
+
}
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
it('rejects replacing the same npm name from a different GitHub origin', async () => {
|
|
613
|
+
const packageRoot = path.join(root, 'package')
|
|
614
|
+
const appDir = path.join(root, 'app')
|
|
615
|
+
const resolver = buildResolver(appDir, packageRoot, "export const enabledModules = [{ id: 'marketing_consents', from: '@pkarw/consents', locator: 'github:pkarw/original' }]\n")
|
|
616
|
+
const lookup = jest.spyOn(github, 'resolveGithubModuleTarget').mockReturnValue({
|
|
617
|
+
packageName: '@pkarw/consents', moduleIds: ['marketing_consents'],
|
|
618
|
+
packageSpec: '@pkarw/consents@github:pkarw/replacement#' + 'a'.repeat(40), locator: 'github:pkarw/replacement',
|
|
619
|
+
})
|
|
620
|
+
try {
|
|
621
|
+
await expect(addOfficialModule(resolver, 'github:pkarw/replacement', false, undefined, true)).rejects.toThrow('github:pkarw/original')
|
|
622
|
+
} finally {
|
|
623
|
+
lookup.mockRestore()
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
it('requires explicit trust for GitHub sources even if they declare an official npm namespace', async () => {
|
|
629
|
+
const resolver = buildResolver(path.join(root, 'app'), path.join(root, 'package'), 'export const enabledModules = []\n')
|
|
630
|
+
const lookup = jest.spyOn(github, 'resolveGithubModuleTarget').mockReturnValue({
|
|
631
|
+
packageName: '@open-mercato/consents', moduleIds: ['marketing_consents'],
|
|
632
|
+
packageSpec: '@open-mercato/consents@github:attacker/consents#' + 'a'.repeat(40), locator: 'github:attacker/consents',
|
|
633
|
+
})
|
|
634
|
+
try {
|
|
635
|
+
await expect(addOfficialModule(resolver, 'github:attacker/consents', false)).rejects.toThrow('--allow-third-party')
|
|
636
|
+
expect(lookup).not.toHaveBeenCalled()
|
|
637
|
+
} finally {
|
|
638
|
+
lookup.mockRestore()
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
it('rejects duplicate existing registrations before eject source writes', async () => {
|
|
644
|
+
const packageRoot = path.join(root, 'package')
|
|
645
|
+
const appDir = path.join(root, 'app')
|
|
646
|
+
buildPackageFixture(packageRoot, 'marketing_consents', { ejectable: true })
|
|
647
|
+
const source = "export const enabledModules = [{ id: 'marketing_consents', from: '@open-mercato/test-package' }, { id: 'marketing_consents', from: '@other/consents' }]\n"
|
|
648
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
649
|
+
await expect(enableOfficialModule(resolver, '@open-mercato/test-package', undefined, true)).rejects.toThrow('Duplicate enabled module ID')
|
|
650
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/marketing_consents'))).toBe(false)
|
|
651
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
it('rejects a changed GitHub binding before Yarn even when the selected ID differs', async () => {
|
|
655
|
+
const packageRoot = path.join(root, 'package')
|
|
656
|
+
const appDir = path.join(root, 'app')
|
|
657
|
+
buildPackageFixture(packageRoot, 'original', { packageName: '@vendor/bundle' })
|
|
658
|
+
const source = "export const enabledModules = [{ id: 'original', from: '@vendor/bundle', locator: 'github:vendor/original' }]\n"
|
|
659
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
660
|
+
const lookup = jest.spyOn(github, 'resolveGithubModuleTarget').mockReturnValue({
|
|
661
|
+
packageName: '@vendor/bundle', moduleIds: ['replacement'], locator: 'github:vendor/replacement',
|
|
662
|
+
packageSpec: '@vendor/bundle@github:vendor/replacement#' + 'a'.repeat(40),
|
|
663
|
+
})
|
|
664
|
+
try {
|
|
665
|
+
await expect(addOfficialModule(resolver, 'github:vendor/replacement', false, undefined, true)).rejects.toThrow('Package binding collision')
|
|
666
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
667
|
+
expect(fs.existsSync(path.join(appDir, 'package.json'))).toBe(false)
|
|
668
|
+
} finally { lookup.mockRestore() }
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
it('rejects npm updates that remove an active sibling before installation', async () => {
|
|
672
|
+
const packageRoot = path.join(root, 'package')
|
|
673
|
+
const appDir = path.join(root, 'app')
|
|
674
|
+
buildPackageFixture(packageRoot, 'original', { packageName: '@vendor/bundle' })
|
|
675
|
+
const source = "export const enabledModules = [{ id: 'original', from: '@vendor/bundle', locator: 'npm:@vendor/bundle' }]\n"
|
|
676
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
677
|
+
const lookup = jest.spyOn(inspection, 'inspectNpmModuleTarget').mockReturnValue({
|
|
678
|
+
packageName: '@vendor/bundle', moduleIds: ['replacement'], locator: 'npm:@vendor/bundle', packageSpec: '@vendor/bundle@2.0.0',
|
|
679
|
+
})
|
|
680
|
+
try {
|
|
681
|
+
await expect(addOfficialModule(resolver, '@vendor/bundle@next', false, 'replacement', true)).rejects.toThrow('preserve its source and module layout')
|
|
682
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
683
|
+
} finally { lookup.mockRestore() }
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
it('preserves a single package registration with a local overlay on repeated enable', async () => {
|
|
687
|
+
const packageRoot = path.join(root, 'package')
|
|
688
|
+
const appDir = path.join(root, 'app')
|
|
689
|
+
buildPackageFixture(packageRoot, 'test_package')
|
|
690
|
+
const source = "export const enabledModules = [{ id: 'test_package', from: '@open-mercato/test-package' }]\n"
|
|
691
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
692
|
+
const localPath = path.join(appDir, 'src/modules/test_package/index.ts')
|
|
693
|
+
fs.mkdirSync(path.dirname(localPath), { recursive: true })
|
|
694
|
+
fs.writeFileSync(localPath, 'export const metadata = { title: "Overlay" }')
|
|
695
|
+
await expect(enableOfficialModule(resolver, '@open-mercato/test-package')).resolves.toMatchObject({ registrationChanged: false })
|
|
696
|
+
expect(fs.readFileSync(localPath, 'utf8')).toContain('Overlay')
|
|
697
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('reports a post-install version disagreement without registering or ejecting', async () => {
|
|
701
|
+
const packageRoot = path.join(root, 'package')
|
|
702
|
+
const appDir = path.join(root, 'app')
|
|
703
|
+
buildPackageFixture(packageRoot, 'test_package', { ejectable: true })
|
|
704
|
+
const source = 'export const enabledModules = []\n'
|
|
705
|
+
const resolver = buildResolver(appDir, packageRoot, source)
|
|
706
|
+
const lookup = jest.spyOn(inspection, 'inspectNpmModuleTarget').mockReturnValue({
|
|
707
|
+
packageName: '@open-mercato/test-package', packageSpec: '@open-mercato/test-package@next',
|
|
708
|
+
locator: 'npm:@open-mercato/test-package', moduleIds: ['test_package'], version: '2.0.0',
|
|
709
|
+
})
|
|
710
|
+
jest.mocked(spawn).mockImplementation(() => {
|
|
711
|
+
const child = new EventEmitter()
|
|
712
|
+
queueMicrotask(() => child.emit('exit', 0))
|
|
713
|
+
return child as ReturnType<typeof spawn>
|
|
714
|
+
})
|
|
715
|
+
try {
|
|
716
|
+
await expect(addOfficialModule(resolver, '@open-mercato/test-package@next', true)).rejects.toThrow('differs from inspected version')
|
|
717
|
+
expect(fs.readFileSync(resolver.getModulesConfigPath(), 'utf8')).toBe(source)
|
|
718
|
+
expect(fs.existsSync(path.join(appDir, 'src/modules/test_package'))).toBe(false)
|
|
719
|
+
expect(spawn).toHaveBeenCalledWith(expect.anything(), ['add', '@open-mercato/test-package@next'], expect.anything())
|
|
720
|
+
} finally { lookup.mockRestore(); jest.mocked(spawn).mockReset() }
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
})
|
|
724
|
+
|
|
347
725
|
describe('addOfficialModule', () => {
|
|
348
726
|
let tmpDir: string
|
|
349
727
|
|
|
350
728
|
beforeEach(() => {
|
|
351
729
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'module-install-add-test-'))
|
|
352
730
|
mockedSpawn.mockReset()
|
|
731
|
+
jest.spyOn(inspection, 'inspectNpmModuleTarget').mockImplementation((packageSpec, packageName) => ({
|
|
732
|
+
packageName,
|
|
733
|
+
packageSpec,
|
|
734
|
+
moduleIds: ['test_package'],
|
|
735
|
+
locator: `npm:${packageName}`,
|
|
736
|
+
}))
|
|
353
737
|
})
|
|
354
738
|
|
|
355
739
|
afterEach(() => {
|
|
740
|
+
jest.restoreAllMocks()
|
|
356
741
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
357
742
|
})
|
|
358
743
|
|
|
@@ -468,9 +853,11 @@ describe('addOfficialModule', () => {
|
|
|
468
853
|
new Error('Module "test_package" is gated off. Enable it by setting OM_ENABLE_TEST_PACKAGE=true, then run `yarn generate`.'),
|
|
469
854
|
{ code: 'MODULE_GATED_OFF' },
|
|
470
855
|
)
|
|
856
|
+
const prepareRegistration = modulesConfig.prepareModuleRegistration
|
|
471
857
|
const registrationSpy = jest
|
|
472
|
-
.spyOn(modulesConfig, '
|
|
473
|
-
.
|
|
858
|
+
.spyOn(modulesConfig, 'prepareModuleRegistration')
|
|
859
|
+
.mockImplementationOnce(prepareRegistration)
|
|
860
|
+
.mockImplementationOnce(() => {
|
|
474
861
|
throw gatedOffError
|
|
475
862
|
})
|
|
476
863
|
|
|
@@ -490,9 +877,11 @@ describe('addOfficialModule', () => {
|
|
|
490
877
|
writeAppPackageJson(appDir)
|
|
491
878
|
const resolver = buildResolver(appDir, packageRoot, 'export const enabledModules = []\n')
|
|
492
879
|
simulateYarnAdd(appDir, '@open-mercato/test-package')
|
|
880
|
+
const prepareRegistration = modulesConfig.prepareModuleRegistration
|
|
493
881
|
const registrationSpy = jest
|
|
494
|
-
.spyOn(modulesConfig, '
|
|
495
|
-
.
|
|
882
|
+
.spyOn(modulesConfig, 'prepareModuleRegistration')
|
|
883
|
+
.mockImplementationOnce(prepareRegistration)
|
|
884
|
+
.mockImplementationOnce(() => {
|
|
496
885
|
throw new Error('Could not update modules.ts.')
|
|
497
886
|
})
|
|
498
887
|
|