zitejs 0.9.137 → 0.10.0-start.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/cjs/cli.js +10 -3
- package/dist/cjs/dev/index.js +3 -0
- package/dist/cjs/start/describeBuilt.d.ts +3 -0
- package/dist/cjs/start/describeBuilt.js +104 -0
- package/dist/cjs/start/entries.d.ts +11 -0
- package/dist/cjs/start/entries.js +64 -0
- package/dist/cjs/sync/lib.js +4 -2
- package/dist/esm/cli.js +10 -3
- package/dist/esm/dev/index.js +3 -0
- package/dist/esm/start/describe.d.ts +40 -0
- package/dist/esm/start/describe.js +122 -0
- package/dist/esm/start/describeBuilt.d.ts +3 -0
- package/dist/esm/start/describeBuilt.js +64 -0
- package/dist/esm/start/entries.d.ts +11 -0
- package/dist/esm/start/entries.js +56 -0
- package/dist/esm/start/host.d.ts +3 -0
- package/dist/esm/start/host.js +4 -0
- package/dist/esm/start/index.d.ts +64 -0
- package/dist/esm/start/index.js +123 -0
- package/dist/esm/start/instance.d.ts +12 -0
- package/dist/esm/start/instance.js +89 -0
- package/dist/esm/start/router.d.ts +17 -0
- package/dist/esm/start/router.js +34 -0
- package/dist/esm/start/scope.d.ts +15 -0
- package/dist/esm/start/scope.js +58 -0
- package/dist/esm/start/server.d.ts +47 -0
- package/dist/esm/start/server.js +81 -0
- package/dist/esm/start/types.d.ts +32 -0
- package/dist/esm/start/types.js +1 -0
- package/dist/esm/start/vite.d.ts +9 -0
- package/dist/esm/start/vite.js +114 -0
- package/dist/esm/sync/lib.js +4 -2
- package/package.json +53 -2
- package/dist/cjs/meta/index.d.ts +0 -14
- package/dist/cjs/meta/index.js +0 -12
- package/dist/cjs/notifications/index.d.ts +0 -25
- package/dist/cjs/notifications/index.js +0 -12
- package/dist/esm/meta/index.d.ts +0 -14
- package/dist/esm/meta/index.js +0 -8
- package/dist/esm/notifications/index.d.ts +0 -25
- package/dist/esm/notifications/index.js +0 -8
package/README.md
CHANGED
|
@@ -23,10 +23,15 @@ const result = await zite.sql({
|
|
|
23
23
|
query: 'SELECT "id", "email" FROM "ziteUsers"',
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
// Define API endpoints
|
|
26
|
+
// Define API endpoints. step.run saves each result as it completes, so a
|
|
27
|
+
// failed run resumed from run history skips the steps that already ran.
|
|
27
28
|
import { createEndpoint, ZiteError } from 'zitejs/api';
|
|
28
29
|
export default createEndpoint({
|
|
29
|
-
execute: async ({ input }) => {
|
|
30
|
+
execute: async ({ input, step }) => {
|
|
31
|
+
const order = await step.run('load-order', () => loadOrder(input.id));
|
|
32
|
+
await step.run('charge', () => charge(order));
|
|
33
|
+
return { charged: true };
|
|
34
|
+
},
|
|
30
35
|
});
|
|
31
36
|
|
|
32
37
|
// Authentication
|
package/dist/cjs/cli.js
CHANGED
|
@@ -35,8 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
const dotenv_1 = require("dotenv");
|
|
38
|
-
|
|
39
|
-
(0, dotenv_1.config)({ path: '.env' });
|
|
38
|
+
// Quiet: dotenv 17 prints tips to stdout, which commands like `describe` own.
|
|
39
|
+
(0, dotenv_1.config)({ path: '.env.local', quiet: true });
|
|
40
|
+
(0, dotenv_1.config)({ path: '.env', quiet: true });
|
|
40
41
|
async function main() {
|
|
41
42
|
const command = process.argv[2];
|
|
42
43
|
switch (command) {
|
|
@@ -60,6 +61,11 @@ async function main() {
|
|
|
60
61
|
await runCheck();
|
|
61
62
|
break;
|
|
62
63
|
}
|
|
64
|
+
case 'describe': {
|
|
65
|
+
const { runDescribe } = await Promise.resolve().then(() => __importStar(require('./start/describeBuilt.js')));
|
|
66
|
+
await runDescribe();
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
63
69
|
case 'bundle': {
|
|
64
70
|
const { runBundle } = await Promise.resolve().then(() => __importStar(require('./bundle/index.js')));
|
|
65
71
|
await runBundle();
|
|
@@ -68,7 +74,7 @@ async function main() {
|
|
|
68
74
|
default:
|
|
69
75
|
console.error(command
|
|
70
76
|
? `Unknown command: ${command}`
|
|
71
|
-
: 'Usage: zitejs <sync|dev|generate|check|bundle>');
|
|
77
|
+
: 'Usage: zitejs <sync|dev|generate|check|bundle|describe>');
|
|
72
78
|
console.error('');
|
|
73
79
|
console.error('Commands:');
|
|
74
80
|
console.error(' sync Generate .zite/db.ts from database schema (single-app)');
|
|
@@ -76,6 +82,7 @@ async function main() {
|
|
|
76
82
|
console.error(' generate Regenerate every app and automation .zite/ (monorepo)');
|
|
77
83
|
console.error(' check Run tsc --noEmit (and vite build where there is one) for every app and automation');
|
|
78
84
|
console.error(' bundle Bundle endpoints (src/api/*.ts, or an automation\'s src/index.ts) for cloudflare-lambda');
|
|
85
|
+
console.error(' describe Print what a built TanStack Start app declares: functions, routes, access');
|
|
79
86
|
process.exit(1);
|
|
80
87
|
}
|
|
81
88
|
}
|
package/dist/cjs/dev/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const fs_1 = require("fs");
|
|
|
7
7
|
const fs_2 = require("fs");
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const sourceRoots_js_1 = require("../sourceRoots.js");
|
|
10
|
+
const entries_js_1 = require("../start/entries.js");
|
|
10
11
|
const index_js_1 = require("../sync/index.js");
|
|
11
12
|
const lib_js_1 = require("../sync/lib.js");
|
|
12
13
|
const rules_js_1 = require("../integrations/rules.js");
|
|
@@ -205,6 +206,7 @@ async function runGenerate() {
|
|
|
205
206
|
regenerateAppTypedWrappers(app);
|
|
206
207
|
regenerateAppAirtableSdk(app);
|
|
207
208
|
regenerateAppIntegrationsIndex(app);
|
|
209
|
+
(0, entries_js_1.regenerateStartEntries)(app);
|
|
208
210
|
}
|
|
209
211
|
console.log("Done!");
|
|
210
212
|
}
|
|
@@ -226,6 +228,7 @@ async function runDev() {
|
|
|
226
228
|
regenerateAppApiTs(app);
|
|
227
229
|
regenerateAppTypedWrappers(app);
|
|
228
230
|
regenerateAppIntegrationsIndex(app);
|
|
231
|
+
(0, entries_js_1.regenerateStartEntries)(app);
|
|
229
232
|
}
|
|
230
233
|
// 3. Watch each dir's src/api/ for endpoint changes
|
|
231
234
|
let watchingAny = false;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.describeBuiltApp = describeBuiltApp;
|
|
40
|
+
exports.runDescribe = runDescribe;
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const node_fs_1 = require("node:fs");
|
|
43
|
+
const node_net_1 = require("node:net");
|
|
44
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
45
|
+
const node_url_1 = require("node:url");
|
|
46
|
+
// Only the host can set x-zite-* in production: the front strips them from public traffic.
|
|
47
|
+
const DESCRIBE_HEADERS = { "x-zite-invoke": "describe" };
|
|
48
|
+
const freePort = () => new Promise((resolve, reject) => {
|
|
49
|
+
const server = (0, node_net_1.createServer)();
|
|
50
|
+
server.once("error", reject);
|
|
51
|
+
server.listen(0, () => {
|
|
52
|
+
const { port } = server.address();
|
|
53
|
+
server.close(() => resolve(port));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// A Workers build runs where it will run: workerd, through the app's own
|
|
57
|
+
// `vite preview`, so it uses the same Cloudflare plugin and config as the build.
|
|
58
|
+
async function describeInWorkerd(appDir) {
|
|
59
|
+
const port = await freePort();
|
|
60
|
+
// Its own process group, so stopping it also stops vite and workerd under npx.
|
|
61
|
+
const preview = (0, node_child_process_1.spawn)("npx", ["--no-install", "vite", "preview", "--port", String(port), "--strictPort"], {
|
|
62
|
+
cwd: appDir,
|
|
63
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
64
|
+
detached: true,
|
|
65
|
+
});
|
|
66
|
+
let stderr = "";
|
|
67
|
+
preview.stderr.on("data", (chunk) => (stderr += chunk));
|
|
68
|
+
try {
|
|
69
|
+
const deadline = Date.now() + 60_000;
|
|
70
|
+
while (true) {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(`http://localhost:${port}/`, { headers: DESCRIBE_HEADERS });
|
|
73
|
+
// Right after start, preview can answer before the Worker does; only the
|
|
74
|
+
// app's own describe answer is JSON.
|
|
75
|
+
if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
|
|
76
|
+
return await response.json();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
if (preview.exitCode !== null || Date.now() > deadline) {
|
|
81
|
+
throw new Error(`vite preview didn't serve the app: ${stderr.trim().slice(-500)}`);
|
|
82
|
+
}
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
try {
|
|
88
|
+
process.kill(-preview.pid, "SIGTERM");
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Describes a built Start app from its `dist/server`: a Workers build in workerd, a Node build in-process. */
|
|
94
|
+
async function describeBuiltApp(appDir) {
|
|
95
|
+
const serverDir = node_path_1.default.join(appDir, "dist/server");
|
|
96
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(serverDir, "wrangler.json")))
|
|
97
|
+
return describeInWorkerd(appDir);
|
|
98
|
+
const { default: server } = await Promise.resolve(`${(0, node_url_1.pathToFileURL)(node_path_1.default.join(serverDir, "server.js")).href}`).then(s => __importStar(require(s)));
|
|
99
|
+
return (await server.fetch(new Request("http://describe.local/", { headers: DESCRIBE_HEADERS }))).json();
|
|
100
|
+
}
|
|
101
|
+
async function runDescribe() {
|
|
102
|
+
const appDir = node_path_1.default.resolve(process.argv[3] ?? ".");
|
|
103
|
+
process.stdout.write(`${JSON.stringify(await describeBuiltApp(appDir), null, 2)}\n`);
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SourceDir } from "../sourceRoots.js";
|
|
2
|
+
export declare const START_SERVER_ENTRY = ".zite/server.ts";
|
|
3
|
+
export declare const START_INSTANCE_ENTRY = ".zite/start.ts";
|
|
4
|
+
/** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
|
|
5
|
+
export declare const renderStartServerEntry: () => string;
|
|
6
|
+
/** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
|
|
7
|
+
export declare const renderStartInstance: (hasAppStart: boolean) => string;
|
|
8
|
+
export declare const hasAppStart: (appDir: string) => boolean;
|
|
9
|
+
/** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
|
|
10
|
+
export declare const writeIfChanged: (appDir: string, file: string, content: string) => boolean;
|
|
11
|
+
export declare function regenerateStartEntries(app: SourceDir): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeIfChanged = exports.hasAppStart = exports.renderStartInstance = exports.renderStartServerEntry = exports.START_INSTANCE_ENTRY = exports.START_SERVER_ENTRY = void 0;
|
|
4
|
+
exports.regenerateStartEntries = regenerateStartEntries;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
exports.START_SERVER_ENTRY = ".zite/server.ts";
|
|
8
|
+
exports.START_INSTANCE_ENTRY = ".zite/start.ts";
|
|
9
|
+
const APP_START_FILES = ["src/start.ts", "src/start.tsx"];
|
|
10
|
+
/** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
|
|
11
|
+
const renderStartServerEntry = () => `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
12
|
+
import handler, { createServerEntry } from '@tanstack/react-start/server-entry';
|
|
13
|
+
import { createZiteFetch } from 'zitejs/start/server';
|
|
14
|
+
import { getRouter } from '../src/router';
|
|
15
|
+
|
|
16
|
+
export default createServerEntry({ fetch: createZiteFetch({ handler, getRouter }) });
|
|
17
|
+
`;
|
|
18
|
+
exports.renderStartServerEntry = renderStartServerEntry;
|
|
19
|
+
/** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
|
|
20
|
+
const renderStartInstance = (hasAppStart) => hasAppStart
|
|
21
|
+
? `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
22
|
+
import { createZiteStart } from 'zitejs/start/instance';
|
|
23
|
+
import { startInstance as app } from '../src/start';
|
|
24
|
+
|
|
25
|
+
export const startInstance = createZiteStart(app);
|
|
26
|
+
`
|
|
27
|
+
: `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
28
|
+
import { createZiteStart } from 'zitejs/start/instance';
|
|
29
|
+
|
|
30
|
+
export const startInstance = createZiteStart();
|
|
31
|
+
`;
|
|
32
|
+
exports.renderStartInstance = renderStartInstance;
|
|
33
|
+
const hasAppStart = (appDir) => APP_START_FILES.some((file) => (0, node_fs_1.existsSync)((0, node_path_1.join)(appDir, file)));
|
|
34
|
+
exports.hasAppStart = hasAppStart;
|
|
35
|
+
/** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
|
|
36
|
+
const writeIfChanged = (appDir, file, content) => {
|
|
37
|
+
const target = (0, node_path_1.join)(appDir, file);
|
|
38
|
+
if ((0, node_fs_1.existsSync)(target) && (0, node_fs_1.readFileSync)(target, "utf-8") === content)
|
|
39
|
+
return false;
|
|
40
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true });
|
|
41
|
+
(0, node_fs_1.writeFileSync)(target, content);
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
exports.writeIfChanged = writeIfChanged;
|
|
45
|
+
const isStartApp = (app) => {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(app.path, "zite.config.json"), "utf-8"))?.runtime === "start";
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function regenerateStartEntries(app) {
|
|
54
|
+
if (!isStartApp(app))
|
|
55
|
+
return;
|
|
56
|
+
const entries = [
|
|
57
|
+
[exports.START_SERVER_ENTRY, (0, exports.renderStartServerEntry)()],
|
|
58
|
+
[exports.START_INSTANCE_ENTRY, (0, exports.renderStartInstance)((0, exports.hasAppStart)(app.path))],
|
|
59
|
+
];
|
|
60
|
+
for (const [file, content] of entries) {
|
|
61
|
+
if ((0, exports.writeIfChanged)(app.path, file, content))
|
|
62
|
+
console.log(`Wrote ${(0, node_path_1.join)(app.path, file)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -1232,10 +1232,10 @@ function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1232
1232
|
"// Auto-generated type-narrowing wrapper. Do not edit manually.",
|
|
1233
1233
|
"// Re-exports createEndpoint with context.user typed to the app User.",
|
|
1234
1234
|
"",
|
|
1235
|
-
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStreamInterface, ZiteTrigger, ZiteTriggerContext, ZiteWebhook } from 'zitejs/backend/base';",
|
|
1235
|
+
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStep, ZiteStreamInterface, ZiteTrigger, ZiteTriggerContext, ZiteWebhook } from 'zitejs/backend/base';",
|
|
1236
1236
|
"import type { User } from 'zitejs/auth';",
|
|
1237
1237
|
"",
|
|
1238
|
-
"export type { ZiteErrorCode, ZiteSchedule, ZiteScheduledContext, ZiteStreamInterface, ZiteTrigger, ZiteWebhook };",
|
|
1238
|
+
"export type { ZiteErrorCode, ZiteSchedule, ZiteScheduledContext, ZiteStep, ZiteStreamInterface, ZiteTrigger, ZiteWebhook };",
|
|
1239
1239
|
// A trigger's registration exports are typed against these, and they
|
|
1240
1240
|
// only reach app code through this file.
|
|
1241
1241
|
"export type { ZiteTriggerContext, ZiteTriggerDeliveryInfo, ZiteTriggerDeliveryRun, ZiteTriggerRun, ZiteTriggerSubscribe, ZiteTriggerSubscribeInput, ZiteTriggerSubscribeResult, ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput } from 'zitejs/backend/base';",
|
|
@@ -1331,6 +1331,8 @@ function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1331
1331
|
" execute: (",
|
|
1332
1332
|
" params: {",
|
|
1333
1333
|
" input: TInput;",
|
|
1334
|
+
" /** Durable steps: step.run(name, fn) runs fn once per logical run — its result is saved before anything after it executes, and a resumed run replays completed steps from their saved results. Provided by the platform; never define a local stand-in. */",
|
|
1335
|
+
" step: ZiteStep;",
|
|
1334
1336
|
" context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;",
|
|
1335
1337
|
// Mirrors the base module: a trigger's run gets `trigger`.
|
|
1336
1338
|
" } & (TStream extends true ? { stream: ZiteStreamInterface } : {}) & (TTrigger extends ZiteTrigger ? { trigger?: ZiteTriggerContext<TTrigger> } : {}),",
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
|
-
|
|
4
|
-
config({ path: '.env' });
|
|
3
|
+
// Quiet: dotenv 17 prints tips to stdout, which commands like `describe` own.
|
|
4
|
+
config({ path: '.env.local', quiet: true });
|
|
5
|
+
config({ path: '.env', quiet: true });
|
|
5
6
|
async function main() {
|
|
6
7
|
const command = process.argv[2];
|
|
7
8
|
switch (command) {
|
|
@@ -25,6 +26,11 @@ async function main() {
|
|
|
25
26
|
await runCheck();
|
|
26
27
|
break;
|
|
27
28
|
}
|
|
29
|
+
case 'describe': {
|
|
30
|
+
const { runDescribe } = await import('./start/describeBuilt.js');
|
|
31
|
+
await runDescribe();
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
28
34
|
case 'bundle': {
|
|
29
35
|
const { runBundle } = await import('./bundle/index.js');
|
|
30
36
|
await runBundle();
|
|
@@ -33,7 +39,7 @@ async function main() {
|
|
|
33
39
|
default:
|
|
34
40
|
console.error(command
|
|
35
41
|
? `Unknown command: ${command}`
|
|
36
|
-
: 'Usage: zitejs <sync|dev|generate|check|bundle>');
|
|
42
|
+
: 'Usage: zitejs <sync|dev|generate|check|bundle|describe>');
|
|
37
43
|
console.error('');
|
|
38
44
|
console.error('Commands:');
|
|
39
45
|
console.error(' sync Generate .zite/db.ts from database schema (single-app)');
|
|
@@ -41,6 +47,7 @@ async function main() {
|
|
|
41
47
|
console.error(' generate Regenerate every app and automation .zite/ (monorepo)');
|
|
42
48
|
console.error(' check Run tsc --noEmit (and vite build where there is one) for every app and automation');
|
|
43
49
|
console.error(' bundle Bundle endpoints (src/api/*.ts, or an automation\'s src/index.ts) for cloudflare-lambda');
|
|
50
|
+
console.error(' describe Print what a built TanStack Start app declares: functions, routes, access');
|
|
44
51
|
process.exit(1);
|
|
45
52
|
}
|
|
46
53
|
}
|
package/dist/esm/dev/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { watch } from "fs";
|
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from "fs";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
4
|
import { findWorkspaceRoot, listSourceDirs, } from "../sourceRoots.js";
|
|
5
|
+
import { regenerateStartEntries } from "../start/entries.js";
|
|
5
6
|
import { runSync } from "../sync/index.js";
|
|
6
7
|
import { generateDbTs, generateApiTs, generateBackendWrapperTs, generateAirtableTs, findEmailIntegrationId, generateEmailSdk, generateIntegrationsIndex, } from "../sync/lib.js";
|
|
7
8
|
import { gettableIntegrations, } from "../integrations/rules.js";
|
|
@@ -200,6 +201,7 @@ export async function runGenerate() {
|
|
|
200
201
|
regenerateAppTypedWrappers(app);
|
|
201
202
|
regenerateAppAirtableSdk(app);
|
|
202
203
|
regenerateAppIntegrationsIndex(app);
|
|
204
|
+
regenerateStartEntries(app);
|
|
203
205
|
}
|
|
204
206
|
console.log("Done!");
|
|
205
207
|
}
|
|
@@ -221,6 +223,7 @@ export async function runDev() {
|
|
|
221
223
|
regenerateAppApiTs(app);
|
|
222
224
|
regenerateAppTypedWrappers(app);
|
|
223
225
|
regenerateAppIntegrationsIndex(app);
|
|
226
|
+
regenerateStartEntries(app);
|
|
224
227
|
}
|
|
225
228
|
// 3. Watch each dir's src/api/ for endpoint changes
|
|
226
229
|
let watchingAny = false;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type Access = {
|
|
2
|
+
kind: "signedIn" | "anyone" | "webhook" | "none";
|
|
3
|
+
roles: string[];
|
|
4
|
+
};
|
|
5
|
+
export type DescribedFunction = {
|
|
6
|
+
id: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
file?: string;
|
|
9
|
+
method?: string;
|
|
10
|
+
access: Access;
|
|
11
|
+
input?: unknown;
|
|
12
|
+
problems: string[];
|
|
13
|
+
};
|
|
14
|
+
export type DescribedRoute = {
|
|
15
|
+
path: string;
|
|
16
|
+
methods: Record<string, Access>;
|
|
17
|
+
problems: string[];
|
|
18
|
+
};
|
|
19
|
+
export type Description = {
|
|
20
|
+
functions: DescribedFunction[];
|
|
21
|
+
routes: DescribedRoute[];
|
|
22
|
+
};
|
|
23
|
+
export declare function describeFunction(id: string, request: Request): Promise<DescribedFunction>;
|
|
24
|
+
type RouteLike = {
|
|
25
|
+
fullPath: string;
|
|
26
|
+
parentRoute?: RouteLike;
|
|
27
|
+
options?: {
|
|
28
|
+
server?: {
|
|
29
|
+
middleware?: unknown[];
|
|
30
|
+
handlers?: unknown;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export declare function describeRoutes(routesById: Record<string, RouteLike>): DescribedRoute[];
|
|
35
|
+
export declare function describeApp(opts: {
|
|
36
|
+
fnIds: string[];
|
|
37
|
+
getRouter: () => unknown;
|
|
38
|
+
request: Request;
|
|
39
|
+
}): Promise<Description>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Describes a built app by loading it, not by reading its source: each server
|
|
2
|
+
// function's real middleware chain, each server route's, and input schemas.
|
|
3
|
+
import { createMiddleware, flattenMiddlewares } from "@tanstack/react-start";
|
|
4
|
+
import { createSsrRpc } from "@tanstack/react-start/ssr-rpc";
|
|
5
|
+
import { runWithStartContext } from "@tanstack/start-storage-context";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { ziteMiddlewareKind } from "./index.js";
|
|
8
|
+
const accessOf = (chain, problems) => {
|
|
9
|
+
const kinds = chain.map(ziteMiddlewareKind).filter((k) => !!k);
|
|
10
|
+
const roles = kinds.flatMap((k) => (k.kind === "role" ? [k.role] : []));
|
|
11
|
+
const base = [...new Set(kinds.flatMap((k) => (k.kind === "signedIn" || k.kind === "anyone" || k.kind === "webhook" ? [k.kind] : [])))];
|
|
12
|
+
if (base.length === 0) {
|
|
13
|
+
problems.push("access-missing: no signedIn, anyone or webhook middleware in its chain");
|
|
14
|
+
return { kind: "none", roles };
|
|
15
|
+
}
|
|
16
|
+
if (base.length > 1)
|
|
17
|
+
problems.push(`access-conflict: ${base.join(" + ")}`);
|
|
18
|
+
const kind = base.includes("webhook") ? "webhook" : base.includes("signedIn") ? "signedIn" : "anyone";
|
|
19
|
+
return { kind, roles };
|
|
20
|
+
};
|
|
21
|
+
const jsonSchemaOf = (validator) => {
|
|
22
|
+
if (validator && typeof validator === "object" && "_zod" in validator) {
|
|
23
|
+
try {
|
|
24
|
+
return z.toJSONSchema(validator);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { unrepresentable: true };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return validator ? { opaque: true } : undefined;
|
|
31
|
+
};
|
|
32
|
+
// Describe walks functions one at a time, so one probe slot is enough.
|
|
33
|
+
let probe = null;
|
|
34
|
+
// The only global function middleware while describing: it records what it was
|
|
35
|
+
// handed and ends the call, so no app middleware or handler runs.
|
|
36
|
+
const describeProbe = createMiddleware({ type: "function" }).server(({ serverFnMeta, method }) => {
|
|
37
|
+
if (probe)
|
|
38
|
+
Object.assign(probe, { meta: serverFnMeta, method, reached: true });
|
|
39
|
+
return "zite:describe";
|
|
40
|
+
});
|
|
41
|
+
export async function describeFunction(id, request) {
|
|
42
|
+
const problems = [];
|
|
43
|
+
const seen = [];
|
|
44
|
+
probe = { reached: false };
|
|
45
|
+
// Before running a chain, TanStack asks this set about every middleware in it.
|
|
46
|
+
// Recording the questions gives the exact chain it would have run.
|
|
47
|
+
const executedRequestMiddlewares = {
|
|
48
|
+
has: (middleware) => {
|
|
49
|
+
seen.push(middleware);
|
|
50
|
+
return false;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const startContext = {
|
|
54
|
+
request,
|
|
55
|
+
startOptions: { functionMiddleware: [describeProbe] },
|
|
56
|
+
executedRequestMiddlewares,
|
|
57
|
+
contextAfterGlobalMiddlewares: {},
|
|
58
|
+
handlerType: "serverFn",
|
|
59
|
+
getRouter: () => {
|
|
60
|
+
throw new Error("describe has no router");
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
await runWithStartContext(startContext, () => createSsrRpc(id)({ data: undefined, context: {} }));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
problems.push(`probe-failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
}
|
|
69
|
+
const state = probe;
|
|
70
|
+
probe = null;
|
|
71
|
+
// Fails closed: if TanStack stops asking, or stops running globals first, every
|
|
72
|
+
// function reports a problem and the build stops.
|
|
73
|
+
if (!state.reached || seen[0] !== describeProbe)
|
|
74
|
+
problems.push("probe-not-first: chain could not be read");
|
|
75
|
+
const chain = seen.filter((m) => m !== describeProbe);
|
|
76
|
+
if (!chain.some((m) => ziteMiddlewareKind(m)?.kind === "factory")) {
|
|
77
|
+
problems.push("raw-server-fn: define it with signedInFn or publicFn from zitejs/start");
|
|
78
|
+
}
|
|
79
|
+
const handlerStep = chain.at(-1);
|
|
80
|
+
return {
|
|
81
|
+
id,
|
|
82
|
+
name: state.meta?.name,
|
|
83
|
+
file: state.meta?.filename,
|
|
84
|
+
method: state.method,
|
|
85
|
+
access: accessOf(chain, problems),
|
|
86
|
+
input: jsonSchemaOf(handlerStep?.options?.inputValidator),
|
|
87
|
+
problems,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function describeRoutes(routesById) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const route of Object.values(routesById)) {
|
|
93
|
+
const server = route.options?.server;
|
|
94
|
+
if (!server?.handlers)
|
|
95
|
+
continue;
|
|
96
|
+
const problems = [];
|
|
97
|
+
const ancestry = [];
|
|
98
|
+
for (let r = route; r; r = r.parentRoute)
|
|
99
|
+
ancestry.unshift(r);
|
|
100
|
+
const routeMiddleware = ancestry.flatMap((r) => r.options?.server?.middleware ?? []);
|
|
101
|
+
const handlers = (typeof server.handlers === "function"
|
|
102
|
+
? server.handlers({ createHandlers: (d) => d })
|
|
103
|
+
: server.handlers);
|
|
104
|
+
const methods = {};
|
|
105
|
+
for (const [method, handler] of Object.entries(handlers)) {
|
|
106
|
+
const own = typeof handler === "function" ? [] : (handler.middleware ?? []);
|
|
107
|
+
const chain = flattenMiddlewares([...routeMiddleware, ...own]);
|
|
108
|
+
const methodProblems = [];
|
|
109
|
+
methods[method] = accessOf(chain, methodProblems);
|
|
110
|
+
problems.push(...methodProblems.map((p) => `${method}: ${p}`));
|
|
111
|
+
}
|
|
112
|
+
out.push({ path: route.fullPath, methods, problems });
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
export async function describeApp(opts) {
|
|
117
|
+
const functions = [];
|
|
118
|
+
for (const id of opts.fnIds)
|
|
119
|
+
functions.push(await describeFunction(id, opts.request));
|
|
120
|
+
const router = (await opts.getRouter());
|
|
121
|
+
return { functions, routes: describeRoutes(router.routesById) };
|
|
122
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
// Only the host can set x-zite-* in production: the front strips them from public traffic.
|
|
7
|
+
const DESCRIBE_HEADERS = { "x-zite-invoke": "describe" };
|
|
8
|
+
const freePort = () => new Promise((resolve, reject) => {
|
|
9
|
+
const server = createServer();
|
|
10
|
+
server.once("error", reject);
|
|
11
|
+
server.listen(0, () => {
|
|
12
|
+
const { port } = server.address();
|
|
13
|
+
server.close(() => resolve(port));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
// A Workers build runs where it will run: workerd, through the app's own
|
|
17
|
+
// `vite preview`, so it uses the same Cloudflare plugin and config as the build.
|
|
18
|
+
async function describeInWorkerd(appDir) {
|
|
19
|
+
const port = await freePort();
|
|
20
|
+
// Its own process group, so stopping it also stops vite and workerd under npx.
|
|
21
|
+
const preview = spawn("npx", ["--no-install", "vite", "preview", "--port", String(port), "--strictPort"], {
|
|
22
|
+
cwd: appDir,
|
|
23
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
24
|
+
detached: true,
|
|
25
|
+
});
|
|
26
|
+
let stderr = "";
|
|
27
|
+
preview.stderr.on("data", (chunk) => (stderr += chunk));
|
|
28
|
+
try {
|
|
29
|
+
const deadline = Date.now() + 60_000;
|
|
30
|
+
while (true) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(`http://localhost:${port}/`, { headers: DESCRIBE_HEADERS });
|
|
33
|
+
// Right after start, preview can answer before the Worker does; only the
|
|
34
|
+
// app's own describe answer is JSON.
|
|
35
|
+
if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
|
|
36
|
+
return await response.json();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
if (preview.exitCode !== null || Date.now() > deadline) {
|
|
41
|
+
throw new Error(`vite preview didn't serve the app: ${stderr.trim().slice(-500)}`);
|
|
42
|
+
}
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(-preview.pid, "SIGTERM");
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Describes a built Start app from its `dist/server`: a Workers build in workerd, a Node build in-process. */
|
|
54
|
+
export async function describeBuiltApp(appDir) {
|
|
55
|
+
const serverDir = path.join(appDir, "dist/server");
|
|
56
|
+
if (existsSync(path.join(serverDir, "wrangler.json")))
|
|
57
|
+
return describeInWorkerd(appDir);
|
|
58
|
+
const { default: server } = await import(pathToFileURL(path.join(serverDir, "server.js")).href);
|
|
59
|
+
return (await server.fetch(new Request("http://describe.local/", { headers: DESCRIBE_HEADERS }))).json();
|
|
60
|
+
}
|
|
61
|
+
export async function runDescribe() {
|
|
62
|
+
const appDir = path.resolve(process.argv[3] ?? ".");
|
|
63
|
+
process.stdout.write(`${JSON.stringify(await describeBuiltApp(appDir), null, 2)}\n`);
|
|
64
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SourceDir } from "../sourceRoots.js";
|
|
2
|
+
export declare const START_SERVER_ENTRY = ".zite/server.ts";
|
|
3
|
+
export declare const START_INSTANCE_ENTRY = ".zite/start.ts";
|
|
4
|
+
/** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
|
|
5
|
+
export declare const renderStartServerEntry: () => string;
|
|
6
|
+
/** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
|
|
7
|
+
export declare const renderStartInstance: (hasAppStart: boolean) => string;
|
|
8
|
+
export declare const hasAppStart: (appDir: string) => boolean;
|
|
9
|
+
/** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
|
|
10
|
+
export declare const writeIfChanged: (appDir: string, file: string, content: string) => boolean;
|
|
11
|
+
export declare function regenerateStartEntries(app: SourceDir): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
export const START_SERVER_ENTRY = ".zite/server.ts";
|
|
4
|
+
export const START_INSTANCE_ENTRY = ".zite/start.ts";
|
|
5
|
+
const APP_START_FILES = ["src/start.ts", "src/start.tsx"];
|
|
6
|
+
/** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
|
|
7
|
+
export const renderStartServerEntry = () => `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
8
|
+
import handler, { createServerEntry } from '@tanstack/react-start/server-entry';
|
|
9
|
+
import { createZiteFetch } from 'zitejs/start/server';
|
|
10
|
+
import { getRouter } from '../src/router';
|
|
11
|
+
|
|
12
|
+
export default createServerEntry({ fetch: createZiteFetch({ handler, getRouter }) });
|
|
13
|
+
`;
|
|
14
|
+
/** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
|
|
15
|
+
export const renderStartInstance = (hasAppStart) => hasAppStart
|
|
16
|
+
? `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
17
|
+
import { createZiteStart } from 'zitejs/start/instance';
|
|
18
|
+
import { startInstance as app } from '../src/start';
|
|
19
|
+
|
|
20
|
+
export const startInstance = createZiteStart(app);
|
|
21
|
+
`
|
|
22
|
+
: `// Generated by zitejs for TanStack Start apps. The platform owns this file.
|
|
23
|
+
import { createZiteStart } from 'zitejs/start/instance';
|
|
24
|
+
|
|
25
|
+
export const startInstance = createZiteStart();
|
|
26
|
+
`;
|
|
27
|
+
export const hasAppStart = (appDir) => APP_START_FILES.some((file) => existsSync(join(appDir, file)));
|
|
28
|
+
/** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
|
|
29
|
+
export const writeIfChanged = (appDir, file, content) => {
|
|
30
|
+
const target = join(appDir, file);
|
|
31
|
+
if (existsSync(target) && readFileSync(target, "utf-8") === content)
|
|
32
|
+
return false;
|
|
33
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
34
|
+
writeFileSync(target, content);
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
const isStartApp = (app) => {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(join(app.path, "zite.config.json"), "utf-8"))?.runtime === "start";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export function regenerateStartEntries(app) {
|
|
46
|
+
if (!isStartApp(app))
|
|
47
|
+
return;
|
|
48
|
+
const entries = [
|
|
49
|
+
[START_SERVER_ENTRY, renderStartServerEntry()],
|
|
50
|
+
[START_INSTANCE_ENTRY, renderStartInstance(hasAppStart(app.path))],
|
|
51
|
+
];
|
|
52
|
+
for (const [file, content] of entries) {
|
|
53
|
+
if (writeIfChanged(app.path, file, content))
|
|
54
|
+
console.log(`Wrote ${join(app.path, file)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|