@giselles-ai/sandkit 0.1.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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/adapters/drizzle.d.ts +83 -0
- package/dist/adapters/drizzle.js +9 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/memory.d.ts +6 -0
- package/dist/adapters/memory.js +8 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/sqlite-bun.d.ts +7 -0
- package/dist/adapters/sqlite-bun.js +8 -0
- package/dist/adapters/sqlite-bun.js.map +1 -0
- package/dist/bin.js +697 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-7DLK7LOM.js +44 -0
- package/dist/chunk-7DLK7LOM.js.map +1 -0
- package/dist/chunk-BDPTYR6V.js +407 -0
- package/dist/chunk-BDPTYR6V.js.map +1 -0
- package/dist/chunk-CSOBTLWV.js +202 -0
- package/dist/chunk-CSOBTLWV.js.map +1 -0
- package/dist/chunk-DLGUA3H7.js +9 -0
- package/dist/chunk-DLGUA3H7.js.map +1 -0
- package/dist/chunk-FSDVHEEX.js +45 -0
- package/dist/chunk-FSDVHEEX.js.map +1 -0
- package/dist/chunk-HVYCAAZQ.js +25 -0
- package/dist/chunk-HVYCAAZQ.js.map +1 -0
- package/dist/chunk-LC3IYBAL.js +100 -0
- package/dist/chunk-LC3IYBAL.js.map +1 -0
- package/dist/chunk-REGOUXVI.js +58 -0
- package/dist/chunk-REGOUXVI.js.map +1 -0
- package/dist/chunk-RMMOQD5Y.js +211 -0
- package/dist/chunk-RMMOQD5Y.js.map +1 -0
- package/dist/chunk-UDFWES6J.js +486 -0
- package/dist/chunk-UDFWES6J.js.map +1 -0
- package/dist/chunk-VISDS5T7.js +202 -0
- package/dist/chunk-VISDS5T7.js.map +1 -0
- package/dist/chunk-XM4HGRXW.js +37 -0
- package/dist/chunk-XM4HGRXW.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +397 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +1102 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/mock.d.ts +19 -0
- package/dist/integrations/mock.js +207 -0
- package/dist/integrations/mock.js.map +1 -0
- package/dist/integrations/vercel.d.ts +7 -0
- package/dist/integrations/vercel.js +400 -0
- package/dist/integrations/vercel.js.map +1 -0
- package/dist/policies/ai-gateway.d.ts +15 -0
- package/dist/policies/ai-gateway.js +12 -0
- package/dist/policies/ai-gateway.js.map +1 -0
- package/dist/policies/codex.d.ts +10 -0
- package/dist/policies/codex.js +12 -0
- package/dist/policies/codex.js.map +1 -0
- package/dist/policies/gemini.d.ts +10 -0
- package/dist/policies/gemini.js +12 -0
- package/dist/policies/gemini.js.map +1 -0
- package/dist/schema/index.d.ts +60 -0
- package/dist/schema/index.js +31 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/types-BCgprbo8.d.ts +47 -0
- package/dist/types-BEKQnjeb.d.ts +139 -0
- package/dist/types-Cy36bS1j.d.ts +138 -0
- package/package.json +126 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/generate.ts
|
|
4
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
|
|
7
|
+
// src/schema/generate.ts
|
|
8
|
+
var typeMap = {
|
|
9
|
+
sqlite: {
|
|
10
|
+
text: { type: "text" },
|
|
11
|
+
integer: { type: "integer" },
|
|
12
|
+
boolean: { type: "integer", options: "{ mode: 'boolean' }" },
|
|
13
|
+
json: { type: "text", options: "{ mode: 'json' }" },
|
|
14
|
+
timestamp: { type: "integer", options: "{ mode: 'timestamp_ms' }" }
|
|
15
|
+
},
|
|
16
|
+
postgresql: {
|
|
17
|
+
text: { type: "text" },
|
|
18
|
+
integer: { type: "integer" },
|
|
19
|
+
boolean: { type: "boolean" },
|
|
20
|
+
json: { type: "jsonb" },
|
|
21
|
+
timestamp: { type: "timestamp" }
|
|
22
|
+
},
|
|
23
|
+
mysql: {
|
|
24
|
+
text: { type: "text" },
|
|
25
|
+
integer: { type: "int" },
|
|
26
|
+
boolean: { type: "boolean" },
|
|
27
|
+
json: { type: "json" },
|
|
28
|
+
timestamp: { type: "timestamp" }
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var tableFactoryByDialect = {
|
|
32
|
+
sqlite: "sqliteTable",
|
|
33
|
+
postgresql: "pgTable",
|
|
34
|
+
mysql: "mysqlTable"
|
|
35
|
+
};
|
|
36
|
+
function renderTextSchema(model) {
|
|
37
|
+
const exportNameByTable = new Map(
|
|
38
|
+
model.tables.map((table) => [table.name, table.exportName ?? table.name])
|
|
39
|
+
);
|
|
40
|
+
const entries = model.tables.map((table) => {
|
|
41
|
+
const builder = tableFactoryByDialect[model.dialect];
|
|
42
|
+
const tableExpr = model.dialect === "sqlite" ? `${builder}("${table.name}", {` : `${builder}("${table.name}", {
|
|
43
|
+
`;
|
|
44
|
+
const row = table.columns.map((column) => {
|
|
45
|
+
const mappedDialect = typeMap[model.dialect];
|
|
46
|
+
if (!mappedDialect) {
|
|
47
|
+
throw new Error(`Unsupported dialect "${model.dialect}"`);
|
|
48
|
+
}
|
|
49
|
+
const mappedType = mappedDialect[column.type];
|
|
50
|
+
if (!mappedType) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Unsupported column type "${column.type}" for dialect "${model.dialect}"`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const optionsSuffix = mappedType.options ? `, ${mappedType.options}` : "";
|
|
56
|
+
const defaultValue = column.defaultValue === null ? "default null" : "";
|
|
57
|
+
const required = column.nullable === true ? "" : ".notNull()";
|
|
58
|
+
const unique = column.unique ? ".unique()" : "";
|
|
59
|
+
const primary = column.primaryKey ? ".primaryKey()" : "";
|
|
60
|
+
const ref = column.references ? `.references(() => ${exportNameByTable.get(column.references.table) ?? column.references.table}.${column.references.field})` : "";
|
|
61
|
+
return ` ${column.name}: ${mappedType.type}("${column.name}"${optionsSuffix})${required}${defaultValue}${unique}${primary}${ref}`;
|
|
62
|
+
}).join(",\n");
|
|
63
|
+
const rows = row ? `
|
|
64
|
+
${row}
|
|
65
|
+
` : "\n";
|
|
66
|
+
const exportName = table.exportName ?? table.name;
|
|
67
|
+
return `export const ${exportName} = ${tableExpr}${rows}});`;
|
|
68
|
+
}).join("\n\n");
|
|
69
|
+
const imports = {
|
|
70
|
+
sqlite: 'import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";',
|
|
71
|
+
postgresql: 'import { boolean, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";',
|
|
72
|
+
mysql: 'import { boolean, int, json, mysqlTable, text, timestamp } from "drizzle-orm/mysql-core";'
|
|
73
|
+
};
|
|
74
|
+
return `// This file is generated from Sandkit schema model.
|
|
75
|
+
// Provider: ${model.dialect}
|
|
76
|
+
// Version: ${model.version}
|
|
77
|
+
|
|
78
|
+
${imports[model.dialect]}
|
|
79
|
+
|
|
80
|
+
${entries}
|
|
81
|
+
|
|
82
|
+
export const sandkitSchema = {
|
|
83
|
+
${model.tables.map((table) => {
|
|
84
|
+
const exportName = table.exportName ?? table.name;
|
|
85
|
+
return ` ${exportName},`;
|
|
86
|
+
}).join("\n")}
|
|
87
|
+
};
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
function createGeneratePayload(dialect, model) {
|
|
91
|
+
return {
|
|
92
|
+
dialect,
|
|
93
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
94
|
+
summary: {
|
|
95
|
+
tableCount: model.tables.length,
|
|
96
|
+
tableNames: model.tables.map((table) => table.name)
|
|
97
|
+
},
|
|
98
|
+
schemaText: renderTextSchema(model)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/schema/model.ts
|
|
103
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
104
|
+
var baseSchemaName = "sandkit";
|
|
105
|
+
var tablePrefix = "sandkit_";
|
|
106
|
+
var sandkitWorkspaceExport = "sandkitWorkspaces";
|
|
107
|
+
var sandkitRunExport = "sandkitRuns";
|
|
108
|
+
var sandkitPolicyExport = "sandkitPolicies";
|
|
109
|
+
var sandkitSetupStateExport = "sandkitSetupStates";
|
|
110
|
+
var sandkitWorkspaceTable = {
|
|
111
|
+
name: `${tablePrefix}workspaces`,
|
|
112
|
+
exportName: sandkitWorkspaceExport,
|
|
113
|
+
comment: "Persistent workspace metadata.",
|
|
114
|
+
columns: [
|
|
115
|
+
{
|
|
116
|
+
name: "id",
|
|
117
|
+
type: "text",
|
|
118
|
+
primaryKey: true,
|
|
119
|
+
nullable: false,
|
|
120
|
+
comment: "Workspace identifier."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "metadata",
|
|
124
|
+
type: "json",
|
|
125
|
+
nullable: true,
|
|
126
|
+
comment: "Durable workspace metadata, including sandbox lifecycle state."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "sandboxId",
|
|
130
|
+
type: "text",
|
|
131
|
+
nullable: true,
|
|
132
|
+
comment: "Stable id returned by the sandbox provider."
|
|
133
|
+
},
|
|
134
|
+
{ name: "status", type: "text", nullable: false },
|
|
135
|
+
{ name: "name", type: "text", nullable: true },
|
|
136
|
+
{
|
|
137
|
+
name: "lastResumedAt",
|
|
138
|
+
type: "timestamp",
|
|
139
|
+
nullable: true,
|
|
140
|
+
comment: "Most recent point when Sandkit resolved a concrete sandbox session."
|
|
141
|
+
},
|
|
142
|
+
{ name: "createdAt", type: "timestamp", nullable: false },
|
|
143
|
+
{ name: "updatedAt", type: "timestamp", nullable: false }
|
|
144
|
+
],
|
|
145
|
+
indexes: [{ name: "workspaces_status_idx", columns: ["status"] }]
|
|
146
|
+
};
|
|
147
|
+
var sandkitRunTable = {
|
|
148
|
+
name: `${tablePrefix}runs`,
|
|
149
|
+
exportName: sandkitRunExport,
|
|
150
|
+
comment: "Execution facts for unit-of-work runs.",
|
|
151
|
+
columns: [
|
|
152
|
+
{
|
|
153
|
+
name: "id",
|
|
154
|
+
type: "text",
|
|
155
|
+
primaryKey: true,
|
|
156
|
+
nullable: false,
|
|
157
|
+
comment: "Run identifier."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "workspace_id",
|
|
161
|
+
type: "text",
|
|
162
|
+
nullable: false,
|
|
163
|
+
comment: "FK to workspace.",
|
|
164
|
+
references: {
|
|
165
|
+
table: sandkitWorkspaceTable.name,
|
|
166
|
+
field: "id"
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{ name: "command", type: "text", nullable: false },
|
|
170
|
+
{
|
|
171
|
+
name: "args",
|
|
172
|
+
type: "json",
|
|
173
|
+
nullable: true,
|
|
174
|
+
comment: "Arguments for the executed command."
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "provider",
|
|
178
|
+
type: "text",
|
|
179
|
+
nullable: false,
|
|
180
|
+
comment: "Driver identity used for execution (e.g. vercel-sandbox, mock)."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "execution_target_id",
|
|
184
|
+
type: "text",
|
|
185
|
+
nullable: false,
|
|
186
|
+
comment: "Driver execution target used by the command run."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "status",
|
|
190
|
+
type: "text",
|
|
191
|
+
nullable: false,
|
|
192
|
+
comment: "Unit status."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "policy_snapshot_id",
|
|
196
|
+
type: "text",
|
|
197
|
+
nullable: true,
|
|
198
|
+
comment: "FK to policy snapshot row."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "provider_commit",
|
|
202
|
+
type: "json",
|
|
203
|
+
nullable: true,
|
|
204
|
+
comment: "Provider durability metadata at run completion."
|
|
205
|
+
},
|
|
206
|
+
{ name: "exit_code", type: "integer", nullable: true },
|
|
207
|
+
{ name: "stdout", type: "text", nullable: true },
|
|
208
|
+
{ name: "stderr", type: "text", nullable: true },
|
|
209
|
+
{ name: "started_at", type: "timestamp", nullable: false },
|
|
210
|
+
{ name: "finished_at", type: "timestamp", nullable: true }
|
|
211
|
+
],
|
|
212
|
+
indexes: [
|
|
213
|
+
{ name: "runs_workspace_id_idx", columns: ["workspace_id"] },
|
|
214
|
+
{ name: "runs_finished_at_idx", columns: ["finished_at"] },
|
|
215
|
+
{ name: "runs_status_idx", columns: ["status"] }
|
|
216
|
+
]
|
|
217
|
+
};
|
|
218
|
+
var sandkitPolicyTable = {
|
|
219
|
+
name: `${tablePrefix}policies`,
|
|
220
|
+
exportName: sandkitPolicyExport,
|
|
221
|
+
comment: "Network policy snapshots and source of truth.",
|
|
222
|
+
columns: [
|
|
223
|
+
{
|
|
224
|
+
name: "id",
|
|
225
|
+
type: "text",
|
|
226
|
+
primaryKey: true,
|
|
227
|
+
nullable: false
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "workspace_id",
|
|
231
|
+
type: "text",
|
|
232
|
+
nullable: false,
|
|
233
|
+
comment: "FK to workspace.",
|
|
234
|
+
references: {
|
|
235
|
+
table: sandkitWorkspaceTable.name,
|
|
236
|
+
field: "id"
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{ name: "policy_id", type: "text", nullable: false },
|
|
240
|
+
{ name: "config", type: "json", nullable: false },
|
|
241
|
+
{ name: "created_at", type: "timestamp", nullable: false }
|
|
242
|
+
],
|
|
243
|
+
indexes: [{ name: "policies_workspace_id_idx", columns: ["workspace_id"] }]
|
|
244
|
+
};
|
|
245
|
+
var sandkitSetupStateTable = {
|
|
246
|
+
name: `${tablePrefix}setup_states`,
|
|
247
|
+
exportName: sandkitSetupStateExport,
|
|
248
|
+
comment: "Durable shared setup state shared by all workspaces on one adapter.",
|
|
249
|
+
columns: [
|
|
250
|
+
{
|
|
251
|
+
name: "id",
|
|
252
|
+
type: "text",
|
|
253
|
+
primaryKey: true,
|
|
254
|
+
nullable: false,
|
|
255
|
+
comment: "Shared setup state identifier."
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "state",
|
|
259
|
+
type: "json",
|
|
260
|
+
nullable: false,
|
|
261
|
+
comment: "Serialized durable setup snapshot state."
|
|
262
|
+
},
|
|
263
|
+
{ name: "createdAt", type: "timestamp", nullable: false },
|
|
264
|
+
{ name: "updatedAt", type: "timestamp", nullable: false }
|
|
265
|
+
]
|
|
266
|
+
};
|
|
267
|
+
function createSandkitSchemaModel(dialect = "sqlite") {
|
|
268
|
+
const schemaName = dialect === "postgresql" ? `${baseSchemaName}_schema` : baseSchemaName;
|
|
269
|
+
return {
|
|
270
|
+
name: schemaName,
|
|
271
|
+
version: 4,
|
|
272
|
+
dialect,
|
|
273
|
+
tables: [
|
|
274
|
+
{
|
|
275
|
+
...sandkitWorkspaceTable,
|
|
276
|
+
schema: dialect === "postgresql" ? "public" : void 0
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
...sandkitRunTable,
|
|
280
|
+
schema: dialect === "postgresql" ? "public" : void 0
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
...sandkitPolicyTable,
|
|
284
|
+
schema: dialect === "postgresql" ? "public" : void 0
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
...sandkitSetupStateTable,
|
|
288
|
+
schema: dialect === "postgresql" ? "public" : void 0
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function createModelSnapshot(dialect = "sqlite") {
|
|
294
|
+
return {
|
|
295
|
+
model: createSandkitSchemaModel(dialect),
|
|
296
|
+
generatedAt: nowIso()
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/cli/discovery.ts
|
|
301
|
+
import { existsSync, readFileSync } from "fs";
|
|
302
|
+
import { join } from "path";
|
|
303
|
+
import { createInterface } from "readline";
|
|
304
|
+
var drizzleConfigFiles = [
|
|
305
|
+
"drizzle.config.ts",
|
|
306
|
+
"drizzle.config.js",
|
|
307
|
+
"drizzle.config.mjs",
|
|
308
|
+
"drizzle.config.cjs",
|
|
309
|
+
"drizzle.config.mts",
|
|
310
|
+
"drizzle.config.cts",
|
|
311
|
+
"drizzle.config.json",
|
|
312
|
+
"drizzle.config.yaml",
|
|
313
|
+
"drizzle.config.yml"
|
|
314
|
+
];
|
|
315
|
+
var drizzleDependency = "drizzle-orm";
|
|
316
|
+
function parsePackageJsonDependencies(packagePath, evidence) {
|
|
317
|
+
if (!existsSync(packagePath)) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
let packageData = {};
|
|
321
|
+
try {
|
|
322
|
+
packageData = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
323
|
+
} catch {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
const dependencies = { ...packageData.dependencies, ...packageData.devDependencies };
|
|
327
|
+
const depNames = Object.keys(dependencies);
|
|
328
|
+
if (!depNames.includes(drizzleDependency)) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
evidence.push({
|
|
332
|
+
source: "package.json",
|
|
333
|
+
signal: "Dependency `drizzle-orm` found.",
|
|
334
|
+
score: 1.1
|
|
335
|
+
});
|
|
336
|
+
const sqliteSignals = [
|
|
337
|
+
["better-sqlite3", "sqlite"],
|
|
338
|
+
["libsql", "sqlite"],
|
|
339
|
+
["@libsql/client", "sqlite"],
|
|
340
|
+
["@turso", "sqlite"]
|
|
341
|
+
].flatMap(
|
|
342
|
+
([name, provider]) => depNames.some((dep) => dep.includes(name)) ? [{ provider, weight: 1.6 }] : []
|
|
343
|
+
);
|
|
344
|
+
const postgresSignals = [
|
|
345
|
+
["pg", "postgresql"],
|
|
346
|
+
["postgres", "postgresql"],
|
|
347
|
+
["@vercel/postgres", "postgresql"],
|
|
348
|
+
["@neondatabase/serverless", "postgresql"]
|
|
349
|
+
].flatMap(
|
|
350
|
+
([name, provider]) => depNames.some((dep) => dep.includes(name)) ? [{ provider, weight: 1.6 }] : []
|
|
351
|
+
);
|
|
352
|
+
const addSignals = (signals) => signals.forEach(
|
|
353
|
+
(entry) => evidence.push({
|
|
354
|
+
source: "package.json",
|
|
355
|
+
signal: `Dependency implies ${entry.provider} driver usage (${entry.provider} signal).`,
|
|
356
|
+
provider: entry.provider,
|
|
357
|
+
score: entry.weight
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
addSignals(sqliteSignals);
|
|
361
|
+
addSignals(postgresSignals);
|
|
362
|
+
return [...sqliteSignals, ...postgresSignals];
|
|
363
|
+
}
|
|
364
|
+
function inspectDrizzleConfig(cwd, evidence) {
|
|
365
|
+
const results = [];
|
|
366
|
+
for (const filename of drizzleConfigFiles) {
|
|
367
|
+
const configPath = join(cwd, filename);
|
|
368
|
+
if (!existsSync(configPath)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
let text = "";
|
|
372
|
+
try {
|
|
373
|
+
text = readFileSync(configPath, "utf8");
|
|
374
|
+
} catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
evidence.push({
|
|
378
|
+
source: filename,
|
|
379
|
+
signal: `Found drizzle config file: ${filename}.`,
|
|
380
|
+
score: 0.6
|
|
381
|
+
});
|
|
382
|
+
if (/dialect\s*:\s*["']sqlite["']/i.test(text)) {
|
|
383
|
+
evidence.push({
|
|
384
|
+
source: filename,
|
|
385
|
+
signal: "Drizzle config sets dialect to sqlite.",
|
|
386
|
+
provider: "sqlite",
|
|
387
|
+
score: 3
|
|
388
|
+
});
|
|
389
|
+
return [{ provider: "sqlite", weight: 3 }];
|
|
390
|
+
}
|
|
391
|
+
if (/dialect\s*:\s*["']postgresql["']/i.test(text)) {
|
|
392
|
+
evidence.push({
|
|
393
|
+
source: filename,
|
|
394
|
+
signal: "Drizzle config sets dialect to postgresql.",
|
|
395
|
+
provider: "postgresql",
|
|
396
|
+
score: 3
|
|
397
|
+
});
|
|
398
|
+
return [{ provider: "postgresql", weight: 3 }];
|
|
399
|
+
}
|
|
400
|
+
if (/dialect\s*:\s*["']mysql["']/i.test(text)) {
|
|
401
|
+
evidence.push({
|
|
402
|
+
source: filename,
|
|
403
|
+
signal: "Drizzle config sets dialect to mysql.",
|
|
404
|
+
provider: "mysql",
|
|
405
|
+
score: 3
|
|
406
|
+
});
|
|
407
|
+
return [{ provider: "mysql", weight: 3 }];
|
|
408
|
+
}
|
|
409
|
+
const dbUrlMatch = text.match(/url\s*:\s*["'`](.*?)["'`]/i);
|
|
410
|
+
if (dbUrlMatch?.[1]) {
|
|
411
|
+
const url = dbUrlMatch[1].toLowerCase();
|
|
412
|
+
if (url.includes("turso") || url.includes("libsql") || url.includes("sqlite")) {
|
|
413
|
+
evidence.push({
|
|
414
|
+
source: filename,
|
|
415
|
+
signal: "Drizzle config DB URL contains sqlite/libsql/turso hints.",
|
|
416
|
+
provider: "sqlite",
|
|
417
|
+
score: 2
|
|
418
|
+
});
|
|
419
|
+
results.push({ provider: "sqlite", weight: 2 });
|
|
420
|
+
}
|
|
421
|
+
if (url.includes("postgres")) {
|
|
422
|
+
evidence.push({
|
|
423
|
+
source: filename,
|
|
424
|
+
signal: "Drizzle config DB URL contains postgres hints.",
|
|
425
|
+
provider: "postgresql",
|
|
426
|
+
score: 2
|
|
427
|
+
});
|
|
428
|
+
results.push({ provider: "postgresql", weight: 2 });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return results;
|
|
433
|
+
}
|
|
434
|
+
function collectEvidence(cwd) {
|
|
435
|
+
const evidence = [];
|
|
436
|
+
const packageSignals = parsePackageJsonDependencies(join(cwd, "package.json"), evidence);
|
|
437
|
+
const drizzleSignals = inspectDrizzleConfig(cwd, evidence);
|
|
438
|
+
return {
|
|
439
|
+
evidence,
|
|
440
|
+
isDrizzleProject: packageSignals.length > 0 || drizzleSignals.length > 0
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function rankProviderSignals(evidence) {
|
|
444
|
+
const totals = { sqlite: 0, postgresql: 0, mysql: 0 };
|
|
445
|
+
for (const item of evidence) {
|
|
446
|
+
if (!item.provider) continue;
|
|
447
|
+
totals[item.provider] += item.score;
|
|
448
|
+
}
|
|
449
|
+
const ranked = Object.entries(totals).sort((a, b) => b[1] - a[1]).filter((entry) => entry[1] > 0);
|
|
450
|
+
if (ranked.length === 0) {
|
|
451
|
+
return { provider: void 0, confidence: 0 };
|
|
452
|
+
}
|
|
453
|
+
const [winner, winnerScore] = ranked[0];
|
|
454
|
+
const secondScore = ranked[1]?.[1] ?? 0;
|
|
455
|
+
const confidence = winnerScore === 0 ? 0 : winnerScore / (winnerScore + secondScore);
|
|
456
|
+
return { provider: winner, confidence };
|
|
457
|
+
}
|
|
458
|
+
function promptWithChoices(question, options, fallback) {
|
|
459
|
+
return new Promise((resolve) => {
|
|
460
|
+
const rl = createInterface({
|
|
461
|
+
input: process.stdin,
|
|
462
|
+
output: process.stdout
|
|
463
|
+
});
|
|
464
|
+
rl.question(question, (answer) => {
|
|
465
|
+
rl.close();
|
|
466
|
+
const normalized = answer.trim().toLowerCase();
|
|
467
|
+
if (options.includes(normalized)) {
|
|
468
|
+
resolve(normalized);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
resolve(fallback);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function resolveProviderWithDiscovery(options, cwd = process.cwd()) {
|
|
476
|
+
const requestedProvider = options.provider ?? options.dialect;
|
|
477
|
+
const normalizedProvider = requestedProvider === "pg" ? "postgresql" : requestedProvider;
|
|
478
|
+
if (normalizedProvider) {
|
|
479
|
+
if (normalizedProvider !== "sqlite" && normalizedProvider !== "postgresql" && normalizedProvider !== "mysql") {
|
|
480
|
+
throw new Error("Invalid provider. Use one of: sqlite, postgresql, mysql.");
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
adapter: "drizzle",
|
|
484
|
+
provider: normalizedProvider,
|
|
485
|
+
confidence: 1,
|
|
486
|
+
evidence: [
|
|
487
|
+
{
|
|
488
|
+
source: "cli",
|
|
489
|
+
signal: `Provider explicitly set as ${normalizedProvider}.`,
|
|
490
|
+
provider: normalizedProvider,
|
|
491
|
+
score: 100
|
|
492
|
+
}
|
|
493
|
+
],
|
|
494
|
+
prompted: false
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (options.adapter && options.adapter !== "drizzle") {
|
|
498
|
+
throw new Error("Unsupported adapter. This release supports --adapter drizzle only.");
|
|
499
|
+
}
|
|
500
|
+
const { evidence, isDrizzleProject } = collectEvidence(cwd);
|
|
501
|
+
const inference = rankProviderSignals(evidence);
|
|
502
|
+
const adapter = isDrizzleProject ? "drizzle" : "drizzle";
|
|
503
|
+
if (!isDrizzleProject) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
"Could not detect a drizzle project from this directory. Run inside a Drizzle project and pass --dialect/--adapter explicitly if needed."
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (inference.provider && inference.confidence >= 0.75) {
|
|
509
|
+
return {
|
|
510
|
+
adapter,
|
|
511
|
+
provider: inference.provider,
|
|
512
|
+
confidence: inference.confidence,
|
|
513
|
+
evidence,
|
|
514
|
+
prompted: false
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
"Could not infer database provider confidently. Re-run with --dialect <sqlite|postgresql|pg>."
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const prompt = await promptWithChoices(
|
|
523
|
+
`Could not infer the database provider from repo signals.
|
|
524
|
+
Select provider [sqlite|postgresql|mysql] (default sqlite): `,
|
|
525
|
+
["sqlite", "postgresql", "mysql"],
|
|
526
|
+
"sqlite"
|
|
527
|
+
);
|
|
528
|
+
return {
|
|
529
|
+
adapter,
|
|
530
|
+
provider: prompt,
|
|
531
|
+
confidence: inference.confidence,
|
|
532
|
+
evidence,
|
|
533
|
+
prompted: true
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/cli/generate.ts
|
|
538
|
+
function usage() {
|
|
539
|
+
return [
|
|
540
|
+
"Usage: sandkit generate [--adapter drizzle] [--dialect <sqlite|postgresql|pg>] [--out <file>] [--stdout]",
|
|
541
|
+
"Example: npx @giselles-ai/sandkit generate --dialect sqlite --stdout",
|
|
542
|
+
"Example: npx @giselles-ai/sandkit generate --adapter drizzle --dialect postgresql --stdout",
|
|
543
|
+
"If --stdout is omitted, output is shown to console by default.",
|
|
544
|
+
"Default output file: db/schema/sandkit.ts"
|
|
545
|
+
].join("\n");
|
|
546
|
+
}
|
|
547
|
+
function parseGenerateArgs(argv) {
|
|
548
|
+
let provider;
|
|
549
|
+
let dialect;
|
|
550
|
+
let adapter;
|
|
551
|
+
let out;
|
|
552
|
+
let stdout = false;
|
|
553
|
+
for (let i = 0; i < argv.length; i++) {
|
|
554
|
+
const arg = argv[i];
|
|
555
|
+
if (arg === "--adapter") {
|
|
556
|
+
const next = argv[i + 1];
|
|
557
|
+
if (next === "drizzle") {
|
|
558
|
+
adapter = next;
|
|
559
|
+
i++;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
throw new Error("Unsupported --adapter value. Use --adapter drizzle.");
|
|
563
|
+
}
|
|
564
|
+
if (arg === "--provider") {
|
|
565
|
+
const next = argv[i + 1];
|
|
566
|
+
if (next === "sqlite" || next === "postgresql" || next === "mysql") {
|
|
567
|
+
provider = next;
|
|
568
|
+
i++;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
throw new Error(`Invalid provider: ${next}`);
|
|
572
|
+
}
|
|
573
|
+
if (arg === "--dialect") {
|
|
574
|
+
const next = argv[i + 1];
|
|
575
|
+
if (next === "sqlite" || next === "postgresql" || next === "pg") {
|
|
576
|
+
dialect = next === "pg" ? "postgresql" : next;
|
|
577
|
+
i++;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
throw new Error(`Invalid dialect: ${next}`);
|
|
581
|
+
}
|
|
582
|
+
if (arg === "--out") {
|
|
583
|
+
const next = argv[i + 1];
|
|
584
|
+
if (!next) {
|
|
585
|
+
throw new Error("Missing --out path");
|
|
586
|
+
}
|
|
587
|
+
out = next;
|
|
588
|
+
i++;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (arg === "--stdout") {
|
|
592
|
+
stdout = true;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (arg === "--help" || arg === "-h") {
|
|
596
|
+
throw new Error(usage());
|
|
597
|
+
}
|
|
598
|
+
if (arg === "generate") {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
provider,
|
|
605
|
+
dialect,
|
|
606
|
+
adapter,
|
|
607
|
+
out,
|
|
608
|
+
stdout
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function runGenerateCommand(options) {
|
|
612
|
+
const snapshot = createModelSnapshot(options.provider);
|
|
613
|
+
const payload = createGeneratePayload(options.provider, snapshot.model);
|
|
614
|
+
const defaultOutputFile = options.adapter === "drizzle" || options.adapter === void 0 ? "db/schema/sandkit.ts" : `sandkit-schema.${options.provider}.ts`;
|
|
615
|
+
const outputFile = options.out ?? defaultOutputFile;
|
|
616
|
+
const result = {
|
|
617
|
+
command: "generate",
|
|
618
|
+
provider: options.provider,
|
|
619
|
+
outputTarget: options.stdout ? "stdout" : "file",
|
|
620
|
+
outputFile: options.stdout ? null : outputFile,
|
|
621
|
+
payload: {
|
|
622
|
+
generatedAt: payload.generatedAt,
|
|
623
|
+
summary: payload.summary,
|
|
624
|
+
schemaText: payload.schemaText
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
if (!options.stdout) {
|
|
628
|
+
mkdirSync(dirname(outputFile), { recursive: true });
|
|
629
|
+
writeFileSync(outputFile, payload.schemaText, "utf8");
|
|
630
|
+
}
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
async function runCliGenerate(argv) {
|
|
634
|
+
const options = parseGenerateArgs(argv);
|
|
635
|
+
const discovered = await resolveProviderWithDiscovery({
|
|
636
|
+
provider: options.provider,
|
|
637
|
+
adapter: options.adapter,
|
|
638
|
+
dialect: options.dialect,
|
|
639
|
+
stdout: false,
|
|
640
|
+
out: options.out
|
|
641
|
+
});
|
|
642
|
+
return runGenerateCommand({
|
|
643
|
+
provider: discovered.provider,
|
|
644
|
+
adapter: discovered.adapter,
|
|
645
|
+
dialect: options.dialect,
|
|
646
|
+
out: options.out,
|
|
647
|
+
stdout: options.stdout
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/cli/index.ts
|
|
652
|
+
function buildHelp() {
|
|
653
|
+
return [
|
|
654
|
+
"Sandkit CLI",
|
|
655
|
+
"Commands: generate",
|
|
656
|
+
"",
|
|
657
|
+
" sandkit generate [--adapter drizzle] [--dialect <sqlite|postgresql|pg>] [--stdout] [--out file]",
|
|
658
|
+
"",
|
|
659
|
+
"When --stdout is set, output is returned in memory and printed by caller."
|
|
660
|
+
].join("\n");
|
|
661
|
+
}
|
|
662
|
+
function parseArgs(argv) {
|
|
663
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
664
|
+
throw new Error(buildHelp());
|
|
665
|
+
}
|
|
666
|
+
const command = argv[0];
|
|
667
|
+
if (command !== "generate") {
|
|
668
|
+
throw new Error(`Unknown command: ${command}`);
|
|
669
|
+
}
|
|
670
|
+
const rest = argv.slice(1);
|
|
671
|
+
const commandOptions = parseGenerateArgs(rest);
|
|
672
|
+
return { command, ...commandOptions, rest };
|
|
673
|
+
}
|
|
674
|
+
async function runCli(argv = process.argv.slice(2)) {
|
|
675
|
+
const parsed = parseArgs(argv);
|
|
676
|
+
if (parsed.command === "generate") {
|
|
677
|
+
const { rest } = parsed;
|
|
678
|
+
return runCliGenerate(rest);
|
|
679
|
+
}
|
|
680
|
+
throw new Error(`Unhandled command: ${parsed.command}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/cli/bin.ts
|
|
684
|
+
var args = process.argv.slice(2);
|
|
685
|
+
try {
|
|
686
|
+
const result = await runCli(args);
|
|
687
|
+
if (result.outputTarget === "stdout") {
|
|
688
|
+
process.stdout.write(`${result.payload.schemaText}
|
|
689
|
+
`);
|
|
690
|
+
}
|
|
691
|
+
} catch (error) {
|
|
692
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
693
|
+
process.stderr.write(`${message}
|
|
694
|
+
`);
|
|
695
|
+
process.exitCode = 1;
|
|
696
|
+
}
|
|
697
|
+
//# sourceMappingURL=bin.js.map
|