@open-mercato/cli 0.6.6-develop.5654.1.ca21e35f26 → 0.6.6-develop.5672.1.11e27afad2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.6-develop.5654.1.ca21e35f26",
3
+ "version": "0.6.6-develop.5672.1.11e27afad2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -59,8 +59,8 @@
59
59
  "@mikro-orm/decorators": "^7.1.4",
60
60
  "@mikro-orm/migrations": "^7.1.4",
61
61
  "@mikro-orm/postgresql": "^7.1.4",
62
- "@open-mercato/queue": "0.6.6-develop.5654.1.ca21e35f26",
63
- "@open-mercato/shared": "0.6.6-develop.5654.1.ca21e35f26",
62
+ "@open-mercato/queue": "0.6.6-develop.5672.1.11e27afad2",
63
+ "@open-mercato/shared": "0.6.6-develop.5672.1.11e27afad2",
64
64
  "cross-spawn": "^7.0.6",
65
65
  "pg": "8.21.0",
66
66
  "semver": "^7.8.4",
@@ -70,10 +70,10 @@
70
70
  "typescript": "^6.0.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@open-mercato/shared": "0.6.6-develop.5654.1.ca21e35f26"
73
+ "@open-mercato/shared": "0.6.6-develop.5672.1.11e27afad2"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.6.6-develop.5654.1.ca21e35f26",
76
+ "@open-mercato/shared": "0.6.6-develop.5672.1.11e27afad2",
77
77
  "@types/jest": "^30.0.0",
78
78
  "jest": "^30.4.2",
79
79
  "ts-jest": "^29.4.11"
@@ -32,4 +32,27 @@ describe('parseModuleInstallArgs', () => {
32
32
  'Unsupported option: --installed',
33
33
  )
34
34
  })
35
+
36
+ it('defaults allowThirdParty to false', () => {
37
+ expect(parseModuleInstallArgs(['@open-mercato/test-package'])).toMatchObject({
38
+ allowThirdParty: false,
39
+ })
40
+ })
41
+
42
+ it('accepts --allow-third-party as a boolean flag', () => {
43
+ expect(parseModuleInstallArgs(['@scope/pkg', '--allow-third-party'])).toMatchObject({
44
+ packageSpec: '@scope/pkg',
45
+ allowThirdParty: true,
46
+ })
47
+ expect(parseModuleInstallArgs(['--allow-third-party', '@scope/pkg'])).toMatchObject({
48
+ packageSpec: '@scope/pkg',
49
+ allowThirdParty: true,
50
+ })
51
+ })
52
+
53
+ it('rejects --allow-third-party values', () => {
54
+ expect(() => parseModuleInstallArgs(['@scope/pkg', '--allow-third-party=true'])).toThrow(
55
+ '--allow-third-party does not accept a value',
56
+ )
57
+ })
35
58
  })
@@ -10,15 +10,17 @@ function buildPackageFixture(
10
10
  options?: {
11
11
  ejectable?: boolean
12
12
  extraSourceFiles?: Array<{ relativePath: string; content: string }>
13
+ packageName?: string
13
14
  },
14
15
  ): void {
15
16
  const ejectable = options?.ejectable ?? false
17
+ const packageName = options?.packageName ?? '@open-mercato/test-package'
16
18
  for (const base of ['src', 'dist']) {
17
19
  fs.mkdirSync(path.join(packageRoot, base, 'modules', moduleId), { recursive: true })
18
20
  }
19
21
  fs.writeFileSync(
20
22
  path.join(packageRoot, 'package.json'),
21
- JSON.stringify({ name: '@open-mercato/test-package', version: '0.1.0' }),
23
+ JSON.stringify({ name: packageName, version: '0.1.0' }),
22
24
  )
23
25
  fs.writeFileSync(
24
26
  path.join(packageRoot, 'src', 'modules', moduleId, 'index.ts'),
@@ -236,3 +238,66 @@ describe('enableOfficialModule', () => {
236
238
  ).rejects.toThrow('Unsupported package spec suffix')
237
239
  })
238
240
  })
241
+
242
+ describe('third-party module packages', () => {
243
+ let tmpDir: string
244
+
245
+ beforeEach(() => {
246
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'module-install-3p-test-'))
247
+ })
248
+
249
+ afterEach(() => {
250
+ fs.rmSync(tmpDir, { recursive: true, force: true })
251
+ })
252
+
253
+ it('rejects adding a non-@open-mercato package without --allow-third-party', async () => {
254
+ const packageRoot = path.join(tmpDir, 'node_modules', '@fast-white-cat', 'integration-ksef-direct')
255
+ const appDir = path.join(tmpDir, 'app')
256
+ buildPackageFixture(packageRoot, 'ksef_direct', {
257
+ packageName: '@fast-white-cat/integration-ksef-direct',
258
+ })
259
+
260
+ const resolver = buildResolver(appDir, packageRoot, 'export const enabledModules = []\n')
261
+
262
+ await expect(
263
+ addOfficialModule(resolver, '@fast-white-cat/integration-ksef-direct@0.1.0', false),
264
+ ).rejects.toThrow('Re-run with --allow-third-party')
265
+ })
266
+
267
+ it('rejects enabling a non-@open-mercato package without --allow-third-party', async () => {
268
+ const packageRoot = path.join(tmpDir, 'node_modules', '@fast-white-cat', 'integration-ksef-direct')
269
+ const appDir = path.join(tmpDir, 'app')
270
+ buildPackageFixture(packageRoot, 'ksef_direct', {
271
+ packageName: '@fast-white-cat/integration-ksef-direct',
272
+ })
273
+
274
+ const resolver = buildResolver(appDir, packageRoot, 'export const enabledModules = []\n')
275
+
276
+ await expect(
277
+ enableOfficialModule(resolver, '@fast-white-cat/integration-ksef-direct'),
278
+ ).rejects.toThrow('Re-run with --allow-third-party')
279
+ })
280
+
281
+ it('enables a non-@open-mercato package when --allow-third-party is set', async () => {
282
+ const packageRoot = path.join(tmpDir, 'node_modules', '@fast-white-cat', 'integration-ksef-direct')
283
+ const appDir = path.join(tmpDir, 'app')
284
+ buildPackageFixture(packageRoot, 'ksef_direct', {
285
+ packageName: '@fast-white-cat/integration-ksef-direct',
286
+ })
287
+
288
+ const resolver = buildResolver(appDir, packageRoot, 'export const enabledModules = []\n')
289
+
290
+ await expect(
291
+ enableOfficialModule(resolver, '@fast-white-cat/integration-ksef-direct', undefined, false, true),
292
+ ).resolves.toEqual({
293
+ moduleId: 'ksef_direct',
294
+ packageName: '@fast-white-cat/integration-ksef-direct',
295
+ from: '@fast-white-cat/integration-ksef-direct',
296
+ registrationChanged: true,
297
+ })
298
+
299
+ expect(fs.readFileSync(path.join(appDir, 'src', 'modules.ts'), 'utf8')).toContain(
300
+ "{ id: 'ksef_direct', from: '@fast-white-cat/integration-ksef-direct' }",
301
+ )
302
+ })
303
+ })
@@ -57,6 +57,33 @@ describe('module-package', () => {
57
57
  expect(modulePackage.distModuleDir).toContain(path.join('dist', 'modules', 'test_package'))
58
58
  })
59
59
 
60
+ it('validates a third-party (non-@open-mercato) module package by structure', () => {
61
+ const packageRoot = path.join(tmpDir, 'node_modules', '@fast-white-cat', 'integration-ksef-direct')
62
+ for (const base of ['src', 'dist']) {
63
+ fs.mkdirSync(path.join(packageRoot, base, 'modules', 'ksef_direct'), { recursive: true })
64
+ }
65
+ fs.writeFileSync(
66
+ path.join(packageRoot, 'package.json'),
67
+ JSON.stringify({ name: '@fast-white-cat/integration-ksef-direct', version: '0.1.0' }),
68
+ )
69
+ fs.writeFileSync(
70
+ path.join(packageRoot, 'src', 'modules', 'ksef_direct', 'index.ts'),
71
+ "export const metadata = { title: 'KSeF Direct', ejectable: false }\n",
72
+ )
73
+ fs.writeFileSync(
74
+ path.join(packageRoot, 'dist', 'modules', 'ksef_direct', 'index.js'),
75
+ 'exports.metadata = {};\n',
76
+ )
77
+
78
+ const modulePackage = readOfficialModulePackageFromRoot(
79
+ packageRoot,
80
+ '@fast-white-cat/integration-ksef-direct',
81
+ )
82
+
83
+ expect(modulePackage.packageName).toBe('@fast-white-cat/integration-ksef-direct')
84
+ expect(modulePackage.metadata.moduleId).toBe('ksef_direct')
85
+ })
86
+
60
87
  it('resolves a hoisted installed module package from a monorepo app', () => {
61
88
  const appDir = path.join(tmpDir, 'apps', 'mercato')
62
89
  const installedPackageRoot = path.join(tmpDir, 'node_modules', '@open-mercato', 'test-package')
@@ -2,12 +2,14 @@ export type ParsedModuleInstallArgs = {
2
2
  packageSpec: string | null
3
3
  eject: boolean
4
4
  moduleId: string | null
5
+ allowThirdParty: boolean
5
6
  }
6
7
 
7
8
  export function parseModuleInstallArgs(args: string[]): ParsedModuleInstallArgs {
8
9
  let packageSpec: string | null = null
9
10
  let eject = false
10
11
  let moduleId: string | null = null
12
+ let allowThirdParty = false
11
13
 
12
14
  for (let index = 0; index < args.length; index += 1) {
13
15
  const arg = args[index]
@@ -22,6 +24,15 @@ export function parseModuleInstallArgs(args: string[]): ParsedModuleInstallArgs
22
24
  throw new Error('--eject does not accept a value')
23
25
  }
24
26
 
27
+ if (arg === '--allow-third-party') {
28
+ allowThirdParty = true
29
+ continue
30
+ }
31
+
32
+ if (arg.startsWith('--allow-third-party=')) {
33
+ throw new Error('--allow-third-party does not accept a value')
34
+ }
35
+
25
36
  if (arg === '--module') {
26
37
  const next = args[index + 1]
27
38
  if (next && !next.startsWith('-')) {
@@ -46,5 +57,5 @@ export function parseModuleInstallArgs(args: string[]): ParsedModuleInstallArgs
46
57
  }
47
58
  }
48
59
 
49
- return { packageSpec, eject, moduleId }
60
+ return { packageSpec, eject, moduleId, allowThirdParty }
50
61
  }
@@ -24,7 +24,8 @@ type InstallTarget = {
24
24
  args: string[]
25
25
  }
26
26
 
27
- const SAFE_PACKAGE_SPEC_PATTERN = /^@open-mercato\/[a-z0-9][a-z0-9-]*(?:@.+)?$/i
27
+ const OFFICIAL_PACKAGE_SCOPE = '@open-mercato/'
28
+ const SAFE_PACKAGE_SPEC_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@.+)?$/i
28
29
  const SAFE_PACKAGE_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
29
30
  const SAFE_PACKAGE_FILE_LOCATOR_PATTERN = /^file:[A-Za-z0-9_./: \\-]+$/
30
31
 
@@ -133,21 +134,36 @@ async function runGeneratorsWithRegistrationNotice(
133
134
  }
134
135
  }
135
136
 
137
+ function isOfficialPackage(packageName: string): boolean {
138
+ return packageName.startsWith(OFFICIAL_PACKAGE_SCOPE)
139
+ }
140
+
136
141
  function assertPackageName(packageName: string | null): asserts packageName is string {
137
- if (!packageName || !packageName.startsWith('@open-mercato/')) {
138
- throw new Error('Only @open-mercato/* package specs are supported by "mercato module add".')
142
+ if (!packageName) {
143
+ throw new Error('Could not determine a package name from the provided spec.')
144
+ }
145
+ }
146
+
147
+ function assertThirdPartyAllowed(packageName: string, allowThirdParty: boolean): void {
148
+ if (isOfficialPackage(packageName) || allowThirdParty) {
149
+ return
139
150
  }
151
+
152
+ throw new Error(
153
+ `Package "${packageName}" is outside the ${OFFICIAL_PACKAGE_SCOPE}* scope. Re-run with --allow-third-party to install third-party module packages.`,
154
+ )
140
155
  }
141
156
 
142
- function assertSupportedPackageSpec(packageSpec: string): string {
157
+ function assertSupportedPackageSpec(packageSpec: string, allowThirdParty: boolean): string {
143
158
  const trimmed = packageSpec.trim()
144
159
 
145
160
  if (!SAFE_PACKAGE_SPEC_PATTERN.test(trimmed)) {
146
- throw new Error('Unsupported package spec. Only direct @open-mercato/* package specs are allowed.')
161
+ throw new Error('Unsupported package spec. Provide a valid npm package name with an optional tag/version or file: locator.')
147
162
  }
148
163
 
149
164
  const packageName = parsePackageNameFromSpec(trimmed)
150
165
  assertPackageName(packageName)
166
+ assertThirdPartyAllowed(packageName, allowThirdParty)
151
167
 
152
168
  if (trimmed === packageName) {
153
169
  return trimmed
@@ -240,8 +256,9 @@ export async function addOfficialModule(
240
256
  packageSpec: string,
241
257
  eject: boolean,
242
258
  moduleId?: string,
259
+ allowThirdParty = false,
243
260
  ): Promise<ModuleCommandResult> {
244
- const safePackageSpec = assertSupportedPackageSpec(packageSpec)
261
+ const safePackageSpec = assertSupportedPackageSpec(packageSpec, allowThirdParty)
245
262
  const packageName = parsePackageNameFromSpec(safePackageSpec)
246
263
  assertPackageName(packageName)
247
264
 
@@ -256,10 +273,9 @@ export async function enableOfficialModule(
256
273
  packageName: string,
257
274
  moduleId?: string,
258
275
  eject = false,
276
+ allowThirdParty = false,
259
277
  ): Promise<ModuleCommandResult> {
260
- if (!packageName.startsWith('@open-mercato/')) {
261
- throw new Error('Only @open-mercato/* packages can be enabled with "mercato module enable".')
262
- }
278
+ assertThirdPartyAllowed(packageName, allowThirdParty)
263
279
 
264
280
  const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId)
265
281
  return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject)
@@ -270,8 +270,8 @@ export function readOfficialModulePackageFromRoot(
270
270
 
271
271
  const packageJson = readPackageJson(packageJsonPath)
272
272
  const packageName = packageJson.name
273
- if (!packageName || !packageName.startsWith('@open-mercato/')) {
274
- throw new Error(`Package at ${packageRoot} is not under the @open-mercato scope.`)
273
+ if (!packageName) {
274
+ throw new Error(`Package manifest at ${packageJsonPath} is missing the "name" field.`)
275
275
  }
276
276
 
277
277
  if (expectedPackageName && packageName !== expectedPackageName) {
package/src/mercato.ts CHANGED
@@ -1282,8 +1282,8 @@ export async function run(argv = process.argv) {
1282
1282
 
1283
1283
  if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
1284
1284
  console.log('Usage: yarn mercato module <add|enable|eject> ...')
1285
- console.log(' yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]')
1286
- console.log(' yarn mercato module enable <packageName> [--module <moduleId>] [--eject]')
1285
+ console.log(' yarn mercato module add <packageSpec> [--module <moduleId>] [--eject] [--allow-third-party]')
1286
+ console.log(' yarn mercato module enable <packageName> [--module <moduleId>] [--eject] [--allow-third-party]')
1287
1287
  console.log(' yarn mercato module eject <moduleId>')
1288
1288
  return 0
1289
1289
  }
@@ -1291,14 +1291,14 @@ export async function run(argv = process.argv) {
1291
1291
  if (subcommand === 'add') {
1292
1292
  const { createResolver } = await import('./lib/resolver')
1293
1293
  const { addOfficialModule } = await import('./lib/module-install')
1294
- const { packageSpec, eject, moduleId } = parseModuleInstallArgs(commandArgs)
1294
+ const { packageSpec, eject, moduleId, allowThirdParty } = parseModuleInstallArgs(commandArgs)
1295
1295
 
1296
1296
  if (!packageSpec) {
1297
- console.error('Usage: yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]')
1297
+ console.error('Usage: yarn mercato module add <packageSpec> [--module <moduleId>] [--eject] [--allow-third-party]')
1298
1298
  return 1
1299
1299
  }
1300
1300
 
1301
- const result = await addOfficialModule(createResolver(), packageSpec, eject, moduleId ?? undefined)
1301
+ const result = await addOfficialModule(createResolver(), packageSpec, eject, moduleId ?? undefined, allowThirdParty)
1302
1302
  console.log(`\n✅ Module "${result.moduleId}" enabled from ${result.from}.\n`)
1303
1303
  console.log('Next steps:')
1304
1304
  console.log(' 1. Review generated files if needed: .mercato/generated/')
@@ -1309,14 +1309,14 @@ export async function run(argv = process.argv) {
1309
1309
  if (subcommand === 'enable') {
1310
1310
  const packageName = commandArgs.find((arg) => !arg.startsWith('-'))
1311
1311
  if (!packageName) {
1312
- console.error('Usage: yarn mercato module enable <packageName> [--module <moduleId>] [--eject]')
1312
+ console.error('Usage: yarn mercato module enable <packageName> [--module <moduleId>] [--eject] [--allow-third-party]')
1313
1313
  return 1
1314
1314
  }
1315
1315
 
1316
1316
  const { createResolver } = await import('./lib/resolver')
1317
1317
  const { enableOfficialModule } = await import('./lib/module-install')
1318
- const { moduleId, eject } = parseModuleInstallArgs(commandArgs)
1319
- const result = await enableOfficialModule(createResolver(), packageName, moduleId ?? undefined, eject)
1318
+ const { moduleId, eject, allowThirdParty } = parseModuleInstallArgs(commandArgs)
1319
+ const result = await enableOfficialModule(createResolver(), packageName, moduleId ?? undefined, eject, allowThirdParty)
1320
1320
  console.log(`\n✅ Module "${result.moduleId}" enabled from ${result.from}.\n`)
1321
1321
  console.log('Next steps:')
1322
1322
  console.log(' 1. Review generated files if needed: .mercato/generated/')