@inflector/aura 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA2CnC,CAAlB;iBAAkB,CAAF;;;;;;;;;;;;;;;;qBAR6D,CAAC;;;;;;;;;;;;;;;;CAqC/E,CAAA"}
package/dist/auth.js ADDED
@@ -0,0 +1,71 @@
1
+ import { createAuthClient } from "better-auth/client";
2
+ import { anonymousClient } from "better-auth/client/plugins";
3
+ import { computed } from "nanostores";
4
+ export const AuraAuth = (url, workspace) => {
5
+ const authClient = createAuthClient({
6
+ baseURL: url + "/api/auth/" + workspace,
7
+ plugins: [
8
+ anonymousClient()
9
+ ],
10
+ fetchOptions: {
11
+ credentials: "include",
12
+ },
13
+ });
14
+ const Session = computed(authClient.useSession, (session) => {
15
+ if (!session?.data?.user)
16
+ return session;
17
+ return {
18
+ ...session,
19
+ data: {
20
+ ...session.data,
21
+ user: {
22
+ ...session.data.user,
23
+ image: session.data.user.image
24
+ ? url + session.data.user.image
25
+ : null,
26
+ },
27
+ },
28
+ };
29
+ });
30
+ authClient.getSession();
31
+ return {
32
+ Logout: authClient.signOut,
33
+ Email: {
34
+ SignUp: authClient.signUp.email,
35
+ Login: authClient.signIn.email,
36
+ },
37
+ Oauth: (() => {
38
+ const withDefaultCallback = (provider, data) => authClient.signIn.social({
39
+ provider,
40
+ callbackURL: window.location.href,
41
+ ...(data ?? {}),
42
+ // Let any explicit callbackURL from data override default
43
+ ...(data?.callbackURL ? { callbackURL: data.callbackURL } : {}),
44
+ });
45
+ return {
46
+ Google: (data) => withDefaultCallback("google", data),
47
+ Apple: (data) => withDefaultCallback("apple", data),
48
+ Discord: (data) => withDefaultCallback("discord", data),
49
+ Facebook: (data) => withDefaultCallback("facebook", data),
50
+ Github: (data) => withDefaultCallback("github", data),
51
+ Gitlab: (data) => withDefaultCallback("gitlab", data),
52
+ HuggingFace: (data) => withDefaultCallback("hf", data),
53
+ Linkedin: (data) => withDefaultCallback("linkedin", data),
54
+ Microsoft: (data) => withDefaultCallback("microsoft", data),
55
+ Redit: (data) => withDefaultCallback("redit", data),
56
+ Polar: (data) => withDefaultCallback("polar", data),
57
+ Spotify: (data) => withDefaultCallback("spotify", data),
58
+ Tiktok: (data) => withDefaultCallback("tiktok", data),
59
+ Twitch: (data) => withDefaultCallback("twitch", data),
60
+ X: (data) => withDefaultCallback("x", data),
61
+ VK: (data) => withDefaultCallback("vk", data),
62
+ Zoom: (data) => withDefaultCallback("zoom", data),
63
+ };
64
+ })(),
65
+ Signal: Session,
66
+ Anonymous: {
67
+ SignUp: authClient.signIn.anonymous,
68
+ Delete: authClient.deleteAnonymousUser,
69
+ }
70
+ };
71
+ };
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export declare function build(silent?: boolean): void;
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AA2JA,wBAAgB,KAAK,CAAC,MAAM,UAAQ,QAqBnC"}
package/dist/bin.js ADDED
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env bun
2
+ import chalk from "chalk";
3
+ import * as fs from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { EventSource } from "eventsource";
6
+ // --- UI & Styling Utilities ---
7
+ const BORDER_COLOR = chalk.hex("#5C6BC0");
8
+ const ACCENT_COLOR = chalk.cyanBright;
9
+ const log = {
10
+ info: (msg) => console.log(chalk.blue("ℹ") + " " + msg),
11
+ success: (msg) => console.log(chalk.green("✔") + " " + msg),
12
+ warn: (msg) => console.log(chalk.yellow("⚠") + " " + msg),
13
+ error: (msg) => console.log(chalk.red("✖") + " " + msg),
14
+ dim: (msg) => console.log(chalk.dim(msg)),
15
+ br: () => console.log(""),
16
+ };
17
+ class Spinner {
18
+ timer = null;
19
+ frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
20
+ i = 0;
21
+ text;
22
+ constructor(text) {
23
+ this.text = text;
24
+ }
25
+ start() {
26
+ if (this.timer)
27
+ return;
28
+ process.stdout.write("\x1B[?25l");
29
+ this.timer = setInterval(() => {
30
+ const frame = this.frames[this.i = (this.i + 1) % this.frames.length];
31
+ process.stdout.write(`\r${chalk.cyan(frame)} ${this.text}`);
32
+ }, 80);
33
+ }
34
+ stop(message, success = true) {
35
+ if (this.timer) {
36
+ clearInterval(this.timer);
37
+ this.timer = null;
38
+ }
39
+ process.stdout.write("\r\x1B[K");
40
+ process.stdout.write("\x1B[?25h");
41
+ if (message) {
42
+ const icon = success ? chalk.green("✔") : chalk.red("✖");
43
+ console.log(`${icon} ${message}`);
44
+ }
45
+ }
46
+ setText(text) {
47
+ this.text = text;
48
+ }
49
+ }
50
+ function printBanner() {
51
+ console.log("");
52
+ console.log(chalk.bold.hex("#FFA500")(" A U R A ") + chalk.dim(" | Inflector CLI"));
53
+ console.log(chalk.dim(" ──────────────────────────"));
54
+ console.log("");
55
+ }
56
+ function printBox(title, content) {
57
+ const lines = content.split('\n');
58
+ const width = Math.max(title.length, ...lines.map(l => l.length)) + 4;
59
+ const top = BORDER_COLOR("┌" + "─".repeat(width) + "┐");
60
+ const bottom = BORDER_COLOR("└" + "─".repeat(width) + "┘");
61
+ console.log(top);
62
+ console.log(BORDER_COLOR("│") + " " + chalk.bold.white(title.padEnd(width - 2)) + BORDER_COLOR("│"));
63
+ console.log(BORDER_COLOR("├" + "─".repeat(width) + "┤"));
64
+ lines.forEach(line => {
65
+ console.log(BORDER_COLOR("│") + " " + ACCENT_COLOR(line.padEnd(width - 2)) + BORDER_COLOR("│"));
66
+ });
67
+ console.log(bottom);
68
+ }
69
+ // --- Type Inference Utilities ---
70
+ function isAuraFunctionHandlerExport(content) {
71
+ return /export\s+default\s+AuraFunction\s*\(/.test(content);
72
+ }
73
+ function inferAuraFunctionReturnType(content) {
74
+ if (/return\s+sse\s*\(/.test(content))
75
+ return "SSEHandler";
76
+ if (/return\s*["'`][^"'`]+["'`]/.test(content))
77
+ return "string";
78
+ if (/return\s*\d+(\.\d+)?\s*;/.test(content))
79
+ return "number";
80
+ if (/return\s*(true|false)\s*;/.test(content))
81
+ return "boolean";
82
+ if (/return\s*\[[^\]]*\]\s*;/.test(content))
83
+ return "any[]";
84
+ if (/return\s*{[\s\S]*?};/.test(content))
85
+ return "Record<string, any>";
86
+ return "any";
87
+ }
88
+ function scanFolderWithTypes(folder) {
89
+ const result = {};
90
+ if (!fs.existsSync(folder))
91
+ return result;
92
+ const entries = fs.readdirSync(folder);
93
+ for (const entry of entries) {
94
+ const fullPath = join(folder, entry);
95
+ const stat = fs.statSync(fullPath);
96
+ if (stat.isDirectory()) {
97
+ result[entry] = scanFolderWithTypes(fullPath);
98
+ }
99
+ else if (stat.isFile() && /\.(ts|js)$/.test(entry)) {
100
+ const name = entry.replace(/\.[jt]s$/, "");
101
+ let content = "";
102
+ try {
103
+ content = fs.readFileSync(fullPath, "utf-8");
104
+ }
105
+ catch { }
106
+ if (isAuraFunctionHandlerExport(content)) {
107
+ result[name] = {
108
+ type: "AuraFunctionHandler",
109
+ returnType: inferAuraFunctionReturnType(content)
110
+ };
111
+ }
112
+ else {
113
+ result[name] = { type: "unknown" };
114
+ }
115
+ }
116
+ }
117
+ return result;
118
+ }
119
+ function objectToTypeWithReturn(name, obj, auraType = "AuraFunctionHandler") {
120
+ const lines = [];
121
+ function toFnType(returnType) { return `(...args: any[]) => Promise<${returnType}>`; }
122
+ function recurse(o, indent = " ") {
123
+ const inner = [];
124
+ for (const key in o) {
125
+ const val = o[key];
126
+ if (val && typeof val === "object" && val.type === auraType) {
127
+ inner.push(`${indent}${key}: ${toFnType(val.returnType)};`);
128
+ }
129
+ else if (val && typeof val === "object" && val.type === "unknown") {
130
+ inner.push(`${indent}${key}: any;`);
131
+ }
132
+ else if (typeof val === "object") {
133
+ inner.push(`${indent}${key}: {`);
134
+ inner.push(...recurse(val, indent + " "));
135
+ inner.push(`${indent}}`);
136
+ }
137
+ }
138
+ return inner;
139
+ }
140
+ lines.push(`export type ${name} = {`);
141
+ lines.push(...recurse(obj));
142
+ lines.push(`};`);
143
+ return lines.join("\n");
144
+ }
145
+ export function build(silent = false) {
146
+ const spin = silent ? null : new Spinner("Scanning functions & generating types...");
147
+ if (spin)
148
+ spin.start();
149
+ try {
150
+ const folder = join(process.cwd(), "Aura", "Functions");
151
+ const structure = scanFolderWithTypes(folder);
152
+ const tsType = 'import type { SSEHandler } from "@inflector/aura";\n' + objectToTypeWithReturn("AuraFunctions", structure);
153
+ const generatedDir = join(process.cwd(), "Aura", ".generated");
154
+ if (!fs.existsSync(generatedDir)) {
155
+ fs.mkdirSync(generatedDir, { recursive: true });
156
+ }
157
+ const outFile = join(generatedDir, "index.ts");
158
+ fs.writeFileSync(outFile, tsType, "utf-8");
159
+ if (spin)
160
+ spin.stop("Types Generated", true);
161
+ }
162
+ catch (e) {
163
+ if (spin)
164
+ spin.stop("Build failed", false);
165
+ log.error(e.message);
166
+ }
167
+ }
168
+ // --- Auth & API Helpers ---
169
+ async function getToken() {
170
+ try {
171
+ const Config = JSON.parse(fs.readFileSync("./Aura/config.json", "utf-8"));
172
+ return await Bun.secrets.get({
173
+ name: `aura-deploy-key/${Config.Url}-${Config.WorkSpace}`,
174
+ service: "aura@inflector",
175
+ });
176
+ }
177
+ catch (e) {
178
+ return null;
179
+ }
180
+ }
181
+ async function askUser(question) {
182
+ process.stdout.write(chalk.yellow("? ") + chalk.bold(question) + chalk.dim(" [Y/n] "));
183
+ for await (const line of console) {
184
+ const answer = line.trim().toLowerCase();
185
+ if (answer === 'y' || answer === 'yes' || answer === '')
186
+ return true;
187
+ return false;
188
+ }
189
+ return false;
190
+ }
191
+ /**
192
+ * Ensures user is logged in before proceeding with critical tasks.
193
+ */
194
+ async function checkAndLogin() {
195
+ const token = await getToken();
196
+ if (!token) {
197
+ log.warn("No active session found.");
198
+ const shouldLogin = await askUser("Would you like to Login now?");
199
+ if (shouldLogin) {
200
+ return await login();
201
+ }
202
+ return false;
203
+ }
204
+ return true;
205
+ }
206
+ /**
207
+ * Wrapper for Fetch that handles 400, 401, 403 by prompting to login
208
+ * Accepts an optional Spinner to pause/resume during the prompt.
209
+ */
210
+ async function fetchWithAuth(url, options = {}, spinner) {
211
+ const token = await getToken();
212
+ // First attempt
213
+ let res = await fetch(url, {
214
+ ...options,
215
+ headers: {
216
+ ...options.headers,
217
+ "aura-deploy-key": token ?? ""
218
+ }
219
+ });
220
+ if (res.status === 401 || res.status === 403 || res.status === 400) {
221
+ if (spinner)
222
+ spinner.stop(); // Stop spinner to allow user input
223
+ log.br();
224
+ log.error(`Authorization Error (${res.status})`);
225
+ console.log(chalk.dim(" Your session is invalid or missing."));
226
+ log.br();
227
+ const shouldLogin = await askUser("Would you like to log in now?");
228
+ if (shouldLogin) {
229
+ const loginSuccess = await login();
230
+ if (loginSuccess) {
231
+ if (spinner)
232
+ spinner.start(); // Restart spinner
233
+ const newToken = await getToken();
234
+ // We don't log "Retrying" if we have a spinner running,
235
+ // just update the spinner text if you want, or let it spin.
236
+ res = await fetch(url, {
237
+ ...options,
238
+ headers: {
239
+ ...options.headers,
240
+ "aura-deploy-key": newToken ?? ""
241
+ }
242
+ });
243
+ }
244
+ else {
245
+ throw new Error("Login failed. Operation cancelled.");
246
+ }
247
+ }
248
+ else {
249
+ throw new Error("Login required to proceed.");
250
+ }
251
+ }
252
+ return res;
253
+ }
254
+ // --- Commands ---
255
+ async function pull() {
256
+ // 1. Check Auth first
257
+ if (!await checkAndLogin())
258
+ return;
259
+ const spin = new Spinner("Pulling workspace configuration...");
260
+ spin.start();
261
+ try {
262
+ const Config = JSON.parse(fs.readFileSync('./Aura/config.json', 'utf-8'));
263
+ const res = await fetchWithAuth(Config.Url + "/api/client/pull/" + Config.WorkSpace, {}, spin);
264
+ if (!res.ok) {
265
+ const text = await res.text();
266
+ throw new Error(`Pull failed (${res.status}): ${text}`);
267
+ }
268
+ const data = await res.json();
269
+ fs.writeFileSync("./Aura/schema.ts", data["schema.ts"].replace("@inflector/optima", "@inflector/aura"));
270
+ let fnCount = 0;
271
+ if (data.functions) {
272
+ Object.entries(data.functions).forEach(([relativePath, content]) => {
273
+ const functionFilePath = join("./Aura/Functions", relativePath);
274
+ fs.mkdirSync(dirname(functionFilePath), { recursive: true });
275
+ fs.writeFileSync(functionFilePath, content, "utf-8");
276
+ fnCount++;
277
+ });
278
+ build(true);
279
+ }
280
+ spin.stop("Pull successful", true);
281
+ console.log(chalk.dim(` • Synced schema.ts`));
282
+ console.log(chalk.dim(` • Synced ${fnCount} functions`));
283
+ }
284
+ catch (err) {
285
+ spin.stop("Pull failed", false);
286
+ log.error(err.message);
287
+ }
288
+ }
289
+ function init() {
290
+ log.info("Initializing Aura project...");
291
+ const filesContent = {
292
+ config: {
293
+ path: './Aura/config.json',
294
+ content: JSON.stringify({
295
+ Url: "http://localhost:3000",
296
+ Project: "your-project-id"
297
+ }, null, 2) + "\n"
298
+ },
299
+ indexTs: {
300
+ path: './Aura/index.ts',
301
+ content: `import { CreateAura } from "@inflector/aura"
302
+ import * as AuraConfig from "./config.json"
303
+ import * as Tables from "./schema.ts"
304
+ import type { AuraFunctions } from "./.generated"
305
+ export const Aura = CreateAura<AuraFunctions, typeof Tables>({
306
+ config: AuraConfig,
307
+ Tables
308
+ })
309
+ `
310
+ },
311
+ schemaTs: {
312
+ path: './Aura/schema.ts',
313
+ content: `// Define your schema here\n`
314
+ }
315
+ };
316
+ if (!fs.existsSync('./Aura')) {
317
+ fs.mkdirSync('./Aura', { recursive: true });
318
+ }
319
+ Object.values(filesContent).forEach(file => {
320
+ if (!fs.existsSync(file.path)) {
321
+ fs.writeFileSync(file.path, file.content, 'utf-8');
322
+ console.log(chalk.green(" + ") + chalk.dim(file.path));
323
+ }
324
+ else {
325
+ console.log(chalk.yellow(" ~ ") + chalk.dim(`${file.path} (exists)`));
326
+ }
327
+ });
328
+ log.br();
329
+ log.success("Initialization successful");
330
+ }
331
+ async function push() {
332
+ // 1. Check Auth first
333
+ if (!await checkAndLogin())
334
+ return;
335
+ const spin = new Spinner("Preparing deployment...");
336
+ spin.start();
337
+ try {
338
+ const Config = JSON.parse(fs.readFileSync('./Aura/config.json', 'utf-8'));
339
+ let schemaContent = "";
340
+ try {
341
+ schemaContent = fs.readFileSync('./Aura/schema.ts', 'utf-8').replace("@inflector/aura", "@inflector/optima");
342
+ }
343
+ catch {
344
+ spin.stop("Schema missing", false);
345
+ return;
346
+ }
347
+ function collectFunctions(baseDir, baseRel = "") {
348
+ let results = {};
349
+ if (!fs.existsSync(baseDir) || !fs.statSync(baseDir).isDirectory()) {
350
+ return results;
351
+ }
352
+ for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
353
+ const entryPath = join(baseDir, entry.name);
354
+ const relPath = baseRel ? join(baseRel, entry.name) : entry.name;
355
+ if (entry.isDirectory()) {
356
+ Object.assign(results, collectFunctions(entryPath, relPath));
357
+ }
358
+ else if (entry.isFile()) {
359
+ results[relPath] = fs.readFileSync(entryPath, "utf-8");
360
+ }
361
+ }
362
+ return results;
363
+ }
364
+ const functionsDir = './Aura/Functions';
365
+ const functions = collectFunctions(functionsDir);
366
+ const fnCount = Object.keys(functions).length;
367
+ spin.setText(`Pushing schema & ${fnCount} functions...`);
368
+ const res = await fetchWithAuth(Config.Url + "/api/client/push/" + Config.WorkSpace, {
369
+ method: "POST",
370
+ headers: {
371
+ "Content-Type": "application/json",
372
+ },
373
+ body: JSON.stringify({
374
+ "schema.ts": schemaContent,
375
+ functions
376
+ })
377
+ }, spin); // Pass spinner
378
+ if (!res.ok) {
379
+ const text = await res.text();
380
+ throw new Error(`Push failed (${res.status}): ${text}`);
381
+ }
382
+ const data = await res.json();
383
+ if (data && data.success) {
384
+ spin.stop("Deployed successfully!", true);
385
+ build(true);
386
+ }
387
+ else {
388
+ spin.stop("Push response invalid", false);
389
+ console.log(data);
390
+ }
391
+ }
392
+ catch (err) {
393
+ spin.stop(); // Ensure spinner is stopped before logging error
394
+ log.error(err.message);
395
+ }
396
+ }
397
+ async function ping() {
398
+ const spin = new Spinner("Pinging server...");
399
+ spin.start();
400
+ try {
401
+ const Config = JSON.parse(fs.readFileSync('./Aura/config.json', 'utf-8'));
402
+ const token = await getToken();
403
+ // Start Timer
404
+ const start = performance.now();
405
+ // Use standard fetch here to avoid forcing login for simple ping
406
+ const res = await fetch(Config.Url + "/api/ping", {
407
+ headers: token ? { "aura-deploy-key": token } : {}
408
+ });
409
+ // End Timer
410
+ const end = performance.now();
411
+ if (!res.ok) {
412
+ throw new Error(`Ping failed with status ${res.status}`);
413
+ }
414
+ await res.text();
415
+ const duration = Math.round(end - start);
416
+ let speedColor = chalk.green;
417
+ if (duration > 200)
418
+ speedColor = chalk.yellow;
419
+ if (duration > 500)
420
+ speedColor = chalk.red;
421
+ spin.stop(`Pong! ${chalk.dim("Latency:")} ${speedColor(duration + "ms")}`, true);
422
+ }
423
+ catch (err) {
424
+ spin.stop("Ping failed", false);
425
+ log.error(err.message);
426
+ }
427
+ }
428
+ /**
429
+ * Returns a Promise that resolves to true if login success, false otherwise
430
+ */
431
+ function login() {
432
+ return new Promise((resolve) => {
433
+ let Config;
434
+ try {
435
+ Config = JSON.parse(fs.readFileSync("./Aura/config.json", "utf-8"));
436
+ }
437
+ catch {
438
+ log.error("Config file not found. Run `aura init` first.");
439
+ resolve(false);
440
+ return;
441
+ }
442
+ const url = `${Config.Url}/api/client/login/${Config.WorkSpace}`;
443
+ const sse = new EventSource(url);
444
+ let closed = false;
445
+ let spin = null;
446
+ const close = (success) => {
447
+ if (closed)
448
+ return;
449
+ closed = true;
450
+ sse.close();
451
+ resolve(success);
452
+ };
453
+ const timeout = setTimeout(() => {
454
+ if (spin)
455
+ spin.stop("Login timed out", false);
456
+ close(false);
457
+ }, 5 * 60 * 1000);
458
+ sse.addEventListener("open", () => {
459
+ spin = new Spinner("Connecting to authentication server...");
460
+ spin.start();
461
+ });
462
+ sse.addEventListener("token-created", (e) => {
463
+ if (spin)
464
+ spin.stop();
465
+ try {
466
+ const { loginToken } = JSON.parse(e.data);
467
+ const loginUrl = `${Config.Url}/api/client/login-complete/${loginToken}`;
468
+ log.br();
469
+ printBox("Authentication Required", loginUrl);
470
+ log.br();
471
+ spin = new Spinner("Waiting for browser approval...");
472
+ spin.start();
473
+ }
474
+ catch {
475
+ log.error("Failed to parse token-created payload");
476
+ }
477
+ });
478
+ sse.addEventListener("token-revoked", async (e) => {
479
+ try {
480
+ const { loginToken } = JSON.parse(e.data);
481
+ if (!loginToken) {
482
+ if (spin)
483
+ spin.stop("Received event without token", false);
484
+ close(false);
485
+ return;
486
+ }
487
+ await Bun.secrets.set({
488
+ name: `aura-deploy-key/${Config.Url}-${Config.WorkSpace}`,
489
+ service: "aura@inflector",
490
+ value: loginToken
491
+ });
492
+ if (spin)
493
+ spin.stop("Login successful", true);
494
+ clearTimeout(timeout);
495
+ close(true);
496
+ }
497
+ catch {
498
+ if (spin)
499
+ spin.stop("Failed to parse response", false);
500
+ close(false);
501
+ }
502
+ });
503
+ sse.onerror = () => {
504
+ // Optional: handle connection errors nicely
505
+ };
506
+ });
507
+ }
508
+ async function main() {
509
+ const args = process.argv.slice(2);
510
+ const Command = args[0];
511
+ printBanner();
512
+ try {
513
+ switch (Command) {
514
+ case "ping":
515
+ await ping();
516
+ break;
517
+ case "init":
518
+ init();
519
+ break;
520
+ case "push":
521
+ await push();
522
+ break;
523
+ case "pull":
524
+ await pull();
525
+ break;
526
+ case "build":
527
+ build();
528
+ break;
529
+ case "login":
530
+ await login();
531
+ break;
532
+ case undefined:
533
+ log.info("Available commands: init, pull, push, build, login, ping");
534
+ break;
535
+ default:
536
+ log.warn(`Unknown Command: ${Command}`);
537
+ log.info("Try: init, pull, push, build, login");
538
+ }
539
+ }
540
+ catch (e) {
541
+ log.error("Unexpected Error: " + e.message);
542
+ }
543
+ log.br();
544
+ process.exit(0);
545
+ }
546
+ main();