@fragments-sdk/cli 0.2.2 → 0.3.1
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/dist/bin.js +1 -1
- package/dist/{viewer-YRF4SQE4.js → viewer-UYSS7DVA.js} +15 -24
- package/dist/viewer-UYSS7DVA.js.map +1 -0
- package/package.json +1 -1
- package/src/viewer/__tests__/viewer-integration.test.ts +166 -0
- package/src/viewer/server.ts +6 -5
- package/src/viewer/tailwind.config.js +1 -1
- package/src/viewer/vite-plugin.ts +9 -7
- package/dist/viewer-YRF4SQE4.js.map +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Integration tests for the consolidated viewer.
|
|
9
|
+
*
|
|
10
|
+
* After packages were merged into @fragments-sdk/cli, the viewer's
|
|
11
|
+
* path resolution changed. These tests verify that:
|
|
12
|
+
* - Viewer assets (HTML, TSX entry points) are found at the correct paths
|
|
13
|
+
* - The @fragments/core alias resolves to the consolidated core source
|
|
14
|
+
* - The virtual module generates valid import statements
|
|
15
|
+
* - The Vite config references correct file system locations
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Simulate the same path resolution used in server.ts and vite-plugin.ts
|
|
19
|
+
// At runtime, __dirname is dist/. In tests (vitest), it's the source dir.
|
|
20
|
+
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const viewerDir = resolve(testDir, "..");
|
|
22
|
+
const cliPackageRoot = resolve(viewerDir, "../..");
|
|
23
|
+
|
|
24
|
+
describe("viewer path resolution", () => {
|
|
25
|
+
it("viewerRoot resolves to a directory containing index.html", () => {
|
|
26
|
+
// server.ts: viewerRoot = resolve(cliPackageRoot, "src/viewer")
|
|
27
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
28
|
+
expect(existsSync(resolve(viewerRoot, "index.html"))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("viewerRoot contains entry.tsx", () => {
|
|
32
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
33
|
+
expect(existsSync(resolve(viewerRoot, "entry.tsx"))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("viewerRoot contains preview-frame.html", () => {
|
|
37
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
38
|
+
expect(existsSync(resolve(viewerRoot, "preview-frame.html"))).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("viewerRoot contains preview-frame-entry.tsx", () => {
|
|
42
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
43
|
+
expect(existsSync(resolve(viewerRoot, "preview-frame-entry.tsx"))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("viewerRoot contains render-template.html", () => {
|
|
47
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
48
|
+
expect(existsSync(resolve(viewerRoot, "render-template.html"))).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("viewerRoot contains tailwind.config.js", () => {
|
|
52
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
53
|
+
expect(existsSync(resolve(viewerRoot, "tailwind.config.js"))).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("viewerRoot contains postcss.config.js", () => {
|
|
57
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
58
|
+
expect(existsSync(resolve(viewerRoot, "postcss.config.js"))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("viewerRoot contains styles/globals.css", () => {
|
|
62
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
63
|
+
expect(existsSync(resolve(viewerRoot, "styles/globals.css"))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("@fragments/core alias resolution", () => {
|
|
68
|
+
it("core/index.ts exists at the expected path", () => {
|
|
69
|
+
// server.ts: "@fragments/core": resolve(cliPackageRoot, "src/core/index.ts")
|
|
70
|
+
const corePath = resolve(cliPackageRoot, "src/core/index.ts");
|
|
71
|
+
expect(existsSync(corePath)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("core/index.ts exports storyModuleToSegment", async () => {
|
|
75
|
+
const corePath = resolve(cliPackageRoot, "src/core/index.ts");
|
|
76
|
+
const content = await readFile(corePath, "utf-8");
|
|
77
|
+
// The virtual module imports { storyModuleToSegment, setPreviewConfig }
|
|
78
|
+
expect(content).toContain("storyModuleToSegment");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("core/index.ts exports setPreviewConfig", async () => {
|
|
82
|
+
const corePath = resolve(cliPackageRoot, "src/core/index.ts");
|
|
83
|
+
const content = await readFile(corePath, "utf-8");
|
|
84
|
+
expect(content).toContain("setPreviewConfig");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("virtual module @fragments/core import", () => {
|
|
89
|
+
it("vite-plugin generates import from @fragments/core (resolved via alias)", async () => {
|
|
90
|
+
// The virtual module template in vite-plugin.ts must import from @fragments/core
|
|
91
|
+
// which is resolved by the Vite alias to the consolidated core source
|
|
92
|
+
const pluginPath = resolve(viewerDir, "vite-plugin.ts");
|
|
93
|
+
const content = await readFile(pluginPath, "utf-8");
|
|
94
|
+
|
|
95
|
+
// The generated virtual module string should reference @fragments/core
|
|
96
|
+
expect(content).toContain(
|
|
97
|
+
'import { storyModuleToSegment, setPreviewConfig } from "@fragments/core"'
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("server.ts sets up @fragments/core alias to src/core/index.ts", async () => {
|
|
102
|
+
const serverPath = resolve(viewerDir, "server.ts");
|
|
103
|
+
const content = await readFile(serverPath, "utf-8");
|
|
104
|
+
|
|
105
|
+
// The alias must resolve @fragments/core to the consolidated core
|
|
106
|
+
expect(content).toContain('"@fragments/core": resolve(cliPackageRoot, "src/core/index.ts")');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("server.ts sets up @fragments/viewer alias", async () => {
|
|
110
|
+
const serverPath = resolve(viewerDir, "server.ts");
|
|
111
|
+
const content = await readFile(serverPath, "utf-8");
|
|
112
|
+
|
|
113
|
+
expect(content).toContain('"@fragments/viewer": viewerRoot');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("viewer HTML templates", () => {
|
|
118
|
+
it("index.html contains entry.tsx script reference", async () => {
|
|
119
|
+
const htmlPath = resolve(viewerDir, "index.html");
|
|
120
|
+
const content = await readFile(htmlPath, "utf-8");
|
|
121
|
+
|
|
122
|
+
// The HTML references /src/entry.tsx which gets rewritten to an absolute path
|
|
123
|
+
expect(content).toContain('src="/src/entry.tsx"');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("preview-frame.html contains preview-frame-entry.tsx reference", async () => {
|
|
127
|
+
const htmlPath = resolve(viewerDir, "preview-frame.html");
|
|
128
|
+
const content = await readFile(htmlPath, "utf-8");
|
|
129
|
+
|
|
130
|
+
expect(content).toContain('src="/src/preview-frame-entry.tsx"');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("vite-plugin rewrites entry paths using viewerAssetsRoot", async () => {
|
|
134
|
+
const pluginPath = resolve(viewerDir, "vite-plugin.ts");
|
|
135
|
+
const content = await readFile(pluginPath, "utf-8");
|
|
136
|
+
|
|
137
|
+
// The plugin must use viewerAssetsRoot (not __dirname or resolve(__dirname, ".."))
|
|
138
|
+
// to find viewer HTML and entry files
|
|
139
|
+
expect(content).toContain("const viewerAssetsRoot = resolve(__dirname,");
|
|
140
|
+
expect(content).toContain('const viewerRoot = viewerAssetsRoot');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("no stale @fragments/* package references", () => {
|
|
145
|
+
it("server.ts does not reference old resolveFragmentsPackage for core", async () => {
|
|
146
|
+
const serverPath = resolve(viewerDir, "server.ts");
|
|
147
|
+
const content = await readFile(serverPath, "utf-8");
|
|
148
|
+
|
|
149
|
+
// Should NOT try to resolve @fragments/core from node_modules
|
|
150
|
+
expect(content).not.toContain('resolveFragmentsPackage("core"');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("no source files import from @fragments/core as a runtime dependency", async () => {
|
|
154
|
+
// All source-level imports should use relative paths (../core/).
|
|
155
|
+
// Only the generated virtual module string uses @fragments/core (resolved via alias).
|
|
156
|
+
const serverPath = resolve(viewerDir, "server.ts");
|
|
157
|
+
const content = await readFile(serverPath, "utf-8");
|
|
158
|
+
|
|
159
|
+
// server.ts should import from relative paths, not @fragments/core
|
|
160
|
+
const lines = content.split("\n");
|
|
161
|
+
const importLines = lines.filter(
|
|
162
|
+
(l) => l.startsWith("import") && l.includes("@fragments/core")
|
|
163
|
+
);
|
|
164
|
+
expect(importLines).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
});
|
package/src/viewer/server.ts
CHANGED
|
@@ -27,10 +27,11 @@ import { loadConfig, discoverSegmentFiles } from "../core/node.js";
|
|
|
27
27
|
import { segmentsPlugin } from "./vite-plugin.js";
|
|
28
28
|
|
|
29
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
|
|
30
|
+
// At runtime, __dirname is dist/. Viewer assets live in src/viewer/.
|
|
31
|
+
const cliPackageRoot = resolve(__dirname, "..");
|
|
32
|
+
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
31
33
|
const viewerTailwindConfig = resolve(viewerRoot, "tailwind.config.js");
|
|
32
|
-
|
|
33
|
-
const packagesRoot = resolve(viewerRoot, "..");
|
|
34
|
+
const packagesRoot = resolve(cliPackageRoot, "..");
|
|
34
35
|
|
|
35
36
|
export interface DevServerOptions {
|
|
36
37
|
/** Port to run the server on */
|
|
@@ -149,14 +150,14 @@ export async function createDevServer(
|
|
|
149
150
|
alias: {
|
|
150
151
|
// Allow importing from viewer package
|
|
151
152
|
"@fragments/viewer": viewerRoot,
|
|
153
|
+
// Resolve @fragments/core to the consolidated core source
|
|
154
|
+
"@fragments/core": resolve(cliPackageRoot, "src/core/index.ts"),
|
|
152
155
|
// Ensure ALL react imports resolve to project's node_modules
|
|
153
156
|
// This is critical for viewer files loaded from outside project root
|
|
154
157
|
"react": safeRealpath(join(nodeModulesPath, "react")),
|
|
155
158
|
"react-dom": safeRealpath(join(nodeModulesPath, "react-dom")),
|
|
156
159
|
"react/jsx-runtime": safeRealpath(join(nodeModulesPath, "react/jsx-runtime")),
|
|
157
160
|
"react/jsx-dev-runtime": safeRealpath(join(nodeModulesPath, "react/jsx-dev-runtime")),
|
|
158
|
-
// Resolve @fragments packages - try project's node_modules first, fall back to monorepo
|
|
159
|
-
"@fragments/core": resolveFragmentsPackage("core", nodeModulesPath),
|
|
160
161
|
},
|
|
161
162
|
},
|
|
162
163
|
};
|
|
@@ -98,6 +98,8 @@ interface CompareResponse {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
101
|
+
// At runtime, __dirname is dist/. Viewer assets live in src/viewer/.
|
|
102
|
+
const viewerAssetsRoot = resolve(__dirname, "..", "src/viewer");
|
|
101
103
|
|
|
102
104
|
// Store pending render requests (for internal render page to pick up)
|
|
103
105
|
const pendingRenders = new Map<
|
|
@@ -1551,8 +1553,8 @@ async function loadSegmentsForContext(
|
|
|
1551
1553
|
* Serve the viewer HTML page.
|
|
1552
1554
|
*/
|
|
1553
1555
|
async function serveViewerHTML(res: any, server: ViteDevServer): Promise<void> {
|
|
1554
|
-
const viewerRoot =
|
|
1555
|
-
const entryPath = resolve(viewerRoot, "
|
|
1556
|
+
const viewerRoot = viewerAssetsRoot;
|
|
1557
|
+
const entryPath = resolve(viewerRoot, "entry.tsx");
|
|
1556
1558
|
|
|
1557
1559
|
try {
|
|
1558
1560
|
// Read the viewer HTML template
|
|
@@ -1578,12 +1580,12 @@ async function serveViewerHTML(res: any, server: ViteDevServer): Promise<void> {
|
|
|
1578
1580
|
* This is used for iframe-based component preview with CSS isolation.
|
|
1579
1581
|
*/
|
|
1580
1582
|
async function servePreviewFrameHTML(res: any, server: ViteDevServer): Promise<void> {
|
|
1581
|
-
const viewerRoot =
|
|
1582
|
-
const entryPath = resolve(viewerRoot, "
|
|
1583
|
+
const viewerRoot = viewerAssetsRoot;
|
|
1584
|
+
const entryPath = resolve(viewerRoot, "preview-frame-entry.tsx");
|
|
1583
1585
|
|
|
1584
1586
|
try {
|
|
1585
1587
|
// Read the preview frame HTML template
|
|
1586
|
-
let html = await readFile(resolve(viewerRoot, "
|
|
1588
|
+
let html = await readFile(resolve(viewerRoot, "preview-frame.html"), "utf-8");
|
|
1587
1589
|
|
|
1588
1590
|
// Rewrite the entry path to use absolute path to viewer package
|
|
1589
1591
|
html = html.replace("/src/preview-frame-entry.tsx", entryPath);
|
|
@@ -1679,12 +1681,12 @@ async function serveRenderHTML(
|
|
|
1679
1681
|
server: ViteDevServer,
|
|
1680
1682
|
renderScript: string
|
|
1681
1683
|
): Promise<void> {
|
|
1682
|
-
const viewerRoot =
|
|
1684
|
+
const viewerRoot = viewerAssetsRoot;
|
|
1683
1685
|
|
|
1684
1686
|
try {
|
|
1685
1687
|
// Read the render template
|
|
1686
1688
|
let html = await readFile(
|
|
1687
|
-
resolve(viewerRoot, "
|
|
1689
|
+
resolve(viewerRoot, "render-template.html"),
|
|
1688
1690
|
"utf-8"
|
|
1689
1691
|
);
|
|
1690
1692
|
|