@frontmcp/nx 1.9.1 → 1.9.3
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/executors/deploy/deploy.impl.js +2 -1
- package/executors/deploy/deploy.impl.js.map +1 -1
- package/generators/lib/lib/normalize-options.js +39 -1
- package/generators/lib/lib/normalize-options.js.map +1 -1
- package/generators/lib/schema.json +1 -1
- package/generators/server/files/cloudflare/wrangler.toml__tmpl__ +1 -1
- package/generators/server/files/lambda/template.yaml__tmpl__ +11 -1
- package/generators/server/files/node/Dockerfile__tmpl__ +10 -6
- package/generators/server/files/node/docker-compose.yml__tmpl__ +8 -1
- package/generators/server/server.js +9 -4
- package/generators/server/server.js.map +1 -1
- package/generators/ui-shared/ensure-ui-package.js +38 -2
- package/generators/ui-shared/ensure-ui-package.js.map +1 -1
- package/package.json +2 -2
- package/utils/package-manager.d.ts +17 -0
- package/utils/package-manager.js +79 -5
- package/utils/package-manager.js.map +1 -1
|
@@ -4,7 +4,8 @@ exports.default = deployExecutor;
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const DEPLOY_COMMANDS = {
|
|
6
6
|
node: 'docker compose up --build -d',
|
|
7
|
-
|
|
7
|
+
// The deploy target depends on build, which already wrote the Build Output API tree to .vercel/output
|
|
8
|
+
vercel: 'npx vercel deploy --prebuilt --prod',
|
|
8
9
|
lambda: 'sam build && sam deploy',
|
|
9
10
|
cloudflare: 'npx wrangler deploy',
|
|
10
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.impl.js","sourceRoot":"","sources":["../../../../src/executors/deploy/deploy.impl.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"deploy.impl.js","sourceRoot":"","sources":["../../../../src/executors/deploy/deploy.impl.ts"],"names":[],"mappings":";;AAaA,iCA0BC;AAvCD,iDAAyC;AAKzC,MAAM,eAAe,GAA2B;IAC9C,IAAI,EAAE,8BAA8B;IACpC,sGAAsG;IACtG,MAAM,EAAE,qCAAqC;IAC7C,MAAM,EAAE,yBAAyB;IACjC,UAAU,EAAE,qBAAqB;CAClC,CAAC;AAEa,KAAK,UAAU,cAAc,CAC1C,OAA6B,EAC7B,OAAwB;IAExB,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,sBAAsB,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;IAExF,OAAO,CAAC,GAAG,CAAC,aAAa,WAAW,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;IAEnC,IAAI,CAAC;QACH,IAAA,wBAAQ,EAAC,OAAO,EAAE;YAChB,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE;YACrC,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE;SAC1C,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC","sourcesContent":["import { execSync } from 'child_process';\n\nimport type { ExecutorContext } from '../executor-context.js';\nimport type { DeployExecutorSchema } from './schema.js';\n\nconst DEPLOY_COMMANDS: Record<string, string> = {\n node: 'docker compose up --build -d',\n // The deploy target depends on build, which already wrote the Build Output API tree to .vercel/output\n vercel: 'npx vercel deploy --prebuilt --prod',\n lambda: 'sam build && sam deploy',\n cloudflare: 'npx wrangler deploy',\n};\n\nexport default async function deployExecutor(\n options: DeployExecutorSchema,\n context: ExecutorContext,\n): Promise<{ success: boolean }> {\n const command = DEPLOY_COMMANDS[options.target];\n if (!command) {\n console.error(`Unknown deployment target: ${options.target}`);\n return { success: false };\n }\n\n const projectName = context.projectName ?? '';\n const projectRoot = context.projectsConfigurations?.projects?.[projectName]?.root ?? '';\n\n console.log(`Deploying ${projectName} to ${options.target}...`);\n console.log(`Running: ${command}`);\n\n try {\n execSync(command, {\n cwd: `${context.root}/${projectRoot}`,\n stdio: 'inherit',\n env: { ...process.env, FORCE_COLOR: '1' },\n });\n return { success: true };\n } catch {\n return { success: false };\n }\n}\n"]}
|
|
@@ -3,12 +3,50 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.normalizeOptions = normalizeOptions;
|
|
4
4
|
const devkit_1 = require("@nx/devkit");
|
|
5
5
|
const project_paths_js_1 = require("../../../utils/project-paths.js");
|
|
6
|
+
function readWorkspaceManifest(tree) {
|
|
7
|
+
return tree.exists('package.json') ? (0, devkit_1.readJson)(tree, 'package.json') : {};
|
|
8
|
+
}
|
|
9
|
+
/** The workspace's own npm scope (`@acme/source` → `@acme/<name>`), else the bare name. */
|
|
10
|
+
function defaultImportPath(workspace, fileName) {
|
|
11
|
+
const scope = /^(@[^/]+)\//.exec(workspace.name ?? '')?.[1];
|
|
12
|
+
return scope ? `${scope}/${fileName}` : fileName;
|
|
13
|
+
}
|
|
14
|
+
/** A `paths` key that resolves the import path: the exact key, else a single-`*` wildcard key, as TypeScript matches them. */
|
|
15
|
+
function findMappingKey(paths, importPath) {
|
|
16
|
+
if (paths[importPath])
|
|
17
|
+
return importPath;
|
|
18
|
+
return Object.keys(paths).find((key) => {
|
|
19
|
+
const [prefix, suffix, ...rest] = key.split('*');
|
|
20
|
+
return (suffix !== undefined &&
|
|
21
|
+
rest.length === 0 &&
|
|
22
|
+
importPath.length >= prefix.length + suffix.length &&
|
|
23
|
+
importPath.startsWith(prefix) &&
|
|
24
|
+
importPath.endsWith(suffix));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Refuse an import path that would point an existing alias or an installed package at the new library. */
|
|
28
|
+
function assertImportPathIsFree(tree, workspace, importPath) {
|
|
29
|
+
const paths = tree.exists('tsconfig.base.json')
|
|
30
|
+
? ((0, devkit_1.readJson)(tree, 'tsconfig.base.json').compilerOptions
|
|
31
|
+
?.paths ?? {})
|
|
32
|
+
: {};
|
|
33
|
+
const mappingKey = findMappingKey(paths, importPath);
|
|
34
|
+
if (mappingKey) {
|
|
35
|
+
const via = mappingKey === importPath ? '' : ` by "${mappingKey}"`;
|
|
36
|
+
throw new Error(`The import path "${importPath}" is already mapped in tsconfig.base.json${via} to ${paths[mappingKey].join(', ')}. Pass --importPath with another name.`);
|
|
37
|
+
}
|
|
38
|
+
if (workspace.dependencies?.[importPath] ?? workspace.devDependencies?.[importPath]) {
|
|
39
|
+
throw new Error(`The import path "${importPath}" is a package this workspace depends on; the library would shadow it. Pass --importPath with another name.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
6
42
|
function normalizeOptions(tree, schema) {
|
|
7
43
|
const { className, fileName, propertyName } = (0, devkit_1.names)(schema.name);
|
|
8
44
|
const { projectRoot, offset } = (0, project_paths_js_1.resolveProjectPaths)(schema.directory ?? (0, devkit_1.joinPathFragments)('libs', fileName));
|
|
9
45
|
const libType = schema.libType ?? 'generic';
|
|
10
46
|
const publishable = schema.publishable ?? false;
|
|
11
|
-
const
|
|
47
|
+
const workspace = readWorkspaceManifest(tree);
|
|
48
|
+
const importPath = schema.importPath ?? defaultImportPath(workspace, fileName);
|
|
49
|
+
assertImportPathIsFree(tree, workspace, importPath);
|
|
12
50
|
const parsedTags = schema.tags
|
|
13
51
|
? schema.tags.split(',').map((t) => t.trim())
|
|
14
52
|
: ['scope:libs'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-options.js","sourceRoot":"","sources":["../../../../../src/generators/lib/lib/normalize-options.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"normalize-options.js","sourceRoot":"","sources":["../../../../../src/generators/lib/lib/normalize-options.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Tree, names, joinPathFragments, readJson } from '@nx/devkit';\nimport type { LibGeneratorSchema } from '../schema.js';\nimport { getProjectTsOptions, resolveProjectPaths, type ProjectModuleResolution } from '../../../utils/project-paths.js';\n\nexport interface NormalizedLibOptions {\n name: string;\n projectName: string;\n projectRoot: string;\n offset: string;\n moduleResolution: ProjectModuleResolution;\n resetCustomConditions: boolean;\n className: string;\n fileName: string;\n propertyName: string;\n libType: 'generic' | 'plugin' | 'adapter' | 'tool-register';\n publishable: boolean;\n importPath: string;\n parsedTags: string[];\n skipFormat: boolean;\n}\n\ninterface WorkspaceManifest {\n name?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nfunction readWorkspaceManifest(tree: Tree): WorkspaceManifest {\n return tree.exists('package.json') ? readJson<WorkspaceManifest>(tree, 'package.json') : {};\n}\n\n/** The workspace's own npm scope (`@acme/source` → `@acme/<name>`), else the bare name. */\nfunction defaultImportPath(workspace: WorkspaceManifest, fileName: string): string {\n const scope = /^(@[^/]+)\\//.exec(workspace.name ?? '')?.[1];\n return scope ? `${scope}/${fileName}` : fileName;\n}\n\n/** A `paths` key that resolves the import path: the exact key, else a single-`*` wildcard key, as TypeScript matches them. */\nfunction findMappingKey(paths: Record<string, string[]>, importPath: string): string | undefined {\n if (paths[importPath]) return importPath;\n return Object.keys(paths).find((key) => {\n const [prefix, suffix, ...rest] = key.split('*');\n return (\n suffix !== undefined &&\n rest.length === 0 &&\n importPath.length >= prefix.length + suffix.length &&\n importPath.startsWith(prefix) &&\n importPath.endsWith(suffix)\n );\n });\n}\n\n/** Refuse an import path that would point an existing alias or an installed package at the new library. */\nfunction assertImportPathIsFree(tree: Tree, workspace: WorkspaceManifest, importPath: string): void {\n const paths = tree.exists('tsconfig.base.json')\n ? (readJson<{ compilerOptions?: { paths?: Record<string, string[]> } }>(tree, 'tsconfig.base.json').compilerOptions\n ?.paths ?? {})\n : {};\n const mappingKey = findMappingKey(paths, importPath);\n if (mappingKey) {\n const via = mappingKey === importPath ? '' : ` by \"${mappingKey}\"`;\n throw new Error(\n `The import path \"${importPath}\" is already mapped in tsconfig.base.json${via} to ${paths[mappingKey].join(', ')}. Pass --importPath with another name.`,\n );\n }\n if (workspace.dependencies?.[importPath] ?? workspace.devDependencies?.[importPath]) {\n throw new Error(\n `The import path \"${importPath}\" is a package this workspace depends on; the library would shadow it. Pass --importPath with another name.`,\n );\n }\n}\n\nexport function normalizeOptions(tree: Tree, schema: LibGeneratorSchema): NormalizedLibOptions {\n const { className, fileName, propertyName } = names(schema.name);\n const { projectRoot, offset } = resolveProjectPaths(schema.directory ?? joinPathFragments('libs', fileName));\n const libType = schema.libType ?? 'generic';\n const publishable = schema.publishable ?? false;\n const workspace = readWorkspaceManifest(tree);\n const importPath = schema.importPath ?? defaultImportPath(workspace, fileName);\n assertImportPathIsFree(tree, workspace, importPath);\n const parsedTags = schema.tags\n ? schema.tags.split(',').map((t) => t.trim())\n : ['scope:libs'];\n\n if (publishable) {\n parsedTags.push('scope:publishable');\n }\n\n return {\n name: schema.name,\n projectName: fileName,\n projectRoot,\n offset,\n ...getProjectTsOptions(tree),\n className,\n fileName,\n propertyName,\n libType,\n publishable,\n importPath,\n parsedTags,\n skipFormat: schema.skipFormat ?? false,\n };\n}\n"]}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"importPath": {
|
|
43
43
|
"type": "string",
|
|
44
|
-
"description": "The npm scope/import path (e.g., @my-org/my-lib).
|
|
44
|
+
"description": "The npm scope/import path (e.g., @my-org/my-lib). Defaults to the workspace's npm scope (from the root package.json name) plus the library name, or the bare name when the workspace has no scope."
|
|
45
45
|
},
|
|
46
46
|
"tags": {
|
|
47
47
|
"type": "string",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name = "<%= projectName %>"
|
|
2
2
|
main = "dist/cloudflare/index.js"
|
|
3
|
-
compatibility_date = "2024-
|
|
3
|
+
compatibility_date = "2024-11-11"
|
|
4
4
|
# Required: the FrontMCP worker entry require()s @frontmcp/sdk + Express, which
|
|
5
5
|
# need Node builtins. Without nodejs_compat the Worker fails to boot.
|
|
6
6
|
compatibility_flags = ["nodejs_compat"]
|
|
@@ -2,18 +2,28 @@ AWSTemplateFormatVersion: '2010-09-09'
|
|
|
2
2
|
Transform: AWS::Serverless-2016-10-31
|
|
3
3
|
Description: '<%= className %> FrontMCP Server'
|
|
4
4
|
|
|
5
|
+
Parameters:
|
|
6
|
+
McpSessionSecret:
|
|
7
|
+
Type: String
|
|
8
|
+
NoEcho: true
|
|
9
|
+
Description: MCP_SESSION_SECRET for session-ID encryption, required in production. Generate it once (openssl rand -hex 32) and pass the same value on every deploy; a new value ends every open session
|
|
10
|
+
|
|
5
11
|
Globals:
|
|
6
12
|
Function:
|
|
7
13
|
Timeout: 30
|
|
8
14
|
Runtime: nodejs24.x
|
|
9
15
|
MemorySize: 256
|
|
16
|
+
Environment:
|
|
17
|
+
Variables:
|
|
18
|
+
NODE_ENV: production
|
|
19
|
+
MCP_SESSION_SECRET: !Ref McpSessionSecret
|
|
10
20
|
|
|
11
21
|
Resources:
|
|
12
22
|
McpFunction:
|
|
13
23
|
Type: AWS::Serverless::Function
|
|
14
24
|
Properties:
|
|
15
25
|
# `nx build <%= projectName %>` (target lambda) writes dist/lambda/handler.cjs, which exports `handler`.
|
|
16
|
-
#
|
|
26
|
+
# It bundles @codegenie/serverless-express, so the function needs no layer.
|
|
17
27
|
CodeUri: dist/lambda/
|
|
18
28
|
Handler: handler.handler
|
|
19
29
|
Events:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Build from the workspace root: docker build -f <%= projectRoot %>/Dockerfile .
|
|
2
2
|
FROM node:24-slim AS builder
|
|
3
3
|
WORKDIR /app
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
COPY . .
|
|
7
|
-
RUN
|
|
4
|
+
<% if (packageManager.docker.setup) { %>RUN <%= packageManager.docker.setup %>
|
|
5
|
+
<% } %><% if (packageManager.docker.env) { %>ENV <%= packageManager.docker.env %>
|
|
6
|
+
<% } %>COPY . .
|
|
7
|
+
RUN <%= packageManager.docker.installFrozen %>
|
|
8
|
+
RUN <%= packageManager.exec %> nx build <%= projectName %>
|
|
9
|
+
RUN <%= packageManager.docker.pruneProduction %>
|
|
8
10
|
|
|
9
11
|
FROM node:24-slim AS runner
|
|
10
12
|
WORKDIR /app
|
|
@@ -13,10 +15,12 @@ ENV NODE_ENV=production
|
|
|
13
15
|
# The server binds 127.0.0.1 by default, which a published port cannot reach.
|
|
14
16
|
ENV FRONTMCP_BIND_ADDRESS=all
|
|
15
17
|
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
|
|
18
|
+
COPY --from=builder /app/package.json ./
|
|
19
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
16
20
|
COPY --from=builder /app/<%= projectRoot %>/dist ./dist
|
|
17
|
-
COPY --from=builder /app/package.json /app/package-lock.json* ./
|
|
18
|
-
RUN npm ci --omit=dev --ignore-scripts
|
|
19
21
|
USER appuser
|
|
20
22
|
EXPOSE 3000
|
|
23
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
24
|
+
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + '/health').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
|
|
21
25
|
# `nx build <%= projectName %>` (target node) writes dist/node/<%= projectName %>.bundle.js
|
|
22
26
|
CMD ["node", "dist/node/<%= projectName %>.bundle.js"]
|
|
@@ -8,6 +8,8 @@ services:
|
|
|
8
8
|
environment:
|
|
9
9
|
- NODE_ENV=${NODE_ENV:-production}
|
|
10
10
|
- PORT=${PORT:-3000}
|
|
11
|
+
# Required in production (session-ID encryption). Compose stops here when it is unset.
|
|
12
|
+
- MCP_SESSION_SECRET=${MCP_SESSION_SECRET:?set MCP_SESSION_SECRET (openssl rand -hex 32)}
|
|
11
13
|
<% if (redis === 'docker') { %>
|
|
12
14
|
- REDIS_HOST=redis
|
|
13
15
|
- REDIS_PORT=6379
|
|
@@ -17,8 +19,10 @@ services:
|
|
|
17
19
|
|
|
18
20
|
redis:
|
|
19
21
|
image: redis:7-alpine
|
|
22
|
+
# Loopback only: the app reaches Redis over the compose network, and a
|
|
23
|
+
# Redis without a password must not listen on the host's public interfaces.
|
|
20
24
|
ports:
|
|
21
|
-
- '6379:6379'
|
|
25
|
+
- '127.0.0.1:6379:6379'
|
|
22
26
|
volumes:
|
|
23
27
|
- redis-data:/data
|
|
24
28
|
command: redis-server --appendonly yes
|
|
@@ -32,6 +36,9 @@ volumes:
|
|
|
32
36
|
redis-data:
|
|
33
37
|
<% } %>
|
|
34
38
|
|
|
39
|
+
# Generate MCP_SESSION_SECRET once and keep it in a .env file next to this file (a new value ends every open session):
|
|
40
|
+
# echo "MCP_SESSION_SECRET=$(openssl rand -hex 32)" >> .env
|
|
41
|
+
# docker compose up --build
|
|
35
42
|
# Selective rebuild:
|
|
36
43
|
# docker compose up --build app # rebuild only the app
|
|
37
44
|
# docker compose up --build # rebuild everything
|
|
@@ -6,6 +6,7 @@ const fs = tslib_1.__importStar(require("fs"));
|
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const devkit_1 = require("@nx/devkit");
|
|
8
8
|
const add_dependencies_js_1 = require("../../utils/add-dependencies.js");
|
|
9
|
+
const package_manager_js_1 = require("../../utils/package-manager.js");
|
|
9
10
|
const versions_js_1 = require("../../utils/versions.js");
|
|
10
11
|
const index_js_1 = require("./lib/index.js");
|
|
11
12
|
async function serverGenerator(tree, schema) {
|
|
@@ -24,19 +25,23 @@ async function serverGeneratorInternal(tree, schema) {
|
|
|
24
25
|
// Generate target-specific files
|
|
25
26
|
const targetDir = (0, path_1.join)(__dirname, 'files', options.deploymentTarget);
|
|
26
27
|
(0, devkit_1.generateFiles)(tree, targetDir, options.projectRoot, templateVars);
|
|
28
|
+
if (options.deploymentTarget === 'node') {
|
|
29
|
+
(0, package_manager_js_1.ensureYarnBerryPinned)(tree);
|
|
30
|
+
}
|
|
27
31
|
// Copy skills from catalog
|
|
28
32
|
const bundle = schema.skills ?? 'recommended';
|
|
29
33
|
if (bundle !== 'none') {
|
|
30
34
|
scaffoldCatalogSkills(tree, options.projectRoot, options.deploymentTarget, bundle);
|
|
31
35
|
}
|
|
32
36
|
// The lambda build wraps the server with @codegenie/serverless-express and fails without it.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
if (options.deploymentTarget === 'lambda') {
|
|
38
|
+
(0, add_dependencies_js_1.addFrontmcpDependencies)(tree, (0, versions_js_1.getLambdaDependencies)(), {}, { keepExistingVersions: true });
|
|
39
|
+
}
|
|
36
40
|
if (!options.skipFormat) {
|
|
37
41
|
await (0, devkit_1.formatFiles)(tree);
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
// The shell's package.json joins the workspace: install, so the lock file the Dockerfile installs from lists it.
|
|
44
|
+
return () => (0, devkit_1.installPackagesTask)(tree, true);
|
|
40
45
|
}
|
|
41
46
|
function scaffoldCatalogSkills(tree, projectRoot, target, bundle) {
|
|
42
47
|
// Load skills catalog via @frontmcp/skills package at runtime
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/generators/server/server.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/generators/server/server.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport { join } from 'path';\n\nimport {\n formatFiles,\n generateFiles,\n installPackagesTask,\n names as nxNames,\n type GeneratorCallback,\n type Tree,\n} from '@nx/devkit';\n\nimport { addFrontmcpDependencies } from '../../utils/add-dependencies.js';\nimport { ensureYarnBerryPinned } from '../../utils/package-manager.js';\nimport { getLambdaDependencies } from '../../utils/versions.js';\nimport { normalizeOptions } from './lib/index.js';\nimport type { ServerGeneratorSchema } from './schema.js';\n\nexport async function serverGenerator(tree: Tree, schema: ServerGeneratorSchema): Promise<GeneratorCallback | void> {\n return serverGeneratorInternal(tree, schema);\n}\n\nasync function serverGeneratorInternal(tree: Tree, schema: ServerGeneratorSchema): Promise<GeneratorCallback | void> {\n const options = normalizeOptions(tree, schema);\n\n const templateVars = {\n ...options,\n names: nxNames,\n tmpl: '',\n dot: '.',\n };\n\n // Generate common files (main.ts, project.json, tsconfig)\n generateFiles(tree, join(__dirname, 'files', 'common'), options.projectRoot, templateVars);\n\n // Generate target-specific files\n const targetDir = join(__dirname, 'files', options.deploymentTarget);\n generateFiles(tree, targetDir, options.projectRoot, templateVars);\n if (options.deploymentTarget === 'node') {\n ensureYarnBerryPinned(tree);\n }\n\n // Copy skills from catalog\n const bundle = schema.skills ?? 'recommended';\n if (bundle !== 'none') {\n scaffoldCatalogSkills(tree, options.projectRoot, options.deploymentTarget, bundle);\n }\n\n // The lambda build wraps the server with @codegenie/serverless-express and fails without it.\n if (options.deploymentTarget === 'lambda') {\n addFrontmcpDependencies(tree, getLambdaDependencies(), {}, { keepExistingVersions: true });\n }\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n // The shell's package.json joins the workspace: install, so the lock file the Dockerfile installs from lists it.\n return () => installPackagesTask(tree, true);\n}\n\nfunction scaffoldCatalogSkills(tree: Tree, projectRoot: string, target: string, bundle: string): void {\n // Load skills catalog via @frontmcp/skills package at runtime\n let skills: {\n loadManifest: () => {\n skills: Array<{ name: string; path: string; targets: string[]; hasResources: boolean; bundle?: string[] }>;\n };\n resolveSkillPath: (entry: { path: string }) => string;\n getSkillsByTarget: (\n s: Array<{ targets: string[] }>,\n t: string,\n ) => Array<{ name: string; path: string; targets: string[]; hasResources: boolean; bundle?: string[] }>;\n getSkillsByBundle: (\n s: Array<{ bundle?: string[] }>,\n b: string,\n ) => Array<{ name: string; path: string; targets: string[]; hasResources: boolean; bundle?: string[] }>;\n };\n try {\n skills = require('@frontmcp/skills');\n } catch {\n return;\n }\n\n let manifest;\n try {\n manifest = skills.loadManifest();\n } catch {\n return;\n }\n\n const targetFiltered = skills.getSkillsByTarget(manifest.skills, target);\n const matchingSkills = skills.getSkillsByBundle(targetFiltered, bundle);\n\n for (const skill of matchingSkills) {\n const sourceDir = skills.resolveSkillPath(skill);\n const destDir = join(projectRoot, 'skills', skill.name);\n copyDirToTree(tree, sourceDir, destDir);\n }\n}\n\nfunction copyDirToTree(tree: Tree, sourceDir: string, destDir: string): void {\n if (!fs.existsSync(sourceDir)) return;\n const entries = fs.readdirSync(sourceDir, { withFileTypes: true });\n for (const entry of entries) {\n const srcPath = join(sourceDir, entry.name);\n const destPath = join(destDir, entry.name);\n if (entry.isDirectory()) {\n copyDirToTree(tree, srcPath, destPath);\n } else {\n const content = fs.readFileSync(srcPath);\n tree.write(destPath, content);\n }\n }\n}\n\nexport default serverGenerator;\n"]}
|
|
@@ -19,6 +19,31 @@ const REACT_DEV_DEPENDENCIES = {
|
|
|
19
19
|
'@testing-library/react': '^16.0.0',
|
|
20
20
|
'jest-environment-jsdom': '^30.0.2',
|
|
21
21
|
};
|
|
22
|
+
function jestPluginInfersTestTarget(tree) {
|
|
23
|
+
const plugins = (0, devkit_1.readNxJson)(tree)?.plugins ?? [];
|
|
24
|
+
return plugins.some((entry) => {
|
|
25
|
+
if (typeof entry === 'string')
|
|
26
|
+
return entry === '@nx/jest/plugin';
|
|
27
|
+
if (entry.plugin !== '@nx/jest/plugin')
|
|
28
|
+
return false;
|
|
29
|
+
const targetName = entry.options?.targetName;
|
|
30
|
+
return targetName === undefined || targetName === 'test';
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The Nx version the workspace runs, so `@nx/*` packages added here match it. */
|
|
34
|
+
function workspaceNxVersion(tree) {
|
|
35
|
+
if (!tree.exists('package.json'))
|
|
36
|
+
return (0, versions_js_1.getNxVersion)();
|
|
37
|
+
const { dependencies, devDependencies } = (0, devkit_1.readJson)(tree, 'package.json');
|
|
38
|
+
return devDependencies?.['nx'] ?? dependencies?.['nx'] ?? (0, versions_js_1.getNxVersion)();
|
|
39
|
+
}
|
|
40
|
+
function testTarget(packageRoot) {
|
|
41
|
+
return {
|
|
42
|
+
executor: 'nx:run-commands',
|
|
43
|
+
cache: true,
|
|
44
|
+
options: { command: 'jest --config jest.config.cjs', cwd: packageRoot },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
22
47
|
function buildTargets(packageRoot) {
|
|
23
48
|
const common = {
|
|
24
49
|
main: `${packageRoot}/src/index.ts`,
|
|
@@ -43,6 +68,8 @@ function buildTargets(packageRoot) {
|
|
|
43
68
|
outputPath: `dist/${packageRoot}/esm`,
|
|
44
69
|
format: ['esm'],
|
|
45
70
|
declaration: false,
|
|
71
|
+
// build-cjs type-checks the same sources; a noEmit check here fails with TS5069 in TS-solution workspaces.
|
|
72
|
+
skipTypeCheck: true,
|
|
46
73
|
esbuildOptions: { outExtension: { '.js': '.mjs' } },
|
|
47
74
|
},
|
|
48
75
|
},
|
|
@@ -74,7 +101,11 @@ function ensureUiPackage(tree, options) {
|
|
|
74
101
|
sourceRoot: `${packageRoot}/src`,
|
|
75
102
|
projectType: 'library',
|
|
76
103
|
tags: [],
|
|
77
|
-
targets:
|
|
104
|
+
targets: {
|
|
105
|
+
...buildTargets(packageRoot),
|
|
106
|
+
// A workspace without @nx/jest/plugin (e.g. `create-nx-workspace --preset=ts`) infers no test target.
|
|
107
|
+
...(!jestPluginInfersTestTarget(tree) && { test: testTarget(packageRoot) }),
|
|
108
|
+
},
|
|
78
109
|
});
|
|
79
110
|
(0, devkit_1.writeJson)(tree, `${packageRoot}/tsconfig.json`, {
|
|
80
111
|
extends: `${offset}tsconfig.base.json`,
|
|
@@ -108,7 +139,12 @@ function ensureUiPackage(tree, options) {
|
|
|
108
139
|
}
|
|
109
140
|
}
|
|
110
141
|
const range = `~${(0, versions_js_1.getFrontmcpVersion)()}`;
|
|
111
|
-
const { esbuild, ...
|
|
142
|
+
const { esbuild, ...uiBuildDevDependencies } = (0, versions_js_1.getUiBuildDevDependencies)();
|
|
143
|
+
const buildDevDependencies = {
|
|
144
|
+
...uiBuildDevDependencies,
|
|
145
|
+
'@nx/esbuild': workspaceNxVersion(tree),
|
|
146
|
+
...(0, versions_js_1.getJestDevDependencies)(),
|
|
147
|
+
};
|
|
112
148
|
const installUiDependencies = kind === 'react'
|
|
113
149
|
? (0, add_dependencies_js_1.addFrontmcpDependencies)(tree, REACT_DEPENDENCIES, { ...REACT_DEV_DEPENDENCIES, ...buildDevDependencies }, { keepExistingVersions: true })
|
|
114
150
|
: (0, add_dependencies_js_1.addFrontmcpDependencies)(tree, { '@frontmcp/uipack': range }, buildDevDependencies, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-ui-package.js","sourceRoot":"","sources":["../../../../src/generators/ui-shared/ensure-ui-package.ts"],"names":[],"mappings":"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","sourcesContent":["import { offsetFromRoot, readJson, runTasksInSerial, writeJson, type GeneratorCallback, type Tree } from '@nx/devkit';\n\nimport { addFrontmcpDependencies } from '../../utils/add-dependencies.js';\nimport { getModuleResolution } from '../../utils/project-paths.js';\nimport { getFrontmcpVersion, getUiBuildDevDependencies } from '../../utils/versions.js';\n\nexport interface EnsureUiPackageOptions {\n /** e.g. 'ui/components' */\n packageRoot: string;\n /** Nx project name for a package that does not exist yet, e.g. 'ui-components' */\n projectName: string;\n /** 'react' packages render components and are tested in jsdom; 'shell' packages only build HTML strings. */\n kind: 'react' | 'shell';\n}\n\nconst REACT_DEPENDENCIES: Record<string, string> = {\n react: '^19.0.0',\n 'react-dom': '^19.0.0',\n '@mui/material': '^7.0.0',\n '@emotion/react': '^11.14.0',\n '@emotion/styled': '^11.14.0',\n};\n\nconst REACT_DEV_DEPENDENCIES: Record<string, string> = {\n '@types/react': '^19.0.0',\n '@types/react-dom': '^19.0.0',\n '@testing-library/dom': '^10.4.0',\n '@testing-library/react': '^16.0.0',\n 'jest-environment-jsdom': '^30.0.2',\n};\n\nfunction buildTargets(packageRoot: string): Record<string, unknown> {\n const common = {\n main: `${packageRoot}/src/index.ts`,\n tsConfig: `${packageRoot}/tsconfig.json`,\n bundle: true,\n thirdParty: false,\n platform: 'node',\n additionalEntryPoints: [] as string[],\n };\n return {\n 'build-cjs': {\n executor: '@nx/esbuild:esbuild',\n outputs: ['{options.outputPath}'],\n options: { ...common, outputPath: `dist/${packageRoot}`, format: ['cjs'], declaration: true },\n },\n 'build-esm': {\n executor: '@nx/esbuild:esbuild',\n dependsOn: ['build-cjs'],\n outputs: ['{options.outputPath}'],\n options: {\n ...common,\n outputPath: `dist/${packageRoot}/esm`,\n format: ['esm'],\n declaration: false,\n esbuildOptions: { outExtension: { '.js': '.mjs' } },\n },\n },\n build: {\n executor: 'nx:noop',\n dependsOn: ['build-cjs', 'build-esm'],\n },\n };\n}\n\n/**\n * The UI generators emit `.tsx` files that import React and MUI (or the uipack\n * shell builder), and a spec that renders them. Nothing else installs those, so\n * the generators add them — and create the package folder when the workspace\n * does not have one yet, instead of writing files into a folder that no Nx\n * project owns.\n */\nexport function ensureUiPackage(tree: Tree, options: EnsureUiPackageOptions): GeneratorCallback {\n const { packageRoot, projectName, kind } = options;\n\n if (!tree.exists(`${packageRoot}/project.json`)) {\n const offset = offsetFromRoot(packageRoot);\n // `customConditions` (Nx solution workspaces set it) is only valid with a modern `moduleResolution`\n // (TS5098), so a package that inherits it must inherit the base's `module`/`moduleResolution` too.\n const inheritsModernResolution = Boolean(\n tree.exists('tsconfig.base.json') &&\n readJson<{ compilerOptions?: { customConditions?: unknown } }>(tree, 'tsconfig.base.json').compilerOptions\n ?.customConditions,\n );\n writeJson(tree, `${packageRoot}/project.json`, {\n name: projectName,\n $schema: `${offset}node_modules/nx/schemas/project-schema.json`,\n sourceRoot: `${packageRoot}/src`,\n projectType: 'library',\n tags: [],\n targets: buildTargets(packageRoot),\n });\n writeJson(tree, `${packageRoot}/tsconfig.json`, {\n extends: `${offset}tsconfig.base.json`,\n compilerOptions: {\n ...(!inheritsModernResolution && { module: 'commonjs', moduleResolution: getModuleResolution(tree) }),\n ...(kind === 'react' && { jsx: 'react-jsx' }),\n esModuleInterop: true,\n strict: true,\n },\n include: ['src/**/*.ts', 'src/**/*.tsx'],\n exclude: ['src/**/*.spec.ts', 'src/**/*.spec.tsx'],\n });\n tree.write(\n `${packageRoot}/jest.config.cjs`,\n `module.exports = {\n displayName: '${projectName}',\n testEnvironment: '${kind === 'react' ? 'jsdom' : 'node'}',\n transform: {\n '^.+\\\\\\\\.[tj]sx?$': ['@swc/jest', {\n jsc: {\n target: 'es2022',\n parser: { syntax: 'typescript', tsx: true },\n transform: { react: { runtime: 'automatic' } },\n },\n swcrc: false,\n }],\n },\n moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n};\n`,\n );\n if (!tree.exists(`${packageRoot}/src/index.ts`)) {\n tree.write(`${packageRoot}/src/index.ts`, '');\n }\n }\n\n const range = `~${getFrontmcpVersion()}`;\n const { esbuild, ...buildDevDependencies } = getUiBuildDevDependencies();\n const installUiDependencies =\n kind === 'react'\n ? addFrontmcpDependencies(\n tree,\n REACT_DEPENDENCIES,\n { ...REACT_DEV_DEPENDENCIES, ...buildDevDependencies },\n { keepExistingVersions: true },\n )\n : addFrontmcpDependencies(tree, { '@frontmcp/uipack': range }, buildDevDependencies, {\n keepExistingVersions: true,\n });\n // An existing esbuild range is raised rather than kept when it is older than ours: the `^0.25.0` earlier\n // versions of these generators wrote is below the `>=0.27` peer range of @frontmcp/uipack (npm ERESOLVE).\n // A range at or above ours stays, in whichever section it is in.\n const installEsbuild = addFrontmcpDependencies(tree, {}, { esbuild }, { keepExistingVersions: false });\n return runTasksInSerial(installUiDependencies, installEsbuild);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ensure-ui-package.js","sourceRoot":"","sources":["../../../../src/generators/ui-shared/ensure-ui-package.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n offsetFromRoot,\n readJson,\n readNxJson,\n runTasksInSerial,\n writeJson,\n type GeneratorCallback,\n type Tree,\n} from '@nx/devkit';\n\nimport { addFrontmcpDependencies } from '../../utils/add-dependencies.js';\nimport { getModuleResolution } from '../../utils/project-paths.js';\nimport {\n getFrontmcpVersion,\n getJestDevDependencies,\n getNxVersion,\n getUiBuildDevDependencies,\n} from '../../utils/versions.js';\n\nexport interface EnsureUiPackageOptions {\n /** e.g. 'ui/components' */\n packageRoot: string;\n /** Nx project name for a package that does not exist yet, e.g. 'ui-components' */\n projectName: string;\n /** 'react' packages render components and are tested in jsdom; 'shell' packages only build HTML strings. */\n kind: 'react' | 'shell';\n}\n\nconst REACT_DEPENDENCIES: Record<string, string> = {\n react: '^19.0.0',\n 'react-dom': '^19.0.0',\n '@mui/material': '^7.0.0',\n '@emotion/react': '^11.14.0',\n '@emotion/styled': '^11.14.0',\n};\n\nconst REACT_DEV_DEPENDENCIES: Record<string, string> = {\n '@types/react': '^19.0.0',\n '@types/react-dom': '^19.0.0',\n '@testing-library/dom': '^10.4.0',\n '@testing-library/react': '^16.0.0',\n 'jest-environment-jsdom': '^30.0.2',\n};\n\nfunction jestPluginInfersTestTarget(tree: Tree): boolean {\n const plugins = readNxJson(tree)?.plugins ?? [];\n return plugins.some((entry) => {\n if (typeof entry === 'string') return entry === '@nx/jest/plugin';\n if (entry.plugin !== '@nx/jest/plugin') return false;\n const targetName = (entry.options as { targetName?: unknown } | undefined)?.targetName;\n return targetName === undefined || targetName === 'test';\n });\n}\n\n/** The Nx version the workspace runs, so `@nx/*` packages added here match it. */\nfunction workspaceNxVersion(tree: Tree): string {\n if (!tree.exists('package.json')) return getNxVersion();\n const { dependencies, devDependencies } = readJson<{\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n }>(tree, 'package.json');\n return devDependencies?.['nx'] ?? dependencies?.['nx'] ?? getNxVersion();\n}\n\nfunction testTarget(packageRoot: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n cache: true,\n options: { command: 'jest --config jest.config.cjs', cwd: packageRoot },\n };\n}\n\nfunction buildTargets(packageRoot: string): Record<string, unknown> {\n const common = {\n main: `${packageRoot}/src/index.ts`,\n tsConfig: `${packageRoot}/tsconfig.json`,\n bundle: true,\n thirdParty: false,\n platform: 'node',\n additionalEntryPoints: [] as string[],\n };\n return {\n 'build-cjs': {\n executor: '@nx/esbuild:esbuild',\n outputs: ['{options.outputPath}'],\n options: { ...common, outputPath: `dist/${packageRoot}`, format: ['cjs'], declaration: true },\n },\n 'build-esm': {\n executor: '@nx/esbuild:esbuild',\n dependsOn: ['build-cjs'],\n outputs: ['{options.outputPath}'],\n options: {\n ...common,\n outputPath: `dist/${packageRoot}/esm`,\n format: ['esm'],\n declaration: false,\n // build-cjs type-checks the same sources; a noEmit check here fails with TS5069 in TS-solution workspaces.\n skipTypeCheck: true,\n esbuildOptions: { outExtension: { '.js': '.mjs' } },\n },\n },\n build: {\n executor: 'nx:noop',\n dependsOn: ['build-cjs', 'build-esm'],\n },\n };\n}\n\n/**\n * The UI generators emit `.tsx` files that import React and MUI (or the uipack\n * shell builder), and a spec that renders them. Nothing else installs those, so\n * the generators add them — and create the package folder when the workspace\n * does not have one yet, instead of writing files into a folder that no Nx\n * project owns.\n */\nexport function ensureUiPackage(tree: Tree, options: EnsureUiPackageOptions): GeneratorCallback {\n const { packageRoot, projectName, kind } = options;\n\n if (!tree.exists(`${packageRoot}/project.json`)) {\n const offset = offsetFromRoot(packageRoot);\n // `customConditions` (Nx solution workspaces set it) is only valid with a modern `moduleResolution`\n // (TS5098), so a package that inherits it must inherit the base's `module`/`moduleResolution` too.\n const inheritsModernResolution = Boolean(\n tree.exists('tsconfig.base.json') &&\n readJson<{ compilerOptions?: { customConditions?: unknown } }>(tree, 'tsconfig.base.json').compilerOptions\n ?.customConditions,\n );\n writeJson(tree, `${packageRoot}/project.json`, {\n name: projectName,\n $schema: `${offset}node_modules/nx/schemas/project-schema.json`,\n sourceRoot: `${packageRoot}/src`,\n projectType: 'library',\n tags: [],\n targets: {\n ...buildTargets(packageRoot),\n // A workspace without @nx/jest/plugin (e.g. `create-nx-workspace --preset=ts`) infers no test target.\n ...(!jestPluginInfersTestTarget(tree) && { test: testTarget(packageRoot) }),\n },\n });\n writeJson(tree, `${packageRoot}/tsconfig.json`, {\n extends: `${offset}tsconfig.base.json`,\n compilerOptions: {\n ...(!inheritsModernResolution && { module: 'commonjs', moduleResolution: getModuleResolution(tree) }),\n ...(kind === 'react' && { jsx: 'react-jsx' }),\n esModuleInterop: true,\n strict: true,\n },\n include: ['src/**/*.ts', 'src/**/*.tsx'],\n exclude: ['src/**/*.spec.ts', 'src/**/*.spec.tsx'],\n });\n tree.write(\n `${packageRoot}/jest.config.cjs`,\n `module.exports = {\n displayName: '${projectName}',\n testEnvironment: '${kind === 'react' ? 'jsdom' : 'node'}',\n transform: {\n '^.+\\\\\\\\.[tj]sx?$': ['@swc/jest', {\n jsc: {\n target: 'es2022',\n parser: { syntax: 'typescript', tsx: true },\n transform: { react: { runtime: 'automatic' } },\n },\n swcrc: false,\n }],\n },\n moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],\n};\n`,\n );\n if (!tree.exists(`${packageRoot}/src/index.ts`)) {\n tree.write(`${packageRoot}/src/index.ts`, '');\n }\n }\n\n const range = `~${getFrontmcpVersion()}`;\n const { esbuild, ...uiBuildDevDependencies } = getUiBuildDevDependencies();\n const buildDevDependencies = {\n ...uiBuildDevDependencies,\n '@nx/esbuild': workspaceNxVersion(tree),\n ...getJestDevDependencies(),\n };\n const installUiDependencies =\n kind === 'react'\n ? addFrontmcpDependencies(\n tree,\n REACT_DEPENDENCIES,\n { ...REACT_DEV_DEPENDENCIES, ...buildDevDependencies },\n { keepExistingVersions: true },\n )\n : addFrontmcpDependencies(tree, { '@frontmcp/uipack': range }, buildDevDependencies, {\n keepExistingVersions: true,\n });\n // An existing esbuild range is raised rather than kept when it is older than ours: the `^0.25.0` earlier\n // versions of these generators wrote is below the `>=0.27` peer range of @frontmcp/uipack (npm ERESOLVE).\n // A range at or above ours stays, in whichever section it is in.\n const installEsbuild = addFrontmcpDependencies(tree, {}, { esbuild }, { keepExistingVersions: false });\n return runTasksInSerial(installUiDependencies, installEsbuild);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/nx",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.3",
|
|
4
4
|
"description": "Nx plugin for FrontMCP — generators and executors for building MCP servers",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@frontmcp/skills": "1.9.
|
|
36
|
+
"@frontmcp/skills": "1.9.3",
|
|
37
37
|
"@nx/devkit": "22.6.4",
|
|
38
38
|
"tslib": "^2.3.0"
|
|
39
39
|
},
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { type Tree } from '@nx/devkit';
|
|
2
2
|
export type WorkspacePackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
3
|
+
/** Commands a generated Dockerfile runs, from the workspace root. */
|
|
4
|
+
export interface DockerPackageManagerCommands {
|
|
5
|
+
/** Makes the package manager available on the `node` image, when npm isn't it. */
|
|
6
|
+
setup?: string;
|
|
7
|
+
/** `NAME=value` the builder stage sets before installing, so the install lays out `node_modules`. */
|
|
8
|
+
env?: string;
|
|
9
|
+
/** Installs exactly what the lockfile records, without lifecycle scripts. */
|
|
10
|
+
installFrozen: string;
|
|
11
|
+
/** Removes dev dependencies from the installed `node_modules`. */
|
|
12
|
+
pruneProduction: string;
|
|
13
|
+
}
|
|
3
14
|
export interface PackageManagerCommands {
|
|
4
15
|
/** Installs the workspace dependencies. */
|
|
5
16
|
install: string;
|
|
6
17
|
/** Runs a locally installed bin, e.g. `npx nx`. */
|
|
7
18
|
exec: string;
|
|
19
|
+
docker: DockerPackageManagerCommands;
|
|
8
20
|
}
|
|
9
21
|
/** The package manager the workspace uses: `nx.json` `cli.packageManager`, else its lockfile, else npm. */
|
|
10
22
|
export declare function detectWorkspacePackageManager(tree: Tree): WorkspacePackageManager;
|
|
23
|
+
/**
|
|
24
|
+
* The image runs `corepack enable`, and corepack falls back to Yarn 1 unless the workspace pins a version, so a Yarn
|
|
25
|
+
* Berry workspace without a pin gets the Yarn version the generator runs under as its `packageManager`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ensureYarnBerryPinned(tree: Tree, userAgent?: string | undefined): void;
|
|
11
28
|
export declare function getPackageManagerCommands(tree: Tree): PackageManagerCommands;
|
package/utils/package-manager.js
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectWorkspacePackageManager = detectWorkspacePackageManager;
|
|
4
|
+
exports.ensureYarnBerryPinned = ensureYarnBerryPinned;
|
|
4
5
|
exports.getPackageManagerCommands = getPackageManagerCommands;
|
|
5
6
|
const devkit_1 = require("@nx/devkit");
|
|
6
7
|
const COMMANDS = {
|
|
7
|
-
npm: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
npm: {
|
|
9
|
+
install: 'npm install',
|
|
10
|
+
exec: 'npx',
|
|
11
|
+
docker: { installFrozen: 'npm ci --ignore-scripts', pruneProduction: 'npm prune --omit=dev' },
|
|
12
|
+
},
|
|
13
|
+
yarn: {
|
|
14
|
+
install: 'yarn install',
|
|
15
|
+
exec: 'yarn',
|
|
16
|
+
docker: {
|
|
17
|
+
setup: 'corepack enable',
|
|
18
|
+
env: 'YARN_NODE_LINKER=node-modules',
|
|
19
|
+
installFrozen: 'yarn install --immutable --mode=skip-build',
|
|
20
|
+
pruneProduction: 'yarn workspaces focus --all --production',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
pnpm: {
|
|
24
|
+
install: 'pnpm install',
|
|
25
|
+
exec: 'pnpm exec',
|
|
26
|
+
docker: {
|
|
27
|
+
setup: 'corepack enable',
|
|
28
|
+
installFrozen: 'pnpm install --frozen-lockfile --ignore-scripts',
|
|
29
|
+
pruneProduction: 'pnpm prune --prod --ignore-scripts',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
bun: {
|
|
33
|
+
install: 'bun install',
|
|
34
|
+
exec: 'bunx',
|
|
35
|
+
docker: {
|
|
36
|
+
setup: 'npm install -g bun',
|
|
37
|
+
installFrozen: 'bun install --frozen-lockfile --ignore-scripts',
|
|
38
|
+
pruneProduction: 'bun install --frozen-lockfile --production --ignore-scripts',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/** Yarn 1 has no `.yarnrc.yml`, `--immutable` or `workspaces focus`. */
|
|
43
|
+
const YARN_CLASSIC_DOCKER = {
|
|
44
|
+
setup: 'corepack enable',
|
|
45
|
+
installFrozen: 'yarn install --frozen-lockfile --ignore-scripts',
|
|
46
|
+
pruneProduction: 'yarn install --frozen-lockfile --production --ignore-scripts',
|
|
11
47
|
};
|
|
12
48
|
const LOCKFILES = [
|
|
13
49
|
['bun.lock', 'bun'],
|
|
@@ -27,7 +63,45 @@ function detectWorkspacePackageManager(tree) {
|
|
|
27
63
|
const match = LOCKFILES.find(([lockfile]) => tree.exists(lockfile));
|
|
28
64
|
return match ? match[1] : 'npm';
|
|
29
65
|
}
|
|
66
|
+
function rootPackageManagerField(tree) {
|
|
67
|
+
if (!tree.exists('package.json'))
|
|
68
|
+
return undefined;
|
|
69
|
+
const packageManager = (0, devkit_1.readJson)(tree, 'package.json').packageManager;
|
|
70
|
+
return typeof packageManager === 'string' ? packageManager : undefined;
|
|
71
|
+
}
|
|
72
|
+
/** Yarn 2+ writes `.yarnrc.yml`, a `__metadata` lockfile header, or a `yarn@2+` packageManager. */
|
|
73
|
+
function isYarnBerry(tree) {
|
|
74
|
+
if (tree.exists('.yarnrc.yml'))
|
|
75
|
+
return true;
|
|
76
|
+
if (/^yarn@(?!1\.)/.test(rootPackageManagerField(tree) ?? ''))
|
|
77
|
+
return true;
|
|
78
|
+
return tree.read('yarn.lock', 'utf-8')?.includes('__metadata:') ?? false;
|
|
79
|
+
}
|
|
80
|
+
function pinsYarnVersion(tree) {
|
|
81
|
+
if (rootPackageManagerField(tree)?.startsWith('yarn@'))
|
|
82
|
+
return true;
|
|
83
|
+
return /^yarnPath:/m.test(tree.read('.yarnrc.yml', 'utf-8') ?? '');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The image runs `corepack enable`, and corepack falls back to Yarn 1 unless the workspace pins a version, so a Yarn
|
|
87
|
+
* Berry workspace without a pin gets the Yarn version the generator runs under as its `packageManager`.
|
|
88
|
+
*/
|
|
89
|
+
function ensureYarnBerryPinned(tree, userAgent = process.env['npm_config_user_agent']) {
|
|
90
|
+
if (detectWorkspacePackageManager(tree) !== 'yarn' || !isYarnBerry(tree) || pinsYarnVersion(tree))
|
|
91
|
+
return;
|
|
92
|
+
const runningYarnVersion = /\byarn\/(\d+\.\d+\.\d+)/.exec(userAgent ?? '')?.[1];
|
|
93
|
+
if (runningYarnVersion && !runningYarnVersion.startsWith('1.') && tree.exists('package.json')) {
|
|
94
|
+
(0, devkit_1.updateJson)(tree, 'package.json', (json) => ({ ...json, packageManager: `yarn@${runningYarnVersion}` }));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
devkit_1.logger.warn('The workspace uses Yarn Berry but pins no Yarn version, so the Docker image would install with Yarn 1. ' +
|
|
98
|
+
'Run `yarn set version <version>` to add a packageManager field before building the image.');
|
|
99
|
+
}
|
|
30
100
|
function getPackageManagerCommands(tree) {
|
|
31
|
-
|
|
101
|
+
const packageManager = detectWorkspacePackageManager(tree);
|
|
102
|
+
if (packageManager === 'yarn' && !isYarnBerry(tree)) {
|
|
103
|
+
return { ...COMMANDS.yarn, docker: YARN_CLASSIC_DOCKER };
|
|
104
|
+
}
|
|
105
|
+
return COMMANDS[packageManager];
|
|
32
106
|
}
|
|
33
107
|
//# sourceMappingURL=package-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-manager.js","sourceRoot":"","sources":["../../../src/utils/package-manager.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"package-manager.js","sourceRoot":"","sources":["../../../src/utils/package-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger, readJson, readNxJson, updateJson, type Tree } from '@nx/devkit';\n\nexport type WorkspacePackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';\n\n/** Commands a generated Dockerfile runs, from the workspace root. */\nexport interface DockerPackageManagerCommands {\n /** Makes the package manager available on the `node` image, when npm isn't it. */\n setup?: string;\n /** `NAME=value` the builder stage sets before installing, so the install lays out `node_modules`. */\n env?: string;\n /** Installs exactly what the lockfile records, without lifecycle scripts. */\n installFrozen: string;\n /** Removes dev dependencies from the installed `node_modules`. */\n pruneProduction: string;\n}\n\nexport interface PackageManagerCommands {\n /** Installs the workspace dependencies. */\n install: string;\n /** Runs a locally installed bin, e.g. `npx nx`. */\n exec: string;\n docker: DockerPackageManagerCommands;\n}\n\nconst COMMANDS: Record<WorkspacePackageManager, PackageManagerCommands> = {\n npm: {\n install: 'npm install',\n exec: 'npx',\n docker: { installFrozen: 'npm ci --ignore-scripts', pruneProduction: 'npm prune --omit=dev' },\n },\n yarn: {\n install: 'yarn install',\n exec: 'yarn',\n docker: {\n setup: 'corepack enable',\n env: 'YARN_NODE_LINKER=node-modules',\n installFrozen: 'yarn install --immutable --mode=skip-build',\n pruneProduction: 'yarn workspaces focus --all --production',\n },\n },\n pnpm: {\n install: 'pnpm install',\n exec: 'pnpm exec',\n docker: {\n setup: 'corepack enable',\n installFrozen: 'pnpm install --frozen-lockfile --ignore-scripts',\n pruneProduction: 'pnpm prune --prod --ignore-scripts',\n },\n },\n bun: {\n install: 'bun install',\n exec: 'bunx',\n docker: {\n setup: 'npm install -g bun',\n installFrozen: 'bun install --frozen-lockfile --ignore-scripts',\n pruneProduction: 'bun install --frozen-lockfile --production --ignore-scripts',\n },\n },\n};\n\n/** Yarn 1 has no `.yarnrc.yml`, `--immutable` or `workspaces focus`. */\nconst YARN_CLASSIC_DOCKER: DockerPackageManagerCommands = {\n setup: 'corepack enable',\n installFrozen: 'yarn install --frozen-lockfile --ignore-scripts',\n pruneProduction: 'yarn install --frozen-lockfile --production --ignore-scripts',\n};\n\nconst LOCKFILES: ReadonlyArray<readonly [string, WorkspacePackageManager]> = [\n ['bun.lock', 'bun'],\n ['bun.lockb', 'bun'],\n ['pnpm-lock.yaml', 'pnpm'],\n ['yarn.lock', 'yarn'],\n ['package-lock.json', 'npm'],\n];\n\nfunction isPackageManager(value: unknown): value is WorkspacePackageManager {\n return typeof value === 'string' && Object.prototype.hasOwnProperty.call(COMMANDS, value);\n}\n\n/** The package manager the workspace uses: `nx.json` `cli.packageManager`, else its lockfile, else npm. */\nexport function detectWorkspacePackageManager(tree: Tree): WorkspacePackageManager {\n const configured = readNxJson(tree)?.cli?.packageManager;\n if (isPackageManager(configured)) return configured;\n const match = LOCKFILES.find(([lockfile]) => tree.exists(lockfile));\n return match ? match[1] : 'npm';\n}\n\nfunction rootPackageManagerField(tree: Tree): string | undefined {\n if (!tree.exists('package.json')) return undefined;\n const packageManager: unknown = readJson(tree, 'package.json').packageManager;\n return typeof packageManager === 'string' ? packageManager : undefined;\n}\n\n/** Yarn 2+ writes `.yarnrc.yml`, a `__metadata` lockfile header, or a `yarn@2+` packageManager. */\nfunction isYarnBerry(tree: Tree): boolean {\n if (tree.exists('.yarnrc.yml')) return true;\n if (/^yarn@(?!1\\.)/.test(rootPackageManagerField(tree) ?? '')) return true;\n return tree.read('yarn.lock', 'utf-8')?.includes('__metadata:') ?? false;\n}\n\nfunction pinsYarnVersion(tree: Tree): boolean {\n if (rootPackageManagerField(tree)?.startsWith('yarn@')) return true;\n return /^yarnPath:/m.test(tree.read('.yarnrc.yml', 'utf-8') ?? '');\n}\n\n/**\n * The image runs `corepack enable`, and corepack falls back to Yarn 1 unless the workspace pins a version, so a Yarn\n * Berry workspace without a pin gets the Yarn version the generator runs under as its `packageManager`.\n */\nexport function ensureYarnBerryPinned(tree: Tree, userAgent = process.env['npm_config_user_agent']): void {\n if (detectWorkspacePackageManager(tree) !== 'yarn' || !isYarnBerry(tree) || pinsYarnVersion(tree)) return;\n const runningYarnVersion = /\\byarn\\/(\\d+\\.\\d+\\.\\d+)/.exec(userAgent ?? '')?.[1];\n if (runningYarnVersion && !runningYarnVersion.startsWith('1.') && tree.exists('package.json')) {\n updateJson(tree, 'package.json', (json) => ({ ...json, packageManager: `yarn@${runningYarnVersion}` }));\n return;\n }\n logger.warn(\n 'The workspace uses Yarn Berry but pins no Yarn version, so the Docker image would install with Yarn 1. ' +\n 'Run `yarn set version <version>` to add a packageManager field before building the image.',\n );\n}\n\nexport function getPackageManagerCommands(tree: Tree): PackageManagerCommands {\n const packageManager = detectWorkspacePackageManager(tree);\n if (packageManager === 'yarn' && !isYarnBerry(tree)) {\n return { ...COMMANDS.yarn, docker: YARN_CLASSIC_DOCKER };\n }\n return COMMANDS[packageManager];\n}\n"]}
|