@stjoseph/prumo 0.0.5 → 0.1.0
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/.prumo-templates/api/auth.md +51 -95
- package/.prumo-templates/api/config.md +30 -56
- package/.prumo-templates/api/errors.md +64 -44
- package/.prumo-templates/api/logging.md +27 -44
- package/.prumo-templates/api/observability.md +36 -62
- package/.prumo-templates/api/pagination.md +23 -38
- package/.prumo-templates/api/routes.md +67 -48
- package/.prumo-templates/api/structure.md +130 -0
- package/.prumo-templates/client/data.md +4 -3
- package/.prumo-templates/core/conventions.md +10 -7
- package/.prumo-templates/core/testing.md +43 -26
- package/.prumo-templates/core/tooling.md +33 -30
- package/.prumo-templates/database/entities.md +53 -61
- package/.prumo-templates/database/queries.md +34 -47
- package/.prumo-templates/database/transactions.md +28 -38
- package/.prumo-templates/mcp/server.md +54 -0
- package/.prumo-templates/monorepo/dependencies.md +3 -3
- package/.prumo-templates/monorepo/layout.md +1 -1
- package/README.md +12 -6
- package/dist/asker.js +4 -0
- package/dist/clean.js +2 -2
- package/dist/cli.js +4 -0
- package/dist/commands.js +2 -0
- package/dist/compose.js +12 -8
- package/dist/context.js +2 -0
- package/dist/generate.js +8 -0
- package/dist/mcp.js +100 -0
- package/dist/questions.js +21 -1
- package/package.json +4 -3
- package/templates/api/.swcrc +13 -0
- package/templates/api/README.md +10 -1
- package/templates/api/biome.jsonc +5 -3
- package/templates/api/package.json +22 -28
- package/templates/api/pnpm-workspace.yaml +0 -2
- package/templates/api/scripts/ports.mjs +508 -0
- package/templates/api/src/domain/shared/errors/domain-error.ts +5 -0
- package/templates/api/src/domain/shared/transactions/transaction-manager.ts +5 -0
- package/templates/api/src/domain/users/dto/profile-response.dto.ts +14 -0
- package/templates/api/src/domain/users/dto/update-profile.dto.ts +10 -0
- package/templates/api/src/domain/users/entities/profile.entity.ts +26 -0
- package/templates/api/src/domain/users/errors/profile-not-found.error.ts +9 -0
- package/templates/api/src/domain/users/repositories/profile.repository.ts +8 -0
- package/templates/api/src/domain/users/use-cases/create-profile.use-case.ts +19 -0
- package/templates/api/src/domain/users/use-cases/find-profile.use-case.ts +22 -0
- package/templates/api/src/domain/users/use-cases/update-profile.use-case.ts +26 -0
- package/templates/api/src/infra/auth/auth.cli.ts +5 -0
- package/templates/api/src/{auth → infra/auth}/auth.factory.ts +26 -1
- package/templates/api/src/infra/config/env.ts +47 -0
- package/templates/api/src/infra/database/mikroorm/entities/auth-user.schema.ts +16 -0
- package/templates/api/src/infra/database/mikroorm/entities/profile.schema.ts +28 -0
- package/templates/api/src/{mikro-orm.config.ts → infra/database/mikroorm/mikro-orm.config.ts} +1 -1
- package/templates/api/src/infra/database/mikroorm/mikro-orm.factory.ts +17 -0
- package/templates/api/src/infra/database/mikroorm/repositories/mikroorm-profile.repository.ts +17 -0
- package/templates/api/src/infra/database/mikroorm/transactions/mikroorm-transaction-manager.ts +14 -0
- package/templates/api/src/infra/di/index.ts +32 -0
- package/templates/api/src/infra/di/users.di.ts +7 -0
- package/templates/api/src/infra/http/app.ts +99 -0
- package/templates/api/src/infra/http/controllers/auth.controller.ts +36 -0
- package/templates/api/src/infra/http/controllers/health.controller.ts +19 -0
- package/templates/api/src/infra/http/controllers/mcp.controller.ts +40 -0
- package/templates/api/src/infra/http/controllers/users.controller.ts +35 -0
- package/templates/api/src/infra/http/errors/error-handler.ts +116 -0
- package/templates/api/src/infra/http/errors/http-error.ts +8 -0
- package/templates/api/src/infra/http/fastify.d.ts +17 -0
- package/templates/api/src/infra/http/fetch-bridge.ts +42 -0
- package/templates/api/src/infra/http/hooks/auth.hook.ts +32 -0
- package/templates/api/src/infra/http/hooks/request-context.hook.ts +9 -0
- package/templates/api/src/infra/http/server.ts +34 -0
- package/templates/api/src/infra/mcp/mcp.server.ts +20 -0
- package/templates/api/src/infra/mcp/tools/users.tool.ts +39 -0
- package/templates/api/test/domain/users/use-cases/find-profile.use-case.spec.ts +22 -0
- package/templates/api/test/domain/users/use-cases/update-profile.use-case.spec.ts +26 -0
- package/templates/api/{src → test/infra}/auth/auth.factory.spec.ts +4 -11
- package/templates/api/test/infra/database/mikroorm/mikro-orm.factory.spec.ts +8 -0
- package/templates/api/test/infra/database/mikroorm/repositories/mikroorm-profile.repository.spec.ts +51 -0
- package/templates/api/test/infra/database/mikroorm/transactions/mikroorm-transaction-manager.spec.ts +29 -0
- package/templates/api/test/infra/http/app.spec.ts +21 -0
- package/templates/api/test/infra/http/controllers/mcp.controller.spec.ts +152 -0
- package/templates/api/test/infra/http/controllers/users.controller.spec.ts +105 -0
- package/templates/api/test/support/factories/profile.factory.ts +25 -0
- package/templates/api/test/{factories → support/factories}/user.factory.ts +1 -1
- package/templates/api/test/support/fakes/in-memory-profile.repository.ts +14 -0
- package/templates/api/test/{setup.ts → support/setup.ts} +26 -14
- package/templates/api/tsconfig.json +3 -6
- package/templates/api/vitest.config.mts +4 -4
- package/templates/mobile/.env.example +3 -0
- package/templates/mobile/README.md +10 -0
- package/templates/mobile/jest.config.js +5 -1
- package/templates/mobile/package.json +3 -3
- package/templates/mobile/scripts/ports.mjs +508 -0
- package/templates/mobile/{src → test}/features/auth/authenticated-layout.spec.tsx +1 -1
- package/templates/mobile/{src → test}/features/auth/sign-in-form.spec.tsx +2 -2
- package/templates/mobile/{src → test}/features/auth/sign-out-button.spec.tsx +2 -2
- package/templates/mobile/{src → test}/features/profile/profile-form.spec.tsx +2 -2
- package/templates/mobile/tsconfig.json +2 -1
- package/templates/site/.env.example +2 -0
- package/templates/site/README.md +12 -1
- package/templates/site/gitignore +1 -0
- package/templates/site/package.json +1 -1
- package/templates/site/scripts/ports.mjs +508 -0
- package/templates/site/{src → test}/app/page.spec.ts +1 -1
- package/templates/site/tsconfig.json +2 -1
- package/templates/site/vitest.config.mts +4 -2
- package/templates/web/.env.example +3 -0
- package/templates/web/README.md +10 -1
- package/templates/web/package.json +5 -3
- package/templates/web/scripts/ports.mjs +508 -0
- package/templates/web/src/features/auth/auth-client.ts +5 -0
- package/templates/web/src/features/auth/sign-in-form.tsx +10 -3
- package/templates/web/src/features/oauth/consent-form.tsx +63 -0
- package/templates/web/src/routeTree.gen.ts +21 -2
- package/templates/web/src/routes/_authenticated/consent.tsx +21 -0
- package/templates/web/{src → test}/app.spec.tsx +2 -2
- package/templates/web/{src → test}/features/auth/sign-in-form.spec.tsx +2 -2
- package/templates/web/test/features/oauth/consent-form.spec.tsx +45 -0
- package/templates/web/{src → test}/features/profile/profile-form.spec.tsx +2 -2
- package/templates/web/test/{render-app.tsx → support/render-app.tsx} +1 -1
- package/templates/web/tsconfig.json +2 -1
- package/templates/web/vite.config.ts +3 -3
- package/templates/workspace/README.md +7 -3
- package/templates/workspace/scripts/ports.mjs +508 -0
- package/.prumo-templates/api/modules.md +0 -117
- package/templates/api/nest-cli.json +0 -10
- package/templates/api/src/app.module.ts +0 -46
- package/templates/api/src/auth/auth.constants.ts +0 -2
- package/templates/api/src/auth/auth.controller.ts +0 -19
- package/templates/api/src/auth/auth.guard.ts +0 -43
- package/templates/api/src/auth/auth.module.ts +0 -30
- package/templates/api/src/auth/auth.ts +0 -4
- package/templates/api/src/auth/current-user.decorator.ts +0 -9
- package/templates/api/src/auth/express.d.ts +0 -10
- package/templates/api/src/auth/public.decorator.ts +0 -4
- package/templates/api/src/config/env.module.ts +0 -11
- package/templates/api/src/config/env.ts +0 -81
- package/templates/api/src/errors/problem-details.filter.ts +0 -86
- package/templates/api/src/errors/validation-exception.factory.ts +0 -30
- package/templates/api/src/health/health.controller.ts +0 -32
- package/templates/api/src/health/health.module.ts +0 -9
- package/templates/api/src/main.ts +0 -43
- package/templates/api/src/mikro-orm.factory.ts +0 -14
- package/templates/api/src/request-context/request-context.module.ts +0 -9
- package/templates/api/src/request-context/request-context.service.ts +0 -17
- package/templates/api/src/request-context/request-id.middleware.ts +0 -19
- package/templates/api/src/users/dto/update-profile.dto.ts +0 -20
- package/templates/api/src/users/entities/profile.entity.ts +0 -27
- package/templates/api/src/users/users.controller.ts +0 -23
- package/templates/api/src/users/users.module.ts +0 -10
- package/templates/api/src/users/users.service.spec.ts +0 -44
- package/templates/api/src/users/users.service.ts +0 -36
- package/templates/api/test/factories/profile.factory.ts +0 -26
- package/templates/api/tsconfig.build.json +0 -8
- /package/templates/api/test/{global-setup.ts → support/global-setup.ts} +0 -0
- /package/templates/mobile/test/{fake-transport.ts → support/fake-transport.ts} +0 -0
- /package/templates/web/test/{fake-transport.ts → support/fake-transport.ts} +0 -0
- /package/templates/web/test/{setup.ts → support/setup.ts} +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# MCP server
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
Serve MCP at `POST /api/mcp`, in `src/infra/http/controllers/mcp.controller.ts`. Protect it with
|
|
6
|
+
`requireMcpAuth` from `@better-auth/mcp`, with the same `resource` as `mcp()` in `auth.factory.ts`:
|
|
7
|
+
`mcpResource(env)`. The route is `public: true` for the session hook, because it answers a bearer access
|
|
8
|
+
token, never a cookie.
|
|
9
|
+
|
|
10
|
+
Build the server per request in `src/infra/mcp/mcp.server.ts` with `createMcpHandler` from
|
|
11
|
+
`@modelcontextprotocol/server`, `legacy: 'reject'`. The user is the token's `sub`, passed through
|
|
12
|
+
`authInfo.extra.userId`.
|
|
13
|
+
|
|
14
|
+
Write a module's tools in `src/infra/mcp/tools/<module>.tool.ts`. A tool resolves a use case and calls
|
|
15
|
+
`execute()`, exactly as a route handler does. Take its input schema from the module's `dto/`, and pass its
|
|
16
|
+
result through the module's output schema with `.parse()` before returning it.
|
|
17
|
+
|
|
18
|
+
Keep Better Auth the authorization server: `jwt()`, `mcp()` and `cimd()` in `auth.factory.ts`, discovery at
|
|
19
|
+
`/.well-known/*`, sign-in and consent on the web app. Do not enable dynamic client registration.
|
|
20
|
+
|
|
21
|
+
## Rationale
|
|
22
|
+
|
|
23
|
+
MCP is a second edge of the product, beside HTTP, so it reaches the domain the same way: through use cases,
|
|
24
|
+
with the user's identity as an argument. A tool that queried a repository would bypass the rules a route
|
|
25
|
+
obeys.
|
|
26
|
+
|
|
27
|
+
`.parse()` is required here and not in a route because Fastify drops a field the response schema does not
|
|
28
|
+
list and MCP does not: a tool returns whatever it is handed, so an entity passed straight through publishes
|
|
29
|
+
every column.
|
|
30
|
+
|
|
31
|
+
`legacy: 'reject'` and CIMD follow the MCP 2026-07-28 profile, which Better Auth's plugin targets. The
|
|
32
|
+
protocol is stateless, so a server per request costs nothing and knows only its caller. Dynamic client
|
|
33
|
+
registration stays off because that profile deprecates it.
|
|
34
|
+
|
|
35
|
+
## Applies to
|
|
36
|
+
|
|
37
|
+
`src/infra/mcp/`, `mcp.controller.ts`, the MCP part of `auth.factory.ts`, and the web app's `/consent` route.
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
✅ async () => asResult(await container.resolve(FindProfileUseCase).execute(userId))
|
|
43
|
+
❌ async () => ({ structuredContent: await em.findOne(Profile, { userId }) })
|
|
44
|
+
|
|
45
|
+
✅ structuredContent: profileResponseSchema.parse(profile)
|
|
46
|
+
❌ structuredContent: profile every column leaves
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Enforcement
|
|
50
|
+
|
|
51
|
+
**Tests.** `mcp.controller.spec.ts` walks the whole authorization, consent included, connects the SDK's
|
|
52
|
+
own client, and asserts a tool returns only the public fields.
|
|
53
|
+
|
|
54
|
+
**Review only.** That a new tool goes through a use case and parses its output.
|
|
@@ -38,9 +38,9 @@ elsewhere, and an import breaks with nothing having changed. The repetition this
|
|
|
38
38
|
every entry is the identical line `"react": "catalog:"`, not a version to keep in sync.
|
|
39
39
|
|
|
40
40
|
An app depending on another app is tempting (importing a type from the API looks like the way to stop
|
|
41
|
-
duplicating the contract), and it is wrong twice. **The type is the wrong one:** a response
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
duplicating the contract), and it is wrong twice. **The type is the wrong one:** a response passes through
|
|
42
|
+
the route's output schema, which drops every field it does not list, so a property can be present on the
|
|
43
|
+
type and absent from the JSON, promising fields the payload never carries. **And an app is deployable:** the dependency means
|
|
44
44
|
building one requires the other, CI needs both for either, and the boundary between two things with
|
|
45
45
|
separate lifecycles stops existing.
|
|
46
46
|
|
|
@@ -82,7 +82,7 @@ with generic code, which is the code that belonged in the package.
|
|
|
82
82
|
**A Vite app and a Next app consume the package as source with no configuration**: Next 16 needs no
|
|
83
83
|
`transpilePackages`; a page importing it builds, stays static, and renders the imported value. Its `exports` points at `src/index.ts`,
|
|
84
84
|
`tsc` with `moduleResolution: bundler` typechecks through it, and `vite build` bundles it, verified in a composed
|
|
85
|
-
workspace. The API does not import the package, so
|
|
85
|
+
workspace. The API does not import the package, so its SWC build never compiles it.
|
|
86
86
|
|
|
87
87
|
**Expo resolves workspace packages automatically from SDK 52**, with no `watchFolders` or
|
|
88
88
|
`nodeModulesPaths` configuration. Older guidance about configuring Metro by hand no longer applies. Verified with
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ terminal a missing answer is an error rather than a default, so nothing importan
|
|
|
52
52
|
silence:
|
|
53
53
|
|
|
54
54
|
```sh
|
|
55
|
-
npx @stjoseph/prumo new my-app --types api,web --single-tenant
|
|
55
|
+
npx @stjoseph/prumo new my-app --types api,web --single-tenant --no-mcp
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
| Flag | What it answers |
|
|
@@ -60,6 +60,7 @@ npx @stjoseph/prumo new my-app --types api,web --single-tenant
|
|
|
60
60
|
| `--types api,web,mobile,site` | What the project contains. More than one type makes a workspace |
|
|
61
61
|
| `--alone` or `--monorepo` | How a single type is laid out |
|
|
62
62
|
| `--multi-tenant` or `--single-tenant` | Whether the application serves several tenants |
|
|
63
|
+
| `--mcp` or `--no-mcp` | With an api and a web: whether AI assistants reach the API through MCP, signed in as the user |
|
|
63
64
|
| `--skip-install` | Stops after writing the files |
|
|
64
65
|
|
|
65
66
|
## Commands
|
|
@@ -81,7 +82,7 @@ is asked, and the exit code is 0 only when `ok` is true.
|
|
|
81
82
|
|
|
82
83
|
| Type | What you get |
|
|
83
84
|
|---|---|
|
|
84
|
-
| `api` |
|
|
85
|
+
| `api` | Fastify with a domain/infra structure, tsyringe, Zod, MikroORM on PostgreSQL, Better Auth in a schema of its own, Swagger UI, Vitest with Testcontainers |
|
|
85
86
|
| `web` | Vite, React, TanStack Router and Query, Tailwind, shadcn/ui, forms validated with Zod |
|
|
86
87
|
| `mobile` | Expo with Expo Router, NativeWind, MMKV, and the session kept in `expo-secure-store` |
|
|
87
88
|
| `site` | Next on the App Router, static by default, for pages that have to be indexed |
|
|
@@ -117,14 +118,19 @@ Once written, those documents belong to the team. Prumo does not come back and r
|
|
|
117
118
|
|---|---|
|
|
118
119
|
| [`docs/stack.md`](https://github.com/stjosephworks/prumo/blob/main/docs/stack.md) | What is locked, what was deliberately dropped, and the version floors |
|
|
119
120
|
| [`docs/structure.md`](https://github.com/stjosephworks/prumo/blob/main/docs/structure.md) | The `.prumo/` layout, and the rule deciding which areas a project receives |
|
|
120
|
-
| [`docs/
|
|
121
|
-
| [`docs/
|
|
121
|
+
| [`docs/document-template.md`](https://github.com/stjosephworks/prumo/blob/main/docs/document-template.md) | The shape every convention document fills |
|
|
122
|
+
| [`docs/maintaining-templates.md`](https://github.com/stjosephworks/prumo/blob/main/docs/maintaining-templates.md) | How a template is changed, and how the change is proved |
|
|
123
|
+
|
|
124
|
+
## Releasing
|
|
125
|
+
|
|
126
|
+
`main` publishes itself. A push to `main` runs the whole CI, and when `cli/package.json` carries a version the
|
|
127
|
+
registry does not have yet, the `publish` job sends it to npm through npm's trusted publishing, with provenance and
|
|
128
|
+
without a token in the repository. Any other push to `main` publishes nothing.
|
|
122
129
|
|
|
123
130
|
## Status
|
|
124
131
|
|
|
125
132
|
Prumo is in beta. `0.0.1` is the first published version: every template runs and every generated
|
|
126
133
|
project is verified in CI, and what a generated project looks like can still change before 1.0.
|
|
127
|
-
|
|
128
|
-
updating a project's `.prumo/` in place comes after 1.0.
|
|
134
|
+
Updating a project's `.prumo/` in place comes after 1.0.
|
|
129
135
|
|
|
130
136
|
MIT licensed. See [`LICENSE`](https://github.com/stjosephworks/prumo/blob/main/LICENSE).
|
package/dist/asker.js
CHANGED
|
@@ -24,5 +24,9 @@ export function terminalAsker(validateName) {
|
|
|
24
24
|
initialValue: 'alone',
|
|
25
25
|
})),
|
|
26
26
|
multiTenant: async () => answered(await prompt.confirm({ message: 'Is it multi-tenant?', initialValue: false })),
|
|
27
|
+
mcp: async () => answered(await prompt.confirm({
|
|
28
|
+
message: 'Let AI assistants use the API through MCP, signed in as the user?',
|
|
29
|
+
initialValue: false,
|
|
30
|
+
})),
|
|
27
31
|
};
|
|
28
32
|
}
|
package/dist/clean.js
CHANGED
|
@@ -17,7 +17,7 @@ export const README_PARAGRAPH = '`DATABASE_URL` starts as `MISSING`. While it is
|
|
|
17
17
|
const README_REPLACEMENT = 'Before the first `pnpm dev`, start Postgres with `docker compose up -d --wait` and run `pnpm db:migrate`.\n' +
|
|
18
18
|
'`.env.example` points at that service on port 5432; when `POSTGRES_PORT` changes, change both URLs with it.\n';
|
|
19
19
|
export const README_ROW = '| `pnpm db:setup` | Creates a development database in Docker, writes its URL into `.env`, and migrates |\n';
|
|
20
|
-
export const WORKSPACE_SENTENCE = 'If the workspace holds `api` and its database is not set up yet,
|
|
20
|
+
export const WORKSPACE_SENTENCE = 'If the workspace holds `api` and its database is not set up yet, `pnpm dev` offers to create the database first.\n\n';
|
|
21
21
|
function textRule(id, file, description, from, to, marker) {
|
|
22
22
|
return {
|
|
23
23
|
id,
|
|
@@ -55,7 +55,7 @@ function rulesFor(root, api, templates) {
|
|
|
55
55
|
textRule('api-readme-row', at('README.md'), 'Remove `db:setup` from the README', README_ROW, '', '`pnpm db:setup`'),
|
|
56
56
|
];
|
|
57
57
|
if (api !== root) {
|
|
58
|
-
rules.push(devRule('root-dev-check', 'package.json', ROOT_DEV_PREFIX), textRule('root-readme', 'README.md', 'Remove the database offer from the README', WORKSPACE_SENTENCE, '', 'offers to create
|
|
58
|
+
rules.push(devRule('root-dev-check', 'package.json', ROOT_DEV_PREFIX), textRule('root-readme', 'README.md', 'Remove the database offer from the README', WORKSPACE_SENTENCE, '', 'offers to create the database'));
|
|
59
59
|
}
|
|
60
60
|
return { rules, script: join(templates, 'api', 'scripts', 'database.mjs') };
|
|
61
61
|
}
|
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,8 @@ async function runNew(args, json) {
|
|
|
38
38
|
monorepo: { type: 'boolean', default: false },
|
|
39
39
|
'multi-tenant': { type: 'boolean', default: false },
|
|
40
40
|
'single-tenant': { type: 'boolean', default: false },
|
|
41
|
+
mcp: { type: 'boolean', default: false },
|
|
42
|
+
'no-mcp': { type: 'boolean', default: false },
|
|
41
43
|
'skip-install': { type: 'boolean', default: false },
|
|
42
44
|
json: { type: 'boolean', default: false },
|
|
43
45
|
},
|
|
@@ -54,6 +56,8 @@ async function runNew(args, json) {
|
|
|
54
56
|
monorepo: values.monorepo,
|
|
55
57
|
multiTenant: values['multi-tenant'],
|
|
56
58
|
singleTenant: values['single-tenant'],
|
|
59
|
+
mcp: values.mcp,
|
|
60
|
+
noMcp: values['no-mcp'],
|
|
57
61
|
}, interactive ? terminalAsker(validateProjectName) : undefined);
|
|
58
62
|
if (!json && answers.types.length > 1) {
|
|
59
63
|
prompt.log.info(`${answers.types.length} types make a workspace: apps/ and packages/ under one root.`);
|
package/dist/commands.js
CHANGED
|
@@ -13,6 +13,8 @@ export const COMMANDS = [
|
|
|
13
13
|
{ flag: '--monorepo', description: 'A workspace, even for a single type' },
|
|
14
14
|
{ flag: '--multi-tenant', description: 'Tenant-aware api, web and mobile' },
|
|
15
15
|
{ flag: '--single-tenant', description: 'No tenancy' },
|
|
16
|
+
{ flag: '--mcp', description: 'An MCP server on the api, authorized through the web' },
|
|
17
|
+
{ flag: '--no-mcp', description: 'No MCP server' },
|
|
16
18
|
{ flag: '--skip-install', description: 'Do not run pnpm install' },
|
|
17
19
|
JSON_OPTION,
|
|
18
20
|
],
|
package/dist/compose.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
4
|
import { prependProperty, readJsonc, removeWhatBaseDeclares, setJsonc } from "./jsonc.js";
|
|
5
|
+
import { applyMcp } from "./mcp.js";
|
|
5
6
|
const CLIENTS = ['web', 'mobile'];
|
|
6
7
|
// One exception to one-version-per-dependency, recorded in DECISIONS.md: stable NativeWind needs Tailwind 3.
|
|
7
8
|
const NAMED_CATALOGS = [{ name: 'tailwind3', dependency: 'tailwindcss', major: '3' }];
|
|
@@ -181,12 +182,15 @@ async function assertCatalogCovers(target, catalog) {
|
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
|
-
export async function composeWorkspace({ templates, target, types, mobileScheme = 'app', }) {
|
|
185
|
+
export async function composeWorkspace({ templates, target, types, mobileScheme = 'app', mcp = false, }) {
|
|
185
186
|
await mkdir(target, { recursive: true });
|
|
186
187
|
await copyTemplate(join(templates, 'workspace'), target);
|
|
187
188
|
const apps = [];
|
|
188
189
|
for (const type of types) {
|
|
189
|
-
|
|
190
|
+
const app = await copyApp(templates, target, type);
|
|
191
|
+
// Before the catalog is collected, so a dependency that leaves never reaches it.
|
|
192
|
+
await applyMcp(app, type, mcp);
|
|
193
|
+
apps.push(app);
|
|
190
194
|
}
|
|
191
195
|
const clients = CLIENTS.filter((client) => types.includes(client));
|
|
192
196
|
if (clients.length > 0) {
|
|
@@ -204,12 +208,12 @@ export async function composeWorkspace({ templates, target, types, mobileScheme
|
|
|
204
208
|
});
|
|
205
209
|
}
|
|
206
210
|
// One command for everything and one per app. Each app answers to `dev`, so the root never needs to know how.
|
|
207
|
-
// The
|
|
208
|
-
// run would bury, so the root asks
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
// The database and the ports are the exception: both may ask a question, which the interleaved output of a
|
|
212
|
+
// parallel run would bury, so the root asks first and each app's own check then finds nothing to do.
|
|
213
|
+
// `pnpm -r --parallel` starts each app in its own process group, so Ctrl+C reaches pnpm and leaves the apps
|
|
214
|
+
// running. The ports script wraps the run, remembers what it started, and stops all of it on the way out.
|
|
215
|
+
const database = types.includes('api') ? 'node apps/api/scripts/database.mjs --check && ' : '';
|
|
216
|
+
const dev = `${database}node scripts/ports.mjs --all -- pnpm -r --parallel dev`;
|
|
213
217
|
await rewriteText(join(target, 'package.json'), (text) => types.reduce((pkg, type) => setJsonc(pkg, ['scripts', type], `pnpm --filter ${type} dev`), setJsonc(text, ['scripts', 'dev'], dev)));
|
|
214
218
|
const manifests = await Promise.all(apps.map(async (app) => readJsonc(await readFile(join(app, 'package.json'), 'utf8'))));
|
|
215
219
|
const catalogs = collectCatalog(manifests);
|
package/dist/context.js
CHANGED
|
@@ -10,6 +10,7 @@ export function areasFor(answers) {
|
|
|
10
10
|
...CLIENTS.filter(has),
|
|
11
11
|
...(answers.architecture === 'monorepo' ? ['monorepo'] : []),
|
|
12
12
|
...(answers.multiTenant ? ['multi-tenancy'] : []),
|
|
13
|
+
...(answers.mcp ? ['mcp'] : []),
|
|
13
14
|
];
|
|
14
15
|
}
|
|
15
16
|
async function titleOf(path) {
|
|
@@ -31,6 +32,7 @@ export async function writeContext(knowledge, root, answers) {
|
|
|
31
32
|
types: answers.types,
|
|
32
33
|
architecture: answers.architecture,
|
|
33
34
|
multiTenant: answers.multiTenant,
|
|
35
|
+
mcp: answers.mcp,
|
|
34
36
|
};
|
|
35
37
|
await writeFile(join(prumo, 'config.json'), `${JSON.stringify(config, null, 2)}\n`);
|
|
36
38
|
await writeFile(join(root, 'AGENTS.md'), `# ${answers.name}\n\nThis project's conventions live in \`.prumo/\`. Read [\`.prumo/INDEX.md\`](.prumo/INDEX.md) before changing how something is done.\n`);
|
package/dist/generate.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { composeWorkspace, copyTemplate } from "./compose.js";
|
|
7
7
|
import { writeContext } from "./context.js";
|
|
8
8
|
import { setJsonc } from "./jsonc.js";
|
|
9
|
+
import { applyMcp } from "./mcp.js";
|
|
9
10
|
import { schemeFor } from "./names.js";
|
|
10
11
|
import { CliError } from "./output.js";
|
|
11
12
|
async function rewrite(path, change) {
|
|
@@ -66,6 +67,7 @@ export async function generate({ templates, knowledge, target, answers, install,
|
|
|
66
67
|
const [only] = answers.types;
|
|
67
68
|
if (answers.architecture === 'alone' && only !== undefined) {
|
|
68
69
|
await copyTemplate(join(templates, only), target);
|
|
70
|
+
await applyMcp(target, only, answers.mcp);
|
|
69
71
|
await nameProject(target, answers.name);
|
|
70
72
|
await writeLocalEnv(target, only);
|
|
71
73
|
if (only === 'api') {
|
|
@@ -81,6 +83,7 @@ export async function generate({ templates, knowledge, target, answers, install,
|
|
|
81
83
|
target,
|
|
82
84
|
types: answers.types,
|
|
83
85
|
mobileScheme: schemeFor(answers.name),
|
|
86
|
+
mcp: answers.mcp,
|
|
84
87
|
});
|
|
85
88
|
await nameProject(target, answers.name);
|
|
86
89
|
for (const type of answers.types) {
|
|
@@ -99,5 +102,10 @@ export async function generate({ templates, knowledge, target, answers, install,
|
|
|
99
102
|
run('pnpm', ['install'], target, childOutput);
|
|
100
103
|
// Rewriting the contract import changes import grouping and line length; only the formatter can settle both.
|
|
101
104
|
run('pnpm', ['exec', 'biome', 'check', '--write'], target, childOutput);
|
|
105
|
+
// The route tree is generated from the route files, and cutting MCP removed one.
|
|
106
|
+
if (answers.types.includes('web')) {
|
|
107
|
+
const web = answers.architecture === 'alone' ? target : join(target, 'apps', 'web');
|
|
108
|
+
run('pnpm', ['exec', 'tsr', 'generate'], web, childOutput);
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { setJsonc } from "./jsonc.js";
|
|
5
|
+
// The api and web templates ship with MCP. A project that answered no receives them without it: these files are
|
|
6
|
+
// deleted, these dependencies removed, and every marked line or block cut. Every other type has nothing to cut.
|
|
7
|
+
const FILES = {
|
|
8
|
+
api: [
|
|
9
|
+
'src/infra/mcp',
|
|
10
|
+
'src/infra/http/controllers/mcp.controller.ts',
|
|
11
|
+
'test/infra/http/controllers/mcp.controller.spec.ts',
|
|
12
|
+
],
|
|
13
|
+
web: ['src/features/oauth', 'src/routes/_authenticated/consent.tsx', 'test/features/oauth'],
|
|
14
|
+
};
|
|
15
|
+
const DEPENDENCIES = {
|
|
16
|
+
api: [
|
|
17
|
+
'@better-auth/cimd',
|
|
18
|
+
'@better-auth/mcp',
|
|
19
|
+
'@modelcontextprotocol/client',
|
|
20
|
+
'@modelcontextprotocol/server',
|
|
21
|
+
],
|
|
22
|
+
web: ['@better-auth/oauth-provider'],
|
|
23
|
+
};
|
|
24
|
+
const BLOCK_START = /^\s*\/\/ prumo:mcp$/;
|
|
25
|
+
const BLOCK_END = /^\s*\/\/ prumo:end-mcp$/;
|
|
26
|
+
// A marked import keeps its marker as a trailing comment, which the import sorter carries along.
|
|
27
|
+
const MARKED_LINE = /\s*\/\/ prumo:mcp$/;
|
|
28
|
+
export function cutMcp(text, keep, file = 'a file') {
|
|
29
|
+
const kept = [];
|
|
30
|
+
let inBlock = false;
|
|
31
|
+
for (const line of text.split('\n')) {
|
|
32
|
+
if (BLOCK_START.test(line)) {
|
|
33
|
+
if (inBlock) {
|
|
34
|
+
throw new Error(`${file} opens a prumo:mcp block inside another`);
|
|
35
|
+
}
|
|
36
|
+
inBlock = true;
|
|
37
|
+
}
|
|
38
|
+
else if (BLOCK_END.test(line)) {
|
|
39
|
+
if (!inBlock) {
|
|
40
|
+
throw new Error(`${file} closes a prumo:mcp block it never opened`);
|
|
41
|
+
}
|
|
42
|
+
inBlock = false;
|
|
43
|
+
}
|
|
44
|
+
else if (MARKED_LINE.test(line)) {
|
|
45
|
+
if (keep) {
|
|
46
|
+
kept.push(line.replace(MARKED_LINE, ''));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (keep || !inBlock) {
|
|
50
|
+
kept.push(line);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (inBlock) {
|
|
54
|
+
throw new Error(`${file} leaves a prumo:mcp block open`);
|
|
55
|
+
}
|
|
56
|
+
return kept.join('\n');
|
|
57
|
+
}
|
|
58
|
+
async function sourceFiles(directory) {
|
|
59
|
+
if (!existsSync(directory)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const entries = await readdir(directory, { withFileTypes: true, recursive: true });
|
|
63
|
+
return entries
|
|
64
|
+
.filter((entry) => entry.isFile() && /\.tsx?$/.test(entry.name))
|
|
65
|
+
.map((entry) => join(entry.parentPath, entry.name));
|
|
66
|
+
}
|
|
67
|
+
export async function applyMcp(app, type, enabled) {
|
|
68
|
+
const files = FILES[type];
|
|
69
|
+
const dependencies = DEPENDENCIES[type];
|
|
70
|
+
if (files === undefined || dependencies === undefined) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!enabled) {
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
await rm(join(app, file), { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
const manifest = join(app, 'package.json');
|
|
78
|
+
let text = await readFile(manifest, 'utf8');
|
|
79
|
+
for (const section of ['dependencies', 'devDependencies']) {
|
|
80
|
+
for (const dependency of dependencies) {
|
|
81
|
+
text = setJsonc(text, [section, dependency], undefined);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await writeFile(manifest, text);
|
|
85
|
+
}
|
|
86
|
+
const sources = [
|
|
87
|
+
...(await sourceFiles(join(app, 'src'))),
|
|
88
|
+
...(await sourceFiles(join(app, 'test'))),
|
|
89
|
+
];
|
|
90
|
+
for (const source of sources) {
|
|
91
|
+
const text = await readFile(source, 'utf8');
|
|
92
|
+
const cut = cutMcp(text, enabled, source);
|
|
93
|
+
if (!enabled && dependencies.some((dependency) => cut.includes(`'${dependency}`))) {
|
|
94
|
+
throw new Error(`${source} still imports an MCP package outside a prumo:mcp marker`);
|
|
95
|
+
}
|
|
96
|
+
if (cut !== text) {
|
|
97
|
+
await writeFile(source, cut);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/questions.js
CHANGED
|
@@ -17,6 +17,9 @@ export async function resolveAnswers(flags, asker) {
|
|
|
17
17
|
if (flags.alone && flags.monorepo) {
|
|
18
18
|
throw new CliError('invalid_input', 'Choose --alone or --monorepo, not both.');
|
|
19
19
|
}
|
|
20
|
+
if (flags.mcp && flags.noMcp) {
|
|
21
|
+
throw new CliError('invalid_input', 'Choose --mcp or --no-mcp, not both.');
|
|
22
|
+
}
|
|
20
23
|
if (flags.multiTenant && flags.singleTenant) {
|
|
21
24
|
throw new CliError('invalid_input', 'Choose --multi-tenant or --single-tenant, not both.');
|
|
22
25
|
}
|
|
@@ -60,5 +63,22 @@ export async function resolveAnswers(flags, asker) {
|
|
|
60
63
|
missing('--multi-tenant or --single-tenant');
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
// MCP authorizes through a sign-in and a consent page, which only a web app can serve.
|
|
67
|
+
const mcpCapable = types.includes('api') && types.includes('web');
|
|
68
|
+
let mcp = false;
|
|
69
|
+
if (!mcpCapable) {
|
|
70
|
+
if (flags.mcp) {
|
|
71
|
+
throw new CliError('invalid_input', '--mcp needs both api and web in --types.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (flags.mcp || flags.noMcp) {
|
|
75
|
+
mcp = flags.mcp;
|
|
76
|
+
}
|
|
77
|
+
else if (asker !== undefined) {
|
|
78
|
+
mcp = await asker.mcp();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
missing('--mcp or --no-mcp');
|
|
82
|
+
}
|
|
83
|
+
return { name, types, architecture, multiTenant, mcp };
|
|
64
84
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stjoseph/prumo",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Scaffolds a TypeScript project and ships it the conventions it follows, in a .prumo/ folder any AI assistant reads.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scaffolding",
|
|
7
7
|
"cli",
|
|
8
8
|
"typescript",
|
|
9
|
-
"
|
|
9
|
+
"fastify",
|
|
10
|
+
"mcp",
|
|
10
11
|
"expo",
|
|
11
12
|
"monorepo",
|
|
12
13
|
"conventions",
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"scripts": {
|
|
36
37
|
"lint": "biome check",
|
|
37
38
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"test": "vitest run --dir
|
|
39
|
+
"test": "vitest run --dir test",
|
|
39
40
|
"prumo": "node src/cli.ts",
|
|
40
41
|
"build": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
41
42
|
"prepack": "pnpm build"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://swc.rs/schema.json",
|
|
3
|
+
"jsc": {
|
|
4
|
+
"parser": { "syntax": "typescript", "decorators": true },
|
|
5
|
+
"transform": { "legacyDecorator": true, "decoratorMetadata": true },
|
|
6
|
+
"target": "es2023",
|
|
7
|
+
"keepClassNames": true,
|
|
8
|
+
"baseUrl": "./",
|
|
9
|
+
"paths": { "@/*": ["./src/*"] }
|
|
10
|
+
},
|
|
11
|
+
"module": { "type": "commonjs" },
|
|
12
|
+
"sourceMaps": true
|
|
13
|
+
}
|
package/templates/api/README.md
CHANGED
|
@@ -19,7 +19,16 @@ starts Postgres from `docker-compose.yml` on port 5432 or the next free one (kep
|
|
|
19
19
|
into `.env`, and migrates. `pnpm db:setup` does the same at any time. Without a terminal every answer comes from a
|
|
20
20
|
flag: `--name`, `--port`, `--skip-migrate`, and `--json` for one JSON result on stdout.
|
|
21
21
|
|
|
22
|
-
The API listens on `http://localhost:3000
|
|
22
|
+
The API listens on the port in `PORT`, `http://localhost:3000` by default. Outside production, its documentation is
|
|
23
|
+
at `/api/docs`.
|
|
24
|
+
|
|
25
|
+
Before it starts, `pnpm dev` checks that the port is free. When something else holds it, it says what does, by
|
|
26
|
+
name and pid, and asks whether to stop that process or to move this app to the next free port. Moving it writes the
|
|
27
|
+
new port to `.env`, along with every URL that pointed at the old one. Without a terminal, pass `--kill` or
|
|
28
|
+
`--change` to `scripts/ports.mjs`, or the command stops rather than choosing for you.
|
|
29
|
+
|
|
30
|
+
Ctrl+C stops everything `pnpm dev` started, not only what the terminal signals: the script remembers the processes
|
|
31
|
+
it spawned, stops them, and frees the port. Anything it did not start is named and left alone.
|
|
23
32
|
|
|
24
33
|
`pnpm db:migrate` builds first, then runs Better Auth's migrations and then the application's. Before
|
|
25
34
|
migrating, Better Auth logs `Database schema mismatch` because its tables do not exist yet; the migration
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
"linter": {
|
|
7
7
|
"rules": {
|
|
8
8
|
"preset": "recommended",
|
|
9
|
-
//
|
|
10
|
-
"style": { "useImportType": "off" }
|
|
9
|
+
// tsyringe injects by the emitted constructor type; `import type` erases it and breaks injection silently.
|
|
10
|
+
"style": { "useImportType": "off" },
|
|
11
|
+
// A file that only re-exports is how an invisible import cycle forms.
|
|
12
|
+
"performance": { "noBarrelFile": "error" }
|
|
11
13
|
}
|
|
12
14
|
},
|
|
13
15
|
"javascript": {
|
|
14
16
|
"formatter": { "quoteStyle": "single", "semicolons": "asNeeded" },
|
|
15
|
-
//
|
|
17
|
+
// Ports are injected with `@inject(TOKEN)` on constructor parameters; Biome cannot parse those without this.
|
|
16
18
|
"parser": { "unsafeParameterDecoratorsEnabled": true }
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -3,57 +3,51 @@
|
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "
|
|
7
|
-
"start": "
|
|
8
|
-
"start:dev": "
|
|
9
|
-
"dev": "node scripts/database.mjs --check && pnpm start:dev",
|
|
10
|
-
"start:prod": "node dist/main",
|
|
6
|
+
"build": "swc src -d dist --strip-leading-paths --delete-dir-on-start --ignore \"**/*.spec.ts,**/*.d.ts\"",
|
|
7
|
+
"start": "node dist/infra/http/server.js",
|
|
8
|
+
"start:dev": "node --watch -r @swc-node/register src/infra/http/server.ts",
|
|
9
|
+
"dev": "node scripts/database.mjs --check && node scripts/ports.mjs -- pnpm start:dev",
|
|
11
10
|
"lint": "biome check",
|
|
12
11
|
"typecheck": "tsc --noEmit",
|
|
13
12
|
"test": "vitest run",
|
|
14
13
|
"test:watch": "vitest",
|
|
15
14
|
"test:cov": "vitest run --coverage",
|
|
16
15
|
"db:setup": "node scripts/database.mjs",
|
|
17
|
-
"db:migrate": "
|
|
16
|
+
"db:migrate": "pnpm build && auth migrate --config dist/infra/auth/auth.cli.js --yes && mikro-orm migration:up",
|
|
18
17
|
"db:migration:create": "mikro-orm migration:create",
|
|
19
18
|
"prepare": "node .githooks/install.mjs"
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
|
-
"@better-auth/
|
|
21
|
+
"@better-auth/cimd": "1.7.6",
|
|
22
|
+
"@better-auth/expo": "1.7.6",
|
|
23
|
+
"@better-auth/mcp": "1.7.6",
|
|
24
|
+
"@fastify/cors": "11.3.0",
|
|
25
|
+
"@fastify/swagger": "9.9.0",
|
|
26
|
+
"@fastify/swagger-ui": "6.1.1",
|
|
23
27
|
"@mikro-orm/core": "7.2.0",
|
|
24
28
|
"@mikro-orm/migrations": "7.2.0",
|
|
25
|
-
"@mikro-orm/nestjs": "7.1.0",
|
|
26
29
|
"@mikro-orm/postgresql": "7.2.0",
|
|
27
|
-
"@
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"@nestjs/swagger": "12.0.1",
|
|
32
|
-
"@nestjs/terminus": "12.0.0",
|
|
33
|
-
"better-auth": "1.7.4",
|
|
34
|
-
"class-transformer": "0.5.1",
|
|
35
|
-
"class-validator": "0.15.1",
|
|
36
|
-
"nestjs-pino": "5.1.0",
|
|
30
|
+
"@modelcontextprotocol/server": "2.2.0",
|
|
31
|
+
"better-auth": "1.7.6",
|
|
32
|
+
"fastify": "5.12.5",
|
|
33
|
+
"fastify-type-provider-zod": "7.0.0",
|
|
37
34
|
"pg": "8.23.0",
|
|
38
|
-
"pino": "10.3.1",
|
|
39
|
-
"pino-http": "11.0.0",
|
|
40
35
|
"reflect-metadata": "0.2.2",
|
|
41
|
-
"
|
|
36
|
+
"tsyringe": "4.10.0",
|
|
37
|
+
"zod": "4.6.4"
|
|
42
38
|
},
|
|
43
39
|
"devDependencies": {
|
|
44
40
|
"@biomejs/biome": "2.5.13",
|
|
45
41
|
"@mikro-orm/cli": "7.2.0",
|
|
46
|
-
"@
|
|
47
|
-
"@nestjs/schematics": "12.0.1",
|
|
48
|
-
"@nestjs/testing": "12.0.1",
|
|
42
|
+
"@modelcontextprotocol/client": "2.2.0",
|
|
49
43
|
"@swc-node/register": "1.12.1",
|
|
44
|
+
"@swc/cli": "0.8.1",
|
|
50
45
|
"@swc/core": "1.16.2",
|
|
51
46
|
"@testcontainers/postgresql": "12.1.0",
|
|
52
|
-
"@types/express": "5.0.6",
|
|
53
47
|
"@types/node": "24.13.4",
|
|
54
48
|
"@types/pg": "8.23.1",
|
|
55
49
|
"@vitest/coverage-v8": "4.1.11",
|
|
56
|
-
"auth": "1.7.
|
|
50
|
+
"auth": "1.7.6",
|
|
57
51
|
"pino-pretty": "13.1.3",
|
|
58
52
|
"typescript": "6.0.3",
|
|
59
53
|
"unplugin-swc": "1.6.0",
|
|
@@ -64,8 +58,8 @@
|
|
|
64
58
|
},
|
|
65
59
|
"mikro-orm": {
|
|
66
60
|
"configPaths": [
|
|
67
|
-
"./src/mikro-orm.config.ts",
|
|
68
|
-
"./dist/mikro-orm.config.js"
|
|
61
|
+
"./src/infra/database/mikroorm/mikro-orm.config.ts",
|
|
62
|
+
"./dist/infra/database/mikroorm/mikro-orm.config.js"
|
|
69
63
|
]
|
|
70
64
|
}
|
|
71
65
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
allowBuilds:
|
|
2
|
-
# Its install script only sends usage telemetry; it arrives through @nestjs/swagger's swagger-ui-dist.
|
|
3
|
-
'@scarf/scarf': false
|
|
4
2
|
# Its platform binary arrives as an optional dependency, so the install script is not needed.
|
|
5
3
|
'@swc/core': false
|
|
6
4
|
# These three arrive through Testcontainers, and the suite runs without their native builds.
|