zitejs 0.9.73 → 0.9.75

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.
@@ -0,0 +1,345 @@
1
+ export interface ZiteAuthUser {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ firstName?: string;
6
+ lastName?: string;
7
+ }
8
+ export interface ZiteAuthConfig {
9
+ flowPublicId?: string;
10
+ onSignup?: (user: ZiteAuthUser) => Promise<void>;
11
+ onLogin?: (user: ZiteAuthUser) => Promise<void>;
12
+ beforeSignup?: (email: string) => Promise<boolean>;
13
+ sendEmail?: (to: string, subject: string, body: string) => Promise<void>;
14
+ }
15
+ export declare function ziteAuth(config?: ZiteAuthConfig): import("better-auth").Auth<{
16
+ database: {
17
+ url: string;
18
+ };
19
+ secret: string;
20
+ baseURL: string;
21
+ advanced: {
22
+ disableCSRFCheck: true;
23
+ };
24
+ rateLimit: {
25
+ window: number;
26
+ max: number;
27
+ };
28
+ emailAndPassword: {
29
+ enabled: true;
30
+ };
31
+ plugins: [{
32
+ id: "generic-oauth";
33
+ version: string;
34
+ init: (ctx: import("better-auth").AuthContext) => {
35
+ context: {
36
+ socialProviders: import("better-auth").OAuthProvider<Record<string, any>, Partial<import("better-auth").ProviderOptions<any>>>[];
37
+ };
38
+ };
39
+ endpoints: {
40
+ signInWithOAuth2: import("better-auth").StrictEndpoint<"/sign-in/oauth2", {
41
+ method: "POST";
42
+ body: import("better-auth").ZodObject<{
43
+ providerId: import("better-auth").ZodString;
44
+ callbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
45
+ errorCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
46
+ newUserCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
47
+ disableRedirect: import("better-auth").ZodOptional<import("better-auth").ZodBoolean>;
48
+ scopes: import("better-auth").ZodOptional<import("better-auth").ZodArray<import("better-auth").ZodString>>;
49
+ requestSignUp: import("better-auth").ZodOptional<import("better-auth").ZodBoolean>;
50
+ additionalData: import("better-auth").ZodOptional<import("better-auth").ZodRecord<import("better-auth").ZodString, import("better-auth").ZodAny>>;
51
+ }, import("zod/v4/core").$strip>;
52
+ metadata: {
53
+ openapi: {
54
+ description: string;
55
+ responses: {
56
+ 200: {
57
+ description: string;
58
+ content: {
59
+ "application/json": {
60
+ schema: {
61
+ type: "object";
62
+ properties: {
63
+ url: {
64
+ type: string;
65
+ };
66
+ redirect: {
67
+ type: string;
68
+ };
69
+ };
70
+ };
71
+ };
72
+ };
73
+ };
74
+ };
75
+ };
76
+ };
77
+ }, {
78
+ url: string;
79
+ redirect: boolean;
80
+ }>;
81
+ oAuth2Callback: import("better-auth").StrictEndpoint<"/oauth2/callback/:providerId", {
82
+ method: "GET";
83
+ query: import("better-auth").ZodObject<{
84
+ code: import("better-auth").ZodOptional<import("better-auth").ZodString>;
85
+ error: import("better-auth").ZodOptional<import("better-auth").ZodString>;
86
+ error_description: import("better-auth").ZodOptional<import("better-auth").ZodString>;
87
+ state: import("better-auth").ZodOptional<import("better-auth").ZodString>;
88
+ iss: import("better-auth").ZodOptional<import("better-auth").ZodString>;
89
+ }, import("zod/v4/core").$strip>;
90
+ metadata: {
91
+ allowedMediaTypes: string[];
92
+ openapi: {
93
+ description: string;
94
+ responses: {
95
+ 200: {
96
+ description: string;
97
+ content: {
98
+ "application/json": {
99
+ schema: {
100
+ type: "object";
101
+ properties: {
102
+ url: {
103
+ type: string;
104
+ };
105
+ };
106
+ };
107
+ };
108
+ };
109
+ };
110
+ };
111
+ };
112
+ scope: "server";
113
+ };
114
+ }, never>;
115
+ oAuth2LinkAccount: import("better-auth").StrictEndpoint<"/oauth2/link", {
116
+ method: "POST";
117
+ body: import("better-auth").ZodObject<{
118
+ providerId: import("better-auth").ZodString;
119
+ callbackURL: import("better-auth").ZodString;
120
+ scopes: import("better-auth").ZodOptional<import("better-auth").ZodArray<import("better-auth").ZodString>>;
121
+ errorCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
122
+ }, import("zod/v4/core").$strip>;
123
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
124
+ session: {
125
+ session: Record<string, any> & {
126
+ id: string;
127
+ createdAt: Date;
128
+ updatedAt: Date;
129
+ userId: string;
130
+ expiresAt: Date;
131
+ token: string;
132
+ ipAddress?: string | null | undefined;
133
+ userAgent?: string | null | undefined;
134
+ };
135
+ user: Record<string, any> & {
136
+ id: string;
137
+ createdAt: Date;
138
+ updatedAt: Date;
139
+ email: string;
140
+ emailVerified: boolean;
141
+ name: string;
142
+ image?: string | null | undefined;
143
+ };
144
+ };
145
+ }>)[];
146
+ metadata: {
147
+ openapi: {
148
+ description: string;
149
+ responses: {
150
+ "200": {
151
+ description: string;
152
+ content: {
153
+ "application/json": {
154
+ schema: {
155
+ type: "object";
156
+ properties: {
157
+ url: {
158
+ type: string;
159
+ format: string;
160
+ description: string;
161
+ };
162
+ redirect: {
163
+ type: string;
164
+ description: string;
165
+ enum: boolean[];
166
+ };
167
+ };
168
+ required: string[];
169
+ };
170
+ };
171
+ };
172
+ };
173
+ };
174
+ };
175
+ };
176
+ }, {
177
+ url: string;
178
+ redirect: boolean;
179
+ }>;
180
+ };
181
+ options: import("better-auth/plugins").GenericOAuthOptions;
182
+ $ERROR_CODES: {
183
+ INVALID_OAUTH_CONFIGURATION: import("better-auth").RawError<"INVALID_OAUTH_CONFIGURATION">;
184
+ TOKEN_URL_NOT_FOUND: import("better-auth").RawError<"TOKEN_URL_NOT_FOUND">;
185
+ PROVIDER_CONFIG_NOT_FOUND: import("better-auth").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
186
+ PROVIDER_ID_REQUIRED: import("better-auth").RawError<"PROVIDER_ID_REQUIRED">;
187
+ INVALID_OAUTH_CONFIG: import("better-auth").RawError<"INVALID_OAUTH_CONFIG">;
188
+ SESSION_REQUIRED: import("better-auth").RawError<"SESSION_REQUIRED">;
189
+ ISSUER_MISMATCH: import("better-auth").RawError<"ISSUER_MISMATCH">;
190
+ ISSUER_MISSING: import("better-auth").RawError<"ISSUER_MISSING">;
191
+ };
192
+ }, {
193
+ id: "magic-link";
194
+ version: string;
195
+ endpoints: {
196
+ signInMagicLink: import("better-auth").StrictEndpoint<"/sign-in/magic-link", {
197
+ method: "POST";
198
+ requireHeaders: true;
199
+ body: import("better-auth").ZodObject<{
200
+ email: import("better-auth").ZodEmail;
201
+ name: import("better-auth").ZodOptional<import("better-auth").ZodString>;
202
+ callbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
203
+ newUserCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
204
+ errorCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
205
+ metadata: import("better-auth").ZodOptional<import("better-auth").ZodRecord<import("better-auth").ZodString, import("better-auth").ZodAny>>;
206
+ }, import("zod/v4/core").$strip>;
207
+ metadata: {
208
+ openapi: {
209
+ operationId: string;
210
+ description: string;
211
+ responses: {
212
+ 200: {
213
+ description: string;
214
+ content: {
215
+ "application/json": {
216
+ schema: {
217
+ type: "object";
218
+ properties: {
219
+ status: {
220
+ type: string;
221
+ };
222
+ };
223
+ };
224
+ };
225
+ };
226
+ };
227
+ };
228
+ };
229
+ };
230
+ }, {
231
+ status: boolean;
232
+ }>;
233
+ magicLinkVerify: import("better-auth").StrictEndpoint<"/magic-link/verify", {
234
+ method: "GET";
235
+ query: import("better-auth").ZodObject<{
236
+ token: import("better-auth").ZodString;
237
+ callbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
238
+ errorCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
239
+ newUserCallbackURL: import("better-auth").ZodOptional<import("better-auth").ZodString>;
240
+ }, import("zod/v4/core").$strip>;
241
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>)[];
242
+ requireHeaders: true;
243
+ metadata: {
244
+ openapi: {
245
+ operationId: string;
246
+ description: string;
247
+ responses: {
248
+ 200: {
249
+ description: string;
250
+ content: {
251
+ "application/json": {
252
+ schema: {
253
+ type: "object";
254
+ properties: {
255
+ session: {
256
+ $ref: string;
257
+ };
258
+ user: {
259
+ $ref: string;
260
+ };
261
+ };
262
+ };
263
+ };
264
+ };
265
+ };
266
+ };
267
+ };
268
+ };
269
+ }, {
270
+ token: string;
271
+ user: {
272
+ id: string;
273
+ createdAt: Date;
274
+ updatedAt: Date;
275
+ email: string;
276
+ emailVerified: boolean;
277
+ name: string;
278
+ image?: string | null | undefined;
279
+ };
280
+ session: {
281
+ id: string;
282
+ createdAt: Date;
283
+ updatedAt: Date;
284
+ userId: string;
285
+ expiresAt: Date;
286
+ token: string;
287
+ ipAddress?: string | null | undefined;
288
+ userAgent?: string | null | undefined;
289
+ };
290
+ }>;
291
+ };
292
+ rateLimit: {
293
+ pathMatcher(path: string): boolean;
294
+ window: number;
295
+ max: number;
296
+ }[];
297
+ options: import("better-auth/plugins").MagicLinkOptions;
298
+ }];
299
+ user: {
300
+ modelName: "zite_user";
301
+ additionalFields: {
302
+ firstName: {
303
+ type: "string";
304
+ required: false;
305
+ };
306
+ lastName: {
307
+ type: "string";
308
+ required: false;
309
+ };
310
+ };
311
+ };
312
+ session: {
313
+ modelName: "zite_session";
314
+ };
315
+ account: {
316
+ modelName: "zite_account";
317
+ };
318
+ verification: {
319
+ modelName: "zite_verification";
320
+ };
321
+ databaseHooks: {
322
+ user: {
323
+ create: {
324
+ before: ((user: {
325
+ id: string;
326
+ createdAt: Date;
327
+ updatedAt: Date;
328
+ email: string;
329
+ emailVerified: boolean;
330
+ name: string;
331
+ image?: string | null | undefined;
332
+ } & Record<string, unknown>) => Promise<undefined>) | undefined;
333
+ after: ((user: {
334
+ id: string;
335
+ createdAt: Date;
336
+ updatedAt: Date;
337
+ email: string;
338
+ emailVerified: boolean;
339
+ name: string;
340
+ image?: string | null | undefined;
341
+ } & Record<string, unknown>) => Promise<void>) | undefined;
342
+ };
343
+ };
344
+ };
345
+ }>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ziteAuth = ziteAuth;
4
+ const better_auth_1 = require("better-auth");
5
+ const plugins_1 = require("better-auth/plugins");
6
+ function ziteAuth(config = {}) {
7
+ const sendMagicLinkEmail = config.sendEmail
8
+ ? async ({ email, url }) => {
9
+ await config.sendEmail(email, 'Sign in to your app', `Click here to sign in: ${url}`);
10
+ }
11
+ : async ({ email, url }) => {
12
+ console.log(`[ziteAuth] Magic link for ${email}: ${url}`);
13
+ };
14
+ return (0, better_auth_1.betterAuth)({
15
+ database: { url: process.env.DATABASE_URL },
16
+ secret: process.env.ZITE_AUTH_SECRET || 'zite-local-dev-secret',
17
+ baseURL: 'http://localhost',
18
+ advanced: { disableCSRFCheck: true },
19
+ rateLimit: { window: 60, max: 10 },
20
+ emailAndPassword: { enabled: true },
21
+ plugins: [
22
+ (0, plugins_1.genericOAuth)({
23
+ config: [
24
+ {
25
+ providerId: 'zite',
26
+ clientId: process.env.ZITE_OAUTH_CLIENT_ID,
27
+ clientSecret: process.env.ZITE_OAUTH_CLIENT_SECRET,
28
+ discoveryUrl: process.env.ZITE_AUTH_URL +
29
+ '/.well-known/openid-configuration',
30
+ },
31
+ ],
32
+ }),
33
+ (0, plugins_1.magicLink)({ sendMagicLink: sendMagicLinkEmail }),
34
+ ],
35
+ user: {
36
+ modelName: 'zite_user',
37
+ additionalFields: {
38
+ firstName: { type: 'string', required: false },
39
+ lastName: { type: 'string', required: false },
40
+ },
41
+ },
42
+ session: { modelName: 'zite_session' },
43
+ account: { modelName: 'zite_account' },
44
+ verification: { modelName: 'zite_verification' },
45
+ databaseHooks: {
46
+ user: {
47
+ create: {
48
+ before: config.beforeSignup
49
+ ? async (user) => {
50
+ const allowed = await config.beforeSignup(user.email);
51
+ if (!allowed) {
52
+ throw new Error('Signup not allowed for this email');
53
+ }
54
+ return undefined;
55
+ }
56
+ : undefined,
57
+ after: config.onSignup
58
+ ? async (user) => {
59
+ await config.onSignup({
60
+ id: user.id,
61
+ email: user.email,
62
+ name: user.name,
63
+ firstName: user.firstName,
64
+ lastName: user.lastName,
65
+ });
66
+ }
67
+ : undefined,
68
+ },
69
+ },
70
+ },
71
+ });
72
+ }
@@ -1,3 +1,8 @@
1
+ import type { ZiteSchedule } from "../schedules/index.js";
2
+ export type { ZiteSchedule };
3
+ export type ZiteWebhook = {
4
+ paused?: boolean;
5
+ };
1
6
  export interface ZiteRequestContext {
2
7
  user: {
3
8
  id: string;
@@ -31,6 +36,8 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
31
36
  outputSchema?: SchemaLike<TOutput>;
32
37
  stream?: TStream;
33
38
  authenticated?: boolean;
39
+ schedule?: ZiteSchedule;
40
+ webhook?: ZiteWebhook;
34
41
  execute: (params: {
35
42
  input: TInput;
36
43
  context: ZiteRequestContext | ZiteScheduledContext;
@@ -39,4 +46,3 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
39
46
  } : {})) => Promise<TOutput> | TOutput;
40
47
  }
41
48
  export declare function createEndpoint<TInput = unknown, TOutput = unknown, TStream extends boolean = false>(config: EndpointConfig<TInput, TOutput, TStream>): EndpointConfig<TInput, TOutput, TStream>;
42
- export {};
@@ -23,10 +23,17 @@ const ENVIRONMENTS = {
23
23
  local: "http://localhost:2506",
24
24
  };
25
25
  function getRunnerUrl() {
26
+ const explicit = (0, env_js_1.getEnv)("ZITE_RUNNER_URL", "VITE_ZITE_RUNNER_URL");
27
+ if (explicit)
28
+ return explicit;
29
+ // Published apps use same-origin proxy (/api/*) — the CF worker
30
+ // forwards to the workflow-runner. This ensures cookies work and
31
+ // the browser sees a single origin.
32
+ if (getMode() === "live")
33
+ return "";
34
+ // Preview/editor mode: call the workflow-runner directly
26
35
  const env = (0, env_js_1.getEnv)("ZITE_ENV", "VITE_ZITE_ENV") ?? "production";
27
- return ((0, env_js_1.getEnv)("ZITE_RUNNER_URL", "VITE_ZITE_RUNNER_URL") ??
28
- ENVIRONMENTS[env] ??
29
- ENVIRONMENTS.production);
36
+ return ENVIRONMENTS[env] ?? ENVIRONMENTS.production;
30
37
  }
31
38
  /**
32
39
  * Get the user's usage token for endpoint auth.
@@ -49,12 +56,18 @@ function getUsageToken() {
49
56
  }
50
57
  function buildFetchOptions(name, input, extra) {
51
58
  const appId = (0, env_js_1.getEnv)("ZITE_FLOW_ID", "VITE_ZITE_FLOW_ID") ?? "";
59
+ const runnerUrl = getRunnerUrl();
52
60
  const token = getUsageToken();
53
61
  const ziteAuthToken = typeof localStorage !== "undefined"
54
62
  ? localStorage.getItem("zite.auth.token")
55
63
  : null;
64
+ // Same-origin mode (published apps): /api/{name}
65
+ // Direct mode (preview): {runnerUrl}/public/{appId}/api/{name}
66
+ const url = runnerUrl === ""
67
+ ? "/api/" + name
68
+ : runnerUrl + "/public/" + appId + "/api/" + name;
56
69
  return {
57
- url: getRunnerUrl() + "/public/" + appId + "/api/" + name,
70
+ url,
58
71
  init: {
59
72
  method: "POST",
60
73
  headers: {
@@ -52,6 +52,18 @@ function getEmailIntegrationId(appDir) {
52
52
  catch { }
53
53
  return undefined;
54
54
  }
55
+ function getDeclaredEnvVarNames(appDir) {
56
+ try {
57
+ const configPath = (0, path_1.join)("apps", appDir, "zite.config.json");
58
+ if (!(0, fs_2.existsSync)(configPath))
59
+ return [];
60
+ const config = JSON.parse((0, fs_2.readFileSync)(configPath, "utf-8"));
61
+ return Object.keys(config.envVars ?? {});
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
55
67
  function regenerateAppTypedWrappers(appDir) {
56
68
  const outDir = (0, path_1.join)("apps", appDir, ".zite");
57
69
  (0, fs_2.mkdirSync)(outDir, { recursive: true });
@@ -66,7 +78,7 @@ function regenerateAppTypedWrappers(appDir) {
66
78
  (0, fs_2.mkdirSync)(intDir, { recursive: true });
67
79
  (0, fs_2.writeFileSync)((0, path_1.join)(intDir, "email.ts"), (0, lib_js_1.generateEmailSdk)(emailIntegrationId));
68
80
  }
69
- (0, fs_2.writeFileSync)((0, path_1.join)(outDir, "backend.ts"), (0, lib_js_1.generateBackendWrapperTs)());
81
+ (0, fs_2.writeFileSync)((0, path_1.join)(outDir, "backend.ts"), (0, lib_js_1.generateBackendWrapperTs)(getDeclaredEnvVarNames(appDir)));
70
82
  }
71
83
  function regenerateAppApiTs(appDir) {
72
84
  const apiDir = (0, path_1.join)("apps", appDir, "src", "api");
@@ -21,7 +21,7 @@ export declare function toCamelCase(name: string): string;
21
21
  *
22
22
  * If `existingSchema` is provided, locked SDK names are preserved for
23
23
  * tables/fields that already exist (matched by ID). New tables/fields
24
- * get fresh camelCase SDK names. This is how `zitejs sync` preserves
24
+ * get fresh camelCase SDK names. This is how `zitejs generate` preserves
25
25
  * name stability across schema changes.
26
26
  */
27
27
  export declare function generateSchema(database: Database, existingSchema?: ZiteSchema): ZiteSchema;
@@ -58,7 +58,7 @@ export type AirtableLock = {
58
58
  tables: AirtableLockTable[];
59
59
  };
60
60
  export declare function generateAirtableTs(lock: AirtableLock): string | null;
61
- export declare function generateBackendWrapperTs(): string;
61
+ export declare function generateBackendWrapperTs(envVarNames?: string[]): string;
62
62
  /**
63
63
  * Generate `.zite/integrations/email.ts` — the `Email` client for an app with
64
64
  * an email integration connected. Mirrors the airtable SDK generation: a thin
@@ -188,7 +188,7 @@ function fieldJsdoc(schemaField, table) {
188
188
  *
189
189
  * If `existingSchema` is provided, locked SDK names are preserved for
190
190
  * tables/fields that already exist (matched by ID). New tables/fields
191
- * get fresh camelCase SDK names. This is how `zitejs sync` preserves
191
+ * get fresh camelCase SDK names. This is how `zitejs generate` preserves
192
192
  * name stability across schema changes.
193
193
  */
194
194
  function generateSchema(database, existingSchema) {
@@ -417,7 +417,7 @@ function generateApiTs(endpointFiles) {
417
417
  }
418
418
  const hasStreaming = endpoints.some((e) => e.stream);
419
419
  const lines = [
420
- "// Auto-generated by zitejs sync. Do not edit manually.",
420
+ "// Auto-generated by zitejs generate. Do not edit manually.",
421
421
  "",
422
422
  hasStreaming
423
423
  ? "import { createCaller, createStreamingCaller } from 'zitejs/caller';"
@@ -460,7 +460,7 @@ function tsTypeForField(field) {
460
460
  }
461
461
  function generateUserTs(usersTableFields) {
462
462
  const lines = [
463
- "// Auto-generated by zitejs sync. Do not edit manually.",
463
+ "// Auto-generated by zitejs generate. Do not edit manually.",
464
464
  "",
465
465
  ];
466
466
  if (usersTableFields && usersTableFields.length > 0) {
@@ -700,14 +700,20 @@ function generateAirtableTs(lock) {
700
700
  }
701
701
  return lines.join("\n");
702
702
  }
703
- function generateBackendWrapperTs() {
703
+ function generateBackendWrapperTs(envVarNames = []) {
704
+ const names = [
705
+ "ZITE_APP_URL",
706
+ ...envVarNames.filter((n) => n !== "ZITE_APP_URL"),
707
+ ];
704
708
  return [
705
709
  "// Auto-generated type-narrowing wrapper. Do not edit manually.",
706
710
  "// Re-exports createEndpoint with context.user typed to the app User.",
707
711
  "",
708
- "import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext as _ZiteScheduledContext } from 'zitejs/backend/base';",
712
+ "import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext as _ZiteScheduledContext, ZiteSchedule, ZiteWebhook } from 'zitejs/backend/base';",
709
713
  "import type { User } from './user';",
710
714
  "",
715
+ "export type { ZiteSchedule, ZiteWebhook };",
716
+ "",
711
717
  "export class ZiteError extends Error {",
712
718
  " statusCode: number;",
713
719
  " constructor(message: string, options?: { statusCode?: number }) {",
@@ -734,6 +740,8 @@ function generateBackendWrapperTs() {
734
740
  " outputSchema?: SchemaLike<TOutput>;",
735
741
  " stream?: boolean;",
736
742
  " authenticated?: boolean;",
743
+ " schedule?: ZiteSchedule;",
744
+ " webhook?: ZiteWebhook;",
737
745
  " execute: (params: { input: TInput; context: ZiteRequestContext | ZiteScheduledContext }) => Promise<TOutput> | TOutput;",
738
746
  "}",
739
747
  "",
@@ -743,6 +751,17 @@ function generateBackendWrapperTs() {
743
751
  " return config;",
744
752
  "}",
745
753
  "",
754
+ // Declared in the wrapper (not a standalone .d.ts) so the merge is
755
+ // guaranteed to be in the program — every endpoint imports this file,
756
+ // while tsconfig `include` does not reliably pick up dot-directories.
757
+ "declare global {",
758
+ " namespace NodeJS {",
759
+ " interface ProcessEnv {",
760
+ ...names.map((name) => ` ${JSON.stringify(name)}: string;`),
761
+ " }",
762
+ " }",
763
+ "}",
764
+ "",
746
765
  ].join("\n");
747
766
  }
748
767
  /**