ngx-json-render 0.9.11 → 0.9.12
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/README.md
CHANGED
|
@@ -802,7 +802,7 @@ A host loads the view as one HTML document with nowhere to fetch chunks from, so
|
|
|
802
802
|
}
|
|
803
803
|
```
|
|
804
804
|
|
|
805
|
-
`ng run mcp-view:mcp` runs the application build, then writes `dist/mcp-app/view.html` with every script and stylesheet inlined, and the fonts and images the stylesheets use as data URLs, and `dist/mcp-app/server.mjs` with its dependencies bundled in, so `node dist/mcp-app/server.mjs` runs anywhere without `node_modules`. Files the page cannot reach, such as an image referenced only from a template, are listed as a warning: point at them from a stylesheet or from an `https:` origin in `csp`. The server bundle is not type-checked; run `tsc --noEmit` on it first if you want that. Options: `server` (leave it out to build the view only), `tsConfig` for the server,
|
|
805
|
+
`ng run mcp-view:mcp` runs the application build, then writes `dist/mcp-app/view.html` with every script and stylesheet inlined, and the fonts and images the stylesheets use as data URLs, and `dist/mcp-app/server.mjs` with its dependencies bundled in, so `node dist/mcp-app/server.mjs` runs anywhere without `node_modules`. Files the page cannot reach, such as an image referenced only from a template, are listed as a warning: point at them from a stylesheet or from an `https:` origin in `csp`. The server bundle is not type-checked; run `tsc --noEmit` on it first if you want that. Options: `server` (leave it out to build the view only), `tsConfig` for the server, `externalPackages: true` to keep `node_modules` packages out of `server.mjs`, and `keepZodLocales: true` if the view switches zod to another language: by default the builder leaves zod's translations other than English out of `view.html`, about 250 kB that `import { z } from 'zod'` otherwise pulls in.
|
|
806
806
|
|
|
807
807
|
[`projects/mcp-app`](https://github.com/shteynu/ngx-json-render/tree/main/projects/mcp-app) is a complete example with the Material catalog: the view, built with this builder, and this server, the one behind `https://ngx-json-render.vercel.app/mcp`.
|
|
808
808
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-json-render",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.12",
|
|
4
4
|
"description": "Generative UI for Angular: a json-render renderer that streams AI-generated JSON specs into real Angular components with signals. Works with AG-UI agents and CopilotKit, MCP Apps and the Vercel AI SDK.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -187,7 +187,7 @@ function writeFiles(name, source) {
|
|
|
187
187
|
const from = (file, specifier) => source.kind === 'material' ? specifier : relativeImport(file, specifier);
|
|
188
188
|
const appFile = node_path_1.posix.join(appDir, 'app.ts');
|
|
189
189
|
tree.create(appFile, viewComponent(name, source.registryExport, from(appFile, registry)));
|
|
190
|
-
overwrite(tree, node_path_1.posix.join(src, 'main.ts'), mainFile(material));
|
|
190
|
+
overwrite(tree, node_path_1.posix.join(src, 'main.ts'), mainFile(material, zonelessProvider(readManifest(tree))));
|
|
191
191
|
overwrite(tree, node_path_1.posix.join(src, 'index.html'), indexFile(name, material));
|
|
192
192
|
tree.create(node_path_1.posix.join(src, material ? 'styles.scss' : 'styles.css'), material ? MATERIAL_STYLES : PLAIN_STYLES);
|
|
193
193
|
const serverFile = node_path_1.posix.join(root, 'server.ts');
|
|
@@ -262,24 +262,26 @@ ${registry}
|
|
|
262
262
|
template: \`
|
|
263
263
|
@if (mcp.error(); as error) {
|
|
264
264
|
<p class="status error">Could not connect to the host: {{ error.message }}</p>
|
|
265
|
-
} @else
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
@if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
265
|
+
} @else {
|
|
266
|
+
@if (mcp.spec(); as spec) {
|
|
267
|
+
<json-render
|
|
268
|
+
[spec]="spec"
|
|
269
|
+
[loading]="mcp.loading()"
|
|
270
|
+
[registry]="registry"
|
|
271
|
+
[handlers]="mcp.handlers"
|
|
272
|
+
/>
|
|
273
|
+
@if (mcp.lastMessage(); as outcome) {
|
|
274
|
+
@if (outcome.ok) {
|
|
275
|
+
<p class="status" role="status">Message passed to the chat.</p>
|
|
276
|
+
} @else {
|
|
277
|
+
<p class="status error" role="alert">
|
|
278
|
+
Could not send the message: {{ outcome.error.message }}
|
|
279
|
+
</p>
|
|
280
|
+
}
|
|
279
281
|
}
|
|
282
|
+
} @else {
|
|
283
|
+
<p class="status">Waiting for the model's spec…</p>
|
|
280
284
|
}
|
|
281
|
-
} @else {
|
|
282
|
-
<p class="status">Waiting for the model's spec…</p>
|
|
283
285
|
}
|
|
284
286
|
\`,
|
|
285
287
|
styles: \`
|
|
@@ -319,21 +321,32 @@ export class App {
|
|
|
319
321
|
}
|
|
320
322
|
`;
|
|
321
323
|
}
|
|
322
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Angular 19 has zoneless change detection only as
|
|
326
|
+
* `provideExperimentalZonelessChangeDetection`; 20 renamed it.
|
|
327
|
+
*/
|
|
328
|
+
function zonelessProvider(manifest) {
|
|
329
|
+
const range = manifest.dependencies?.['@angular/core'];
|
|
330
|
+
const major = range ? Number(/\d+/.exec(range)?.[0]) : NaN;
|
|
331
|
+
return major === 19
|
|
332
|
+
? 'provideExperimentalZonelessChangeDetection'
|
|
333
|
+
: 'provideZonelessChangeDetection';
|
|
334
|
+
}
|
|
335
|
+
function mainFile(material, zoneless) {
|
|
323
336
|
if (!material) {
|
|
324
|
-
return `import {
|
|
337
|
+
return `import { ${zoneless} } from '@angular/core';
|
|
325
338
|
import { bootstrapApplication } from '@angular/platform-browser';
|
|
326
339
|
import { App } from './app/app';
|
|
327
340
|
|
|
328
341
|
bootstrapApplication(App, {
|
|
329
|
-
providers: [
|
|
342
|
+
providers: [${zoneless}()],
|
|
330
343
|
}).catch((err) => console.error(err));
|
|
331
344
|
`;
|
|
332
345
|
}
|
|
333
346
|
return `import {
|
|
334
347
|
inject,
|
|
335
348
|
provideEnvironmentInitializer,
|
|
336
|
-
|
|
349
|
+
${zoneless},
|
|
337
350
|
} from '@angular/core';
|
|
338
351
|
import { MatIconRegistry } from '@angular/material/icon';
|
|
339
352
|
import { bootstrapApplication } from '@angular/platform-browser';
|
|
@@ -341,7 +354,7 @@ import { App } from './app/app';
|
|
|
341
354
|
|
|
342
355
|
bootstrapApplication(App, {
|
|
343
356
|
providers: [
|
|
344
|
-
|
|
357
|
+
${zoneless}(),
|
|
345
358
|
// The Material catalog names icons as Material Symbols ligatures.
|
|
346
359
|
provideEnvironmentInitializer(() =>
|
|
347
360
|
inject(MatIconRegistry).setDefaultFontSetClass('material-symbols-outlined'),
|
|
@@ -6,6 +6,11 @@ const node_fs_1 = require("node:fs");
|
|
|
6
6
|
const node_module_1 = require("node:module");
|
|
7
7
|
const node_path_1 = require("node:path");
|
|
8
8
|
const inline_cjs_1 = require("./inline.cjs");
|
|
9
|
+
/** The application builders whose build this one can run in-process. */
|
|
10
|
+
const APPLICATION_BUILDERS = [
|
|
11
|
+
'@angular/build:application',
|
|
12
|
+
'@angular-devkit/build-angular:application',
|
|
13
|
+
];
|
|
9
14
|
/**
|
|
10
15
|
* `ngx-json-render:mcp-app`: runs the view's application build, folds its
|
|
11
16
|
* browser output into one `view.html` (an MCP Apps host loads the view as a
|
|
@@ -15,9 +20,7 @@ const inline_cjs_1 = require("./inline.cjs");
|
|
|
15
20
|
async function buildMcpApp(options, context) {
|
|
16
21
|
const root = context.workspaceRoot;
|
|
17
22
|
const target = (0, architect_1.targetFromTargetString)(options.buildTarget, context.target?.project);
|
|
18
|
-
const
|
|
19
|
-
const result = await run.result;
|
|
20
|
-
await run.stop();
|
|
23
|
+
const result = await buildView(options, context, target);
|
|
21
24
|
if (!result.success)
|
|
22
25
|
return result;
|
|
23
26
|
const esbuild = loadEsbuild(root);
|
|
@@ -69,6 +72,50 @@ async function buildMcpApp(options, context) {
|
|
|
69
72
|
}
|
|
70
73
|
return { success: true, outputPath: out };
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Runs the view's application build. For an application target in this
|
|
77
|
+
* project it runs in-process, so it can leave zod's translations out (see
|
|
78
|
+
* `zodLocalesPlugin`); any other target is scheduled as it is.
|
|
79
|
+
*/
|
|
80
|
+
async function buildView(options, context, target) {
|
|
81
|
+
const builderName = await context.getBuilderNameForTarget(target);
|
|
82
|
+
if (options.keepZodLocales ||
|
|
83
|
+
target.project !== context.target?.project ||
|
|
84
|
+
!APPLICATION_BUILDERS.includes(builderName)) {
|
|
85
|
+
const run = await context.scheduleTarget(target);
|
|
86
|
+
const result = await run.result;
|
|
87
|
+
await run.stop();
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
const { buildApplication } = (0, node_module_1.createRequire)((0, node_path_1.join)(context.workspaceRoot, 'package.json'))('@angular/build');
|
|
91
|
+
const targetOptions = await context.validateOptions(await context.getTargetOptions(target), builderName);
|
|
92
|
+
for await (const output of buildApplication({ ...targetOptions, watch: false }, context, { codePlugins: [zodLocalesPlugin()] })) {
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
return { success: false, error: 'The application build produced no result.' };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* zod 4 re-exports all of its translations as `z.locales`, and `import { z }`
|
|
99
|
+
* keeps every one of them in the bundle — about 250 kB of a view that only
|
|
100
|
+
* shows English errors. The English one, which zod loads on its own, stays
|
|
101
|
+
* reachable as `z.locales.en`.
|
|
102
|
+
*/
|
|
103
|
+
function zodLocalesPlugin() {
|
|
104
|
+
const namespace = 'ngx-json-render-zod-locales';
|
|
105
|
+
return {
|
|
106
|
+
name: namespace,
|
|
107
|
+
setup(build) {
|
|
108
|
+
build.onResolve({ filter: /^\.\.\/locales\/index\.js$/ }, (args) => /[\\/]zod[\\/]v4[\\/](classic|mini|core)$/.test(args.resolveDir)
|
|
109
|
+
? { path: (0, node_path_1.join)(args.resolveDir, '..', 'locales'), namespace }
|
|
110
|
+
: undefined);
|
|
111
|
+
build.onLoad({ filter: /.*/, namespace }, (args) => ({
|
|
112
|
+
contents: "export { default as en } from './en.js';",
|
|
113
|
+
resolveDir: args.path,
|
|
114
|
+
loader: 'js',
|
|
115
|
+
}));
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
72
119
|
/** Where the application build wrote its browser files. */
|
|
73
120
|
async function browserOutput(context, target) {
|
|
74
121
|
const options = await context.getTargetOptions(target);
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"default": false,
|
|
26
26
|
"description": "Leave node_modules packages out of server.mjs, to be installed next to it. By default the server is one self-contained file."
|
|
27
|
+
},
|
|
28
|
+
"keepZodLocales": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"default": false,
|
|
31
|
+
"description": "Keep zod's translations (z.locales) in view.html. By default only English stays, which saves about 250 kB; set this if the view calls z.config with another locale."
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
"required": ["buildTarget", "outputPath"],
|
|
@@ -56,7 +56,7 @@ function declares(manifest, name) {
|
|
|
56
56
|
function warnOnOldAngular(manifest, warn) {
|
|
57
57
|
const range = manifest.dependencies?.['@angular/core'];
|
|
58
58
|
const major = range ? Number(/\d+/.exec(range)?.[0]) : NaN;
|
|
59
|
-
if (major <
|
|
60
|
-
warn(`ngx-json-render needs Angular
|
|
59
|
+
if (major < 19) {
|
|
60
|
+
warn(`ngx-json-render needs Angular 19 or newer; this workspace has @angular/core ${range}.`);
|
|
61
61
|
}
|
|
62
62
|
}
|