ccnew 0.1.10

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.
Files changed (62) hide show
  1. package/README.md +107 -0
  2. package/build/icon.ico +0 -0
  3. package/build/icon.png +0 -0
  4. package/core/apply.js +152 -0
  5. package/core/backup.js +53 -0
  6. package/core/constants.js +78 -0
  7. package/core/desktop-service.js +403 -0
  8. package/core/desktop-state.js +1021 -0
  9. package/core/index.js +1468 -0
  10. package/core/paths.js +99 -0
  11. package/core/presets.js +171 -0
  12. package/core/probe.js +70 -0
  13. package/core/routing.js +334 -0
  14. package/core/store.js +218 -0
  15. package/core/utils.js +225 -0
  16. package/core/writers/codex.js +102 -0
  17. package/core/writers/index.js +16 -0
  18. package/core/writers/openclaw.js +93 -0
  19. package/core/writers/opencode.js +91 -0
  20. package/desktop/assets/fml-icon.png +0 -0
  21. package/desktop/assets/march-mark.svg +26 -0
  22. package/desktop/main.js +275 -0
  23. package/desktop/preload.cjs +67 -0
  24. package/desktop/preload.js +49 -0
  25. package/desktop/renderer/app.js +327 -0
  26. package/desktop/renderer/index.html +130 -0
  27. package/desktop/renderer/styles.css +490 -0
  28. package/package.json +111 -0
  29. package/scripts/build-web.mjs +95 -0
  30. package/scripts/desktop-dev.mjs +90 -0
  31. package/scripts/desktop-pack-win.mjs +81 -0
  32. package/scripts/postinstall.mjs +49 -0
  33. package/scripts/prepublish-check.mjs +57 -0
  34. package/scripts/serve-site.mjs +51 -0
  35. package/site/app.js +10 -0
  36. package/site/assets/fml-icon.png +0 -0
  37. package/site/assets/march-mark.svg +26 -0
  38. package/site/index.html +337 -0
  39. package/site/styles.css +840 -0
  40. package/src/App.tsx +1557 -0
  41. package/src/components/layout/app-sidebar.tsx +103 -0
  42. package/src/components/layout/top-toolbar.tsx +44 -0
  43. package/src/components/layout/workspace-tabs.tsx +32 -0
  44. package/src/components/providers/inspector-panel.tsx +84 -0
  45. package/src/components/providers/metric-strip.tsx +26 -0
  46. package/src/components/providers/provider-editor.tsx +87 -0
  47. package/src/components/providers/provider-table.tsx +85 -0
  48. package/src/components/ui/logo-mark.tsx +32 -0
  49. package/src/features/mcp/mcp-view.tsx +45 -0
  50. package/src/features/prompts/prompts-view.tsx +40 -0
  51. package/src/features/providers/providers-view.tsx +40 -0
  52. package/src/features/providers/types.ts +26 -0
  53. package/src/features/skills/skills-view.tsx +44 -0
  54. package/src/hooks/use-control-workspace.ts +235 -0
  55. package/src/index.css +22 -0
  56. package/src/lib/client.ts +726 -0
  57. package/src/lib/query-client.ts +3 -0
  58. package/src/lib/workspace-sections.ts +34 -0
  59. package/src/main.tsx +14 -0
  60. package/src/types.ts +137 -0
  61. package/src/vite-env.d.ts +64 -0
  62. package/src-tauri/README.md +11 -0
@@ -0,0 +1,490 @@
1
+ :root {
2
+ --bg: #06070b;
3
+ --panel: rgba(18, 24, 34, 0.76);
4
+ --panel-strong: rgba(12, 15, 22, 0.88);
5
+ --ink: rgba(255, 255, 255, 0.95);
6
+ --muted: rgba(222, 229, 239, 0.72);
7
+ --line: rgba(255, 255, 255, 0.1);
8
+ --accent: #8ec5ff;
9
+ --accent-strong: #5b9dff;
10
+ --accent-soft: rgba(142, 197, 255, 0.12);
11
+ --gold: #ff9159;
12
+ --rose: #ff5fa2;
13
+ --shadow: 0 28px 90px rgba(0, 0, 0, 0.42);
14
+ }
15
+
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ html,
21
+ body {
22
+ margin: 0;
23
+ height: 100%;
24
+ }
25
+
26
+ body {
27
+ font-family: "Manrope", "Segoe UI Variable", "PingFang SC", sans-serif;
28
+ color: var(--ink);
29
+ background:
30
+ radial-gradient(900px 640px at 12% 10%, rgba(91, 157, 255, 0.3), rgba(0, 0, 0, 0) 58%),
31
+ radial-gradient(840px 600px at 88% 16%, rgba(255, 95, 162, 0.2), rgba(0, 0, 0, 0) 62%),
32
+ radial-gradient(960px 720px at 50% 100%, rgba(255, 145, 89, 0.14), rgba(0, 0, 0, 0) 60%),
33
+ linear-gradient(180deg, #090b11 0%, #05060a 100%);
34
+ }
35
+
36
+ body::before,
37
+ body::after {
38
+ content: "";
39
+ position: fixed;
40
+ inset: -10%;
41
+ pointer-events: none;
42
+ }
43
+
44
+ body::before {
45
+ background:
46
+ repeating-linear-gradient(
47
+ 90deg,
48
+ rgba(255, 255, 255, 0.025) 0,
49
+ rgba(255, 255, 255, 0.025) 1px,
50
+ transparent 1px,
51
+ transparent 52px
52
+ ),
53
+ repeating-linear-gradient(
54
+ 0deg,
55
+ rgba(255, 255, 255, 0.025) 0,
56
+ rgba(255, 255, 255, 0.025) 1px,
57
+ transparent 1px,
58
+ transparent 52px
59
+ );
60
+ opacity: 0.26;
61
+ transform: rotate(-7deg);
62
+ }
63
+
64
+ body::after {
65
+ background:
66
+ radial-gradient(700px 460px at 20% 18%, rgba(142, 197, 255, 0.18), rgba(0, 0, 0, 0) 62%),
67
+ radial-gradient(720px 520px at 84% 20%, rgba(255, 95, 162, 0.14), rgba(0, 0, 0, 0) 66%),
68
+ radial-gradient(860px 620px at 52% 88%, rgba(255, 145, 89, 0.12), rgba(0, 0, 0, 0) 64%);
69
+ filter: blur(28px);
70
+ opacity: 0.92;
71
+ }
72
+
73
+ button,
74
+ input,
75
+ select {
76
+ font: inherit;
77
+ }
78
+
79
+ .shell {
80
+ display: grid;
81
+ grid-template-columns: 276px minmax(0, 1fr);
82
+ min-height: 100vh;
83
+ }
84
+
85
+ .sidebar {
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: 18px;
89
+ padding: 28px 18px;
90
+ background:
91
+ radial-gradient(circle at top, rgba(91, 157, 255, 0.14), transparent 36%),
92
+ linear-gradient(180deg, rgba(8, 12, 18, 0.98), rgba(13, 18, 28, 0.94));
93
+ color: #eef6f4;
94
+ }
95
+
96
+ .brand {
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 12px;
100
+ padding: 6px 4px 16px;
101
+ }
102
+
103
+ .brand img {
104
+ width: 46px;
105
+ height: 46px;
106
+ }
107
+
108
+ .brand strong,
109
+ .hero h1,
110
+ .panel h2,
111
+ .stat strong {
112
+ font-family: "Space Grotesk", "Segoe UI Variable", sans-serif;
113
+ }
114
+
115
+ .brand strong {
116
+ font-size: 1.05rem;
117
+ }
118
+
119
+ .brand span,
120
+ .eyebrow,
121
+ .sidebar-card span {
122
+ color: rgba(238, 246, 244, 0.72);
123
+ letter-spacing: 0.08em;
124
+ text-transform: uppercase;
125
+ font-size: 0.75rem;
126
+ }
127
+
128
+ .platforms,
129
+ .provider-list,
130
+ .file-list,
131
+ .probe-list,
132
+ .form {
133
+ display: grid;
134
+ gap: 12px;
135
+ }
136
+
137
+ .platform-tab,
138
+ .sidebar-card,
139
+ .panel,
140
+ .stat,
141
+ .provider-card,
142
+ .file-item,
143
+ .probe-row {
144
+ border: 1px solid rgba(255, 255, 255, 0.08);
145
+ border-radius: 22px;
146
+ }
147
+
148
+ .platform-tab {
149
+ display: grid;
150
+ gap: 4px;
151
+ width: 100%;
152
+ padding: 14px 16px;
153
+ color: inherit;
154
+ text-align: left;
155
+ background: rgba(255, 255, 255, 0.04);
156
+ cursor: pointer;
157
+ transition: transform 0.18s ease, background 0.18s ease, border-color 0.18s ease;
158
+ }
159
+
160
+ .platform-tab strong {
161
+ font-size: 1rem;
162
+ }
163
+
164
+ .platform-tab small,
165
+ .provider-card small,
166
+ .probe-row small,
167
+ .empty,
168
+ .inline-status {
169
+ color: rgba(238, 246, 244, 0.72);
170
+ font-size: 0.8rem;
171
+ }
172
+
173
+ .platform-tab.active {
174
+ background: linear-gradient(135deg, rgba(91, 157, 255, 0.24), rgba(255, 95, 162, 0.12));
175
+ border-color: rgba(142, 197, 255, 0.18);
176
+ }
177
+
178
+ .sidebar-card {
179
+ margin-top: auto;
180
+ display: grid;
181
+ gap: 10px;
182
+ padding: 18px;
183
+ background: rgba(255, 255, 255, 0.04);
184
+ }
185
+
186
+ .sidebar-card strong {
187
+ font-size: 1.08rem;
188
+ line-height: 1.5;
189
+ }
190
+
191
+ .sidebar-card small {
192
+ color: rgba(238, 246, 244, 0.68);
193
+ line-height: 1.7;
194
+ }
195
+
196
+ .workspace {
197
+ position: relative;
198
+ z-index: 1;
199
+ padding: 28px;
200
+ }
201
+
202
+ .hero,
203
+ .panel-head,
204
+ .form-actions,
205
+ .hero-actions,
206
+ .provider-card header,
207
+ .file-item header,
208
+ .probe-row header {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: space-between;
212
+ gap: 12px;
213
+ }
214
+
215
+ .hero {
216
+ margin-bottom: 22px;
217
+ position: relative;
218
+ overflow: hidden;
219
+ padding: 24px;
220
+ border: 1px solid var(--line);
221
+ border-radius: 30px;
222
+ background: linear-gradient(180deg, rgba(18, 24, 34, 0.86), rgba(10, 13, 19, 0.94));
223
+ box-shadow: var(--shadow);
224
+ }
225
+
226
+ .hero::before {
227
+ content: "";
228
+ position: absolute;
229
+ inset: -30% auto auto -20%;
230
+ width: 240px;
231
+ height: 240px;
232
+ border-radius: 999px;
233
+ background: radial-gradient(circle, rgba(142, 197, 255, 0.18), rgba(0, 0, 0, 0));
234
+ }
235
+
236
+ .hero::after {
237
+ content: "";
238
+ position: absolute;
239
+ right: -120px;
240
+ top: -100px;
241
+ width: 300px;
242
+ height: 300px;
243
+ border-radius: 999px;
244
+ background: radial-gradient(circle, rgba(255, 95, 162, 0.14), rgba(0, 0, 0, 0));
245
+ }
246
+
247
+ .hero h1 {
248
+ margin: 8px 0 10px;
249
+ font-size: 3.1rem;
250
+ letter-spacing: -0.05em;
251
+ position: relative;
252
+ z-index: 1;
253
+ }
254
+
255
+ .hero p {
256
+ max-width: 760px;
257
+ margin: 0;
258
+ color: var(--muted);
259
+ font-size: 1rem;
260
+ line-height: 1.7;
261
+ position: relative;
262
+ z-index: 1;
263
+ }
264
+
265
+ .hero-actions button,
266
+ .form-actions button,
267
+ .file-item button,
268
+ .provider-card button {
269
+ padding: 12px 16px;
270
+ border: 1px solid var(--line);
271
+ border-radius: 999px;
272
+ color: #edf4ff;
273
+ background: rgba(255, 255, 255, 0.04);
274
+ cursor: pointer;
275
+ transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
276
+ }
277
+
278
+ .hero-actions button:hover,
279
+ .form-actions button:hover,
280
+ .file-item button:hover,
281
+ .provider-card button:hover,
282
+ .platform-tab:hover {
283
+ transform: translateY(-1px);
284
+ }
285
+
286
+ .solid {
287
+ color: #f7fbff;
288
+ border-color: transparent;
289
+ background: linear-gradient(135deg, var(--accent-strong), var(--gold));
290
+ box-shadow: 0 16px 36px rgba(91, 157, 255, 0.18);
291
+ }
292
+
293
+ .stats {
294
+ display: grid;
295
+ grid-template-columns: repeat(4, minmax(0, 1fr));
296
+ gap: 16px;
297
+ margin-bottom: 18px;
298
+ }
299
+
300
+ .stat,
301
+ .panel {
302
+ border-color: rgba(255, 255, 255, 0.08);
303
+ background: rgba(18, 24, 34, 0.72);
304
+ box-shadow: var(--shadow);
305
+ backdrop-filter: blur(18px);
306
+ }
307
+
308
+ .stat {
309
+ display: grid;
310
+ gap: 8px;
311
+ padding: 18px;
312
+ }
313
+
314
+ .stat strong {
315
+ font-size: 1.4rem;
316
+ }
317
+
318
+ .stat span,
319
+ .panel label span,
320
+ .provider-card p,
321
+ .provider-card span,
322
+ .file-item code,
323
+ .probe-row span,
324
+ .empty,
325
+ .inline-status {
326
+ color: var(--muted);
327
+ }
328
+
329
+ .grid {
330
+ display: grid;
331
+ grid-template-columns: 1.15fr 0.85fr;
332
+ gap: 18px;
333
+ }
334
+
335
+ .panel {
336
+ padding: 22px;
337
+ }
338
+
339
+ .panel h2 {
340
+ margin: 6px 0 0;
341
+ font-size: 1.5rem;
342
+ letter-spacing: -0.04em;
343
+ }
344
+
345
+ .provider-list,
346
+ .file-list,
347
+ .probe-list,
348
+ .form {
349
+ margin-top: 18px;
350
+ }
351
+
352
+ .provider-card,
353
+ .file-item,
354
+ .probe-row {
355
+ padding: 16px 18px;
356
+ background: rgba(255, 255, 255, 0.04);
357
+ }
358
+
359
+ .provider-card.active,
360
+ .probe-row.best {
361
+ border-color: rgba(142, 197, 255, 0.24);
362
+ background: linear-gradient(180deg, rgba(91, 157, 255, 0.12), rgba(255, 255, 255, 0.06));
363
+ }
364
+
365
+ .provider-card strong,
366
+ .file-item strong,
367
+ .probe-row strong {
368
+ font-size: 1rem;
369
+ }
370
+
371
+ .provider-card p,
372
+ .provider-card span,
373
+ .provider-card small,
374
+ .file-item code,
375
+ .probe-row span,
376
+ .probe-row small {
377
+ margin: 4px 0 0;
378
+ display: block;
379
+ }
380
+
381
+ .badge {
382
+ padding: 8px 10px;
383
+ border-radius: 999px;
384
+ background: rgba(91, 157, 255, 0.14);
385
+ color: #dbe9ff;
386
+ font-size: 0.74rem;
387
+ font-weight: 700;
388
+ }
389
+
390
+ .form label {
391
+ display: grid;
392
+ gap: 8px;
393
+ }
394
+
395
+ .form input,
396
+ .form select {
397
+ width: 100%;
398
+ padding: 14px 16px;
399
+ color: var(--ink);
400
+ border: 1px solid var(--line);
401
+ border-radius: 18px;
402
+ background: rgba(255, 255, 255, 0.04);
403
+ }
404
+
405
+ .inline-status {
406
+ margin-top: 14px;
407
+ padding: 14px 16px;
408
+ border-radius: 16px;
409
+ background: rgba(255, 255, 255, 0.04);
410
+ font-size: 0.9rem;
411
+ }
412
+
413
+ .file-item code {
414
+ font-family: "Cascadia Code", "Consolas", monospace;
415
+ font-size: 0.86rem;
416
+ word-break: break-all;
417
+ }
418
+
419
+ .file-item button,
420
+ .provider-card button {
421
+ align-self: flex-start;
422
+ padding: 10px 14px;
423
+ }
424
+
425
+ .empty {
426
+ padding: 16px;
427
+ font-size: 0.95rem;
428
+ }
429
+
430
+ .toast {
431
+ position: fixed;
432
+ right: 24px;
433
+ bottom: 24px;
434
+ min-width: 240px;
435
+ max-width: 440px;
436
+ padding: 14px 16px;
437
+ border-radius: 16px;
438
+ color: #eef6f4;
439
+ background: rgba(18, 37, 49, 0.94);
440
+ box-shadow: 0 16px 40px rgba(18, 37, 49, 0.24);
441
+ opacity: 0;
442
+ transform: translateY(8px);
443
+ pointer-events: none;
444
+ transition: opacity 0.18s ease, transform 0.18s ease;
445
+ font-size: 0.92rem;
446
+ }
447
+
448
+ .toast.visible {
449
+ opacity: 1;
450
+ transform: translateY(0);
451
+ }
452
+
453
+ @media (max-width: 1220px) {
454
+ .shell {
455
+ grid-template-columns: 1fr;
456
+ }
457
+
458
+ .platforms {
459
+ grid-template-columns: repeat(3, minmax(0, 1fr));
460
+ }
461
+
462
+ .stats,
463
+ .grid {
464
+ grid-template-columns: repeat(2, minmax(0, 1fr));
465
+ }
466
+ }
467
+
468
+ @media (max-width: 860px) {
469
+ .workspace {
470
+ padding: 20px;
471
+ }
472
+
473
+ .hero,
474
+ .panel-head,
475
+ .hero-actions,
476
+ .form-actions {
477
+ flex-direction: column;
478
+ align-items: stretch;
479
+ }
480
+
481
+ .stats,
482
+ .grid,
483
+ .platforms {
484
+ grid-template-columns: 1fr;
485
+ }
486
+
487
+ .hero h1 {
488
+ font-size: 2.3rem;
489
+ }
490
+ }
package/package.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "name": "ccnew",
3
+ "version": "0.1.10",
4
+ "description": "ccon relay manager for Codex, OpenCode, and OpenClaw.",
5
+ "type": "module",
6
+ "main": "desktop/main.js",
7
+ "bin": {
8
+ "ccon": "core/index.js"
9
+ },
10
+ "files": [
11
+ "core",
12
+ "desktop",
13
+ "scripts",
14
+ "site",
15
+ "src",
16
+ "src-tauri",
17
+ "build",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "dev": "vite --configLoader native",
22
+ "build:web": "node ./scripts/build-web.mjs",
23
+ "preview": "vite preview",
24
+ "typecheck": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc --noEmit",
25
+ "start": "node ./core/index.js",
26
+ "check": "node --check ./core/index.js",
27
+ "verify:publish": "node ./scripts/prepublish-check.mjs",
28
+ "desktop:dev": "node ./scripts/desktop-dev.mjs",
29
+ "desktop:check": "node --check ./desktop/main.js && node --check ./desktop/preload.cjs && node --check ./desktop/renderer/app.js && node --check ./core/desktop-service.js",
30
+ "desktop:install-runtime": "node ./node_modules/electron/install.js",
31
+ "desktop:pack": "npm run build:web && node ./scripts/desktop-pack-win.mjs",
32
+ "desktop:pack:builder": "npm run build:web && electron-builder --win nsis portable",
33
+ "site:dev": "node ./scripts/serve-site.mjs",
34
+ "site:check": "node --check ./scripts/serve-site.mjs",
35
+ "postinstall": "node ./scripts/postinstall.mjs",
36
+ "prepublishOnly": "npm run verify:publish",
37
+ "pack:dry": "npm pack"
38
+ },
39
+ "publishConfig": {
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "keywords": [
43
+ "codex",
44
+ "openclaw",
45
+ "opencode",
46
+ "relay",
47
+ "config",
48
+ "cli"
49
+ ],
50
+ "author": "ccon",
51
+ "license": "MIT",
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "dependencies": {
56
+ "@iarna/toml": "^2.2.5",
57
+ "@tanstack/react-query": "^5.95.2",
58
+ "chalk": "^5.4.1",
59
+ "clsx": "^2.1.1",
60
+ "commander": "^12.1.0",
61
+ "inquirer": "^12.6.3",
62
+ "lucide-react": "^1.7.0",
63
+ "react": "^19.2.4",
64
+ "react-dom": "^19.2.4"
65
+ },
66
+ "devDependencies": {
67
+ "@tailwindcss/postcss": "^4.1.15",
68
+ "@types/react": "^19.2.2",
69
+ "@types/react-dom": "^19.2.2",
70
+ "@vitejs/plugin-react": "^5.1.0",
71
+ "autoprefixer": "^10.4.21",
72
+ "electron": "^41.1.0",
73
+ "electron-builder": "^26.8.1",
74
+ "postcss": "^8.5.6",
75
+ "tailwindcss": "^4.1.15",
76
+ "typescript": "^5.9.3",
77
+ "vite": "^7.1.12"
78
+ },
79
+ "build": {
80
+ "appId": "cn.ccon.desktop",
81
+ "productName": "ccon",
82
+ "executableName": "ccon",
83
+ "npmRebuild": false,
84
+ "directories": {
85
+ "output": "release",
86
+ "buildResources": "build"
87
+ },
88
+ "files": [
89
+ "desktop/**/*",
90
+ "dist/**/*",
91
+ "src/**/*",
92
+ "site/**/*",
93
+ "package.json",
94
+ "README.md",
95
+ "core/**/*"
96
+ ],
97
+ "win": {
98
+ "icon": "build/icon.ico",
99
+ "target": [
100
+ "nsis",
101
+ "portable"
102
+ ],
103
+ "artifactName": "ccon-${version}-${arch}.${ext}"
104
+ },
105
+ "nsis": {
106
+ "oneClick": false,
107
+ "allowToChangeInstallationDirectory": true,
108
+ "perMachine": false
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,95 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import * as esbuild from "esbuild";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const rootDir = path.resolve(__dirname, "..");
9
+ const distDir = path.join(rootDir, "dist");
10
+ const distAssetsDir = path.join(distDir, "assets");
11
+
12
+ async function buildCss() {
13
+ const css = `:root {
14
+ color: #0f172a;
15
+ background:
16
+ radial-gradient(circle at top left, rgba(37, 99, 235, 0.05), transparent 18%),
17
+ linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
18
+ font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ min-width: 320px;
28
+ min-height: 100vh;
29
+ background: transparent;
30
+ -webkit-font-smoothing: antialiased;
31
+ -moz-osx-font-smoothing: grayscale;
32
+ }
33
+
34
+ #root {
35
+ min-height: 100vh;
36
+ }
37
+ `;
38
+
39
+ await fs.writeFile(path.join(distAssetsDir, "index.css"), css, "utf8");
40
+ }
41
+
42
+ async function buildJs() {
43
+ await esbuild.build({
44
+ entryPoints: [path.join(rootDir, "src", "main.tsx")],
45
+ bundle: true,
46
+ format: "esm",
47
+ outfile: path.join(distAssetsDir, "index.js"),
48
+ jsx: "automatic",
49
+ target: ["es2020"],
50
+ sourcemap: false,
51
+ loader: {
52
+ ".css": "empty",
53
+ ".svg": "file",
54
+ ".png": "file"
55
+ },
56
+ define: {
57
+ "import.meta.env.BASE_URL": '"./"'
58
+ }
59
+ });
60
+ }
61
+
62
+ async function writeHtml() {
63
+ const template = await fs.readFile(path.join(rootDir, "index.html"), "utf8");
64
+ const html = template
65
+ .replace(
66
+ "</head>",
67
+ ' <link rel="stylesheet" href="./assets/index.css" />\n <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>\n </head>'
68
+ )
69
+ .replace('<script type="module" src="/src/main.tsx"></script>', '<script type="module" src="./assets/index.js"></script>');
70
+
71
+ await fs.writeFile(path.join(distDir, "index.html"), html, "utf8");
72
+ }
73
+
74
+ async function copyPublicAssets() {
75
+ const publicAssetsDir = path.join(rootDir, "public", "assets");
76
+ await fs.cp(publicAssetsDir, distAssetsDir, {
77
+ recursive: true,
78
+ force: true
79
+ });
80
+ }
81
+
82
+ async function main() {
83
+ await fs.rm(distDir, { recursive: true, force: true });
84
+ await fs.mkdir(distAssetsDir, { recursive: true });
85
+
86
+ await Promise.all([buildCss(), buildJs(), copyPublicAssets()]);
87
+ await writeHtml();
88
+
89
+ console.log(`Built web assets to ${distDir}`);
90
+ }
91
+
92
+ main().catch((error) => {
93
+ console.error(error);
94
+ process.exitCode = 1;
95
+ });