@tailorkit/app 0.1.0-beta.2 → 0.1.0-beta.20

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 (54) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +184 -0
  3. package/dist/builder/client-views.d.ts +16 -0
  4. package/dist/builder/drizzle.d.ts +2 -0
  5. package/dist/builder/generate-migrations.d.ts +6 -0
  6. package/dist/builder/index.d.ts +1 -2
  7. package/dist/builder/instances.d.ts +19 -0
  8. package/dist/builder/migrations.d.ts +6 -0
  9. package/dist/builder/schema-check.d.ts +2 -0
  10. package/dist/builder/server.d.ts +6 -0
  11. package/dist/builder/upload-manifest.d.ts +13 -3
  12. package/dist/builder.js +986 -21
  13. package/dist/chunk-7NOEYJOB.js +197 -0
  14. package/dist/{chunk-7L6ZZHCK.js → chunk-DEU6RITM.js} +6 -1
  15. package/dist/chunk-GVB2PAK7.js +28 -0
  16. package/dist/chunk-IJBQ77AH.js +35 -0
  17. package/dist/chunk-RHCGWGSG.js +645 -0
  18. package/dist/client/connection.d.ts +31 -0
  19. package/dist/client/query-store.d.ts +23 -0
  20. package/dist/client/reference.d.ts +20 -0
  21. package/dist/client/session.d.ts +14 -0
  22. package/dist/client.d.ts +7 -0
  23. package/dist/client.js +32 -0
  24. package/dist/config/config.d.ts +88 -4
  25. package/dist/config/index.d.ts +0 -1
  26. package/dist/config/loader.d.ts +0 -1
  27. package/dist/config-loader.js +2 -2
  28. package/dist/config.js +1 -1
  29. package/dist/database/schema.d.ts +78 -0
  30. package/dist/database/types.d.ts +7 -0
  31. package/dist/errors.d.ts +6 -0
  32. package/dist/index.d.ts +2 -72
  33. package/dist/index.js +81 -76
  34. package/dist/preact-version.d.ts +0 -1
  35. package/dist/preact.d.ts +52 -0
  36. package/dist/protocol.d.ts +74 -0
  37. package/dist/protocol.js +28 -0
  38. package/dist/server/builder.d.ts +40 -0
  39. package/dist/server/bundle.d.ts +17 -0
  40. package/dist/server/functions.d.ts +89 -0
  41. package/dist/server/instances.d.ts +16 -0
  42. package/dist/server.d.ts +9 -0
  43. package/dist/server.js +58 -0
  44. package/dist/views.d.ts +139 -0
  45. package/package.json +30 -23
  46. package/LICENSE +0 -674
  47. package/dist/builder/index.d.ts.map +0 -1
  48. package/dist/builder/upload-manifest.d.ts.map +0 -1
  49. package/dist/chunk-AGGRUUWG.js +0 -22
  50. package/dist/config/config.d.ts.map +0 -1
  51. package/dist/config/index.d.ts.map +0 -1
  52. package/dist/config/loader.d.ts.map +0 -1
  53. package/dist/index.d.ts.map +0 -1
  54. package/dist/preact-version.d.ts.map +0 -1
package/dist/builder.js CHANGED
@@ -1,12 +1,77 @@
1
1
  import {
2
2
  loadTailorKitConfig
3
- } from "./chunk-7L6ZZHCK.js";
4
- import "./chunk-AGGRUUWG.js";
3
+ } from "./chunk-DEU6RITM.js";
4
+ import "./chunk-GVB2PAK7.js";
5
+
6
+ // src/builder/index.ts
7
+ import path7 from "path";
8
+ import { createRequire as createRequire2 } from "module";
9
+ import { readFile as readFile4, realpath, writeFile as writeFile2 } from "fs/promises";
10
+
11
+ // ../asset-delivery/dist/logo-validation.mjs
12
+ var maxLogoBytes = 262144;
13
+ var maxLogoDimension = 2048;
14
+ var readPngDimensions = (content) => {
15
+ if (content.length < 33 || ![
16
+ 137,
17
+ 80,
18
+ 78,
19
+ 71,
20
+ 13,
21
+ 10,
22
+ 26,
23
+ 10
24
+ ].every((byte, index) => content[index] === byte)) throw new Error("Logo content is not a valid PNG file.");
25
+ const view = new DataView(content.buffer, content.byteOffset, content.byteLength);
26
+ const isIhdr = content[12] === 73 && content[13] === 72 && content[14] === 68 && content[15] === 82;
27
+ if (view.getUint32(8) !== 13 || !isIhdr) throw new Error("Logo content is not a valid PNG file.");
28
+ return {
29
+ width: view.getUint32(16),
30
+ height: view.getUint32(20)
31
+ };
32
+ };
33
+ var readWebpDimensions = (content) => {
34
+ const text = (start, end) => new TextDecoder().decode(content.subarray(start, end));
35
+ if (content.length < 30 || text(0, 4) !== "RIFF" || text(8, 12) !== "WEBP") throw new Error("Logo content is not a valid WebP file.");
36
+ const view = new DataView(content.buffer, content.byteOffset, content.byteLength);
37
+ const chunk = text(12, 16);
38
+ if (chunk === "VP8X") return {
39
+ width: 1 + view.getUint16(24, true) + (content[26] ?? 0) * 65536,
40
+ height: 1 + view.getUint16(27, true) + (content[29] ?? 0) * 65536
41
+ };
42
+ if (chunk === "VP8L" && content[20] === 47) {
43
+ const bits = view.getUint32(21, true);
44
+ return {
45
+ width: bits % 16384 + 1,
46
+ height: Math.floor(bits / 16384) % 16384 + 1
47
+ };
48
+ }
49
+ if (chunk === "VP8 " && content[23] === 157 && content[24] === 1 && content[25] === 42) return {
50
+ width: view.getUint16(26, true) % 16384,
51
+ height: view.getUint16(28, true) % 16384
52
+ };
53
+ throw new Error("Logo content is not a supported WebP file.");
54
+ };
55
+ var validateSvg = (content) => {
56
+ const svg = new TextDecoder("utf-8", {
57
+ fatal: true,
58
+ ignoreBOM: false
59
+ }).decode(content);
60
+ if (!/<svg(?:\s|>)/iu.test(svg)) throw new Error("Logo content is not a valid SVG file.");
61
+ if (/<(?:script|foreignObject|iframe|object|embed)(?:\s|>)/iu.test(svg) || /\son[a-z]+\s*=/iu.test(svg) || /(?:href|src)\s*=\s*["'](?!#)/iu.test(svg) || /(?:@import|url\(\s*(?!["']?#))/iu.test(svg) || /<!DOCTYPE|<!ENTITY|<\?xml-stylesheet/iu.test(svg)) throw new Error("SVG logos cannot contain scripts or external resources.");
62
+ };
63
+ function validateLogoAsset(content, contentType) {
64
+ if (content.byteLength < 1 || content.byteLength > 262144) throw new Error(`Logo must be between 1 and ${maxLogoBytes} bytes.`);
65
+ if (contentType === "image/svg+xml") {
66
+ validateSvg(content);
67
+ return {};
68
+ }
69
+ const dimensions = contentType === "image/png" ? readPngDimensions(content) : readWebpDimensions(content);
70
+ if (dimensions.width < 1 || dimensions.height < 1 || dimensions.width > 2048 || dimensions.height > 2048) throw new Error(`Raster logos cannot exceed ${maxLogoDimension} by ${maxLogoDimension} pixels.`);
71
+ return dimensions;
72
+ }
5
73
 
6
74
  // src/builder/index.ts
7
- import path from "path";
8
- import { createRequire } from "module";
9
- import { writeFile } from "fs/promises";
10
75
  import { build as viteBuild } from "vite";
11
76
 
12
77
  // src/preact-version.ts
@@ -31,45 +96,889 @@ var assertSupportedPreactVersion = (version) => {
31
96
  return check;
32
97
  };
33
98
 
99
+ // src/builder/client-views.ts
100
+ import { execFile } from "child_process";
101
+ import { promisify } from "util";
102
+
34
103
  // src/builder/upload-manifest.ts
35
104
  import { z } from "zod";
36
105
  var tailorkitUploadManifestSchema = z.object({
37
106
  assets: z.object({
38
- client: z.literal("client.js")
107
+ client: z.literal("client/client.js"),
108
+ server: z.literal("server/server.js").optional(),
109
+ logos: z.object({
110
+ dark: z.string().regex(/^client\/logo-dark\.(?:png|svg|webp)$/u).optional(),
111
+ light: z.string().regex(/^client\/logo-light\.(?:png|svg|webp)$/u).optional()
112
+ }).optional()
39
113
  }),
114
+ views: z.array(
115
+ z.object({
116
+ slot: z.string().min(1).max(255),
117
+ path: z.string().startsWith("/").max(1024),
118
+ instances: z.literal(true).optional(),
119
+ disabled: z.literal(true).optional()
120
+ })
121
+ ).max(1e3).optional(),
40
122
  version: z.literal(1)
41
123
  });
42
- var createTailorKitUploadManifest = () => tailorkitUploadManifestSchema.parse({
124
+ var createTailorKitUploadManifest = (logos, server = false, views) => tailorkitUploadManifestSchema.parse({
43
125
  assets: {
44
- client: "client.js"
126
+ client: "client/client.js",
127
+ ...server ? { server: "server/server.js" } : {},
128
+ ...logos && Object.keys(logos).length > 0 ? { logos } : {}
45
129
  },
130
+ views,
46
131
  version: 1
47
132
  });
48
133
 
134
+ // src/builder/client-views.ts
135
+ var inspectClient = `
136
+ import { readFile } from "node:fs/promises";
137
+ const source = await readFile(process.argv[1], "utf8");
138
+ const { default: client } = await import("data:text/javascript;base64," + Buffer.from(source).toString("base64"));
139
+ if (!client?.slots || typeof client.slots !== "object") throw new Error("Client must default-export defineClient(...)");
140
+ const instanceResolvers = [];
141
+ const views = Object.entries(client.slots).flatMap(([slot, definitions]) =>
142
+ Object.entries(definitions).flatMap(([path, view]) => {
143
+ if (view === false) return [{ slot, path, disabled: true }];
144
+ const resolver = view?.instances?.resolver;
145
+ if (resolver !== undefined) instanceResolvers.push({ slot, path, resolver });
146
+ return [{ slot, path, ...(resolver !== undefined ? { instances: true } : {}) }];
147
+ })
148
+ );
149
+ process.stdout.write("\\nTAILORKIT_VIEWS=" + JSON.stringify({ views, instanceResolvers }));
150
+ process.exit(0);
151
+ `;
152
+ async function readClientManifest(filename) {
153
+ try {
154
+ const { stdout } = await promisify(execFile)(
155
+ process.execPath,
156
+ ["--input-type=module", "--eval", inspectClient, filename],
157
+ { timeout: 1e4, maxBuffer: 2 * 1024 * 1024 }
158
+ );
159
+ const result = stdout.slice(
160
+ stdout.lastIndexOf("\nTAILORKIT_VIEWS=") + "\nTAILORKIT_VIEWS=".length
161
+ );
162
+ const manifest = JSON.parse(result);
163
+ const views = tailorkitUploadManifestSchema.shape.views.unwrap().parse(manifest.views);
164
+ if (!Array.isArray(manifest.instanceResolvers) || manifest.instanceResolvers.some(
165
+ (ref) => !ref || typeof ref !== "object" || typeof ref.slot !== "string" || typeof ref.path !== "string" || typeof ref.resolver !== "string" || !/^i[0-9a-f]{24}$/u.test(ref.resolver)
166
+ )) {
167
+ throw new Error("Invalid instance resolver references");
168
+ }
169
+ return { views, instanceResolvers: manifest.instanceResolvers };
170
+ } catch (error) {
171
+ throw new Error(
172
+ "Unable to read app views during the build. Keep browser-only side effects inside components or effects, and default-export defineClient(...).",
173
+ { cause: error }
174
+ );
175
+ }
176
+ }
177
+
178
+ // src/builder/server.ts
179
+ import { mkdir, mkdtemp as mkdtemp2, writeFile, cp, rm as rm2 } from "fs/promises";
180
+ import path5 from "path";
181
+ import { build } from "vite";
182
+
183
+ // src/builder/migrations.ts
184
+ import { createHash } from "crypto";
185
+ import { readdir, readFile } from "fs/promises";
186
+ import path from "path";
187
+ async function readAppMigrations(directory, optional = false) {
188
+ const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
189
+ if (error.code === "ENOENT") {
190
+ if (optional) return [];
191
+ throw new Error(
192
+ `App migrations directory is missing: ${directory}. Run tailorkit db generate and commit migrations before building.`,
193
+ { cause: error }
194
+ );
195
+ }
196
+ throw error;
197
+ });
198
+ const migrations = [];
199
+ const files = [directory];
200
+ const names = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
201
+ for (const name of names) {
202
+ if (!/^\d{14}_[\w-]+$/u.test(name)) {
203
+ throw new Error(`Unsupported app migration folder: ${name}. Use Drizzle Kit v1 migrations.`);
204
+ }
205
+ const file = path.join(directory, name, "migration.sql");
206
+ const sql = await readFile(file, "utf8");
207
+ const statements = sql.split("--> statement-breakpoint").map((part) => part.trim()).filter(Boolean);
208
+ if (!statements.length) throw new Error(`App migration is empty: ${name}`);
209
+ migrations.push({
210
+ id: name,
211
+ hash: createHash("sha256").update(sql).digest("hex"),
212
+ statements
213
+ });
214
+ files.push(file);
215
+ }
216
+ return { migrations, files };
217
+ }
218
+
219
+ // src/server/bundle.ts
220
+ var runtimeManifest = {
221
+ apiVersion: 1,
222
+ requires: ["database", "actions"]
223
+ };
224
+
225
+ // src/builder/schema-check.ts
226
+ import { execFile as execFile2 } from "child_process";
227
+ import { access as access2, mkdtemp, readFile as readFile2, readdir as readdir2, rm } from "fs/promises";
228
+ import { tmpdir } from "os";
229
+ import path3 from "path";
230
+ import { promisify as promisify2 } from "util";
231
+
232
+ // src/builder/drizzle.ts
233
+ import { access } from "fs/promises";
234
+ import { createRequire } from "module";
235
+ import path2 from "path";
236
+ async function drizzleGenerateArguments(root, output, name) {
237
+ const require2 = createRequire(path2.join(root, "package.json"));
238
+ for (const directory of require2.resolve.paths("drizzle-kit") ?? []) {
239
+ const cli = path2.join(directory, "drizzle-kit/bin.cjs");
240
+ if (await access(cli).then(() => true).catch(() => false)) {
241
+ return [
242
+ "--preserve-symlinks-main",
243
+ cli,
244
+ "generate",
245
+ "--dialect",
246
+ "sqlite",
247
+ "--schema",
248
+ path2.join(root, "src/schema.ts"),
249
+ "--out",
250
+ output,
251
+ ...name === void 0 ? [] : ["--name", name]
252
+ ];
253
+ }
254
+ }
255
+ throw new Error(
256
+ "Install drizzle-kit and drizzle-orm as dev dependencies, then run tailorkit db generate."
257
+ );
258
+ }
259
+
260
+ // src/builder/schema-check.ts
261
+ var run = promisify2(execFile2);
262
+ async function snapshot(directory) {
263
+ const entries = await readdir2(directory, { withFileTypes: true }).catch((error) => {
264
+ if (error.code === "ENOENT") return [];
265
+ throw error;
266
+ });
267
+ const latest = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().at(-1);
268
+ if (!latest) return;
269
+ const filename = path3.join(directory, latest, "snapshot.json");
270
+ const value = JSON.parse(await readFile2(filename, "utf8"));
271
+ if (value.version !== "7" || value.dialect !== "sqlite" || !Array.isArray(value.ddl)) {
272
+ throw new Error(
273
+ `Unsupported app schema snapshot: ${filename}. Use Drizzle Kit v1 SQLite migrations.`
274
+ );
275
+ }
276
+ return value;
277
+ }
278
+ function canonical(value) {
279
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
280
+ if (value && typeof value === "object") {
281
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
282
+ }
283
+ return JSON.stringify(value);
284
+ }
285
+ async function checkAppSchema(root, migrationsDirectory) {
286
+ const schema = path3.join(root, "src/schema.ts");
287
+ try {
288
+ await access2(schema);
289
+ } catch (error) {
290
+ if (error.code === "ENOENT") return;
291
+ throw error;
292
+ }
293
+ const temporary = await mkdtemp(path3.join(tmpdir(), "tailorkit-schema-check-"));
294
+ try {
295
+ const { stdout } = await run(
296
+ process.execPath,
297
+ await drizzleGenerateArguments(root, temporary),
298
+ {
299
+ cwd: root,
300
+ timeout: 3e4,
301
+ maxBuffer: 1024 * 1024,
302
+ env: { ...process.env, CI: "true" }
303
+ }
304
+ ).catch((error) => {
305
+ throw new Error(
306
+ "Could not check the app database schema with Drizzle Kit. Check src/schema.ts and your drizzle-kit/drizzle-orm dependencies.",
307
+ { cause: error }
308
+ );
309
+ });
310
+ const current = await snapshot(temporary);
311
+ if (!current && !stdout.includes("No schema changes")) {
312
+ throw new Error(
313
+ "Drizzle Kit did not produce a schema snapshot. Check src/schema.ts and run tailorkit db generate."
314
+ );
315
+ }
316
+ const saved = await snapshot(migrationsDirectory);
317
+ const expected = (current?.ddl ?? []).map(canonical).sort();
318
+ const actual = (saved?.ddl ?? []).map(canonical).sort();
319
+ if (canonical(expected) !== canonical(actual)) {
320
+ throw new Error(
321
+ `App database migrations are missing or out of sync with src/schema.ts. Run tailorkit db generate, commit the generated files in ${path3.relative(root, migrationsDirectory)}, then build again.`
322
+ );
323
+ }
324
+ } finally {
325
+ await rm(temporary, { recursive: true, force: true });
326
+ }
327
+ }
328
+
329
+ // src/builder/instances.ts
330
+ import { createHash as createHash2 } from "crypto";
331
+ import { readFile as readFile3 } from "fs/promises";
332
+ import path4 from "path";
333
+ import { parseSync, transformWithOxc } from "vite";
334
+ var suffix = "?tailorkit-instances";
335
+ var clientImports = /* @__PURE__ */ new Set([
336
+ "tailorkit/client",
337
+ "tailorkit/app/client",
338
+ "@tailorkit/app/client"
339
+ ]);
340
+ var browserGlobals = /* @__PURE__ */ new Set([
341
+ "window",
342
+ "document",
343
+ "navigator",
344
+ "localStorage",
345
+ "sessionStorage"
346
+ ]);
347
+ function isNode(value) {
348
+ return !!value && typeof value === "object" && "type" in value;
349
+ }
350
+ function children(node) {
351
+ return Object.values(node).flatMap(
352
+ (value) => Array.isArray(value) ? value.filter(isNode) : isNode(value) ? [value] : []
353
+ );
354
+ }
355
+ function walk(node, visit, ancestors = []) {
356
+ visit(node, ancestors);
357
+ for (const child of children(node)) walk(child, visit, [...ancestors, node]);
358
+ }
359
+ function boundNames(node) {
360
+ if (!node) return [];
361
+ switch (node.type) {
362
+ case "Identifier":
363
+ return [node.name];
364
+ case "RestElement":
365
+ return boundNames(node.argument);
366
+ case "AssignmentPattern":
367
+ return boundNames(node.left);
368
+ case "ArrayPattern":
369
+ return node.elements.flatMap(boundNames);
370
+ case "ObjectPattern":
371
+ return node.properties.flatMap(
372
+ (p) => boundNames(p.type === "Property" ? p.value : p.argument)
373
+ );
374
+ default:
375
+ return [];
376
+ }
377
+ }
378
+ function declarations(node) {
379
+ if (node.type === "VariableDeclaration")
380
+ return node.declarations.flatMap((d) => boundNames(d.id));
381
+ if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration")
382
+ return boundNames(node.id);
383
+ return [];
384
+ }
385
+ function isFunction(node) {
386
+ return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
387
+ node.type
388
+ );
389
+ }
390
+ function freeNames(root) {
391
+ const references = /* @__PURE__ */ new Set();
392
+ function pattern(node, scope) {
393
+ if (node.type === "AssignmentPattern") {
394
+ visit(node.right, scope);
395
+ pattern(node.left, scope);
396
+ } else if (node.type === "ObjectPattern") {
397
+ for (const p of node.properties) {
398
+ if (p.type === "Property") {
399
+ if (p.computed) visit(p.key, scope);
400
+ pattern(p.value, scope);
401
+ } else pattern(p.argument, scope);
402
+ }
403
+ } else if (node.type === "ArrayPattern") {
404
+ for (const element of node.elements) if (element) pattern(element, scope);
405
+ } else if (node.type === "RestElement") pattern(node.argument, scope);
406
+ }
407
+ function visit(node, parent) {
408
+ if (node.type === "Identifier") {
409
+ if (!parent.has(node.name)) references.add(node.name);
410
+ return;
411
+ }
412
+ if (node.type === "ImportDeclaration") return;
413
+ if (node.type === "ExportSpecifier") {
414
+ visit(node.local, parent);
415
+ return;
416
+ }
417
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
418
+ let hoisted2 = function(body) {
419
+ if (isFunction(body)) return;
420
+ if (body.type === "VariableDeclaration" && body.kind === "var") {
421
+ for (const name of declarations(body)) scope.add(name);
422
+ }
423
+ for (const child of children(body)) hoisted2(child);
424
+ };
425
+ var hoisted = hoisted2;
426
+ const scope = /* @__PURE__ */ new Set([
427
+ ...parent,
428
+ ...boundNames(node.id),
429
+ ...node.params.flatMap(boundNames)
430
+ ]);
431
+ if (node.body) hoisted2(node.body);
432
+ for (const p of node.params) pattern(p, scope);
433
+ if (node.body) visit(node.body, scope);
434
+ return;
435
+ }
436
+ if (node.type === "BlockStatement" || node.type === "Program") {
437
+ const scope = /* @__PURE__ */ new Set([...parent, ...node.body.flatMap(declarations)]);
438
+ for (const statement of node.body) visit(statement, scope);
439
+ return;
440
+ }
441
+ if (node.type === "VariableDeclarator") {
442
+ pattern(node.id, parent);
443
+ if (node.init) visit(node.init, parent);
444
+ return;
445
+ }
446
+ if (node.type === "Property" || node.type === "MethodDefinition" || node.type === "PropertyDefinition") {
447
+ if (node.computed) visit(node.key, parent);
448
+ if (node.value) visit(node.value, parent);
449
+ return;
450
+ }
451
+ if (node.type === "MemberExpression") {
452
+ visit(node.object, parent);
453
+ if (node.computed) visit(node.property, parent);
454
+ return;
455
+ }
456
+ if (node.type === "CatchClause") {
457
+ const scope = /* @__PURE__ */ new Set([...parent, ...boundNames(node.param)]);
458
+ if (node.param) pattern(node.param, scope);
459
+ visit(node.body, scope);
460
+ return;
461
+ }
462
+ if (node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement") {
463
+ const declaration = node.type === "ForStatement" ? node.init : node.left;
464
+ const scope = /* @__PURE__ */ new Set([...parent, ...declaration ? declarations(declaration) : []]);
465
+ for (const child of children(node)) visit(child, scope);
466
+ return;
467
+ }
468
+ if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
469
+ const scope = /* @__PURE__ */ new Set([...parent, ...boundNames(node.id)]);
470
+ if (node.superClass) visit(node.superClass, scope);
471
+ visit(node.body, scope);
472
+ return;
473
+ }
474
+ if (node.type === "LabeledStatement") {
475
+ visit(node.body, parent);
476
+ return;
477
+ }
478
+ if (node.type === "BreakStatement" || node.type === "ContinueStatement") return;
479
+ for (const child of children(node)) visit(child, parent);
480
+ }
481
+ visit(root, /* @__PURE__ */ new Set());
482
+ return references;
483
+ }
484
+ function property(node, name) {
485
+ return node.properties.find(
486
+ (p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" ? p.key.name === name : p.key.type === "Literal" && p.key.value === name)
487
+ );
488
+ }
489
+ function parse(filename, source) {
490
+ const result = parseSync(filename, source);
491
+ if (result.errors.length)
492
+ throw new Error(`Unable to parse ${filename}: ${result.errors[0]?.message}`);
493
+ return result.program;
494
+ }
495
+ function units(program, source) {
496
+ const imports = [];
497
+ const statements = [];
498
+ for (const statement of program.body) {
499
+ if (statement.type === "ImportDeclaration") {
500
+ imports.push(statement);
501
+ continue;
502
+ }
503
+ const exported = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration";
504
+ const node = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
505
+ if (node.type === "VariableDeclaration") {
506
+ for (const d of node.declarations)
507
+ statements.push({
508
+ node: d,
509
+ names: boundNames(d.id),
510
+ code: `${exported ? "export " : ""}${node.kind} ${source.slice(d.start, d.end)};`,
511
+ exported
512
+ });
513
+ } else
514
+ statements.push({
515
+ node,
516
+ names: declarations(node),
517
+ code: source.slice(statement.start, statement.end),
518
+ exported
519
+ });
520
+ }
521
+ return { imports, statements };
522
+ }
523
+ function closure(roots, statements) {
524
+ const bindings = new Map(statements.flatMap((u) => u.names.map((name) => [name, u])));
525
+ const names = /* @__PURE__ */ new Set();
526
+ const selected = /* @__PURE__ */ new Set();
527
+ function add(name) {
528
+ if (names.has(name)) return;
529
+ names.add(name);
530
+ const unit = bindings.get(name);
531
+ if (!unit || selected.has(unit)) return;
532
+ selected.add(unit);
533
+ for (const ref of freeNames(unit.node)) add(ref);
534
+ }
535
+ for (const root of roots) add(root);
536
+ return { names, selected };
537
+ }
538
+ function renderImports(imports, needed, source, sideEffects) {
539
+ return imports.flatMap((i) => {
540
+ if (!i.specifiers.length) return sideEffects ? [source.slice(i.start, i.end)] : [];
541
+ const parts = i.specifiers.filter((s) => needed(s.local.name));
542
+ if (!parts.length) return [];
543
+ const defaultImport = parts.find((s) => s.type === "ImportDefaultSpecifier");
544
+ const namespace = parts.find((s) => s.type === "ImportNamespaceSpecifier");
545
+ const named = parts.filter((s) => s.type === "ImportSpecifier");
546
+ const clauses = [
547
+ defaultImport?.local.name,
548
+ namespace ? `* as ${namespace.local.name}` : void 0,
549
+ named.length ? `{ ${named.map((s) => `${source.slice(s.imported.start, s.imported.end)} as ${s.local.name}`).join(", ")} }` : void 0
550
+ ].filter(Boolean);
551
+ return [`import ${clauses.join(", ")} from ${JSON.stringify(i.source.value)};`];
552
+ }).join("\n");
553
+ }
554
+ async function extractInstances(filename, source, root, includedNames) {
555
+ const transformed = await transformWithOxc(source, filename, {
556
+ jsx: { importSource: "preact" },
557
+ sourcemap: false
558
+ });
559
+ source = transformed.code;
560
+ const program = parse(filename.replace(/\.[^.]+$/u, ".js"), source);
561
+ const aliases = /* @__PURE__ */ new Set();
562
+ for (const statement of program.body) {
563
+ if (statement.type !== "ImportDeclaration" || !clientImports.has(String(statement.source.value)))
564
+ continue;
565
+ for (const s of statement.specifiers) {
566
+ if (s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "defineView")
567
+ aliases.add(s.local.name);
568
+ }
569
+ }
570
+ let resolvers = [];
571
+ walk(program, (node, ancestors) => {
572
+ if (node.type !== "CallExpression" || node.callee.type !== "Identifier" || !aliases.has(node.callee.name))
573
+ return;
574
+ const options = node.arguments[0];
575
+ if (options?.type !== "ObjectExpression") return;
576
+ const instances = property(options, "instances");
577
+ if (!instances) return;
578
+ const fail = (message) => {
579
+ throw new Error(`${filename}: ${message}`);
580
+ };
581
+ if (ancestors.some(isFunction))
582
+ fail("defineView with instances must be declared at module scope.");
583
+ const owner = ancestors.findLast((n) => n.type === "VariableDeclarator");
584
+ const viewName = owner?.type === "VariableDeclarator" && owner.id.type === "Identifier" ? owner.id.name : "default";
585
+ const view = property(options, "view")?.value;
586
+ if (view?.type !== "Literal" || typeof view.value !== "string")
587
+ fail("Instance view paths must be string literals.");
588
+ if (instances.value.type !== "ObjectExpression")
589
+ fail("instances must be an inline object with dataSchema and resolve.");
590
+ const config = instances.value;
591
+ if (config.properties.some((p) => p.type !== "Property" || p.computed || p.kind !== "init"))
592
+ fail("Instance options cannot contain spreads, getters or computed properties.");
593
+ const resolve = property(config, "resolve");
594
+ const schema = property(config, "dataSchema");
595
+ if (!resolve || !schema || resolve.method || !["ArrowFunctionExpression", "FunctionExpression"].includes(resolve.value.type))
596
+ fail("instances requires dataSchema and an inline resolve function.");
597
+ const name = `i${createHash2("sha256").update(
598
+ `${path4.relative(root, filename).split(path4.sep).join("/")}\0${viewName}\0${view.value}`
599
+ ).digest("hex").slice(0, 24)}`;
600
+ if (resolvers.some((r) => r.name === name))
601
+ fail("Instance resolvers must have distinct module-level variable names.");
602
+ resolvers.push({
603
+ name,
604
+ view: view.value,
605
+ call: node,
606
+ instances,
607
+ resolve,
608
+ schema
609
+ });
610
+ });
611
+ if (includedNames) {
612
+ resolvers = resolvers.filter((r) => includedNames.includes(r.name));
613
+ if (resolvers.length !== includedNames.length)
614
+ throw new Error(`${filename}: Instance definitions changed; rebuild the app client.`);
615
+ }
616
+ if (!resolvers.length) return;
617
+ const original = units(program, source);
618
+ const server = closure(
619
+ resolvers.flatMap((r) => [...freeNames(r.resolve.value), ...freeNames(r.schema.value)]),
620
+ original.statements
621
+ );
622
+ for (const name of server.names) {
623
+ if (browserGlobals.has(name))
624
+ throw new Error(`${filename}: Instance resolvers cannot access browser global ${name}.`);
625
+ }
626
+ for (const unit of server.selected) {
627
+ if (resolvers.some((r) => unit.node.start <= r.call.start && unit.node.end >= r.call.end))
628
+ throw new Error(
629
+ `${filename}: Instance resolvers cannot capture a view definition. Use the supplied context and queries.`
630
+ );
631
+ }
632
+ const serverSource = [
633
+ renderImports(original.imports, (name) => server.names.has(name), source, false),
634
+ ...original.statements.filter((u) => server.selected.has(u)).map((u) => u.code.replace(/^export /u, "")),
635
+ `export const resolvers = { ${resolvers.map((r) => `${r.name}: { dataSchema: ${source.slice(r.schema.value.start, r.schema.value.end)}, resolve: ${source.slice(r.resolve.value.start, r.resolve.value.end)} }`).join(", ")} };`
636
+ ].join("\n");
637
+ let browserSource = source;
638
+ for (const r of [...resolvers].sort(
639
+ (a, b) => b.instances.value.start - a.instances.value.start
640
+ )) {
641
+ browserSource = browserSource.slice(0, r.instances.value.start) + `{ resolver: ${JSON.stringify(r.name)} }` + browserSource.slice(r.instances.value.end);
642
+ }
643
+ const browser = units(parse("browser.js", browserSource), browserSource);
644
+ const serverBindings = new Set([...server.selected].flatMap((u) => u.names));
645
+ const browserRoots = browser.statements.filter(
646
+ (u) => u.exported || !u.names.length || !u.names.some((name) => serverBindings.has(name))
647
+ );
648
+ const browserNeeded = closure(
649
+ browserRoots.flatMap((u) => [...freeNames(u.node), ...u.names]),
650
+ browser.statements
651
+ );
652
+ return {
653
+ names: resolvers.map((r) => r.name),
654
+ server: serverSource,
655
+ browser: [
656
+ renderImports(
657
+ browser.imports,
658
+ (name) => !server.names.has(name) || browserNeeded.names.has(name),
659
+ browserSource,
660
+ true
661
+ ),
662
+ ...browser.statements.filter((u) => browserRoots.includes(u) || browserNeeded.selected.has(u)).map((u) => u.code)
663
+ ].join("\n")
664
+ };
665
+ }
666
+ function instanceExtractionPlugin(root, serverConfigured, modules) {
667
+ return {
668
+ name: "tailorkit-instance-extraction",
669
+ enforce: "pre",
670
+ async transform(source, filename) {
671
+ if (filename.includes("node_modules") || !/\.[cm]?[jt]sx?$/u.test(filename) || !source.includes("instances") && !modules.has(filename))
672
+ return;
673
+ const result = await extractInstances(filename, source, root);
674
+ if (!result) {
675
+ modules.delete(filename);
676
+ return;
677
+ }
678
+ if (!serverConfigured)
679
+ throw new Error("View instances require server configuration in tailorkit.config.ts.");
680
+ modules.set(filename, { filename, names: result.names });
681
+ return { code: result.browser, map: null };
682
+ }
683
+ };
684
+ }
685
+ function instanceServerPlugin(root, modules) {
686
+ return {
687
+ name: "tailorkit-instance-server-modules",
688
+ enforce: "pre",
689
+ resolveId(id) {
690
+ if (id.endsWith(suffix)) return id;
691
+ },
692
+ async load(id) {
693
+ if (!id.endsWith(suffix)) return;
694
+ const filename = id.slice(0, -suffix.length);
695
+ this.addWatchFile(filename);
696
+ const names = modules.find((module) => module.filename === filename)?.names;
697
+ const result = await extractInstances(
698
+ filename,
699
+ await readFile3(filename, "utf8"),
700
+ root,
701
+ names
702
+ );
703
+ if (!result)
704
+ throw new Error(`Instance resolver missing from ${filename}; rebuild the app client.`);
705
+ return result.server;
706
+ }
707
+ };
708
+ }
709
+ function instanceServerImports(modules) {
710
+ return modules.map(
711
+ (m, i) => `import { resolvers as instances${i} } from ${JSON.stringify(m.filename + suffix)};`
712
+ ).join("\n");
713
+ }
714
+ function instanceServerBindings(modules, registrations) {
715
+ return `[${registrations.map(({ slot, path: viewPath, resolver }) => {
716
+ const index = modules.findIndex((module) => module.names.includes(resolver));
717
+ if (index < 0) throw new Error("Instance resolver was not extracted by the app build.");
718
+ return `{ slot: ${JSON.stringify(slot)}, path: ${JSON.stringify(viewPath)}, ...instances${index}[${JSON.stringify(resolver)}] }`;
719
+ }).join(", ")}]`;
720
+ }
721
+
722
+ // src/builder/server.ts
723
+ async function buildServer(loaded, watch = false, buildOutDir, instances = [], registrations = []) {
724
+ const config = loaded.config.server;
725
+ if (!config) {
726
+ return;
727
+ }
728
+ const output = buildOutDir ?? path5.resolve(loaded.root, loaded.config.build?.outDir ?? ".tailorkit");
729
+ const directory = path5.join(output, "server");
730
+ const migrationsDirectory = path5.resolve(loaded.root, config.migrations ?? "./migrations");
731
+ const migrationsOutput = path5.join(output, "migrations");
732
+ const temporaryRoot = path5.join(output, "tmp");
733
+ validateServerBuildOutput(loaded, output);
734
+ await rm2(directory, { recursive: true, force: true });
735
+ await mkdir(directory, { recursive: true });
736
+ await mkdir(temporaryRoot, { recursive: true });
737
+ const temporary = await mkdtemp2(path5.join(temporaryRoot, "server-"));
738
+ const entry = path5.join(temporary, "entry.mjs");
739
+ let watching = false;
740
+ let ready;
741
+ const initialBuild = watch ? new Promise((resolve) => {
742
+ ready = resolve;
743
+ }) : void 0;
744
+ try {
745
+ await writeFile(
746
+ entry,
747
+ `${instances.length ? `import app from ${JSON.stringify(path5.resolve(loaded.root, config.entry ?? "src/server.ts"))};
748
+ import { withInstanceResolvers } from "tailorkit/server";
749
+ ${instanceServerImports(instances)}
750
+ export default withInstanceResolvers(app, ${instanceServerBindings(instances, registrations)});` : `export { default } from ${JSON.stringify(path5.resolve(loaded.root, config.entry ?? "src/server.ts"))};`}
751
+ export { default as migrations } from "tailorkit:migrations";
752
+ export const runtimeManifest = ${JSON.stringify(runtimeManifest)};
753
+ `
754
+ );
755
+ const result = await build({
756
+ root: loaded.root,
757
+ configFile: false,
758
+ plugins: [
759
+ instanceServerPlugin(loaded.root, instances),
760
+ {
761
+ name: "tailorkit-app-migrations",
762
+ closeWatcher: () => rm2(temporary, { recursive: true, force: true }),
763
+ resolveId(id) {
764
+ if (id === "tailorkit:migrations") {
765
+ return "\0tailorkit:migrations";
766
+ }
767
+ },
768
+ async load(id) {
769
+ if (id !== "\0tailorkit:migrations") {
770
+ return;
771
+ }
772
+ const { migrations, files } = await readAppMigrations(
773
+ migrationsDirectory,
774
+ config.migrations === void 0
775
+ );
776
+ await checkAppSchema(loaded.root, migrationsDirectory);
777
+ this.addWatchFile(path5.join(loaded.root, "src/schema.ts"));
778
+ for (const file of files) {
779
+ this.addWatchFile(file);
780
+ }
781
+ await rm2(migrationsOutput, { recursive: true, force: true });
782
+ await mkdir(migrationsOutput, { recursive: true });
783
+ await cp(migrationsDirectory, migrationsOutput, { recursive: true }).catch(
784
+ (error) => {
785
+ if (config.migrations === void 0 && error.code === "ENOENT") {
786
+ return;
787
+ }
788
+ throw error;
789
+ }
790
+ );
791
+ return `export default ${JSON.stringify(migrations)};`;
792
+ }
793
+ }
794
+ ],
795
+ build: {
796
+ ssr: entry,
797
+ outDir: directory,
798
+ emptyOutDir: false,
799
+ target: "es2022",
800
+ watch: watch ? {} : null,
801
+ minify: "oxc",
802
+ rollupOptions: {
803
+ external: [],
804
+ output: {
805
+ entryFileNames: "server.js",
806
+ inlineDynamicImports: true,
807
+ minify: true,
808
+ minifyInternalExports: true
809
+ }
810
+ }
811
+ },
812
+ ssr: { noExternal: true }
813
+ });
814
+ watching = watch;
815
+ if (watch && "on" in result && initialBuild) {
816
+ try {
817
+ await Promise.race([
818
+ initialBuild,
819
+ new Promise(
820
+ (_, reject) => result.on("event", (event) => {
821
+ if (event.code === "ERROR") reject(event.error);
822
+ if (event.code === "END") ready?.();
823
+ })
824
+ )
825
+ ]);
826
+ } catch (error) {
827
+ await result.close();
828
+ throw error;
829
+ }
830
+ }
831
+ return result;
832
+ } finally {
833
+ if (!watching) await rm2(temporary, { recursive: true, force: true });
834
+ }
835
+ }
836
+ function validateServerBuildOutput(loaded, output) {
837
+ if (!loaded.config.server) return;
838
+ const migrationsDirectory = path5.resolve(
839
+ loaded.root,
840
+ loaded.config.server.migrations ?? "./migrations"
841
+ );
842
+ for (const name of ["server", "client", "migrations", "tmp"]) {
843
+ if (overlaps(migrationsDirectory, path5.join(output, name))) {
844
+ throw new Error("App build output must not overlap migration source");
845
+ }
846
+ }
847
+ }
848
+ function overlaps(source, destination) {
849
+ const contains = (parent, child) => {
850
+ const relative = path5.relative(parent, child);
851
+ return relative === "" || !path5.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path5.sep}`);
852
+ };
853
+ return contains(source, destination) || contains(destination, source);
854
+ }
855
+
856
+ // src/builder/generate-migrations.ts
857
+ import { spawn } from "child_process";
858
+ import { access as access3 } from "fs/promises";
859
+ import path6 from "path";
860
+ async function generateAppMigrations(options = {}) {
861
+ const loaded = await loadTailorKitConfig(options.configPath, options.cwd);
862
+ if (!loaded.config.server) {
863
+ throw new Error(
864
+ "Enable server: {} in tailorkit.config.ts before generating database migrations."
865
+ );
866
+ }
867
+ await access3(path6.join(loaded.root, "src/schema.ts")).catch((error) => {
868
+ throw new Error(
869
+ "Define the app database schema in src/schema.ts before generating migrations.",
870
+ { cause: error }
871
+ );
872
+ });
873
+ const directory = path6.resolve(loaded.root, loaded.config.server.migrations ?? "./migrations");
874
+ const args = await drizzleGenerateArguments(loaded.root, directory, options.name);
875
+ await new Promise((resolve, reject) => {
876
+ const child = spawn(process.execPath, args, { cwd: loaded.root, stdio: "inherit" });
877
+ child.once("error", reject);
878
+ child.once("close", (code, signal) => {
879
+ if (code === 0) resolve();
880
+ else reject(new Error(`Drizzle migration generation failed (${signal ?? code}).`));
881
+ });
882
+ });
883
+ await checkAppSchema(loaded.root, directory);
884
+ return directory;
885
+ }
886
+
49
887
  // src/builder/index.ts
50
- var preactExternal = /^preact(?:\/.*)?$/u;
51
888
  var preactPackageJson = "preact/package.json";
52
889
  var preactPackageJsonModuleId = "\0tailorkit-preact-package-json";
53
890
  var buildApp = async (options = {}) => {
54
891
  const loaded = await loadTailorKitConfig(options.configPath, options.cwd);
55
- const entry = options.entry ?? loaded.config.client?.entry ?? "./src/client.ts";
892
+ loaded.root = await realpath(loaded.root);
893
+ const entry = path7.resolve(loaded.root, loaded.config.client?.entry ?? "src/client.ts");
894
+ const serverEntry = path7.resolve(loaded.root, loaded.config.server?.entry ?? "src/server.ts");
56
895
  const outDir = options.outDir ?? loaded.config.build?.outDir ?? ".tailorkit";
57
896
  const preactVersion = getInstalledPreactVersion(loaded.root);
58
897
  assertSupportedPreactVersion(preactVersion);
59
- const resolvedOutDir = path.resolve(loaded.root, outDir);
898
+ const resolvedOutDir = path7.resolve(loaded.root, outDir);
899
+ validateServerBuildOutput(loaded, resolvedOutDir);
900
+ const clientOutDir = path7.join(resolvedOutDir, "client");
901
+ const instanceModules = /* @__PURE__ */ new Map();
902
+ let serverWatcher;
903
+ const writeBuildExtras = async () => {
904
+ const { views, instanceResolvers } = await readClientManifest(
905
+ path7.join(clientOutDir, "client.js")
906
+ );
907
+ const resolverNames = new Set(instanceResolvers.map((registration) => registration.resolver));
908
+ const activeModules = [...instanceModules.values()].map((module) => ({
909
+ ...module,
910
+ names: module.names.filter((name) => resolverNames.has(name))
911
+ })).filter((module) => module.names.length).sort((a, b) => a.filename.localeCompare(b.filename));
912
+ if (activeModules.reduce((count, module) => count + module.names.length, 0) !== resolverNames.size) {
913
+ throw new Error(
914
+ "Client references an instance resolver that was not extracted by the app build."
915
+ );
916
+ }
917
+ if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
918
+ serverWatcher = await buildServer(
919
+ loaded,
920
+ options.watch,
921
+ resolvedOutDir,
922
+ activeModules,
923
+ instanceResolvers
924
+ );
925
+ const logoManifest = {};
926
+ for (const variant of ["light", "dark"]) {
927
+ const configuredPath = loaded.config.logos?.[variant];
928
+ if (!configuredPath) {
929
+ continue;
930
+ }
931
+ const extension = path7.extname(configuredPath).toLowerCase().slice(1);
932
+ const contentType = { png: "image/png", svg: "image/svg+xml", webp: "image/webp" }[extension];
933
+ if (!contentType) {
934
+ throw new Error(`The ${variant} logo must be an SVG, PNG, or WebP file.`);
935
+ }
936
+ const content = await readFile4(path7.resolve(loaded.root, configuredPath));
937
+ validateLogoAsset(content, contentType);
938
+ const filename = `logo-${variant}.${extension}`;
939
+ await writeFile2(path7.join(clientOutDir, filename), content);
940
+ logoManifest[variant] = `client/${filename}`;
941
+ }
942
+ await writeFile2(path7.join(clientOutDir, "views.json"), `${JSON.stringify(views)}
943
+ `);
944
+ await writeFile2(
945
+ path7.join(resolvedOutDir, "tailorkit-upload.json"),
946
+ `${JSON.stringify(createTailorKitUploadManifest(logoManifest, Boolean(loaded.config.server), views), null, 2)}
947
+ `,
948
+ "utf-8"
949
+ );
950
+ };
951
+ let firstBuildDone;
952
+ let firstBuildFailed;
953
+ const firstBuild = new Promise((resolve, reject) => {
954
+ firstBuildDone = resolve;
955
+ firstBuildFailed = reject;
956
+ });
957
+ if (!options.watch) {
958
+ void firstBuild.catch(() => {
959
+ });
960
+ }
961
+ const completeBuild = async () => {
962
+ try {
963
+ await writeBuildExtras();
964
+ firstBuildDone?.();
965
+ } catch (error) {
966
+ firstBuildFailed?.(error instanceof Error ? error : new Error(String(error)));
967
+ throw error;
968
+ }
969
+ };
60
970
  const result = await viteBuild({
61
971
  build: {
62
972
  emptyOutDir: true,
63
973
  lib: {
64
- entry: path.resolve(loaded.root, entry),
974
+ entry,
65
975
  fileName: "client",
66
976
  formats: ["es"]
67
977
  },
68
- outDir: resolvedOutDir,
978
+ outDir: clientOutDir,
69
979
  watch: options.watch ? {} : null,
70
980
  minify: "oxc",
71
981
  rollupOptions: {
72
- external: (id) => id !== preactPackageJson && id !== preactPackageJsonModuleId && preactExternal.test(id),
73
982
  output: {
74
983
  comments: {
75
984
  annotation: false,
@@ -83,7 +992,37 @@ var buildApp = async (options = {}) => {
83
992
  },
84
993
  configFile: false,
85
994
  mode: options.mode,
995
+ oxc: { jsx: { importSource: "preact" } },
86
996
  plugins: [
997
+ instanceExtractionPlugin(loaded.root, Boolean(loaded.config.server), instanceModules),
998
+ {
999
+ name: "tailorkit-browser-server-boundary",
1000
+ enforce: "pre",
1001
+ async resolveId(id, importer) {
1002
+ if (/^(?:@tailorkit\/app\/server$|tailorkit\/(?:server|app\/server)$|effect(?:\/|$))/u.test(
1003
+ id
1004
+ ) || importer && path7.resolve(path7.dirname(importer), id) === serverEntry) {
1005
+ throw new Error(
1006
+ "App server implementations cannot be imported into a browser bundle. Import api from #tailorkit and server types with import type."
1007
+ );
1008
+ }
1009
+ if (importer) {
1010
+ const resolved = await this.resolve(id, importer, { skipSelf: true });
1011
+ if (resolved?.id.split("?")[0] === serverEntry) {
1012
+ throw new Error(
1013
+ "App server implementations cannot be imported into a browser bundle. Import api from #tailorkit and server types with import type."
1014
+ );
1015
+ }
1016
+ }
1017
+ return null;
1018
+ }
1019
+ },
1020
+ {
1021
+ name: "tailorkit-preview-build-ready",
1022
+ async writeBundle() {
1023
+ await completeBuild();
1024
+ }
1025
+ },
87
1026
  {
88
1027
  name: "tailorkit-preact-package-json",
89
1028
  enforce: "pre",
@@ -103,17 +1042,42 @@ var buildApp = async (options = {}) => {
103
1042
  }
104
1043
  ],
105
1044
  root: loaded.root
1045
+ }).catch(async (error) => {
1046
+ if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1047
+ throw error;
106
1048
  });
107
- await writeFile(
108
- path.join(resolvedOutDir, "tailorkit-upload.json"),
109
- `${JSON.stringify(createTailorKitUploadManifest(), null, 2)}
110
- `,
111
- "utf-8"
112
- );
1049
+ if (options.watch) {
1050
+ if (result && typeof result === "object" && "on" in result) {
1051
+ result.on("event", (event) => {
1052
+ if (event.code === "ERROR") {
1053
+ firstBuildFailed?.(event.error ?? new Error("Initial app build failed."));
1054
+ }
1055
+ });
1056
+ }
1057
+ try {
1058
+ await firstBuild;
1059
+ } catch (error) {
1060
+ if (result && typeof result === "object" && "close" in result) {
1061
+ await result.close();
1062
+ }
1063
+ if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1064
+ throw error;
1065
+ }
1066
+ if (result && "close" in result && serverWatcher && "close" in serverWatcher) {
1067
+ const clientWatcher = result;
1068
+ return {
1069
+ on: clientWatcher.on.bind(clientWatcher),
1070
+ async close() {
1071
+ await clientWatcher.close();
1072
+ if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1073
+ }
1074
+ };
1075
+ }
1076
+ }
113
1077
  return result;
114
1078
  };
115
1079
  function getInstalledPreactVersion(root) {
116
- const require2 = createRequire(path.join(root, "package.json"));
1080
+ const require2 = createRequire2(path7.join(root, "package.json"));
117
1081
  try {
118
1082
  const packageJsonPath = require2.resolve("preact/package.json");
119
1083
  const packageJson = require2(packageJsonPath);
@@ -131,5 +1095,6 @@ function getInstalledPreactVersion(root) {
131
1095
  export {
132
1096
  buildApp,
133
1097
  createTailorKitUploadManifest,
1098
+ generateAppMigrations,
134
1099
  tailorkitUploadManifestSchema
135
1100
  };