@kuratchi/js 0.0.21 → 0.0.22
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 +65 -29
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +90 -39
- package/dist/compiler/compiler-shared.d.ts +1 -0
- package/dist/compiler/config-reading.d.ts +6 -0
- package/dist/compiler/config-reading.js +92 -16
- package/dist/compiler/desktop-manifest.d.ts +48 -0
- package/dist/compiler/desktop-manifest.js +175 -0
- package/dist/compiler/durable-object-pipeline.d.ts +7 -3
- package/dist/compiler/durable-object-pipeline.js +30 -34
- package/dist/compiler/index.js +2 -3
- package/dist/compiler/parser.js +6 -0
- package/dist/compiler/routes-module-feature-blocks.js +5 -1
- package/dist/compiler/template.js +37 -3
- package/dist/compiler/wrangler-sync.js +47 -0
- package/dist/create.js +19 -19
- package/dist/index.d.ts +1 -1
- package/dist/runtime/desktop.d.ts +19 -0
- package/dist/runtime/desktop.js +82 -0
- package/dist/runtime/types.d.ts +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,12 +63,12 @@ Route files are not client files. They are server-rendered routes that can opt i
|
|
|
63
63
|
|
|
64
64
|
### Route file structure
|
|
65
65
|
|
|
66
|
-
```html
|
|
67
|
-
<script>
|
|
68
|
-
import { getItems, addItem, deleteItem } from '$
|
|
69
|
-
|
|
70
|
-
const items = await getItems();
|
|
71
|
-
</script>
|
|
66
|
+
```html
|
|
67
|
+
<script>
|
|
68
|
+
import { getItems, addItem, deleteItem } from '$server/items';
|
|
69
|
+
|
|
70
|
+
const items = await getItems();
|
|
71
|
+
</script>
|
|
72
72
|
|
|
73
73
|
<!-- Template — plain HTML with minimal extensions -->
|
|
74
74
|
<ul>
|
|
@@ -78,8 +78,8 @@ Route files are not client files. They are server-rendered routes that can opt i
|
|
|
78
78
|
</ul>
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
The `$
|
|
82
|
-
Private server logic should live in `src/server/` and be imported into routes explicitly.
|
|
81
|
+
The `$server/` alias resolves to `src/server/`. Use that as the canonical home for reusable server-only modules.
|
|
82
|
+
Private server logic should live in `src/server/` and be imported into routes explicitly.
|
|
83
83
|
|
|
84
84
|
### Layout file
|
|
85
85
|
|
|
@@ -132,6 +132,40 @@ for (const item of items) {
|
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
### Attribute expressions
|
|
136
|
+
|
|
137
|
+
Use `{expression}` in attribute values for dynamic content:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<!-- Ternary expressions -->
|
|
141
|
+
<div class={isActive ? 'active' : 'inactive'}>...</div>
|
|
142
|
+
<button class={count > 0 ? 'has-items' : ''}>View ({count})</button>
|
|
143
|
+
|
|
144
|
+
<!-- Any JS expression -->
|
|
145
|
+
<a href={`/items/${item.id}`}>{item.name}</a>
|
|
146
|
+
<img src={user.avatar} alt={user.name} />
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Boolean attributes
|
|
150
|
+
|
|
151
|
+
Boolean attributes like `disabled`, `checked`, `selected`, etc. are conditionally rendered based on the expression value:
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<!-- Renders: <button disabled> or <button> -->
|
|
155
|
+
<button disabled={isLoading}>Submit</button>
|
|
156
|
+
|
|
157
|
+
<!-- Form elements -->
|
|
158
|
+
<input type="checkbox" checked={todo.completed} />
|
|
159
|
+
<option selected={item.id === selectedId}>{item.name}</option>
|
|
160
|
+
|
|
161
|
+
<!-- Other boolean attributes -->
|
|
162
|
+
<details open={showDetails}>...</details>
|
|
163
|
+
<input readonly={!canEdit} />
|
|
164
|
+
<input required={isRequired} />
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Supported boolean attributes: `disabled`, `checked`, `selected`, `readonly`, `required`, `hidden`, `open`, `autofocus`, `autoplay`, `controls`, `default`, `defer`, `formnovalidate`, `inert`, `loop`, `multiple`, `muted`, `novalidate`, `reversed`, `async`.
|
|
168
|
+
|
|
135
169
|
### Components
|
|
136
170
|
|
|
137
171
|
Import `.html` components from your `src/lib/` directory or from packages:
|
|
@@ -241,10 +275,10 @@ Failure and edge behavior:
|
|
|
241
275
|
|
|
242
276
|
Export server functions from a route's `<script>` block and reference them with `action={fn}`. The compiler automatically registers them as dispatchable actions.
|
|
243
277
|
|
|
244
|
-
```html
|
|
245
|
-
<script>
|
|
246
|
-
import { addItem, deleteItem } from '$
|
|
247
|
-
</script>
|
|
278
|
+
```html
|
|
279
|
+
<script>
|
|
280
|
+
import { addItem, deleteItem } from '$server/items';
|
|
281
|
+
</script>
|
|
248
282
|
|
|
249
283
|
<!-- Standard form — POST-Redirect-GET -->
|
|
250
284
|
<form action={addItem} method="POST">
|
|
@@ -256,8 +290,8 @@ Export server functions from a route's `<script>` block and reference them with
|
|
|
256
290
|
The action function receives the raw `FormData`. Throw `ActionError` to surface a message back to the form — see [Error handling](#error-handling).
|
|
257
291
|
|
|
258
292
|
```ts
|
|
259
|
-
// src/
|
|
260
|
-
import { ActionError } from '@kuratchi/js';
|
|
293
|
+
// src/server/items.ts
|
|
294
|
+
import { ActionError } from '@kuratchi/js';
|
|
261
295
|
|
|
262
296
|
export async function addItem(formData: FormData): Promise<void> {
|
|
263
297
|
const title = (formData.get('title') as string)?.trim();
|
|
@@ -303,10 +337,10 @@ export async function signIn(formData: FormData) {
|
|
|
303
337
|
|
|
304
338
|
In the template, the action's state object is available under its function name:
|
|
305
339
|
|
|
306
|
-
```html
|
|
307
|
-
<script>
|
|
308
|
-
import { signIn } from '$
|
|
309
|
-
</script>
|
|
340
|
+
```html
|
|
341
|
+
<script>
|
|
342
|
+
import { signIn } from '$server/auth';
|
|
343
|
+
</script>
|
|
310
344
|
|
|
311
345
|
<form action={signIn}>
|
|
312
346
|
(signIn.error ? `<p class="error">${signIn.error}</p>` : '')
|
|
@@ -364,16 +398,18 @@ for (const rec of (recommendations ?? [])) {
|
|
|
364
398
|
|
|
365
399
|
These `data-*` attributes wire up client-side interactivity without writing JavaScript.
|
|
366
400
|
|
|
367
|
-
### `data-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
```html
|
|
372
|
-
<button data-
|
|
373
|
-
<button data-
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
|
|
401
|
+
### `data-post` — client-triggered server action
|
|
402
|
+
|
|
403
|
+
Use `data-post={fn(args)}` for button-style server actions without exposing compiler transport details in templates:
|
|
404
|
+
|
|
405
|
+
```html
|
|
406
|
+
<button data-post={deleteItem(item.id)} data-refresh="" type="button">Delete</button>
|
|
407
|
+
<button data-post={toggleItem(item.id, true)} data-refresh="" type="button">Done</button>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Kuratchi lowers `data-post` into its internal action transport automatically. Authored `data-action` / `data-args` attributes are deprecated and should be treated as compiler output only.
|
|
411
|
+
|
|
412
|
+
The action function receives the direct arguments you passed in the template:
|
|
377
413
|
|
|
378
414
|
```ts
|
|
379
415
|
export async function deleteItem(id: number): Promise<void> {
|
|
@@ -387,7 +423,7 @@ export async function toggleItem(id: number, done: boolean): Promise<void> {
|
|
|
387
423
|
|
|
388
424
|
### `data-refresh` — partial refresh
|
|
389
425
|
|
|
390
|
-
After a `data-
|
|
426
|
+
After a `data-post` call succeeds, elements with `data-refresh` re-fetch their content:
|
|
391
427
|
|
|
392
428
|
```html
|
|
393
429
|
<section data-refresh="/items">
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* CLI entry point
|
|
3
|
+
* CLI entry point � kuratchi build | watch | create
|
|
4
4
|
*/
|
|
5
5
|
import { compile } from './compiler/index.js';
|
|
6
6
|
import * as path from 'node:path';
|
|
@@ -11,6 +11,7 @@ import { spawn } from 'node:child_process';
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
12
|
const command = args[0];
|
|
13
13
|
const projectDir = process.cwd();
|
|
14
|
+
let cachedScriptRuntimeExecutable = null;
|
|
14
15
|
void main().catch((err) => {
|
|
15
16
|
console.error(`[kuratchi] ${err?.message ?? err}`);
|
|
16
17
|
process.exit(1);
|
|
@@ -33,15 +34,15 @@ async function main() {
|
|
|
33
34
|
await runTypes();
|
|
34
35
|
return;
|
|
35
36
|
default:
|
|
36
|
-
console.log(`
|
|
37
|
-
KuratchiJS CLI
|
|
38
|
-
|
|
39
|
-
Usage:
|
|
40
|
-
kuratchi create [name] Scaffold a new KuratchiJS project
|
|
41
|
-
kuratchi build Compile routes once
|
|
42
|
-
kuratchi dev Compile, watch for changes, and start wrangler dev server
|
|
43
|
-
kuratchi watch Compile + watch only (no wrangler
|
|
44
|
-
kuratchi types Generate TypeScript types from schema to src/app.d.ts
|
|
37
|
+
console.log(`
|
|
38
|
+
KuratchiJS CLI
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
kuratchi create [name] Scaffold a new KuratchiJS project
|
|
42
|
+
kuratchi build Compile routes once
|
|
43
|
+
kuratchi dev Compile, watch for changes, and start wrangler dev server
|
|
44
|
+
kuratchi watch Compile + watch only (no wrangler � for custom setups)
|
|
45
|
+
kuratchi types Generate TypeScript types from schema to src/app.d.ts
|
|
45
46
|
`);
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
@@ -61,7 +62,7 @@ async function runBuild(isDev = false) {
|
|
|
61
62
|
console.log('[kuratchi] Compiling...');
|
|
62
63
|
try {
|
|
63
64
|
const outFile = await compile({ projectDir, isDev });
|
|
64
|
-
console.log(`[kuratchi] Built
|
|
65
|
+
console.log(`[kuratchi] Built -> ${path.relative(projectDir, outFile)}`);
|
|
65
66
|
}
|
|
66
67
|
catch (err) {
|
|
67
68
|
console.error(`[kuratchi] Build failed: ${err.message}`);
|
|
@@ -70,30 +71,8 @@ async function runBuild(isDev = false) {
|
|
|
70
71
|
}
|
|
71
72
|
async function runWatch(withWrangler = false) {
|
|
72
73
|
await runBuild(true);
|
|
73
|
-
|
|
74
|
-
const serverDir = path.join(projectDir, 'src', 'server');
|
|
75
|
-
const watchDirs = [routesDir, serverDir].filter(d => fs.existsSync(d));
|
|
76
|
-
let rebuildTimeout = null;
|
|
77
|
-
const triggerRebuild = () => {
|
|
78
|
-
if (rebuildTimeout)
|
|
79
|
-
clearTimeout(rebuildTimeout);
|
|
80
|
-
rebuildTimeout = setTimeout(async () => {
|
|
81
|
-
console.log('[kuratchi] File changed, rebuilding...');
|
|
82
|
-
try {
|
|
83
|
-
await compile({ projectDir, isDev: true });
|
|
84
|
-
console.log('[kuratchi] Rebuilt.');
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
console.error(`[kuratchi] Rebuild failed: ${err.message}`);
|
|
88
|
-
}
|
|
89
|
-
}, 100);
|
|
90
|
-
};
|
|
91
|
-
for (const dir of watchDirs) {
|
|
92
|
-
fs.watch(dir, { recursive: true }, triggerRebuild);
|
|
93
|
-
}
|
|
74
|
+
startCompilerWatch();
|
|
94
75
|
console.log('[kuratchi] Watching for changes...');
|
|
95
|
-
// `kuratchi dev` also starts the wrangler dev server.
|
|
96
|
-
// `kuratchi watch` is the compiler-only mode for custom setups.
|
|
97
76
|
if (withWrangler) {
|
|
98
77
|
await startWranglerDev();
|
|
99
78
|
return;
|
|
@@ -171,16 +150,52 @@ function resolveWranglerBin() {
|
|
|
171
150
|
return null;
|
|
172
151
|
}
|
|
173
152
|
}
|
|
174
|
-
function
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
177
|
-
|
|
153
|
+
function getScriptRuntimeExecutable() {
|
|
154
|
+
if (cachedScriptRuntimeExecutable)
|
|
155
|
+
return cachedScriptRuntimeExecutable;
|
|
156
|
+
if (!process.versions.bun) {
|
|
157
|
+
cachedScriptRuntimeExecutable = process.execPath;
|
|
158
|
+
return cachedScriptRuntimeExecutable;
|
|
159
|
+
}
|
|
160
|
+
const nodeExecutable = findExecutableOnPath('node');
|
|
161
|
+
cachedScriptRuntimeExecutable = nodeExecutable ?? process.execPath;
|
|
162
|
+
return cachedScriptRuntimeExecutable;
|
|
163
|
+
}
|
|
164
|
+
function startCompilerWatch() {
|
|
165
|
+
const routesDir = path.join(projectDir, 'src', 'routes');
|
|
166
|
+
const serverDir = path.join(projectDir, 'src', 'server');
|
|
167
|
+
const watchDirs = [routesDir, serverDir].filter(d => fs.existsSync(d));
|
|
168
|
+
const watchers = [];
|
|
169
|
+
let rebuildTimeout = null;
|
|
170
|
+
const triggerRebuild = () => {
|
|
171
|
+
if (rebuildTimeout)
|
|
172
|
+
clearTimeout(rebuildTimeout);
|
|
173
|
+
rebuildTimeout = setTimeout(async () => {
|
|
174
|
+
console.log('[kuratchi] File changed, rebuilding...');
|
|
175
|
+
try {
|
|
176
|
+
await compile({ projectDir, isDev: true });
|
|
177
|
+
console.log('[kuratchi] Rebuilt.');
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.error(`[kuratchi] Rebuild failed: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}, 100);
|
|
183
|
+
};
|
|
184
|
+
for (const dir of watchDirs) {
|
|
185
|
+
watchers.push(fs.watch(dir, { recursive: true }, triggerRebuild));
|
|
186
|
+
}
|
|
187
|
+
return () => {
|
|
188
|
+
if (rebuildTimeout)
|
|
189
|
+
clearTimeout(rebuildTimeout);
|
|
190
|
+
for (const watcher of watchers)
|
|
191
|
+
watcher.close();
|
|
192
|
+
};
|
|
178
193
|
}
|
|
179
194
|
function spawnWranglerProcess(wranglerArgs) {
|
|
180
195
|
const localWranglerBin = resolveWranglerBin();
|
|
181
196
|
const stdio = ['pipe', 'inherit', 'inherit'];
|
|
182
197
|
if (localWranglerBin) {
|
|
183
|
-
return spawn(
|
|
198
|
+
return spawn(getScriptRuntimeExecutable(), [localWranglerBin, ...wranglerArgs], {
|
|
184
199
|
cwd: projectDir,
|
|
185
200
|
stdio,
|
|
186
201
|
});
|
|
@@ -191,3 +206,39 @@ function spawnWranglerProcess(wranglerArgs) {
|
|
|
191
206
|
stdio,
|
|
192
207
|
});
|
|
193
208
|
}
|
|
209
|
+
function findExecutableOnPath(command) {
|
|
210
|
+
const pathEnv = process.env.PATH;
|
|
211
|
+
if (!pathEnv)
|
|
212
|
+
return null;
|
|
213
|
+
const pathEntries = pathEnv.split(path.delimiter).filter(Boolean);
|
|
214
|
+
const extensions = process.platform === 'win32'
|
|
215
|
+
? (process.env.PATHEXT?.split(';').filter(Boolean) ?? ['.EXE', '.CMD', '.BAT', '.COM'])
|
|
216
|
+
: [''];
|
|
217
|
+
const hasExtension = !!path.extname(command);
|
|
218
|
+
for (const entry of pathEntries) {
|
|
219
|
+
if (hasExtension) {
|
|
220
|
+
const candidate = path.join(entry, command);
|
|
221
|
+
if (isExecutableFile(candidate))
|
|
222
|
+
return candidate;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
for (const ext of extensions) {
|
|
226
|
+
const candidate = path.join(entry, `${command}${process.platform === 'win32' ? ext.toLowerCase() : ext}`);
|
|
227
|
+
if (isExecutableFile(candidate))
|
|
228
|
+
return candidate;
|
|
229
|
+
const exactCaseCandidate = path.join(entry, `${command}${ext}`);
|
|
230
|
+
if (exactCaseCandidate !== candidate && isExecutableFile(exactCaseCandidate))
|
|
231
|
+
return exactCaseCandidate;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function isExecutableFile(filePath) {
|
|
237
|
+
try {
|
|
238
|
+
const stat = fs.statSync(filePath);
|
|
239
|
+
return stat.isFile();
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type AuthConfigEntry, type DoConfigEntry, type OrmDatabaseEntry, type SecurityConfigEntry, type WorkerClassConfigEntry } from './compiler-shared.js';
|
|
2
|
+
export interface UiConfigEntry {
|
|
3
|
+
theme: string;
|
|
4
|
+
radius: string;
|
|
5
|
+
library?: 'tailwindcss';
|
|
6
|
+
plugins: string[];
|
|
7
|
+
}
|
|
2
8
|
export declare function readUiTheme(projectDir: string): string | null;
|
|
3
9
|
export declare function readUiConfigValues(projectDir: string): {
|
|
4
10
|
theme: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
3
4
|
function skipWhitespace(source, start) {
|
|
4
5
|
let i = start;
|
|
5
6
|
while (i < source.length && /\s/.test(source[i]))
|
|
@@ -51,7 +52,15 @@ function readConfigBlock(source, key) {
|
|
|
51
52
|
}
|
|
52
53
|
return { kind: 'call-empty', body: '' };
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
function normalizeUiPluginName(plugin) {
|
|
56
|
+
const normalized = plugin.trim();
|
|
57
|
+
if (!normalized)
|
|
58
|
+
return normalized;
|
|
59
|
+
if (normalized === 'forms')
|
|
60
|
+
return '@tailwindcss/forms';
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
function readUiConfig(projectDir) {
|
|
55
64
|
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
56
65
|
if (!fs.existsSync(configPath))
|
|
57
66
|
return null;
|
|
@@ -60,7 +69,78 @@ export function readUiTheme(projectDir) {
|
|
|
60
69
|
if (!uiBlock)
|
|
61
70
|
return null;
|
|
62
71
|
const themeMatch = uiBlock.body.match(/theme\s*:\s*['"]([^'"]+)['"]/);
|
|
63
|
-
const
|
|
72
|
+
const radiusMatch = uiBlock.body.match(/radius\s*:\s*['"]([^'"]+)['"]/);
|
|
73
|
+
const libraryMatch = uiBlock.body.match(/library\s*:\s*['"]([^'"]+)['"]/);
|
|
74
|
+
const pluginsMatch = uiBlock.body.match(/plugins\s*:\s*\[([\s\S]*?)\]/);
|
|
75
|
+
const plugins = [];
|
|
76
|
+
if (pluginsMatch) {
|
|
77
|
+
const itemRegex = /['"]([^'"]+)['"]/g;
|
|
78
|
+
let pluginMatch;
|
|
79
|
+
while ((pluginMatch = itemRegex.exec(pluginsMatch[1])) !== null) {
|
|
80
|
+
const plugin = normalizeUiPluginName(pluginMatch[1]);
|
|
81
|
+
if (plugin)
|
|
82
|
+
plugins.push(plugin);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
theme: themeMatch?.[1] ?? 'dark',
|
|
87
|
+
radius: radiusMatch?.[1] ?? 'default',
|
|
88
|
+
library: libraryMatch?.[1] === 'tailwindcss' ? 'tailwindcss' : undefined,
|
|
89
|
+
plugins,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function findTailwindCliPath(projectDir) {
|
|
93
|
+
const candidates = [
|
|
94
|
+
path.join(projectDir, 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.mjs'),
|
|
95
|
+
path.join(projectDir, 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.js'),
|
|
96
|
+
path.join(projectDir, '..', 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.mjs'),
|
|
97
|
+
path.join(projectDir, '..', 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.js'),
|
|
98
|
+
path.join(projectDir, '..', '..', 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.mjs'),
|
|
99
|
+
path.join(projectDir, '..', '..', 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.js'),
|
|
100
|
+
path.join(path.resolve(projectDir, '../..'), 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.mjs'),
|
|
101
|
+
path.join(path.resolve(projectDir, '../..'), 'node_modules', '@tailwindcss', 'cli', 'dist', 'index.js'),
|
|
102
|
+
];
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
if (fs.existsSync(candidate))
|
|
105
|
+
return candidate;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function buildTailwindCss(projectDir, uiConfig) {
|
|
110
|
+
const cliPath = findTailwindCliPath(projectDir);
|
|
111
|
+
if (!cliPath) {
|
|
112
|
+
console.warn('[kuratchi] ui.library: "tailwindcss" configured but @tailwindcss/cli could not be resolved');
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const uiDir = path.join(projectDir, '.kuratchi', 'ui');
|
|
116
|
+
if (!fs.existsSync(uiDir))
|
|
117
|
+
fs.mkdirSync(uiDir, { recursive: true });
|
|
118
|
+
const inputPath = path.join(uiDir, 'tailwind.input.css');
|
|
119
|
+
const outputPath = path.join(uiDir, 'tailwind.output.css');
|
|
120
|
+
const sourcePath = path.relative(uiDir, path.join(projectDir, 'src')).replace(/\\/g, '/');
|
|
121
|
+
const configSource = path.relative(uiDir, path.join(projectDir, 'kuratchi.config.ts')).replace(/\\/g, '/');
|
|
122
|
+
const lines = [
|
|
123
|
+
'@import "tailwindcss";',
|
|
124
|
+
`@source "${sourcePath}";`,
|
|
125
|
+
`@source "${configSource}";`,
|
|
126
|
+
...uiConfig.plugins.map((plugin) => `@plugin "${plugin}";`),
|
|
127
|
+
'',
|
|
128
|
+
];
|
|
129
|
+
fs.writeFileSync(inputPath, lines.join('\n'), 'utf-8');
|
|
130
|
+
execFileSync(process.execPath, [cliPath, '-i', inputPath, '-o', outputPath], {
|
|
131
|
+
cwd: projectDir,
|
|
132
|
+
stdio: 'pipe',
|
|
133
|
+
});
|
|
134
|
+
return fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf-8') : null;
|
|
135
|
+
}
|
|
136
|
+
export function readUiTheme(projectDir) {
|
|
137
|
+
const uiConfig = readUiConfig(projectDir);
|
|
138
|
+
if (!uiConfig)
|
|
139
|
+
return null;
|
|
140
|
+
if (uiConfig.library === 'tailwindcss') {
|
|
141
|
+
return buildTailwindCss(projectDir, uiConfig);
|
|
142
|
+
}
|
|
143
|
+
const themeValue = uiConfig.theme ?? 'default';
|
|
64
144
|
if (themeValue === 'default' || themeValue === 'dark' || themeValue === 'light' || themeValue === 'system') {
|
|
65
145
|
const candidates = [
|
|
66
146
|
path.join(projectDir, 'node_modules', '@kuratchi/ui', 'src', 'styles', 'theme.css'),
|
|
@@ -83,18 +163,12 @@ export function readUiTheme(projectDir) {
|
|
|
83
163
|
return null;
|
|
84
164
|
}
|
|
85
165
|
export function readUiConfigValues(projectDir) {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
166
|
+
const uiConfig = readUiConfig(projectDir);
|
|
167
|
+
if (!uiConfig)
|
|
88
168
|
return null;
|
|
89
|
-
const source = fs.readFileSync(configPath, 'utf-8');
|
|
90
|
-
const uiBlock = readConfigBlock(source, 'ui');
|
|
91
|
-
if (!uiBlock)
|
|
92
|
-
return null;
|
|
93
|
-
const themeMatch = uiBlock.body.match(/theme\s*:\s*['"]([^'"]+)['"]/);
|
|
94
|
-
const radiusMatch = uiBlock.body.match(/radius\s*:\s*['"]([^'"]+)['"]/);
|
|
95
169
|
return {
|
|
96
|
-
theme:
|
|
97
|
-
radius:
|
|
170
|
+
theme: uiConfig.theme,
|
|
171
|
+
radius: uiConfig.radius,
|
|
98
172
|
};
|
|
99
173
|
}
|
|
100
174
|
export function readOrmConfig(projectDir) {
|
|
@@ -134,10 +208,12 @@ export function readOrmConfig(projectDir) {
|
|
|
134
208
|
const skipMigrations = skipMatch?.[1] === 'true';
|
|
135
209
|
const typeMatch = rest.match(/type\s*:\s*['"]?(d1|do)['"]?/);
|
|
136
210
|
const type = typeMatch?.[1] ?? 'd1';
|
|
211
|
+
const remoteMatch = rest.match(/remote\s*:\s*(true|false)/);
|
|
212
|
+
const remote = remoteMatch?.[1] === 'true';
|
|
137
213
|
const schemaImportPath = importMap.get(schemaExportName);
|
|
138
214
|
if (!schemaImportPath)
|
|
139
215
|
continue;
|
|
140
|
-
entries.push({ binding, schemaImportPath, schemaExportName, skipMigrations, type });
|
|
216
|
+
entries.push({ binding, schemaImportPath, schemaExportName, skipMigrations, type, remote });
|
|
141
217
|
}
|
|
142
218
|
return entries;
|
|
143
219
|
}
|
|
@@ -174,10 +250,10 @@ export function readDoConfig(projectDir) {
|
|
|
174
250
|
const source = fs.readFileSync(configPath, 'utf-8');
|
|
175
251
|
const doIdx = source.search(/durableObjects\s*:\s*\{/);
|
|
176
252
|
if (doIdx === -1)
|
|
177
|
-
return
|
|
253
|
+
return readWranglerDoConfig(projectDir);
|
|
178
254
|
const braceStart = source.indexOf('{', doIdx);
|
|
179
255
|
if (braceStart === -1)
|
|
180
|
-
return
|
|
256
|
+
return readWranglerDoConfig(projectDir);
|
|
181
257
|
let depth = 0;
|
|
182
258
|
let braceEnd = braceStart;
|
|
183
259
|
for (let i = braceStart; i < source.length; i++) {
|
|
@@ -227,7 +303,7 @@ export function readDoConfig(projectDir) {
|
|
|
227
303
|
continue;
|
|
228
304
|
entries.push({ binding: match[1], className: match[2] });
|
|
229
305
|
}
|
|
230
|
-
return entries;
|
|
306
|
+
return entries.length > 0 ? entries : readWranglerDoConfig(projectDir);
|
|
231
307
|
}
|
|
232
308
|
/** Read durable_objects.bindings from wrangler.jsonc / wrangler.json as fallback. */
|
|
233
309
|
function readWranglerDoConfig(projectDir) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DesktopConfigEntry, OrmDatabaseEntry } from './compiler-shared.js';
|
|
2
|
+
export interface DesktopManifest {
|
|
3
|
+
formatVersion: 1;
|
|
4
|
+
generatedAt: string;
|
|
5
|
+
projectDir: string;
|
|
6
|
+
app: {
|
|
7
|
+
name: string;
|
|
8
|
+
id: string;
|
|
9
|
+
initialPath: string;
|
|
10
|
+
window: {
|
|
11
|
+
title: string;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
runtime: {
|
|
17
|
+
workerEntrypoint: string;
|
|
18
|
+
assetsRoot: string | null;
|
|
19
|
+
compatibilityDate: string;
|
|
20
|
+
compatibilityFlags: string[];
|
|
21
|
+
cloudflareAccountId: string | null;
|
|
22
|
+
};
|
|
23
|
+
bindings: {
|
|
24
|
+
desktop: {
|
|
25
|
+
notifications: boolean;
|
|
26
|
+
files: boolean;
|
|
27
|
+
};
|
|
28
|
+
remote: Array<{
|
|
29
|
+
binding: string;
|
|
30
|
+
type: 'd1';
|
|
31
|
+
remote: boolean;
|
|
32
|
+
databaseId: string;
|
|
33
|
+
databaseName: string | null;
|
|
34
|
+
} | {
|
|
35
|
+
binding: string;
|
|
36
|
+
type: 'r2';
|
|
37
|
+
remote: boolean;
|
|
38
|
+
bucketName: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function buildDesktopManifest(opts: {
|
|
43
|
+
projectDir: string;
|
|
44
|
+
workerFile: string;
|
|
45
|
+
desktopConfig: DesktopConfigEntry | null;
|
|
46
|
+
ormDatabases: OrmDatabaseEntry[];
|
|
47
|
+
}): DesktopManifest | null;
|
|
48
|
+
export declare function writeDesktopManifest(projectDir: string, manifest: DesktopManifest): string;
|