@tthr/cli 0.0.1
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/chunk-MCKGQKYU.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +870 -0
- package/dist/open-OC46UHP7.js +344 -0
- package/package.json +39 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
__require,
|
|
14
|
+
__commonJS
|
|
15
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import chalk2 from "chalk";
|
|
8
|
+
import ora from "ora";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
import fs2 from "fs-extra";
|
|
11
|
+
import path2 from "path";
|
|
12
|
+
|
|
13
|
+
// src/utils/auth.ts
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import fs from "fs-extra";
|
|
16
|
+
import path from "path";
|
|
17
|
+
var CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || "", ".tether");
|
|
18
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
19
|
+
async function getCredentials() {
|
|
20
|
+
try {
|
|
21
|
+
if (await fs.pathExists(CREDENTIALS_FILE)) {
|
|
22
|
+
return await fs.readJSON(CREDENTIALS_FILE);
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
async function requireAuth() {
|
|
29
|
+
const credentials = await getCredentials();
|
|
30
|
+
if (!credentials) {
|
|
31
|
+
console.error(chalk.red("\n\u2717 Not logged in\n"));
|
|
32
|
+
console.log(chalk.dim("Run `tthr login` to authenticate\n"));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
return credentials;
|
|
36
|
+
}
|
|
37
|
+
async function saveCredentials(credentials) {
|
|
38
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
39
|
+
await fs.writeJSON(CREDENTIALS_FILE, credentials, { spaces: 2, mode: 384 });
|
|
40
|
+
}
|
|
41
|
+
async function clearCredentials() {
|
|
42
|
+
try {
|
|
43
|
+
await fs.remove(CREDENTIALS_FILE);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/commands/init.ts
|
|
49
|
+
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
50
|
+
var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tthr.io/api/v1";
|
|
51
|
+
async function getLatestVersion(packageName) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
return "latest";
|
|
56
|
+
}
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
return `^${data.version}`;
|
|
59
|
+
} catch {
|
|
60
|
+
return "latest";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function initCommand(name, options) {
|
|
64
|
+
const credentials = await requireAuth();
|
|
65
|
+
console.log(chalk2.bold("\n\u26A1 Create a new Tether project\n"));
|
|
66
|
+
let projectName = name;
|
|
67
|
+
if (!projectName) {
|
|
68
|
+
const response = await prompts({
|
|
69
|
+
type: "text",
|
|
70
|
+
name: "name",
|
|
71
|
+
message: "Project name:",
|
|
72
|
+
initial: "my-tether-app"
|
|
73
|
+
});
|
|
74
|
+
projectName = response.name;
|
|
75
|
+
if (!projectName) {
|
|
76
|
+
console.log(chalk2.red("Project name is required"));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let template = options.template;
|
|
81
|
+
if (options.template === "vue") {
|
|
82
|
+
const response = await prompts({
|
|
83
|
+
type: "select",
|
|
84
|
+
name: "template",
|
|
85
|
+
message: "Select a framework:",
|
|
86
|
+
choices: [
|
|
87
|
+
{ title: "Vue / Nuxt", value: "vue" },
|
|
88
|
+
{ title: "Svelte / SvelteKit", value: "svelte" },
|
|
89
|
+
{ title: "React / Next.js", value: "react" },
|
|
90
|
+
{ title: "Vanilla JavaScript", value: "vanilla" }
|
|
91
|
+
],
|
|
92
|
+
initial: 0
|
|
93
|
+
});
|
|
94
|
+
template = response.template || "vue";
|
|
95
|
+
}
|
|
96
|
+
const projectPath = path2.resolve(process.cwd(), projectName);
|
|
97
|
+
if (await fs2.pathExists(projectPath)) {
|
|
98
|
+
const { overwrite } = await prompts({
|
|
99
|
+
type: "confirm",
|
|
100
|
+
name: "overwrite",
|
|
101
|
+
message: `Directory ${projectName} already exists. Overwrite?`,
|
|
102
|
+
initial: false
|
|
103
|
+
});
|
|
104
|
+
if (!overwrite) {
|
|
105
|
+
console.log(chalk2.yellow("Cancelled"));
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
await fs2.remove(projectPath);
|
|
109
|
+
}
|
|
110
|
+
const spinner = ora("Creating project on Tether...").start();
|
|
111
|
+
let projectId;
|
|
112
|
+
let apiKey;
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(`${API_URL}/projects`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
"Authorization": `Bearer ${credentials.accessToken}`
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ name: projectName, source: "cli" })
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
const error = await response.json().catch(() => ({}));
|
|
124
|
+
throw new Error(error.error || "Failed to create project on server");
|
|
125
|
+
}
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
projectId = data.project.id;
|
|
128
|
+
apiKey = data.apiKey;
|
|
129
|
+
spinner.text = "Creating project structure...";
|
|
130
|
+
await fs2.ensureDir(projectPath);
|
|
131
|
+
await fs2.ensureDir(path2.join(projectPath, "tether"));
|
|
132
|
+
await fs2.ensureDir(path2.join(projectPath, "tether", "functions"));
|
|
133
|
+
await fs2.ensureDir(path2.join(projectPath, "_generated"));
|
|
134
|
+
await fs2.writeFile(
|
|
135
|
+
path2.join(projectPath, "tether.config.ts"),
|
|
136
|
+
`import { defineConfig } from '@tthr/cli';
|
|
137
|
+
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
// Project configuration
|
|
140
|
+
projectId: process.env.TETHER_PROJECT_ID,
|
|
141
|
+
|
|
142
|
+
// API endpoint (defaults to Tether Cloud)
|
|
143
|
+
url: process.env.TETHER_URL || 'https://tthr.io',
|
|
144
|
+
|
|
145
|
+
// Schema file location
|
|
146
|
+
schema: './tether/schema.ts',
|
|
147
|
+
|
|
148
|
+
// Functions directory
|
|
149
|
+
functions: './tether/functions',
|
|
150
|
+
|
|
151
|
+
// Generated types output
|
|
152
|
+
output: './_generated',
|
|
153
|
+
});
|
|
154
|
+
`
|
|
155
|
+
);
|
|
156
|
+
await fs2.writeFile(
|
|
157
|
+
path2.join(projectPath, "tether", "schema.ts"),
|
|
158
|
+
`import { defineSchema, text, integer, timestamp } from '@tthr/schema';
|
|
159
|
+
|
|
160
|
+
export default defineSchema({
|
|
161
|
+
// Example: posts table
|
|
162
|
+
posts: {
|
|
163
|
+
id: text().primaryKey(),
|
|
164
|
+
title: text().notNull(),
|
|
165
|
+
content: text(),
|
|
166
|
+
authorId: text().notNull(),
|
|
167
|
+
createdAt: timestamp().notNull().default('now'),
|
|
168
|
+
updatedAt: timestamp().notNull().default('now'),
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Example: comments table
|
|
172
|
+
comments: {
|
|
173
|
+
id: text().primaryKey(),
|
|
174
|
+
postId: text().notNull().references('posts.id'),
|
|
175
|
+
content: text().notNull(),
|
|
176
|
+
authorId: text().notNull(),
|
|
177
|
+
createdAt: timestamp().notNull().default('now'),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
`
|
|
181
|
+
);
|
|
182
|
+
await fs2.writeFile(
|
|
183
|
+
path2.join(projectPath, "tether", "functions", "posts.ts"),
|
|
184
|
+
`import { query, mutation, z } from '@tthr/server';
|
|
185
|
+
import { db } from '../../_generated/db';
|
|
186
|
+
|
|
187
|
+
// List all posts
|
|
188
|
+
export const list = query({
|
|
189
|
+
args: z.object({
|
|
190
|
+
limit: z.number().optional().default(20),
|
|
191
|
+
}),
|
|
192
|
+
handler: async ({ args }) => {
|
|
193
|
+
return db.posts.findMany({
|
|
194
|
+
orderBy: { createdAt: 'desc' },
|
|
195
|
+
limit: args.limit,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Get a single post
|
|
201
|
+
export const get = query({
|
|
202
|
+
args: z.object({
|
|
203
|
+
id: z.string(),
|
|
204
|
+
}),
|
|
205
|
+
handler: async ({ args }) => {
|
|
206
|
+
return db.posts.findUnique({
|
|
207
|
+
where: { id: args.id },
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Create a new post
|
|
213
|
+
export const create = mutation({
|
|
214
|
+
args: z.object({
|
|
215
|
+
title: z.string().min(1),
|
|
216
|
+
content: z.string().optional(),
|
|
217
|
+
}),
|
|
218
|
+
handler: async ({ args, ctx }) => {
|
|
219
|
+
const id = crypto.randomUUID();
|
|
220
|
+
const now = new Date().toISOString();
|
|
221
|
+
|
|
222
|
+
await db.posts.create({
|
|
223
|
+
data: {
|
|
224
|
+
id,
|
|
225
|
+
title: args.title,
|
|
226
|
+
content: args.content ?? '',
|
|
227
|
+
authorId: ctx.userId,
|
|
228
|
+
createdAt: now,
|
|
229
|
+
updatedAt: now,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return { id };
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Update a post
|
|
238
|
+
export const update = mutation({
|
|
239
|
+
args: z.object({
|
|
240
|
+
id: z.string(),
|
|
241
|
+
title: z.string().min(1).optional(),
|
|
242
|
+
content: z.string().optional(),
|
|
243
|
+
}),
|
|
244
|
+
handler: async ({ args }) => {
|
|
245
|
+
await db.posts.update({
|
|
246
|
+
where: { id: args.id },
|
|
247
|
+
data: {
|
|
248
|
+
...(args.title && { title: args.title }),
|
|
249
|
+
...(args.content !== undefined && { content: args.content }),
|
|
250
|
+
updatedAt: new Date().toISOString(),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Delete a post
|
|
257
|
+
export const remove = mutation({
|
|
258
|
+
args: z.object({
|
|
259
|
+
id: z.string(),
|
|
260
|
+
}),
|
|
261
|
+
handler: async ({ args }) => {
|
|
262
|
+
await db.posts.delete({
|
|
263
|
+
where: { id: args.id },
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
`
|
|
268
|
+
);
|
|
269
|
+
await fs2.writeFile(
|
|
270
|
+
path2.join(projectPath, "tether", "seed.ts"),
|
|
271
|
+
`import { db } from '../_generated/db';
|
|
272
|
+
|
|
273
|
+
export async function seed() {
|
|
274
|
+
console.log('Seeding database...');
|
|
275
|
+
|
|
276
|
+
// Add your seed data here
|
|
277
|
+
const now = new Date().toISOString();
|
|
278
|
+
|
|
279
|
+
await db.posts.create({
|
|
280
|
+
data: {
|
|
281
|
+
id: 'post-1',
|
|
282
|
+
title: 'Welcome to Tether',
|
|
283
|
+
content: 'This is your first post. Edit or delete it to get started!',
|
|
284
|
+
authorId: 'system',
|
|
285
|
+
createdAt: now,
|
|
286
|
+
updatedAt: now,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
console.log('Seeding complete!');
|
|
291
|
+
}
|
|
292
|
+
`
|
|
293
|
+
);
|
|
294
|
+
await fs2.writeFile(
|
|
295
|
+
path2.join(projectPath, ".env"),
|
|
296
|
+
`# Tether Configuration
|
|
297
|
+
TETHER_PROJECT_ID=${projectId}
|
|
298
|
+
TETHER_URL=${isDev ? "http://localhost:3001" : "https://tthr.io"}
|
|
299
|
+
TETHER_API_KEY=${apiKey}
|
|
300
|
+
`
|
|
301
|
+
);
|
|
302
|
+
await fs2.writeFile(
|
|
303
|
+
path2.join(projectPath, ".gitignore"),
|
|
304
|
+
`# Dependencies
|
|
305
|
+
node_modules/
|
|
306
|
+
|
|
307
|
+
# Build output
|
|
308
|
+
dist/
|
|
309
|
+
.output/
|
|
310
|
+
|
|
311
|
+
# Generated files
|
|
312
|
+
_generated/
|
|
313
|
+
|
|
314
|
+
# Environment
|
|
315
|
+
.env
|
|
316
|
+
.env.local
|
|
317
|
+
.env.*.local
|
|
318
|
+
|
|
319
|
+
# IDE
|
|
320
|
+
.idea/
|
|
321
|
+
.vscode/
|
|
322
|
+
*.swp
|
|
323
|
+
*.swo
|
|
324
|
+
|
|
325
|
+
# OS
|
|
326
|
+
.DS_Store
|
|
327
|
+
Thumbs.db
|
|
328
|
+
`
|
|
329
|
+
);
|
|
330
|
+
spinner.text = "Fetching latest package versions...";
|
|
331
|
+
const [
|
|
332
|
+
tthrClientVersion,
|
|
333
|
+
tthrSchemaVersion,
|
|
334
|
+
tthrServerVersion,
|
|
335
|
+
tthrCliVersion,
|
|
336
|
+
typescriptVersion,
|
|
337
|
+
tsxVersion
|
|
338
|
+
] = await Promise.all([
|
|
339
|
+
getLatestVersion("@tthr/client"),
|
|
340
|
+
getLatestVersion("@tthr/schema"),
|
|
341
|
+
getLatestVersion("@tthr/server"),
|
|
342
|
+
getLatestVersion("@tthr/cli"),
|
|
343
|
+
getLatestVersion("typescript"),
|
|
344
|
+
getLatestVersion("tsx")
|
|
345
|
+
]);
|
|
346
|
+
const dependencies = {
|
|
347
|
+
"@tthr/client": tthrClientVersion,
|
|
348
|
+
"@tthr/schema": tthrSchemaVersion,
|
|
349
|
+
"@tthr/server": tthrServerVersion
|
|
350
|
+
};
|
|
351
|
+
if (template === "vue") {
|
|
352
|
+
const [tthrVueVersion, vueVersion] = await Promise.all([
|
|
353
|
+
getLatestVersion("@tthr/vue"),
|
|
354
|
+
getLatestVersion("vue")
|
|
355
|
+
]);
|
|
356
|
+
dependencies["@tthr/vue"] = tthrVueVersion;
|
|
357
|
+
dependencies["vue"] = vueVersion;
|
|
358
|
+
} else if (template === "react") {
|
|
359
|
+
const [tthrReactVersion, reactVersion, reactDomVersion] = await Promise.all([
|
|
360
|
+
getLatestVersion("@tthr/react"),
|
|
361
|
+
getLatestVersion("react"),
|
|
362
|
+
getLatestVersion("react-dom")
|
|
363
|
+
]);
|
|
364
|
+
dependencies["@tthr/react"] = tthrReactVersion;
|
|
365
|
+
dependencies["react"] = reactVersion;
|
|
366
|
+
dependencies["react-dom"] = reactDomVersion;
|
|
367
|
+
} else if (template === "svelte") {
|
|
368
|
+
const [tthrSvelteVersion, svelteVersion] = await Promise.all([
|
|
369
|
+
getLatestVersion("@tthr/svelte"),
|
|
370
|
+
getLatestVersion("svelte")
|
|
371
|
+
]);
|
|
372
|
+
dependencies["@tthr/svelte"] = tthrSvelteVersion;
|
|
373
|
+
dependencies["svelte"] = svelteVersion;
|
|
374
|
+
}
|
|
375
|
+
const packageJson = {
|
|
376
|
+
name: projectName,
|
|
377
|
+
version: "0.0.1",
|
|
378
|
+
private: true,
|
|
379
|
+
type: "module",
|
|
380
|
+
scripts: {
|
|
381
|
+
"dev": "tthr dev",
|
|
382
|
+
"generate": "tthr generate",
|
|
383
|
+
"migrate": "tthr migrate",
|
|
384
|
+
"seed": "tsx tether/seed.ts"
|
|
385
|
+
},
|
|
386
|
+
dependencies,
|
|
387
|
+
devDependencies: {
|
|
388
|
+
"@tthr/cli": tthrCliVersion,
|
|
389
|
+
"typescript": typescriptVersion,
|
|
390
|
+
"tsx": tsxVersion
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
await fs2.writeJSON(path2.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
394
|
+
await fs2.writeJSON(
|
|
395
|
+
path2.join(projectPath, "tsconfig.json"),
|
|
396
|
+
{
|
|
397
|
+
compilerOptions: {
|
|
398
|
+
target: "ES2022",
|
|
399
|
+
module: "ESNext",
|
|
400
|
+
moduleResolution: "bundler",
|
|
401
|
+
strict: true,
|
|
402
|
+
esModuleInterop: true,
|
|
403
|
+
skipLibCheck: true,
|
|
404
|
+
forceConsistentCasingInFileNames: true,
|
|
405
|
+
paths: {
|
|
406
|
+
"@/*": ["./*"]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
include: ["**/*.ts"],
|
|
410
|
+
exclude: ["node_modules", "dist"]
|
|
411
|
+
},
|
|
412
|
+
{ spaces: 2 }
|
|
413
|
+
);
|
|
414
|
+
spinner.succeed("Project structure created");
|
|
415
|
+
console.log("\n" + chalk2.green("\u2713") + " Project created successfully!\n");
|
|
416
|
+
console.log("Next steps:\n");
|
|
417
|
+
console.log(chalk2.cyan(` cd ${projectName}`));
|
|
418
|
+
console.log(chalk2.cyan(" npm install"));
|
|
419
|
+
console.log(chalk2.cyan(" npm run dev"));
|
|
420
|
+
console.log("\n" + chalk2.dim("For more information, visit https://tthr.io/docs\n"));
|
|
421
|
+
} catch (error) {
|
|
422
|
+
spinner.fail("Failed to create project");
|
|
423
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/commands/dev.ts
|
|
429
|
+
import chalk3 from "chalk";
|
|
430
|
+
import ora2 from "ora";
|
|
431
|
+
import fs3 from "fs-extra";
|
|
432
|
+
import path3 from "path";
|
|
433
|
+
async function devCommand(options) {
|
|
434
|
+
await requireAuth();
|
|
435
|
+
const configPath = path3.resolve(process.cwd(), "tether.config.ts");
|
|
436
|
+
if (!await fs3.pathExists(configPath)) {
|
|
437
|
+
console.log(chalk3.red("\nError: Not a Tether project"));
|
|
438
|
+
console.log(chalk3.dim("Run `tthr init` to create a new project\n"));
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
console.log(chalk3.bold("\n\u26A1 Starting Tether development server\n"));
|
|
442
|
+
const spinner = ora2("Starting server...").start();
|
|
443
|
+
try {
|
|
444
|
+
spinner.succeed(`Development server running on port ${options.port}`);
|
|
445
|
+
console.log("\n" + chalk3.cyan(` Local: http://localhost:${options.port}`));
|
|
446
|
+
console.log(chalk3.cyan(` WebSocket: ws://localhost:${options.port}/ws
|
|
447
|
+
`));
|
|
448
|
+
console.log(chalk3.dim(" Press Ctrl+C to stop\n"));
|
|
449
|
+
process.on("SIGINT", () => {
|
|
450
|
+
console.log(chalk3.yellow("\n\nShutting down...\n"));
|
|
451
|
+
process.exit(0);
|
|
452
|
+
});
|
|
453
|
+
await new Promise(() => {
|
|
454
|
+
});
|
|
455
|
+
} catch (error) {
|
|
456
|
+
spinner.fail("Failed to start server");
|
|
457
|
+
console.error(chalk3.red(error instanceof Error ? error.message : "Unknown error"));
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/commands/generate.ts
|
|
463
|
+
import chalk4 from "chalk";
|
|
464
|
+
import ora3 from "ora";
|
|
465
|
+
import fs4 from "fs-extra";
|
|
466
|
+
import path4 from "path";
|
|
467
|
+
async function generateCommand() {
|
|
468
|
+
await requireAuth();
|
|
469
|
+
const configPath = path4.resolve(process.cwd(), "tether.config.ts");
|
|
470
|
+
if (!await fs4.pathExists(configPath)) {
|
|
471
|
+
console.log(chalk4.red("\nError: Not a Tether project"));
|
|
472
|
+
console.log(chalk4.dim("Run `tthr init` to create a new project\n"));
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
console.log(chalk4.bold("\n\u26A1 Generating types from schema\n"));
|
|
476
|
+
const spinner = ora3("Reading schema...").start();
|
|
477
|
+
try {
|
|
478
|
+
const schemaPath = path4.resolve(process.cwd(), "tether", "schema.ts");
|
|
479
|
+
const outputDir = path4.resolve(process.cwd(), "_generated");
|
|
480
|
+
if (!await fs4.pathExists(schemaPath)) {
|
|
481
|
+
spinner.fail("Schema file not found");
|
|
482
|
+
console.log(chalk4.dim(`Expected: ${schemaPath}
|
|
483
|
+
`));
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
spinner.text = "Generating types...";
|
|
487
|
+
await fs4.ensureDir(outputDir);
|
|
488
|
+
await fs4.writeFile(
|
|
489
|
+
path4.join(outputDir, "db.ts"),
|
|
490
|
+
`// Auto-generated by Tether CLI - do not edit manually
|
|
491
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
492
|
+
|
|
493
|
+
import type { TetherDatabase } from '@tthr/client';
|
|
494
|
+
|
|
495
|
+
export interface Post {
|
|
496
|
+
id: string;
|
|
497
|
+
title: string;
|
|
498
|
+
content: string | null;
|
|
499
|
+
authorId: string;
|
|
500
|
+
createdAt: string;
|
|
501
|
+
updatedAt: string;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export interface Comment {
|
|
505
|
+
id: string;
|
|
506
|
+
postId: string;
|
|
507
|
+
content: string;
|
|
508
|
+
authorId: string;
|
|
509
|
+
createdAt: string;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export interface Schema {
|
|
513
|
+
posts: Post;
|
|
514
|
+
comments: Comment;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Database client with typed tables
|
|
518
|
+
export declare const db: TetherDatabase<Schema>;
|
|
519
|
+
`
|
|
520
|
+
);
|
|
521
|
+
await fs4.writeFile(
|
|
522
|
+
path4.join(outputDir, "api.ts"),
|
|
523
|
+
`// Auto-generated by Tether CLI - do not edit manually
|
|
524
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
525
|
+
|
|
526
|
+
import type { TetherClient } from '@tthr/client';
|
|
527
|
+
import type { Schema } from './db';
|
|
528
|
+
|
|
529
|
+
export interface PostsApi {
|
|
530
|
+
list: (args?: { limit?: number }) => Promise<Schema['posts'][]>;
|
|
531
|
+
get: (args: { id: string }) => Promise<Schema['posts'] | null>;
|
|
532
|
+
create: (args: { title: string; content?: string }) => Promise<{ id: string }>;
|
|
533
|
+
update: (args: { id: string; title?: string; content?: string }) => Promise<void>;
|
|
534
|
+
remove: (args: { id: string }) => Promise<void>;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export interface Api {
|
|
538
|
+
posts: PostsApi;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Typed Tether client
|
|
542
|
+
export declare const tether: TetherClient<Api>;
|
|
543
|
+
`
|
|
544
|
+
);
|
|
545
|
+
await fs4.writeFile(
|
|
546
|
+
path4.join(outputDir, "index.ts"),
|
|
547
|
+
`// Auto-generated by Tether CLI - do not edit manually
|
|
548
|
+
export * from './db';
|
|
549
|
+
export * from './api';
|
|
550
|
+
`
|
|
551
|
+
);
|
|
552
|
+
spinner.succeed("Types generated");
|
|
553
|
+
console.log("\n" + chalk4.green("\u2713") + " Generated files:");
|
|
554
|
+
console.log(chalk4.dim(" _generated/db.ts"));
|
|
555
|
+
console.log(chalk4.dim(" _generated/api.ts"));
|
|
556
|
+
console.log(chalk4.dim(" _generated/index.ts\n"));
|
|
557
|
+
} catch (error) {
|
|
558
|
+
spinner.fail("Failed to generate types");
|
|
559
|
+
console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/commands/migrate.ts
|
|
565
|
+
import chalk5 from "chalk";
|
|
566
|
+
import ora4 from "ora";
|
|
567
|
+
import fs5 from "fs-extra";
|
|
568
|
+
import path5 from "path";
|
|
569
|
+
import prompts2 from "prompts";
|
|
570
|
+
async function migrateCommand(action, options) {
|
|
571
|
+
await requireAuth();
|
|
572
|
+
const configPath = path5.resolve(process.cwd(), "tether.config.ts");
|
|
573
|
+
if (!await fs5.pathExists(configPath)) {
|
|
574
|
+
console.log(chalk5.red("\nError: Not a Tether project"));
|
|
575
|
+
console.log(chalk5.dim("Run `tthr init` to create a new project\n"));
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
const migrationsDir = path5.resolve(process.cwd(), "tether", "migrations");
|
|
579
|
+
switch (action) {
|
|
580
|
+
case "create":
|
|
581
|
+
await createMigration(migrationsDir, options.name);
|
|
582
|
+
break;
|
|
583
|
+
case "up":
|
|
584
|
+
await runMigrations(migrationsDir, "up");
|
|
585
|
+
break;
|
|
586
|
+
case "down":
|
|
587
|
+
await runMigrations(migrationsDir, "down");
|
|
588
|
+
break;
|
|
589
|
+
case "status":
|
|
590
|
+
default:
|
|
591
|
+
await showStatus(migrationsDir);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function createMigration(migrationsDir, name) {
|
|
596
|
+
console.log(chalk5.bold("\n\u26A1 Create migration\n"));
|
|
597
|
+
let migrationName = name;
|
|
598
|
+
if (!migrationName) {
|
|
599
|
+
const response = await prompts2({
|
|
600
|
+
type: "text",
|
|
601
|
+
name: "name",
|
|
602
|
+
message: "Migration name:",
|
|
603
|
+
initial: "update_schema"
|
|
604
|
+
});
|
|
605
|
+
migrationName = response.name;
|
|
606
|
+
if (!migrationName) {
|
|
607
|
+
console.log(chalk5.red("Migration name is required"));
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const spinner = ora4("Creating migration...").start();
|
|
612
|
+
try {
|
|
613
|
+
await fs5.ensureDir(migrationsDir);
|
|
614
|
+
const timestamp = Date.now();
|
|
615
|
+
const filename = `${timestamp}_${migrationName}.sql`;
|
|
616
|
+
const filepath = path5.join(migrationsDir, filename);
|
|
617
|
+
await fs5.writeFile(
|
|
618
|
+
filepath,
|
|
619
|
+
`-- Migration: ${migrationName}
|
|
620
|
+
-- Created at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
621
|
+
|
|
622
|
+
-- Up migration
|
|
623
|
+
-- Add your schema changes here
|
|
624
|
+
|
|
625
|
+
-- Example:
|
|
626
|
+
-- CREATE TABLE IF NOT EXISTS users (
|
|
627
|
+
-- id TEXT PRIMARY KEY,
|
|
628
|
+
-- email TEXT NOT NULL UNIQUE,
|
|
629
|
+
-- name TEXT,
|
|
630
|
+
-- created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
631
|
+
-- );
|
|
632
|
+
|
|
633
|
+
-- Down migration (for rollback)
|
|
634
|
+
-- DROP TABLE IF EXISTS users;
|
|
635
|
+
`
|
|
636
|
+
);
|
|
637
|
+
spinner.succeed("Migration created");
|
|
638
|
+
console.log(chalk5.dim(`
|
|
639
|
+
${filepath}
|
|
640
|
+
`));
|
|
641
|
+
} catch (error) {
|
|
642
|
+
spinner.fail("Failed to create migration");
|
|
643
|
+
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function runMigrations(migrationsDir, direction) {
|
|
648
|
+
console.log(chalk5.bold(`
|
|
649
|
+
\u26A1 Running migrations (${direction})
|
|
650
|
+
`));
|
|
651
|
+
const spinner = ora4("Checking migrations...").start();
|
|
652
|
+
try {
|
|
653
|
+
if (!await fs5.pathExists(migrationsDir)) {
|
|
654
|
+
spinner.info("No migrations directory found");
|
|
655
|
+
console.log(chalk5.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const files = await fs5.readdir(migrationsDir);
|
|
659
|
+
const migrations = files.filter((f) => f.endsWith(".sql")).sort((a, b) => direction === "up" ? a.localeCompare(b) : b.localeCompare(a));
|
|
660
|
+
if (migrations.length === 0) {
|
|
661
|
+
spinner.info("No migrations found");
|
|
662
|
+
console.log(chalk5.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
spinner.text = `Found ${migrations.length} migration(s)`;
|
|
666
|
+
spinner.succeed(`Would run ${migrations.length} migration(s)`);
|
|
667
|
+
console.log("\nMigrations to run:");
|
|
668
|
+
for (const migration of migrations) {
|
|
669
|
+
console.log(chalk5.dim(` ${migration}`));
|
|
670
|
+
}
|
|
671
|
+
console.log(chalk5.yellow("\nNote: Migration execution not yet implemented\n"));
|
|
672
|
+
} catch (error) {
|
|
673
|
+
spinner.fail("Failed to run migrations");
|
|
674
|
+
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async function showStatus(migrationsDir) {
|
|
679
|
+
console.log(chalk5.bold("\n\u26A1 Migration status\n"));
|
|
680
|
+
try {
|
|
681
|
+
if (!await fs5.pathExists(migrationsDir)) {
|
|
682
|
+
console.log(chalk5.dim("No migrations directory found"));
|
|
683
|
+
console.log(chalk5.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const files = await fs5.readdir(migrationsDir);
|
|
687
|
+
const migrations = files.filter((f) => f.endsWith(".sql")).sort();
|
|
688
|
+
if (migrations.length === 0) {
|
|
689
|
+
console.log(chalk5.dim("No migrations found"));
|
|
690
|
+
console.log(chalk5.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
console.log(`Found ${migrations.length} migration(s):
|
|
694
|
+
`);
|
|
695
|
+
for (const migration of migrations) {
|
|
696
|
+
console.log(chalk5.yellow(" \u25CB") + ` ${migration} ${chalk5.dim("(pending)")}`);
|
|
697
|
+
}
|
|
698
|
+
console.log();
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/commands/login.ts
|
|
706
|
+
import chalk6 from "chalk";
|
|
707
|
+
import ora5 from "ora";
|
|
708
|
+
var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
709
|
+
var API_URL2 = isDev2 ? "http://localhost:3001/api/v1" : "https://tthr.io/api/v1";
|
|
710
|
+
var AUTH_URL = isDev2 ? "http://localhost:3000/cli" : "https://tthr.io/cli";
|
|
711
|
+
async function loginCommand() {
|
|
712
|
+
console.log(chalk6.bold("\u26A1 Login to Tether\n"));
|
|
713
|
+
const existing = await getCredentials();
|
|
714
|
+
if (existing) {
|
|
715
|
+
console.log(chalk6.green("\u2713") + ` Already logged in as ${chalk6.cyan(existing.email)}`);
|
|
716
|
+
console.log(chalk6.dim("\nRun `tthr logout` to sign out\n"));
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const spinner = ora5("Generating authentication code...").start();
|
|
720
|
+
try {
|
|
721
|
+
const deviceCode = await requestDeviceCode();
|
|
722
|
+
spinner.stop();
|
|
723
|
+
const authUrl = `${AUTH_URL}/${deviceCode.userCode}`;
|
|
724
|
+
console.log(chalk6.dim("Open this URL in your browser to authenticate:\n"));
|
|
725
|
+
console.log(chalk6.cyan.bold(` ${authUrl}
|
|
726
|
+
`));
|
|
727
|
+
console.log(chalk6.dim(`Your code: ${chalk6.white.bold(deviceCode.userCode)}
|
|
728
|
+
`));
|
|
729
|
+
const credentials = await pollForApproval(deviceCode.deviceCode, deviceCode.interval, deviceCode.expiresIn);
|
|
730
|
+
await saveCredentials(credentials);
|
|
731
|
+
console.log(chalk6.green("\u2713") + ` Logged in as ${chalk6.cyan(credentials.email)}`);
|
|
732
|
+
console.log();
|
|
733
|
+
} catch (error) {
|
|
734
|
+
spinner.fail("Authentication failed");
|
|
735
|
+
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
736
|
+
console.log(chalk6.dim("\nTry again or visit https://tthr.io/docs/cli for help\n"));
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async function logoutCommand() {
|
|
741
|
+
console.log(chalk6.bold("\n\u26A1 Logout from Tether\n"));
|
|
742
|
+
const credentials = await getCredentials();
|
|
743
|
+
if (!credentials) {
|
|
744
|
+
console.log(chalk6.dim("Not logged in\n"));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const spinner = ora5("Logging out...").start();
|
|
748
|
+
try {
|
|
749
|
+
await clearCredentials();
|
|
750
|
+
spinner.succeed(`Logged out from ${chalk6.cyan(credentials.email)}`);
|
|
751
|
+
console.log();
|
|
752
|
+
} catch (error) {
|
|
753
|
+
spinner.fail("Logout failed");
|
|
754
|
+
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function whoamiCommand() {
|
|
759
|
+
const credentials = await getCredentials();
|
|
760
|
+
if (!credentials) {
|
|
761
|
+
console.log(chalk6.dim("\nNot logged in"));
|
|
762
|
+
console.log(chalk6.dim("Run `tthr login` to authenticate\n"));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
console.log(chalk6.bold("\n\u26A1 Current user\n"));
|
|
766
|
+
console.log(` Email: ${chalk6.cyan(credentials.email)}`);
|
|
767
|
+
console.log(` User ID: ${chalk6.dim(credentials.userId)}`);
|
|
768
|
+
if (credentials.expiresAt) {
|
|
769
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
770
|
+
const now = /* @__PURE__ */ new Date();
|
|
771
|
+
if (expiresAt > now) {
|
|
772
|
+
console.log(` Session: ${chalk6.green("Active")}`);
|
|
773
|
+
} else {
|
|
774
|
+
console.log(` Session: ${chalk6.yellow("Expired")}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
console.log();
|
|
778
|
+
}
|
|
779
|
+
function generateUserCode() {
|
|
780
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
781
|
+
const nums = "23456789";
|
|
782
|
+
let code = "";
|
|
783
|
+
for (let i = 0; i < 4; i++) {
|
|
784
|
+
code += chars[Math.floor(Math.random() * chars.length)];
|
|
785
|
+
}
|
|
786
|
+
code += "-";
|
|
787
|
+
for (let i = 0; i < 4; i++) {
|
|
788
|
+
code += nums[Math.floor(Math.random() * nums.length)];
|
|
789
|
+
}
|
|
790
|
+
return code;
|
|
791
|
+
}
|
|
792
|
+
async function requestDeviceCode() {
|
|
793
|
+
const userCode = generateUserCode();
|
|
794
|
+
const deviceCode = crypto.randomUUID();
|
|
795
|
+
const response = await fetch(`${API_URL2}/auth/device`, {
|
|
796
|
+
method: "POST",
|
|
797
|
+
headers: { "Content-Type": "application/json" },
|
|
798
|
+
body: JSON.stringify({ userCode, deviceCode })
|
|
799
|
+
}).catch(() => null);
|
|
800
|
+
if (response?.ok) {
|
|
801
|
+
return await response.json();
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
deviceCode,
|
|
805
|
+
userCode,
|
|
806
|
+
expiresIn: 120,
|
|
807
|
+
// 2 minutes
|
|
808
|
+
interval: 5
|
|
809
|
+
// Poll every 5 seconds
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
async function pollForApproval(deviceCode, interval, expiresIn) {
|
|
813
|
+
const startTime = Date.now();
|
|
814
|
+
const expiresAt = startTime + expiresIn * 1e3;
|
|
815
|
+
const spinner = ora5("").start();
|
|
816
|
+
const updateCountdown = () => {
|
|
817
|
+
const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1e3));
|
|
818
|
+
const mins = Math.floor(remaining / 60);
|
|
819
|
+
const secs = remaining % 60;
|
|
820
|
+
const timeStr = mins > 0 ? `${mins}:${secs.toString().padStart(2, "0")}` : `${secs}s`;
|
|
821
|
+
spinner.text = `Waiting for approval... ${chalk6.dim(`(${timeStr} remaining)`)}`;
|
|
822
|
+
};
|
|
823
|
+
updateCountdown();
|
|
824
|
+
const countdownInterval = setInterval(updateCountdown, 1e3);
|
|
825
|
+
try {
|
|
826
|
+
while (Date.now() < expiresAt) {
|
|
827
|
+
await sleep(interval * 1e3);
|
|
828
|
+
spinner.text = `Checking... ${chalk6.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
|
|
829
|
+
const response = await fetch(`${API_URL2}/auth/device/${deviceCode}`, {
|
|
830
|
+
method: "GET"
|
|
831
|
+
}).catch(() => null);
|
|
832
|
+
updateCountdown();
|
|
833
|
+
if (response?.ok) {
|
|
834
|
+
const data = await response.json();
|
|
835
|
+
if (data.status === "approved" && data.credentials) {
|
|
836
|
+
spinner.stop();
|
|
837
|
+
return data.credentials;
|
|
838
|
+
}
|
|
839
|
+
if (data.status === "denied") {
|
|
840
|
+
spinner.fail("Authentication denied");
|
|
841
|
+
throw new Error("Authentication request was denied");
|
|
842
|
+
}
|
|
843
|
+
if (data.status === "expired") {
|
|
844
|
+
spinner.fail("Authentication expired");
|
|
845
|
+
throw new Error("Authentication request expired");
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
spinner.fail("Authentication timed out");
|
|
850
|
+
throw new Error("Authentication timed out");
|
|
851
|
+
} finally {
|
|
852
|
+
clearInterval(countdownInterval);
|
|
853
|
+
spinner.stop();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function sleep(ms) {
|
|
857
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/index.ts
|
|
861
|
+
var program = new Command();
|
|
862
|
+
program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version("0.0.1");
|
|
863
|
+
program.command("init [name]").description("Create a new Tether project").option("-t, --template <template>", "Project template (vue, svelte, react, vanilla)", "vue").action(initCommand);
|
|
864
|
+
program.command("dev").description("Start local development server with hot reload").option("-p, --port <port>", "Port to run on", "3001").action(devCommand);
|
|
865
|
+
program.command("generate").alias("gen").description("Generate types from schema").action(generateCommand);
|
|
866
|
+
program.command("migrate").description("Database migrations").argument("[action]", "Migration action: create, up, down, status", "status").option("-n, --name <name>", "Migration name (for create)").action(migrateCommand);
|
|
867
|
+
program.command("login").description("Authenticate with Tether").action(loginCommand);
|
|
868
|
+
program.command("logout").description("Sign out of Tether").action(logoutCommand);
|
|
869
|
+
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
870
|
+
program.parse();
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__commonJS,
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-MCKGQKYU.js";
|
|
5
|
+
|
|
6
|
+
// ../../node_modules/open/node_modules/is-docker/index.js
|
|
7
|
+
var require_is_docker = __commonJS({
|
|
8
|
+
"../../node_modules/open/node_modules/is-docker/index.js"(exports, module) {
|
|
9
|
+
"use strict";
|
|
10
|
+
var fs = __require("fs");
|
|
11
|
+
var isDocker;
|
|
12
|
+
function hasDockerEnv() {
|
|
13
|
+
try {
|
|
14
|
+
fs.statSync("/.dockerenv");
|
|
15
|
+
return true;
|
|
16
|
+
} catch (_) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function hasDockerCGroup() {
|
|
21
|
+
try {
|
|
22
|
+
return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
module.exports = () => {
|
|
28
|
+
if (isDocker === void 0) {
|
|
29
|
+
isDocker = hasDockerEnv() || hasDockerCGroup();
|
|
30
|
+
}
|
|
31
|
+
return isDocker;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ../../node_modules/open/node_modules/is-wsl/index.js
|
|
37
|
+
var require_is_wsl = __commonJS({
|
|
38
|
+
"../../node_modules/open/node_modules/is-wsl/index.js"(exports, module) {
|
|
39
|
+
"use strict";
|
|
40
|
+
var os = __require("os");
|
|
41
|
+
var fs = __require("fs");
|
|
42
|
+
var isDocker = require_is_docker();
|
|
43
|
+
var isWsl = () => {
|
|
44
|
+
if (process.platform !== "linux") {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (os.release().toLowerCase().includes("microsoft")) {
|
|
48
|
+
if (isDocker()) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return fs.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isDocker() : false;
|
|
55
|
+
} catch (_) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
if (process.env.__IS_WSL_TEST__) {
|
|
60
|
+
module.exports = isWsl;
|
|
61
|
+
} else {
|
|
62
|
+
module.exports = isWsl();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ../../node_modules/define-lazy-prop/index.js
|
|
68
|
+
var require_define_lazy_prop = __commonJS({
|
|
69
|
+
"../../node_modules/define-lazy-prop/index.js"(exports, module) {
|
|
70
|
+
"use strict";
|
|
71
|
+
module.exports = (object, propertyName, fn) => {
|
|
72
|
+
const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
|
|
73
|
+
Object.defineProperty(object, propertyName, {
|
|
74
|
+
configurable: true,
|
|
75
|
+
enumerable: true,
|
|
76
|
+
get() {
|
|
77
|
+
const result = fn();
|
|
78
|
+
define(result);
|
|
79
|
+
return result;
|
|
80
|
+
},
|
|
81
|
+
set(value) {
|
|
82
|
+
define(value);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return object;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ../../node_modules/open/index.js
|
|
91
|
+
var require_open = __commonJS({
|
|
92
|
+
"../../node_modules/open/index.js"(exports, module) {
|
|
93
|
+
var path = __require("path");
|
|
94
|
+
var childProcess = __require("child_process");
|
|
95
|
+
var { promises: fs, constants: fsConstants } = __require("fs");
|
|
96
|
+
var isWsl = require_is_wsl();
|
|
97
|
+
var isDocker = require_is_docker();
|
|
98
|
+
var defineLazyProperty = require_define_lazy_prop();
|
|
99
|
+
var localXdgOpenPath = path.join(__dirname, "xdg-open");
|
|
100
|
+
var { platform, arch } = process;
|
|
101
|
+
var hasContainerEnv = () => {
|
|
102
|
+
try {
|
|
103
|
+
fs.statSync("/run/.containerenv");
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var cachedResult;
|
|
110
|
+
function isInsideContainer() {
|
|
111
|
+
if (cachedResult === void 0) {
|
|
112
|
+
cachedResult = hasContainerEnv() || isDocker();
|
|
113
|
+
}
|
|
114
|
+
return cachedResult;
|
|
115
|
+
}
|
|
116
|
+
var getWslDrivesMountPoint = /* @__PURE__ */ (() => {
|
|
117
|
+
const defaultMountPoint = "/mnt/";
|
|
118
|
+
let mountPoint;
|
|
119
|
+
return async function() {
|
|
120
|
+
if (mountPoint) {
|
|
121
|
+
return mountPoint;
|
|
122
|
+
}
|
|
123
|
+
const configFilePath = "/etc/wsl.conf";
|
|
124
|
+
let isConfigFileExists = false;
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(configFilePath, fsConstants.F_OK);
|
|
127
|
+
isConfigFileExists = true;
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
if (!isConfigFileExists) {
|
|
131
|
+
return defaultMountPoint;
|
|
132
|
+
}
|
|
133
|
+
const configContent = await fs.readFile(configFilePath, { encoding: "utf8" });
|
|
134
|
+
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
|
|
135
|
+
if (!configMountPoint) {
|
|
136
|
+
return defaultMountPoint;
|
|
137
|
+
}
|
|
138
|
+
mountPoint = configMountPoint.groups.mountPoint.trim();
|
|
139
|
+
mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
|
|
140
|
+
return mountPoint;
|
|
141
|
+
};
|
|
142
|
+
})();
|
|
143
|
+
var pTryEach = async (array, mapper) => {
|
|
144
|
+
let latestError;
|
|
145
|
+
for (const item of array) {
|
|
146
|
+
try {
|
|
147
|
+
return await mapper(item);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
latestError = error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
throw latestError;
|
|
153
|
+
};
|
|
154
|
+
var baseOpen = async (options) => {
|
|
155
|
+
options = {
|
|
156
|
+
wait: false,
|
|
157
|
+
background: false,
|
|
158
|
+
newInstance: false,
|
|
159
|
+
allowNonzeroExitCode: false,
|
|
160
|
+
...options
|
|
161
|
+
};
|
|
162
|
+
if (Array.isArray(options.app)) {
|
|
163
|
+
return pTryEach(options.app, (singleApp) => baseOpen({
|
|
164
|
+
...options,
|
|
165
|
+
app: singleApp
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
let { name: app, arguments: appArguments = [] } = options.app || {};
|
|
169
|
+
appArguments = [...appArguments];
|
|
170
|
+
if (Array.isArray(app)) {
|
|
171
|
+
return pTryEach(app, (appName) => baseOpen({
|
|
172
|
+
...options,
|
|
173
|
+
app: {
|
|
174
|
+
name: appName,
|
|
175
|
+
arguments: appArguments
|
|
176
|
+
}
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
let command;
|
|
180
|
+
const cliArguments = [];
|
|
181
|
+
const childProcessOptions = {};
|
|
182
|
+
if (platform === "darwin") {
|
|
183
|
+
command = "open";
|
|
184
|
+
if (options.wait) {
|
|
185
|
+
cliArguments.push("--wait-apps");
|
|
186
|
+
}
|
|
187
|
+
if (options.background) {
|
|
188
|
+
cliArguments.push("--background");
|
|
189
|
+
}
|
|
190
|
+
if (options.newInstance) {
|
|
191
|
+
cliArguments.push("--new");
|
|
192
|
+
}
|
|
193
|
+
if (app) {
|
|
194
|
+
cliArguments.push("-a", app);
|
|
195
|
+
}
|
|
196
|
+
} else if (platform === "win32" || isWsl && !isInsideContainer() && !app) {
|
|
197
|
+
const mountPoint = await getWslDrivesMountPoint();
|
|
198
|
+
command = isWsl ? `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` : `${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`;
|
|
199
|
+
cliArguments.push(
|
|
200
|
+
"-NoProfile",
|
|
201
|
+
"-NonInteractive",
|
|
202
|
+
"\u2013ExecutionPolicy",
|
|
203
|
+
"Bypass",
|
|
204
|
+
"-EncodedCommand"
|
|
205
|
+
);
|
|
206
|
+
if (!isWsl) {
|
|
207
|
+
childProcessOptions.windowsVerbatimArguments = true;
|
|
208
|
+
}
|
|
209
|
+
const encodedArguments = ["Start"];
|
|
210
|
+
if (options.wait) {
|
|
211
|
+
encodedArguments.push("-Wait");
|
|
212
|
+
}
|
|
213
|
+
if (app) {
|
|
214
|
+
encodedArguments.push(`"\`"${app}\`""`, "-ArgumentList");
|
|
215
|
+
if (options.target) {
|
|
216
|
+
appArguments.unshift(options.target);
|
|
217
|
+
}
|
|
218
|
+
} else if (options.target) {
|
|
219
|
+
encodedArguments.push(`"${options.target}"`);
|
|
220
|
+
}
|
|
221
|
+
if (appArguments.length > 0) {
|
|
222
|
+
appArguments = appArguments.map((arg) => `"\`"${arg}\`""`);
|
|
223
|
+
encodedArguments.push(appArguments.join(","));
|
|
224
|
+
}
|
|
225
|
+
options.target = Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
|
|
226
|
+
} else {
|
|
227
|
+
if (app) {
|
|
228
|
+
command = app;
|
|
229
|
+
} else {
|
|
230
|
+
const isBundled = !__dirname || __dirname === "/";
|
|
231
|
+
let exeLocalXdgOpen = false;
|
|
232
|
+
try {
|
|
233
|
+
await fs.access(localXdgOpenPath, fsConstants.X_OK);
|
|
234
|
+
exeLocalXdgOpen = true;
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
const useSystemXdgOpen = process.versions.electron || platform === "android" || isBundled || !exeLocalXdgOpen;
|
|
238
|
+
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
239
|
+
}
|
|
240
|
+
if (appArguments.length > 0) {
|
|
241
|
+
cliArguments.push(...appArguments);
|
|
242
|
+
}
|
|
243
|
+
if (!options.wait) {
|
|
244
|
+
childProcessOptions.stdio = "ignore";
|
|
245
|
+
childProcessOptions.detached = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (options.target) {
|
|
249
|
+
cliArguments.push(options.target);
|
|
250
|
+
}
|
|
251
|
+
if (platform === "darwin" && appArguments.length > 0) {
|
|
252
|
+
cliArguments.push("--args", ...appArguments);
|
|
253
|
+
}
|
|
254
|
+
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
255
|
+
if (options.wait) {
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
subprocess.once("error", reject);
|
|
258
|
+
subprocess.once("close", (exitCode) => {
|
|
259
|
+
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
260
|
+
reject(new Error(`Exited with code ${exitCode}`));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
resolve(subprocess);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
subprocess.unref();
|
|
268
|
+
return subprocess;
|
|
269
|
+
};
|
|
270
|
+
var open = (target, options) => {
|
|
271
|
+
if (typeof target !== "string") {
|
|
272
|
+
throw new TypeError("Expected a `target`");
|
|
273
|
+
}
|
|
274
|
+
return baseOpen({
|
|
275
|
+
...options,
|
|
276
|
+
target
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
var openApp = (name, options) => {
|
|
280
|
+
if (typeof name !== "string") {
|
|
281
|
+
throw new TypeError("Expected a `name`");
|
|
282
|
+
}
|
|
283
|
+
const { arguments: appArguments = [] } = options || {};
|
|
284
|
+
if (appArguments !== void 0 && appArguments !== null && !Array.isArray(appArguments)) {
|
|
285
|
+
throw new TypeError("Expected `appArguments` as Array type");
|
|
286
|
+
}
|
|
287
|
+
return baseOpen({
|
|
288
|
+
...options,
|
|
289
|
+
app: {
|
|
290
|
+
name,
|
|
291
|
+
arguments: appArguments
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
function detectArchBinary(binary) {
|
|
296
|
+
if (typeof binary === "string" || Array.isArray(binary)) {
|
|
297
|
+
return binary;
|
|
298
|
+
}
|
|
299
|
+
const { [arch]: archBinary } = binary;
|
|
300
|
+
if (!archBinary) {
|
|
301
|
+
throw new Error(`${arch} is not supported`);
|
|
302
|
+
}
|
|
303
|
+
return archBinary;
|
|
304
|
+
}
|
|
305
|
+
function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
306
|
+
if (wsl && isWsl) {
|
|
307
|
+
return detectArchBinary(wsl);
|
|
308
|
+
}
|
|
309
|
+
if (!platformBinary) {
|
|
310
|
+
throw new Error(`${platform} is not supported`);
|
|
311
|
+
}
|
|
312
|
+
return detectArchBinary(platformBinary);
|
|
313
|
+
}
|
|
314
|
+
var apps = {};
|
|
315
|
+
defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
|
|
316
|
+
darwin: "google chrome",
|
|
317
|
+
win32: "chrome",
|
|
318
|
+
linux: ["google-chrome", "google-chrome-stable", "chromium"]
|
|
319
|
+
}, {
|
|
320
|
+
wsl: {
|
|
321
|
+
ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
322
|
+
x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
|
|
323
|
+
}
|
|
324
|
+
}));
|
|
325
|
+
defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
|
|
326
|
+
darwin: "firefox",
|
|
327
|
+
win32: "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
|
|
328
|
+
linux: "firefox"
|
|
329
|
+
}, {
|
|
330
|
+
wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
|
|
331
|
+
}));
|
|
332
|
+
defineLazyProperty(apps, "edge", () => detectPlatformBinary({
|
|
333
|
+
darwin: "microsoft edge",
|
|
334
|
+
win32: "msedge",
|
|
335
|
+
linux: ["microsoft-edge", "microsoft-edge-dev"]
|
|
336
|
+
}, {
|
|
337
|
+
wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
|
|
338
|
+
}));
|
|
339
|
+
open.apps = apps;
|
|
340
|
+
open.openApp = openApp;
|
|
341
|
+
module.exports = open;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
export default require_open();
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tthr/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Tether CLI - project scaffolding, migrations, and deployment",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tthr": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
22
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"start": "TETHER_DEV=true node dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^13.0.0",
|
|
28
|
+
"chalk": "^5.4.1",
|
|
29
|
+
"ora": "^8.1.1",
|
|
30
|
+
"prompts": "^2.4.2",
|
|
31
|
+
"fs-extra": "^11.2.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/fs-extra": "^11.0.4",
|
|
35
|
+
"@types/prompts": "^2.4.9",
|
|
36
|
+
"tsup": "^8.3.6",
|
|
37
|
+
"typescript": "^5.7.2"
|
|
38
|
+
}
|
|
39
|
+
}
|