@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.
- package/LICENSE.md +201 -0
- package/README.md +184 -0
- package/dist/builder/client-views.d.ts +16 -0
- package/dist/builder/drizzle.d.ts +2 -0
- package/dist/builder/generate-migrations.d.ts +6 -0
- package/dist/builder/index.d.ts +1 -2
- package/dist/builder/instances.d.ts +19 -0
- package/dist/builder/migrations.d.ts +6 -0
- package/dist/builder/schema-check.d.ts +2 -0
- package/dist/builder/server.d.ts +6 -0
- package/dist/builder/upload-manifest.d.ts +13 -3
- package/dist/builder.js +986 -21
- package/dist/chunk-7NOEYJOB.js +197 -0
- package/dist/{chunk-7L6ZZHCK.js → chunk-DEU6RITM.js} +6 -1
- package/dist/chunk-GVB2PAK7.js +28 -0
- package/dist/chunk-IJBQ77AH.js +35 -0
- package/dist/chunk-RHCGWGSG.js +645 -0
- package/dist/client/connection.d.ts +31 -0
- package/dist/client/query-store.d.ts +23 -0
- package/dist/client/reference.d.ts +20 -0
- package/dist/client/session.d.ts +14 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +32 -0
- package/dist/config/config.d.ts +88 -4
- package/dist/config/index.d.ts +0 -1
- package/dist/config/loader.d.ts +0 -1
- package/dist/config-loader.js +2 -2
- package/dist/config.js +1 -1
- package/dist/database/schema.d.ts +78 -0
- package/dist/database/types.d.ts +7 -0
- package/dist/errors.d.ts +6 -0
- package/dist/index.d.ts +2 -72
- package/dist/index.js +81 -76
- package/dist/preact-version.d.ts +0 -1
- package/dist/preact.d.ts +52 -0
- package/dist/protocol.d.ts +74 -0
- package/dist/protocol.js +28 -0
- package/dist/server/builder.d.ts +40 -0
- package/dist/server/bundle.d.ts +17 -0
- package/dist/server/functions.d.ts +89 -0
- package/dist/server/instances.d.ts +16 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +58 -0
- package/dist/views.d.ts +139 -0
- package/package.json +30 -23
- package/LICENSE +0 -674
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/builder/upload-manifest.d.ts.map +0 -1
- package/dist/chunk-AGGRUUWG.js +0 -22
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/loader.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- 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-
|
|
4
|
-
import "./chunk-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
974
|
+
entry,
|
|
65
975
|
fileName: "client",
|
|
66
976
|
formats: ["es"]
|
|
67
977
|
},
|
|
68
|
-
outDir:
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 =
|
|
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
|
};
|