@frontmcp/nx 0.12.2 → 1.0.0-beta.2
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/generators/ui-component/files/__className__/__className__.spec.tsx__tmpl__ +17 -0
- package/generators/ui-component/files/__className__/__className__.tsx__tmpl__ +14 -0
- package/generators/ui-component/files/__className__/index.ts__tmpl__ +3 -0
- package/generators/ui-component/schema.d.ts +5 -0
- package/generators/ui-component/schema.js +3 -0
- package/generators/ui-component/schema.js.map +1 -0
- package/generators/ui-component/schema.json +27 -0
- package/generators/ui-component/ui-component.d.ts +4 -0
- package/generators/ui-component/ui-component.js +33 -0
- package/generators/ui-component/ui-component.js.map +1 -0
- package/generators/ui-page/files/__className__/__className__.spec.tsx__tmpl__ +17 -0
- package/generators/ui-page/files/__className__/__className__.tsx__tmpl__ +16 -0
- package/generators/ui-page/files/__className__/index.ts__tmpl__ +3 -0
- package/generators/ui-page/schema.d.ts +5 -0
- package/generators/ui-page/schema.js +3 -0
- package/generators/ui-page/schema.js.map +1 -0
- package/generators/ui-page/schema.json +27 -0
- package/generators/ui-page/ui-page.d.ts +4 -0
- package/generators/ui-page/ui-page.js +33 -0
- package/generators/ui-page/ui-page.js.map +1 -0
- package/generators/ui-shared/add-ui-entry.d.ts +16 -0
- package/generators/ui-shared/add-ui-entry.js +60 -0
- package/generators/ui-shared/add-ui-entry.js.map +1 -0
- package/generators/ui-shell/files/__fileName__/__fileName__.shell.spec.ts__tmpl__ +16 -0
- package/generators/ui-shell/files/__fileName__/__fileName__.shell.ts__tmpl__ +20 -0
- package/generators/ui-shell/files/__fileName__/index.ts__tmpl__ +2 -0
- package/generators/ui-shell/schema.d.ts +5 -0
- package/generators/ui-shell/schema.js +3 -0
- package/generators/ui-shell/schema.js.map +1 -0
- package/generators/ui-shell/schema.json +27 -0
- package/generators/ui-shell/ui-shell.d.ts +4 -0
- package/generators/ui-shell/ui-shell.js +30 -0
- package/generators/ui-shell/ui-shell.js.map +1 -0
- package/generators/workspace/files/AGENTS.md__tmpl__ +52 -0
- package/generators/workspace/files/CLAUDE.md__tmpl__ +216 -0
- package/generators/workspace/files/README.md__tmpl__ +70 -0
- package/generators/workspace/files/__dot__cursorrules__tmpl__ +3 -0
- package/generators/workspace/files/__dot__gitignore__tmpl__ +3 -0
- package/generators/workspace/files/__dot__mcp.json__tmpl__ +8 -0
- package/generators/workspace/files/__dot__nvmrc__tmpl__ +1 -0
- package/generators.json +15 -0
- package/index.d.ts +3 -0
- package/index.js +7 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { <%= className %> } from './<%= className %>';
|
|
3
|
+
|
|
4
|
+
describe('<%= className %>', () => {
|
|
5
|
+
it('should be defined', () => {
|
|
6
|
+
expect(<%= className %>).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should be a function component', () => {
|
|
10
|
+
expect(typeof <%= className %>).toBe('function');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should render without crashing', () => {
|
|
14
|
+
const { container } = render(<<%= className %> />);
|
|
15
|
+
expect(container.firstChild).not.toBeNull();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Typography } from '@mui/material';
|
|
3
|
+
|
|
4
|
+
export interface <%= className %>Props {
|
|
5
|
+
// TODO: define props
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function <%= className %>(props: <%= className %>Props) {
|
|
9
|
+
return (
|
|
10
|
+
<Box>
|
|
11
|
+
<Typography>TODO: implement <%= className %></Typography>
|
|
12
|
+
</Box>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../src/generators/ui-component/schema.ts"],"names":[],"mappings":"","sourcesContent":["export interface UiComponentGeneratorSchema {\n name: string;\n description?: string;\n skipFormat?: boolean;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "FrontMcpUiComponent",
|
|
4
|
+
"title": "FrontMCP UI Component Generator",
|
|
5
|
+
"description": "Add a UI component entry to ui/components",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Component name (PascalCase, e.g. LoginForm)",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
12
|
+
"x-prompt": "What name would you like for the component (PascalCase)?",
|
|
13
|
+
"x-priority": "important"
|
|
14
|
+
},
|
|
15
|
+
"description": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Brief description of the component",
|
|
18
|
+
"default": ""
|
|
19
|
+
},
|
|
20
|
+
"skipFormat": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false,
|
|
23
|
+
"x-priority": "internal"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["name"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Tree, type GeneratorCallback } from '@nx/devkit';
|
|
2
|
+
import type { UiComponentGeneratorSchema } from './schema.js';
|
|
3
|
+
export declare function uiComponentGenerator(tree: Tree, schema: UiComponentGeneratorSchema): Promise<GeneratorCallback | void>;
|
|
4
|
+
export default uiComponentGenerator;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uiComponentGenerator = uiComponentGenerator;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const add_ui_entry_js_1 = require("../ui-shared/add-ui-entry.js");
|
|
7
|
+
const PACKAGE_ROOT = 'ui/components';
|
|
8
|
+
const IMPORT_PATH = '@frontmcp/ui-components';
|
|
9
|
+
async function uiComponentGenerator(tree, schema) {
|
|
10
|
+
const trimmedName = schema.name?.trim();
|
|
11
|
+
if (!trimmedName) {
|
|
12
|
+
throw new Error('Generator name must not be blank');
|
|
13
|
+
}
|
|
14
|
+
const { className } = (0, devkit_1.names)(trimmedName);
|
|
15
|
+
// Generate component files from templates
|
|
16
|
+
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {
|
|
17
|
+
className,
|
|
18
|
+
name: schema.name,
|
|
19
|
+
description: schema.description ?? '',
|
|
20
|
+
tmpl: '',
|
|
21
|
+
});
|
|
22
|
+
// Add entry point to project.json, tsconfig.base.json, and barrel index.ts
|
|
23
|
+
(0, add_ui_entry_js_1.addUiEntry)(tree, {
|
|
24
|
+
packageRoot: PACKAGE_ROOT,
|
|
25
|
+
entryName: className,
|
|
26
|
+
importPath: IMPORT_PATH,
|
|
27
|
+
});
|
|
28
|
+
if (!schema.skipFormat) {
|
|
29
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.default = uiComponentGenerator;
|
|
33
|
+
//# sourceMappingURL=ui-component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-component.js","sourceRoot":"","sources":["../../../../src/generators/ui-component/ui-component.ts"],"names":[],"mappings":";;AAQA,oDA6BC;AArCD,uCAAkG;AAClG,+BAA4B;AAE5B,kEAA0D;AAE1D,MAAM,YAAY,GAAG,eAAe,CAAC;AACrC,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAEvC,KAAK,UAAU,oBAAoB,CACxC,IAAU,EACV,MAAkC;IAElC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,cAAK,EAAC,WAAW,CAAC,CAAC;IAEzC,0CAA0C;IAC1C,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,YAAY,MAAM,EAAE;QACnE,SAAS;QACT,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE;QACrC,IAAI,EAAE,EAAE;KACT,CAAC,CAAC;IAEH,2EAA2E;IAC3E,IAAA,4BAAU,EAAC,IAAI,EAAE;QACf,WAAW,EAAE,YAAY;QACzB,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,WAAW;KACxB,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,kBAAe,oBAAoB,CAAC","sourcesContent":["import { type Tree, formatFiles, generateFiles, names, type GeneratorCallback } from '@nx/devkit';\nimport { join } from 'path';\nimport type { UiComponentGeneratorSchema } from './schema.js';\nimport { addUiEntry } from '../ui-shared/add-ui-entry.js';\n\nconst PACKAGE_ROOT = 'ui/components';\nconst IMPORT_PATH = '@frontmcp/ui-components';\n\nexport async function uiComponentGenerator(\n tree: Tree,\n schema: UiComponentGeneratorSchema,\n): Promise<GeneratorCallback | void> {\n const trimmedName = schema.name?.trim();\n if (!trimmedName) {\n throw new Error('Generator name must not be blank');\n }\n\n const { className } = names(trimmedName);\n\n // Generate component files from templates\n generateFiles(tree, join(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {\n className,\n name: schema.name,\n description: schema.description ?? '',\n tmpl: '',\n });\n\n // Add entry point to project.json, tsconfig.base.json, and barrel index.ts\n addUiEntry(tree, {\n packageRoot: PACKAGE_ROOT,\n entryName: className,\n importPath: IMPORT_PATH,\n });\n\n if (!schema.skipFormat) {\n await formatFiles(tree);\n }\n}\n\nexport default uiComponentGenerator;\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { <%= className %> } from './<%= className %>';
|
|
3
|
+
|
|
4
|
+
describe('<%= className %>', () => {
|
|
5
|
+
it('should be defined', () => {
|
|
6
|
+
expect(<%= className %>).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should be a function component', () => {
|
|
10
|
+
expect(typeof <%= className %>).toBe('function');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should render without crashing', () => {
|
|
14
|
+
const { container } = render(<<%= className %> />);
|
|
15
|
+
expect(container.firstChild).not.toBeNull();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Box, Typography } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
export interface <%= className %>Props {
|
|
4
|
+
title?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function <%= className %>({ title = '<%= className %>' }: <%= className %>Props) {
|
|
8
|
+
return (
|
|
9
|
+
<Box sx={{ p: 3 }}>
|
|
10
|
+
<Typography variant="h4">{title}</Typography>
|
|
11
|
+
<Typography variant="body1" sx={{ mt: 2 }}>
|
|
12
|
+
TODO: implement <%= className %>
|
|
13
|
+
</Typography>
|
|
14
|
+
</Box>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../src/generators/ui-page/schema.ts"],"names":[],"mappings":"","sourcesContent":["export interface UiPageGeneratorSchema {\n name: string;\n description?: string;\n skipFormat?: boolean;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "FrontMcpUiPage",
|
|
4
|
+
"title": "FrontMCP UI Page Generator",
|
|
5
|
+
"description": "Add a UI page entry to ui/pages",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Page name (PascalCase, e.g. AdminDashboard)",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
12
|
+
"x-prompt": "What name would you like for the page (PascalCase)?",
|
|
13
|
+
"x-priority": "important"
|
|
14
|
+
},
|
|
15
|
+
"description": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Brief description of the page",
|
|
18
|
+
"default": ""
|
|
19
|
+
},
|
|
20
|
+
"skipFormat": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false,
|
|
23
|
+
"x-priority": "internal"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["name"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Tree, type GeneratorCallback } from '@nx/devkit';
|
|
2
|
+
import type { UiPageGeneratorSchema } from './schema.js';
|
|
3
|
+
export declare function uiPageGenerator(tree: Tree, schema: UiPageGeneratorSchema): Promise<GeneratorCallback | void>;
|
|
4
|
+
export default uiPageGenerator;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uiPageGenerator = uiPageGenerator;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const add_ui_entry_js_1 = require("../ui-shared/add-ui-entry.js");
|
|
7
|
+
const PACKAGE_ROOT = 'ui/pages';
|
|
8
|
+
const IMPORT_PATH = '@frontmcp/ui-pages';
|
|
9
|
+
async function uiPageGenerator(tree, schema) {
|
|
10
|
+
const trimmedName = schema.name?.trim();
|
|
11
|
+
if (!trimmedName) {
|
|
12
|
+
throw new Error('Generator name must not be blank');
|
|
13
|
+
}
|
|
14
|
+
const { className } = (0, devkit_1.names)(trimmedName);
|
|
15
|
+
// Generate page files from templates
|
|
16
|
+
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {
|
|
17
|
+
className,
|
|
18
|
+
name: schema.name,
|
|
19
|
+
description: schema.description ?? '',
|
|
20
|
+
tmpl: '',
|
|
21
|
+
});
|
|
22
|
+
// Add entry point to project.json, tsconfig.base.json, and barrel index.ts
|
|
23
|
+
(0, add_ui_entry_js_1.addUiEntry)(tree, {
|
|
24
|
+
packageRoot: PACKAGE_ROOT,
|
|
25
|
+
entryName: className,
|
|
26
|
+
importPath: IMPORT_PATH,
|
|
27
|
+
});
|
|
28
|
+
if (!schema.skipFormat) {
|
|
29
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.default = uiPageGenerator;
|
|
33
|
+
//# sourceMappingURL=ui-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-page.js","sourceRoot":"","sources":["../../../../src/generators/ui-page/ui-page.ts"],"names":[],"mappings":";;AAQA,0CA0BC;AAlCD,uCAAkG;AAClG,+BAA4B;AAE5B,kEAA0D;AAE1D,MAAM,YAAY,GAAG,UAAU,CAAC;AAChC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAElC,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,MAA6B;IAC7E,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,IAAA,cAAK,EAAC,WAAW,CAAC,CAAC;IAEzC,qCAAqC;IACrC,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,YAAY,MAAM,EAAE;QACnE,SAAS;QACT,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE;QACrC,IAAI,EAAE,EAAE;KACT,CAAC,CAAC;IAEH,2EAA2E;IAC3E,IAAA,4BAAU,EAAC,IAAI,EAAE;QACf,WAAW,EAAE,YAAY;QACzB,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,WAAW;KACxB,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,kBAAe,eAAe,CAAC","sourcesContent":["import { type Tree, formatFiles, generateFiles, names, type GeneratorCallback } from '@nx/devkit';\nimport { join } from 'path';\nimport type { UiPageGeneratorSchema } from './schema.js';\nimport { addUiEntry } from '../ui-shared/add-ui-entry.js';\n\nconst PACKAGE_ROOT = 'ui/pages';\nconst IMPORT_PATH = '@frontmcp/ui-pages';\n\nexport async function uiPageGenerator(tree: Tree, schema: UiPageGeneratorSchema): Promise<GeneratorCallback | void> {\n const trimmedName = schema.name?.trim();\n if (!trimmedName) {\n throw new Error('Generator name must not be blank');\n }\n\n const { className } = names(trimmedName);\n\n // Generate page files from templates\n generateFiles(tree, join(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {\n className,\n name: schema.name,\n description: schema.description ?? '',\n tmpl: '',\n });\n\n // Add entry point to project.json, tsconfig.base.json, and barrel index.ts\n addUiEntry(tree, {\n packageRoot: PACKAGE_ROOT,\n entryName: className,\n importPath: IMPORT_PATH,\n });\n\n if (!schema.skipFormat) {\n await formatFiles(tree);\n }\n}\n\nexport default uiPageGenerator;\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Tree } from '@nx/devkit';
|
|
2
|
+
export interface AddUiEntryOptions {
|
|
3
|
+
/** The UI package root, e.g. 'ui/components' */
|
|
4
|
+
packageRoot: string;
|
|
5
|
+
/** The entry name, e.g. 'LoginForm' or 'admin-dashboard' */
|
|
6
|
+
entryName: string;
|
|
7
|
+
/** The npm scope name, e.g. '@frontmcp/ui-components' */
|
|
8
|
+
importPath: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Adds an entry point to an existing UI package:
|
|
12
|
+
* 1. Updates project.json `additionalEntryPoints` in both build-cjs and build-esm
|
|
13
|
+
* 2. Updates tsconfig.base.json path aliases (wildcard already covers it, but explicit is fine)
|
|
14
|
+
* 3. Adds re-export to the barrel index.ts
|
|
15
|
+
*/
|
|
16
|
+
export declare function addUiEntry(tree: Tree, options: AddUiEntryOptions): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addUiEntry = addUiEntry;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
function escapeRegExp(str) {
|
|
6
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Adds an entry point to an existing UI package:
|
|
10
|
+
* 1. Updates project.json `additionalEntryPoints` in both build-cjs and build-esm
|
|
11
|
+
* 2. Updates tsconfig.base.json path aliases (wildcard already covers it, but explicit is fine)
|
|
12
|
+
* 3. Adds re-export to the barrel index.ts
|
|
13
|
+
*/
|
|
14
|
+
function addUiEntry(tree, options) {
|
|
15
|
+
const { packageRoot, entryName, importPath } = options;
|
|
16
|
+
const entryPath = `${packageRoot}/src/${entryName}/index.ts`;
|
|
17
|
+
// 1. Update project.json — add to additionalEntryPoints in build-cjs and build-esm
|
|
18
|
+
const projectJsonPath = `${packageRoot}/project.json`;
|
|
19
|
+
if (tree.exists(projectJsonPath)) {
|
|
20
|
+
(0, devkit_1.updateJson)(tree, projectJsonPath, (json) => {
|
|
21
|
+
for (const target of ['build-cjs', 'build-esm']) {
|
|
22
|
+
const opts = json.targets?.[target]?.options;
|
|
23
|
+
if (opts) {
|
|
24
|
+
opts.additionalEntryPoints = opts.additionalEntryPoints ?? [];
|
|
25
|
+
if (!opts.additionalEntryPoints.includes(entryPath)) {
|
|
26
|
+
opts.additionalEntryPoints.push(entryPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return json;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// 2. Update tsconfig.base.json — add explicit path alias
|
|
34
|
+
// The wildcard alias already covers resolution, but adding an explicit
|
|
35
|
+
// entry improves IDE completion and Nx dep graph accuracy.
|
|
36
|
+
if (tree.exists('tsconfig.base.json')) {
|
|
37
|
+
(0, devkit_1.updateJson)(tree, 'tsconfig.base.json', (json) => {
|
|
38
|
+
const paths = json.compilerOptions?.paths ?? {};
|
|
39
|
+
const aliasKey = `${importPath}/${entryName}`;
|
|
40
|
+
if (!paths[aliasKey]) {
|
|
41
|
+
paths[aliasKey] = [`${packageRoot}/src/${entryName}/index.ts`];
|
|
42
|
+
}
|
|
43
|
+
json.compilerOptions = json.compilerOptions ?? {};
|
|
44
|
+
json.compilerOptions.paths = paths;
|
|
45
|
+
return json;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// 3. Add re-export to barrel index.ts
|
|
49
|
+
const barrelPath = `${packageRoot}/src/index.ts`;
|
|
50
|
+
if (tree.exists(barrelPath)) {
|
|
51
|
+
const existing = tree.read(barrelPath, 'utf-8') ?? '';
|
|
52
|
+
const exportLine = `export * from './${entryName}';\n`;
|
|
53
|
+
const duplicatePattern = new RegExp(`^export \\* from '\\./${escapeRegExp(entryName)}';?$`, 'm');
|
|
54
|
+
if (!duplicatePattern.test(existing)) {
|
|
55
|
+
const normalized = existing.endsWith('\n') || existing === '' ? existing : existing + '\n';
|
|
56
|
+
tree.write(barrelPath, normalized + exportLine);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=add-ui-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-ui-entry.js","sourceRoot":"","sources":["../../../../src/generators/ui-shared/add-ui-entry.ts"],"names":[],"mappings":";;AAqBA,gCAgDC;AArED,uCAAmD;AAEnD,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAWD;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,IAAU,EAAE,OAA0B;IAC/D,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,SAAS,GAAG,GAAG,WAAW,QAAQ,SAAS,WAAW,CAAC;IAE7D,mFAAmF;IACnF,MAAM,eAAe,GAAG,GAAG,WAAW,eAAe,CAAC;IACtD,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,IAAA,mBAAU,EAAC,IAAI,EAAE,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE;YACzC,KAAK,MAAM,MAAM,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;gBAC7C,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,IAAI,EAAE,CAAC;oBAC9D,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;wBACpD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC7C,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,yDAAyD;IACzD,0EAA0E;IAC1E,8DAA8D;IAC9D,IAAI,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACtC,IAAA,mBAAU,EAAC,IAAI,EAAE,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,QAAQ,GAAG,GAAG,UAAU,IAAI,SAAS,EAAE,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,WAAW,QAAQ,SAAS,WAAW,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC;YAClD,IAAI,CAAC,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,sCAAsC;IACtC,MAAM,UAAU,GAAG,GAAG,WAAW,eAAe,CAAC;IACjD,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,UAAU,GAAG,oBAAoB,SAAS,MAAM,CAAC;QACvD,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,yBAAyB,YAAY,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC;YAC3F,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { type Tree, updateJson } from '@nx/devkit';\n\nfunction escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport interface AddUiEntryOptions {\n /** The UI package root, e.g. 'ui/components' */\n packageRoot: string;\n /** The entry name, e.g. 'LoginForm' or 'admin-dashboard' */\n entryName: string;\n /** The npm scope name, e.g. '@frontmcp/ui-components' */\n importPath: string;\n}\n\n/**\n * Adds an entry point to an existing UI package:\n * 1. Updates project.json `additionalEntryPoints` in both build-cjs and build-esm\n * 2. Updates tsconfig.base.json path aliases (wildcard already covers it, but explicit is fine)\n * 3. Adds re-export to the barrel index.ts\n */\nexport function addUiEntry(tree: Tree, options: AddUiEntryOptions): void {\n const { packageRoot, entryName, importPath } = options;\n const entryPath = `${packageRoot}/src/${entryName}/index.ts`;\n\n // 1. Update project.json — add to additionalEntryPoints in build-cjs and build-esm\n const projectJsonPath = `${packageRoot}/project.json`;\n if (tree.exists(projectJsonPath)) {\n updateJson(tree, projectJsonPath, (json) => {\n for (const target of ['build-cjs', 'build-esm']) {\n const opts = json.targets?.[target]?.options;\n if (opts) {\n opts.additionalEntryPoints = opts.additionalEntryPoints ?? [];\n if (!opts.additionalEntryPoints.includes(entryPath)) {\n opts.additionalEntryPoints.push(entryPath);\n }\n }\n }\n return json;\n });\n }\n\n // 2. Update tsconfig.base.json — add explicit path alias\n // The wildcard alias already covers resolution, but adding an explicit\n // entry improves IDE completion and Nx dep graph accuracy.\n if (tree.exists('tsconfig.base.json')) {\n updateJson(tree, 'tsconfig.base.json', (json) => {\n const paths = json.compilerOptions?.paths ?? {};\n const aliasKey = `${importPath}/${entryName}`;\n if (!paths[aliasKey]) {\n paths[aliasKey] = [`${packageRoot}/src/${entryName}/index.ts`];\n }\n json.compilerOptions = json.compilerOptions ?? {};\n json.compilerOptions.paths = paths;\n return json;\n });\n }\n\n // 3. Add re-export to barrel index.ts\n const barrelPath = `${packageRoot}/src/index.ts`;\n if (tree.exists(barrelPath)) {\n const existing = tree.read(barrelPath, 'utf-8') ?? '';\n const exportLine = `export * from './${entryName}';\\n`;\n const duplicatePattern = new RegExp(`^export \\\\* from '\\\\./${escapeRegExp(entryName)}';?$`, 'm');\n if (!duplicatePattern.test(existing)) {\n const normalized = existing.endsWith('\\n') || existing === '' ? existing : existing + '\\n';\n tree.write(barrelPath, normalized + exportLine);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { build<%= className %>Shell } from './<%= fileName %>.shell';
|
|
2
|
+
|
|
3
|
+
describe('build<%= className %>Shell', () => {
|
|
4
|
+
it('should be defined', () => {
|
|
5
|
+
expect(build<%= className %>Shell).toBeDefined();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('should be a function', () => {
|
|
9
|
+
expect(typeof build<%= className %>Shell).toBe('function');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should return an HTML string', () => {
|
|
13
|
+
const result = build<%= className %>Shell({ toolName: 'test' });
|
|
14
|
+
expect(typeof result).toBe('string');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { buildShell } from '@frontmcp/uipack';
|
|
2
|
+
|
|
3
|
+
function escapeHtml(str: string): string {
|
|
4
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface <%= className %>ShellOptions {
|
|
8
|
+
toolName: string;
|
|
9
|
+
input?: unknown;
|
|
10
|
+
output?: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function build<%= className %>Shell(options: <%= className %>ShellOptions) {
|
|
14
|
+
const content = `<div id="app">TODO: implement ${escapeHtml(options.toolName)} shell</div>`;
|
|
15
|
+
return buildShell(content, {
|
|
16
|
+
toolName: options.toolName,
|
|
17
|
+
input: options.input,
|
|
18
|
+
output: options.output,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../src/generators/ui-shell/schema.ts"],"names":[],"mappings":"","sourcesContent":["export interface UiShellGeneratorSchema {\n name: string;\n description?: string;\n skipFormat?: boolean;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "FrontMcpUiShell",
|
|
4
|
+
"title": "FrontMCP UI Shell Generator",
|
|
5
|
+
"description": "Add a shell template entry to ui/shells",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Shell name (kebab-case, e.g. admin-dashboard)",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
12
|
+
"x-prompt": "What name would you like for the shell (kebab-case)?",
|
|
13
|
+
"x-priority": "important"
|
|
14
|
+
},
|
|
15
|
+
"description": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Brief description of the shell template",
|
|
18
|
+
"default": ""
|
|
19
|
+
},
|
|
20
|
+
"skipFormat": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false,
|
|
23
|
+
"x-priority": "internal"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["name"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Tree, type GeneratorCallback } from '@nx/devkit';
|
|
2
|
+
import type { UiShellGeneratorSchema } from './schema.js';
|
|
3
|
+
export declare function uiShellGenerator(tree: Tree, schema: UiShellGeneratorSchema): Promise<GeneratorCallback | void>;
|
|
4
|
+
export default uiShellGenerator;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uiShellGenerator = uiShellGenerator;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const add_ui_entry_js_1 = require("../ui-shared/add-ui-entry.js");
|
|
7
|
+
const PACKAGE_ROOT = 'ui/shells';
|
|
8
|
+
const IMPORT_PATH = '@frontmcp/ui-shells';
|
|
9
|
+
async function uiShellGenerator(tree, schema) {
|
|
10
|
+
const { className, fileName } = (0, devkit_1.names)(schema.name);
|
|
11
|
+
// Generate shell files from templates (kebab-case naming)
|
|
12
|
+
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {
|
|
13
|
+
className,
|
|
14
|
+
fileName,
|
|
15
|
+
name: schema.name,
|
|
16
|
+
description: schema.description ?? '',
|
|
17
|
+
tmpl: '',
|
|
18
|
+
});
|
|
19
|
+
// Add entry point to project.json, tsconfig.base.json, and barrel index.ts
|
|
20
|
+
(0, add_ui_entry_js_1.addUiEntry)(tree, {
|
|
21
|
+
packageRoot: PACKAGE_ROOT,
|
|
22
|
+
entryName: fileName,
|
|
23
|
+
importPath: IMPORT_PATH,
|
|
24
|
+
});
|
|
25
|
+
if (!schema.skipFormat) {
|
|
26
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.default = uiShellGenerator;
|
|
30
|
+
//# sourceMappingURL=ui-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-shell.js","sourceRoot":"","sources":["../../../../src/generators/ui-shell/ui-shell.ts"],"names":[],"mappings":";;AAQA,4CAsBC;AA9BD,uCAAkG;AAClG,+BAA4B;AAE5B,kEAA0D;AAE1D,MAAM,YAAY,GAAG,WAAW,CAAC;AACjC,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAEnC,KAAK,UAAU,gBAAgB,CAAC,IAAU,EAAE,MAA8B;IAC/E,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEnD,0DAA0D;IAC1D,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,YAAY,MAAM,EAAE;QACnE,SAAS;QACT,QAAQ;QACR,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE;QACrC,IAAI,EAAE,EAAE;KACT,CAAC,CAAC;IAEH,2EAA2E;IAC3E,IAAA,4BAAU,EAAC,IAAI,EAAE;QACf,WAAW,EAAE,YAAY;QACzB,SAAS,EAAE,QAAQ;QACnB,UAAU,EAAE,WAAW;KACxB,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,kBAAe,gBAAgB,CAAC","sourcesContent":["import { type Tree, formatFiles, generateFiles, names, type GeneratorCallback } from '@nx/devkit';\nimport { join } from 'path';\nimport type { UiShellGeneratorSchema } from './schema.js';\nimport { addUiEntry } from '../ui-shared/add-ui-entry.js';\n\nconst PACKAGE_ROOT = 'ui/shells';\nconst IMPORT_PATH = '@frontmcp/ui-shells';\n\nexport async function uiShellGenerator(tree: Tree, schema: UiShellGeneratorSchema): Promise<GeneratorCallback | void> {\n const { className, fileName } = names(schema.name);\n\n // Generate shell files from templates (kebab-case naming)\n generateFiles(tree, join(__dirname, 'files'), `${PACKAGE_ROOT}/src`, {\n className,\n fileName,\n name: schema.name,\n description: schema.description ?? '',\n tmpl: '',\n });\n\n // Add entry point to project.json, tsconfig.base.json, and barrel index.ts\n addUiEntry(tree, {\n packageRoot: PACKAGE_ROOT,\n entryName: fileName,\n importPath: IMPORT_PATH,\n });\n\n if (!schema.skipFormat) {\n await formatFiles(tree);\n }\n}\n\nexport default uiShellGenerator;\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# <%= name %> — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Type
|
|
4
|
+
|
|
5
|
+
FrontMCP monorepo — TypeScript-first framework for building MCP (Model Context Protocol) servers.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
- **Monorepo**: Nx-based with `apps/`, `libs/`, `servers/` workspaces
|
|
10
|
+
- **Language**: TypeScript with strict mode enabled
|
|
11
|
+
- **Build system**: Nx (`npx nx build/test/serve <project>`)
|
|
12
|
+
- **Testing**: Jest with 95%+ coverage requirement
|
|
13
|
+
- **Package manager**: <%= packageManager %>
|
|
14
|
+
|
|
15
|
+
## Code Generation
|
|
16
|
+
|
|
17
|
+
**Always use Nx generators** — never create files manually:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx nx g @frontmcp/nx:tool <Name> --project <lib>
|
|
21
|
+
npx nx g @frontmcp/nx:resource <Name> --project <lib>
|
|
22
|
+
npx nx g @frontmcp/nx:prompt <Name> --project <lib>
|
|
23
|
+
npx nx g @frontmcp/nx:provider <Name> --project <lib>
|
|
24
|
+
npx nx g @frontmcp/nx:app <name>
|
|
25
|
+
npx nx g @frontmcp/nx:lib <name>
|
|
26
|
+
npx nx g @frontmcp/nx:server <name>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
See `CLAUDE.md` for the full list of 19 generators.
|
|
30
|
+
|
|
31
|
+
## Key Decorators & Context Classes
|
|
32
|
+
|
|
33
|
+
| Decorator | Context Class | Purpose |
|
|
34
|
+
|-----------|---------------|---------|
|
|
35
|
+
| `@Tool` | `ToolContext` | MCP tool with Zod input/output |
|
|
36
|
+
| `@Resource` / `@ResourceTemplate` | `ResourceContext` | MCP resource |
|
|
37
|
+
| `@Prompt` | `PromptContext` | MCP prompt returning `GetPromptResult` |
|
|
38
|
+
| `@Provider` | — | DI provider with Symbol token |
|
|
39
|
+
| `@App` | — | Application grouping tools/resources/prompts |
|
|
40
|
+
| `@FrontMcp` | — | Server entry point |
|
|
41
|
+
|
|
42
|
+
## Constraints
|
|
43
|
+
|
|
44
|
+
- No `any` types — use `unknown` for generic defaults
|
|
45
|
+
- No raw `node:crypto` or `fs` — use `@frontmcp/utils`
|
|
46
|
+
- No non-null assertions (`!`) — throw proper errors
|
|
47
|
+
- Zod v4 for all schema validation
|
|
48
|
+
- `@frontmcp/testing` for test harnesses
|
|
49
|
+
|
|
50
|
+
## Documentation Access
|
|
51
|
+
|
|
52
|
+
The `.mcp.json` file configures a `frontmcp-docs` MCP server. Use the `SearchAgentFront` tool to search FrontMCP documentation.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# <%= name %> — FrontMCP Workspace
|
|
2
|
+
|
|
3
|
+
This is a **FrontMCP** monorepo — a TypeScript-first framework for building production-grade MCP (Model Context Protocol) servers with decorators, dependency injection, and strict type safety.
|
|
4
|
+
|
|
5
|
+
## Monorepo Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<%= name %>/
|
|
9
|
+
apps/ # MCP application definitions (@App decorator)
|
|
10
|
+
libs/ # Shared libraries (tools, resources, providers)
|
|
11
|
+
servers/ # Deployment shells (Express/Fastify adapters)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Key Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Build / test / serve a specific project
|
|
18
|
+
npx nx build <project>
|
|
19
|
+
npx nx test <project>
|
|
20
|
+
npx nx serve <project>
|
|
21
|
+
|
|
22
|
+
# Run across all projects
|
|
23
|
+
npx nx run-many -t build
|
|
24
|
+
npx nx run-many -t test
|
|
25
|
+
|
|
26
|
+
# Start dev server with watch mode
|
|
27
|
+
npx nx dev <server-project>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Nx Generators
|
|
31
|
+
|
|
32
|
+
Always use generators to scaffold new code — never create files manually.
|
|
33
|
+
|
|
34
|
+
### Structural generators
|
|
35
|
+
|
|
36
|
+
| Generator | Command | Description |
|
|
37
|
+
|-----------|---------|-------------|
|
|
38
|
+
| workspace | `npx nx g @frontmcp/nx:workspace <name>` | New FrontMCP monorepo |
|
|
39
|
+
| app | `npx nx g @frontmcp/nx:app <name>` | MCP application |
|
|
40
|
+
| lib | `npx nx g @frontmcp/nx:lib <name>` | Shared library |
|
|
41
|
+
| server | `npx nx g @frontmcp/nx:server <name>` | Deployment shell |
|
|
42
|
+
|
|
43
|
+
### Component generators (use `--project` to target a library)
|
|
44
|
+
|
|
45
|
+
| Generator | Command | Description |
|
|
46
|
+
|-----------|---------|-------------|
|
|
47
|
+
| tool | `npx nx g @frontmcp/nx:tool <Name> --project <lib>` | MCP tool with Zod schemas |
|
|
48
|
+
| resource | `npx nx g @frontmcp/nx:resource <Name> --project <lib>` | MCP resource |
|
|
49
|
+
| prompt | `npx nx g @frontmcp/nx:prompt <Name> --project <lib>` | MCP prompt |
|
|
50
|
+
| skill | `npx nx g @frontmcp/nx:skill <Name> --project <lib>` | Composite skill |
|
|
51
|
+
| agent | `npx nx g @frontmcp/nx:agent <Name> --project <lib>` | Agent definition |
|
|
52
|
+
| provider | `npx nx g @frontmcp/nx:provider <Name> --project <lib>` | DI provider |
|
|
53
|
+
| plugin | `npx nx g @frontmcp/nx:plugin <Name> --project <lib>` | Plugin |
|
|
54
|
+
| adapter | `npx nx g @frontmcp/nx:adapter <Name> --project <lib>` | Framework adapter |
|
|
55
|
+
| auth-provider | `npx nx g @frontmcp/nx:auth-provider <Name> --project <lib>` | Auth provider |
|
|
56
|
+
| flow | `npx nx g @frontmcp/nx:flow <Name> --project <lib>` | Flow definition |
|
|
57
|
+
| job | `npx nx g @frontmcp/nx:job <Name> --project <lib>` | Background job |
|
|
58
|
+
| workflow | `npx nx g @frontmcp/nx:workflow <Name> --project <lib>` | Multi-step workflow |
|
|
59
|
+
|
|
60
|
+
### UI generators
|
|
61
|
+
|
|
62
|
+
| Generator | Command | Description |
|
|
63
|
+
|-----------|---------|-------------|
|
|
64
|
+
| ui-component | `npx nx g @frontmcp/nx:ui-component <Name>` | React component (PascalCase) |
|
|
65
|
+
| ui-page | `npx nx g @frontmcp/nx:ui-page <Name>` | Full-page React component |
|
|
66
|
+
| ui-shell | `npx nx g @frontmcp/nx:ui-shell <name>` | Server-side HTML shell (kebab-case) |
|
|
67
|
+
|
|
68
|
+
## Architecture Patterns
|
|
69
|
+
|
|
70
|
+
### Server entry point (`main.ts`)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { FrontMcp, StdioTransport } from '@frontmcp/sdk';
|
|
74
|
+
import { MyApp } from './my-app.app';
|
|
75
|
+
|
|
76
|
+
@FrontMcp({
|
|
77
|
+
name: 'my-server',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
apps: [MyApp],
|
|
80
|
+
})
|
|
81
|
+
class MyServer {}
|
|
82
|
+
|
|
83
|
+
new StdioTransport(MyServer).start();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### App definition
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { App } from '@frontmcp/sdk';
|
|
90
|
+
import { MyTool } from './tools/my-tool.tool';
|
|
91
|
+
import { MyResource } from './resources/my-resource.resource';
|
|
92
|
+
|
|
93
|
+
@App({
|
|
94
|
+
tools: [MyTool],
|
|
95
|
+
resources: [MyResource],
|
|
96
|
+
prompts: [],
|
|
97
|
+
providers: [],
|
|
98
|
+
})
|
|
99
|
+
export class MyApp {}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Tool (most common pattern)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { Tool, ToolContext } from '@frontmcp/sdk';
|
|
106
|
+
import { z } from 'zod';
|
|
107
|
+
|
|
108
|
+
const inputSchema = z.object({
|
|
109
|
+
query: z.string().describe('Search query'),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const outputSchema = z.object({
|
|
113
|
+
results: z.array(z.string()),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
@Tool({
|
|
117
|
+
name: 'search',
|
|
118
|
+
description: 'Search for items',
|
|
119
|
+
inputSchema,
|
|
120
|
+
outputSchema,
|
|
121
|
+
})
|
|
122
|
+
export class SearchTool extends ToolContext<typeof inputSchema, typeof outputSchema> {
|
|
123
|
+
async execute(input: z.infer<typeof inputSchema>) {
|
|
124
|
+
const results = ['result1', 'result2'];
|
|
125
|
+
return { results };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Resource
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { Resource, ResourceContext } from '@frontmcp/sdk';
|
|
134
|
+
|
|
135
|
+
@Resource({
|
|
136
|
+
uri: 'myapp://config',
|
|
137
|
+
name: 'App Configuration',
|
|
138
|
+
mimeType: 'application/json',
|
|
139
|
+
})
|
|
140
|
+
export class ConfigResource extends ResourceContext {
|
|
141
|
+
async read() {
|
|
142
|
+
return { uri: 'myapp://config', text: JSON.stringify({ key: 'value' }) };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Resource Template (dynamic URIs)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { ResourceTemplate, ResourceContext } from '@frontmcp/sdk';
|
|
151
|
+
|
|
152
|
+
@ResourceTemplate({
|
|
153
|
+
uriTemplate: 'myapp://users/{userId}',
|
|
154
|
+
name: 'User Profile',
|
|
155
|
+
mimeType: 'application/json',
|
|
156
|
+
})
|
|
157
|
+
export class UserResource extends ResourceContext<{ userId: string }> {
|
|
158
|
+
async read(params: { userId: string }) {
|
|
159
|
+
return { uri: `myapp://users/${params.userId}`, text: JSON.stringify({ id: params.userId }) };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Prompt
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { Prompt, PromptContext } from '@frontmcp/sdk';
|
|
168
|
+
import type { GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
|
|
169
|
+
|
|
170
|
+
@Prompt({
|
|
171
|
+
name: 'summarize',
|
|
172
|
+
description: 'Summarize content',
|
|
173
|
+
})
|
|
174
|
+
export class SummarizePrompt extends PromptContext {
|
|
175
|
+
async execute(args: Record<string, string>): Promise<GetPromptResult> {
|
|
176
|
+
return {
|
|
177
|
+
messages: [
|
|
178
|
+
{ role: 'user', content: { type: 'text', text: `Summarize: ${args['content']}` } },
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Provider (dependency injection)
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { Provider } from '@frontmcp/sdk';
|
|
189
|
+
|
|
190
|
+
export const DB_TOKEN = Symbol('DB');
|
|
191
|
+
|
|
192
|
+
@Provider({ token: DB_TOKEN })
|
|
193
|
+
export class DatabaseProvider {
|
|
194
|
+
provide() {
|
|
195
|
+
return new DatabaseClient({ url: process.env['DATABASE_URL'] });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// In a tool, access via this.get(DB_TOKEN):
|
|
200
|
+
async execute(input) {
|
|
201
|
+
const db = this.get(DB_TOKEN);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Code Conventions
|
|
206
|
+
|
|
207
|
+
- **TypeScript strict mode** — no `any` types without justification; prefer `unknown`
|
|
208
|
+
- **Zod v4** for all input/output schema validation
|
|
209
|
+
- **`@frontmcp/utils`** for crypto and file-system operations (never use `node:crypto` or `fs` directly)
|
|
210
|
+
- **95%+ test coverage** required (statements, branches, functions, lines)
|
|
211
|
+
- **`@frontmcp/testing`** for test harnesses (`createTestHarness`, `createToolTestHarness`)
|
|
212
|
+
- **No non-null assertions** (`!`) — throw proper errors instead
|
|
213
|
+
|
|
214
|
+
## Documentation
|
|
215
|
+
|
|
216
|
+
This workspace includes a `.mcp.json` that configures the **frontmcp-docs** MCP server. AI coding agents can use the `SearchAgentFront` tool to search the full FrontMCP documentation at any time.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# <%= name %>
|
|
2
|
+
|
|
3
|
+
A FrontMCP Nx monorepo — TypeScript-first framework for building production-grade MCP servers.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
<%= packageManager %> install
|
|
10
|
+
|
|
11
|
+
# Start dev server (sample app)
|
|
12
|
+
npx nx dev demo
|
|
13
|
+
|
|
14
|
+
# Build all projects
|
|
15
|
+
npx nx run-many -t build
|
|
16
|
+
|
|
17
|
+
# Run all tests
|
|
18
|
+
npx nx run-many -t test
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<%= name %>/
|
|
25
|
+
apps/ # MCP application definitions (@App decorator)
|
|
26
|
+
libs/ # Shared libraries (tools, resources, providers)
|
|
27
|
+
servers/ # Deployment shells (Express/Fastify adapters)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Nx Generators
|
|
31
|
+
|
|
32
|
+
Use generators to scaffold new code:
|
|
33
|
+
|
|
34
|
+
| Generator | Command | Description |
|
|
35
|
+
|-----------|---------|-------------|
|
|
36
|
+
| app | `npx nx g @frontmcp/nx:app <name>` | MCP application |
|
|
37
|
+
| lib | `npx nx g @frontmcp/nx:lib <name>` | Shared library |
|
|
38
|
+
| server | `npx nx g @frontmcp/nx:server <name>` | Deployment shell |
|
|
39
|
+
| tool | `npx nx g @frontmcp/nx:tool <Name> --project <lib>` | MCP tool with Zod schemas |
|
|
40
|
+
| resource | `npx nx g @frontmcp/nx:resource <Name> --project <lib>` | MCP resource |
|
|
41
|
+
| prompt | `npx nx g @frontmcp/nx:prompt <Name> --project <lib>` | MCP prompt |
|
|
42
|
+
| provider | `npx nx g @frontmcp/nx:provider <Name> --project <lib>` | DI provider |
|
|
43
|
+
|
|
44
|
+
See `CLAUDE.md` for the full list of generators and architecture patterns.
|
|
45
|
+
|
|
46
|
+
## Development Commands
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Build a specific project
|
|
50
|
+
npx nx build <project>
|
|
51
|
+
|
|
52
|
+
# Test a specific project
|
|
53
|
+
npx nx test <project>
|
|
54
|
+
|
|
55
|
+
# Serve a project with watch
|
|
56
|
+
npx nx dev <project>
|
|
57
|
+
|
|
58
|
+
# Launch MCP Inspector
|
|
59
|
+
npx nx inspector <project>
|
|
60
|
+
|
|
61
|
+
# Run across all projects
|
|
62
|
+
npx nx run-many -t build
|
|
63
|
+
npx nx run-many -t test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Learn More
|
|
67
|
+
|
|
68
|
+
- [FrontMCP Documentation](https://docs.agentfront.dev)
|
|
69
|
+
- [MCP Specification](https://modelcontextprotocol.io)
|
|
70
|
+
- AI coding agents can use the `frontmcp-docs` MCP server configured in `.mcp.json`
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Read CLAUDE.md for full project instructions, architecture patterns, and code conventions.
|
|
2
|
+
Always use Nx generators (`npx nx g @frontmcp/nx:<generator>`) — never create files manually.
|
|
3
|
+
Use the frontmcp-docs MCP server (configured in .mcp.json) to search FrontMCP documentation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
package/generators.json
CHANGED
|
@@ -79,6 +79,21 @@
|
|
|
79
79
|
"factory": "./generators/workflow/workflow",
|
|
80
80
|
"schema": "./generators/workflow/schema.json",
|
|
81
81
|
"description": "Generate a @Workflow class"
|
|
82
|
+
},
|
|
83
|
+
"ui-component": {
|
|
84
|
+
"factory": "./generators/ui-component/ui-component",
|
|
85
|
+
"schema": "./generators/ui-component/schema.json",
|
|
86
|
+
"description": "Add a UI component entry to ui/components"
|
|
87
|
+
},
|
|
88
|
+
"ui-page": {
|
|
89
|
+
"factory": "./generators/ui-page/ui-page",
|
|
90
|
+
"schema": "./generators/ui-page/schema.json",
|
|
91
|
+
"description": "Add a UI page entry to ui/pages"
|
|
92
|
+
},
|
|
93
|
+
"ui-shell": {
|
|
94
|
+
"factory": "./generators/ui-shell/ui-shell",
|
|
95
|
+
"schema": "./generators/ui-shell/schema.json",
|
|
96
|
+
"description": "Add a shell template entry to ui/shells"
|
|
82
97
|
}
|
|
83
98
|
}
|
|
84
99
|
}
|
package/index.d.ts
CHANGED
|
@@ -12,5 +12,8 @@ export { pluginGenerator } from './generators/plugin/plugin.js';
|
|
|
12
12
|
export { adapterGenerator } from './generators/adapter/adapter.js';
|
|
13
13
|
export { authProviderGenerator } from './generators/auth-provider/auth-provider.js';
|
|
14
14
|
export { flowGenerator } from './generators/flow/flow.js';
|
|
15
|
+
export { uiComponentGenerator } from './generators/ui-component/ui-component.js';
|
|
16
|
+
export { uiPageGenerator } from './generators/ui-page/ui-page.js';
|
|
17
|
+
export { uiShellGenerator } from './generators/ui-shell/ui-shell.js';
|
|
15
18
|
export { getFrontmcpVersion, getFrontmcpDependencies, getFrontmcpDevDependencies, getNxDependencies, } from './utils/versions.js';
|
|
16
19
|
export { toClassName, toPropertyName, toFileName, toConstantName } from './utils/names.js';
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toConstantName = exports.toFileName = exports.toPropertyName = exports.toClassName = exports.getNxDependencies = exports.getFrontmcpDevDependencies = exports.getFrontmcpDependencies = exports.getFrontmcpVersion = exports.flowGenerator = exports.authProviderGenerator = exports.adapterGenerator = exports.pluginGenerator = exports.providerGenerator = exports.agentGenerator = exports.skillGenerator = exports.promptGenerator = exports.resourceGenerator = exports.toolGenerator = exports.serverGenerator = exports.libGenerator = exports.appGenerator = exports.workspaceGenerator = void 0;
|
|
3
|
+
exports.toConstantName = exports.toFileName = exports.toPropertyName = exports.toClassName = exports.getNxDependencies = exports.getFrontmcpDevDependencies = exports.getFrontmcpDependencies = exports.getFrontmcpVersion = exports.uiShellGenerator = exports.uiPageGenerator = exports.uiComponentGenerator = exports.flowGenerator = exports.authProviderGenerator = exports.adapterGenerator = exports.pluginGenerator = exports.providerGenerator = exports.agentGenerator = exports.skillGenerator = exports.promptGenerator = exports.resourceGenerator = exports.toolGenerator = exports.serverGenerator = exports.libGenerator = exports.appGenerator = exports.workspaceGenerator = void 0;
|
|
4
4
|
// Generators
|
|
5
5
|
var workspace_js_1 = require("./generators/workspace/workspace.js");
|
|
6
6
|
Object.defineProperty(exports, "workspaceGenerator", { enumerable: true, get: function () { return workspace_js_1.workspaceGenerator; } });
|
|
@@ -30,6 +30,12 @@ var auth_provider_js_1 = require("./generators/auth-provider/auth-provider.js");
|
|
|
30
30
|
Object.defineProperty(exports, "authProviderGenerator", { enumerable: true, get: function () { return auth_provider_js_1.authProviderGenerator; } });
|
|
31
31
|
var flow_js_1 = require("./generators/flow/flow.js");
|
|
32
32
|
Object.defineProperty(exports, "flowGenerator", { enumerable: true, get: function () { return flow_js_1.flowGenerator; } });
|
|
33
|
+
var ui_component_js_1 = require("./generators/ui-component/ui-component.js");
|
|
34
|
+
Object.defineProperty(exports, "uiComponentGenerator", { enumerable: true, get: function () { return ui_component_js_1.uiComponentGenerator; } });
|
|
35
|
+
var ui_page_js_1 = require("./generators/ui-page/ui-page.js");
|
|
36
|
+
Object.defineProperty(exports, "uiPageGenerator", { enumerable: true, get: function () { return ui_page_js_1.uiPageGenerator; } });
|
|
37
|
+
var ui_shell_js_1 = require("./generators/ui-shell/ui-shell.js");
|
|
38
|
+
Object.defineProperty(exports, "uiShellGenerator", { enumerable: true, get: function () { return ui_shell_js_1.uiShellGenerator; } });
|
|
33
39
|
// Utilities
|
|
34
40
|
var versions_js_1 = require("./utils/versions.js");
|
|
35
41
|
Object.defineProperty(exports, "getFrontmcpVersion", { enumerable: true, get: function () { return versions_js_1.getFrontmcpVersion; } });
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,aAAa;AACb,oEAAyE;AAAhE,kHAAA,kBAAkB,OAAA;AAC3B,kDAAuD;AAA9C,sGAAA,YAAY,OAAA;AACrB,kDAAuD;AAA9C,sGAAA,YAAY,OAAA;AACrB,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,qDAA0D;AAAjD,wGAAA,aAAa,OAAA;AACtB,iEAAsE;AAA7D,gHAAA,iBAAiB,OAAA;AAC1B,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,wDAA6D;AAApD,0GAAA,cAAc,OAAA;AACvB,wDAA6D;AAApD,0GAAA,cAAc,OAAA;AACvB,iEAAsE;AAA7D,gHAAA,iBAAiB,OAAA;AAC1B,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,8DAAmE;AAA1D,8GAAA,gBAAgB,OAAA;AACzB,gFAAoF;AAA3E,yHAAA,qBAAqB,OAAA;AAC9B,qDAA0D;AAAjD,wGAAA,aAAa,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,aAAa;AACb,oEAAyE;AAAhE,kHAAA,kBAAkB,OAAA;AAC3B,kDAAuD;AAA9C,sGAAA,YAAY,OAAA;AACrB,kDAAuD;AAA9C,sGAAA,YAAY,OAAA;AACrB,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,qDAA0D;AAAjD,wGAAA,aAAa,OAAA;AACtB,iEAAsE;AAA7D,gHAAA,iBAAiB,OAAA;AAC1B,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,wDAA6D;AAApD,0GAAA,cAAc,OAAA;AACvB,wDAA6D;AAApD,0GAAA,cAAc,OAAA;AACvB,iEAAsE;AAA7D,gHAAA,iBAAiB,OAAA;AAC1B,2DAAgE;AAAvD,4GAAA,eAAe,OAAA;AACxB,8DAAmE;AAA1D,8GAAA,gBAAgB,OAAA;AACzB,gFAAoF;AAA3E,yHAAA,qBAAqB,OAAA;AAC9B,qDAA0D;AAAjD,wGAAA,aAAa,OAAA;AACtB,6EAAiF;AAAxE,uHAAA,oBAAoB,OAAA;AAC7B,8DAAkE;AAAzD,6GAAA,eAAe,OAAA;AACxB,iEAAqE;AAA5D,+GAAA,gBAAgB,OAAA;AAEzB,YAAY;AACZ,mDAK6B;AAJ3B,iHAAA,kBAAkB,OAAA;AAClB,sHAAA,uBAAuB,OAAA;AACvB,yHAAA,0BAA0B,OAAA;AAC1B,gHAAA,iBAAiB,OAAA;AAEnB,6CAA2F;AAAlF,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,0GAAA,cAAc,OAAA","sourcesContent":["// Generators\nexport { workspaceGenerator } from './generators/workspace/workspace.js';\nexport { appGenerator } from './generators/app/app.js';\nexport { libGenerator } from './generators/lib/lib.js';\nexport { serverGenerator } from './generators/server/server.js';\nexport { toolGenerator } from './generators/tool/tool.js';\nexport { resourceGenerator } from './generators/resource/resource.js';\nexport { promptGenerator } from './generators/prompt/prompt.js';\nexport { skillGenerator } from './generators/skill/skill.js';\nexport { agentGenerator } from './generators/agent/agent.js';\nexport { providerGenerator } from './generators/provider/provider.js';\nexport { pluginGenerator } from './generators/plugin/plugin.js';\nexport { adapterGenerator } from './generators/adapter/adapter.js';\nexport { authProviderGenerator } from './generators/auth-provider/auth-provider.js';\nexport { flowGenerator } from './generators/flow/flow.js';\nexport { uiComponentGenerator } from './generators/ui-component/ui-component.js';\nexport { uiPageGenerator } from './generators/ui-page/ui-page.js';\nexport { uiShellGenerator } from './generators/ui-shell/ui-shell.js';\n\n// Utilities\nexport {\n getFrontmcpVersion,\n getFrontmcpDependencies,\n getFrontmcpDevDependencies,\n getNxDependencies,\n} from './utils/versions.js';\nexport { toClassName, toPropertyName, toFileName, toConstantName } from './utils/names.js';\n"]}
|
package/package.json
CHANGED