tthr 0.0.27 → 0.0.28
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/dist/index.js +130 -45
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,14 +167,16 @@ async function deploySchemaToServer(projectId, token, schemaPath, dryRun) {
|
|
|
167
167
|
const schemaSource = await fs3.readFile(schemaPath, "utf-8");
|
|
168
168
|
const tables = parseSchema(schemaSource);
|
|
169
169
|
spinner.text = `Found ${tables.length} table(s)`;
|
|
170
|
+
const sql = generateSchemaSQL(tables);
|
|
170
171
|
if (dryRun) {
|
|
171
172
|
spinner.info("Dry run - would deploy schema:");
|
|
172
173
|
for (const table of tables) {
|
|
173
|
-
console.log(chalk2.dim(` - ${table.name}`));
|
|
174
|
+
console.log(chalk2.dim(` - ${table.name} (${Object.keys(table.columns).length} columns)`));
|
|
174
175
|
}
|
|
176
|
+
console.log(chalk2.bold("\nGenerated SQL:\n"));
|
|
177
|
+
console.log(chalk2.cyan(sql));
|
|
175
178
|
return;
|
|
176
179
|
}
|
|
177
|
-
const sql = generateSchemaSQL(tables);
|
|
178
180
|
spinner.text = "Deploying schema...";
|
|
179
181
|
const response = await fetch(`${API_URL}/projects/${projectId}/deploy/schema`, {
|
|
180
182
|
method: "POST",
|
|
@@ -333,54 +335,63 @@ function parseSchema(source) {
|
|
|
333
335
|
}
|
|
334
336
|
return tables;
|
|
335
337
|
}
|
|
338
|
+
function getColumnSqlType(type) {
|
|
339
|
+
switch (type) {
|
|
340
|
+
case "text":
|
|
341
|
+
return "TEXT";
|
|
342
|
+
case "integer":
|
|
343
|
+
return "INTEGER";
|
|
344
|
+
case "real":
|
|
345
|
+
return "REAL";
|
|
346
|
+
case "blob":
|
|
347
|
+
return "BLOB";
|
|
348
|
+
case "timestamp":
|
|
349
|
+
return "TEXT";
|
|
350
|
+
case "boolean":
|
|
351
|
+
return "INTEGER";
|
|
352
|
+
case "json":
|
|
353
|
+
return "TEXT";
|
|
354
|
+
default:
|
|
355
|
+
return "TEXT";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function buildColumnSql(colName, def, forAlterTable = false) {
|
|
359
|
+
const sqlType = getColumnSqlType(def.type);
|
|
360
|
+
let colSql = `${colName} ${sqlType}`;
|
|
361
|
+
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
362
|
+
if (def.notNull && !forAlterTable) colSql += " NOT NULL";
|
|
363
|
+
if (def.unique) colSql += " UNIQUE";
|
|
364
|
+
if (def.hasDefault && def.type === "timestamp") {
|
|
365
|
+
colSql += " DEFAULT (datetime('now'))";
|
|
366
|
+
}
|
|
367
|
+
if (def.references) {
|
|
368
|
+
const [refTable, refCol] = def.references.split(".");
|
|
369
|
+
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
370
|
+
}
|
|
371
|
+
return colSql;
|
|
372
|
+
}
|
|
336
373
|
function generateSchemaSQL(tables) {
|
|
337
374
|
const statements = [];
|
|
338
375
|
for (const table of tables) {
|
|
339
376
|
const columnDefs = [];
|
|
340
377
|
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
341
378
|
const def = colDef;
|
|
342
|
-
|
|
343
|
-
switch (def.type) {
|
|
344
|
-
case "text":
|
|
345
|
-
sqlType = "TEXT";
|
|
346
|
-
break;
|
|
347
|
-
case "integer":
|
|
348
|
-
sqlType = "INTEGER";
|
|
349
|
-
break;
|
|
350
|
-
case "real":
|
|
351
|
-
sqlType = "REAL";
|
|
352
|
-
break;
|
|
353
|
-
case "blob":
|
|
354
|
-
sqlType = "BLOB";
|
|
355
|
-
break;
|
|
356
|
-
case "timestamp":
|
|
357
|
-
sqlType = "TEXT";
|
|
358
|
-
break;
|
|
359
|
-
case "boolean":
|
|
360
|
-
sqlType = "INTEGER";
|
|
361
|
-
break;
|
|
362
|
-
case "json":
|
|
363
|
-
sqlType = "TEXT";
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
let colSql = `${colName} ${sqlType}`;
|
|
367
|
-
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
368
|
-
if (def.notNull) colSql += " NOT NULL";
|
|
369
|
-
if (def.unique) colSql += " UNIQUE";
|
|
370
|
-
if (def.hasDefault && def.type === "timestamp") {
|
|
371
|
-
colSql += " DEFAULT (datetime('now'))";
|
|
372
|
-
}
|
|
373
|
-
if (def.references) {
|
|
374
|
-
const [refTable, refCol] = def.references.split(".");
|
|
375
|
-
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
376
|
-
}
|
|
377
|
-
columnDefs.push(colSql);
|
|
379
|
+
columnDefs.push(buildColumnSql(colName, def));
|
|
378
380
|
}
|
|
379
381
|
statements.push(
|
|
380
382
|
`CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
381
383
|
${columnDefs.join(",\n ")}
|
|
382
384
|
);`
|
|
383
385
|
);
|
|
386
|
+
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
387
|
+
const def = colDef;
|
|
388
|
+
if (def.primaryKey) continue;
|
|
389
|
+
const alterColSql = buildColumnSql(colName, def, true);
|
|
390
|
+
statements.push(
|
|
391
|
+
`-- Add column if missing (will error if exists, which is OK)
|
|
392
|
+
ALTER TABLE ${table.name} ADD COLUMN ${alterColSql};`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
384
395
|
}
|
|
385
396
|
return statements.join("\n\n");
|
|
386
397
|
}
|
|
@@ -1087,8 +1098,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
|
1087
1098
|
}
|
|
1088
1099
|
}
|
|
1089
1100
|
}
|
|
1090
|
-
function getInstallCommand(pm,
|
|
1091
|
-
const devFlag =
|
|
1101
|
+
function getInstallCommand(pm, isDev5 = false) {
|
|
1102
|
+
const devFlag = isDev5 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
|
|
1092
1103
|
return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
|
|
1093
1104
|
}
|
|
1094
1105
|
async function installTetherPackages(projectPath, template, packageManager) {
|
|
@@ -2144,18 +2155,18 @@ function detectPackageManager() {
|
|
|
2144
2155
|
}
|
|
2145
2156
|
return "npm";
|
|
2146
2157
|
}
|
|
2147
|
-
function getInstallCommand2(pm, packages,
|
|
2158
|
+
function getInstallCommand2(pm, packages, isDev5) {
|
|
2148
2159
|
const packagesStr = packages.join(" ");
|
|
2149
2160
|
switch (pm) {
|
|
2150
2161
|
case "bun":
|
|
2151
|
-
return
|
|
2162
|
+
return isDev5 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
|
|
2152
2163
|
case "pnpm":
|
|
2153
|
-
return
|
|
2164
|
+
return isDev5 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
|
|
2154
2165
|
case "yarn":
|
|
2155
|
-
return
|
|
2166
|
+
return isDev5 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
|
|
2156
2167
|
case "npm":
|
|
2157
2168
|
default:
|
|
2158
|
-
return
|
|
2169
|
+
return isDev5 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
|
|
2159
2170
|
}
|
|
2160
2171
|
}
|
|
2161
2172
|
async function getLatestVersion2(packageName) {
|
|
@@ -2255,6 +2266,79 @@ async function updateCommand(options) {
|
|
|
2255
2266
|
}
|
|
2256
2267
|
}
|
|
2257
2268
|
|
|
2269
|
+
// src/commands/exec.ts
|
|
2270
|
+
import chalk9 from "chalk";
|
|
2271
|
+
import ora8 from "ora";
|
|
2272
|
+
import fs9 from "fs-extra";
|
|
2273
|
+
import path9 from "path";
|
|
2274
|
+
var isDev4 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2275
|
+
var API_URL4 = isDev4 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
2276
|
+
async function execCommand(sql) {
|
|
2277
|
+
if (!sql || sql.trim() === "") {
|
|
2278
|
+
console.log(chalk9.red("\nError: SQL query required"));
|
|
2279
|
+
console.log(chalk9.dim('Usage: tthr exec "SELECT * FROM table_name"\n'));
|
|
2280
|
+
process.exit(1);
|
|
2281
|
+
}
|
|
2282
|
+
const credentials = await requireAuth();
|
|
2283
|
+
const envPath = path9.resolve(process.cwd(), ".env");
|
|
2284
|
+
let projectId;
|
|
2285
|
+
if (await fs9.pathExists(envPath)) {
|
|
2286
|
+
const envContent = await fs9.readFile(envPath, "utf-8");
|
|
2287
|
+
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2288
|
+
projectId = match?.[1]?.trim();
|
|
2289
|
+
}
|
|
2290
|
+
if (!projectId) {
|
|
2291
|
+
console.log(chalk9.red("\nError: Project ID not found"));
|
|
2292
|
+
console.log(chalk9.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
2293
|
+
process.exit(1);
|
|
2294
|
+
}
|
|
2295
|
+
const spinner = ora8("Executing query...").start();
|
|
2296
|
+
try {
|
|
2297
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
|
|
2298
|
+
method: "POST",
|
|
2299
|
+
headers: {
|
|
2300
|
+
"Content-Type": "application/json",
|
|
2301
|
+
"Authorization": `Bearer ${credentials.accessToken}`
|
|
2302
|
+
},
|
|
2303
|
+
body: JSON.stringify({ sql })
|
|
2304
|
+
});
|
|
2305
|
+
if (!response.ok) {
|
|
2306
|
+
const error = await response.json();
|
|
2307
|
+
spinner.fail(`Query failed: ${error.error || response.statusText}`);
|
|
2308
|
+
process.exit(1);
|
|
2309
|
+
}
|
|
2310
|
+
const result = await response.json();
|
|
2311
|
+
spinner.succeed("Query executed");
|
|
2312
|
+
if (result.columns && result.rows) {
|
|
2313
|
+
console.log();
|
|
2314
|
+
if (result.rows.length === 0) {
|
|
2315
|
+
console.log(chalk9.dim(" No rows returned"));
|
|
2316
|
+
} else {
|
|
2317
|
+
console.log(chalk9.bold(" " + result.columns.join(" ")));
|
|
2318
|
+
console.log(chalk9.dim(" " + result.columns.map(() => "--------").join(" ")));
|
|
2319
|
+
for (const row of result.rows) {
|
|
2320
|
+
const values = result.columns.map((col) => {
|
|
2321
|
+
const val = row[col];
|
|
2322
|
+
if (val === null) return chalk9.dim("NULL");
|
|
2323
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
2324
|
+
return String(val);
|
|
2325
|
+
});
|
|
2326
|
+
console.log(" " + values.join(" "));
|
|
2327
|
+
}
|
|
2328
|
+
console.log(chalk9.dim(`
|
|
2329
|
+
${result.rows.length} row(s)`));
|
|
2330
|
+
}
|
|
2331
|
+
} else if (result.rowsAffected !== void 0) {
|
|
2332
|
+
console.log(chalk9.dim(`
|
|
2333
|
+
${result.rowsAffected} row(s) affected`));
|
|
2334
|
+
}
|
|
2335
|
+
console.log();
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
spinner.fail(`Failed to execute query: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2338
|
+
process.exit(1);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2258
2342
|
// src/index.ts
|
|
2259
2343
|
var program = new Command();
|
|
2260
2344
|
program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version("0.0.1");
|
|
@@ -2267,4 +2351,5 @@ program.command("login").description("Authenticate with Tether").action(loginCom
|
|
|
2267
2351
|
program.command("logout").description("Sign out of Tether").action(logoutCommand);
|
|
2268
2352
|
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
2269
2353
|
program.command("update").description("Update all Tether packages to the latest version").option("--dry-run", "Show what would be updated without updating").action(updateCommand);
|
|
2354
|
+
program.command("exec <sql>").description("Execute a SQL query against the project database").action(execCommand);
|
|
2270
2355
|
program.parse();
|