@apollo/client-ai-apps 0.2.3 → 0.2.4

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.
@@ -0,0 +1,4 @@
1
+ export declare const AbsoluteAssetImportsPlugin: () => {
2
+ name: string;
3
+ transformIndexHtml(html: string, ctx: any): string;
4
+ };
@@ -1 +1,2 @@
1
1
  export * from "./application_manifest_plugin";
2
+ export * from "./absolute_asset_imports_plugin";
@@ -157,11 +157,9 @@ var ApplicationManifestPlugin = () => {
157
157
  resourceDomains: packageJson.csp?.resourceDomains ?? []
158
158
  }
159
159
  };
160
- if (config.command === "build") {
161
- const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
162
- mkdirSync(path.dirname(dest), { recursive: true });
163
- writeFileSync(dest, JSON.stringify(manifest));
164
- }
160
+ const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
161
+ mkdirSync(path.dirname(dest), { recursive: true });
162
+ writeFileSync(dest, JSON.stringify(manifest));
165
163
  writeFileSync(".application-manifest.json", JSON.stringify(manifest));
166
164
  };
167
165
  return {
@@ -231,7 +229,21 @@ function removeClientDirective(doc) {
231
229
  }
232
230
  });
233
231
  }
232
+
233
+ // src/vite/absolute_asset_imports_plugin.ts
234
+ var AbsoluteAssetImportsPlugin = () => {
235
+ return {
236
+ name: "absolute-asset-imports",
237
+ transformIndexHtml(html, ctx) {
238
+ if (!ctx.server) return html;
239
+ let baseUrl = (ctx.server.config?.server?.origin ?? ctx.server.resolvedUrls?.local[0]).replace(/\/$/, "");
240
+ baseUrl = baseUrl.replace(/\/$/, "");
241
+ return html.replace(/(from\s+["'])\/([^"']+)/g, `$1${baseUrl}/$2`).replace(/(src=["'])\/([^"']+)/gi, `$1${baseUrl}/$2`);
242
+ }
243
+ };
244
+ };
234
245
  export {
246
+ AbsoluteAssetImportsPlugin,
235
247
  ApplicationManifestPlugin,
236
248
  sortTopLevelDefinitions
237
249
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo/client-ai-apps",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,100 @@
1
+ import { expect, test, vi, describe, beforeEach, Mock } from "vitest";
2
+ import { AbsoluteAssetImportsPlugin } from "./absolute_asset_imports_plugin";
3
+
4
+ test("Should replace root relative scripts with full url when origin is provided", () => {
5
+ const ctx = {
6
+ server: {
7
+ config: {
8
+ server: {
9
+ origin: "http://localhost:3000/",
10
+ },
11
+ },
12
+ },
13
+ };
14
+ const html = `<html><head><script type="module" src="/@vite/client"></script></head><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
15
+ const plugin = AbsoluteAssetImportsPlugin();
16
+
17
+ let result = plugin.transformIndexHtml(html, ctx);
18
+
19
+ expect(result).toMatchInlineSnapshot(
20
+ `"<html><head><script type="module" src="http://localhost:3000/@vite/client"></script></head><body><script module src="http://localhost:3000/assets/main.ts?t=12345"></script></body></html>"`
21
+ );
22
+ });
23
+
24
+ test("Should replace root relative scripts with full url when origin is not provided", () => {
25
+ const ctx = {
26
+ server: {
27
+ resolvedUrls: {
28
+ local: ["http://localhost:3000/"],
29
+ },
30
+ },
31
+ };
32
+ const html = `<html><head> <script type="module">import { injectIntoGlobalHook } from "/@react-refresh";
33
+ injectIntoGlobalHook(window);
34
+ window.$RefreshReg$ = () => {};
35
+ window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
36
+ const plugin = AbsoluteAssetImportsPlugin();
37
+
38
+ let result = plugin.transformIndexHtml(html, ctx);
39
+
40
+ expect(result).toMatchInlineSnapshot(`
41
+ "<html><head> <script type="module">import { injectIntoGlobalHook } from "http://localhost:3000/@react-refresh";
42
+ injectIntoGlobalHook(window);
43
+ window.$RefreshReg$ = () => {};
44
+ window.$RefreshSig$ = () => (type) => type;</script></head></html>"
45
+ `);
46
+ });
47
+
48
+ test("Should replace root relative imports with full url when origin is provided", () => {
49
+ const ctx = {
50
+ server: {
51
+ config: {
52
+ server: {
53
+ origin: "http://localhost:3000/",
54
+ },
55
+ },
56
+ },
57
+ };
58
+ const html = `<html><head> <script type="module">import { injectIntoGlobalHook } from "/@react-refresh";
59
+ injectIntoGlobalHook(window);
60
+ window.$RefreshReg$ = () => {};
61
+ window.$RefreshSig$ = () => (type) => type;</script></head></html>`;
62
+ const plugin = AbsoluteAssetImportsPlugin();
63
+
64
+ let result = plugin.transformIndexHtml(html, ctx);
65
+
66
+ expect(result).toMatchInlineSnapshot(`
67
+ "<html><head> <script type="module">import { injectIntoGlobalHook } from "http://localhost:3000/@react-refresh";
68
+ injectIntoGlobalHook(window);
69
+ window.$RefreshReg$ = () => {};
70
+ window.$RefreshSig$ = () => (type) => type;</script></head></html>"
71
+ `);
72
+ });
73
+
74
+ test("Should replace root relative imports with full url when origin is not provided", () => {
75
+ const ctx = {
76
+ server: {
77
+ resolvedUrls: {
78
+ local: ["http://localhost:3000/"],
79
+ },
80
+ },
81
+ };
82
+ const html = `<html><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
83
+ const plugin = AbsoluteAssetImportsPlugin();
84
+
85
+ let result = plugin.transformIndexHtml(html, ctx);
86
+
87
+ expect(result).toMatchInlineSnapshot(
88
+ `"<html><body><script module src="http://localhost:3000/assets/main.ts?t=12345"></script></body></html>"`
89
+ );
90
+ });
91
+
92
+ test("Should not modify html when not running a local server", () => {
93
+ const ctx = {};
94
+ const html = `<html><head><script type="module" src="/@vite/client"></script></head><body><script module src="/assets/main.ts?t=12345"></script></body></html>`;
95
+ const plugin = AbsoluteAssetImportsPlugin();
96
+
97
+ let result = plugin.transformIndexHtml(html, ctx);
98
+
99
+ expect(result).toMatchInlineSnapshot(`"<html><head><script type="module" src="/@vite/client"></script></head><body><script module src="/assets/main.ts?t=12345"></script></body></html>"`);
100
+ });
@@ -0,0 +1,20 @@
1
+ export const AbsoluteAssetImportsPlugin = () => {
2
+ return {
3
+ name: "absolute-asset-imports",
4
+
5
+ transformIndexHtml(html: string, ctx: any) {
6
+ if (!ctx.server) return html;
7
+
8
+ let baseUrl = (ctx.server.config?.server?.origin ?? ctx.server.resolvedUrls?.local[0]).replace(/\/$/, "");
9
+ baseUrl = baseUrl.replace(/\/$/, "");
10
+
11
+ return (
12
+ html
13
+ // import "/@vite/..." or "/@react-refresh"
14
+ .replace(/(from\s+["'])\/([^"']+)/g, `$1${baseUrl}/$2`)
15
+ // src="/src/..."
16
+ .replace(/(src=["'])\/([^"']+)/gi, `$1${baseUrl}/$2`)
17
+ );
18
+ },
19
+ };
20
+ };
@@ -4,6 +4,8 @@ import fs from "fs";
4
4
  import * as glob from "glob";
5
5
  import path from "path";
6
6
 
7
+ const root = process.cwd();
8
+
7
9
  vi.mock(import("fs"), async (importOriginal) => {
8
10
  const actual = await importOriginal();
9
11
  return {
@@ -21,7 +23,7 @@ vi.mock(import("path"), async (importOriginal) => {
21
23
  return {
22
24
  default: {
23
25
  ...actual.default,
24
- resolve: vi.fn(),
26
+ resolve: vi.fn((...args) => args.map((a, i) => (i === 0 ? a : a.replace(/^\//, ""))).join("/")),
25
27
  dirname: vi.fn(),
26
28
  },
27
29
  };
@@ -43,7 +45,7 @@ describe("buildStart", () => {
43
45
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
44
46
  if (path === "package.json") {
45
47
  return JSON.stringify({});
46
- } else if (path === "my-component.tsx") {
48
+ } else if (path === root + "/my-component.tsx") {
47
49
  return `
48
50
  const MY_QUERY = gql\`query HelloWorldQuery($name: string!) @tool(name: "hello-world", description: "This is an awesome tool!", extraInputs: [{
49
51
  name: "doStuff",
@@ -54,11 +56,10 @@ describe("buildStart", () => {
54
56
  }
55
57
  });
56
58
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
57
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
58
59
  vi.spyOn(fs, "writeFileSync");
59
60
 
60
61
  const plugin = ApplicationManifestPlugin();
61
- plugin.configResolved({ command: "serve", server: {} });
62
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
62
63
  await plugin.buildStart();
63
64
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
64
65
 
@@ -66,8 +67,8 @@ describe("buildStart", () => {
66
67
  let contentObj = JSON.parse(content);
67
68
  contentObj.hash = "abc";
68
69
 
69
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
70
- expect(file).toBe(".application-manifest.json");
70
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
71
+ expect(file).toBe(root + "/dist/.application-manifest.json");
71
72
  expect(contentObj).toMatchInlineSnapshot(`
72
73
  {
73
74
  "csp": {
@@ -134,18 +135,17 @@ describe("buildStart", () => {
134
135
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
135
136
  if (path === "package.json") {
136
137
  return JSON.stringify({});
137
- } else if (path === "my-component.tsx") {
138
+ } else if (path === root + "/my-component.tsx") {
138
139
  return `
139
140
  const MY_QUERY = \`query HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
140
141
  `;
141
142
  }
142
143
  });
143
144
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
144
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
145
145
  vi.spyOn(fs, "writeFileSync");
146
146
 
147
147
  const plugin = ApplicationManifestPlugin();
148
- plugin.configResolved({ command: "serve", server: {} });
148
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
149
149
  await plugin.buildStart();
150
150
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
151
151
 
@@ -153,8 +153,8 @@ describe("buildStart", () => {
153
153
  let contentObj = JSON.parse(content);
154
154
  contentObj.hash = "abc";
155
155
 
156
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
157
- expect(file).toBe(".application-manifest.json");
156
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
157
+ expect(file).toBe(root + "/dist/.application-manifest.json");
158
158
  expect(contentObj).toMatchInlineSnapshot(`
159
159
  {
160
160
  "csp": {
@@ -174,18 +174,17 @@ describe("buildStart", () => {
174
174
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
175
175
  if (path === "package.json") {
176
176
  return JSON.stringify({});
177
- } else if (path === "my-component.tsx") {
177
+ } else if (path === root + "/my-component.tsx") {
178
178
  return `
179
179
  const MY_QUERY = gql\`query HelloWorldQuery { helloWorld }\`;
180
180
  `;
181
181
  }
182
182
  });
183
183
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
184
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
185
184
  vi.spyOn(fs, "writeFileSync");
186
185
 
187
186
  const plugin = ApplicationManifestPlugin();
188
- plugin.configResolved({ command: "serve", server: {} });
187
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
189
188
  await plugin.buildStart();
190
189
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
191
190
 
@@ -193,8 +192,8 @@ describe("buildStart", () => {
193
192
  let contentObj = JSON.parse(content);
194
193
  contentObj.hash = "abc";
195
194
 
196
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
197
- expect(file).toBe(".application-manifest.json");
195
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
196
+ expect(file).toBe(root + "/dist/.application-manifest.json");
198
197
  expect(contentObj).toMatchInlineSnapshot(`
199
198
  {
200
199
  "csp": {
@@ -226,18 +225,17 @@ describe("buildStart", () => {
226
225
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
227
226
  if (path === "package.json") {
228
227
  return JSON.stringify({});
229
- } else if (path === "my-component.tsx") {
228
+ } else if (path === root + "/my-component.tsx") {
230
229
  return `
231
230
  const MY_QUERY = gql\`query HelloWorldQuery @prefetch { helloWorld }\`;
232
231
  `;
233
232
  }
234
233
  });
235
234
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
236
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
237
235
  vi.spyOn(fs, "writeFileSync");
238
236
 
239
237
  const plugin = ApplicationManifestPlugin();
240
- plugin.configResolved({ command: "serve", server: {} });
238
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
241
239
  await plugin.buildStart();
242
240
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
243
241
 
@@ -245,8 +243,8 @@ describe("buildStart", () => {
245
243
  let contentObj = JSON.parse(content);
246
244
  contentObj.hash = "abc";
247
245
 
248
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
249
- expect(file).toBe(".application-manifest.json");
246
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
247
+ expect(file).toBe(root + "/dist/.application-manifest.json");
250
248
  expect(contentObj).toMatchInlineSnapshot(`
251
249
  {
252
250
  "csp": {
@@ -301,18 +299,17 @@ describe("buildStart", () => {
301
299
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
302
300
  if (path === "package.json") {
303
301
  return JSON.stringify({});
304
- } else if (path === "my-component.tsx") {
302
+ } else if (path === root + "/my-component.tsx") {
305
303
  return `
306
304
  const MY_QUERY = gql\`mutation HelloWorldQuery @tool(name: "hello-world", description: "This is an awesome tool!") { helloWorld }\`;
307
305
  `;
308
306
  }
309
307
  });
310
308
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
311
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
312
309
  vi.spyOn(fs, "writeFileSync");
313
310
 
314
311
  const plugin = ApplicationManifestPlugin();
315
- plugin.configResolved({ command: "serve", server: {} });
312
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
316
313
  await plugin.buildStart();
317
314
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
318
315
 
@@ -320,8 +317,8 @@ describe("buildStart", () => {
320
317
  let contentObj = JSON.parse(content);
321
318
  contentObj.hash = "abc";
322
319
 
323
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
324
- expect(file).toBe(".application-manifest.json");
320
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
321
+ expect(file).toBe(root + "/dist/.application-manifest.json");
325
322
  expect(contentObj).toMatchInlineSnapshot(`
326
323
  {
327
324
  "csp": {
@@ -395,7 +392,7 @@ describe("buildStart", () => {
395
392
  vi.spyOn(fs, "writeFileSync");
396
393
 
397
394
  const plugin = ApplicationManifestPlugin();
398
- plugin.configResolved({ command: "serve", mode: "staging", server: {} });
395
+ plugin.configResolved({ command: "serve", mode: "staging", server: {}, build: { outDir: "/dist" } });
399
396
  await plugin.buildStart();
400
397
 
401
398
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
@@ -419,7 +416,7 @@ describe("buildStart", () => {
419
416
  vi.spyOn(fs, "writeFileSync");
420
417
 
421
418
  const plugin = ApplicationManifestPlugin();
422
- plugin.configResolved({ command: "serve", server: { https: {}, port: "5678" } });
419
+ plugin.configResolved({ command: "serve", server: { https: {}, port: "5678" }, build: { outDir: "/dist" } });
423
420
  await plugin.buildStart();
424
421
 
425
422
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
@@ -443,7 +440,11 @@ describe("buildStart", () => {
443
440
  vi.spyOn(fs, "writeFileSync");
444
441
 
445
442
  const plugin = ApplicationManifestPlugin();
446
- plugin.configResolved({ command: "serve", server: { port: "5678", host: "awesome.com" } });
443
+ plugin.configResolved({
444
+ command: "serve",
445
+ server: { port: "5678", host: "awesome.com" },
446
+ build: { outDir: "/dist" },
447
+ });
447
448
  await plugin.buildStart();
448
449
 
449
450
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
@@ -590,7 +591,7 @@ describe("buildStart", () => {
590
591
  vi.spyOn(fs, "readFileSync").mockImplementation((path) => {
591
592
  if (path === "package.json") {
592
593
  return JSON.stringify({});
593
- } else if (path === "my-component.tsx") {
594
+ } else if (path === root + "/my-component.tsx") {
594
595
  return `
595
596
  const MY_QUERY = gql\`
596
597
  fragment A on User { firstName }
@@ -608,11 +609,10 @@ describe("buildStart", () => {
608
609
  }
609
610
  });
610
611
  vi.spyOn(glob, "glob").mockImplementation(() => Promise.resolve(["my-component.tsx"]));
611
- vi.spyOn(path, "resolve").mockImplementation((_, file) => file);
612
612
  vi.spyOn(fs, "writeFileSync");
613
613
 
614
614
  const plugin = ApplicationManifestPlugin();
615
- plugin.configResolved({ command: "serve", server: {} });
615
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
616
616
  await plugin.buildStart();
617
617
  let [file, content] = (fs.writeFileSync as unknown as Mock).mock.calls[0];
618
618
 
@@ -620,8 +620,8 @@ describe("buildStart", () => {
620
620
  let contentObj = JSON.parse(content);
621
621
  contentObj.hash = "abc";
622
622
 
623
- expect(fs.writeFileSync).toHaveBeenCalledOnce();
624
- expect(file).toBe(".application-manifest.json");
623
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
624
+ expect(file).toBe(`${root}/dist/.application-manifest.json`);
625
625
  expect(contentObj).toMatchInlineSnapshot(`
626
626
  {
627
627
  "csp": {
@@ -819,12 +819,12 @@ describe("configureServer", () => {
819
819
  server.watcher.init();
820
820
 
821
821
  const plugin = ApplicationManifestPlugin();
822
- plugin.configResolved({ command: "serve", server: {} });
822
+ plugin.configResolved({ command: "serve", server: {}, build: { outDir: "/dist" } });
823
823
  await plugin.buildStart();
824
824
  await plugin.configureServer(server);
825
825
  await server.watcher.trigger("package.json");
826
826
  await server.watcher.trigger("my-component.tsx");
827
827
 
828
- expect(fs.writeFileSync).toBeCalledTimes(3);
828
+ expect(fs.writeFileSync).toBeCalledTimes(6);
829
829
  });
830
830
  });
@@ -207,11 +207,11 @@ export const ApplicationManifestPlugin = () => {
207
207
  },
208
208
  };
209
209
 
210
- if (config.command === "build") {
211
- const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
212
- mkdirSync(path.dirname(dest), { recursive: true });
213
- writeFileSync(dest, JSON.stringify(manifest));
214
- }
210
+ // Always write to build directory so the MCP server picks it up
211
+ const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
212
+ mkdirSync(path.dirname(dest), { recursive: true });
213
+ writeFileSync(dest, JSON.stringify(manifest));
214
+
215
215
  // Always write to the dev location so that the app can bundle the manifest content
216
216
  writeFileSync(".application-manifest.json", JSON.stringify(manifest));
217
217
  };
package/src/vite/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./application_manifest_plugin";
2
+ export * from "./absolute_asset_imports_plugin";