@open-mercato/cli 0.6.6-develop.5654.1.ca21e35f26 → 0.6.6-develop.5675.1.d585628349

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.
@@ -147,7 +147,7 @@ When the user asks to **create a new application** or a **new module**, do not i
147
147
  | **Backend admin pages** | Auto-discovered files under `src/modules/<id>/backend/**`, paired `page.meta.ts` with `requireAuth` + `requireFeatures` + `pageGroup`/`pageGroupKey`/`pageOrder` | `.ai/skills/om-backend-ui-design/SKILL.md`, `.ai/skills/om-module-scaffold/references/navigation-patterns.md`; <https://docs.open-mercato.dev/framework/modules/routes-and-pages> |
148
148
  | **Frontend public pages** | Auto-discovered files under `src/modules/<id>/frontend/**`. Customer portal pages live under `frontend/[orgSlug]/portal/<path>/page.tsx` with `requireCustomerAuth` / `requireCustomerFeatures` in `page.meta.ts` | `.ai/guides/ui.md` → Portal Extension; <https://docs.open-mercato.dev/framework/modules/routes-and-pages> |
149
149
  | **API routes** | Files under `src/modules/<id>/api/**/route.ts` exporting handlers + `metadata` (per-method `requireAuth` / `requireFeatures`) + `openApi`. NEVER write a top-level `export const requireAuth` — the registry no longer recognises it | `.ai/guides/core.md` → API Routes; <https://docs.open-mercato.dev/framework/api/api-development-guide> |
150
- | **CRUD APIs (factory)** | `makeCrudRoute({ entity, entityId, operations, schema, indexer: { entityType } })` from `@open-mercato/shared/lib/crud/factory`. Always set `indexer` so query-index coverage stays correct. Custom (non-`makeCrudRoute`) write routes MUST call `validateCrudMutationGuard` before the mutation and `runCrudMutationGuardAfterSuccess` after success | `.ai/skills/om-module-scaffold/SKILL.md` → Create API Routes; <https://docs.open-mercato.dev/framework/api/crud-factory> |
150
+ | **CRUD APIs (factory)** | `makeCrudRoute({ metadata, orm, list, create, update, del, indexer })` from `@open-mercato/shared/lib/crud/factory` (`orm` is required; `list`/`create`/`update`/`del` are the per-operation configs). Always set `indexer` so query-index coverage stays correct. Custom (non-`makeCrudRoute`) write routes MUST call `validateCrudMutationGuard` before the mutation and `runCrudMutationGuardAfterSuccess` after success | `.ai/skills/om-module-scaffold/SKILL.md` → Create API Routes; <https://docs.open-mercato.dev/framework/api/crud-factory> |
151
151
  | **CRUD forms in admin** | `<CrudForm entityId apiPath mode fields />` from `@open-mercato/ui/backend/CrudForm`; helpers `createCrud` / `updateCrud` / `deleteCrud` from `@open-mercato/ui/backend/utils/crud`; `createCrudFormError` from `@open-mercato/ui/backend/utils/serverErrors`. Never raw `<form>`, never raw `fetch` | `.ai/skills/om-backend-ui-design/SKILL.md`; <https://docs.open-mercato.dev/framework/admin-ui/crud-form> |
152
152
  | **DataTables in admin** | `<DataTable entityId apiPath title columns />` from `@open-mercato/ui/backend/DataTable`. Keep `entityId` and `extensionTableId` stable so widget injection (columns, row actions, bulk actions, filters, toolbar) keeps working | `.ai/skills/om-backend-ui-design/SKILL.md`; <https://docs.open-mercato.dev/framework/admin-ui/data-grids> |
153
153
  | **Authorization (RBAC)** | Declare features in `<module>/acl.ts`, grant them in `<module>/setup.ts` `defaultRoleFeatures`, gate pages and routes with `requireFeatures` in `metadata` / `page.meta.ts`. NEVER use `requireRoles` (role names mutate). Run `yarn mercato auth sync-role-acls` after adding new features | `.ai/guides/core.md` → Access Control; <https://docs.open-mercato.dev/framework/rbac/overview> |
@@ -81,51 +81,15 @@ Omit empty severity sections.
81
81
 
82
82
  ## Quick Rule Reference
83
83
 
84
- ### Architecture
85
-
86
- - **NO direct ORM relationships between modules** use FK IDs, fetch separately
87
- - **Always filter by `organization_id`** for tenant-scoped entities
88
- - **Use DI (Awilix)** to inject services never `new` directly
89
- - **NO direct module-to-module calls** for side effects use events
90
- - **Cross-module data**: use extension entities + `data/extensions.ts`
91
-
92
- ### Security
93
-
94
- - **Validate all inputs with zod** in `data/validators.ts`
95
- - **Use `findWithDecryption`** instead of raw `em.find`/`em.findOne`
96
- - **Hash passwords with bcryptjs (cost >= 10)** — never log credentials
97
- - **Every endpoint MUST declare auth guards** (`requireAuth`, `requireRoles`, `requireFeatures`)
98
-
99
- ### Data Integrity
100
-
101
- - **Migration files and snapshots must match entity intent** — prefer `yarn db:generate`; scoped manual SQL is allowed only to avoid unrelated generated churn and must include `.snapshot-open-mercato.json`
102
- - **Validate migration scope** — autogenerated doesn't mean correct
103
- - **Workers/subscribers MUST be idempotent**
104
- - **Commands MUST be undoable** — include before/after snapshots
105
-
106
- ### Naming
107
-
108
- - Modules: **plural, snake_case** (folders and `id`)
109
- - JS/TS identifiers: **camelCase**
110
- - Database: **snake_case**, table names plural
111
- - Standard columns: `id`, `created_at`, `updated_at`, `deleted_at`, `is_active`, `organization_id`
112
-
113
- ### UI & HTTP
114
-
115
- - Forms: `CrudForm` — never custom
116
- - Tables: `DataTable` — never manual markup
117
- - Notifications: `flash()` — never `alert()` or custom toast
118
- - API calls: `apiCall`/`apiCallOrThrow` — never raw `fetch`
119
- - Dialogs: `Cmd/Ctrl+Enter` (submit), `Escape` (cancel)
120
- - `pageSize` MUST be <= 100
121
-
122
- ### Code Quality
123
-
124
- - No `any` types — use zod + `z.infer`
125
- - No empty `catch` blocks
126
- - No one-letter variable names
127
- - Boolean parsing: use `parseBooleanToken`/`parseBooleanWithDefault`
128
- - Don't add docstrings/comments to code you didn't change
84
+ `AGENTS.md` is the single source of truth for these rules — read it there instead of relying on a copy, so this skill never drifts from the canon. Map the dimension you are reviewing to the owning section:
85
+
86
+ - **Architecture** (FK-only cross-module links, `organization_id` scoping, DI/Awilix, events for side effects, extension entities) → `AGENTS.md` → Architecture Rules + Mandatory Module Mechanisms
87
+ - **Security** (zod validation, `findWithDecryption` over raw `em.find`/`em.findOne`, bcryptjs ≥ 10, never log credentials) → `AGENTS.md` → Data & Security
88
+ - **Authorization (RBAC)** gate pages and routes with `requireFeatures`; **NEVER `requireRoles`** (role names mutate and can be spoofed) → `AGENTS.md` → Data & Security (RBAC) + Access Control
89
+ - **Data Integrity** (migration files + snapshots match intent, idempotent workers/subscribers, undoable commands) → `AGENTS.md` → CRITICAL Rules + Data & Security
90
+ - **Naming & standard columns** `AGENTS.md` → Naming Conventions / Conventions
91
+ - **UI & HTTP** (`CrudForm`, `DataTable`, `flash()`, `apiCall` over raw `fetch`, dialog `Cmd/Ctrl+Enter`/`Escape`, `pageSize` ≤ 100) → `AGENTS.md` → UI & HTTP + Mandatory Module Mechanisms
92
+ - **Code Quality** (no `any`, no empty `catch`, no one-letter names, `parseBooleanToken`/`parseBooleanWithDefault`, don't comment code you didn't change) → `AGENTS.md` → Code Quality
129
93
 
130
94
  ## Review Heuristics
131
95
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  - [ ] All inputs validated with zod in `data/validators.ts`
14
14
  - [ ] No `any` types
15
- - [ ] Auth guards on all endpoints (`requireAuth`, `requireRoles`, `requireFeatures`)
15
+ - [ ] Auth guards on all endpoints (`requireAuth` + `requireFeatures`; never `requireRoles` — role names mutate)
16
16
  - [ ] Passwords hashed with bcryptjs (cost >= 10)
17
17
  - [ ] No credentials logged or in error messages
18
18
  - [ ] `findWithDecryption` used instead of raw `em.find`/`em.findOne`
@@ -1,5 +1,7 @@
1
1
  # Naming Conventions Quick Reference
2
2
 
3
+ > **Source of truth:** `AGENTS.md` (Naming Conventions / Conventions, Architecture Rules, Multi-tenant scoping) owns these rules. This file is an example-oriented cheat sheet for scaffolding — when it disagrees with `AGENTS.md`, `AGENTS.md` wins.
4
+
3
5
  ## Module & Files
4
6
 
5
7
  | Element | Convention | Example |
@@ -25,17 +27,7 @@
25
27
 
26
28
  ## Standard Entity Columns
27
29
 
28
- Every tenant-scoped entity MUST include:
29
-
30
- ```typescript
31
- id: string // UUID v4 primary key
32
- organization_id: string // Tenant organization (indexed)
33
- tenant_id: string // Tenant ID (indexed)
34
- is_active: boolean // Soft active flag (default: true)
35
- created_at: Date // Creation timestamp
36
- updated_at: Date // Last update timestamp (auto-updated)
37
- deleted_at: Date | null // Soft delete timestamp
38
- ```
30
+ Every tenant-scoped entity MUST carry the standard columns (`id`, `organization_id`, `tenant_id`, `is_active`, `created_at`, `updated_at`, `deleted_at`) with `organization_id`/`tenant_id` indexed. See `AGENTS.md` → Conventions / Multi-tenant scoping for the authoritative list and types.
39
31
 
40
32
  ## API Routes
41
33
 
@@ -55,6 +47,4 @@ All HTTP methods live in a **single** `api/<entities>/route.ts` that exports nam
55
47
 
56
48
  ## Cross-Module References
57
49
 
58
- - Store as `uuid` FK field: `customer_id`, `order_id`
59
- - Never use `@ManyToOne` / `@OneToMany` decorators across modules
60
- - Fetch related data via separate API calls or enrichers
50
+ Store cross-module links as `uuid` FK fields (`customer_id`, `order_id`) and fetch related data via separate API calls or enrichers — never `@ManyToOne` / `@OneToMany` decorators across modules. See `AGENTS.md` → Architecture Rules for the authoritative rule.
@@ -130,7 +130,7 @@ When in doubt about whether an action mutates state, treat it as mutating and as
130
130
 
131
131
  2. **Do pages export `metadata` with `requireAuth`?**
132
132
  ```typescript
133
- export const metadata = { requireAuth: true, features: ['<module_id>.view'] }
133
+ export const metadata = { requireAuth: true, requireFeatures: ['<module_id>.view'] }
134
134
  ```
135
135
  Proposed fix: Add metadata export.
136
136
 
@@ -209,12 +209,16 @@ When in doubt about whether an action mutates state, treat it as mutating and as
209
209
  **Checklist**:
210
210
 
211
211
  1. **Is the file in the correct path?**
212
- `src/modules/<module_id>/api/<method>/<route-path>.ts`
213
- Method folders: `get/`, `post/`, `put/`, `delete/`
212
+ `src/modules/<module_id>/api/<resource>/route.ts` — all HTTP methods live in a single `route.ts`. Use path-based folders (`<resource>/`, `<resource>/[id]/`), never per-method `get/`/`post/`/`put/`/`delete/` folders.
214
213
 
215
- 2. **Does it export a default handler?**
214
+ 2. **Does it export per-method handlers plus `metadata`?**
216
215
  ```typescript
217
- export default handler
216
+ export const metadata = {
217
+ GET: { requireAuth: true, requireFeatures: ['<module_id>.view'] },
218
+ POST: { requireAuth: true, requireFeatures: ['<module_id>.manage'] },
219
+ }
220
+ export async function GET(req: Request) { /* ... */ }
221
+ export async function POST(req: Request) { /* ... */ }
218
222
  ```
219
223
 
220
224
  3. **Does it export `openApi`?**
@@ -290,7 +294,7 @@ export const metadata = { icon: <Trophy className="size-4" /> }
290
294
  1. **Check browser network tab** — look for the POST/PUT request and response
291
295
  2. **Is the zod schema matching the form fields?** Mismatched field names cause silent failures
292
296
  3. **Are required fields filled?** Check form validation
293
- 4. **Does the API route handle the HTTP method?** Check `api/post/` or `api/put/` exists
297
+ 4. **Does the API route handle the HTTP method?** Check `api/<resource>/route.ts` exports a `POST`/`PUT` handler (and lists it in `metadata`)
294
298
 
295
299
  ---
296
300
 
@@ -2,6 +2,7 @@ function parseModuleInstallArgs(args) {
2
2
  let packageSpec = null;
3
3
  let eject = false;
4
4
  let moduleId = null;
5
+ let allowThirdParty = false;
5
6
  for (let index = 0; index < args.length; index += 1) {
6
7
  const arg = args[index];
7
8
  if (!arg) continue;
@@ -12,6 +13,13 @@ function parseModuleInstallArgs(args) {
12
13
  if (arg.startsWith("--eject=")) {
13
14
  throw new Error("--eject does not accept a value");
14
15
  }
16
+ if (arg === "--allow-third-party") {
17
+ allowThirdParty = true;
18
+ continue;
19
+ }
20
+ if (arg.startsWith("--allow-third-party=")) {
21
+ throw new Error("--allow-third-party does not accept a value");
22
+ }
15
23
  if (arg === "--module") {
16
24
  const next = args[index + 1];
17
25
  if (next && !next.startsWith("-")) {
@@ -32,7 +40,7 @@ function parseModuleInstallArgs(args) {
32
40
  packageSpec = arg;
33
41
  }
34
42
  }
35
- return { packageSpec, eject, moduleId };
43
+ return { packageSpec, eject, moduleId, allowThirdParty };
36
44
  }
37
45
  export {
38
46
  parseModuleInstallArgs
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/module-install-args.ts"],
4
- "sourcesContent": ["export type ParsedModuleInstallArgs = {\n packageSpec: string | null\n eject: boolean\n moduleId: string | null\n}\n\nexport function parseModuleInstallArgs(args: string[]): ParsedModuleInstallArgs {\n let packageSpec: string | null = null\n let eject = false\n let moduleId: string | null = null\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index]\n if (!arg) continue\n\n if (arg === '--eject') {\n eject = true\n continue\n }\n\n if (arg.startsWith('--eject=')) {\n throw new Error('--eject does not accept a value')\n }\n\n if (arg === '--module') {\n const next = args[index + 1]\n if (next && !next.startsWith('-')) {\n moduleId = next\n index += 1\n continue\n }\n throw new Error(`--module requires a moduleId value`)\n }\n\n if (arg.startsWith('--module=')) {\n moduleId = arg.slice('--module='.length) || null\n continue\n }\n\n if (arg.startsWith('-')) {\n throw new Error(`Unsupported option: ${arg}`)\n }\n\n if (!arg.startsWith('-') && !packageSpec) {\n packageSpec = arg\n }\n }\n\n return { packageSpec, eject, moduleId }\n}\n"],
5
- "mappings": "AAMO,SAAS,uBAAuB,MAAyC;AAC9E,MAAI,cAA6B;AACjC,MAAI,QAAQ;AACZ,MAAI,WAA0B;AAE9B,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,IAAK;AAEV,QAAI,QAAQ,WAAW;AACrB,cAAQ;AACR;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,UAAU,GAAG;AAC9B,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,QAAI,QAAQ,YAAY;AACtB,YAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,UAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;AACjC,mBAAW;AACX,iBAAS;AACT;AAAA,MACF;AACA,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,QAAI,IAAI,WAAW,WAAW,GAAG;AAC/B,iBAAW,IAAI,MAAM,YAAY,MAAM,KAAK;AAC5C;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,GAAG,GAAG;AACvB,YAAM,IAAI,MAAM,uBAAuB,GAAG,EAAE;AAAA,IAC9C;AAEA,QAAI,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,aAAa;AACxC,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,SAAS;AACxC;",
4
+ "sourcesContent": ["export type ParsedModuleInstallArgs = {\n packageSpec: string | null\n eject: boolean\n moduleId: string | null\n allowThirdParty: boolean\n}\n\nexport function parseModuleInstallArgs(args: string[]): ParsedModuleInstallArgs {\n let packageSpec: string | null = null\n let eject = false\n let moduleId: string | null = null\n let allowThirdParty = false\n\n for (let index = 0; index < args.length; index += 1) {\n const arg = args[index]\n if (!arg) continue\n\n if (arg === '--eject') {\n eject = true\n continue\n }\n\n if (arg.startsWith('--eject=')) {\n throw new Error('--eject does not accept a value')\n }\n\n if (arg === '--allow-third-party') {\n allowThirdParty = true\n continue\n }\n\n if (arg.startsWith('--allow-third-party=')) {\n throw new Error('--allow-third-party does not accept a value')\n }\n\n if (arg === '--module') {\n const next = args[index + 1]\n if (next && !next.startsWith('-')) {\n moduleId = next\n index += 1\n continue\n }\n throw new Error(`--module requires a moduleId value`)\n }\n\n if (arg.startsWith('--module=')) {\n moduleId = arg.slice('--module='.length) || null\n continue\n }\n\n if (arg.startsWith('-')) {\n throw new Error(`Unsupported option: ${arg}`)\n }\n\n if (!arg.startsWith('-') && !packageSpec) {\n packageSpec = arg\n }\n }\n\n return { packageSpec, eject, moduleId, allowThirdParty }\n}\n"],
5
+ "mappings": "AAOO,SAAS,uBAAuB,MAAyC;AAC9E,MAAI,cAA6B;AACjC,MAAI,QAAQ;AACZ,MAAI,WAA0B;AAC9B,MAAI,kBAAkB;AAEtB,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,IAAK;AAEV,QAAI,QAAQ,WAAW;AACrB,cAAQ;AACR;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,UAAU,GAAG;AAC9B,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,QAAI,QAAQ,uBAAuB;AACjC,wBAAkB;AAClB;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,sBAAsB,GAAG;AAC1C,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,QAAI,QAAQ,YAAY;AACtB,YAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,UAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;AACjC,mBAAW;AACX,iBAAS;AACT;AAAA,MACF;AACA,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,QAAI,IAAI,WAAW,WAAW,GAAG;AAC/B,iBAAW,IAAI,MAAM,YAAY,MAAM,KAAK;AAC5C;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,GAAG,GAAG;AACvB,YAAM,IAAI,MAAM,uBAAuB,GAAG,EAAE;AAAA,IAC9C;AAEA,QAAI,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,aAAa;AACxC,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,UAAU,gBAAgB;AACzD;",
6
6
  "names": []
7
7
  }
@@ -9,7 +9,8 @@ import {
9
9
  } from "./module-package.js";
10
10
  import { ensureModuleRegistration } from "./modules-config.js";
11
11
  import { resolveSpawnCommand } from "./spawn.js";
12
- const SAFE_PACKAGE_SPEC_PATTERN = /^@open-mercato\/[a-z0-9][a-z0-9-]*(?:@.+)?$/i;
12
+ const OFFICIAL_PACKAGE_SCOPE = "@open-mercato/";
13
+ const SAFE_PACKAGE_SPEC_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@.+)?$/i;
13
14
  const SAFE_PACKAGE_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
14
15
  const SAFE_PACKAGE_FILE_LOCATOR_PATTERN = /^file:[A-Za-z0-9_./: \\-]+$/;
15
16
  function resolveYarnBinary() {
@@ -91,18 +92,30 @@ async function runGeneratorsWithRegistrationNotice(resolver, moduleId) {
91
92
  );
92
93
  }
93
94
  }
95
+ function isOfficialPackage(packageName) {
96
+ return packageName.startsWith(OFFICIAL_PACKAGE_SCOPE);
97
+ }
94
98
  function assertPackageName(packageName) {
95
- if (!packageName || !packageName.startsWith("@open-mercato/")) {
96
- throw new Error('Only @open-mercato/* package specs are supported by "mercato module add".');
99
+ if (!packageName) {
100
+ throw new Error("Could not determine a package name from the provided spec.");
97
101
  }
98
102
  }
99
- function assertSupportedPackageSpec(packageSpec) {
103
+ function assertThirdPartyAllowed(packageName, allowThirdParty) {
104
+ if (isOfficialPackage(packageName) || allowThirdParty) {
105
+ return;
106
+ }
107
+ throw new Error(
108
+ `Package "${packageName}" is outside the ${OFFICIAL_PACKAGE_SCOPE}* scope. Re-run with --allow-third-party to install third-party module packages.`
109
+ );
110
+ }
111
+ function assertSupportedPackageSpec(packageSpec, allowThirdParty) {
100
112
  const trimmed = packageSpec.trim();
101
113
  if (!SAFE_PACKAGE_SPEC_PATTERN.test(trimmed)) {
102
- throw new Error("Unsupported package spec. Only direct @open-mercato/* package specs are allowed.");
114
+ throw new Error("Unsupported package spec. Provide a valid npm package name with an optional tag/version or file: locator.");
103
115
  }
104
116
  const packageName = parsePackageNameFromSpec(trimmed);
105
117
  assertPackageName(packageName);
118
+ assertThirdPartyAllowed(packageName, allowThirdParty);
106
119
  if (trimmed === packageName) {
107
120
  return trimmed;
108
121
  }
@@ -159,18 +172,16 @@ async function registerResolvedOfficialModule(resolver, modulePackage, packageNa
159
172
  registrationChanged: registration.changed
160
173
  };
161
174
  }
162
- async function addOfficialModule(resolver, packageSpec, eject, moduleId) {
163
- const safePackageSpec = assertSupportedPackageSpec(packageSpec);
175
+ async function addOfficialModule(resolver, packageSpec, eject, moduleId, allowThirdParty = false) {
176
+ const safePackageSpec = assertSupportedPackageSpec(packageSpec, allowThirdParty);
164
177
  const packageName = parsePackageNameFromSpec(safePackageSpec);
165
178
  assertPackageName(packageName);
166
179
  await installPackageSpec(resolver, safePackageSpec);
167
180
  const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId);
168
181
  return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject);
169
182
  }
170
- async function enableOfficialModule(resolver, packageName, moduleId, eject = false) {
171
- if (!packageName.startsWith("@open-mercato/")) {
172
- throw new Error('Only @open-mercato/* packages can be enabled with "mercato module enable".');
173
- }
183
+ async function enableOfficialModule(resolver, packageName, moduleId, eject = false, allowThirdParty = false) {
184
+ assertThirdPartyAllowed(packageName, allowThirdParty);
174
185
  const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId);
175
186
  return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject);
176
187
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/module-install.ts"],
4
- "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport spawn from 'cross-spawn'\nimport { copyDirRecursive, rewriteCrossModuleImports } from './eject'\nimport {\n parsePackageNameFromSpec,\n resolveInstalledOfficialModulePackage,\n validateEjectBoundaries,\n type ValidatedOfficialModulePackage,\n} from './module-package'\nimport { ensureModuleRegistration } from './modules-config'\nimport type { PackageResolver } from './resolver'\nimport { resolveSpawnCommand } from './spawn'\n\ntype ModuleCommandResult = {\n moduleId: string\n packageName: string\n from: string\n registrationChanged: boolean\n}\n\ntype InstallTarget = {\n cwd: string\n args: string[]\n}\n\nconst SAFE_PACKAGE_SPEC_PATTERN = /^@open-mercato\\/[a-z0-9][a-z0-9-]*(?:@.+)?$/i\nconst SAFE_PACKAGE_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/\nconst SAFE_PACKAGE_FILE_LOCATOR_PATTERN = /^file:[A-Za-z0-9_./: \\\\-]+$/\n\nfunction resolveYarnBinary(): string {\n return process.platform === 'win32' ? 'yarn.cmd' : 'yarn'\n}\n\nfunction readAppPackageName(appDir: string): string {\n const packageJsonPath = path.join(appDir, 'package.json')\n if (!fs.existsSync(packageJsonPath)) {\n throw new Error(`App package.json not found at ${packageJsonPath}.`)\n }\n\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { name?: string }\n if (!packageJson.name) {\n throw new Error(`App package.json at ${packageJsonPath} is missing the \"name\" field.`)\n }\n\n return packageJson.name\n}\n\nfunction resolveInstallTarget(resolver: PackageResolver): InstallTarget {\n if (!resolver.isMonorepo()) {\n return {\n cwd: resolver.getAppDir(),\n args: ['add'],\n }\n }\n\n return {\n cwd: resolver.getRootDir(),\n args: ['workspace', readAppPackageName(resolver.getAppDir()), 'add'],\n }\n}\n\nfunction runCommand(\n command: string,\n args: string[],\n cwd: string,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const resolvedSpawn = resolveSpawnCommand(command, args)\n const child = spawn(resolvedSpawn.command, resolvedSpawn.args, {\n cwd,\n env: process.env,\n stdio: 'inherit',\n ...resolvedSpawn.spawnOptions,\n })\n\n child.on('error', reject)\n child.on('exit', (code) => {\n if (code === 0) {\n resolve()\n return\n }\n\n reject(new Error(`Command failed: ${command} ${args.join(' ')} (exit ${code ?? 'unknown'}).`))\n })\n })\n}\n\nasync function installPackageSpec(\n resolver: PackageResolver,\n packageSpec: string,\n): Promise<void> {\n const target = resolveInstallTarget(resolver)\n await runCommand(resolveYarnBinary(), [...target.args, packageSpec], target.cwd)\n}\n\nexport async function runModuleGenerators(\n resolver: PackageResolver,\n quiet = true,\n): Promise<void> {\n const {\n generateEntityIds,\n generateModuleDi,\n generateModuleEntities,\n generateModulePackageSources,\n generateModuleRegistry,\n generateModuleRegistryApp,\n generateModuleRegistryCli,\n generateOpenApi,\n } = await import('./generators')\n\n await generateEntityIds({ resolver, quiet })\n await generateModuleRegistry({ resolver, quiet })\n await generateModuleRegistryApp({ resolver, quiet })\n await generateModuleRegistryCli({ resolver, quiet })\n await generateModuleEntities({ resolver, quiet })\n await generateModuleDi({ resolver, quiet })\n await generateModulePackageSources({ resolver, quiet })\n await generateOpenApi({ resolver, quiet })\n}\n\nasync function runGeneratorsWithRegistrationNotice(\n resolver: PackageResolver,\n moduleId: string,\n): Promise<void> {\n try {\n await runModuleGenerators(resolver)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `Module \"${moduleId}\" is enabled, but generated artifacts are stale because generation failed. Rerun \"yarn mercato generate\" after fixing the underlying error. ${message}`,\n )\n }\n}\n\nfunction assertPackageName(packageName: string | null): asserts packageName is string {\n if (!packageName || !packageName.startsWith('@open-mercato/')) {\n throw new Error('Only @open-mercato/* package specs are supported by \"mercato module add\".')\n }\n}\n\nfunction assertSupportedPackageSpec(packageSpec: string): string {\n const trimmed = packageSpec.trim()\n\n if (!SAFE_PACKAGE_SPEC_PATTERN.test(trimmed)) {\n throw new Error('Unsupported package spec. Only direct @open-mercato/* package specs are allowed.')\n }\n\n const packageName = parsePackageNameFromSpec(trimmed)\n assertPackageName(packageName)\n\n if (trimmed === packageName) {\n return trimmed\n }\n\n const suffix = trimmed.slice(packageName.length + 1)\n if (SAFE_PACKAGE_TAG_PATTERN.test(suffix) || SAFE_PACKAGE_FILE_LOCATOR_PATTERN.test(suffix)) {\n return trimmed\n }\n\n throw new Error('Unsupported package spec suffix. Use a tag/version token or a file: locator.')\n}\n\nfunction validateBeforeRegistration(\n modulePackage: ValidatedOfficialModulePackage,\n resolver: PackageResolver,\n eject: boolean,\n): string {\n const appModuleDir = path.join(resolver.getAppDir(), 'src', 'modules', modulePackage.metadata.moduleId)\n\n if (eject && fs.existsSync(appModuleDir)) {\n throw new Error(`Destination directory already exists: ${appModuleDir}. Remove it first or rerun without --eject.`)\n }\n\n return appModuleDir\n}\n\nfunction prepareRegistrationSource(\n modulePackage: ValidatedOfficialModulePackage,\n resolver: PackageResolver,\n packageName: string,\n eject: boolean,\n): string {\n const appModuleDir = validateBeforeRegistration(modulePackage, resolver, eject)\n\n if (!eject) {\n return packageName\n }\n\n if (!modulePackage.metadata.ejectable) {\n throw new Error(`Package \"${packageName}\" is not ejectable. --eject requires open-mercato.ejectable === true.`)\n }\n\n validateEjectBoundaries(modulePackage)\n copyDirRecursive(modulePackage.sourceModuleDir, appModuleDir)\n rewriteCrossModuleImports(\n modulePackage.sourceModuleDir,\n appModuleDir,\n modulePackage.metadata.moduleId,\n packageName,\n )\n\n return '@app'\n}\n\nasync function registerResolvedOfficialModule(\n resolver: PackageResolver,\n modulePackage: ValidatedOfficialModulePackage,\n packageName: string,\n eject: boolean,\n): Promise<ModuleCommandResult> {\n const from = prepareRegistrationSource(modulePackage, resolver, packageName, eject)\n\n const registration = ensureModuleRegistration(\n resolver.getModulesConfigPath(),\n {\n id: modulePackage.metadata.moduleId,\n from,\n },\n )\n\n if (!registration.changed) {\n throw new Error(\n `Module \"${modulePackage.metadata.moduleId}\" from \"${from}\" is already enabled in modules.ts.`,\n )\n }\n\n await runGeneratorsWithRegistrationNotice(resolver, modulePackage.metadata.moduleId)\n\n return {\n moduleId: modulePackage.metadata.moduleId,\n packageName,\n from,\n registrationChanged: registration.changed,\n }\n}\n\nexport async function addOfficialModule(\n resolver: PackageResolver,\n packageSpec: string,\n eject: boolean,\n moduleId?: string,\n): Promise<ModuleCommandResult> {\n const safePackageSpec = assertSupportedPackageSpec(packageSpec)\n const packageName = parsePackageNameFromSpec(safePackageSpec)\n assertPackageName(packageName)\n\n await installPackageSpec(resolver, safePackageSpec)\n\n const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId)\n return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject)\n}\n\nexport async function enableOfficialModule(\n resolver: PackageResolver,\n packageName: string,\n moduleId?: string,\n eject = false,\n): Promise<ModuleCommandResult> {\n if (!packageName.startsWith('@open-mercato/')) {\n throw new Error('Only @open-mercato/* packages can be enabled with \"mercato module enable\".')\n }\n\n const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId)\n return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport spawn from 'cross-spawn'\nimport { copyDirRecursive, rewriteCrossModuleImports } from './eject'\nimport {\n parsePackageNameFromSpec,\n resolveInstalledOfficialModulePackage,\n validateEjectBoundaries,\n type ValidatedOfficialModulePackage,\n} from './module-package'\nimport { ensureModuleRegistration } from './modules-config'\nimport type { PackageResolver } from './resolver'\nimport { resolveSpawnCommand } from './spawn'\n\ntype ModuleCommandResult = {\n moduleId: string\n packageName: string\n from: string\n registrationChanged: boolean\n}\n\ntype InstallTarget = {\n cwd: string\n args: string[]\n}\n\nconst OFFICIAL_PACKAGE_SCOPE = '@open-mercato/'\nconst SAFE_PACKAGE_SPEC_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*(?:@.+)?$/i\nconst SAFE_PACKAGE_TAG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/\nconst SAFE_PACKAGE_FILE_LOCATOR_PATTERN = /^file:[A-Za-z0-9_./: \\\\-]+$/\n\nfunction resolveYarnBinary(): string {\n return process.platform === 'win32' ? 'yarn.cmd' : 'yarn'\n}\n\nfunction readAppPackageName(appDir: string): string {\n const packageJsonPath = path.join(appDir, 'package.json')\n if (!fs.existsSync(packageJsonPath)) {\n throw new Error(`App package.json not found at ${packageJsonPath}.`)\n }\n\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { name?: string }\n if (!packageJson.name) {\n throw new Error(`App package.json at ${packageJsonPath} is missing the \"name\" field.`)\n }\n\n return packageJson.name\n}\n\nfunction resolveInstallTarget(resolver: PackageResolver): InstallTarget {\n if (!resolver.isMonorepo()) {\n return {\n cwd: resolver.getAppDir(),\n args: ['add'],\n }\n }\n\n return {\n cwd: resolver.getRootDir(),\n args: ['workspace', readAppPackageName(resolver.getAppDir()), 'add'],\n }\n}\n\nfunction runCommand(\n command: string,\n args: string[],\n cwd: string,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const resolvedSpawn = resolveSpawnCommand(command, args)\n const child = spawn(resolvedSpawn.command, resolvedSpawn.args, {\n cwd,\n env: process.env,\n stdio: 'inherit',\n ...resolvedSpawn.spawnOptions,\n })\n\n child.on('error', reject)\n child.on('exit', (code) => {\n if (code === 0) {\n resolve()\n return\n }\n\n reject(new Error(`Command failed: ${command} ${args.join(' ')} (exit ${code ?? 'unknown'}).`))\n })\n })\n}\n\nasync function installPackageSpec(\n resolver: PackageResolver,\n packageSpec: string,\n): Promise<void> {\n const target = resolveInstallTarget(resolver)\n await runCommand(resolveYarnBinary(), [...target.args, packageSpec], target.cwd)\n}\n\nexport async function runModuleGenerators(\n resolver: PackageResolver,\n quiet = true,\n): Promise<void> {\n const {\n generateEntityIds,\n generateModuleDi,\n generateModuleEntities,\n generateModulePackageSources,\n generateModuleRegistry,\n generateModuleRegistryApp,\n generateModuleRegistryCli,\n generateOpenApi,\n } = await import('./generators')\n\n await generateEntityIds({ resolver, quiet })\n await generateModuleRegistry({ resolver, quiet })\n await generateModuleRegistryApp({ resolver, quiet })\n await generateModuleRegistryCli({ resolver, quiet })\n await generateModuleEntities({ resolver, quiet })\n await generateModuleDi({ resolver, quiet })\n await generateModulePackageSources({ resolver, quiet })\n await generateOpenApi({ resolver, quiet })\n}\n\nasync function runGeneratorsWithRegistrationNotice(\n resolver: PackageResolver,\n moduleId: string,\n): Promise<void> {\n try {\n await runModuleGenerators(resolver)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `Module \"${moduleId}\" is enabled, but generated artifacts are stale because generation failed. Rerun \"yarn mercato generate\" after fixing the underlying error. ${message}`,\n )\n }\n}\n\nfunction isOfficialPackage(packageName: string): boolean {\n return packageName.startsWith(OFFICIAL_PACKAGE_SCOPE)\n}\n\nfunction assertPackageName(packageName: string | null): asserts packageName is string {\n if (!packageName) {\n throw new Error('Could not determine a package name from the provided spec.')\n }\n}\n\nfunction assertThirdPartyAllowed(packageName: string, allowThirdParty: boolean): void {\n if (isOfficialPackage(packageName) || allowThirdParty) {\n return\n }\n\n throw new Error(\n `Package \"${packageName}\" is outside the ${OFFICIAL_PACKAGE_SCOPE}* scope. Re-run with --allow-third-party to install third-party module packages.`,\n )\n}\n\nfunction assertSupportedPackageSpec(packageSpec: string, allowThirdParty: boolean): string {\n const trimmed = packageSpec.trim()\n\n if (!SAFE_PACKAGE_SPEC_PATTERN.test(trimmed)) {\n throw new Error('Unsupported package spec. Provide a valid npm package name with an optional tag/version or file: locator.')\n }\n\n const packageName = parsePackageNameFromSpec(trimmed)\n assertPackageName(packageName)\n assertThirdPartyAllowed(packageName, allowThirdParty)\n\n if (trimmed === packageName) {\n return trimmed\n }\n\n const suffix = trimmed.slice(packageName.length + 1)\n if (SAFE_PACKAGE_TAG_PATTERN.test(suffix) || SAFE_PACKAGE_FILE_LOCATOR_PATTERN.test(suffix)) {\n return trimmed\n }\n\n throw new Error('Unsupported package spec suffix. Use a tag/version token or a file: locator.')\n}\n\nfunction validateBeforeRegistration(\n modulePackage: ValidatedOfficialModulePackage,\n resolver: PackageResolver,\n eject: boolean,\n): string {\n const appModuleDir = path.join(resolver.getAppDir(), 'src', 'modules', modulePackage.metadata.moduleId)\n\n if (eject && fs.existsSync(appModuleDir)) {\n throw new Error(`Destination directory already exists: ${appModuleDir}. Remove it first or rerun without --eject.`)\n }\n\n return appModuleDir\n}\n\nfunction prepareRegistrationSource(\n modulePackage: ValidatedOfficialModulePackage,\n resolver: PackageResolver,\n packageName: string,\n eject: boolean,\n): string {\n const appModuleDir = validateBeforeRegistration(modulePackage, resolver, eject)\n\n if (!eject) {\n return packageName\n }\n\n if (!modulePackage.metadata.ejectable) {\n throw new Error(`Package \"${packageName}\" is not ejectable. --eject requires open-mercato.ejectable === true.`)\n }\n\n validateEjectBoundaries(modulePackage)\n copyDirRecursive(modulePackage.sourceModuleDir, appModuleDir)\n rewriteCrossModuleImports(\n modulePackage.sourceModuleDir,\n appModuleDir,\n modulePackage.metadata.moduleId,\n packageName,\n )\n\n return '@app'\n}\n\nasync function registerResolvedOfficialModule(\n resolver: PackageResolver,\n modulePackage: ValidatedOfficialModulePackage,\n packageName: string,\n eject: boolean,\n): Promise<ModuleCommandResult> {\n const from = prepareRegistrationSource(modulePackage, resolver, packageName, eject)\n\n const registration = ensureModuleRegistration(\n resolver.getModulesConfigPath(),\n {\n id: modulePackage.metadata.moduleId,\n from,\n },\n )\n\n if (!registration.changed) {\n throw new Error(\n `Module \"${modulePackage.metadata.moduleId}\" from \"${from}\" is already enabled in modules.ts.`,\n )\n }\n\n await runGeneratorsWithRegistrationNotice(resolver, modulePackage.metadata.moduleId)\n\n return {\n moduleId: modulePackage.metadata.moduleId,\n packageName,\n from,\n registrationChanged: registration.changed,\n }\n}\n\nexport async function addOfficialModule(\n resolver: PackageResolver,\n packageSpec: string,\n eject: boolean,\n moduleId?: string,\n allowThirdParty = false,\n): Promise<ModuleCommandResult> {\n const safePackageSpec = assertSupportedPackageSpec(packageSpec, allowThirdParty)\n const packageName = parsePackageNameFromSpec(safePackageSpec)\n assertPackageName(packageName)\n\n await installPackageSpec(resolver, safePackageSpec)\n\n const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId)\n return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject)\n}\n\nexport async function enableOfficialModule(\n resolver: PackageResolver,\n packageName: string,\n moduleId?: string,\n eject = false,\n allowThirdParty = false,\n): Promise<ModuleCommandResult> {\n assertThirdPartyAllowed(packageName, allowThirdParty)\n\n const modulePackage = resolveInstalledOfficialModulePackage(resolver, packageName, moduleId)\n return registerResolvedOfficialModule(resolver, modulePackage, packageName, eject)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -168,8 +168,8 @@ function readOfficialModulePackageFromRoot(packageRoot, expectedPackageName, tar
168
168
  }
169
169
  const packageJson = readPackageJson(packageJsonPath);
170
170
  const packageName = packageJson.name;
171
- if (!packageName || !packageName.startsWith("@open-mercato/")) {
172
- throw new Error(`Package at ${packageRoot} is not under the @open-mercato scope.`);
171
+ if (!packageName) {
172
+ throw new Error(`Package manifest at ${packageJsonPath} is missing the "name" field.`);
173
173
  }
174
174
  if (expectedPackageName && packageName !== expectedPackageName) {
175
175
  throw new Error(`Resolved package "${packageName}" does not match requested package "${expectedPackageName}".`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/module-package.ts"],
4
- "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { createRequire } from 'node:module'\nimport type { PackageResolver } from './resolver'\n\ntype PackageJsonRecord = {\n name?: string\n version?: string\n dependencies?: Record<string, string>\n peerDependencies?: Record<string, string>\n}\n\nexport type ModulePackageMetadata = {\n moduleId: string\n ejectable: boolean\n}\n\nexport type ModuleInfoSnapshot = {\n title?: string\n description?: string\n}\n\nexport type ValidatedOfficialModulePackage = {\n packageName: string\n packageRoot: string\n packageJson: PackageJsonRecord\n metadata: ModulePackageMetadata\n moduleInfo: ModuleInfoSnapshot\n sourceModuleDir: string\n distModuleDir: string\n}\n\ntype DiscoveredModule = {\n moduleId: string\n ejectable: boolean\n}\n\nconst SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']\nconst SKIP_DIRS = new Set(['__tests__', '__mocks__', 'node_modules'])\nconst MODULE_ID_PATTERN = /^[a-z0-9]+(?:_[a-z0-9]+)*$/\nconst requireFromCli = createRequire(path.join(process.cwd(), 'package.json'))\n\nfunction shouldSkipEntryName(name: string): boolean {\n return SKIP_DIRS.has(name) || name === '.DS_Store' || name.startsWith('._')\n}\n\nfunction readPackageJson(packageJsonPath: string): PackageJsonRecord {\n try {\n const raw = fs.readFileSync(packageJsonPath, 'utf8')\n return JSON.parse(raw) as PackageJsonRecord\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to read package manifest at ${packageJsonPath}: ${message}`)\n }\n}\n\nfunction parseModuleInfo(indexPath: string): { title?: string; description?: string; ejectable?: boolean } {\n if (!fs.existsSync(indexPath)) return {}\n\n const source = fs.readFileSync(indexPath, 'utf8')\n const result: { title?: string; description?: string; ejectable?: boolean } = {}\n\n const titleMatch = source.match(/\\btitle\\s*:\\s*['\"]([^'\"]+)['\"]/)\n if (titleMatch) result.title = titleMatch[1]\n\n const descriptionMatch = source.match(/\\bdescription\\s*:\\s*['\"]([^'\"]+)['\"]/)\n if (descriptionMatch) result.description = descriptionMatch[1]\n\n const ejectableMatch = source.match(/\\bejectable\\s*:\\s*(true|false)/)\n if (ejectableMatch) result.ejectable = ejectableMatch[1] === 'true'\n\n return result\n}\n\nexport function discoverModulesInPackage(packageRoot: string): DiscoveredModule[] {\n const srcModulesDir = path.join(packageRoot, 'src', 'modules')\n const distModulesDir = path.join(packageRoot, 'dist', 'modules')\n\n const enumerationDir = fs.existsSync(srcModulesDir)\n ? srcModulesDir\n : fs.existsSync(distModulesDir)\n ? distModulesDir\n : null\n\n if (!enumerationDir) return []\n\n const modules: DiscoveredModule[] = []\n\n for (const entry of fs.readdirSync(enumerationDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || shouldSkipEntryName(entry.name) || !MODULE_ID_PATTERN.test(entry.name)) continue\n\n const moduleId = entry.name\n const srcIndexPath = path.join(packageRoot, 'src', 'modules', moduleId, 'index.ts')\n const distIndexPath = path.join(packageRoot, 'dist', 'modules', moduleId, 'index.js')\n const indexPath = fs.existsSync(srcIndexPath) ? srcIndexPath : distIndexPath\n\n const info = parseModuleInfo(indexPath)\n modules.push({ moduleId, ejectable: info.ejectable ?? false })\n }\n\n return modules\n}\n\nfunction resolveRelativeImportTarget(sourceFile: string, importPath: string): string | null {\n if (!importPath.startsWith('.')) return null\n\n const basePath = path.resolve(path.dirname(sourceFile), importPath)\n const candidates = [basePath]\n\n for (const ext of SOURCE_FILE_EXTENSIONS) {\n candidates.push(`${basePath}${ext}`)\n candidates.push(path.join(basePath, `index${ext}`))\n }\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\nfunction collectSourceFiles(dir: string): string[] {\n const files: string[] = []\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (shouldSkipEntryName(entry.name)) continue\n\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n files.push(...collectSourceFiles(fullPath))\n continue\n }\n\n const ext = path.extname(entry.name)\n if (!SOURCE_FILE_EXTENSIONS.includes(ext)) continue\n files.push(fullPath)\n }\n\n return files\n}\n\nfunction collectBoundaryViolations(moduleDir: string): string[] {\n const modulesRoot = path.resolve(moduleDir, '..')\n const modulePrefix = `${moduleDir}${path.sep}`\n const modulesPrefix = `${modulesRoot}${path.sep}`\n const files = collectSourceFiles(moduleDir)\n const violations = new Set<string>()\n\n for (const filePath of files) {\n const content = fs.readFileSync(filePath, 'utf8')\n const matches = [\n ...content.matchAll(/\\bfrom\\s*['\"]([^'\"]+)['\"]/g),\n ...content.matchAll(/\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g),\n ]\n\n for (const match of matches) {\n const specifier = match[1]\n if (!specifier) continue\n const resolvedTarget = resolveRelativeImportTarget(filePath, specifier)\n if (!resolvedTarget) continue\n if (resolvedTarget.startsWith(modulePrefix)) continue\n if (resolvedTarget.startsWith(modulesPrefix)) continue\n\n const relativeSource = path.relative(moduleDir, filePath).split(path.sep).join('/')\n const relativeTarget = path.relative(path.dirname(filePath), resolvedTarget).split(path.sep).join('/')\n violations.add(`${relativeSource} -> ${relativeTarget}`)\n }\n }\n\n return Array.from(violations).sort((left, right) => left.localeCompare(right))\n}\n\nfunction findResolvedPackageRoot(\n resolvedPath: string,\n packageName: string,\n): string | null {\n let currentPath = fs.statSync(resolvedPath).isDirectory()\n ? resolvedPath\n : path.dirname(resolvedPath)\n\n while (currentPath !== path.dirname(currentPath)) {\n const packageJsonPath = path.join(currentPath, 'package.json')\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = readPackageJson(packageJsonPath)\n if (packageJson.name === packageName) {\n return currentPath\n }\n }\n currentPath = path.dirname(currentPath)\n }\n\n return null\n}\n\nfunction resolveInstalledPackageRootWithRequire(packageName: string, appDir: string): string | null {\n const specifiers = [`${packageName}/package.json`, packageName]\n\n for (const specifier of specifiers) {\n try {\n const resolvedPath = requireFromCli.resolve(specifier, {\n paths: [appDir],\n })\n const packageRoot =\n specifier === packageName\n ? findResolvedPackageRoot(resolvedPath, packageName)\n : path.dirname(resolvedPath)\n\n if (packageRoot) {\n return packageRoot\n }\n } catch {\n continue\n }\n }\n\n return null\n}\n\nexport function parsePackageNameFromSpec(packageSpec: string): string | null {\n const trimmed = packageSpec.trim()\n if (!trimmed) return null\n\n if (trimmed.startsWith('@')) {\n const slashIndex = trimmed.indexOf('/')\n if (slashIndex < 0) return null\n const versionSeparator = trimmed.indexOf('@', slashIndex + 1)\n return versionSeparator < 0 ? trimmed : trimmed.slice(0, versionSeparator)\n }\n\n const versionSeparator = trimmed.indexOf('@')\n return versionSeparator < 0 ? trimmed : trimmed.slice(0, versionSeparator)\n}\n\nexport function resolveInstalledPackageRoot(\n resolver: PackageResolver,\n packageName: string,\n): string {\n const resolvedWithRequire = resolveInstalledPackageRootWithRequire(packageName, resolver.getAppDir())\n if (resolvedWithRequire) {\n return resolvedWithRequire\n }\n\n const fallback = resolver.getPackageRoot(packageName)\n if (fs.existsSync(path.join(fallback, 'package.json'))) {\n return fallback\n }\n\n // In monorepo mode, getPackageRoot resolves to packages/<name> (workspace convention),\n // but externally installed packages land in root node_modules \u2014 check there too.\n const nodeModulesFallback = path.join(resolver.getRootDir(), 'node_modules', packageName)\n if (fs.existsSync(path.join(nodeModulesFallback, 'package.json'))) {\n return nodeModulesFallback\n }\n\n throw new Error(`Package \"${packageName}\" is not installed in ${resolver.getAppDir()}.`)\n}\n\nexport function readOfficialModulePackageFromRoot(\n packageRoot: string,\n expectedPackageName?: string,\n targetModuleId?: string,\n): ValidatedOfficialModulePackage {\n const packageJsonPath = path.join(packageRoot, 'package.json')\n if (!fs.existsSync(packageJsonPath)) {\n throw new Error(`Package manifest not found at ${packageJsonPath}.`)\n }\n\n const packageJson = readPackageJson(packageJsonPath)\n const packageName = packageJson.name\n if (!packageName || !packageName.startsWith('@open-mercato/')) {\n throw new Error(`Package at ${packageRoot} is not under the @open-mercato scope.`)\n }\n\n if (expectedPackageName && packageName !== expectedPackageName) {\n throw new Error(`Resolved package \"${packageName}\" does not match requested package \"${expectedPackageName}\".`)\n }\n\n const discoveredModules = discoverModulesInPackage(packageRoot)\n\n if (discoveredModules.length === 0) {\n throw new Error(`Package \"${packageName}\" has no modules in src/modules/ or dist/modules/.`)\n }\n\n let selected: DiscoveredModule\n\n if (targetModuleId) {\n const found = discoveredModules.find((m) => m.moduleId === targetModuleId)\n if (!found) {\n const available = discoveredModules.map((m) => m.moduleId).join(', ')\n throw new Error(\n `Package \"${packageName}\" does not contain module \"${targetModuleId}\". Available: ${available}`,\n )\n }\n selected = found\n } else {\n if (discoveredModules.length > 1) {\n const available = discoveredModules.map((m) => m.moduleId).join(', ')\n throw new Error(\n `Package \"${packageName}\" contains multiple modules (${available}). Specify one with --module <moduleId>.`,\n )\n }\n selected = discoveredModules[0]\n }\n\n const { moduleId } = selected\n const sourceModuleDir = path.join(packageRoot, 'src', 'modules', moduleId)\n const distModuleDir = path.join(packageRoot, 'dist', 'modules', moduleId)\n\n if (!fs.existsSync(sourceModuleDir)) {\n throw new Error(`Package \"${packageName}\" is missing src/modules/${moduleId}.`)\n }\n\n if (!fs.existsSync(distModuleDir)) {\n throw new Error(`Package \"${packageName}\" is missing dist/modules/${moduleId}.`)\n }\n\n const info = parseModuleInfo(path.join(sourceModuleDir, 'index.ts'))\n\n return {\n packageName,\n packageRoot,\n packageJson,\n metadata: { moduleId, ejectable: selected.ejectable },\n moduleInfo: { title: info.title, description: info.description },\n sourceModuleDir,\n distModuleDir,\n }\n}\n\nexport function resolveInstalledOfficialModulePackage(\n resolver: PackageResolver,\n packageName: string,\n moduleId?: string,\n): ValidatedOfficialModulePackage {\n const packageRoot = resolveInstalledPackageRoot(resolver, packageName)\n return readOfficialModulePackageFromRoot(packageRoot, packageName, moduleId)\n}\n\nexport function validateEjectBoundaries(modulePackage: ValidatedOfficialModulePackage): void {\n const violations = collectBoundaryViolations(modulePackage.sourceModuleDir)\n if (violations.length === 0) {\n return\n }\n\n throw new Error(\n [\n `Package \"${modulePackage.packageName}\" cannot be added with --eject because it imports files outside src/modules/${modulePackage.metadata.moduleId}.`,\n 'Invalid imports:',\n ...violations.map((violation) => `- ${violation}`),\n ].join('\\n'),\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { createRequire } from 'node:module'\nimport type { PackageResolver } from './resolver'\n\ntype PackageJsonRecord = {\n name?: string\n version?: string\n dependencies?: Record<string, string>\n peerDependencies?: Record<string, string>\n}\n\nexport type ModulePackageMetadata = {\n moduleId: string\n ejectable: boolean\n}\n\nexport type ModuleInfoSnapshot = {\n title?: string\n description?: string\n}\n\nexport type ValidatedOfficialModulePackage = {\n packageName: string\n packageRoot: string\n packageJson: PackageJsonRecord\n metadata: ModulePackageMetadata\n moduleInfo: ModuleInfoSnapshot\n sourceModuleDir: string\n distModuleDir: string\n}\n\ntype DiscoveredModule = {\n moduleId: string\n ejectable: boolean\n}\n\nconst SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']\nconst SKIP_DIRS = new Set(['__tests__', '__mocks__', 'node_modules'])\nconst MODULE_ID_PATTERN = /^[a-z0-9]+(?:_[a-z0-9]+)*$/\nconst requireFromCli = createRequire(path.join(process.cwd(), 'package.json'))\n\nfunction shouldSkipEntryName(name: string): boolean {\n return SKIP_DIRS.has(name) || name === '.DS_Store' || name.startsWith('._')\n}\n\nfunction readPackageJson(packageJsonPath: string): PackageJsonRecord {\n try {\n const raw = fs.readFileSync(packageJsonPath, 'utf8')\n return JSON.parse(raw) as PackageJsonRecord\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to read package manifest at ${packageJsonPath}: ${message}`)\n }\n}\n\nfunction parseModuleInfo(indexPath: string): { title?: string; description?: string; ejectable?: boolean } {\n if (!fs.existsSync(indexPath)) return {}\n\n const source = fs.readFileSync(indexPath, 'utf8')\n const result: { title?: string; description?: string; ejectable?: boolean } = {}\n\n const titleMatch = source.match(/\\btitle\\s*:\\s*['\"]([^'\"]+)['\"]/)\n if (titleMatch) result.title = titleMatch[1]\n\n const descriptionMatch = source.match(/\\bdescription\\s*:\\s*['\"]([^'\"]+)['\"]/)\n if (descriptionMatch) result.description = descriptionMatch[1]\n\n const ejectableMatch = source.match(/\\bejectable\\s*:\\s*(true|false)/)\n if (ejectableMatch) result.ejectable = ejectableMatch[1] === 'true'\n\n return result\n}\n\nexport function discoverModulesInPackage(packageRoot: string): DiscoveredModule[] {\n const srcModulesDir = path.join(packageRoot, 'src', 'modules')\n const distModulesDir = path.join(packageRoot, 'dist', 'modules')\n\n const enumerationDir = fs.existsSync(srcModulesDir)\n ? srcModulesDir\n : fs.existsSync(distModulesDir)\n ? distModulesDir\n : null\n\n if (!enumerationDir) return []\n\n const modules: DiscoveredModule[] = []\n\n for (const entry of fs.readdirSync(enumerationDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || shouldSkipEntryName(entry.name) || !MODULE_ID_PATTERN.test(entry.name)) continue\n\n const moduleId = entry.name\n const srcIndexPath = path.join(packageRoot, 'src', 'modules', moduleId, 'index.ts')\n const distIndexPath = path.join(packageRoot, 'dist', 'modules', moduleId, 'index.js')\n const indexPath = fs.existsSync(srcIndexPath) ? srcIndexPath : distIndexPath\n\n const info = parseModuleInfo(indexPath)\n modules.push({ moduleId, ejectable: info.ejectable ?? false })\n }\n\n return modules\n}\n\nfunction resolveRelativeImportTarget(sourceFile: string, importPath: string): string | null {\n if (!importPath.startsWith('.')) return null\n\n const basePath = path.resolve(path.dirname(sourceFile), importPath)\n const candidates = [basePath]\n\n for (const ext of SOURCE_FILE_EXTENSIONS) {\n candidates.push(`${basePath}${ext}`)\n candidates.push(path.join(basePath, `index${ext}`))\n }\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\nfunction collectSourceFiles(dir: string): string[] {\n const files: string[] = []\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (shouldSkipEntryName(entry.name)) continue\n\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n files.push(...collectSourceFiles(fullPath))\n continue\n }\n\n const ext = path.extname(entry.name)\n if (!SOURCE_FILE_EXTENSIONS.includes(ext)) continue\n files.push(fullPath)\n }\n\n return files\n}\n\nfunction collectBoundaryViolations(moduleDir: string): string[] {\n const modulesRoot = path.resolve(moduleDir, '..')\n const modulePrefix = `${moduleDir}${path.sep}`\n const modulesPrefix = `${modulesRoot}${path.sep}`\n const files = collectSourceFiles(moduleDir)\n const violations = new Set<string>()\n\n for (const filePath of files) {\n const content = fs.readFileSync(filePath, 'utf8')\n const matches = [\n ...content.matchAll(/\\bfrom\\s*['\"]([^'\"]+)['\"]/g),\n ...content.matchAll(/\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g),\n ]\n\n for (const match of matches) {\n const specifier = match[1]\n if (!specifier) continue\n const resolvedTarget = resolveRelativeImportTarget(filePath, specifier)\n if (!resolvedTarget) continue\n if (resolvedTarget.startsWith(modulePrefix)) continue\n if (resolvedTarget.startsWith(modulesPrefix)) continue\n\n const relativeSource = path.relative(moduleDir, filePath).split(path.sep).join('/')\n const relativeTarget = path.relative(path.dirname(filePath), resolvedTarget).split(path.sep).join('/')\n violations.add(`${relativeSource} -> ${relativeTarget}`)\n }\n }\n\n return Array.from(violations).sort((left, right) => left.localeCompare(right))\n}\n\nfunction findResolvedPackageRoot(\n resolvedPath: string,\n packageName: string,\n): string | null {\n let currentPath = fs.statSync(resolvedPath).isDirectory()\n ? resolvedPath\n : path.dirname(resolvedPath)\n\n while (currentPath !== path.dirname(currentPath)) {\n const packageJsonPath = path.join(currentPath, 'package.json')\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = readPackageJson(packageJsonPath)\n if (packageJson.name === packageName) {\n return currentPath\n }\n }\n currentPath = path.dirname(currentPath)\n }\n\n return null\n}\n\nfunction resolveInstalledPackageRootWithRequire(packageName: string, appDir: string): string | null {\n const specifiers = [`${packageName}/package.json`, packageName]\n\n for (const specifier of specifiers) {\n try {\n const resolvedPath = requireFromCli.resolve(specifier, {\n paths: [appDir],\n })\n const packageRoot =\n specifier === packageName\n ? findResolvedPackageRoot(resolvedPath, packageName)\n : path.dirname(resolvedPath)\n\n if (packageRoot) {\n return packageRoot\n }\n } catch {\n continue\n }\n }\n\n return null\n}\n\nexport function parsePackageNameFromSpec(packageSpec: string): string | null {\n const trimmed = packageSpec.trim()\n if (!trimmed) return null\n\n if (trimmed.startsWith('@')) {\n const slashIndex = trimmed.indexOf('/')\n if (slashIndex < 0) return null\n const versionSeparator = trimmed.indexOf('@', slashIndex + 1)\n return versionSeparator < 0 ? trimmed : trimmed.slice(0, versionSeparator)\n }\n\n const versionSeparator = trimmed.indexOf('@')\n return versionSeparator < 0 ? trimmed : trimmed.slice(0, versionSeparator)\n}\n\nexport function resolveInstalledPackageRoot(\n resolver: PackageResolver,\n packageName: string,\n): string {\n const resolvedWithRequire = resolveInstalledPackageRootWithRequire(packageName, resolver.getAppDir())\n if (resolvedWithRequire) {\n return resolvedWithRequire\n }\n\n const fallback = resolver.getPackageRoot(packageName)\n if (fs.existsSync(path.join(fallback, 'package.json'))) {\n return fallback\n }\n\n // In monorepo mode, getPackageRoot resolves to packages/<name> (workspace convention),\n // but externally installed packages land in root node_modules \u2014 check there too.\n const nodeModulesFallback = path.join(resolver.getRootDir(), 'node_modules', packageName)\n if (fs.existsSync(path.join(nodeModulesFallback, 'package.json'))) {\n return nodeModulesFallback\n }\n\n throw new Error(`Package \"${packageName}\" is not installed in ${resolver.getAppDir()}.`)\n}\n\nexport function readOfficialModulePackageFromRoot(\n packageRoot: string,\n expectedPackageName?: string,\n targetModuleId?: string,\n): ValidatedOfficialModulePackage {\n const packageJsonPath = path.join(packageRoot, 'package.json')\n if (!fs.existsSync(packageJsonPath)) {\n throw new Error(`Package manifest not found at ${packageJsonPath}.`)\n }\n\n const packageJson = readPackageJson(packageJsonPath)\n const packageName = packageJson.name\n if (!packageName) {\n throw new Error(`Package manifest at ${packageJsonPath} is missing the \"name\" field.`)\n }\n\n if (expectedPackageName && packageName !== expectedPackageName) {\n throw new Error(`Resolved package \"${packageName}\" does not match requested package \"${expectedPackageName}\".`)\n }\n\n const discoveredModules = discoverModulesInPackage(packageRoot)\n\n if (discoveredModules.length === 0) {\n throw new Error(`Package \"${packageName}\" has no modules in src/modules/ or dist/modules/.`)\n }\n\n let selected: DiscoveredModule\n\n if (targetModuleId) {\n const found = discoveredModules.find((m) => m.moduleId === targetModuleId)\n if (!found) {\n const available = discoveredModules.map((m) => m.moduleId).join(', ')\n throw new Error(\n `Package \"${packageName}\" does not contain module \"${targetModuleId}\". Available: ${available}`,\n )\n }\n selected = found\n } else {\n if (discoveredModules.length > 1) {\n const available = discoveredModules.map((m) => m.moduleId).join(', ')\n throw new Error(\n `Package \"${packageName}\" contains multiple modules (${available}). Specify one with --module <moduleId>.`,\n )\n }\n selected = discoveredModules[0]\n }\n\n const { moduleId } = selected\n const sourceModuleDir = path.join(packageRoot, 'src', 'modules', moduleId)\n const distModuleDir = path.join(packageRoot, 'dist', 'modules', moduleId)\n\n if (!fs.existsSync(sourceModuleDir)) {\n throw new Error(`Package \"${packageName}\" is missing src/modules/${moduleId}.`)\n }\n\n if (!fs.existsSync(distModuleDir)) {\n throw new Error(`Package \"${packageName}\" is missing dist/modules/${moduleId}.`)\n }\n\n const info = parseModuleInfo(path.join(sourceModuleDir, 'index.ts'))\n\n return {\n packageName,\n packageRoot,\n packageJson,\n metadata: { moduleId, ejectable: selected.ejectable },\n moduleInfo: { title: info.title, description: info.description },\n sourceModuleDir,\n distModuleDir,\n }\n}\n\nexport function resolveInstalledOfficialModulePackage(\n resolver: PackageResolver,\n packageName: string,\n moduleId?: string,\n): ValidatedOfficialModulePackage {\n const packageRoot = resolveInstalledPackageRoot(resolver, packageName)\n return readOfficialModulePackageFromRoot(packageRoot, packageName, moduleId)\n}\n\nexport function validateEjectBoundaries(modulePackage: ValidatedOfficialModulePackage): void {\n const violations = collectBoundaryViolations(modulePackage.sourceModuleDir)\n if (violations.length === 0) {\n return\n }\n\n throw new Error(\n [\n `Package \"${modulePackage.packageName}\" cannot be added with --eject because it imports files outside src/modules/${modulePackage.metadata.moduleId}.`,\n 'Invalid imports:',\n ...violations.map((violation) => `- ${violation}`),\n ].join('\\n'),\n )\n}\n"],
5
+ "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAmC9B,MAAM,yBAAyB,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AAC5E,MAAM,YAAY,oBAAI,IAAI,CAAC,aAAa,aAAa,cAAc,CAAC;AACpE,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB,cAAc,KAAK,KAAK,QAAQ,IAAI,GAAG,cAAc,CAAC;AAE7E,SAAS,oBAAoB,MAAuB;AAClD,SAAO,UAAU,IAAI,IAAI,KAAK,SAAS,eAAe,KAAK,WAAW,IAAI;AAC5E;AAEA,SAAS,gBAAgB,iBAA4C;AACnE,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,iBAAiB,MAAM;AACnD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,UAAM,IAAI,MAAM,sCAAsC,eAAe,KAAK,OAAO,EAAE;AAAA,EACrF;AACF;AAEA,SAAS,gBAAgB,WAAkF;AACzG,MAAI,CAAC,GAAG,WAAW,SAAS,EAAG,QAAO,CAAC;AAEvC,QAAM,SAAS,GAAG,aAAa,WAAW,MAAM;AAChD,QAAM,SAAwE,CAAC;AAE/E,QAAM,aAAa,OAAO,MAAM,gCAAgC;AAChE,MAAI,WAAY,QAAO,QAAQ,WAAW,CAAC;AAE3C,QAAM,mBAAmB,OAAO,MAAM,sCAAsC;AAC5E,MAAI,iBAAkB,QAAO,cAAc,iBAAiB,CAAC;AAE7D,QAAM,iBAAiB,OAAO,MAAM,gCAAgC;AACpE,MAAI,eAAgB,QAAO,YAAY,eAAe,CAAC,MAAM;AAE7D,SAAO;AACT;AAEO,SAAS,yBAAyB,aAAyC;AAChF,QAAM,gBAAgB,KAAK,KAAK,aAAa,OAAO,SAAS;AAC7D,QAAM,iBAAiB,KAAK,KAAK,aAAa,QAAQ,SAAS;AAE/D,QAAM,iBAAiB,GAAG,WAAW,aAAa,IAC9C,gBACA,GAAG,WAAW,cAAc,IAC1B,iBACA;AAEN,MAAI,CAAC,eAAgB,QAAO,CAAC;AAE7B,QAAM,UAA8B,CAAC;AAErC,aAAW,SAAS,GAAG,YAAY,gBAAgB,EAAE,eAAe,KAAK,CAAC,GAAG;AAC3E,QAAI,CAAC,MAAM,YAAY,KAAK,oBAAoB,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,MAAM,IAAI,EAAG;AAEpG,UAAM,WAAW,MAAM;AACvB,UAAM,eAAe,KAAK,KAAK,aAAa,OAAO,WAAW,UAAU,UAAU;AAClF,UAAM,gBAAgB,KAAK,KAAK,aAAa,QAAQ,WAAW,UAAU,UAAU;AACpF,UAAM,YAAY,GAAG,WAAW,YAAY,IAAI,eAAe;AAE/D,UAAM,OAAO,gBAAgB,SAAS;AACtC,YAAQ,KAAK,EAAE,UAAU,WAAW,KAAK,aAAa,MAAM,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;AAEA,SAAS,4BAA4B,YAAoB,YAAmC;AAC1F,MAAI,CAAC,WAAW,WAAW,GAAG,EAAG,QAAO;AAExC,QAAM,WAAW,KAAK,QAAQ,KAAK,QAAQ,UAAU,GAAG,UAAU;AAClE,QAAM,aAAa,CAAC,QAAQ;AAE5B,aAAW,OAAO,wBAAwB;AACxC,eAAW,KAAK,GAAG,QAAQ,GAAG,GAAG,EAAE;AACnC,eAAW,KAAK,KAAK,KAAK,UAAU,QAAQ,GAAG,EAAE,CAAC;AAAA,EACpD;AAEA,aAAW,aAAa,YAAY;AAClC,QAAI,GAAG,WAAW,SAAS,GAAG;AAC5B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,mBAAmB,KAAuB;AACjD,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAU,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAE3D,aAAW,SAAS,SAAS;AAC3B,QAAI,oBAAoB,MAAM,IAAI,EAAG;AAErC,UAAM,WAAW,KAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,KAAK,GAAG,mBAAmB,QAAQ,CAAC;AAC1C;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,QAAQ,MAAM,IAAI;AACnC,QAAI,CAAC,uBAAuB,SAAS,GAAG,EAAG;AAC3C,UAAM,KAAK,QAAQ;AAAA,EACrB;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,WAA6B;AAC9D,QAAM,cAAc,KAAK,QAAQ,WAAW,IAAI;AAChD,QAAM,eAAe,GAAG,SAAS,GAAG,KAAK,GAAG;AAC5C,QAAM,gBAAgB,GAAG,WAAW,GAAG,KAAK,GAAG;AAC/C,QAAM,QAAQ,mBAAmB,SAAS;AAC1C,QAAM,aAAa,oBAAI,IAAY;AAEnC,aAAW,YAAY,OAAO;AAC5B,UAAM,UAAU,GAAG,aAAa,UAAU,MAAM;AAChD,UAAM,UAAU;AAAA,MACd,GAAG,QAAQ,SAAS,4BAA4B;AAAA,MAChD,GAAG,QAAQ,SAAS,wCAAwC;AAAA,IAC9D;AAEA,eAAW,SAAS,SAAS;AAC3B,YAAM,YAAY,MAAM,CAAC;AACzB,UAAI,CAAC,UAAW;AAChB,YAAM,iBAAiB,4BAA4B,UAAU,SAAS;AACtE,UAAI,CAAC,eAAgB;AACrB,UAAI,eAAe,WAAW,YAAY,EAAG;AAC7C,UAAI,eAAe,WAAW,aAAa,EAAG;AAE9C,YAAM,iBAAiB,KAAK,SAAS,WAAW,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AAClF,YAAM,iBAAiB,KAAK,SAAS,KAAK,QAAQ,QAAQ,GAAG,cAAc,EAAE,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AACrG,iBAAW,IAAI,GAAG,cAAc,OAAO,cAAc,EAAE;AAAA,IACzD;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC;AAC/E;AAEA,SAAS,wBACP,cACA,aACe;AACf,MAAI,cAAc,GAAG,SAAS,YAAY,EAAE,YAAY,IACpD,eACA,KAAK,QAAQ,YAAY;AAE7B,SAAO,gBAAgB,KAAK,QAAQ,WAAW,GAAG;AAChD,UAAM,kBAAkB,KAAK,KAAK,aAAa,cAAc;AAC7D,QAAI,GAAG,WAAW,eAAe,GAAG;AAClC,YAAM,cAAc,gBAAgB,eAAe;AACnD,UAAI,YAAY,SAAS,aAAa;AACpC,eAAO;AAAA,MACT;AAAA,IACF;AACA,kBAAc,KAAK,QAAQ,WAAW;AAAA,EACxC;AAEA,SAAO;AACT;AAEA,SAAS,uCAAuC,aAAqB,QAA+B;AAClG,QAAM,aAAa,CAAC,GAAG,WAAW,iBAAiB,WAAW;AAE9D,aAAW,aAAa,YAAY;AAClC,QAAI;AACF,YAAM,eAAe,eAAe,QAAQ,WAAW;AAAA,QACrD,OAAO,CAAC,MAAM;AAAA,MAChB,CAAC;AACD,YAAM,cACJ,cAAc,cACV,wBAAwB,cAAc,WAAW,IACjD,KAAK,QAAQ,YAAY;AAE/B,UAAI,aAAa;AACf,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,yBAAyB,aAAoC;AAC3E,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAMA,oBAAmB,QAAQ,QAAQ,KAAK,aAAa,CAAC;AAC5D,WAAOA,oBAAmB,IAAI,UAAU,QAAQ,MAAM,GAAGA,iBAAgB;AAAA,EAC3E;AAEA,QAAM,mBAAmB,QAAQ,QAAQ,GAAG;AAC5C,SAAO,mBAAmB,IAAI,UAAU,QAAQ,MAAM,GAAG,gBAAgB;AAC3E;AAEO,SAAS,4BACd,UACA,aACQ;AACR,QAAM,sBAAsB,uCAAuC,aAAa,SAAS,UAAU,CAAC;AACpG,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,eAAe,WAAW;AACpD,MAAI,GAAG,WAAW,KAAK,KAAK,UAAU,cAAc,CAAC,GAAG;AACtD,WAAO;AAAA,EACT;AAIA,QAAM,sBAAsB,KAAK,KAAK,SAAS,WAAW,GAAG,gBAAgB,WAAW;AACxF,MAAI,GAAG,WAAW,KAAK,KAAK,qBAAqB,cAAc,CAAC,GAAG;AACjE,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,YAAY,WAAW,yBAAyB,SAAS,UAAU,CAAC,GAAG;AACzF;AAEO,SAAS,kCACd,aACA,qBACA,gBACgC;AAChC,QAAM,kBAAkB,KAAK,KAAK,aAAa,cAAc;AAC7D,MAAI,CAAC,GAAG,WAAW,eAAe,GAAG;AACnC,UAAM,IAAI,MAAM,iCAAiC,eAAe,GAAG;AAAA,EACrE;AAEA,QAAM,cAAc,gBAAgB,eAAe;AACnD,QAAM,cAAc,YAAY;AAChC,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,uBAAuB,eAAe,+BAA+B;AAAA,EACvF;AAEA,MAAI,uBAAuB,gBAAgB,qBAAqB;AAC9D,UAAM,IAAI,MAAM,qBAAqB,WAAW,uCAAuC,mBAAmB,IAAI;AAAA,EAChH;AAEA,QAAM,oBAAoB,yBAAyB,WAAW;AAE9D,MAAI,kBAAkB,WAAW,GAAG;AAClC,UAAM,IAAI,MAAM,YAAY,WAAW,oDAAoD;AAAA,EAC7F;AAEA,MAAI;AAEJ,MAAI,gBAAgB;AAClB,UAAM,QAAQ,kBAAkB,KAAK,CAAC,MAAM,EAAE,aAAa,cAAc;AACzE,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,kBAAkB,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI;AACpE,YAAM,IAAI;AAAA,QACR,YAAY,WAAW,8BAA8B,cAAc,iBAAiB,SAAS;AAAA,MAC/F;AAAA,IACF;AACA,eAAW;AAAA,EACb,OAAO;AACL,QAAI,kBAAkB,SAAS,GAAG;AAChC,YAAM,YAAY,kBAAkB,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI;AACpE,YAAM,IAAI;AAAA,QACR,YAAY,WAAW,gCAAgC,SAAS;AAAA,MAClE;AAAA,IACF;AACA,eAAW,kBAAkB,CAAC;AAAA,EAChC;AAEA,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,kBAAkB,KAAK,KAAK,aAAa,OAAO,WAAW,QAAQ;AACzE,QAAM,gBAAgB,KAAK,KAAK,aAAa,QAAQ,WAAW,QAAQ;AAExE,MAAI,CAAC,GAAG,WAAW,eAAe,GAAG;AACnC,UAAM,IAAI,MAAM,YAAY,WAAW,4BAA4B,QAAQ,GAAG;AAAA,EAChF;AAEA,MAAI,CAAC,GAAG,WAAW,aAAa,GAAG;AACjC,UAAM,IAAI,MAAM,YAAY,WAAW,6BAA6B,QAAQ,GAAG;AAAA,EACjF;AAEA,QAAM,OAAO,gBAAgB,KAAK,KAAK,iBAAiB,UAAU,CAAC;AAEnE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,EAAE,UAAU,WAAW,SAAS,UAAU;AAAA,IACpD,YAAY,EAAE,OAAO,KAAK,OAAO,aAAa,KAAK,YAAY;AAAA,IAC/D;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,sCACd,UACA,aACA,UACgC;AAChC,QAAM,cAAc,4BAA4B,UAAU,WAAW;AACrE,SAAO,kCAAkC,aAAa,aAAa,QAAQ;AAC7E;AAEO,SAAS,wBAAwB,eAAqD;AAC3F,QAAM,aAAa,0BAA0B,cAAc,eAAe;AAC1E,MAAI,WAAW,WAAW,GAAG;AAC3B;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,MACE,YAAY,cAAc,WAAW,+EAA+E,cAAc,SAAS,QAAQ;AAAA,MACnJ;AAAA,MACA,GAAG,WAAW,IAAI,CAAC,cAAc,KAAK,SAAS,EAAE;AAAA,IACnD,EAAE,KAAK,IAAI;AAAA,EACb;AACF;",
6
6
  "names": ["versionSeparator"]
7
7
  }
package/dist/mercato.js CHANGED
@@ -1016,20 +1016,20 @@ async function run(argv = process.argv) {
1016
1016
  const commandArgs = remaining.filter(Boolean);
1017
1017
  if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
1018
1018
  console.log("Usage: yarn mercato module <add|enable|eject> ...");
1019
- console.log(" yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]");
1020
- console.log(" yarn mercato module enable <packageName> [--module <moduleId>] [--eject]");
1019
+ console.log(" yarn mercato module add <packageSpec> [--module <moduleId>] [--eject] [--allow-third-party]");
1020
+ console.log(" yarn mercato module enable <packageName> [--module <moduleId>] [--eject] [--allow-third-party]");
1021
1021
  console.log(" yarn mercato module eject <moduleId>");
1022
1022
  return 0;
1023
1023
  }
1024
1024
  if (subcommand === "add") {
1025
1025
  const { createResolver } = await import("./lib/resolver.js");
1026
1026
  const { addOfficialModule } = await import("./lib/module-install.js");
1027
- const { packageSpec, eject, moduleId } = parseModuleInstallArgs(commandArgs);
1027
+ const { packageSpec, eject, moduleId, allowThirdParty } = parseModuleInstallArgs(commandArgs);
1028
1028
  if (!packageSpec) {
1029
- console.error("Usage: yarn mercato module add <packageSpec> [--module <moduleId>] [--eject]");
1029
+ console.error("Usage: yarn mercato module add <packageSpec> [--module <moduleId>] [--eject] [--allow-third-party]");
1030
1030
  return 1;
1031
1031
  }
1032
- const result = await addOfficialModule(createResolver(), packageSpec, eject, moduleId ?? void 0);
1032
+ const result = await addOfficialModule(createResolver(), packageSpec, eject, moduleId ?? void 0, allowThirdParty);
1033
1033
  console.log(`
1034
1034
  \u2705 Module "${result.moduleId}" enabled from ${result.from}.
1035
1035
  `);
@@ -1041,13 +1041,13 @@ async function run(argv = process.argv) {
1041
1041
  if (subcommand === "enable") {
1042
1042
  const packageName = commandArgs.find((arg) => !arg.startsWith("-"));
1043
1043
  if (!packageName) {
1044
- console.error("Usage: yarn mercato module enable <packageName> [--module <moduleId>] [--eject]");
1044
+ console.error("Usage: yarn mercato module enable <packageName> [--module <moduleId>] [--eject] [--allow-third-party]");
1045
1045
  return 1;
1046
1046
  }
1047
1047
  const { createResolver } = await import("./lib/resolver.js");
1048
1048
  const { enableOfficialModule } = await import("./lib/module-install.js");
1049
- const { moduleId, eject } = parseModuleInstallArgs(commandArgs);
1050
- const result = await enableOfficialModule(createResolver(), packageName, moduleId ?? void 0, eject);
1049
+ const { moduleId, eject, allowThirdParty } = parseModuleInstallArgs(commandArgs);
1050
+ const result = await enableOfficialModule(createResolver(), packageName, moduleId ?? void 0, eject, allowThirdParty);
1051
1051
  console.log(`
1052
1052
  \u2705 Module "${result.moduleId}" enabled from ${result.from}.
1053
1053
  `);