@getjack/jack 0.1.33 → 0.1.35
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 +6 -6
- package/package.json +1 -1
- package/src/commands/down.ts +39 -7
- package/src/commands/link.ts +2 -4
- package/src/commands/logs.ts +2 -4
- package/src/commands/mcp.ts +12 -10
- package/src/commands/secrets.ts +3 -1
- package/src/commands/services.ts +4 -2
- package/src/commands/sync.ts +5 -6
- package/src/lib/auth/client.ts +5 -2
- package/src/lib/binding-validator.ts +39 -3
- package/src/lib/build-helper.ts +18 -19
- package/src/lib/control-plane.ts +1 -0
- package/src/lib/crypto.ts +84 -0
- package/src/lib/deploy-upload.ts +7 -3
- package/src/lib/do-config.ts +110 -0
- package/src/lib/do-export-validator.ts +26 -0
- package/src/lib/hooks.ts +1 -2
- package/src/lib/jsonc-edit.ts +292 -0
- package/src/lib/managed-deploy.ts +36 -1
- package/src/lib/project-link.ts +37 -0
- package/src/lib/project-operations.ts +37 -46
- package/src/lib/prompts.ts +2 -2
- package/src/lib/resources.ts +4 -5
- package/src/lib/schema.ts +8 -12
- package/src/lib/services/db-create.ts +2 -2
- package/src/lib/services/db-execute.ts +9 -6
- package/src/lib/services/db-list.ts +6 -4
- package/src/lib/services/endpoint-test.ts +275 -0
- package/src/lib/services/project-delete.ts +190 -0
- package/src/lib/services/project-environment.ts +457 -0
- package/src/lib/services/storage-config.ts +7 -309
- package/src/lib/services/storage-create.ts +2 -1
- package/src/lib/services/storage-delete.ts +3 -2
- package/src/lib/services/storage-info.ts +2 -1
- package/src/lib/services/storage-list.ts +6 -3
- package/src/lib/services/vectorize-config.ts +7 -264
- package/src/lib/services/vectorize-create.ts +2 -1
- package/src/lib/services/vectorize-delete.ts +6 -4
- package/src/lib/services/vectorize-list.ts +6 -3
- package/src/lib/storage/index.ts +21 -23
- package/src/lib/telemetry.ts +1 -0
- package/src/lib/wrangler-config.ts +43 -312
- package/src/lib/zip-packager.ts +28 -0
- package/src/mcp/test-utils.ts +31 -0
- package/src/mcp/tools/index.ts +271 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/types.ts +4 -0
- package/templates/AI-BINDINGS.md +34 -76
- package/templates/CLAUDE.md +22 -1
- package/templates/ai-chat/src/index.ts +7 -14
- package/templates/ai-chat/src/jack-ai.ts +0 -6
- package/templates/chat/.jack.json +45 -0
- package/templates/chat/bun.lock +1588 -0
- package/templates/chat/components.json +23 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +41 -0
- package/templates/chat/src/chat-agent.ts +61 -0
- package/templates/chat/src/client/app.tsx +189 -0
- package/templates/chat/src/client/chat.tsx +222 -0
- package/templates/chat/src/client/components/prompt-kit/chat-container.tsx +47 -0
- package/templates/chat/src/client/components/prompt-kit/loader.tsx +33 -0
- package/templates/chat/src/client/components/prompt-kit/markdown.tsx +84 -0
- package/templates/chat/src/client/components/prompt-kit/message.tsx +54 -0
- package/templates/chat/src/client/components/prompt-kit/prompt-suggestion.tsx +20 -0
- package/templates/chat/src/client/components/prompt-kit/reasoning.tsx +134 -0
- package/templates/chat/src/client/components/prompt-kit/scroll-button.tsx +28 -0
- package/templates/chat/src/client/components/ui/button.tsx +38 -0
- package/templates/chat/src/client/lib/utils.ts +6 -0
- package/templates/chat/src/client/main.tsx +11 -0
- package/templates/chat/src/client/styles.css +125 -0
- package/templates/chat/src/index.ts +25 -0
- package/templates/chat/src/jack-ai.ts +94 -0
- package/templates/chat/tsconfig.json +18 -0
- package/templates/chat/vite.config.ts +14 -0
- package/templates/chat/wrangler.jsonc +18 -0
- package/templates/cron/.jack.json +18 -28
- package/templates/cron/schema.sql +10 -20
- package/templates/cron/src/admin.ts +321 -0
- package/templates/cron/src/index.ts +151 -81
- package/templates/cron/src/monitor.ts +124 -0
- package/templates/nextjs-clerk/app/layout.tsx +2 -0
- package/templates/semantic-search/src/index.ts +5 -43
- package/templates/semantic-search/src/jack-ai.ts +0 -6
- package/templates/telegram-bot/.jack.json +56 -0
- package/templates/telegram-bot/bun.lock +41 -0
- package/templates/telegram-bot/package.json +16 -0
- package/templates/telegram-bot/src/index.ts +236 -0
- package/templates/telegram-bot/src/jack-ai.ts +100 -0
- package/templates/telegram-bot/tsconfig.json +11 -0
- package/templates/telegram-bot/wrangler.jsonc +8 -0
- package/templates/cron/src/jobs.ts +0 -139
- package/templates/cron/src/webhooks.ts +0 -95
- package/templates/semantic-search/src/jack-vectorize.ts +0 -169
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project environment introspection service
|
|
3
|
+
*
|
|
4
|
+
* Consolidates multiple API calls into a single environment snapshot:
|
|
5
|
+
* project info, bindings, DB schema, crons, variables, and config issues.
|
|
6
|
+
*
|
|
7
|
+
* Used by both CLI and MCP (get_project_environment tool).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import {
|
|
13
|
+
type CronScheduleInfo,
|
|
14
|
+
executeManagedSql,
|
|
15
|
+
fetchProjectResources,
|
|
16
|
+
listCronSchedules as listCronSchedulesApi,
|
|
17
|
+
} from "../control-plane.ts";
|
|
18
|
+
import { type DeployMode, readProjectLink } from "../project-link.ts";
|
|
19
|
+
import { getProjectStatus } from "../project-operations.ts";
|
|
20
|
+
import {
|
|
21
|
+
type ControlPlaneResource,
|
|
22
|
+
type ResolvedResources,
|
|
23
|
+
convertControlPlaneResources,
|
|
24
|
+
parseWranglerResources,
|
|
25
|
+
} from "../resources.ts";
|
|
26
|
+
import { findWranglerConfig, hasWranglerConfig } from "../wrangler-config.ts";
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export interface ProjectEnvironment {
|
|
33
|
+
project: {
|
|
34
|
+
name: string;
|
|
35
|
+
url: string | null;
|
|
36
|
+
deploy_mode: DeployMode;
|
|
37
|
+
last_deploy: {
|
|
38
|
+
at: string | null;
|
|
39
|
+
status: string | null;
|
|
40
|
+
message: string | null;
|
|
41
|
+
source: string | null;
|
|
42
|
+
} | null;
|
|
43
|
+
};
|
|
44
|
+
bindings: EnvironmentBindings;
|
|
45
|
+
secrets_set: string[];
|
|
46
|
+
variables: Record<string, string>;
|
|
47
|
+
database: DatabaseSchema | null;
|
|
48
|
+
crons: CronEntry[];
|
|
49
|
+
issues: EnvironmentIssue[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface EnvironmentBindings {
|
|
53
|
+
d1?: { binding: string; database_name: string; database_id?: string };
|
|
54
|
+
r2?: Array<{ binding: string; bucket_name: string }>;
|
|
55
|
+
kv?: Array<{ binding: string; namespace_id: string; name?: string }>;
|
|
56
|
+
ai?: { binding: string };
|
|
57
|
+
vectorize?: Array<{ binding: string; index_name: string }>;
|
|
58
|
+
durable_objects?: Array<{ binding: string; class_name: string }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface DatabaseSchema {
|
|
62
|
+
name: string;
|
|
63
|
+
tables: TableSchema[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TableSchema {
|
|
67
|
+
name: string;
|
|
68
|
+
columns: ColumnSchema[];
|
|
69
|
+
row_count: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ColumnSchema {
|
|
73
|
+
name: string;
|
|
74
|
+
type: string;
|
|
75
|
+
pk: boolean;
|
|
76
|
+
notnull: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CronEntry {
|
|
80
|
+
expression: string;
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
last_run_status: string | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface EnvironmentIssue {
|
|
86
|
+
severity: "warning" | "error";
|
|
87
|
+
message: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Main Function
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get a consolidated environment snapshot for a project.
|
|
96
|
+
* Handles both managed and BYO deploy modes.
|
|
97
|
+
*/
|
|
98
|
+
export async function getProjectEnvironment(projectDir: string): Promise<ProjectEnvironment> {
|
|
99
|
+
const link = await readProjectLink(projectDir);
|
|
100
|
+
const deployMode: DeployMode = link?.deploy_mode ?? "byo";
|
|
101
|
+
|
|
102
|
+
// 1. Get project status (name, URL, deploy info)
|
|
103
|
+
const status = await getProjectStatus(undefined, projectDir);
|
|
104
|
+
if (!status) {
|
|
105
|
+
throw new Error("Project not found. Ensure you're in a valid jack project directory.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. Get bindings
|
|
109
|
+
const { bindings, rawResources, wranglerResources } = await getBindings(
|
|
110
|
+
projectDir,
|
|
111
|
+
deployMode,
|
|
112
|
+
link?.project_id,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// 3. Get variables from wrangler.jsonc
|
|
116
|
+
const variables = wranglerResources?.vars ?? {};
|
|
117
|
+
|
|
118
|
+
// 4. Get secrets names from wrangler.jsonc (we only report names, not values)
|
|
119
|
+
const secretsSet = await getSecretsNames(projectDir);
|
|
120
|
+
|
|
121
|
+
// 5. Get database schema (if D1 exists)
|
|
122
|
+
let database: DatabaseSchema | null = null;
|
|
123
|
+
if (bindings.d1) {
|
|
124
|
+
database = await getDatabaseSchema(
|
|
125
|
+
projectDir,
|
|
126
|
+
deployMode,
|
|
127
|
+
link?.project_id,
|
|
128
|
+
bindings.d1.database_name,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 6. Get crons (managed only)
|
|
133
|
+
let crons: CronEntry[] = [];
|
|
134
|
+
if (deployMode === "managed" && link?.project_id) {
|
|
135
|
+
crons = await getCrons(link.project_id);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 7. Detect issues
|
|
139
|
+
const issues = detectIssues(bindings, rawResources, wranglerResources, projectDir);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
project: {
|
|
143
|
+
name: status.name,
|
|
144
|
+
url: status.workerUrl,
|
|
145
|
+
deploy_mode: deployMode,
|
|
146
|
+
last_deploy:
|
|
147
|
+
status.lastDeployAt || status.lastDeployStatus
|
|
148
|
+
? {
|
|
149
|
+
at: status.lastDeployAt,
|
|
150
|
+
status: status.lastDeployStatus,
|
|
151
|
+
message: status.lastDeployMessage,
|
|
152
|
+
source: status.lastDeploySource,
|
|
153
|
+
}
|
|
154
|
+
: null,
|
|
155
|
+
},
|
|
156
|
+
bindings,
|
|
157
|
+
secrets_set: secretsSet,
|
|
158
|
+
variables,
|
|
159
|
+
database,
|
|
160
|
+
crons,
|
|
161
|
+
issues,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// Internal Helpers
|
|
167
|
+
// ============================================================================
|
|
168
|
+
|
|
169
|
+
async function getBindings(
|
|
170
|
+
projectDir: string,
|
|
171
|
+
deployMode: DeployMode,
|
|
172
|
+
projectId?: string,
|
|
173
|
+
): Promise<{
|
|
174
|
+
bindings: EnvironmentBindings;
|
|
175
|
+
rawResources?: ControlPlaneResource[];
|
|
176
|
+
wranglerResources?: ResolvedResources;
|
|
177
|
+
}> {
|
|
178
|
+
const result: EnvironmentBindings = {};
|
|
179
|
+
let rawResources: ControlPlaneResource[] | undefined;
|
|
180
|
+
let wranglerResources: ResolvedResources | undefined;
|
|
181
|
+
|
|
182
|
+
if (deployMode === "managed" && projectId) {
|
|
183
|
+
// Managed: fetch from control plane
|
|
184
|
+
rawResources = await fetchProjectResources(projectId);
|
|
185
|
+
const resolved = convertControlPlaneResources(rawResources);
|
|
186
|
+
wranglerResources = resolved;
|
|
187
|
+
|
|
188
|
+
if (resolved.d1) {
|
|
189
|
+
result.d1 = {
|
|
190
|
+
binding: resolved.d1.binding,
|
|
191
|
+
database_name: resolved.d1.name,
|
|
192
|
+
database_id: resolved.d1.id,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (resolved.r2?.length) {
|
|
196
|
+
result.r2 = resolved.r2.map((r) => ({
|
|
197
|
+
binding: r.binding,
|
|
198
|
+
bucket_name: r.name,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
if (resolved.kv?.length) {
|
|
202
|
+
result.kv = resolved.kv.map((k) => ({
|
|
203
|
+
binding: k.binding,
|
|
204
|
+
namespace_id: k.id,
|
|
205
|
+
name: k.name,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
if (resolved.ai) {
|
|
209
|
+
result.ai = { binding: resolved.ai.binding };
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
// BYO: parse from wrangler.jsonc
|
|
213
|
+
wranglerResources = await parseWranglerResources(projectDir);
|
|
214
|
+
|
|
215
|
+
if (wranglerResources.d1) {
|
|
216
|
+
result.d1 = {
|
|
217
|
+
binding: wranglerResources.d1.binding,
|
|
218
|
+
database_name: wranglerResources.d1.name,
|
|
219
|
+
database_id: wranglerResources.d1.id,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (wranglerResources.r2?.length) {
|
|
223
|
+
result.r2 = wranglerResources.r2.map((r) => ({
|
|
224
|
+
binding: r.binding,
|
|
225
|
+
bucket_name: r.name,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
if (wranglerResources.kv?.length) {
|
|
229
|
+
result.kv = wranglerResources.kv.map((k) => ({
|
|
230
|
+
binding: k.binding,
|
|
231
|
+
namespace_id: k.id,
|
|
232
|
+
name: k.name,
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
if (wranglerResources.ai) {
|
|
236
|
+
result.ai = { binding: wranglerResources.ai.binding };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Also parse wrangler.jsonc for vectorize and durable_objects (not in ResolvedResources)
|
|
241
|
+
await enrichFromWranglerConfig(projectDir, result);
|
|
242
|
+
|
|
243
|
+
return { bindings: result, rawResources, wranglerResources };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse vectorize indexes and durable objects from wrangler.jsonc,
|
|
248
|
+
* since these aren't covered by the standard ResolvedResources type.
|
|
249
|
+
*/
|
|
250
|
+
async function enrichFromWranglerConfig(
|
|
251
|
+
projectDir: string,
|
|
252
|
+
bindings: EnvironmentBindings,
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
255
|
+
if (!wranglerPath) return;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const { parseJsonc } = await import("../jsonc.ts");
|
|
259
|
+
const content = await Bun.file(wranglerPath).text();
|
|
260
|
+
const config = parseJsonc<{
|
|
261
|
+
vectorize?: Array<{ binding: string; index_name: string }>;
|
|
262
|
+
durable_objects?: {
|
|
263
|
+
bindings?: Array<{ name: string; class_name: string }>;
|
|
264
|
+
};
|
|
265
|
+
}>(content);
|
|
266
|
+
|
|
267
|
+
if (config.vectorize?.length) {
|
|
268
|
+
bindings.vectorize = config.vectorize.map((v) => ({
|
|
269
|
+
binding: v.binding,
|
|
270
|
+
index_name: v.index_name,
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (config.durable_objects?.bindings?.length) {
|
|
275
|
+
bindings.durable_objects = config.durable_objects.bindings.map((d) => ({
|
|
276
|
+
binding: d.name,
|
|
277
|
+
class_name: d.class_name,
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
// Failed to parse, skip enrichment
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get secret names from wrangler.jsonc (from vars that look like secrets)
|
|
287
|
+
* and any .dev.vars file.
|
|
288
|
+
*/
|
|
289
|
+
async function getSecretsNames(projectDir: string): Promise<string[]> {
|
|
290
|
+
const secrets = new Set<string>();
|
|
291
|
+
|
|
292
|
+
// Check .dev.vars for secret names (these are typically what's set via wrangler secret)
|
|
293
|
+
const devVarsPath = join(projectDir, ".dev.vars");
|
|
294
|
+
if (existsSync(devVarsPath)) {
|
|
295
|
+
try {
|
|
296
|
+
const content = await Bun.file(devVarsPath).text();
|
|
297
|
+
for (const line of content.split("\n")) {
|
|
298
|
+
const trimmed = line.trim();
|
|
299
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
300
|
+
const eqIdx = trimmed.indexOf("=");
|
|
301
|
+
if (eqIdx > 0) {
|
|
302
|
+
secrets.add(trimmed.slice(0, eqIdx).trim());
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
// Ignore read errors
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return Array.from(secrets);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get database schema via SQL introspection.
|
|
316
|
+
*/
|
|
317
|
+
async function getDatabaseSchema(
|
|
318
|
+
projectDir: string,
|
|
319
|
+
deployMode: DeployMode,
|
|
320
|
+
projectId: string | undefined,
|
|
321
|
+
databaseName: string,
|
|
322
|
+
): Promise<DatabaseSchema | null> {
|
|
323
|
+
try {
|
|
324
|
+
// Get table list
|
|
325
|
+
const tablesResult = await runSql(
|
|
326
|
+
projectDir,
|
|
327
|
+
deployMode,
|
|
328
|
+
projectId,
|
|
329
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%'",
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
if (!tablesResult?.length) {
|
|
333
|
+
return { name: databaseName, tables: [] };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const tableNames = tablesResult.map((row) => (row as { name: string }).name);
|
|
337
|
+
|
|
338
|
+
// Get schema + row counts in parallel across all tables
|
|
339
|
+
const tables = await Promise.all(
|
|
340
|
+
tableNames.map(async (tableName) => {
|
|
341
|
+
const escaped = tableName.replace(/"/g, '""');
|
|
342
|
+
const [columnsResult, countResult] = await Promise.all([
|
|
343
|
+
runSql(projectDir, deployMode, projectId, `PRAGMA table_info("${escaped}")`),
|
|
344
|
+
runSql(projectDir, deployMode, projectId, `SELECT COUNT(*) as count FROM "${escaped}"`),
|
|
345
|
+
]);
|
|
346
|
+
|
|
347
|
+
const columns: ColumnSchema[] = (columnsResult ?? []).map((col: unknown) => {
|
|
348
|
+
const c = col as {
|
|
349
|
+
name: string;
|
|
350
|
+
type: string;
|
|
351
|
+
pk: number;
|
|
352
|
+
notnull: number;
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
name: c.name,
|
|
356
|
+
type: c.type,
|
|
357
|
+
pk: c.pk === 1,
|
|
358
|
+
notnull: c.notnull === 1,
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const rowCount = (countResult?.[0] as { count: number })?.count ?? 0;
|
|
363
|
+
|
|
364
|
+
return { name: tableName, columns, row_count: rowCount };
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return { name: databaseName, tables };
|
|
369
|
+
} catch {
|
|
370
|
+
// DB introspection failed — return null rather than crashing the whole environment call
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Execute SQL against the project's D1 database.
|
|
377
|
+
* Routes through managed or BYO path.
|
|
378
|
+
*/
|
|
379
|
+
async function runSql(
|
|
380
|
+
projectDir: string,
|
|
381
|
+
deployMode: DeployMode,
|
|
382
|
+
projectId: string | undefined,
|
|
383
|
+
sql: string,
|
|
384
|
+
): Promise<unknown[] | null> {
|
|
385
|
+
if (deployMode === "managed" && projectId) {
|
|
386
|
+
const result = await executeManagedSql(projectId, sql);
|
|
387
|
+
return result.results ?? null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// BYO: use executeSql service
|
|
391
|
+
const { executeSql } = await import("./db-execute.ts");
|
|
392
|
+
const result = await executeSql({
|
|
393
|
+
projectDir,
|
|
394
|
+
sql,
|
|
395
|
+
allowWrite: false,
|
|
396
|
+
interactive: false,
|
|
397
|
+
});
|
|
398
|
+
return result.results ?? null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get cron schedules for a managed project.
|
|
403
|
+
*/
|
|
404
|
+
async function getCrons(projectId: string): Promise<CronEntry[]> {
|
|
405
|
+
try {
|
|
406
|
+
const schedules = await listCronSchedulesApi(projectId);
|
|
407
|
+
return schedules.map((s: CronScheduleInfo) => ({
|
|
408
|
+
expression: s.expression,
|
|
409
|
+
enabled: s.enabled,
|
|
410
|
+
last_run_status: s.last_run_status,
|
|
411
|
+
}));
|
|
412
|
+
} catch {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Detect configuration issues by comparing bindings against known resources.
|
|
419
|
+
*/
|
|
420
|
+
function detectIssues(
|
|
421
|
+
bindings: EnvironmentBindings,
|
|
422
|
+
_rawResources?: ControlPlaneResource[],
|
|
423
|
+
wranglerResources?: ResolvedResources,
|
|
424
|
+
projectDir?: string,
|
|
425
|
+
): EnvironmentIssue[] {
|
|
426
|
+
const issues: EnvironmentIssue[] = [];
|
|
427
|
+
|
|
428
|
+
// Check for wrangler config existence
|
|
429
|
+
if (projectDir && !hasWranglerConfig(projectDir)) {
|
|
430
|
+
issues.push({
|
|
431
|
+
severity: "warning",
|
|
432
|
+
message: "No wrangler config found in project directory",
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check for D1 binding without database_id (BYO projects)
|
|
437
|
+
if (bindings.d1 && !bindings.d1.database_id) {
|
|
438
|
+
issues.push({
|
|
439
|
+
severity: "warning",
|
|
440
|
+
message: `D1 binding '${bindings.d1.binding}' has no database_id — database may not be created yet`,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check for KV bindings without namespace_id
|
|
445
|
+
if (bindings.kv) {
|
|
446
|
+
for (const kv of bindings.kv) {
|
|
447
|
+
if (!kv.namespace_id) {
|
|
448
|
+
issues.push({
|
|
449
|
+
severity: "warning",
|
|
450
|
+
message: `KV binding '${kv.binding}' has no namespace_id — namespace may not be created yet`,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return issues;
|
|
457
|
+
}
|