@tailorkit/vite 0.1.0-beta.31 → 0.1.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailorkit/vite",
3
- "version": "0.1.0-beta.31",
3
+ "version": "0.1.0-beta.32",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@tailorkit/app": "0.1.0-beta.31",
46
+ "@tailorkit/app": "0.1.0-beta.32",
47
47
  "cosmiconfig": "^10.0.1",
48
48
  "zod": "^4.6.5"
49
49
  },
@@ -1,19 +0,0 @@
1
- import { z } from "zod";
2
- //#region src/config/config.ts
3
- const logosConfigSchema = z.object({
4
- dark: z.string().min(1).optional(),
5
- light: z.string().min(1).optional()
6
- });
7
- const tailorkitConfigSchema = z.strictObject({
8
- appId: z.string().min(1).optional(),
9
- host: z.string().url(),
10
- logos: logosConfigSchema.optional(),
11
- server: z.strictObject({
12
- entry: z.string().min(1).default("src/server.ts"),
13
- migrations: z.string().min(1).optional()
14
- }).optional()
15
- });
16
- const defineConfig = (config) => config;
17
- const defineTailorKitConfig = defineConfig;
18
- //#endregion
19
- export { defineTailorKitConfig as n, tailorkitConfigSchema as r, defineConfig as t };
@@ -1,73 +0,0 @@
1
- import { z } from "zod";
2
- //#region src/config/config.d.ts
3
- declare const tailorkitConfigSchema: z.ZodObject<{
4
- appId: z.ZodOptional<z.ZodString>;
5
- host: z.ZodString;
6
- logos: z.ZodOptional<z.ZodObject<{
7
- dark: z.ZodOptional<z.ZodString>;
8
- light: z.ZodOptional<z.ZodString>;
9
- }, z.core.$strip>>;
10
- server: z.ZodOptional<z.ZodObject<{
11
- entry: z.ZodDefault<z.ZodString>;
12
- migrations: z.ZodOptional<z.ZodString>;
13
- }, z.core.$strict>>;
14
- }, z.core.$strict>;
15
- /**
16
- * App configuration exported from `tailorkit.config.ts`.
17
- * Relative paths resolve from the app root: the Vite config directory when building.
18
- */
19
- interface TailorKitConfig extends z.input<typeof tailorkitConfigSchema> {
20
- /**
21
- * ID of the app on the TailorKit host, used for deployment and remote preview.
22
- * When omitted, the first deployment creates an app and saves its ID here.
23
- * Remote preview requires an existing app ID.
24
- * @default undefined
25
- */
26
- appId?: string;
27
- /**
28
- * URL of the TailorKit host used for authentication, deployment, remote preview,
29
- * and fetching the host schema for generated types. Required; has no default.
30
- */
31
- host: string;
32
- /**
33
- * Optional app logo assets included in the build and uploaded on deployment.
34
- * Accepts SVG, PNG, or WebP files. Omit to upload no custom logos.
35
- * @default undefined
36
- */
37
- logos?: {
38
- /**
39
- * Path to the app logo for dark mode. Omit to upload no dark-mode logo.
40
- * @default undefined
41
- */
42
- dark?: string;
43
- /**
44
- * Path to the app logo for light mode. Omit to upload no light-mode logo.
45
- * @default undefined
46
- */
47
- light?: string;
48
- };
49
- /**
50
- * Enables the private app backend and its database migrations when present.
51
- * Set to `{}` to use the default paths; omit for a client-only app.
52
- * @default undefined
53
- */
54
- server?: {
55
- /**
56
- * Source entry point for the app's private backend bundle.
57
- * @default "src/server.ts"
58
- */
59
- entry?: string;
60
- /**
61
- * Directory containing Drizzle migrations, used for generation and bundling.
62
- * When omitted, uses `./src/db/migrations` and allows that directory to be absent.
63
- * An explicitly configured directory must exist when building the backend.
64
- * @default "./src/db/migrations"
65
- */
66
- migrations?: string;
67
- };
68
- }
69
- type ResolvedTailorKitConfig = z.output<typeof tailorkitConfigSchema>;
70
- declare const defineConfig: (config: TailorKitConfig) => TailorKitConfig;
71
- declare const defineTailorKitConfig: typeof defineConfig;
72
- //#endregion
73
- export { defineTailorKitConfig as i, TailorKitConfig as n, defineConfig as r, ResolvedTailorKitConfig as t };
@@ -1,438 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import path from "node:path";
3
- import { access, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
- import { z } from "zod";
5
- import { execFile } from "node:child_process";
6
- import { promisify } from "node:util";
7
- import { parseSync } from "vite";
8
- import { tmpdir } from "node:os";
9
- import { existsSync, watch } from "node:fs";
10
- //#region src/builder/upload-manifest.ts
11
- const tailorkitUploadManifestSchema = z.object({
12
- assets: z.object({
13
- client: z.literal("client/client.js"),
14
- server: z.literal("server/server.js").optional(),
15
- logos: z.object({
16
- dark: z.string().regex(/^client\/logo-dark\.(?:png|svg|webp)$/u).optional(),
17
- light: z.string().regex(/^client\/logo-light\.(?:png|svg|webp)$/u).optional()
18
- }).optional()
19
- }),
20
- views: z.array(z.object({
21
- slot: z.string().min(1).max(255),
22
- path: z.string().startsWith("/").max(1024),
23
- instances: z.literal(true).optional(),
24
- disabled: z.literal(true).optional()
25
- })).max(1e3).optional(),
26
- version: z.literal(1)
27
- });
28
- const createTailorKitUploadManifest = (logos, server = false, views) => tailorkitUploadManifestSchema.parse({
29
- assets: {
30
- client: "client/client.js",
31
- ...server ? { server: "server/server.js" } : {},
32
- ...logos && Object.keys(logos).length > 0 ? { logos } : {}
33
- },
34
- views,
35
- version: 1
36
- });
37
- //#endregion
38
- //#region src/builder/database-paths.ts
39
- function appDatabasePaths(root, migrations) {
40
- return {
41
- schema: path.join(root, "src/db/schema.ts"),
42
- migrations: path.resolve(root, migrations ?? "./src/db/migrations")
43
- };
44
- }
45
- //#endregion
46
- //#region src/builder/drizzle.ts
47
- /** Use the app's Drizzle version with explicit flags; no Drizzle config file is needed. */
48
- async function drizzleGenerateArguments(root, output, name) {
49
- const require = createRequire(path.join(root, "package.json"));
50
- for (const directory of require.resolve.paths("drizzle-kit") ?? []) {
51
- const cli = path.join(directory, "drizzle-kit/bin.cjs");
52
- if (await access(cli).then(() => true).catch(() => false)) return [
53
- "--preserve-symlinks-main",
54
- cli,
55
- "generate",
56
- "--dialect",
57
- "sqlite",
58
- "--schema",
59
- appDatabasePaths(root).schema,
60
- "--out",
61
- output,
62
- ...name === void 0 ? [] : ["--name", name]
63
- ];
64
- }
65
- throw new Error("Install drizzle-kit and drizzle-orm as dev dependencies, then run tailorkit db generate.");
66
- }
67
- //#endregion
68
- //#region src/builder/schema-check.ts
69
- const run = promisify(execFile);
70
- async function snapshot(directory) {
71
- const latest = (await readdir(directory, { withFileTypes: true }).catch((error) => {
72
- if (error.code === "ENOENT") return [];
73
- throw error;
74
- })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().at(-1);
75
- if (!latest) return;
76
- const filename = path.join(directory, latest, "snapshot.json");
77
- const value = JSON.parse(await readFile(filename, "utf8"));
78
- if (value.version !== "7" || value.dialect !== "sqlite" || !Array.isArray(value.ddl)) throw new Error(`Unsupported app schema snapshot: ${filename}. Use Drizzle Kit v1 SQLite migrations.`);
79
- return value;
80
- }
81
- function canonical(value) {
82
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
83
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
84
- return JSON.stringify(value);
85
- }
86
- /** Generate a fresh snapshot with Drizzle in disposable storage, leaving committed history unchanged. */
87
- async function checkAppSchema(root, migrationsDirectory) {
88
- const schema = appDatabasePaths(root).schema;
89
- try {
90
- await access(schema);
91
- } catch (error) {
92
- if (error.code === "ENOENT") return;
93
- throw error;
94
- }
95
- const temporary = await mkdtemp(path.join(tmpdir(), "tailorkit-schema-check-"));
96
- try {
97
- const { stdout } = await run(process.execPath, await drizzleGenerateArguments(root, temporary), {
98
- cwd: root,
99
- timeout: 3e4,
100
- maxBuffer: 1048576,
101
- env: {
102
- ...process.env,
103
- CI: "true"
104
- }
105
- }).catch((error) => {
106
- throw new Error(`Could not check the app database schema with Drizzle Kit. Check ${path.relative(root, schema)} and your drizzle-kit/drizzle-orm dependencies.`, { cause: error });
107
- });
108
- const current = await snapshot(temporary);
109
- if (!current && !stdout.includes("No schema changes")) throw new Error(`Drizzle Kit did not produce a schema snapshot. Check ${path.relative(root, schema)} and run tailorkit db generate.`);
110
- const saved = await snapshot(migrationsDirectory);
111
- const expected = (current?.ddl ?? []).map(canonical).sort();
112
- const actual = (saved?.ddl ?? []).map(canonical).sort();
113
- if (canonical(expected) !== canonical(actual)) throw new Error(`App database migrations are missing or out of sync with ${path.relative(root, schema)}. Run tailorkit db generate, commit the generated files in ${path.relative(root, migrationsDirectory)}, then build again.`);
114
- } finally {
115
- await rm(temporary, {
116
- recursive: true,
117
- force: true
118
- });
119
- }
120
- }
121
- //#endregion
122
- //#region src/builder/define-view.ts
123
- const clientImports = /* @__PURE__ */ new Set([
124
- "tailorkit/client",
125
- "tailorkit/app",
126
- "tailorkit/app/client",
127
- "@tailorkit/app/client",
128
- "@tailorkit/app"
129
- ]);
130
- function createScope(parent, functionScope = false) {
131
- return {
132
- parent,
133
- functionScope,
134
- bindings: /* @__PURE__ */ new Map()
135
- };
136
- }
137
- function bindingNames(node) {
138
- if (!node) return [];
139
- switch (node.type) {
140
- case "Identifier": return [node.name];
141
- case "RestElement": return bindingNames(node.argument);
142
- case "AssignmentPattern": return bindingNames(node.left);
143
- case "ArrayPattern": return node.elements.flatMap(bindingNames);
144
- case "ObjectPattern": return node.properties.flatMap((property) => bindingNames(property.type === "Property" ? property.value : property.argument));
145
- case "TSParameterProperty": return bindingNames(node.parameter);
146
- default: return [];
147
- }
148
- }
149
- function bind(node, scope) {
150
- for (const name of bindingNames(node)) scope.bindings.set(name, "local");
151
- }
152
- function classScope(node, parent) {
153
- if (node.type === "ClassDeclaration") bind(node.id, parent);
154
- const scope = createScope(parent);
155
- bind(node.id, scope);
156
- return scope;
157
- }
158
- function resolve(scope, name) {
159
- for (let current = scope; current; current = current.parent) if (current.bindings.has(name)) return current.bindings.get(name);
160
- }
161
- function childNodes(node) {
162
- return Object.values(node).flatMap((value) => {
163
- return (Array.isArray(value) ? value : [value]).filter((child) => !!child && typeof child === "object" && "type" in child);
164
- });
165
- }
166
- function bindImports(node, scope) {
167
- const sdk = node.importKind !== "type" && clientImports.has(String(node.source.value));
168
- for (const specifier of node.specifiers) {
169
- let binding = "local";
170
- if (sdk && specifier.type === "ImportNamespaceSpecifier") binding = "namespace";
171
- else if (sdk && specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && specifier.imported.name === "defineView") binding = "view";
172
- scope.bindings.set(specifier.local.name, binding);
173
- }
174
- }
175
- /** Resolve SDK import bindings, including declarations hoisted past a call. */
176
- function callScopes(program) {
177
- const scopes = /* @__PURE__ */ new WeakMap();
178
- function visitFunction(node, scope) {
179
- if (node.type === "FunctionDeclaration") bind(node.id, scope);
180
- const parameters = createScope(scope, true);
181
- if (node.type !== "ArrowFunctionExpression") bind(node.id, parameters);
182
- for (const parameter of node.params) bind(parameter, parameters);
183
- for (const parameter of node.params) visit(parameter, parameters);
184
- if (node.body) visit(node.body, createScope(parameters, true));
185
- }
186
- function bindVariables(node, scope) {
187
- let target = scope;
188
- if (node.kind === "var") while (!target.functionScope && target.parent) target = target.parent;
189
- for (const declaration of node.declarations) bind(declaration.id, target);
190
- }
191
- function visit(node, scope) {
192
- scopes.set(node, scope);
193
- switch (node.type) {
194
- case "ImportDeclaration":
195
- bindImports(node, scope);
196
- return;
197
- case "VariableDeclaration":
198
- bindVariables(node, scope);
199
- break;
200
- case "FunctionDeclaration":
201
- case "FunctionExpression":
202
- case "ArrowFunctionExpression":
203
- visitFunction(node, scope);
204
- return;
205
- case "ClassDeclaration":
206
- case "ClassExpression":
207
- scope = classScope(node, scope);
208
- break;
209
- case "CatchClause":
210
- scope = createScope(scope);
211
- bind(node.param, scope);
212
- break;
213
- case "SwitchStatement":
214
- visit(node.discriminant, scope);
215
- scope = createScope(scope);
216
- for (const branch of node.cases) visit(branch, scope);
217
- return;
218
- case "BlockStatement":
219
- case "ForStatement":
220
- case "ForInStatement":
221
- case "ForOfStatement":
222
- scope = createScope(scope);
223
- break;
224
- case "StaticBlock":
225
- case "TSModuleBlock":
226
- scope = createScope(scope, true);
227
- break;
228
- case "TSEnumDeclaration":
229
- case "TSModuleDeclaration": bind(node.id, scope);
230
- }
231
- for (const child of childNodes(node)) visit(child, scope);
232
- }
233
- visit(program, createScope(void 0, true));
234
- return scopes;
235
- }
236
- /** Recognize the same SDK calls in route inference and instance extraction. */
237
- function createDefineViewMatcher(program) {
238
- const scopes = callScopes(program);
239
- return (node) => {
240
- if (node.type !== "CallExpression") return false;
241
- const { callee } = node;
242
- const scope = scopes.get(node);
243
- if (callee.type === "Identifier") return resolve(scope, callee.name) === "view";
244
- if (callee.type !== "MemberExpression" || callee.object.type !== "Identifier" || resolve(scope, callee.object.name) !== "namespace") return false;
245
- return callee.computed ? callee.property.type === "Literal" && callee.property.value === "defineView" : callee.property.type === "Identifier" && callee.property.name === "defineView";
246
- };
247
- }
248
- //#endregion
249
- //#region src/builder/file-routes.ts
250
- const clientRuntimeImport = "tailorkit:file-client-runtime";
251
- function fileViewLocation(filename, root) {
252
- const relative = path.relative(path.join(root, "src/slots"), filename).split(path.sep);
253
- if (relative.length < 2 || relative[0] === ".." || !filename.endsWith(".view.tsx")) return;
254
- const [slot, ...routeSegments] = relative;
255
- if (!slot) return;
256
- return {
257
- slot,
258
- path: routePath(routeSegments.join("/").slice(0, -9), filename)
259
- };
260
- }
261
- function routePath(name, filename) {
262
- const segments = name.split(/[./]/u);
263
- if (segments.some((segment) => !segment || segment === "..")) throw new Error(`${filename} has an empty route segment. Use route.nested-route.view.tsx.`);
264
- return `/${segments.join("/")}`;
265
- }
266
- /** Fill missing defineView options before instance extraction, on both build sides. */
267
- function inferFileView(source, filename, root) {
268
- const location = fileViewLocation(filename, root);
269
- if (!location) return source;
270
- const { slot, path: viewPath } = location;
271
- const parsed = parseSync(filename, source);
272
- if (parsed.errors.length) throw new Error(`${filename}: ${parsed.errors[0]?.message}`);
273
- const isDefineViewCall = createDefineViewMatcher(parsed.program);
274
- const edits = [];
275
- function visit(node) {
276
- if (isDefineViewCall(node)) {
277
- const options = node.arguments[0];
278
- if (options?.type !== "ObjectExpression") throw new Error(`${filename}: file views require defineView with an inline options object.`);
279
- const properties = new Set(options.properties.flatMap((property) => {
280
- if (property.type !== "Property" || property.computed) return [];
281
- if (property.key.type === "Identifier") return [property.key.name];
282
- if (property.key.type === "Literal") return [String(property.key.value)];
283
- return [];
284
- }));
285
- const defaults = [!properties.has("slot") ? `slot: ${JSON.stringify(slot)},` : "", !properties.has("view") ? `view: ${JSON.stringify(viewPath)},` : ""].join("");
286
- if (defaults) edits.push({
287
- position: options.start + 1,
288
- text: defaults
289
- });
290
- }
291
- for (const value of Object.values(node)) {
292
- const children = Array.isArray(value) ? value : [value];
293
- for (const child of children) if (child && typeof child === "object" && "type" in child) visit(child);
294
- }
295
- }
296
- visit(parsed.program);
297
- for (const edit of edits.toSorted((a, b) => b.position - a.position)) source = source.slice(0, edit.position) + edit.text + source.slice(edit.position);
298
- return source;
299
- }
300
- async function discoverFileRoutes(root) {
301
- const views = [];
302
- const roots = [];
303
- const layouts = [];
304
- const slotsDir = path.join(root, "src/slots");
305
- async function scan(directory, slot, prefix = "") {
306
- const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
307
- if (error.code === "ENOENT") return [];
308
- throw error;
309
- });
310
- for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
311
- const filename = path.join(directory, entry.name);
312
- if (entry.isDirectory()) await scan(filename, slot ?? entry.name, slot ? `${prefix}${entry.name}/` : "");
313
- else if (entry.isFile() && slot) {
314
- if (entry.name.endsWith(".view.tsx")) {
315
- fileViewLocation(filename, root);
316
- views.push({
317
- filename,
318
- slot
319
- });
320
- } else if (entry.name === "root.tsx" && !prefix) roots.push({
321
- filename,
322
- slot
323
- });
324
- else if (entry.name === "layout.tsx" || entry.name.endsWith(".layout.tsx")) {
325
- const name = entry.name === "layout.tsx" ? prefix.replace(/\/$/u, "") : prefix + entry.name.slice(0, -11);
326
- layouts.push({
327
- filename,
328
- slot,
329
- path: name ? routePath(name, filename) : "/"
330
- });
331
- }
332
- }
333
- }
334
- }
335
- await scan(slotsDir);
336
- const rootFile = path.join(root, "src/root.tsx");
337
- return {
338
- root: existsSync(rootFile) ? { filename: rootFile } : void 0,
339
- roots,
340
- layouts,
341
- views
342
- };
343
- }
344
- /** Root, layout, view and binding sources to check before deployment. */
345
- async function getClientSourceFiles(root) {
346
- const discovered = await discoverFileRoutes(root);
347
- const bindings = path.join(root, "src/tailorkit.gen.ts");
348
- return [
349
- ...discovered.root ? [discovered.root.filename] : [],
350
- ...discovered.roots.map((module) => module.filename),
351
- ...discovered.layouts.map((module) => module.filename),
352
- ...discovered.views.map((module) => module.filename),
353
- ...existsSync(bindings) ? [bindings] : []
354
- ];
355
- }
356
- async function fileClientSource(root) {
357
- const discovered = await discoverFileRoutes(root);
358
- const imports = [`import { createFileClient } from ${JSON.stringify(clientRuntimeImport)};`];
359
- let index = 0;
360
- function module(entry) {
361
- const binding = `module${index++}`;
362
- imports.push(`import ${binding} from ${JSON.stringify(entry.filename)};`);
363
- return `{ filename: ${JSON.stringify(path.relative(root, entry.filename))}, definition: ${binding}`;
364
- }
365
- const rootModule = discovered.root ? `${module(discovered.root)} }` : "undefined";
366
- const roots = discovered.roots.map((entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)} }`);
367
- const layouts = discovered.layouts.map((entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)}, path: ${JSON.stringify(entry.path)} }`);
368
- const views = discovered.views.map((entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)} }`);
369
- return `${imports.join("\n")}\nexport default createFileClient({ root: ${rootModule}, roots: [${roots.join(",")}], layouts: [${layouts.join(",")}], views: [${views.join(",")}] });`;
370
- }
371
- /** A generated entry in build output lets the bundler track imports normally.
372
- * Directory watching also covers routes that did not exist at the first build. */
373
- async function createFileRouteEntry(root, output, watching) {
374
- let source = await fileClientSource(root);
375
- const temporaryRoot = path.join(output, "tmp");
376
- await mkdir(temporaryRoot, { recursive: true });
377
- const directory = await mkdtemp(path.join(temporaryRoot, "client-"));
378
- const entry = path.join(directory, "entry.ts");
379
- await writeFile(entry, source);
380
- let pending = Promise.resolve();
381
- let closed = false;
382
- let timer;
383
- async function refresh() {
384
- if (closed) return;
385
- let next;
386
- try {
387
- next = await fileClientSource(root);
388
- } catch (error) {
389
- next = `throw new Error(${JSON.stringify(error instanceof Error ? error.message : String(error))});`;
390
- }
391
- if (next === source) return;
392
- source = next;
393
- await writeFile(entry, next);
394
- }
395
- const watcher = watching ? watch(path.join(root, "src"), { recursive: true }, () => {
396
- if (timer) clearTimeout(timer);
397
- timer = setTimeout(() => {
398
- pending = pending.then(refresh);
399
- }, 25);
400
- }) : void 0;
401
- return {
402
- entry,
403
- async close() {
404
- closed = true;
405
- if (timer) clearTimeout(timer);
406
- watcher?.close();
407
- try {
408
- await pending;
409
- } finally {
410
- await rm(directory, {
411
- recursive: true,
412
- force: true
413
- });
414
- }
415
- }
416
- };
417
- }
418
- function fileRoutesPlugin(root) {
419
- return {
420
- name: "tailorkit-file-routes",
421
- enforce: "pre",
422
- async resolveId(id) {
423
- if (id !== clientRuntimeImport) return;
424
- const importer = path.join(root(), "src/root.tsx");
425
- return await this.resolve("tailorkit/client", importer, { skipSelf: true }) ?? this.resolve("@tailorkit/app/client", importer, { skipSelf: true });
426
- },
427
- transform(source, filename) {
428
- if (!filename.endsWith(".view.tsx")) return;
429
- const code = inferFileView(source, filename, root());
430
- return code === source ? void 0 : {
431
- code,
432
- map: null
433
- };
434
- }
435
- };
436
- }
437
- //#endregion
438
- export { createDefineViewMatcher as a, appDatabasePaths as c, inferFileView as i, createTailorKitUploadManifest as l, fileRoutesPlugin as n, checkAppSchema as o, getClientSourceFiles as r, drizzleGenerateArguments as s, createFileRouteEntry as t, tailorkitUploadManifestSchema as u };