transone-cli 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.
@@ -0,0 +1,1429 @@
1
+ // @bun
2
+ import {
3
+ __require
4
+ } from "./index-yb2wsc7f.js";
5
+
6
+ // src/target/types.ts
7
+ var TARGET_TYPES = [
8
+ "web",
9
+ "mp-weixin",
10
+ "mp-alipay",
11
+ "mp-bytedance",
12
+ "app-ios",
13
+ "app-android",
14
+ "app-harmony"
15
+ ];
16
+ function isTargetType(value) {
17
+ return typeof value === "string" && TARGET_TYPES.includes(value);
18
+ }
19
+
20
+ // src/config.ts
21
+ import { existsSync } from "fs";
22
+ import { resolve } from "path";
23
+ var DEFAULT_CONFIG = {
24
+ entry: "src/main.ts",
25
+ server: {
26
+ host: "127.0.0.1",
27
+ port: 52211,
28
+ proxy: {}
29
+ },
30
+ build: {
31
+ outDir: "dist/build/h5",
32
+ basePath: "",
33
+ directoryPages: false
34
+ }
35
+ };
36
+ function defineConfig(config) {
37
+ return config;
38
+ }
39
+ async function resolveConfig(options = {}) {
40
+ const root = resolve(options.root ?? process.cwd());
41
+ const configFile = resolve(root, "transone.config.ts");
42
+ const hasInlineConfig = options.config !== undefined;
43
+ const loadedConfig = hasInlineConfig ? { exists: false } : await loadConfigFile(configFile);
44
+ let fileConfig = {};
45
+ if (loadedConfig.exists) {
46
+ const config2 = loadedConfig.config;
47
+ validateUserConfig(config2);
48
+ fileConfig = config2;
49
+ }
50
+ if (hasInlineConfig) {
51
+ validateUserConfig(options.config);
52
+ }
53
+ const inlineOverrides = toInlineOverrides(options);
54
+ validateUserConfig(inlineOverrides);
55
+ const config = mergeConfig(fileConfig, options.config ?? {}, inlineOverrides);
56
+ const entry = resolve(root, config.entry);
57
+ if (!existsSync(entry) && !config.library) {
58
+ throw new Error(`Entry file does not exist: ${entry}`);
59
+ }
60
+ const pages = await resolvePages(root, entry, config.pages);
61
+ const target = resolveTarget(options.target);
62
+ return {
63
+ root,
64
+ ...loadedConfig.exists ? { configFile } : {},
65
+ entry,
66
+ pages,
67
+ target,
68
+ server: {
69
+ host: config.server.host,
70
+ port: config.server.port,
71
+ proxy: config.server.proxy
72
+ },
73
+ build: {
74
+ outDir: resolve(root, config.build.outDir),
75
+ basePath: normalizeBasePath(config.build.basePath),
76
+ directoryPages: config.build.directoryPages
77
+ },
78
+ ...config.library ? { library: resolveLibrary(root, config.library) } : {}
79
+ };
80
+ }
81
+ function resolveLibrary(root, library) {
82
+ return {
83
+ entry: resolve(root, library.entry ?? "src/index.ts"),
84
+ outDir: resolve(root, library.outDir ?? "dist"),
85
+ external: library.external ?? [],
86
+ tsconfigs: (library.tsconfigs ?? ["tsconfig.build.json"]).map((path) => resolve(root, path)),
87
+ dts: library.dts ?? true,
88
+ splitting: library.splitting ?? true,
89
+ sourcemap: library.sourcemap ?? true,
90
+ minify: library.minify
91
+ };
92
+ }
93
+ function normalizeBasePath(base) {
94
+ const trimmed = (base ?? "").trim();
95
+ if (!trimmed || trimmed === "/") {
96
+ return "";
97
+ }
98
+ const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
99
+ return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
100
+ }
101
+ function resolveTarget(target) {
102
+ if (target === undefined) {
103
+ return "web";
104
+ }
105
+ if (isTargetType(target)) {
106
+ return target;
107
+ }
108
+ throw new Error(`Unknown build target "${String(target)}". ` + "Available targets: web, mp-weixin, mp-alipay, mp-bytedance, " + "app-ios, app-android, app-harmony.");
109
+ }
110
+ async function resolvePages(root, entry, pages) {
111
+ const resolved = { "/": entry };
112
+ for (const [route, pageEntry] of Object.entries(pages ?? {})) {
113
+ const normalized = normalizePageRoute(route);
114
+ const absolute = resolve(root, pageEntry);
115
+ if (!existsSync(absolute)) {
116
+ throw new Error(`Page entry file does not exist for "${normalized}": ${absolute}`);
117
+ }
118
+ resolved[normalized] = absolute;
119
+ }
120
+ return resolved;
121
+ }
122
+ function normalizePageRoute(route) {
123
+ if (route === "/") {
124
+ throw new Error('Config pages must not redefine the root page "/"; use config.entry instead');
125
+ }
126
+ const normalized = route.replace(/\/+$/, "");
127
+ if (normalized === "") {
128
+ throw new Error(`Page route must not be empty: ${route}`);
129
+ }
130
+ return normalized;
131
+ }
132
+ var configLoadSequence = 0;
133
+ async function loadConfigFile(configFile) {
134
+ if (!existsSync(configFile)) {
135
+ return { exists: false };
136
+ }
137
+ configLoadSequence += 1;
138
+ const module = await import(`${configFile}?transone_config=${configLoadSequence}`);
139
+ return { exists: true, config: module.default };
140
+ }
141
+ function toInlineOverrides(options) {
142
+ const server = {};
143
+ const build = {};
144
+ if (options.host !== undefined) {
145
+ server.host = options.host;
146
+ }
147
+ if (options.port !== undefined) {
148
+ server.port = options.port;
149
+ }
150
+ if (options.outDir !== undefined) {
151
+ build.outDir = options.outDir;
152
+ }
153
+ if (options.base !== undefined) {
154
+ build.basePath = options.base;
155
+ }
156
+ return {
157
+ ...Object.keys(server).length > 0 ? { server } : {},
158
+ ...Object.keys(build).length > 0 ? { build } : {}
159
+ };
160
+ }
161
+ function mergeConfig(...configs) {
162
+ return configs.reduce((merged, config) => {
163
+ const server = config.server;
164
+ const build = config.build;
165
+ return {
166
+ entry: config.entry ?? merged.entry,
167
+ pages: config.pages ?? merged.pages,
168
+ server: {
169
+ host: server?.host ?? merged.server.host,
170
+ port: server?.port ?? merged.server.port,
171
+ proxy: {
172
+ ...merged.server.proxy,
173
+ ...server?.proxy
174
+ }
175
+ },
176
+ build: {
177
+ outDir: build?.outDir ?? merged.build.outDir,
178
+ basePath: build?.basePath ?? merged.build.basePath,
179
+ directoryPages: build?.directoryPages ?? merged.build.directoryPages
180
+ },
181
+ library: config.library ?? merged.library
182
+ };
183
+ }, DEFAULT_CONFIG);
184
+ }
185
+ function validateUserConfig(config) {
186
+ assertRecord(config, "Config");
187
+ if (config.entry !== undefined && typeof config.entry !== "string") {
188
+ throw new Error("Config entry must be a string");
189
+ }
190
+ if (config.pages !== undefined) {
191
+ validatePagesConfig(config.pages);
192
+ }
193
+ if (config.server !== undefined) {
194
+ validateServerConfig(config.server);
195
+ }
196
+ if (config.build !== undefined) {
197
+ validateBuildConfig(config.build);
198
+ }
199
+ if (config.library !== undefined) {
200
+ validateLibraryConfig(config.library);
201
+ }
202
+ }
203
+ function validateLibraryConfig(library) {
204
+ assertRecord(library, "Config library");
205
+ if (library.entry !== undefined && typeof library.entry !== "string") {
206
+ throw new Error("Config library.entry must be a string");
207
+ }
208
+ if (library.outDir !== undefined && typeof library.outDir !== "string") {
209
+ throw new Error("Config library.outDir must be a string");
210
+ }
211
+ if (library.external !== undefined) {
212
+ assertStringArray(library.external, "Config library.external");
213
+ }
214
+ if (library.tsconfigs !== undefined) {
215
+ assertStringArray(library.tsconfigs, "Config library.tsconfigs");
216
+ }
217
+ for (const key of ["dts", "splitting", "sourcemap", "minify"]) {
218
+ if (library[key] !== undefined && typeof library[key] !== "boolean") {
219
+ throw new Error(`Config library.${key} must be a boolean`);
220
+ }
221
+ }
222
+ }
223
+ function assertStringArray(value, name) {
224
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
225
+ throw new Error(`${name} must be an array of strings`);
226
+ }
227
+ }
228
+ function validatePagesConfig(pages, label = "Config pages") {
229
+ assertRecord(pages, label);
230
+ for (const [route, entry] of Object.entries(pages)) {
231
+ if (!route.startsWith("/")) {
232
+ throw new Error(`Page route must start with "/": ${route}`);
233
+ }
234
+ if (typeof entry !== "string") {
235
+ throw new Error(`Page entry for "${route}" must be a string`);
236
+ }
237
+ }
238
+ }
239
+ function validateServerConfig(server) {
240
+ assertRecord(server, "Config server");
241
+ if (server.host !== undefined && typeof server.host !== "string") {
242
+ throw new Error("Config server.host must be a string");
243
+ }
244
+ const port = server.port;
245
+ if (port !== undefined && (typeof port !== "number" || !Number.isInteger(port) || port < 0 || port > 65535)) {
246
+ throw new Error("Config server.port must be an integer between 0 and 65535");
247
+ }
248
+ if (server.proxy === undefined) {
249
+ return;
250
+ }
251
+ assertRecord(server.proxy, "Config server.proxy");
252
+ for (const [prefix, value] of Object.entries(server.proxy)) {
253
+ if (!prefix.startsWith("/")) {
254
+ throw new Error(`Proxy prefix must start with "/": ${prefix}`);
255
+ }
256
+ const target = getProxyTarget(value);
257
+ validateProxyTarget(target);
258
+ }
259
+ }
260
+ function validateBuildConfig(build) {
261
+ assertRecord(build, "Config build");
262
+ if (build.outDir !== undefined && typeof build.outDir !== "string") {
263
+ throw new Error("Config build.outDir must be a string");
264
+ }
265
+ if (build.basePath !== undefined && typeof build.basePath !== "string") {
266
+ throw new Error("Config build.basePath must be a string");
267
+ }
268
+ if (build.directoryPages !== undefined && typeof build.directoryPages !== "boolean") {
269
+ throw new Error("Config build.directoryPages must be a boolean");
270
+ }
271
+ }
272
+ function getProxyTarget(value) {
273
+ if (typeof value === "string") {
274
+ return value;
275
+ }
276
+ assertRecord(value, "Proxy options");
277
+ if (typeof value.target !== "string") {
278
+ throw new Error("Proxy target must be a string");
279
+ }
280
+ if (value.changeOrigin !== undefined && typeof value.changeOrigin !== "boolean") {
281
+ throw new Error("Proxy changeOrigin must be a boolean");
282
+ }
283
+ if (value.rewrite !== undefined && typeof value.rewrite !== "function") {
284
+ throw new Error("Proxy rewrite must be a function");
285
+ }
286
+ return value.target;
287
+ }
288
+ function validateProxyTarget(target) {
289
+ try {
290
+ const url = new URL(target);
291
+ if (url.protocol === "http:" || url.protocol === "https:") {
292
+ return;
293
+ }
294
+ } catch {}
295
+ throw new Error(`Proxy target must use http or https: ${target}`);
296
+ }
297
+ function assertRecord(value, name) {
298
+ if (!isRecord(value)) {
299
+ throw new Error(`${name} must be an object`);
300
+ }
301
+ }
302
+ function isRecord(value) {
303
+ return typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
304
+ }
305
+
306
+ // src/target/web-target.ts
307
+ import { mkdir, rm, writeFile } from "fs/promises";
308
+ import { dirname as dirname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
309
+
310
+ // src/build-output.ts
311
+ function isEntryJavaScriptOutput(output) {
312
+ return output.kind === "entry-point" && isJavaScriptPath(output.path);
313
+ }
314
+ function isStylesheetOutput(output) {
315
+ return /\.css$/i.test(output.path);
316
+ }
317
+ function isJavaScriptPath(path) {
318
+ return /\.(?:[cm]?js)$/i.test(path);
319
+ }
320
+
321
+ // src/safe-path.ts
322
+ import { lstat, realpath } from "fs/promises";
323
+ import { dirname, isAbsolute, relative, resolve as resolve2, sep } from "path";
324
+ async function assertSafeSubdirectory(root, path, errorMessage) {
325
+ try {
326
+ const canonicalRoot = await realpath(root);
327
+ const canonicalPath = await canonicalizePotentialPath(path);
328
+ if (!isStrictSubdirectory(canonicalRoot, canonicalPath)) {
329
+ throw new Error(errorMessage);
330
+ }
331
+ } catch {
332
+ throw new Error(errorMessage);
333
+ }
334
+ }
335
+ async function assertSafeSubdirectoryDoesNotContain(root, path, protectedPath, errorMessage) {
336
+ try {
337
+ const lexicalPath = resolve2(path);
338
+ const lexicalProtectedPath = resolve2(protectedPath);
339
+ const canonicalRoot = await realpath(root);
340
+ const canonicalPath = await canonicalizePotentialPath(path);
341
+ const canonicalProtectedPath = await realpath(protectedPath);
342
+ if (!isStrictSubdirectory(canonicalRoot, canonicalPath) || isPathWithinOrEqual(lexicalPath, lexicalProtectedPath) || isPathWithinOrEqual(canonicalPath, canonicalProtectedPath)) {
343
+ throw new Error(errorMessage);
344
+ }
345
+ } catch {
346
+ throw new Error(errorMessage);
347
+ }
348
+ }
349
+ async function canonicalizePotentialPath(path) {
350
+ const ancestor = await findExistingAncestor(path);
351
+ const canonicalAncestor = await realpath(ancestor);
352
+ return resolve2(canonicalAncestor, relative(ancestor, path));
353
+ }
354
+ async function findExistingAncestor(path) {
355
+ let candidate = path;
356
+ while (candidate !== dirname(candidate)) {
357
+ try {
358
+ await lstat(candidate);
359
+ return candidate;
360
+ } catch (error) {
361
+ if (!isMissingPathError(error)) {
362
+ throw error;
363
+ }
364
+ }
365
+ candidate = dirname(candidate);
366
+ }
367
+ await lstat(candidate);
368
+ return candidate;
369
+ }
370
+ function isMissingPathError(error) {
371
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
372
+ }
373
+ function isStrictSubdirectory(root, path) {
374
+ const pathFromRoot = relative(root, path);
375
+ return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute(pathFromRoot);
376
+ }
377
+ function isPathWithinOrEqual(parent, candidate) {
378
+ const pathFromParent = relative(parent, candidate);
379
+ return pathFromParent === "" || pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep}`) && !isAbsolute(pathFromParent);
380
+ }
381
+
382
+ // src/target/web-target.ts
383
+ var INVALID_OUTPUT_DIRECTORY_MESSAGE = "Build output must be a subdirectory of the project root";
384
+
385
+ class WebTarget {
386
+ type = "web";
387
+ label = "\u9759\u6001\u7AD9\u70B9 / SPA\uFF08\u590D\u7528 TSone \u7B56\u7565\u5316\u6E32\u67D3\uFF09";
388
+ async build(config, _options) {
389
+ const entries = [...new Set(Object.values(config.pages))];
390
+ for (const entry of entries) {
391
+ await assertSafeSubdirectoryDoesNotContain(config.root, config.build.outDir, entry, INVALID_OUTPUT_DIRECTORY_MESSAGE);
392
+ }
393
+ const { renderProjectHtml } = await import("./project-ybacfze8.js");
394
+ for (const [route, entry] of Object.entries(config.pages)) {
395
+ await renderProjectHtml(config, {}, entry, route);
396
+ }
397
+ await rm(config.build.outDir, { recursive: true, force: true });
398
+ await mkdir(config.build.outDir, { recursive: true });
399
+ const builtByEntry = new Map;
400
+ const failures = [];
401
+ for (const entry of entries) {
402
+ const built = await buildPage(config, entry);
403
+ if (typeof built === "string") {
404
+ failures.push(built);
405
+ continue;
406
+ }
407
+ builtByEntry.set(entry, built);
408
+ }
409
+ if (failures.length > 0) {
410
+ throw buildFailure(failures);
411
+ }
412
+ const assetsBuilt = [];
413
+ for (const [route, entry] of Object.entries(config.pages)) {
414
+ const page = builtByEntry.get(entry);
415
+ if (!page) {
416
+ throw new Error(`Missing built assets for entry: ${entry}`);
417
+ }
418
+ const pageHtmlPath = htmlPath(config, route);
419
+ const html = await renderProjectHtml(config, {
420
+ head: page.stylesheetAssets.map((asset) => ({
421
+ tag: "link",
422
+ attributes: {
423
+ rel: "stylesheet",
424
+ href: toAssetUrl(config.build.outDir, pageHtmlPath, asset)
425
+ }
426
+ })),
427
+ scripts: page.javascriptAssets.map((asset) => ({
428
+ type: "module",
429
+ src: toAssetUrl(config.build.outDir, pageHtmlPath, asset)
430
+ }))
431
+ }, page.entry, route);
432
+ await mkdir(dirname2(pageHtmlPath), { recursive: true });
433
+ await writeFile(pageHtmlPath, html);
434
+ assetsBuilt.push(...page.assets, pageHtmlPath);
435
+ }
436
+ return {
437
+ root: config.root,
438
+ outDir: config.build.outDir,
439
+ assetsBuilt
440
+ };
441
+ }
442
+ }
443
+ async function buildPage(config, entry) {
444
+ let result;
445
+ try {
446
+ result = await Bun.build({
447
+ entrypoints: [entry],
448
+ outdir: config.build.outDir,
449
+ target: "browser",
450
+ format: "esm",
451
+ minify: process.env.TRANSONE_MINIFY !== "0",
452
+ naming: { entry: "[name].[ext]", chunk: "[name]-[hash].[ext]" },
453
+ throw: false
454
+ });
455
+ } catch (error) {
456
+ return pageFailureReason(entry, [errorMessage(error)]);
457
+ }
458
+ const assets = result.outputs.map((output) => resolve3(output.path));
459
+ const javascriptAssets = result.outputs.filter(isEntryJavaScriptOutput).map((output) => resolve3(output.path));
460
+ const stylesheetAssets = result.outputs.filter(isStylesheetOutput).map((output) => resolve3(output.path));
461
+ if (!result.success || assets.length === 0 || javascriptAssets.length === 0) {
462
+ return pageFailureReason(entry, result.logs.map((log) => log.message), {
463
+ success: result.success,
464
+ hasOutput: assets.length > 0,
465
+ hasJavaScript: javascriptAssets.length > 0
466
+ });
467
+ }
468
+ return {
469
+ entry,
470
+ assets,
471
+ javascriptAssets,
472
+ stylesheetAssets
473
+ };
474
+ }
475
+ function pageFailureReason(entry, logs, state) {
476
+ const reasons = [
477
+ state && !state.success ? "Bun build reported failure" : "",
478
+ state && !state.hasOutput ? "Bun emitted no output files" : "",
479
+ state && !state.hasJavaScript ? "Bun emitted no JavaScript output" : "",
480
+ ...logs
481
+ ].filter((reason) => reason !== "");
482
+ return `${entry}: ${reasons.join(`
483
+ `)}`;
484
+ }
485
+ function buildFailure(failures) {
486
+ if (failures.length === 1) {
487
+ return new Error(`Failed to build TransOne application: ${failures[0]}`);
488
+ }
489
+ return new Error(`Failed to build TransOne application:
490
+ ${failures.map((failure) => `- ${failure}`).join(`
491
+ `)}`);
492
+ }
493
+ function htmlPath(config, route) {
494
+ if (route === "/") {
495
+ return resolve3(config.build.outDir, "index.html");
496
+ }
497
+ const relativeRoute = route.replace(/^\/+/, "");
498
+ if (config.build.directoryPages) {
499
+ return resolve3(config.build.outDir, relativeRoute, "index.html");
500
+ }
501
+ return resolve3(config.build.outDir, `${relativeRoute}.html`);
502
+ }
503
+ function toAssetUrl(outDir, fromHtmlFile, asset) {
504
+ const fromOutDir = relative2(outDir, asset).split(sep2).join("/");
505
+ if (fromOutDir === "" || fromOutDir === ".." || fromOutDir.startsWith("../") || isAbsolute2(fromOutDir)) {
506
+ throw new Error(`Build asset must be inside the output directory: ${asset}`);
507
+ }
508
+ const fromHtmlDir = dirname2(fromHtmlFile);
509
+ const assetPath = relative2(fromHtmlDir, asset).split(sep2).join("/");
510
+ return `./${assetPath}`;
511
+ }
512
+ function errorMessage(error) {
513
+ return error instanceof Error ? error.message : String(error);
514
+ }
515
+
516
+ // src/target/placeholder-target.ts
517
+ class PlaceholderTarget {
518
+ type;
519
+ label;
520
+ plannedMilestone;
521
+ constructor(type, label, plannedMilestone) {
522
+ this.type = type;
523
+ this.label = label;
524
+ this.plannedMilestone = plannedMilestone;
525
+ }
526
+ async build(_config, _options) {
527
+ const available = ["web"].map((target) => `'${target}'`).join(", ");
528
+ throw new Error(`Target "${this.type}" (${this.label}) \u5C1A\u672A\u5B9E\u73B0\uFF0C\u9884\u8BA1\u5728 ${this.plannedMilestone} \u843D\u5730\u3002` + `\u5F53\u524D\u53EF\u7528\u76EE\u6807\u7AEF\uFF1A${available}\u3002`);
529
+ }
530
+ }
531
+
532
+ // src/target/registry.ts
533
+ class TargetRegistry {
534
+ targets = new Map;
535
+ constructor() {
536
+ this.register(new WebTarget);
537
+ this.register(new PlaceholderTarget("mp-weixin", "\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B", "M2"));
538
+ this.register(new PlaceholderTarget("mp-alipay", "\u963F\u91CC\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B", "M3"));
539
+ this.register(new PlaceholderTarget("mp-bytedance", "\u5B57\u8282\u5C0F\u7A0B\u5E8F\u539F\u751F\u5DE5\u7A0B", "M3"));
540
+ this.register(new PlaceholderTarget("app-ios", "iOS \u539F\u751F App", "\u8FDC\u671F"));
541
+ this.register(new PlaceholderTarget("app-android", "Android \u539F\u751F App", "\u8FDC\u671F"));
542
+ this.register(new PlaceholderTarget("app-harmony", "\u9E3F\u8499\u539F\u751F App\uFF08ArkUI\uFF09", "\u8FDC\u671F"));
543
+ }
544
+ register(target) {
545
+ this.targets.set(target.type, target);
546
+ }
547
+ resolve(type) {
548
+ const target = this.targets.get(type);
549
+ if (!target) {
550
+ throw new Error(`Unknown build target: ${type}`);
551
+ }
552
+ return target;
553
+ }
554
+ getAvailableTargets() {
555
+ return [...this.targets.values()];
556
+ }
557
+ }
558
+ var targetRegistry = new TargetRegistry;
559
+
560
+ // src/build.ts
561
+ import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
562
+ import { dirname as dirname3, resolve as resolve4 } from "path";
563
+ async function build(options = {}) {
564
+ const config = await resolveConfig(options);
565
+ if (options.library) {
566
+ return buildLibrary(config);
567
+ }
568
+ const target = targetRegistry.resolve(config.target);
569
+ return target.build(config, options);
570
+ }
571
+ async function buildLibrary(config) {
572
+ const library = config.library;
573
+ if (!library) {
574
+ throw new Error("TransOne library build requires config.library");
575
+ }
576
+ await rm2(library.outDir, { recursive: true, force: true });
577
+ await mkdir2(library.outDir, { recursive: true });
578
+ if (library.dts) {
579
+ for (const tsconfig of library.tsconfigs) {
580
+ await runTsc(config.root, tsconfig);
581
+ }
582
+ }
583
+ const minify = library.minify ?? process.env.TRANSONE_MINIFY !== "0";
584
+ const result = await Bun.build({
585
+ entrypoints: [library.entry],
586
+ outdir: library.outDir,
587
+ root: dirname3(library.entry),
588
+ target: "browser",
589
+ format: "esm",
590
+ sourcemap: library.sourcemap ? "linked" : undefined,
591
+ splitting: library.splitting,
592
+ minify,
593
+ external: library.external,
594
+ throw: false
595
+ });
596
+ if (!result.success || result.outputs.length === 0) {
597
+ throw new Error(`Failed to build TransOne library:
598
+ ${result.logs.map((log) => log.message).join(`
599
+ `)}`);
600
+ }
601
+ const assetsBuilt = result.outputs.map((output) => resolve4(output.path));
602
+ return { root: config.root, outDir: library.outDir, assetsBuilt };
603
+ }
604
+ async function runTsc(root, tsconfig) {
605
+ const proc = Bun.spawn(["bunx", "tsc", "--project", tsconfig], {
606
+ cwd: root,
607
+ stdout: "inherit",
608
+ stderr: "inherit"
609
+ });
610
+ const exitCode = await proc.exited;
611
+ if (exitCode !== 0) {
612
+ throw new Error(`tsc --project ${tsconfig} failed with exit code ${exitCode}`);
613
+ }
614
+ }
615
+
616
+ // src/create.ts
617
+ import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
618
+ import { basename, join } from "path";
619
+ var TRANSONE_FRAMEWORK_VERSION = "0.1.0";
620
+ var TRANSONE_CLI_VERSION = "0.1.0";
621
+ var GITHUB_URL = "https://github.com/geektech-team/transone";
622
+ function createProject(options = {}) {
623
+ const root = options.root ?? process.cwd();
624
+ const name = options.name ?? basename(root);
625
+ const files = scaffoldFiles(name);
626
+ const existing = files.filter((file) => existsSync2(join(root, file.path)));
627
+ if (existing.length > 0) {
628
+ throw new Error(`Refusing to overwrite existing TransOne project files: ${existing.map((file) => file.path).join(", ")}`);
629
+ }
630
+ mkdirSync(join(root, "src"), { recursive: true });
631
+ const created = [];
632
+ for (const file of files) {
633
+ writeFileSync(join(root, file.path), file.content, "utf8");
634
+ created.push(file.path);
635
+ }
636
+ return { root, files: created };
637
+ }
638
+ function scaffoldFiles(name) {
639
+ return [
640
+ {
641
+ path: "package.json",
642
+ content: `${JSON.stringify({
643
+ name,
644
+ private: true,
645
+ type: "module",
646
+ scripts: {
647
+ dev: "transone dev",
648
+ build: "transone build",
649
+ typecheck: "bunx tsc --noEmit"
650
+ },
651
+ dependencies: {
652
+ "@geektech/transone": `^${TRANSONE_FRAMEWORK_VERSION}`
653
+ },
654
+ devDependencies: {
655
+ "@geektech/transone-cli": `^${TRANSONE_CLI_VERSION}`
656
+ }
657
+ }, null, 2)}
658
+ `
659
+ },
660
+ {
661
+ path: "transone.config.ts",
662
+ content: `import { defineConfig } from '@geektech/transone-cli';
663
+
664
+ export default defineConfig({
665
+ entry: 'src/main.ts',
666
+ });
667
+ `
668
+ },
669
+ {
670
+ path: "tsconfig.json",
671
+ content: `${JSON.stringify({
672
+ compilerOptions: {
673
+ target: "ESNext",
674
+ module: "ESNext",
675
+ moduleResolution: "Bundler",
676
+ lib: ["ESNext", "DOM", "DOM.Iterable"],
677
+ strict: true,
678
+ noEmit: true,
679
+ skipLibCheck: true,
680
+ verbatimModuleSyntax: true
681
+ },
682
+ include: ["src/**/*", "transone.config.ts"]
683
+ }, null, 2)}
684
+ `
685
+ },
686
+ {
687
+ path: ".gitignore",
688
+ content: `node_modules/
689
+ dist/
690
+ `
691
+ },
692
+ {
693
+ path: "src/main.ts",
694
+ content: `import { Component, createApp, h, type VNode } from '@geektech/transone';
695
+
696
+ class App extends Component<object, object> {
697
+ protected initState(): object {
698
+ return {};
699
+ }
700
+
701
+ protected initStyles(): void {
702
+ this.styleManager.addStyle('app-body', {
703
+ selector: 'body',
704
+ properties: {
705
+ margin: 0,
706
+ background: '#f7fbf6',
707
+ color: '#142216',
708
+ fontFamily:
709
+ "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif",
710
+ },
711
+ });
712
+ this.styleManager.addStyle('app-shell', {
713
+ selector: '.app-shell',
714
+ properties: {
715
+ alignItems: 'center',
716
+ display: 'flex',
717
+ flexDirection: 'column',
718
+ gap: '14px',
719
+ justifyContent: 'center',
720
+ minHeight: '100vh',
721
+ },
722
+ });
723
+ this.styleManager.addStyle('app-title', {
724
+ selector: '.app-title',
725
+ properties: {
726
+ fontSize: '64px',
727
+ letterSpacing: 0,
728
+ lineHeight: 1.05,
729
+ margin: 0,
730
+ },
731
+ });
732
+ this.styleManager.addStyle('app-link', {
733
+ selector: '.app-link',
734
+ properties: {
735
+ color: '#2f7c39',
736
+ fontSize: '16px',
737
+ textDecoration: 'none',
738
+ },
739
+ hover: {
740
+ textDecoration: 'underline',
741
+ },
742
+ });
743
+ }
744
+
745
+ protected render(): VNode {
746
+ return h('main', { className: 'app-shell' }, [
747
+ h('h1', { className: 'app-title' }, ['TransOne']),
748
+ h(
749
+ 'a',
750
+ { className: 'app-link', href: '${GITHUB_URL}' },
751
+ ['GitHub']
752
+ ),
753
+ ]);
754
+ }
755
+ }
756
+
757
+ export const app = createApp({
758
+ root: App,
759
+ document: {
760
+ lang: 'zh-CN',
761
+ title: 'TransOne',
762
+ description: '\u8F7B\u91CF\u7EA7\u7EAF TypeScript \u524D\u7AEF\u6846\u67B6',
763
+ },
764
+ });
765
+
766
+ app.mount();
767
+ `
768
+ }
769
+ ];
770
+ }
771
+
772
+ // src/server.ts
773
+ import { randomUUID } from "crypto";
774
+ import {
775
+ basename as basename2,
776
+ extname,
777
+ isAbsolute as isAbsolute4,
778
+ relative as relative4,
779
+ resolve as resolve5,
780
+ sep as sep3
781
+ } from "path";
782
+
783
+ // src/proxy.ts
784
+ var HOP_BY_HOP_HEADERS = new Set([
785
+ "connection",
786
+ "keep-alive",
787
+ "proxy-authenticate",
788
+ "proxy-authorization",
789
+ "te",
790
+ "trailer",
791
+ "transfer-encoding",
792
+ "upgrade"
793
+ ]);
794
+ function createProxyHandler(proxy) {
795
+ const rules = Object.entries(proxy ?? {}).map(([prefix, options]) => toProxyRule(prefix, options)).sort((first, second) => second.prefix.length - first.prefix.length);
796
+ return async (request) => {
797
+ const sourceUrl = new URL(request.url);
798
+ const rule = rules.find(({ prefix }) => sourceUrl.pathname.startsWith(prefix));
799
+ if (!rule) {
800
+ return;
801
+ }
802
+ const target = createTargetUrl(rule, sourceUrl);
803
+ const headers = createRequestHeaders(request, sourceUrl, rule, target);
804
+ const body = request.method === "GET" || request.method === "HEAD" ? undefined : request.body;
805
+ try {
806
+ const upstream = await fetch(target, {
807
+ method: request.method,
808
+ headers,
809
+ body,
810
+ signal: request.signal,
811
+ redirect: "manual"
812
+ });
813
+ return new Response(upstream.body, {
814
+ status: upstream.status,
815
+ statusText: upstream.statusText,
816
+ headers: filterHeaders(upstream.headers)
817
+ });
818
+ } catch {
819
+ return new Response("Bad Gateway", { status: 502 });
820
+ }
821
+ };
822
+ }
823
+ function toProxyRule(prefix, value) {
824
+ const options = typeof value === "string" ? { target: value } : value;
825
+ return {
826
+ prefix,
827
+ target: new URL(options.target),
828
+ changeOrigin: options.changeOrigin ?? false,
829
+ ...options.rewrite === undefined ? {} : { rewrite: options.rewrite }
830
+ };
831
+ }
832
+ function createTargetUrl(rule, sourceUrl) {
833
+ const target = new URL(rule.target);
834
+ const pathname = rule.rewrite?.(sourceUrl.pathname) ?? sourceUrl.pathname;
835
+ target.pathname = joinPathnames(target.pathname, pathname);
836
+ target.search = sourceUrl.search;
837
+ return target;
838
+ }
839
+ function joinPathnames(basePathname, pathname) {
840
+ const base = basePathname === "/" ? "" : basePathname.replace(/\/+$/, "");
841
+ const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
842
+ return `${base}${path}` || "/";
843
+ }
844
+ function createRequestHeaders(request, sourceUrl, rule, target) {
845
+ const headers = filterHeaders(request.headers);
846
+ const incomingHost = request.headers.get("host") ?? sourceUrl.host;
847
+ headers.set("host", rule.changeOrigin ? target.host : incomingHost);
848
+ return headers;
849
+ }
850
+ function filterHeaders(source) {
851
+ const headers = new Headers(source);
852
+ const connection = headers.get("connection");
853
+ if (connection) {
854
+ connection.split(",").forEach((header) => {
855
+ const name = header.trim();
856
+ if (name) {
857
+ headers.delete(name);
858
+ }
859
+ });
860
+ }
861
+ HOP_BY_HOP_HEADERS.forEach((header) => headers.delete(header));
862
+ return headers;
863
+ }
864
+
865
+ // src/watch.ts
866
+ import { watch } from "fs";
867
+ import { readdir, stat } from "fs/promises";
868
+ import { isAbsolute as isAbsolute3, join as join2, relative as relative3 } from "path";
869
+ var RECURSIVE_WATCH_UNSUPPORTED_ERROR = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
870
+ var DEFAULT_DEBOUNCE_MS = 100;
871
+ var DEFAULT_POLL_INTERVAL_MS = 300;
872
+ function createFileWatcher(options) {
873
+ const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
874
+ let timer;
875
+ const disposers = [];
876
+ const scheduleChange = (relativePath) => {
877
+ if (timer) {
878
+ clearTimeout(timer);
879
+ }
880
+ timer = setTimeout(() => {
881
+ timer = undefined;
882
+ options.onChange(relativePath);
883
+ }, debounceMs);
884
+ };
885
+ disposers.push(() => {
886
+ if (timer) {
887
+ clearTimeout(timer);
888
+ }
889
+ });
890
+ const nativeWatcher = tryCreateNativeWatcher(options.root, options.isRelevant, scheduleChange);
891
+ if (nativeWatcher) {
892
+ disposers.push(nativeWatcher);
893
+ } else {
894
+ disposers.push(createPollingWatcher(options.root, options.isRelevant, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS, scheduleChange));
895
+ }
896
+ return {
897
+ dispose: () => {
898
+ disposers.splice(0).reverse().forEach((dispose) => dispose());
899
+ }
900
+ };
901
+ }
902
+ function tryCreateNativeWatcher(root, isRelevant, onRelevantChange) {
903
+ let watcher;
904
+ try {
905
+ watcher = watch(root, { recursive: true }, (_event, filename) => {
906
+ if (typeof filename !== "string") {
907
+ return;
908
+ }
909
+ const relativePath = toRelativePath(root, filename);
910
+ if (relativePath === undefined || !isRelevant(relativePath)) {
911
+ return;
912
+ }
913
+ onRelevantChange(relativePath);
914
+ });
915
+ } catch (error) {
916
+ if (isRecursiveWatchUnsupported(error)) {
917
+ return;
918
+ }
919
+ throw error;
920
+ }
921
+ return () => watcher.close();
922
+ }
923
+ function createPollingWatcher(root, isRelevant, intervalMs, onRelevantChange) {
924
+ let running = true;
925
+ let snapshot = new Map;
926
+ let timer;
927
+ const scan = async () => {
928
+ if (!running) {
929
+ return;
930
+ }
931
+ const next = new Map;
932
+ try {
933
+ await collectRelevantFiles(root, isRelevant, next);
934
+ } catch {
935
+ return;
936
+ }
937
+ let changedPath;
938
+ for (const [relativePath, snapshotEntry] of next) {
939
+ const previous = snapshot.get(relativePath);
940
+ if (!previous || previous.mtimeMs !== snapshotEntry.mtimeMs || previous.size !== snapshotEntry.size) {
941
+ changedPath = relativePath;
942
+ break;
943
+ }
944
+ }
945
+ if (changedPath === undefined) {
946
+ for (const relativePath of snapshot.keys()) {
947
+ if (!next.has(relativePath)) {
948
+ changedPath = relativePath;
949
+ break;
950
+ }
951
+ }
952
+ }
953
+ snapshot = next;
954
+ if (changedPath !== undefined) {
955
+ onRelevantChange(changedPath);
956
+ }
957
+ };
958
+ scan();
959
+ timer = setInterval(() => {
960
+ scan();
961
+ }, intervalMs);
962
+ if (typeof timer.unref === "function") {
963
+ timer.unref();
964
+ }
965
+ return () => {
966
+ running = false;
967
+ if (timer) {
968
+ clearInterval(timer);
969
+ }
970
+ };
971
+ }
972
+ async function collectRelevantFiles(root, isRelevant, output) {
973
+ const pendingDirectories = [""];
974
+ while (pendingDirectories.length > 0) {
975
+ const relativeDirectory = pendingDirectories.pop();
976
+ if (relativeDirectory === undefined) {
977
+ break;
978
+ }
979
+ const absoluteDirectory = join2(root, relativeDirectory);
980
+ let entries;
981
+ try {
982
+ entries = await readdir(absoluteDirectory, { withFileTypes: true });
983
+ } catch {
984
+ continue;
985
+ }
986
+ for (const entry of entries) {
987
+ const relativePath = relativeDirectory === "" ? entry.name : `${relativeDirectory}/${entry.name}`;
988
+ if (entry.isDirectory()) {
989
+ pendingDirectories.push(relativePath);
990
+ continue;
991
+ }
992
+ if (!entry.isFile() || !isRelevant(relativePath)) {
993
+ continue;
994
+ }
995
+ try {
996
+ const info = await stat(join2(root, relativePath));
997
+ output.set(relativePath, { mtimeMs: info.mtimeMs, size: info.size });
998
+ } catch {}
999
+ }
1000
+ }
1001
+ }
1002
+ function toRelativePath(root, filename) {
1003
+ const relativePath = isAbsolute3(filename) ? relative3(root, filename) : filename.split("\\").join("/");
1004
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) {
1005
+ return;
1006
+ }
1007
+ return relativePath.split("\\").join("/");
1008
+ }
1009
+ function isRecursiveWatchUnsupported(error) {
1010
+ return typeof error === "object" && error !== null && "code" in error && error.code === RECURSIVE_WATCH_UNSUPPORTED_ERROR;
1011
+ }
1012
+
1013
+ // src/server.ts
1014
+ var DEVELOPMENT_URL_PREFIX = "/dev";
1015
+ var INVALID_DEVELOPMENT_DIRECTORY_MESSAGE = "Development output must be a subdirectory of the project root";
1016
+ var LIVE_RELOAD_PATH = "/__transone/reload";
1017
+ var WATCHED_EXTENSIONS = new Set([
1018
+ ".ts",
1019
+ ".js",
1020
+ ".mjs",
1021
+ ".cjs",
1022
+ ".json",
1023
+ ".css",
1024
+ ".html"
1025
+ ]);
1026
+ var IGNORED_WATCH_SEGMENTS = new Set([
1027
+ "dist",
1028
+ "node_modules",
1029
+ ".git",
1030
+ ".worktrees"
1031
+ ]);
1032
+ var LIVE_RELOAD_CLIENT_SCRIPT = [
1033
+ '<script type="module">',
1034
+ "const transoneReloadSource=new EventSource(",
1035
+ JSON.stringify(LIVE_RELOAD_PATH),
1036
+ ");",
1037
+ 'transoneReloadSource.addEventListener("reload",()=>location.reload());',
1038
+ "</script>"
1039
+ ].join("");
1040
+ async function startDevServer(options = {}) {
1041
+ if (options.watch) {
1042
+ return startWatchingDevServer(options);
1043
+ }
1044
+ const instance = await createServerInstance(options, false);
1045
+ return instance.server;
1046
+ }
1047
+ async function startWatchingDevServer(options) {
1048
+ let instance = await createServerInstance(options, true);
1049
+ let watcher = createProjectWatcher(instance, onChange);
1050
+ async function onChange(changedPath) {
1051
+ const configFile = instance.config.configFile;
1052
+ const configRelativePath = configFile === undefined ? undefined : toRelativePath2(instance.config.root, configFile);
1053
+ if (configRelativePath === changedPath) {
1054
+ await restart();
1055
+ return;
1056
+ }
1057
+ await rebuildAndReload();
1058
+ }
1059
+ async function rebuildAndReload() {
1060
+ const entries = [...new Set(Object.values(instance.config.pages))];
1061
+ instance.markDirty(entries);
1062
+ for (const entry of entries) {
1063
+ const bundle = await instance.getBundle(entry);
1064
+ if (bundle instanceof Response) {
1065
+ console.error("TransOne rebuild failed; keeping the last working page.");
1066
+ return;
1067
+ }
1068
+ }
1069
+ instance.liveReload?.broadcast();
1070
+ }
1071
+ async function restart() {
1072
+ watcher.dispose();
1073
+ instance.server.stop(true);
1074
+ instance = await createServerInstance(options, true);
1075
+ watcher = createProjectWatcher(instance, onChange);
1076
+ console.log(`TransOne dev server restarted at http://${instance.server.hostname}:${instance.server.port}`);
1077
+ }
1078
+ return createServerHandle(() => instance, () => watcher.dispose());
1079
+ }
1080
+ function createProjectWatcher(instance, onChange) {
1081
+ return createFileWatcher({
1082
+ root: instance.config.root,
1083
+ isRelevant: (relativePath) => isWatchedFile(instance, relativePath),
1084
+ onChange: (changedPath) => {
1085
+ onChange(changedPath);
1086
+ }
1087
+ });
1088
+ }
1089
+ async function createServerInstance(options, watch2) {
1090
+ const config = await resolveConfig(options);
1091
+ const { renderProjectHtml } = await import("./project-ybacfze8.js");
1092
+ for (const [route, entry] of Object.entries(config.pages)) {
1093
+ await renderProjectHtml(config, {}, entry, route);
1094
+ }
1095
+ const developmentOutDir = resolve5(config.root, "dist", "dev", "h5");
1096
+ await assertSafeSubdirectory(config.root, developmentOutDir, INVALID_DEVELOPMENT_DIRECTORY_MESSAGE);
1097
+ const proxy = createProxyHandler(config.server.proxy);
1098
+ const sessionId = createGenerationId();
1099
+ const sessionOutDir = resolve5(developmentOutDir, sessionId);
1100
+ const artifacts = new Map;
1101
+ const buildProject = createDevelopmentBuilder(sessionId, sessionOutDir);
1102
+ const liveReload = watch2 ? createLiveReloadHub() : undefined;
1103
+ const bundles = new Map;
1104
+ const dirtyEntries = new Set;
1105
+ const getBundle = async (entry) => {
1106
+ const cached = bundles.get(entry);
1107
+ if (watch2 && cached !== undefined && !dirtyEntries.has(entry)) {
1108
+ return cached;
1109
+ }
1110
+ const bundle = await buildProject(entry);
1111
+ if (bundle instanceof Response) {
1112
+ if (watch2) {
1113
+ dirtyEntries.add(entry);
1114
+ }
1115
+ return bundle;
1116
+ }
1117
+ if (watch2) {
1118
+ bundles.set(entry, bundle);
1119
+ dirtyEntries.delete(entry);
1120
+ }
1121
+ return bundle;
1122
+ };
1123
+ const server = Bun.serve({
1124
+ hostname: config.server.host,
1125
+ port: config.server.port,
1126
+ fetch: async (request) => {
1127
+ const reloadResponse = liveReload?.handleRequest(request);
1128
+ if (reloadResponse) {
1129
+ return reloadResponse;
1130
+ }
1131
+ return await proxy(request) ?? serveProjectRequest(request, config, config.pages, getBundle, artifacts, liveReload);
1132
+ }
1133
+ });
1134
+ return {
1135
+ server,
1136
+ config,
1137
+ getBundle,
1138
+ markDirty: (entries) => {
1139
+ entries.forEach((entry) => dirtyEntries.add(entry));
1140
+ },
1141
+ liveReload
1142
+ };
1143
+ }
1144
+ function createServerHandle(getInstance, disposeWatcher) {
1145
+ return new Proxy({}, {
1146
+ get(_target, property) {
1147
+ if (property === "stop") {
1148
+ return (closeActiveConnections) => {
1149
+ disposeWatcher();
1150
+ getInstance().server.stop(closeActiveConnections);
1151
+ };
1152
+ }
1153
+ const server = getInstance().server;
1154
+ const value = server[property];
1155
+ return typeof value === "function" ? value.bind(server) : value;
1156
+ }
1157
+ });
1158
+ }
1159
+ async function serveProjectRequest(request, config, pages, getBundle, artifacts, liveReload) {
1160
+ const pathname = new URL(request.url).pathname;
1161
+ const route = pageRouteForPathname(pages, pathname);
1162
+ if (route !== undefined) {
1163
+ const entry = pages[route];
1164
+ const bundle = await getBundle(entry);
1165
+ if (bundle instanceof Response) {
1166
+ return bundle;
1167
+ }
1168
+ try {
1169
+ const { renderProjectHtml } = await import("./project-ybacfze8.js");
1170
+ const html = await renderProjectHtml(config, {
1171
+ head: bundle.stylesheets.map(({ pathname: href }) => ({
1172
+ tag: "link",
1173
+ attributes: { rel: "stylesheet", href }
1174
+ })),
1175
+ scripts: [{ type: "module", src: bundle.entry.pathname }]
1176
+ }, entry, route);
1177
+ publishDevelopmentBundle(bundle, artifacts);
1178
+ return new Response(liveReload ? injectLiveReloadScript(html) : html, {
1179
+ headers: {
1180
+ "content-type": "text/html; charset=utf-8",
1181
+ "cache-control": "no-store"
1182
+ }
1183
+ });
1184
+ } catch (error) {
1185
+ console.error(error);
1186
+ return buildFailureResponse();
1187
+ }
1188
+ }
1189
+ if (pathname === "/bundle.js") {
1190
+ const bundle = await getBundle(config.entry);
1191
+ if (bundle instanceof Response) {
1192
+ return bundle;
1193
+ }
1194
+ try {
1195
+ publishDevelopmentBundle(bundle, artifacts);
1196
+ return new Response(null, {
1197
+ status: 307,
1198
+ headers: {
1199
+ location: bundle.entry.pathname,
1200
+ "cache-control": "no-store"
1201
+ }
1202
+ });
1203
+ } catch (error) {
1204
+ console.error(error);
1205
+ return buildFailureResponse();
1206
+ }
1207
+ }
1208
+ const artifact = artifacts.get(pathname);
1209
+ if (artifact) {
1210
+ return new Response(Bun.file(artifact.filePath), {
1211
+ headers: {
1212
+ "content-type": artifact.type,
1213
+ "cache-control": "no-store"
1214
+ }
1215
+ });
1216
+ }
1217
+ return new Response("Not found", {
1218
+ status: 404,
1219
+ headers: { "cache-control": "no-store" }
1220
+ });
1221
+ }
1222
+ function pageRouteForPathname(pages, pathname) {
1223
+ if (pages[pathname] !== undefined) {
1224
+ return pathname;
1225
+ }
1226
+ if (pathname === "/index.html") {
1227
+ return "/";
1228
+ }
1229
+ const candidate = pathname.length > 1 && pathname.endsWith("/") ? pathname.replace(/\/+$/, "") : pathname;
1230
+ if (pages[candidate] !== undefined) {
1231
+ return candidate;
1232
+ }
1233
+ if (candidate.endsWith("/index.html")) {
1234
+ const base = candidate.slice(0, candidate.length - "/index.html".length);
1235
+ if (base === "") {
1236
+ return "/";
1237
+ }
1238
+ if (pages[base] !== undefined) {
1239
+ return base;
1240
+ }
1241
+ }
1242
+ return;
1243
+ }
1244
+ function injectLiveReloadScript(html) {
1245
+ if (html.includes(LIVE_RELOAD_PATH)) {
1246
+ return html;
1247
+ }
1248
+ if (html.includes("</body>")) {
1249
+ return html.replace("</body>", `${LIVE_RELOAD_CLIENT_SCRIPT}</body>`);
1250
+ }
1251
+ return html + LIVE_RELOAD_CLIENT_SCRIPT;
1252
+ }
1253
+ var reloadEncoder = new TextEncoder;
1254
+ function createLiveReloadHub() {
1255
+ const controllers = new Set;
1256
+ function handleRequest(request) {
1257
+ if (new URL(request.url).pathname !== LIVE_RELOAD_PATH) {
1258
+ return;
1259
+ }
1260
+ let streamController;
1261
+ const stream = new ReadableStream({
1262
+ start: (controller) => {
1263
+ streamController = controller;
1264
+ controllers.add(controller);
1265
+ try {
1266
+ controller.enqueue(reloadEncoder.encode(`: connected
1267
+
1268
+ `));
1269
+ } catch {
1270
+ controllers.delete(controller);
1271
+ }
1272
+ },
1273
+ cancel: () => {
1274
+ if (streamController) {
1275
+ controllers.delete(streamController);
1276
+ }
1277
+ }
1278
+ });
1279
+ return new Response(stream, {
1280
+ headers: {
1281
+ "content-type": "text/event-stream",
1282
+ "cache-control": "no-store"
1283
+ }
1284
+ });
1285
+ }
1286
+ function broadcast() {
1287
+ const message = reloadEncoder.encode(`event: reload
1288
+ data: {}
1289
+
1290
+ `);
1291
+ for (const controller of [...controllers]) {
1292
+ try {
1293
+ controller.enqueue(message);
1294
+ } catch {
1295
+ controllers.delete(controller);
1296
+ }
1297
+ }
1298
+ }
1299
+ return { handleRequest, broadcast };
1300
+ }
1301
+ function isWatchedFile(instance, relativePath) {
1302
+ if (relativePath === "" || relativePath.startsWith("..")) {
1303
+ return false;
1304
+ }
1305
+ const segments = relativePath.split("/");
1306
+ if (segments.some((segment) => IGNORED_WATCH_SEGMENTS.has(segment))) {
1307
+ return false;
1308
+ }
1309
+ const absolutePath = resolve5(instance.config.root, relativePath);
1310
+ if (isWithinOrEqual(instance.config.build.outDir, absolutePath)) {
1311
+ return false;
1312
+ }
1313
+ const base = basename2(absolutePath);
1314
+ if (base.startsWith(".") && absolutePath !== instance.config.configFile) {
1315
+ return false;
1316
+ }
1317
+ return WATCHED_EXTENSIONS.has(extname(base).toLowerCase());
1318
+ }
1319
+ function toRelativePath2(root, absolutePath) {
1320
+ return relative4(root, absolutePath).split(sep3).join("/");
1321
+ }
1322
+ function isWithinOrEqual(parent, candidate) {
1323
+ const pathFromParent = relative4(parent, candidate);
1324
+ return pathFromParent === "" || pathFromParent !== ".." && !pathFromParent.startsWith(`..${sep3}`) && !isAbsolute4(pathFromParent);
1325
+ }
1326
+ function createDevelopmentBuilder(sessionId, sessionOutDir) {
1327
+ let queue = Promise.resolve();
1328
+ return (entry) => {
1329
+ const result = queue.then(() => buildProjectBundle(entry, sessionId, sessionOutDir));
1330
+ queue = result.then(() => {
1331
+ return;
1332
+ }, () => {
1333
+ return;
1334
+ });
1335
+ return result;
1336
+ };
1337
+ }
1338
+ async function buildProjectBundle(entry, sessionId, sessionOutDir) {
1339
+ const generationId = createGenerationId();
1340
+ const generationOutDir = resolve5(sessionOutDir, generationId);
1341
+ const generationUrl = `${DEVELOPMENT_URL_PREFIX}/${sessionId}/${generationId}`;
1342
+ const buildOptions = {
1343
+ entrypoints: [entry],
1344
+ outdir: generationOutDir,
1345
+ target: "browser",
1346
+ format: "esm",
1347
+ sourcemap: "inline",
1348
+ write: true,
1349
+ throw: false,
1350
+ publicPath: `${generationUrl}/`,
1351
+ naming: {
1352
+ entry: "[name]-[hash].[ext]",
1353
+ chunk: "[name]-[hash].[ext]",
1354
+ asset: "assets/[name]-[hash].[ext]"
1355
+ }
1356
+ };
1357
+ try {
1358
+ const result = await Bun.build(buildOptions);
1359
+ const entryArtifact = result.outputs.find(isEntryJavaScriptOutput);
1360
+ if (!result.success || !entryArtifact) {
1361
+ result.logs.forEach((log) => console.error(log));
1362
+ if (!entryArtifact) {
1363
+ console.error("Failed to build project bundle: no JavaScript output");
1364
+ }
1365
+ return buildFailureResponse();
1366
+ }
1367
+ const pendingArtifacts = new Map;
1368
+ const outputs = new Map;
1369
+ for (const output of result.outputs) {
1370
+ const developmentOutput = createDevelopmentOutput(generationOutDir, generationUrl, output);
1371
+ if (pendingArtifacts.has(developmentOutput.pathname)) {
1372
+ throw new Error(`Development output URL collision: ${developmentOutput.pathname}`);
1373
+ }
1374
+ pendingArtifacts.set(developmentOutput.pathname, developmentOutput);
1375
+ outputs.set(output, developmentOutput);
1376
+ }
1377
+ const entry2 = outputs.get(entryArtifact);
1378
+ if (!entry2) {
1379
+ throw new Error("Development entry output was not indexed");
1380
+ }
1381
+ return {
1382
+ entry: entry2,
1383
+ stylesheets: result.outputs.filter(isStylesheetOutput).map((output) => outputs.get(output)).filter((output) => output !== undefined),
1384
+ artifacts: pendingArtifacts
1385
+ };
1386
+ } catch (error) {
1387
+ console.error(error);
1388
+ return buildFailureResponse();
1389
+ }
1390
+ }
1391
+ function publishDevelopmentBundle(bundle, artifacts) {
1392
+ for (const [pathname, output] of bundle.artifacts) {
1393
+ const existing = artifacts.get(pathname);
1394
+ if (existing) {
1395
+ if (existing.filePath === output.filePath) {
1396
+ continue;
1397
+ }
1398
+ throw new Error(`Development output URL collision: ${pathname}`);
1399
+ }
1400
+ artifacts.set(pathname, output);
1401
+ }
1402
+ }
1403
+ function createDevelopmentOutput(generationOutDir, generationUrl, output) {
1404
+ const filePath = resolve5(output.path);
1405
+ const pathFromGeneration = relative4(generationOutDir, filePath);
1406
+ if (pathFromGeneration === "" || pathFromGeneration === ".." || pathFromGeneration.startsWith(`..${sep3}`) || isAbsolute4(pathFromGeneration)) {
1407
+ throw new Error(`Development output must not escape outside its generation directory: ${output.path}`);
1408
+ }
1409
+ const urlPath = pathFromGeneration.split(sep3).map((segment) => encodeURIComponent(segment)).join("/");
1410
+ return {
1411
+ filePath,
1412
+ pathname: `${generationUrl}/${urlPath}`,
1413
+ type: output.type
1414
+ };
1415
+ }
1416
+ function createGenerationId() {
1417
+ return randomUUID().replace(/-/g, "");
1418
+ }
1419
+ function buildFailureResponse() {
1420
+ return new Response("Failed to build project bundle", {
1421
+ status: 500,
1422
+ headers: { "cache-control": "no-store" }
1423
+ });
1424
+ }
1425
+
1426
+ export { TARGET_TYPES, isTargetType, defineConfig, resolveConfig, WebTarget, PlaceholderTarget, TargetRegistry, targetRegistry, build, createProject, startDevServer };
1427
+
1428
+ //# debugId=312AA42B6393002164756E2164756E21
1429
+ //# sourceMappingURL=index-30n2h2ep.js.map