@microsoft/fast-test-harness 0.1.0
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 +267 -0
- package/dist/dts/build/dom-shim.d.ts +10 -0
- package/dist/dts/build/dom-shim.d.ts.map +1 -0
- package/dist/dts/build/dom-shim.test.d.ts +2 -0
- package/dist/dts/build/dom-shim.test.d.ts.map +1 -0
- package/dist/dts/build/generate-stylesheets.d.ts +62 -0
- package/dist/dts/build/generate-stylesheets.d.ts.map +1 -0
- package/dist/dts/build/generate-stylesheets.test.d.ts +2 -0
- package/dist/dts/build/generate-stylesheets.test.d.ts.map +1 -0
- package/dist/dts/build/generate-templates.d.ts +69 -0
- package/dist/dts/build/generate-templates.d.ts.map +1 -0
- package/dist/dts/build/generate-templates.test.d.ts +2 -0
- package/dist/dts/build/generate-templates.test.d.ts.map +1 -0
- package/dist/dts/build/generate-webui-templates.d.ts +54 -0
- package/dist/dts/build/generate-webui-templates.d.ts.map +1 -0
- package/dist/dts/build/generate-webui-templates.test.d.ts +2 -0
- package/dist/dts/build/generate-webui-templates.test.d.ts.map +1 -0
- package/dist/dts/fixtures/assertions.d.ts +19 -0
- package/dist/dts/fixtures/assertions.d.ts.map +1 -0
- package/dist/dts/fixtures/csr-fixture.d.ts +114 -0
- package/dist/dts/fixtures/csr-fixture.d.ts.map +1 -0
- package/dist/dts/fixtures/csr-fixture.pw.spec.d.ts +2 -0
- package/dist/dts/fixtures/csr-fixture.pw.spec.d.ts.map +1 -0
- package/dist/dts/fixtures/index.d.ts +30 -0
- package/dist/dts/fixtures/index.d.ts.map +1 -0
- package/dist/dts/fixtures/ssr-fixture.d.ts +42 -0
- package/dist/dts/fixtures/ssr-fixture.d.ts.map +1 -0
- package/dist/dts/fixtures/ssr-fixture.pw.spec.d.ts +2 -0
- package/dist/dts/fixtures/ssr-fixture.pw.spec.d.ts.map +1 -0
- package/dist/dts/index.d.ts +7 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/ssr/entry-client.d.ts +2 -0
- package/dist/dts/ssr/entry-client.d.ts.map +1 -0
- package/dist/dts/ssr/render.d.ts +127 -0
- package/dist/dts/ssr/render.d.ts.map +1 -0
- package/dist/dts/ssr/render.test.d.ts +2 -0
- package/dist/dts/ssr/render.test.d.ts.map +1 -0
- package/dist/esm/build/dom-shim.js +142 -0
- package/dist/esm/build/dom-shim.test.js +202 -0
- package/dist/esm/build/generate-stylesheets.js +70 -0
- package/dist/esm/build/generate-stylesheets.test.js +74 -0
- package/dist/esm/build/generate-templates.js +243 -0
- package/dist/esm/build/generate-templates.test.js +231 -0
- package/dist/esm/build/generate-webui-templates.js +121 -0
- package/dist/esm/build/generate-webui-templates.test.js +179 -0
- package/dist/esm/fixtures/assertions.js +49 -0
- package/dist/esm/fixtures/csr-fixture.js +153 -0
- package/dist/esm/fixtures/csr-fixture.pw.spec.js +137 -0
- package/dist/esm/fixtures/index.js +48 -0
- package/dist/esm/fixtures/ssr-fixture.js +113 -0
- package/dist/esm/fixtures/ssr-fixture.pw.spec.js +189 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/ssr/entry-client.js +2 -0
- package/dist/esm/ssr/render.js +381 -0
- package/dist/esm/ssr/render.test.js +236 -0
- package/package.json +88 -0
- package/playwright.config.d.ts +4 -0
- package/playwright.config.mjs +38 -0
- package/public/styles.css +15 -0
- package/server.mjs +317 -0
- package/start.mjs +244 -0
- package/vite.config.d.ts +4 -0
- package/vite.config.mjs +35 -0
package/server.mjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { createServer as createHttpServer } from "node:http";
|
|
3
|
+
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { load } from "cheerio";
|
|
5
|
+
|
|
6
|
+
const MIME_TYPES = {
|
|
7
|
+
".html": "text/html",
|
|
8
|
+
".js": "application/javascript",
|
|
9
|
+
".mjs": "application/javascript",
|
|
10
|
+
".css": "text/css",
|
|
11
|
+
".json": "application/json",
|
|
12
|
+
".wasm": "application/wasm",
|
|
13
|
+
".svg": "image/svg+xml",
|
|
14
|
+
".png": "image/png",
|
|
15
|
+
".jpg": "image/jpeg",
|
|
16
|
+
".gif": "image/gif",
|
|
17
|
+
".ico": "image/x-icon",
|
|
18
|
+
".woff": "font/woff",
|
|
19
|
+
".woff2": "font/woff2",
|
|
20
|
+
".ttf": "font/ttf",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read the full request body as a string.
|
|
25
|
+
*/
|
|
26
|
+
function readBody(req) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
req.on("data", chunk => chunks.push(chunk));
|
|
30
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
31
|
+
req.on("error", reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Send a JSON response.
|
|
37
|
+
*/
|
|
38
|
+
function jsonResponse(res, statusCode, data) {
|
|
39
|
+
const body = JSON.stringify(data);
|
|
40
|
+
res.writeHead(statusCode, {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"Content-Length": Buffer.byteLength(body),
|
|
43
|
+
});
|
|
44
|
+
res.end(body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Send an HTML response.
|
|
49
|
+
*/
|
|
50
|
+
function htmlResponse(res, statusCode, html) {
|
|
51
|
+
res.writeHead(statusCode, {
|
|
52
|
+
"Content-Type": "text/html",
|
|
53
|
+
"Content-Length": Buffer.byteLength(html),
|
|
54
|
+
});
|
|
55
|
+
res.end(html);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Try to serve a static file from `root`. Returns true if served.
|
|
60
|
+
*/
|
|
61
|
+
async function tryServeStatic(req, res, root) {
|
|
62
|
+
const urlPath = new URL(req.url, "http://localhost").pathname;
|
|
63
|
+
const filePath = resolve(root, `.${urlPath}`);
|
|
64
|
+
|
|
65
|
+
// Prevent path traversal — reject if the resolved path escapes root.
|
|
66
|
+
const rel = relative(root, filePath);
|
|
67
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const stat = await fs.stat(filePath);
|
|
73
|
+
if (!stat.isFile()) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const ext = extname(filePath);
|
|
77
|
+
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
78
|
+
const content = await fs.readFile(filePath);
|
|
79
|
+
res.writeHead(200, {
|
|
80
|
+
"Content-Type": mime,
|
|
81
|
+
"Content-Length": content.length,
|
|
82
|
+
});
|
|
83
|
+
res.end(content);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function startServer(cwd = process.cwd(), root, configFile, options = {}) {
|
|
91
|
+
const {
|
|
92
|
+
port = process.env.PORT ? Number(process.env.PORT) : 3278,
|
|
93
|
+
base = process.env.BASE || "/",
|
|
94
|
+
debug = process.env.FAST_DEBUG === "true",
|
|
95
|
+
} = options;
|
|
96
|
+
|
|
97
|
+
root = root ?? resolve(cwd, "./test");
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await fs.access(root);
|
|
101
|
+
} catch {
|
|
102
|
+
console.error(
|
|
103
|
+
`Error: Vite root directory does not exist: ${root}\n` +
|
|
104
|
+
` Use --root to specify a different directory, or run from a package with a test/ folder.`,
|
|
105
|
+
);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (configFile) {
|
|
110
|
+
try {
|
|
111
|
+
await fs.access(configFile);
|
|
112
|
+
} catch {
|
|
113
|
+
console.error(
|
|
114
|
+
`Error: Vite config file not found: ${configFile}\n` +
|
|
115
|
+
` Use --config to specify a different config file.`,
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const indexPath = resolve(root, "./index.html");
|
|
122
|
+
|
|
123
|
+
let realTempDir;
|
|
124
|
+
if (debug) {
|
|
125
|
+
const tempDir = resolve(root, "temp");
|
|
126
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
127
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
128
|
+
realTempDir = await fs.realpath(tempDir);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const pendingGenerations = new Map();
|
|
132
|
+
let cachedIndexHtml = null;
|
|
133
|
+
const fixtureCache = new Map();
|
|
134
|
+
|
|
135
|
+
const { createServer } = await import("vite");
|
|
136
|
+
|
|
137
|
+
const vite = await createServer({
|
|
138
|
+
root,
|
|
139
|
+
...(configFile && { configFile }),
|
|
140
|
+
server: {
|
|
141
|
+
middlewareMode: true,
|
|
142
|
+
watch: {
|
|
143
|
+
ignored: ["**/temp/**", "**/ssr-*.html"],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
appType: "custom",
|
|
147
|
+
plugins: [
|
|
148
|
+
{
|
|
149
|
+
name: "fast-test-harness:resolve-css-links",
|
|
150
|
+
transformIndexHtml: {
|
|
151
|
+
order: "pre",
|
|
152
|
+
async handler(html) {
|
|
153
|
+
const $ = load(html, {
|
|
154
|
+
xmlMode: false,
|
|
155
|
+
decodeEntities: false,
|
|
156
|
+
});
|
|
157
|
+
let changed = false;
|
|
158
|
+
|
|
159
|
+
for (const el of $("link[href$='.css']").toArray()) {
|
|
160
|
+
const href = $(el).attr("href");
|
|
161
|
+
if (
|
|
162
|
+
!href ||
|
|
163
|
+
href.startsWith("/") ||
|
|
164
|
+
href.startsWith(".") ||
|
|
165
|
+
href.startsWith("http")
|
|
166
|
+
) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const resolved = await vite.pluginContainer.resolveId(
|
|
170
|
+
href,
|
|
171
|
+
root,
|
|
172
|
+
);
|
|
173
|
+
if (resolved?.id) {
|
|
174
|
+
$(el).attr("href", `/@fs/${resolved.id}`);
|
|
175
|
+
changed = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return changed ? $.html() : html;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const server = createHttpServer(async (req, res) => {
|
|
187
|
+
const url = new URL(req.url, "http://localhost");
|
|
188
|
+
const pathname = url.pathname;
|
|
189
|
+
|
|
190
|
+
// POST /generate-fixture — SSR fixture generation.
|
|
191
|
+
if (req.method === "POST" && pathname === "/generate-fixture") {
|
|
192
|
+
try {
|
|
193
|
+
const body = JSON.parse(await readBody(req));
|
|
194
|
+
|
|
195
|
+
if (!body.testId) {
|
|
196
|
+
throw new Error("testId is required");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!/^[a-z0-9_-]+$/i.test(body.testId)) {
|
|
200
|
+
throw new Error("testId contains invalid characters");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (body.attributes) {
|
|
204
|
+
body.attributes = JSON.parse(body.attributes);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (body.styles) {
|
|
208
|
+
body.styles = JSON.parse(body.styles);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const testId = body.testId;
|
|
212
|
+
const filename = `ssr-${testId}.html`;
|
|
213
|
+
|
|
214
|
+
const fixtureUrl = `/${filename}`;
|
|
215
|
+
|
|
216
|
+
if (pendingGenerations.has(filename)) {
|
|
217
|
+
await pendingGenerations.get(filename);
|
|
218
|
+
return jsonResponse(res, 200, { url: fixtureUrl });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const generateTask = (async () => {
|
|
222
|
+
const templateFile = await fs.readFile(
|
|
223
|
+
resolve(root, "./ssr.html"),
|
|
224
|
+
"utf-8",
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const { render } = await vite.ssrLoadModule("/src/entry-server.js");
|
|
228
|
+
|
|
229
|
+
const { template, fixture, preloadLinks } = render(body);
|
|
230
|
+
|
|
231
|
+
const styleTags = (body.styles || [])
|
|
232
|
+
.map(s => `<style>${s}</style>`)
|
|
233
|
+
.join("\n");
|
|
234
|
+
|
|
235
|
+
const assembled = templateFile
|
|
236
|
+
.replace(
|
|
237
|
+
"<!--fixturetitle-->",
|
|
238
|
+
() => body.testTitle || "FAST Test Harness (SSR)",
|
|
239
|
+
)
|
|
240
|
+
.replace("<!--templates-->", () => template ?? "")
|
|
241
|
+
.replace("<!--fixture-->", () => fixture ?? "")
|
|
242
|
+
.replace(
|
|
243
|
+
"<!--stylespreload-->",
|
|
244
|
+
() => `${preloadLinks ?? ""}${styleTags}`,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const html = await vite.transformIndexHtml(fixtureUrl, assembled);
|
|
248
|
+
|
|
249
|
+
fixtureCache.set(fixtureUrl, html);
|
|
250
|
+
|
|
251
|
+
if (debug) {
|
|
252
|
+
const filePath = resolve(realTempDir, filename);
|
|
253
|
+
await fs.writeFile(filePath, html, "utf-8");
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
|
|
257
|
+
pendingGenerations.set(filename, generateTask);
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
await generateTask;
|
|
261
|
+
jsonResponse(res, 200, { url: fixtureUrl });
|
|
262
|
+
} finally {
|
|
263
|
+
pendingGenerations.delete(filename);
|
|
264
|
+
}
|
|
265
|
+
} catch (e) {
|
|
266
|
+
vite?.ssrFixStacktrace?.(e);
|
|
267
|
+
console.log(e.stack);
|
|
268
|
+
res.writeHead(500).end("Internal Server Error");
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// GET /ssr-*.html — serve cached SSR fixtures.
|
|
274
|
+
if (req.method === "GET" && /^\/ssr-[^/]+\.html$/.test(pathname)) {
|
|
275
|
+
const cached = fixtureCache.get(pathname);
|
|
276
|
+
if (cached) {
|
|
277
|
+
return htmlResponse(res, 200, cached);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Try static files from cwd.
|
|
282
|
+
if (req.method === "GET" && (await tryServeStatic(req, res, cwd))) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Delegate to Vite's middleware (module transforms, HMR, etc.).
|
|
287
|
+
// Vite handles its own routes; for anything left over, serve
|
|
288
|
+
// the HTML shell for navigation requests.
|
|
289
|
+
vite.middlewares(req, res, async () => {
|
|
290
|
+
const accept = req.headers.accept || "";
|
|
291
|
+
if (!accept.includes("text/html")) {
|
|
292
|
+
res.writeHead(404).end();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
if (!cachedIndexHtml) {
|
|
298
|
+
const indexFile = await fs.readFile(indexPath, "utf-8");
|
|
299
|
+
cachedIndexHtml = await vite.transformIndexHtml(
|
|
300
|
+
req.url || "/",
|
|
301
|
+
indexFile,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
htmlResponse(res, 200, cachedIndexHtml);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
vite?.ssrFixStacktrace?.(e);
|
|
308
|
+
console.log(e.stack);
|
|
309
|
+
res.writeHead(500).end("Internal Server Error");
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
server.listen(port, () => {
|
|
315
|
+
console.log(`Server started at http://localhost:${port}`);
|
|
316
|
+
});
|
|
317
|
+
}
|
package/start.mjs
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
|
|
5
|
+
const subcommands = new Set([
|
|
6
|
+
"serve",
|
|
7
|
+
"generate-templates",
|
|
8
|
+
"generate-stylesheets",
|
|
9
|
+
"generate-webui-templates",
|
|
10
|
+
]);
|
|
11
|
+
const firstArg = process.argv[2];
|
|
12
|
+
const subcommand = subcommands.has(firstArg) ? firstArg : null;
|
|
13
|
+
const looksLikeSubcommand = firstArg && !firstArg.startsWith("-");
|
|
14
|
+
const args = subcommand ? process.argv.slice(3) : process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
function showVersion() {
|
|
17
|
+
const { version } = JSON.parse(
|
|
18
|
+
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
19
|
+
);
|
|
20
|
+
console.log(version);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function showHelp() {
|
|
25
|
+
console.log(`Usage: fast-test-harness [command] [options]
|
|
26
|
+
|
|
27
|
+
Commands:
|
|
28
|
+
serve Start the test harness dev server (default)
|
|
29
|
+
generate-templates Generate <f-template> HTML files from compiled templates
|
|
30
|
+
generate-stylesheets Generate CSS files from compiled ElementStyles
|
|
31
|
+
generate-webui-templates Generate WebUI-compatible DSD templates from compiled templates
|
|
32
|
+
|
|
33
|
+
Run fast-test-harness <command> --help for command-specific options.
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-v, --version Show version number
|
|
37
|
+
-h, --help Show this help message
|
|
38
|
+
`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── serve (default) ─────────────────────────────────────────────────────
|
|
43
|
+
async function runServe() {
|
|
44
|
+
const { values } = parseArgs({
|
|
45
|
+
args,
|
|
46
|
+
options: {
|
|
47
|
+
port: { type: "string", short: "p" },
|
|
48
|
+
base: { type: "string", short: "b" },
|
|
49
|
+
root: { type: "string", short: "r" },
|
|
50
|
+
config: { type: "string", short: "c" },
|
|
51
|
+
debug: { type: "boolean", short: "d", default: false },
|
|
52
|
+
help: { type: "boolean", short: "h", default: false },
|
|
53
|
+
version: { type: "boolean", short: "v", default: false },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (values.version) showVersion();
|
|
58
|
+
if (values.help) {
|
|
59
|
+
console.log(`Usage: fast-test-harness [serve] [options]
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
-p, --port <number> Server port (env: PORT, default: 3278)
|
|
63
|
+
-b, --base <path> Base URL path (env: BASE, default: /)
|
|
64
|
+
-r, --root <path> Vite root directory (default: <cwd>/test)
|
|
65
|
+
-c, --config <path> Vite config file path (default: Vite auto-discovery)
|
|
66
|
+
-d, --debug Write SSR fixtures to temp/ for inspection
|
|
67
|
+
-v, --version Show version number
|
|
68
|
+
-h, --help Show this help message
|
|
69
|
+
`);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { startServer } = await import("./server.mjs");
|
|
74
|
+
|
|
75
|
+
const port = values.port ? Number(values.port) : undefined;
|
|
76
|
+
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
77
|
+
console.error(`Error: Invalid port number: ${values.port}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await startServer(process.cwd(), values.root, values.config, {
|
|
82
|
+
...(port && { port }),
|
|
83
|
+
...(values.base && { base: values.base }),
|
|
84
|
+
...(values.debug && { debug: true }),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── generate-templates ──────────────────────────────────────────────────
|
|
89
|
+
async function runGenerateTemplates() {
|
|
90
|
+
const { values } = parseArgs({
|
|
91
|
+
args,
|
|
92
|
+
options: {
|
|
93
|
+
cwd: { type: "string" },
|
|
94
|
+
"dist-dir": { type: "string" },
|
|
95
|
+
"out-dir": { type: "string" },
|
|
96
|
+
pattern: { type: "string" },
|
|
97
|
+
"tag-prefix": { type: "string" },
|
|
98
|
+
help: { type: "boolean", short: "h", default: false },
|
|
99
|
+
version: { type: "boolean", short: "v", default: false },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (values.version) showVersion();
|
|
104
|
+
if (values.help) {
|
|
105
|
+
console.log(`Usage: fast-test-harness generate-templates [options]
|
|
106
|
+
|
|
107
|
+
Generate <f-template> HTML files from compiled FAST Element template modules.
|
|
108
|
+
|
|
109
|
+
Options:
|
|
110
|
+
--cwd <path> Package root directory (default: cwd)
|
|
111
|
+
--dist-dir <path> Compiled JS directory, relative to cwd (default: dist)
|
|
112
|
+
--out-dir <path> Output directory for HTML files (default: same as dist-dir)
|
|
113
|
+
--pattern <glob> Glob for template modules (default: **/*.template.js)
|
|
114
|
+
--tag-prefix <prefix> Tag name prefix (default: fast)
|
|
115
|
+
-v, --version Show version number
|
|
116
|
+
-h, --help Show this help message
|
|
117
|
+
`);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { generateFTemplates } = await import("./dist/esm/build/generate-templates.js");
|
|
122
|
+
await generateFTemplates({
|
|
123
|
+
...(values.cwd && { cwd: values.cwd }),
|
|
124
|
+
...(values["dist-dir"] && { distDir: values["dist-dir"] }),
|
|
125
|
+
...(values["out-dir"] && { outDir: values["out-dir"] }),
|
|
126
|
+
...(values.pattern && { pattern: values.pattern }),
|
|
127
|
+
...(values["tag-prefix"] && { tagPrefix: values["tag-prefix"] }),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── generate-stylesheets ────────────────────────────────────────────────
|
|
132
|
+
async function runGenerateStylesheets() {
|
|
133
|
+
const { values } = parseArgs({
|
|
134
|
+
args,
|
|
135
|
+
options: {
|
|
136
|
+
cwd: { type: "string" },
|
|
137
|
+
"dist-dir": { type: "string" },
|
|
138
|
+
"out-dir": { type: "string" },
|
|
139
|
+
pattern: { type: "string" },
|
|
140
|
+
help: { type: "boolean", short: "h", default: false },
|
|
141
|
+
version: { type: "boolean", short: "v", default: false },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (values.version) showVersion();
|
|
146
|
+
if (values.help) {
|
|
147
|
+
console.log(`Usage: fast-test-harness generate-stylesheets [options]
|
|
148
|
+
|
|
149
|
+
Generate CSS files from compiled FAST Element style modules.
|
|
150
|
+
|
|
151
|
+
Options:
|
|
152
|
+
--cwd <path> Package root directory (default: cwd)
|
|
153
|
+
--dist-dir <path> Compiled JS directory, relative to cwd (default: dist)
|
|
154
|
+
--out-dir <path> Output directory for CSS files (default: same as dist-dir)
|
|
155
|
+
--pattern <glob> Glob for style modules (default: **/*.styles.js)
|
|
156
|
+
-v, --version Show version number
|
|
157
|
+
-h, --help Show this help message
|
|
158
|
+
`);
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { generateStylesheets } = await import(
|
|
163
|
+
"./dist/esm/build/generate-stylesheets.js"
|
|
164
|
+
);
|
|
165
|
+
await generateStylesheets({
|
|
166
|
+
...(values.cwd && { cwd: values.cwd }),
|
|
167
|
+
...(values["dist-dir"] && { distDir: values["dist-dir"] }),
|
|
168
|
+
...(values["out-dir"] && { outDir: values["out-dir"] }),
|
|
169
|
+
...(values.pattern && { pattern: values.pattern }),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── generate-webui-templates ─────────────────────────────────────────────
|
|
174
|
+
async function runGenerateWebuiTemplates() {
|
|
175
|
+
const { values } = parseArgs({
|
|
176
|
+
args,
|
|
177
|
+
options: {
|
|
178
|
+
cwd: { type: "string" },
|
|
179
|
+
"dist-dir": { type: "string" },
|
|
180
|
+
"out-dir": { type: "string" },
|
|
181
|
+
pattern: { type: "string" },
|
|
182
|
+
"tag-prefix": { type: "string" },
|
|
183
|
+
help: { type: "boolean", short: "h", default: false },
|
|
184
|
+
version: { type: "boolean", short: "v", default: false },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (values.version) showVersion();
|
|
189
|
+
if (values.help) {
|
|
190
|
+
console.log(`Usage: fast-test-harness generate-webui-templates [options]
|
|
191
|
+
|
|
192
|
+
Generate WebUI-compatible DSD template files from compiled FAST Element template modules.
|
|
193
|
+
Strips client-side bindings and the f-template wrapper, outputs <template shadowrootmode="open">.
|
|
194
|
+
|
|
195
|
+
Options:
|
|
196
|
+
--cwd <path> Package root directory (default: cwd)
|
|
197
|
+
--dist-dir <path> Compiled JS directory, relative to cwd (default: dist)
|
|
198
|
+
--out-dir <path> Output directory for HTML files (default: same as dist-dir)
|
|
199
|
+
--pattern <glob> Glob for template modules (default: **/*.template.js)
|
|
200
|
+
--tag-prefix <prefix> Tag name prefix (default: fast)
|
|
201
|
+
-v, --version Show version number
|
|
202
|
+
-h, --help Show this help message
|
|
203
|
+
`);
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const { generateWebuiTemplates } = await import(
|
|
208
|
+
"./dist/esm/build/generate-webui-templates.js"
|
|
209
|
+
);
|
|
210
|
+
await generateWebuiTemplates({
|
|
211
|
+
...(values.cwd && { cwd: values.cwd }),
|
|
212
|
+
...(values["dist-dir"] && { distDir: values["dist-dir"] }),
|
|
213
|
+
...(values["out-dir"] && { outDir: values["out-dir"] }),
|
|
214
|
+
...(values.pattern && { pattern: values.pattern }),
|
|
215
|
+
...(values["tag-prefix"] && { tagPrefix: values["tag-prefix"] }),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── dispatch ────────────────────────────────────────────────────────────
|
|
220
|
+
// Top-level --version / --help before subcommand dispatch
|
|
221
|
+
if (args.includes("--version") && !subcommand) showVersion();
|
|
222
|
+
if (args.includes("--help") && !subcommand) showHelp();
|
|
223
|
+
|
|
224
|
+
if (looksLikeSubcommand && !subcommand) {
|
|
225
|
+
console.error(
|
|
226
|
+
`Unknown command: ${firstArg}\n\nAvailable commands: serve, generate-templates, generate-stylesheets, generate-webui-templates\nRun fast-test-harness --help for usage.\n`,
|
|
227
|
+
);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
switch (subcommand) {
|
|
232
|
+
case "generate-templates":
|
|
233
|
+
await runGenerateTemplates();
|
|
234
|
+
break;
|
|
235
|
+
case "generate-stylesheets":
|
|
236
|
+
await runGenerateStylesheets();
|
|
237
|
+
break;
|
|
238
|
+
case "generate-webui-templates":
|
|
239
|
+
await runGenerateWebuiTemplates();
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
await runServe();
|
|
243
|
+
break;
|
|
244
|
+
}
|
package/vite.config.d.ts
ADDED
package/vite.config.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
|
|
3
|
+
const PORT = process.env.PORT ? Number(process.env.PORT) : 3278;
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
clearScreen: false,
|
|
7
|
+
resolve: {
|
|
8
|
+
conditions: ["test"],
|
|
9
|
+
},
|
|
10
|
+
server: {
|
|
11
|
+
port: PORT,
|
|
12
|
+
strictPort: true,
|
|
13
|
+
debug: true,
|
|
14
|
+
},
|
|
15
|
+
esbuild: {
|
|
16
|
+
tsconfigRaw: {
|
|
17
|
+
compilerOptions: {
|
|
18
|
+
// Needed for FAST's observable system
|
|
19
|
+
useDefineForClassFields: false,
|
|
20
|
+
experimentalDecorators: true,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
build: {
|
|
25
|
+
outDir: "./dist",
|
|
26
|
+
minify: false,
|
|
27
|
+
sourcemap: true,
|
|
28
|
+
},
|
|
29
|
+
optimizeDeps: {
|
|
30
|
+
exclude: ["@microsoft/fast-element", "@microsoft/fast-html"],
|
|
31
|
+
},
|
|
32
|
+
preview: {
|
|
33
|
+
port: PORT,
|
|
34
|
+
},
|
|
35
|
+
});
|