@tarout/cli 0.2.0 → 0.2.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.
@@ -0,0 +1,669 @@
1
+ import {
2
+ ExitCode,
3
+ error,
4
+ exit,
5
+ isJsonMode,
6
+ jsonError,
7
+ outputJson
8
+ } from "./chunk-FS74WWHV.js";
9
+
10
+ // src/lib/api.ts
11
+ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
12
+ import superjson from "superjson";
13
+
14
+ // src/lib/config.ts
15
+ import Conf from "conf";
16
+ import {
17
+ chmodSync,
18
+ existsSync,
19
+ mkdirSync,
20
+ readFileSync,
21
+ rmSync,
22
+ writeFileSync
23
+ } from "fs";
24
+ import { join } from "path";
25
+ var config = new Conf({
26
+ projectName: "tarout",
27
+ configFileMode: 384,
28
+ defaults: {
29
+ currentProfile: "default",
30
+ profiles: {}
31
+ }
32
+ });
33
+ function getConfig() {
34
+ return config.store;
35
+ }
36
+ function getCurrentProfile() {
37
+ const cfg = getConfig();
38
+ return cfg.profiles[cfg.currentProfile] || null;
39
+ }
40
+ function setProfile(name, profile) {
41
+ config.set(`profiles.${name}`, profile);
42
+ }
43
+ function setCurrentProfile(name) {
44
+ config.set("currentProfile", name);
45
+ }
46
+ function clearConfig() {
47
+ config.clear();
48
+ }
49
+ function isLoggedIn() {
50
+ const profile = getCurrentProfile();
51
+ return profile !== null && !!profile.token || !!process.env.TAROUT_TOKEN;
52
+ }
53
+ function getToken() {
54
+ const profile = getCurrentProfile();
55
+ return profile?.token || process.env.TAROUT_TOKEN || null;
56
+ }
57
+ function getApiUrl() {
58
+ const profile = getCurrentProfile();
59
+ return profile?.apiUrl || "https://tarout.sa";
60
+ }
61
+ function updateProfile(updates) {
62
+ const cfg = getConfig();
63
+ const currentProfileName = cfg.currentProfile;
64
+ const currentProfile = cfg.profiles[currentProfileName];
65
+ if (currentProfile) {
66
+ config.set(`profiles.${currentProfileName}`, {
67
+ ...currentProfile,
68
+ ...updates
69
+ });
70
+ }
71
+ }
72
+ var PROJECT_CONFIG_DIR = ".tarout";
73
+ var PROJECT_CONFIG_FILE = "project.json";
74
+ function chmodIfSupported(path, mode) {
75
+ try {
76
+ chmodSync(path, mode);
77
+ } catch {
78
+ }
79
+ }
80
+ function getProjectConfigDir(basePath) {
81
+ const base = basePath || process.cwd();
82
+ return join(base, PROJECT_CONFIG_DIR);
83
+ }
84
+ function getProjectConfigPath(basePath) {
85
+ return join(getProjectConfigDir(basePath), PROJECT_CONFIG_FILE);
86
+ }
87
+ function isProjectLinked(basePath) {
88
+ return existsSync(getProjectConfigPath(basePath));
89
+ }
90
+ function getProjectConfig(basePath) {
91
+ const configPath = getProjectConfigPath(basePath);
92
+ if (!existsSync(configPath)) {
93
+ return null;
94
+ }
95
+ try {
96
+ const content = readFileSync(configPath, "utf-8");
97
+ return JSON.parse(content);
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function setProjectConfig(config2, basePath) {
103
+ const configDir = getProjectConfigDir(basePath);
104
+ const configPath = getProjectConfigPath(basePath);
105
+ if (!existsSync(configDir)) {
106
+ mkdirSync(configDir, { recursive: true, mode: 448 });
107
+ }
108
+ chmodIfSupported(configDir, 448);
109
+ writeFileSync(configPath, JSON.stringify(config2, null, 2), {
110
+ encoding: "utf-8",
111
+ mode: 384
112
+ });
113
+ chmodIfSupported(configPath, 384);
114
+ const gitignorePath = join(configDir, ".gitignore");
115
+ if (!existsSync(gitignorePath)) {
116
+ writeFileSync(
117
+ gitignorePath,
118
+ "# Ignore local tarout config\n*\n!.gitignore\n",
119
+ { encoding: "utf-8", mode: 384 }
120
+ );
121
+ chmodIfSupported(gitignorePath, 384);
122
+ }
123
+ }
124
+ function removeProjectConfig(basePath) {
125
+ const configDir = getProjectConfigDir(basePath);
126
+ if (!existsSync(configDir)) {
127
+ return false;
128
+ }
129
+ try {
130
+ rmSync(configDir, { recursive: true, force: true });
131
+ return true;
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ // src/lib/errors.ts
138
+ var CliError = class extends Error {
139
+ constructor(message, code = ExitCode.GENERAL_ERROR, suggestions) {
140
+ super(message);
141
+ this.code = code;
142
+ this.suggestions = suggestions;
143
+ this.name = "CliError";
144
+ }
145
+ code;
146
+ suggestions;
147
+ };
148
+ var AuthError = class extends CliError {
149
+ constructor(message = "Not logged in. Run 'tarout login' first.") {
150
+ super(message, ExitCode.AUTH_ERROR);
151
+ }
152
+ };
153
+ var NotFoundError = class extends CliError {
154
+ constructor(resource, id, suggestions) {
155
+ super(`${resource} "${id}" not found`, ExitCode.NOT_FOUND, suggestions);
156
+ }
157
+ };
158
+ var InvalidArgumentError = class extends CliError {
159
+ constructor(message) {
160
+ super(message, ExitCode.INVALID_ARGUMENTS);
161
+ }
162
+ };
163
+ var DeploymentFailedError = class extends CliError {
164
+ constructor(message, deploymentId, errorAnalysis) {
165
+ super(message, ExitCode.DEPLOYMENT_FAILED);
166
+ this.deploymentId = deploymentId;
167
+ this.errorAnalysis = errorAnalysis;
168
+ }
169
+ deploymentId;
170
+ errorAnalysis;
171
+ };
172
+ var DeploymentTimeoutError = class extends CliError {
173
+ constructor(message = "Deployment timed out", deploymentId) {
174
+ super(message, ExitCode.DEPLOYMENT_TIMEOUT);
175
+ this.deploymentId = deploymentId;
176
+ }
177
+ deploymentId;
178
+ };
179
+ var BuildFailedError = class extends CliError {
180
+ constructor(message, deploymentId, errorAnalysis) {
181
+ super(message, ExitCode.BUILD_FAILED);
182
+ this.deploymentId = deploymentId;
183
+ this.errorAnalysis = errorAnalysis;
184
+ }
185
+ deploymentId;
186
+ errorAnalysis;
187
+ };
188
+ function handleError(err) {
189
+ if (err instanceof CliError) {
190
+ if (isJsonMode()) {
191
+ outputJson(
192
+ jsonError(getErrorCode(err.code), err.message, err.suggestions)
193
+ );
194
+ } else {
195
+ error(err.message, err.suggestions);
196
+ }
197
+ exit(err.code);
198
+ }
199
+ if (err && typeof err === "object") {
200
+ const e = err;
201
+ const code = e.code ?? e.data?.code;
202
+ if (code) {
203
+ const exitCode = mapTrpcErrorCode(code);
204
+ const message2 = e.message ?? "Request failed";
205
+ if (isJsonMode()) {
206
+ outputJson(jsonError(code, message2));
207
+ } else {
208
+ error(message2);
209
+ }
210
+ exit(exitCode);
211
+ }
212
+ }
213
+ const message = err instanceof Error ? err.message : String(err);
214
+ if (isJsonMode()) {
215
+ outputJson(jsonError("UNKNOWN_ERROR", message));
216
+ } else {
217
+ error(message);
218
+ }
219
+ exit(ExitCode.GENERAL_ERROR);
220
+ }
221
+ function getErrorCode(exitCode) {
222
+ const codes = {
223
+ [ExitCode.SUCCESS]: "SUCCESS",
224
+ [ExitCode.GENERAL_ERROR]: "ERROR",
225
+ [ExitCode.INVALID_ARGUMENTS]: "INVALID_ARGUMENTS",
226
+ [ExitCode.AUTH_ERROR]: "AUTH_ERROR",
227
+ [ExitCode.NOT_FOUND]: "NOT_FOUND",
228
+ [ExitCode.PERMISSION_DENIED]: "PERMISSION_DENIED",
229
+ [ExitCode.DEPLOYMENT_FAILED]: "DEPLOYMENT_FAILED",
230
+ [ExitCode.DEPLOYMENT_TIMEOUT]: "DEPLOYMENT_TIMEOUT",
231
+ [ExitCode.BUILD_FAILED]: "BUILD_FAILED"
232
+ };
233
+ return codes[exitCode] || "ERROR";
234
+ }
235
+ function mapTrpcErrorCode(code) {
236
+ const mapping = {
237
+ UNAUTHORIZED: ExitCode.AUTH_ERROR,
238
+ FORBIDDEN: ExitCode.PERMISSION_DENIED,
239
+ NOT_FOUND: ExitCode.NOT_FOUND,
240
+ BAD_REQUEST: ExitCode.INVALID_ARGUMENTS,
241
+ PARSE_ERROR: ExitCode.INVALID_ARGUMENTS
242
+ };
243
+ return mapping[code] || ExitCode.GENERAL_ERROR;
244
+ }
245
+ function findSimilar(target, candidates, maxResults = 3) {
246
+ const targetLower = target.toLowerCase();
247
+ return candidates.map((candidate) => ({
248
+ candidate,
249
+ score: similarity(targetLower, candidate.toLowerCase())
250
+ })).filter(({ score }) => score > 0.3).sort((a, b) => b.score - a.score).slice(0, maxResults).map(({ candidate }) => candidate);
251
+ }
252
+ function similarity(s1, s2) {
253
+ if (s1 === s2) return 1;
254
+ if (s1.length < 2 || s2.length < 2) return 0;
255
+ const bigrams1 = /* @__PURE__ */ new Set();
256
+ for (let i = 0; i < s1.length - 1; i++) {
257
+ bigrams1.add(s1.slice(i, i + 2));
258
+ }
259
+ let intersection = 0;
260
+ for (let i = 0; i < s2.length - 1; i++) {
261
+ if (bigrams1.has(s2.slice(i, i + 2))) {
262
+ intersection++;
263
+ }
264
+ }
265
+ return 2 * intersection / (s1.length - 1 + s2.length - 1);
266
+ }
267
+ var ERROR_PATTERNS = [
268
+ {
269
+ patterns: [
270
+ /npm ERR!/i,
271
+ /npm error/i,
272
+ /ERESOLVE/i,
273
+ /Could not resolve dependency/i,
274
+ /peer dep missing/i
275
+ ],
276
+ category: "npm_install",
277
+ type: "build_error",
278
+ possibleCauses: [
279
+ "Package version conflicts",
280
+ "Missing peer dependencies",
281
+ "Invalid package.json",
282
+ "Network issues during npm install",
283
+ "Private package without authentication"
284
+ ],
285
+ suggestedFixes: [
286
+ "Run `npm install` locally to reproduce the issue",
287
+ "Check for conflicting package versions in package.json",
288
+ "Try deleting package-lock.json and node_modules, then reinstall",
289
+ "Add missing peer dependencies explicitly",
290
+ "Check if private packages are properly authenticated"
291
+ ]
292
+ },
293
+ {
294
+ patterns: [
295
+ /yarn error/i,
296
+ /YN\d{4}/i,
297
+ /error An unexpected error occurred/i
298
+ ],
299
+ category: "yarn_install",
300
+ type: "build_error",
301
+ possibleCauses: [
302
+ "Package version conflicts",
303
+ "Corrupted yarn.lock file",
304
+ "Network issues during install",
305
+ "Incompatible yarn version"
306
+ ],
307
+ suggestedFixes: [
308
+ "Run `yarn install` locally to reproduce the issue",
309
+ "Delete yarn.lock and node_modules, then reinstall",
310
+ "Check for conflicting resolutions in package.json",
311
+ "Ensure yarn version matches the project requirements"
312
+ ]
313
+ },
314
+ {
315
+ patterns: [/pnpm ERR/i, /ERR_PNPM/i],
316
+ category: "pnpm_install",
317
+ type: "build_error",
318
+ possibleCauses: [
319
+ "Package version conflicts",
320
+ "Incompatible pnpm version",
321
+ "Corrupted pnpm-lock.yaml"
322
+ ],
323
+ suggestedFixes: [
324
+ "Run `pnpm install` locally to reproduce the issue",
325
+ "Delete pnpm-lock.yaml and node_modules, then reinstall",
326
+ "Check pnpm version compatibility"
327
+ ]
328
+ },
329
+ {
330
+ patterns: [/bun install/i, /error: .* failed to resolve/i],
331
+ category: "bun_install",
332
+ type: "build_error",
333
+ possibleCauses: [
334
+ "Package resolution failure",
335
+ "Incompatible bun version",
336
+ "Missing dependencies"
337
+ ],
338
+ suggestedFixes: [
339
+ "Run `bun install` locally to reproduce the issue",
340
+ "Delete bun.lockb and node_modules, then reinstall",
341
+ "Check bun version compatibility"
342
+ ]
343
+ },
344
+ {
345
+ patterns: [
346
+ /error TS\d+/i,
347
+ /TypeScript error/i,
348
+ /tsc.*error/i,
349
+ /Type .* is not assignable to/i,
350
+ /Cannot find module/i,
351
+ /Property .* does not exist/i
352
+ ],
353
+ category: "typescript",
354
+ type: "build_error",
355
+ possibleCauses: [
356
+ "TypeScript compilation errors in source code",
357
+ "Missing type definitions",
358
+ "Incompatible TypeScript version",
359
+ "Strict mode type errors"
360
+ ],
361
+ suggestedFixes: [
362
+ "Run `npx tsc --noEmit` locally to see all type errors",
363
+ "Fix the TypeScript errors in the indicated files",
364
+ "Install missing @types/* packages",
365
+ "Check tsconfig.json for strict mode settings"
366
+ ]
367
+ },
368
+ {
369
+ patterns: [
370
+ /COPY failed/i,
371
+ /RUN.*failed/i,
372
+ /failed to build/i,
373
+ /Error building image/i,
374
+ /Dockerfile/i
375
+ ],
376
+ category: "docker_build",
377
+ type: "build_error",
378
+ possibleCauses: [
379
+ "Invalid Dockerfile syntax",
380
+ "Missing files in build context",
381
+ "Failed RUN commands",
382
+ "Base image not found"
383
+ ],
384
+ suggestedFixes: [
385
+ "Test Docker build locally with `docker build .`",
386
+ "Check that all required files are in the build context",
387
+ "Verify Dockerfile syntax and commands",
388
+ "Ensure base image exists and is accessible"
389
+ ]
390
+ },
391
+ {
392
+ patterns: [
393
+ /build.*failed/i,
394
+ /Build failed/i,
395
+ /exit code 1/i,
396
+ /Command failed/i,
397
+ /Script.*failed/i
398
+ ],
399
+ category: "build_script",
400
+ type: "build_error",
401
+ possibleCauses: [
402
+ "Build script error in package.json",
403
+ "Missing build dependencies",
404
+ "Environment variable issues",
405
+ "Build command not found"
406
+ ],
407
+ suggestedFixes: [
408
+ "Run `npm run build` locally to reproduce the error",
409
+ "Check package.json build script for errors",
410
+ "Ensure all required environment variables are set",
411
+ "Verify build dependencies are installed"
412
+ ]
413
+ },
414
+ {
415
+ patterns: [
416
+ /out of memory/i,
417
+ /JavaScript heap out of memory/i,
418
+ /FATAL ERROR: .* allocation failed/i,
419
+ /OOMKilled/i
420
+ ],
421
+ category: "memory_limit",
422
+ type: "build_error",
423
+ possibleCauses: [
424
+ "Build process requires more memory than allocated",
425
+ "Memory leak in build process",
426
+ "Large dependency tree"
427
+ ],
428
+ suggestedFixes: [
429
+ "Increase memory allocation for the build",
430
+ "Optimize build process to use less memory",
431
+ "Consider splitting large bundles",
432
+ "Add NODE_OPTIONS=--max_old_space_size=4096"
433
+ ]
434
+ },
435
+ {
436
+ patterns: [/timed? ?out/i, /deadline exceeded/i, /timeout/i],
437
+ category: "timeout",
438
+ type: "build_error",
439
+ possibleCauses: [
440
+ "Build process took too long",
441
+ "Network timeout during dependency install",
442
+ "Slow external service calls"
443
+ ],
444
+ suggestedFixes: [
445
+ "Optimize build process for faster execution",
446
+ "Check for slow network requests during build",
447
+ "Consider caching dependencies",
448
+ "Increase build timeout if possible"
449
+ ]
450
+ },
451
+ {
452
+ patterns: [/permission denied/i, /EACCES/i, /EPERM/i],
453
+ category: "permission",
454
+ type: "build_error",
455
+ possibleCauses: [
456
+ "File permission issues",
457
+ "Attempting to write to read-only locations",
458
+ "Docker user permission mismatch"
459
+ ],
460
+ suggestedFixes: [
461
+ "Check file permissions in the project",
462
+ "Ensure Dockerfile uses correct user",
463
+ "Avoid writing to restricted directories"
464
+ ]
465
+ },
466
+ {
467
+ patterns: [
468
+ /ENOTFOUND/i,
469
+ /ECONNREFUSED/i,
470
+ /network error/i,
471
+ /fetch failed/i,
472
+ /getaddrinfo/i
473
+ ],
474
+ category: "network",
475
+ type: "build_error",
476
+ possibleCauses: [
477
+ "Network connectivity issues",
478
+ "DNS resolution failure",
479
+ "Registry unavailable",
480
+ "Firewall blocking connections"
481
+ ],
482
+ suggestedFixes: [
483
+ "Check if the package registry is accessible",
484
+ "Verify network configuration",
485
+ "Try again later if registry is temporarily down",
486
+ "Check for any proxy configuration issues"
487
+ ]
488
+ }
489
+ ];
490
+ function analyzeDeploymentError(logs, errorMessage) {
491
+ const relevantLogLines = [];
492
+ let matchedPattern = null;
493
+ let maxMatches = 0;
494
+ const allLines = errorMessage ? [...logs, errorMessage] : logs;
495
+ for (const pattern of ERROR_PATTERNS) {
496
+ let matches = 0;
497
+ const matchedLines = [];
498
+ for (const line of allLines) {
499
+ for (const regex of pattern.patterns) {
500
+ if (regex.test(line)) {
501
+ matches++;
502
+ if (!matchedLines.includes(line)) {
503
+ matchedLines.push(line);
504
+ }
505
+ break;
506
+ }
507
+ }
508
+ }
509
+ if (matches > maxMatches) {
510
+ maxMatches = matches;
511
+ matchedPattern = pattern;
512
+ relevantLogLines.length = 0;
513
+ relevantLogLines.push(...matchedLines.slice(0, 10));
514
+ }
515
+ }
516
+ const errorIndicators = [/error/i, /failed/i, /fatal/i, /exception/i];
517
+ for (const line of allLines) {
518
+ if (relevantLogLines.length < 15 && !relevantLogLines.includes(line) && errorIndicators.some((indicator) => indicator.test(line))) {
519
+ relevantLogLines.push(line);
520
+ }
521
+ }
522
+ if (matchedPattern) {
523
+ return {
524
+ type: matchedPattern.type,
525
+ category: matchedPattern.category,
526
+ possibleCauses: matchedPattern.possibleCauses,
527
+ suggestedFixes: matchedPattern.suggestedFixes,
528
+ relevantLogLines
529
+ };
530
+ }
531
+ return {
532
+ type: "unknown",
533
+ category: "unknown",
534
+ possibleCauses: [
535
+ "Unable to automatically determine the cause",
536
+ "Check the log output for specific error messages"
537
+ ],
538
+ suggestedFixes: [
539
+ "Review the deployment logs for error messages",
540
+ "Try running the build locally to reproduce the issue",
541
+ "Check environment variables and configuration",
542
+ "Contact support if the issue persists"
543
+ ],
544
+ relevantLogLines
545
+ };
546
+ }
547
+
548
+ // src/lib/password-gate.ts
549
+ var gateCookies = /* @__PURE__ */ new Map();
550
+ function requestUrl(input) {
551
+ if (input instanceof URL) return input;
552
+ if (typeof input === "string") return new URL(input);
553
+ return new URL(input.url);
554
+ }
555
+ function requestHeaders(input, init) {
556
+ const headers = new Headers(
557
+ input instanceof Request ? input.headers : void 0
558
+ );
559
+ if (init?.headers) {
560
+ new Headers(init.headers).forEach((value, key) => {
561
+ headers.set(key, value);
562
+ });
563
+ }
564
+ return headers;
565
+ }
566
+ function isPasswordGateResponse(body) {
567
+ return /site is password-protected/i.test(body);
568
+ }
569
+ function sitePassword() {
570
+ return process.env.TAROUT_SITE_PASSWORD || process.env.SITE_PASSWORD;
571
+ }
572
+ async function unlockPasswordGate(origin) {
573
+ const password = sitePassword();
574
+ if (!password) {
575
+ throw new AuthError(
576
+ "Tarout is currently password-protected. Set TAROUT_SITE_PASSWORD and try again."
577
+ );
578
+ }
579
+ const response = await fetch(`${origin}/api/password-gate`, {
580
+ method: "POST",
581
+ headers: { "content-type": "application/json" },
582
+ body: JSON.stringify({ password })
583
+ });
584
+ if (!response.ok) {
585
+ throw new AuthError(
586
+ "Tarout site password was rejected. Check TAROUT_SITE_PASSWORD and try again."
587
+ );
588
+ }
589
+ const setCookie = response.headers.get("set-cookie");
590
+ const cookie = setCookie?.split(",").find((part) => part.trim().startsWith("__tarout_site_gate="))?.trim().split(";")[0];
591
+ if (!cookie) {
592
+ throw new AuthError("Tarout did not return a site-gate session cookie.");
593
+ }
594
+ gateCookies.set(origin, cookie);
595
+ return cookie;
596
+ }
597
+ async function platformFetch(input, init) {
598
+ const url = requestUrl(input);
599
+ const headers = requestHeaders(input, init);
600
+ const existingCookie = gateCookies.get(url.origin);
601
+ if (existingCookie && !headers.has("cookie")) {
602
+ headers.set("cookie", existingCookie);
603
+ }
604
+ const response = await fetch(input, { ...init, headers });
605
+ if (response.status !== 401) return response;
606
+ const body = await response.clone().text().catch(() => "");
607
+ if (!isPasswordGateResponse(body)) return response;
608
+ const cookie = await unlockPasswordGate(url.origin);
609
+ headers.set("cookie", cookie);
610
+ return fetch(input, { ...init, headers });
611
+ }
612
+
613
+ // src/lib/api.ts
614
+ var client = null;
615
+ function createApiClient() {
616
+ if (!isLoggedIn()) {
617
+ throw new AuthError();
618
+ }
619
+ const token = getToken();
620
+ const apiUrl = getApiUrl();
621
+ return createTRPCProxyClient({
622
+ transformer: superjson,
623
+ links: [
624
+ httpBatchLink({
625
+ url: `${apiUrl}/api/trpc`,
626
+ headers: () => token ? { "x-api-key": token } : {},
627
+ fetch: platformFetch
628
+ })
629
+ ]
630
+ });
631
+ }
632
+ function getApiClient() {
633
+ if (!client) {
634
+ client = createApiClient();
635
+ }
636
+ return client;
637
+ }
638
+ function resetApiClient() {
639
+ client = null;
640
+ }
641
+
642
+ export {
643
+ getCurrentProfile,
644
+ setProfile,
645
+ setCurrentProfile,
646
+ clearConfig,
647
+ isLoggedIn,
648
+ getToken,
649
+ getApiUrl,
650
+ updateProfile,
651
+ isProjectLinked,
652
+ getProjectConfig,
653
+ setProjectConfig,
654
+ removeProjectConfig,
655
+ CliError,
656
+ AuthError,
657
+ NotFoundError,
658
+ InvalidArgumentError,
659
+ DeploymentFailedError,
660
+ DeploymentTimeoutError,
661
+ BuildFailedError,
662
+ handleError,
663
+ findSimilar,
664
+ analyzeDeploymentError,
665
+ platformFetch,
666
+ createApiClient,
667
+ getApiClient,
668
+ resetApiClient
669
+ };