@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.
@@ -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
- vercel: 'npx vercel --prod',
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":";;AAYA,iCA0BC;AAtCD,iDAAyC;AAKzC,MAAM,eAAe,GAA2B;IAC9C,IAAI,EAAE,8BAA8B;IACpC,MAAM,EAAE,mBAAmB;IAC3B,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 vercel: 'npx vercel --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"]}
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 importPath = schema.importPath ?? `@frontmcp/${fileName}`;
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":";;AAqBA,4CA6BC;AAlDD,uCAAiE;AAEjE,sEAAyH;AAmBzH,SAAgB,gBAAgB,CAAC,IAAU,EAAE,MAA0B;IACrE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,IAAA,sCAAmB,EAAC,MAAM,CAAC,SAAS,IAAI,IAAA,0BAAiB,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7G,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,SAAS,CAAC;IAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,KAAK,CAAC;IAChD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,aAAa,QAAQ,EAAE,CAAC;IAChE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI;QAC5B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IAEnB,IAAI,WAAW,EAAE,CAAC;QAChB,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IAED,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,QAAQ;QACrB,WAAW;QACX,MAAM;QACN,GAAG,IAAA,sCAAmB,EAAC,IAAI,CAAC;QAC5B,SAAS;QACT,QAAQ;QACR,YAAY;QACZ,OAAO;QACP,WAAW;QACX,UAAU;QACV,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,KAAK;KACvC,CAAC;AACJ,CAAC","sourcesContent":["import { type Tree, names, joinPathFragments } 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\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 importPath = schema.importPath ?? `@frontmcp/${fileName}`;\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"]}
1
+ {"version":3,"file":"normalize-options.js","sourceRoot":"","sources":["../../../../../src/generators/lib/lib/normalize-options.ts"],"names":[],"mappings":";;AAwEA,4CA+BC;AAvGD,uCAA2E;AAE3E,sEAAyH;AAyBzH,SAAS,qBAAqB,CAAC,IAAU;IACvC,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAA,iBAAQ,EAAoB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED,2FAA2F;AAC3F,SAAS,iBAAiB,CAAC,SAA4B,EAAE,QAAgB;IACvE,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AACnD,CAAC;AAED,8HAA8H;AAC9H,SAAS,cAAc,CAAC,KAA+B,EAAE,UAAkB;IACzE,IAAI,KAAK,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IACzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACrC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjD,OAAO,CACL,MAAM,KAAK,SAAS;YACpB,IAAI,CAAC,MAAM,KAAK,CAAC;YACjB,UAAU,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;YAClD,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC;YAC7B,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC5B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2GAA2G;AAC3G,SAAS,sBAAsB,CAAC,IAAU,EAAE,SAA4B,EAAE,UAAkB;IAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;QAC7C,CAAC,CAAC,CAAC,IAAA,iBAAQ,EAA6D,IAAI,EAAE,oBAAoB,CAAC,CAAC,eAAe;YAC/G,EAAE,KAAK,IAAI,EAAE,CAAC;QAClB,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACrD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,UAAU,GAAG,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,oBAAoB,UAAU,4CAA4C,GAAG,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,wCAAwC,CACzJ,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,eAAe,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CACb,oBAAoB,UAAU,6GAA6G,CAC5I,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,gBAAgB,CAAC,IAAU,EAAE,MAA0B;IACrE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,IAAA,sCAAmB,EAAC,MAAM,CAAC,SAAS,IAAI,IAAA,0BAAiB,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7G,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,SAAS,CAAC;IAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,KAAK,CAAC;IAChD,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,iBAAiB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC/E,sBAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI;QAC5B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IAEnB,IAAI,WAAW,EAAE,CAAC;QAChB,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IAED,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,QAAQ;QACrB,WAAW;QACX,MAAM;QACN,GAAG,IAAA,sCAAmB,EAAC,IAAI,CAAC;QAC5B,SAAS;QACT,QAAQ;QACR,YAAY;QACZ,OAAO;QACP,WAAW;QACX,UAAU;QACV,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,KAAK;KACvC,CAAC;AACJ,CAAC","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). Required for publishable libraries."
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-09-23"
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
- # The bundle requires @codegenie/serverless-express at runtime without bundling it: add a Lambda layer that provides it.
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
- COPY package*.json ./
5
- RUN npm ci --ignore-scripts
6
- COPY . .
7
- RUN npx nx build <%= projectName %>
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
- const installTask = options.deploymentTarget === 'lambda'
34
- ? (0, add_dependencies_js_1.addFrontmcpDependencies)(tree, (0, versions_js_1.getLambdaDependencies)(), {}, { keepExistingVersions: true })
35
- : undefined;
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
- return installTask;
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":"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","sourcesContent":["import * as fs from 'fs';\nimport { join } from 'path';\n\nimport { formatFiles, generateFiles, names as nxNames, type GeneratorCallback, type Tree } from '@nx/devkit';\n\nimport { addFrontmcpDependencies } from '../../utils/add-dependencies.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\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 const installTask =\n options.deploymentTarget === 'lambda'\n ? addFrontmcpDependencies(tree, getLambdaDependencies(), {}, { keepExistingVersions: true })\n : undefined;\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return installTask;\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"]}
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: buildTargets(packageRoot),
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, ...buildDevDependencies } = (0, versions_js_1.getUiBuildDevDependencies)();
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.1",
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.1",
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;
@@ -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: { install: 'npm install', exec: 'npx' },
8
- yarn: { install: 'yarn install', exec: 'yarn' },
9
- pnpm: { install: 'pnpm install', exec: 'pnpm exec' },
10
- bun: { install: 'bun install', exec: 'bunx' },
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
- return COMMANDS[detectWorkspacePackageManager(tree)];
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":";;AA+BA,sEAKC;AAED,8DAEC;AAxCD,uCAAmD;AAWnD,MAAM,QAAQ,GAA4D;IACxE,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE;IAC5C,IAAI,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE;IAC/C,IAAI,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE;IACpD,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE;CAC9C,CAAC;AAEF,MAAM,SAAS,GAA8D;IAC3E,CAAC,UAAU,EAAE,KAAK,CAAC;IACnB,CAAC,WAAW,EAAE,KAAK,CAAC;IACpB,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAC1B,CAAC,WAAW,EAAE,MAAM,CAAC;IACrB,CAAC,mBAAmB,EAAE,KAAK,CAAC;CAC7B,CAAC;AAEF,SAAS,gBAAgB,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC5F,CAAC;AAED,2GAA2G;AAC3G,SAAgB,6BAA6B,CAAC,IAAU;IACtD,MAAM,UAAU,GAAG,IAAA,mBAAU,EAAC,IAAI,CAAC,EAAE,GAAG,EAAE,cAAc,CAAC;IACzD,IAAI,gBAAgB,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAClC,CAAC;AAED,SAAgB,yBAAyB,CAAC,IAAU;IAClD,OAAO,QAAQ,CAAC,6BAA6B,CAAC,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC","sourcesContent":["import { readNxJson, type Tree } from '@nx/devkit';\n\nexport type WorkspacePackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';\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}\n\nconst COMMANDS: Record<WorkspacePackageManager, PackageManagerCommands> = {\n npm: { install: 'npm install', exec: 'npx' },\n yarn: { install: 'yarn install', exec: 'yarn' },\n pnpm: { install: 'pnpm install', exec: 'pnpm exec' },\n bun: { install: 'bun install', exec: 'bunx' },\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\nexport function getPackageManagerCommands(tree: Tree): PackageManagerCommands {\n return COMMANDS[detectWorkspacePackageManager(tree)];\n}\n"]}
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"]}