@kuratchi/js 0.0.21 → 0.0.23

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 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 '$database/items';
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 `$database/` alias resolves to `src/database/`. You can use any path alias configured in your tsconfig.
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 '$database/items';
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/database/items.ts
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 '$database/auth';
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-action` — fetch action (no page reload)
368
-
369
- Calls a server action via `fetch` and refreshes `data-refresh` targets when done:
370
-
371
- ```html
372
- <button data-action="deleteItem" data-args={JSON.stringify([item.id])}>Delete</button>
373
- <button data-action="toggleItem" data-args={JSON.stringify([item.id, true])}>Done</button>
374
- ```
375
-
376
- The action function receives the args array as individual arguments:
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-action` call succeeds, elements with `data-refresh` re-fetch their content:
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * CLI entry point — kuratchi build | watch | create
3
+ * CLI entry point kuratchi build | watch | create
4
4
  */
5
5
  export {};
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * CLI entry point — kuratchi build | watch | create
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 for custom setups)
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 → ${path.relative(projectDir, outFile)}`);
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
- const routesDir = path.join(projectDir, 'src', 'routes');
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 getNodeExecutable() {
175
- if (!process.versions.bun)
176
- return process.execPath;
177
- return 'node';
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(getNodeExecutable(), [localWranglerBin, ...wranglerArgs], {
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
+ }
@@ -4,6 +4,7 @@ export interface OrmDatabaseEntry {
4
4
  schemaExportName: string;
5
5
  skipMigrations: boolean;
6
6
  type: 'd1' | 'do';
7
+ remote: boolean;
7
8
  }
8
9
  export interface AuthConfigEntry {
9
10
  cookieName: string;
@@ -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
- export function readUiTheme(projectDir) {
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 themeValue = themeMatch?.[1] ?? 'default';
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 configPath = path.join(projectDir, 'kuratchi.config.ts');
87
- if (!fs.existsSync(configPath))
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: themeMatch?.[1] ?? 'dark',
97
- radius: radiusMatch?.[1] ?? 'default',
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;