@close-by/clay 0.2.1 → 0.2.3
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/package.json +6 -4
- package/src/index.ts +102 -1
- package/src/tailwind.ts +238 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@close-by/clay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "NiceGUI-like ui.* facade for Clay apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@close-by/clay-core": "0.2.
|
|
29
|
-
"@close-by/clay-components": "0.2.
|
|
30
|
-
"@close-by/clay-server": "0.2.
|
|
28
|
+
"@close-by/clay-core": "0.2.3",
|
|
29
|
+
"@close-by/clay-components": "0.2.3",
|
|
30
|
+
"@close-by/clay-server": "0.2.3",
|
|
31
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
32
|
+
"tailwindcss": "^4.3.3"
|
|
31
33
|
}
|
|
32
34
|
}
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
command as commandFactory,
|
|
53
53
|
resizable as resizableFactory,
|
|
54
54
|
scrollArea as scrollAreaFactory,
|
|
55
|
+
viewportEnter as viewportEnterFactory,
|
|
55
56
|
keybind as keybindFactory,
|
|
56
57
|
kbd as kbdFactory,
|
|
57
58
|
sheet as sheetFactory,
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
markdown as markdownFactory,
|
|
67
68
|
html as htmlFactory,
|
|
68
69
|
image as imageFactory,
|
|
70
|
+
iframe as iframeFactory,
|
|
69
71
|
upload as uploadFactory,
|
|
70
72
|
rating as ratingFactory,
|
|
71
73
|
colorPicker as colorPickerFactory,
|
|
@@ -175,6 +177,7 @@ import {
|
|
|
175
177
|
type ResizablePanelProps,
|
|
176
178
|
type ResizableHandleProps,
|
|
177
179
|
type ScrollAreaProps,
|
|
180
|
+
type ViewportEnterProps,
|
|
178
181
|
type KeybindProps,
|
|
179
182
|
type KbdProps,
|
|
180
183
|
type BreadcrumbItem,
|
|
@@ -200,6 +203,8 @@ import {
|
|
|
200
203
|
type MarkdownProps,
|
|
201
204
|
type HtmlProps,
|
|
202
205
|
type ImageProps,
|
|
206
|
+
type IframeProps,
|
|
207
|
+
type ViewportEnterProps,
|
|
203
208
|
type UploadProps,
|
|
204
209
|
type UploadVariant,
|
|
205
210
|
type UploadedFile,
|
|
@@ -383,8 +388,23 @@ import {
|
|
|
383
388
|
type NavFromPagesOptions,
|
|
384
389
|
} from './pages';
|
|
385
390
|
import { resolveClayClientDir } from './client-dir';
|
|
391
|
+
import {
|
|
392
|
+
detectClayTailwindContent,
|
|
393
|
+
ensureClayTailwindCss,
|
|
394
|
+
mergeClayCss,
|
|
395
|
+
type ClayTailwindOptions,
|
|
396
|
+
} from './tailwind';
|
|
397
|
+
|
|
386
398
|
|
|
387
399
|
export { resolveClayClientDir } from './client-dir';
|
|
400
|
+
export {
|
|
401
|
+
detectClayTailwindContent,
|
|
402
|
+
ensureClayTailwindCss,
|
|
403
|
+
mergeClayCss,
|
|
404
|
+
type ClayTailwindOptions,
|
|
405
|
+
type ClayTailwindResult,
|
|
406
|
+
} from './tailwind';
|
|
407
|
+
|
|
388
408
|
|
|
389
409
|
export type {
|
|
390
410
|
ButtonProps,
|
|
@@ -459,6 +479,8 @@ export type {
|
|
|
459
479
|
MarkdownProps,
|
|
460
480
|
HtmlProps,
|
|
461
481
|
ImageProps,
|
|
482
|
+
IframeProps,
|
|
483
|
+
ViewportEnterProps,
|
|
462
484
|
UploadProps,
|
|
463
485
|
UploadVariant,
|
|
464
486
|
UploadedFile,
|
|
@@ -759,6 +781,11 @@ export function image(src: string, props?: ImageProps): Element {
|
|
|
759
781
|
return imageFactory(src, props);
|
|
760
782
|
}
|
|
761
783
|
|
|
784
|
+
/** Iframe embed. Prefer `ui.iframe`. See {@link IframeProps}. */
|
|
785
|
+
export function iframe(src: string, props?: IframeProps): Element {
|
|
786
|
+
return iframeFactory(src, props);
|
|
787
|
+
}
|
|
788
|
+
|
|
762
789
|
/** File upload control (`POST /upload`). Prefer `ui.upload`. See {@link UploadProps}. */
|
|
763
790
|
export function upload(props?: UploadProps): Element {
|
|
764
791
|
return uploadFactory(props);
|
|
@@ -1100,6 +1127,23 @@ export function scrollArea(a: any, b?: any): Element {
|
|
|
1100
1127
|
return scrollAreaFactory(a, b);
|
|
1101
1128
|
}
|
|
1102
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* Observe when children enter the viewport (IntersectionObserver).
|
|
1132
|
+
* Prefer `ui.viewportEnter`. See {@link ViewportEnterProps}.
|
|
1133
|
+
*
|
|
1134
|
+
* @example
|
|
1135
|
+
* ```ts
|
|
1136
|
+
* ui.viewportEnter({ onEnter: () => void loadMore(), once: true }, () => {
|
|
1137
|
+
* ui.label('Lazy section');
|
|
1138
|
+
* });
|
|
1139
|
+
* ```
|
|
1140
|
+
*/
|
|
1141
|
+
export function viewportEnter(fn: () => void, props?: ViewportEnterProps): Element;
|
|
1142
|
+
export function viewportEnter(props: ViewportEnterProps, fn: () => void): Element;
|
|
1143
|
+
export function viewportEnter(a: any, b?: any): Element {
|
|
1144
|
+
return viewportEnterFactory(a, b);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1103
1147
|
/**
|
|
1104
1148
|
* Headless keyboard chord listener. Prefer `ui.keybind`. See {@link KeybindProps}.
|
|
1105
1149
|
*
|
|
@@ -1333,8 +1377,61 @@ export function page(path: string, fn: () => void, options?: PageOptions): void
|
|
|
1333
1377
|
export type RunConfig = ClayServerConfig & {
|
|
1334
1378
|
/** Global dashboard shell; wraps every page unless `ui.page(..., { shell: false })`. */
|
|
1335
1379
|
app?: AppProps;
|
|
1380
|
+
/**
|
|
1381
|
+
* Auto-build Tailwind utilities from app modules and inject after the client CSS.
|
|
1382
|
+
*
|
|
1383
|
+
* - `undefined` — on when `pages/`, `src/`, or `app/` exists under cwd
|
|
1384
|
+
* - `true` — same auto-detect
|
|
1385
|
+
* - `false` — off
|
|
1386
|
+
* - object — custom `content` paths, `watch`, `appendCss`, …
|
|
1387
|
+
*
|
|
1388
|
+
* Scans `className` / `.classes(...)` strings; no app-level Tailwind config required.
|
|
1389
|
+
*/
|
|
1390
|
+
tailwind?: boolean | (Omit<ClayTailwindOptions, 'content'> & { content?: string[] });
|
|
1336
1391
|
};
|
|
1337
1392
|
|
|
1393
|
+
const pathDelimiter = process.platform === 'win32' ? ';' : ':';
|
|
1394
|
+
|
|
1395
|
+
function applyClayTailwind(cfg: RunConfig): RunConfig {
|
|
1396
|
+
if (cfg.tailwind === false || process.env.CLAY_NO_TAILWIND === '1') {
|
|
1397
|
+
return { ...cfg, tailwind: false };
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
const tw = cfg.tailwind;
|
|
1401
|
+
const cwd = process.cwd();
|
|
1402
|
+
const envContent = process.env.CLAY_TAILWIND_CONTENT?.split(pathDelimiter).filter(Boolean) ?? [];
|
|
1403
|
+
|
|
1404
|
+
const opts =
|
|
1405
|
+
tw === undefined || tw === true
|
|
1406
|
+
? {
|
|
1407
|
+
content: envContent.length ? envContent : detectClayTailwindContent(cwd),
|
|
1408
|
+
watch: process.env.CLAY_TAILWIND_WATCH === '1',
|
|
1409
|
+
}
|
|
1410
|
+
: {
|
|
1411
|
+
content: tw.content?.length
|
|
1412
|
+
? tw.content
|
|
1413
|
+
: envContent.length
|
|
1414
|
+
? envContent
|
|
1415
|
+
: detectClayTailwindContent(cwd),
|
|
1416
|
+
watch: tw.watch ?? process.env.CLAY_TAILWIND_WATCH === '1',
|
|
1417
|
+
appendCss: tw.appendCss,
|
|
1418
|
+
outFile: tw.outFile,
|
|
1419
|
+
cwd: tw.cwd ?? cwd,
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
if (!opts.content.length) return cfg;
|
|
1423
|
+
|
|
1424
|
+
const { cssPath } = ensureClayTailwindCss({
|
|
1425
|
+
content: opts.content,
|
|
1426
|
+
cwd: opts.cwd ?? cwd,
|
|
1427
|
+
watch: opts.watch,
|
|
1428
|
+
appendCss: opts.appendCss,
|
|
1429
|
+
outFile: opts.outFile,
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
return { ...cfg, css: mergeClayCss(cfg.css, cssPath) };
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1338
1435
|
let runCalled = false;
|
|
1339
1436
|
|
|
1340
1437
|
/** Whether `ui.run` has been invoked in this process (used by the `clay` CLI). */
|
|
@@ -1374,7 +1471,9 @@ export function run(rootOrConfig: PageFn | RunConfig = {}, config: RunConfig = {
|
|
|
1374
1471
|
cfg = { ...cfg, clientDir: resolveClayClientDir() };
|
|
1375
1472
|
}
|
|
1376
1473
|
|
|
1377
|
-
|
|
1474
|
+
cfg = applyClayTailwind(cfg);
|
|
1475
|
+
|
|
1476
|
+
const { app: appProps, tailwind: _tailwind, ...serverConfig } = cfg;
|
|
1378
1477
|
if (appProps) {
|
|
1379
1478
|
setPageWrapper((pageFn) => app(appProps, pageFn));
|
|
1380
1479
|
} else {
|
|
@@ -1415,6 +1514,7 @@ export const ui = {
|
|
|
1415
1514
|
markdown,
|
|
1416
1515
|
html,
|
|
1417
1516
|
image,
|
|
1517
|
+
iframe,
|
|
1418
1518
|
upload,
|
|
1419
1519
|
rating,
|
|
1420
1520
|
colorPicker,
|
|
@@ -1463,6 +1563,7 @@ export const ui = {
|
|
|
1463
1563
|
command,
|
|
1464
1564
|
resizable,
|
|
1465
1565
|
scrollArea,
|
|
1566
|
+
viewportEnter,
|
|
1466
1567
|
keybind,
|
|
1467
1568
|
kbd,
|
|
1468
1569
|
breadcrumb,
|
package/src/tailwind.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Tailwind for Clay apps: scan page modules, emit utilities CSS,
|
|
3
|
+
* inject via `ui.run({ css })` / CLI.
|
|
4
|
+
*
|
|
5
|
+
* Uses `@tailwindcss/cli` (utilities + theme only — client already ships base/preflight).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { dirname, isAbsolute, join, resolve } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
export type ClayTailwindOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* Directories/files to scan for `className` / `.classes(...)` strings.
|
|
15
|
+
* Absolute or cwd-relative. Prefer app dirs — never the repo root with `node_modules`.
|
|
16
|
+
*/
|
|
17
|
+
content: string[];
|
|
18
|
+
/** Working directory for relative paths. Default `process.cwd()`. */
|
|
19
|
+
cwd?: string;
|
|
20
|
+
/** Output CSS path. Default `<cwd>/.clay/tailwind.css`. */
|
|
21
|
+
outFile?: string;
|
|
22
|
+
/** Keep `@tailwindcss/cli --watch` running after the first build. */
|
|
23
|
+
watch?: boolean;
|
|
24
|
+
/** Extra CSS appended after the generated input (token overrides, keyframes). */
|
|
25
|
+
appendCss?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ClayTailwindResult = {
|
|
29
|
+
cssPath: string;
|
|
30
|
+
/** Stop the background watch process (if any). */
|
|
31
|
+
stop: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const THEME_SNIPPET = `
|
|
35
|
+
/* Token utilities for app-scanned classes (vars come from the Clay client). */
|
|
36
|
+
@theme inline {
|
|
37
|
+
--color-background: var(--background);
|
|
38
|
+
--color-foreground: var(--foreground);
|
|
39
|
+
--color-card: var(--card);
|
|
40
|
+
--color-card-foreground: var(--card-foreground);
|
|
41
|
+
--color-popover: var(--popover);
|
|
42
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
43
|
+
--color-primary: var(--primary);
|
|
44
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
45
|
+
--color-secondary: var(--secondary);
|
|
46
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
47
|
+
--color-muted: var(--muted);
|
|
48
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
49
|
+
--color-accent: var(--accent);
|
|
50
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
51
|
+
--color-destructive: var(--destructive);
|
|
52
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
53
|
+
--color-border: var(--border);
|
|
54
|
+
--color-input: var(--input);
|
|
55
|
+
--color-ring: var(--ring);
|
|
56
|
+
--color-chart-1: var(--chart-1);
|
|
57
|
+
--color-chart-2: var(--chart-2);
|
|
58
|
+
--color-chart-3: var(--chart-3);
|
|
59
|
+
--color-chart-4: var(--chart-4);
|
|
60
|
+
--color-chart-5: var(--chart-5);
|
|
61
|
+
--color-sidebar: var(--sidebar);
|
|
62
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
63
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
64
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
65
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
66
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
67
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
68
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
69
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
70
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
71
|
+
--radius-lg: var(--radius);
|
|
72
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
73
|
+
}
|
|
74
|
+
`.trim();
|
|
75
|
+
|
|
76
|
+
function resolveContentPaths(content: string[], cwd: string): string[] {
|
|
77
|
+
const out: string[] = [];
|
|
78
|
+
for (const raw of content) {
|
|
79
|
+
const p = isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
80
|
+
if (existsSync(p)) out.push(p);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Guess scan roots under `cwd` (`pages/`, `src/`, `app/`). */
|
|
86
|
+
export function detectClayTailwindContent(cwd = process.cwd()): string[] {
|
|
87
|
+
const candidates = ['pages', 'src', 'app'];
|
|
88
|
+
return candidates.map((d) => join(cwd, d)).filter((p) => existsSync(p));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveTailwindcssRoot(): string {
|
|
92
|
+
const require = createRequire(import.meta.url);
|
|
93
|
+
const tryPkg = (req: NodeJS.Require): string | null => {
|
|
94
|
+
try {
|
|
95
|
+
return dirname(req.resolve('tailwindcss/package.json'));
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const fromUi = tryPkg(require);
|
|
101
|
+
if (fromUi) return fromUi;
|
|
102
|
+
try {
|
|
103
|
+
const rootReq = createRequire(
|
|
104
|
+
join(fileURLToPath(new URL('.', import.meta.url)), '../../../package.json'),
|
|
105
|
+
);
|
|
106
|
+
const fromRoot = tryPkg(rootReq);
|
|
107
|
+
if (fromRoot) return fromRoot;
|
|
108
|
+
} catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
throw new Error(
|
|
112
|
+
'tailwindcss package not found. Install with: bun add tailwindcss @tailwindcss/cli',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function writeInputCss(inputFile: string, sources: string[], appendCss?: string): void {
|
|
117
|
+
mkdirSync(dirname(inputFile), { recursive: true });
|
|
118
|
+
const twRoot = resolveTailwindcssRoot();
|
|
119
|
+
// Absolute CSS imports so builds work even when `.clay/` is outside node_modules.
|
|
120
|
+
const themeCss = join(twRoot, 'theme.css');
|
|
121
|
+
const utilitiesCss = join(twRoot, 'utilities.css');
|
|
122
|
+
const sourceLines = sources.map((s) => `@source ${JSON.stringify(s)};`).join('\n');
|
|
123
|
+
const body = [
|
|
124
|
+
'/* Generated by @close-by/clay — do not edit */',
|
|
125
|
+
`@import ${JSON.stringify(themeCss)} layer(theme);`,
|
|
126
|
+
`@import ${JSON.stringify(utilitiesCss)} layer(utilities);`,
|
|
127
|
+
'',
|
|
128
|
+
sourceLines,
|
|
129
|
+
'',
|
|
130
|
+
THEME_SNIPPET,
|
|
131
|
+
appendCss ? `\n${appendCss.trim()}\n` : '',
|
|
132
|
+
].join('\n');
|
|
133
|
+
writeFileSync(inputFile, body, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveTailwindCliEntry(): string {
|
|
137
|
+
const require = createRequire(import.meta.url);
|
|
138
|
+
const tryPkg = (req: NodeJS.Require): string | null => {
|
|
139
|
+
try {
|
|
140
|
+
const pkgJsonPath = req.resolve('@tailwindcss/cli/package.json');
|
|
141
|
+
return join(dirname(pkgJsonPath), 'dist/index.mjs');
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const fromUi = tryPkg(require);
|
|
148
|
+
if (fromUi && existsSync(fromUi)) return fromUi;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const rootReq = createRequire(
|
|
152
|
+
join(fileURLToPath(new URL('.', import.meta.url)), '../../../package.json'),
|
|
153
|
+
);
|
|
154
|
+
const fromRoot = tryPkg(rootReq);
|
|
155
|
+
if (fromRoot && existsSync(fromRoot)) return fromRoot;
|
|
156
|
+
} catch {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw new Error(
|
|
161
|
+
'Tailwind CLI not found. Install with: bun add tailwindcss @tailwindcss/cli\n' +
|
|
162
|
+
'(shipped with @close-by/clay — ensure the package is installed).',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build (and optionally watch) Tailwind CSS for Clay page modules.
|
|
168
|
+
* Returns a stylesheet path suitable for `ui.run({ css })`.
|
|
169
|
+
*/
|
|
170
|
+
export function ensureClayTailwindCss(options: ClayTailwindOptions): ClayTailwindResult {
|
|
171
|
+
const cwd = options.cwd ?? process.cwd();
|
|
172
|
+
const sources = resolveContentPaths(options.content, cwd);
|
|
173
|
+
if (sources.length === 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
'Clay Tailwind: no content paths found. Pass content: ["./pages"] or create a pages/ directory.',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const outFile = options.outFile
|
|
180
|
+
? isAbsolute(options.outFile)
|
|
181
|
+
? options.outFile
|
|
182
|
+
: resolve(cwd, options.outFile)
|
|
183
|
+
: join(cwd, '.clay', 'tailwind.css');
|
|
184
|
+
const inputFile = join(dirname(outFile), 'tailwind.input.css');
|
|
185
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
186
|
+
writeInputCss(inputFile, sources, options.appendCss);
|
|
187
|
+
|
|
188
|
+
const cliEntry = resolveTailwindCliEntry();
|
|
189
|
+
const buildArgs = [cliEntry, '-i', inputFile, '-o', outFile];
|
|
190
|
+
|
|
191
|
+
const built = Bun.spawnSync({
|
|
192
|
+
cmd: [process.execPath, ...buildArgs],
|
|
193
|
+
cwd,
|
|
194
|
+
stdout: 'pipe',
|
|
195
|
+
stderr: 'pipe',
|
|
196
|
+
});
|
|
197
|
+
if (built.exitCode !== 0) {
|
|
198
|
+
const err = built.stderr.toString() || built.stdout.toString() || `exit ${built.exitCode}`;
|
|
199
|
+
throw new Error(`Clay Tailwind build failed:\n${err}`);
|
|
200
|
+
}
|
|
201
|
+
if (!existsSync(outFile)) {
|
|
202
|
+
throw new Error(`Clay Tailwind build produced no file at ${outFile}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let child: ReturnType<typeof Bun.spawn> | null = null;
|
|
206
|
+
if (options.watch) {
|
|
207
|
+
child = Bun.spawn({
|
|
208
|
+
cmd: [process.execPath, ...buildArgs, '--watch'],
|
|
209
|
+
cwd,
|
|
210
|
+
stdout: 'ignore',
|
|
211
|
+
stderr: 'inherit',
|
|
212
|
+
stdin: 'ignore',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
cssPath: outFile,
|
|
218
|
+
stop: () => {
|
|
219
|
+
if (!child) return;
|
|
220
|
+
try {
|
|
221
|
+
child.kill();
|
|
222
|
+
} catch {
|
|
223
|
+
// already exited
|
|
224
|
+
}
|
|
225
|
+
child = null;
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Merge a Tailwind stylesheet into existing `css` config (append). */
|
|
231
|
+
export function mergeClayCss(
|
|
232
|
+
existing: string | string[] | undefined,
|
|
233
|
+
extra: string | string[],
|
|
234
|
+
): string[] {
|
|
235
|
+
const a = existing == null ? [] : Array.isArray(existing) ? existing : [existing];
|
|
236
|
+
const b = Array.isArray(extra) ? extra : [extra];
|
|
237
|
+
return [...a, ...b].map((p) => p.trim()).filter(Boolean);
|
|
238
|
+
}
|