@envpilot/cli 1.5.0 → 1.6.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.
|
@@ -7,7 +7,7 @@ function initSentry() {
|
|
|
7
7
|
Sentry.init({
|
|
8
8
|
dsn,
|
|
9
9
|
environment: "cli",
|
|
10
|
-
release: true ? "1.
|
|
10
|
+
release: true ? "1.6.1" : "0.0.0",
|
|
11
11
|
// Free tier: disable performance monitoring
|
|
12
12
|
tracesSampleRate: 0,
|
|
13
13
|
beforeSend(event) {
|
|
@@ -49,6 +49,26 @@ async function flushSentry() {
|
|
|
49
49
|
// src/lib/config.ts
|
|
50
50
|
import Conf from "conf";
|
|
51
51
|
var DEFAULT_API_URL = "https://www.envpilot.dev";
|
|
52
|
+
var HOST_CANONICALIZATION = {
|
|
53
|
+
"envpilot.dev": "www.envpilot.dev"
|
|
54
|
+
};
|
|
55
|
+
function normalizeApiUrl(raw) {
|
|
56
|
+
let value = raw.trim();
|
|
57
|
+
if (!/^https?:\/\//i.test(value)) {
|
|
58
|
+
value = `https://${value}`;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(value);
|
|
62
|
+
const canonicalHost = HOST_CANONICALIZATION[parsed.hostname.toLowerCase()];
|
|
63
|
+
if (canonicalHost) {
|
|
64
|
+
parsed.hostname = canonicalHost;
|
|
65
|
+
}
|
|
66
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
67
|
+
return parsed.toString().replace(/\/$/, "");
|
|
68
|
+
} catch {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
52
72
|
var config = new Conf({
|
|
53
73
|
projectName: "envpilot",
|
|
54
74
|
defaults: {
|
|
@@ -57,7 +77,7 @@ var config = new Conf({
|
|
|
57
77
|
});
|
|
58
78
|
function getConfig() {
|
|
59
79
|
return {
|
|
60
|
-
apiUrl: config.get("apiUrl") ?? DEFAULT_API_URL,
|
|
80
|
+
apiUrl: normalizeApiUrl(config.get("apiUrl") ?? DEFAULT_API_URL),
|
|
61
81
|
accessToken: config.get("accessToken"),
|
|
62
82
|
refreshToken: config.get("refreshToken"),
|
|
63
83
|
activeProjectId: config.get("activeProjectId"),
|
|
@@ -67,10 +87,12 @@ function getConfig() {
|
|
|
67
87
|
};
|
|
68
88
|
}
|
|
69
89
|
function getApiUrl() {
|
|
70
|
-
|
|
90
|
+
const stored = config.get("apiUrl");
|
|
91
|
+
const raw = stored ?? DEFAULT_API_URL;
|
|
92
|
+
return normalizeApiUrl(raw);
|
|
71
93
|
}
|
|
72
94
|
function setApiUrl(url) {
|
|
73
|
-
config.set("apiUrl", url);
|
|
95
|
+
config.set("apiUrl", normalizeApiUrl(url));
|
|
74
96
|
}
|
|
75
97
|
function getAccessToken() {
|
|
76
98
|
return config.get("accessToken");
|
|
@@ -157,7 +179,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
157
179
|
async function openTUI() {
|
|
158
180
|
const [{ render }, { CLIApp }] = await Promise.all([
|
|
159
181
|
import("ink"),
|
|
160
|
-
import("./app-
|
|
182
|
+
import("./app-LNI6WTJZ.js")
|
|
161
183
|
]);
|
|
162
184
|
while (true) {
|
|
163
185
|
let selectedArgv = null;
|
|
@@ -429,6 +451,9 @@ function notInitialized() {
|
|
|
429
451
|
function fileNotFound(path) {
|
|
430
452
|
return new CLIError(`File not found: ${path}`, ErrorCodes.FILE_NOT_FOUND);
|
|
431
453
|
}
|
|
454
|
+
function invalidInput(message) {
|
|
455
|
+
return new CLIError(message, ErrorCodes.INVALID_INPUT);
|
|
456
|
+
}
|
|
432
457
|
|
|
433
458
|
// src/lib/auth-flow.ts
|
|
434
459
|
import open from "open";
|
|
@@ -436,6 +461,12 @@ import chalk4 from "chalk";
|
|
|
436
461
|
import { hostname } from "os";
|
|
437
462
|
|
|
438
463
|
// src/lib/api.ts
|
|
464
|
+
function registrableDomain(hostname2) {
|
|
465
|
+
const parts = hostname2.toLowerCase().split(".").filter(Boolean);
|
|
466
|
+
if (parts.length <= 2) return parts.join(".");
|
|
467
|
+
return parts.slice(-2).join(".");
|
|
468
|
+
}
|
|
469
|
+
var MAX_MANUAL_REDIRECTS = 5;
|
|
439
470
|
var APIError = class extends Error {
|
|
440
471
|
constructor(message, statusCode, code) {
|
|
441
472
|
super(message);
|
|
@@ -471,7 +502,59 @@ var APIClient = class {
|
|
|
471
502
|
const finalUrl = response.url || "";
|
|
472
503
|
const contentType = response.headers.get("content-type") || "";
|
|
473
504
|
const preview = (bodyText || "").slice(0, 512).toLowerCase();
|
|
474
|
-
return
|
|
505
|
+
return location.includes("authkit") || finalUrl.includes("authkit") || contentType.includes("text/html") && (preview.includes("authorization_session_id") || preview.includes("client_id=") || preview.includes("<!doctype html"));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Perform a fetch that follows 3xx redirects manually, re-attaching the
|
|
509
|
+
* Authorization header when the redirect stays inside the same registrable
|
|
510
|
+
* domain (eTLD+1). This defends against the apex→www redirect case where
|
|
511
|
+
* Node's default redirect follower drops the Authorization header on any
|
|
512
|
+
* hostname change and the resulting request comes back as a bogus 401 —
|
|
513
|
+
* which used to wipe the user's credentials.
|
|
514
|
+
*
|
|
515
|
+
* Cross-site redirects (different registrable domain) are followed without
|
|
516
|
+
* the Authorization header, matching browser/fetch security semantics.
|
|
517
|
+
*/
|
|
518
|
+
async fetchWithSafeRedirects(initialUrl, init2) {
|
|
519
|
+
let currentUrl = initialUrl;
|
|
520
|
+
let currentInit = { ...init2, redirect: "manual" };
|
|
521
|
+
for (let hop = 0; hop < MAX_MANUAL_REDIRECTS; hop++) {
|
|
522
|
+
const response = await fetch(currentUrl, currentInit);
|
|
523
|
+
if (response.status < 300 || response.status >= 400) {
|
|
524
|
+
return response;
|
|
525
|
+
}
|
|
526
|
+
const location = response.headers.get("location");
|
|
527
|
+
if (!location) return response;
|
|
528
|
+
const nextUrl = new URL(location, currentUrl);
|
|
529
|
+
const prevHost = new URL(currentUrl).hostname;
|
|
530
|
+
const sameSite = registrableDomain(nextUrl.hostname) === registrableDomain(prevHost);
|
|
531
|
+
const headers = new Headers(currentInit.headers);
|
|
532
|
+
if (!sameSite) {
|
|
533
|
+
headers.delete("Authorization");
|
|
534
|
+
}
|
|
535
|
+
let nextMethod = (currentInit.method || "GET").toUpperCase();
|
|
536
|
+
let nextBody = currentInit.body;
|
|
537
|
+
if (response.status === 301 || response.status === 302 || response.status === 303) {
|
|
538
|
+
if (nextMethod !== "GET" && nextMethod !== "HEAD") {
|
|
539
|
+
nextMethod = "GET";
|
|
540
|
+
nextBody = void 0;
|
|
541
|
+
headers.delete("Content-Type");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
currentUrl = nextUrl.toString();
|
|
545
|
+
currentInit = {
|
|
546
|
+
...currentInit,
|
|
547
|
+
method: nextMethod,
|
|
548
|
+
headers,
|
|
549
|
+
body: nextBody,
|
|
550
|
+
redirect: "manual"
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
throw new APIError(
|
|
554
|
+
`Too many redirects while calling ${initialUrl}`,
|
|
555
|
+
0,
|
|
556
|
+
"TOO_MANY_REDIRECTS"
|
|
557
|
+
);
|
|
475
558
|
}
|
|
476
559
|
/**
|
|
477
560
|
* Make a GET request
|
|
@@ -483,10 +566,9 @@ var APIClient = class {
|
|
|
483
566
|
url.searchParams.set(key, value);
|
|
484
567
|
}
|
|
485
568
|
}
|
|
486
|
-
const response = await
|
|
569
|
+
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
487
570
|
method: "GET",
|
|
488
|
-
headers: this.getHeaders()
|
|
489
|
-
redirect: "follow"
|
|
571
|
+
headers: this.getHeaders()
|
|
490
572
|
});
|
|
491
573
|
return this.handleResponse(response);
|
|
492
574
|
}
|
|
@@ -495,11 +577,10 @@ var APIClient = class {
|
|
|
495
577
|
*/
|
|
496
578
|
async post(path, body) {
|
|
497
579
|
const url = new URL(path, this.baseUrl);
|
|
498
|
-
const response = await
|
|
580
|
+
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
499
581
|
method: "POST",
|
|
500
582
|
headers: this.getHeaders(),
|
|
501
|
-
body: body ? JSON.stringify(body) : void 0
|
|
502
|
-
redirect: "follow"
|
|
583
|
+
body: body ? JSON.stringify(body) : void 0
|
|
503
584
|
});
|
|
504
585
|
return this.handleResponse(response);
|
|
505
586
|
}
|
|
@@ -508,11 +589,10 @@ var APIClient = class {
|
|
|
508
589
|
*/
|
|
509
590
|
async put(path, body) {
|
|
510
591
|
const url = new URL(path, this.baseUrl);
|
|
511
|
-
const response = await
|
|
592
|
+
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
512
593
|
method: "PUT",
|
|
513
594
|
headers: this.getHeaders(),
|
|
514
|
-
body: body ? JSON.stringify(body) : void 0
|
|
515
|
-
redirect: "follow"
|
|
595
|
+
body: body ? JSON.stringify(body) : void 0
|
|
516
596
|
});
|
|
517
597
|
return this.handleResponse(response);
|
|
518
598
|
}
|
|
@@ -521,11 +601,10 @@ var APIClient = class {
|
|
|
521
601
|
*/
|
|
522
602
|
async patch(path, body) {
|
|
523
603
|
const url = new URL(path, this.baseUrl);
|
|
524
|
-
const response = await
|
|
604
|
+
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
525
605
|
method: "PATCH",
|
|
526
606
|
headers: this.getHeaders(),
|
|
527
|
-
body: body ? JSON.stringify(body) : void 0
|
|
528
|
-
redirect: "follow"
|
|
607
|
+
body: body ? JSON.stringify(body) : void 0
|
|
529
608
|
});
|
|
530
609
|
return this.handleResponse(response);
|
|
531
610
|
}
|
|
@@ -534,10 +613,9 @@ var APIClient = class {
|
|
|
534
613
|
*/
|
|
535
614
|
async delete(path) {
|
|
536
615
|
const url = new URL(path, this.baseUrl);
|
|
537
|
-
const response = await
|
|
616
|
+
const response = await this.fetchWithSafeRedirects(url.toString(), {
|
|
538
617
|
method: "DELETE",
|
|
539
|
-
headers: this.getHeaders()
|
|
540
|
-
redirect: "follow"
|
|
618
|
+
headers: this.getHeaders()
|
|
541
619
|
});
|
|
542
620
|
if (!response.ok) {
|
|
543
621
|
await this.handleError(response);
|
|
@@ -554,7 +632,6 @@ var APIClient = class {
|
|
|
554
632
|
if (!contentType.includes("application/json")) {
|
|
555
633
|
const body2 = await response.text();
|
|
556
634
|
if (this.isAuthRedirect(response, body2)) {
|
|
557
|
-
clearAuth();
|
|
558
635
|
throw new APIError(
|
|
559
636
|
"Your CLI session is not authorized for this endpoint. Please run `envpilot login` and try again.",
|
|
560
637
|
401,
|
|
@@ -591,20 +668,12 @@ var APIClient = class {
|
|
|
591
668
|
code = data.code;
|
|
592
669
|
} catch {
|
|
593
670
|
}
|
|
594
|
-
if (response.status >= 300 && response.status < 400 && this.isAuthRedirect(response)) {
|
|
595
|
-
clearAuth();
|
|
596
|
-
throw new APIError(
|
|
597
|
-
"Your CLI session expired or the server redirected this request to browser sign-in. Please run `envpilot login`.",
|
|
598
|
-
401,
|
|
599
|
-
"AUTH_REDIRECT"
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
671
|
if (response.status === 401) {
|
|
603
672
|
clearAuth();
|
|
604
673
|
throw new APIError(
|
|
605
|
-
"Authentication
|
|
674
|
+
"Authentication failed. Please run `envpilot login`.",
|
|
606
675
|
401,
|
|
607
|
-
"UNAUTHORIZED"
|
|
676
|
+
code || "UNAUTHORIZED"
|
|
608
677
|
);
|
|
609
678
|
}
|
|
610
679
|
if (response.status === 403 && code === "TIER_LIMIT_REACHED") {
|
|
@@ -633,7 +702,7 @@ var APIClient = class {
|
|
|
633
702
|
* Get current user info
|
|
634
703
|
*/
|
|
635
704
|
async getCurrentUser() {
|
|
636
|
-
return this.get("/api/cli/auth
|
|
705
|
+
return this.get("/api/cli/auth", { action: "me" });
|
|
637
706
|
}
|
|
638
707
|
/**
|
|
639
708
|
* Get tier info for the active organization
|
|
@@ -673,7 +742,12 @@ var APIClient = class {
|
|
|
673
742
|
return this.get(`/api/cli/projects/${projectId}`);
|
|
674
743
|
}
|
|
675
744
|
/**
|
|
676
|
-
* List variables in a project
|
|
745
|
+
* List variables in a project (with decrypted values).
|
|
746
|
+
*
|
|
747
|
+
* Returns both the variable list and any keys that failed vault decryption.
|
|
748
|
+
* Decryption failures are skipped server-side — they will NOT appear in
|
|
749
|
+
* `variables`. Callers should warn the user about `decryptionFailures`
|
|
750
|
+
* so they know those secrets weren't injected.
|
|
677
751
|
*/
|
|
678
752
|
async listVariables(projectId, environment, organizationId) {
|
|
679
753
|
const params = { projectId };
|
|
@@ -683,11 +757,29 @@ var APIClient = class {
|
|
|
683
757
|
if (organizationId) {
|
|
684
758
|
params.organizationId = organizationId;
|
|
685
759
|
}
|
|
760
|
+
const response = await this.get("/api/cli/variables", params);
|
|
761
|
+
return {
|
|
762
|
+
variables: response.data || [],
|
|
763
|
+
decryptionFailures: response.meta?.decryptionFailures ?? []
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Check the variable fingerprint for a project/environment.
|
|
768
|
+
*
|
|
769
|
+
* Returns a short hash of variable metadata (id + version + updatedAt)
|
|
770
|
+
* WITHOUT decrypting vault secrets. The CLI uses this to decide whether
|
|
771
|
+
* a cached variable set is still current before doing a full (expensive)
|
|
772
|
+
* fetch. If the fingerprint matches, the cache can be extended for free.
|
|
773
|
+
*/
|
|
774
|
+
async checkFingerprint(projectId, environment, organizationId) {
|
|
775
|
+
const params = { projectId };
|
|
776
|
+
if (environment) params.environment = environment;
|
|
777
|
+
if (organizationId) params.organizationId = organizationId;
|
|
686
778
|
const response = await this.get(
|
|
687
|
-
"/api/cli/variables",
|
|
779
|
+
"/api/cli/variables/fingerprint",
|
|
688
780
|
params
|
|
689
781
|
);
|
|
690
|
-
return response.
|
|
782
|
+
return response.fingerprint;
|
|
691
783
|
}
|
|
692
784
|
/**
|
|
693
785
|
* Get a variable by ID (with decrypted value)
|
|
@@ -726,25 +818,25 @@ var APIClient = class {
|
|
|
726
818
|
* Initiate CLI authentication flow
|
|
727
819
|
*/
|
|
728
820
|
async initiateAuth(deviceName) {
|
|
729
|
-
return this.post("/api/cli/auth
|
|
821
|
+
return this.post("/api/cli/auth?action=initiate", { deviceName });
|
|
730
822
|
}
|
|
731
823
|
/**
|
|
732
824
|
* Poll for authentication status
|
|
733
825
|
*/
|
|
734
826
|
async pollAuth(code) {
|
|
735
|
-
return this.get("/api/cli/auth
|
|
827
|
+
return this.get("/api/cli/auth", { action: "poll", code });
|
|
736
828
|
}
|
|
737
829
|
/**
|
|
738
830
|
* Refresh access token
|
|
739
831
|
*/
|
|
740
832
|
async refreshToken(refreshToken) {
|
|
741
|
-
return this.post("/api/cli/auth
|
|
833
|
+
return this.post("/api/cli/auth?action=refresh", { refreshToken });
|
|
742
834
|
}
|
|
743
835
|
/**
|
|
744
836
|
* Revoke access token (logout)
|
|
745
837
|
*/
|
|
746
838
|
async revokeToken() {
|
|
747
|
-
return this.post("/api/cli/auth
|
|
839
|
+
return this.post("/api/cli/auth?action=revoke", {});
|
|
748
840
|
}
|
|
749
841
|
};
|
|
750
842
|
function createAPIClient() {
|
|
@@ -766,7 +858,7 @@ async function performLogin(options) {
|
|
|
766
858
|
spinner.start();
|
|
767
859
|
let initResponse;
|
|
768
860
|
try {
|
|
769
|
-
initResponse = await api.
|
|
861
|
+
initResponse = await api.initiateAuth(deviceName);
|
|
770
862
|
} catch (error2) {
|
|
771
863
|
spinner.stop();
|
|
772
864
|
throw error2;
|
|
@@ -792,10 +884,7 @@ async function performLogin(options) {
|
|
|
792
884
|
await sleep(POLL_INTERVAL_MS);
|
|
793
885
|
let pollResponse;
|
|
794
886
|
try {
|
|
795
|
-
pollResponse = await api.
|
|
796
|
-
action: "poll",
|
|
797
|
-
code: initResponse.code
|
|
798
|
-
});
|
|
887
|
+
pollResponse = await api.pollAuth(initResponse.code);
|
|
799
888
|
consecutiveErrors = 0;
|
|
800
889
|
} catch {
|
|
801
890
|
consecutiveErrors++;
|
|
@@ -808,12 +897,13 @@ async function performLogin(options) {
|
|
|
808
897
|
}
|
|
809
898
|
if (pollResponse.status === "authenticated") {
|
|
810
899
|
pollSpinner.stop();
|
|
811
|
-
if (pollResponse.accessToken) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
setRefreshToken(pollResponse.refreshToken);
|
|
900
|
+
if (!pollResponse.accessToken || !pollResponse.refreshToken) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
"Authentication succeeded but the server did not return session tokens. This is likely a server-side issue. Please try again or contact support."
|
|
903
|
+
);
|
|
816
904
|
}
|
|
905
|
+
setAccessToken(pollResponse.accessToken);
|
|
906
|
+
setRefreshToken(pollResponse.refreshToken);
|
|
817
907
|
if (pollResponse.user) {
|
|
818
908
|
setUser({
|
|
819
909
|
id: pollResponse.user.id,
|
|
@@ -836,7 +926,7 @@ async function performLogin(options) {
|
|
|
836
926
|
}
|
|
837
927
|
|
|
838
928
|
// src/commands/login.ts
|
|
839
|
-
var loginCommand = new Command("login").description("Authenticate with Envpilot").option("--api-url <url>", "API URL (default:
|
|
929
|
+
var loginCommand = new Command("login").description("Authenticate with Envpilot").option("--api-url <url>", "API URL (default: https://www.envpilot.dev)").option("--no-browser", "Do not automatically open the browser").action(async (options) => {
|
|
840
930
|
try {
|
|
841
931
|
if (options.apiUrl) {
|
|
842
932
|
setApiUrl(options.apiUrl);
|
|
@@ -904,6 +994,8 @@ var variableSchema = z.object({
|
|
|
904
994
|
description: z.string().optional(),
|
|
905
995
|
isSensitive: z.boolean().optional(),
|
|
906
996
|
version: z.number().optional(),
|
|
997
|
+
updatedAt: z.number().optional(),
|
|
998
|
+
createdAt: z.number().optional(),
|
|
907
999
|
tags: z.array(variableTagSchema).optional()
|
|
908
1000
|
});
|
|
909
1001
|
var environmentSchema = z.enum([
|
|
@@ -2264,11 +2356,11 @@ var pushCommand = new Command4("push").description("Upload local .env file to cl
|
|
|
2264
2356
|
if (fmt === "env") {
|
|
2265
2357
|
localVars = readEnvFile(inputPath);
|
|
2266
2358
|
} else {
|
|
2267
|
-
const { readFileSync:
|
|
2268
|
-
if (!
|
|
2359
|
+
const { readFileSync: readFileSync5, existsSync: existsSync5 } = await import("fs");
|
|
2360
|
+
if (!existsSync5(inputPath)) {
|
|
2269
2361
|
localVars = null;
|
|
2270
2362
|
} else {
|
|
2271
|
-
const content =
|
|
2363
|
+
const content = readFileSync5(inputPath, "utf-8");
|
|
2272
2364
|
localVars = parse(content, fmt, { prefix: options.prefix });
|
|
2273
2365
|
}
|
|
2274
2366
|
}
|
|
@@ -3077,16 +3169,18 @@ async function handleSet(key, value) {
|
|
|
3077
3169
|
process.exit(1);
|
|
3078
3170
|
}
|
|
3079
3171
|
switch (key) {
|
|
3080
|
-
case "apiUrl":
|
|
3172
|
+
case "apiUrl": {
|
|
3173
|
+
const normalized = normalizeApiUrl(value);
|
|
3081
3174
|
try {
|
|
3082
|
-
new URL(
|
|
3175
|
+
new URL(normalized);
|
|
3083
3176
|
} catch {
|
|
3084
3177
|
error("Invalid URL format");
|
|
3085
3178
|
process.exit(1);
|
|
3086
3179
|
}
|
|
3087
3180
|
setApiUrl(value);
|
|
3088
|
-
success(`Set apiUrl to ${
|
|
3181
|
+
success(`Set apiUrl to ${normalized}`);
|
|
3089
3182
|
break;
|
|
3183
|
+
}
|
|
3090
3184
|
default:
|
|
3091
3185
|
error(`Cannot set key: ${key}`);
|
|
3092
3186
|
console.log();
|
|
@@ -3712,40 +3806,417 @@ var whoamiCommand = new Command12("whoami").description("Show the current authen
|
|
|
3712
3806
|
}
|
|
3713
3807
|
});
|
|
3714
3808
|
|
|
3715
|
-
// src/commands/
|
|
3809
|
+
// src/commands/run.ts
|
|
3716
3810
|
import { Command as Command13 } from "commander";
|
|
3811
|
+
import { spawn as spawn2 } from "child_process";
|
|
3717
3812
|
import chalk15 from "chalk";
|
|
3813
|
+
|
|
3814
|
+
// src/lib/variables-cache.ts
|
|
3815
|
+
import { createHash } from "crypto";
|
|
3816
|
+
import {
|
|
3817
|
+
readFileSync as readFileSync4,
|
|
3818
|
+
writeFileSync as writeFileSync4,
|
|
3819
|
+
mkdirSync as mkdirSync2,
|
|
3820
|
+
existsSync as existsSync4,
|
|
3821
|
+
readdirSync,
|
|
3822
|
+
unlinkSync as unlinkSync3,
|
|
3823
|
+
statSync as statSync2
|
|
3824
|
+
} from "fs";
|
|
3825
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
3826
|
+
function getCacheDir() {
|
|
3827
|
+
return join4(dirname2(getConfigPath()), "run-cache");
|
|
3828
|
+
}
|
|
3829
|
+
function getCacheKey(projectId, environment, organizationId) {
|
|
3830
|
+
const tokenSlice = (getAccessToken() ?? "").slice(0, 16);
|
|
3831
|
+
return createHash("sha256").update(`${projectId}:${environment}:${organizationId}:${tokenSlice}`).digest("hex").slice(0, 20);
|
|
3832
|
+
}
|
|
3833
|
+
function getCachePath(key) {
|
|
3834
|
+
return join4(getCacheDir(), `${key}.json`);
|
|
3835
|
+
}
|
|
3836
|
+
function computeFingerprint(variables) {
|
|
3837
|
+
return createHash("sha256").update(
|
|
3838
|
+
variables.map((v) => `${v._id}:${v.version ?? 0}:${v.updatedAt ?? 0}`).sort().join("|")
|
|
3839
|
+
).digest("hex").slice(0, 16);
|
|
3840
|
+
}
|
|
3841
|
+
function readCache(projectId, environment, organizationId) {
|
|
3842
|
+
try {
|
|
3843
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3844
|
+
const path = getCachePath(key);
|
|
3845
|
+
if (!existsSync4(path)) return null;
|
|
3846
|
+
const raw = readFileSync4(path, "utf-8");
|
|
3847
|
+
const entry = JSON.parse(raw);
|
|
3848
|
+
if (entry.apiUrl !== getApiUrl()) return null;
|
|
3849
|
+
return entry;
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
function probeCache(projectId, environment, organizationId, ttlSeconds) {
|
|
3855
|
+
const entry = readCache(projectId, environment, organizationId);
|
|
3856
|
+
if (!entry) return { hit: false };
|
|
3857
|
+
const fresh = isFresh(entry, ttlSeconds);
|
|
3858
|
+
return { hit: true, fresh, entry };
|
|
3859
|
+
}
|
|
3860
|
+
function writeCache(projectId, environment, organizationId, variables) {
|
|
3861
|
+
try {
|
|
3862
|
+
const cacheDir = getCacheDir();
|
|
3863
|
+
mkdirSync2(cacheDir, { recursive: true, mode: 448 });
|
|
3864
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3865
|
+
const path = getCachePath(key);
|
|
3866
|
+
const entry = {
|
|
3867
|
+
variables,
|
|
3868
|
+
fetchedAt: Date.now(),
|
|
3869
|
+
projectId,
|
|
3870
|
+
environment,
|
|
3871
|
+
organizationId,
|
|
3872
|
+
apiUrl: getApiUrl(),
|
|
3873
|
+
fingerprint: computeFingerprint(variables)
|
|
3874
|
+
};
|
|
3875
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2), {
|
|
3876
|
+
encoding: "utf-8",
|
|
3877
|
+
mode: 384
|
|
3878
|
+
// owner read/write only — same as .env
|
|
3879
|
+
});
|
|
3880
|
+
} catch {
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
function extendCacheFreshness(projectId, environment, organizationId) {
|
|
3884
|
+
try {
|
|
3885
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3886
|
+
const path = getCachePath(key);
|
|
3887
|
+
if (!existsSync4(path)) return;
|
|
3888
|
+
const raw = readFileSync4(path, "utf-8");
|
|
3889
|
+
const entry = JSON.parse(raw);
|
|
3890
|
+
entry.fetchedAt = Date.now();
|
|
3891
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2), {
|
|
3892
|
+
encoding: "utf-8",
|
|
3893
|
+
mode: 384
|
|
3894
|
+
});
|
|
3895
|
+
} catch {
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
function isFresh(entry, ttlSeconds) {
|
|
3899
|
+
return Date.now() - entry.fetchedAt < ttlSeconds * 1e3;
|
|
3900
|
+
}
|
|
3901
|
+
function formatAge(fetchedAt) {
|
|
3902
|
+
const ageSec = Math.floor((Date.now() - fetchedAt) / 1e3);
|
|
3903
|
+
if (ageSec < 60) return `${ageSec}s ago`;
|
|
3904
|
+
const ageMin = Math.floor(ageSec / 60);
|
|
3905
|
+
if (ageMin < 60) return `${ageMin}m ago`;
|
|
3906
|
+
return `${Math.floor(ageMin / 60)}h ago`;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
// src/commands/run.ts
|
|
3910
|
+
var DEFAULT_TTL = 3600;
|
|
3911
|
+
var runCommand = new Command13("run").description(
|
|
3912
|
+
"Run a command with project secrets injected as environment variables (no .env file written)"
|
|
3913
|
+
).argument("[command...]", "Command and arguments to execute").option(
|
|
3914
|
+
"-e, --env <environment>",
|
|
3915
|
+
"Environment to load (development, staging, production)"
|
|
3916
|
+
).option(
|
|
3917
|
+
"-p, --project <name-or-id>",
|
|
3918
|
+
"Linked project to load secrets from (defaults to active)"
|
|
3919
|
+
).option(
|
|
3920
|
+
"-o, --organization <id>",
|
|
3921
|
+
"Organization id (overrides linked project's org)"
|
|
3922
|
+
).option(
|
|
3923
|
+
"--keep-existing",
|
|
3924
|
+
"Don't overwrite env vars that are already set in the parent shell"
|
|
3925
|
+
).option(
|
|
3926
|
+
"--print",
|
|
3927
|
+
"Print the variables that would be injected and exit (no command runs)"
|
|
3928
|
+
).option(
|
|
3929
|
+
"--shell",
|
|
3930
|
+
"Run the command through the user's shell (enables pipes, &&, $VAR expansion)"
|
|
3931
|
+
).option("-q, --quiet", "Suppress informational messages").option("--no-cache", "Skip cache and always fetch fresh secrets from server").option(
|
|
3932
|
+
"--cache-ttl <seconds>",
|
|
3933
|
+
"How long cached secrets stay fresh before a fingerprint check (default: 3600s / 1h)",
|
|
3934
|
+
String(DEFAULT_TTL)
|
|
3935
|
+
).passThroughOptions().allowExcessArguments().action(async (commandArgs, options) => {
|
|
3936
|
+
try {
|
|
3937
|
+
if (!options.print && (!commandArgs || commandArgs.length === 0)) {
|
|
3938
|
+
throw invalidInput(
|
|
3939
|
+
"No command provided. Usage: envpilot run -- <command> [args...]"
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
if (!isAuthenticated()) {
|
|
3943
|
+
throw notAuthenticated();
|
|
3944
|
+
}
|
|
3945
|
+
const config2 = readProjectConfigV2();
|
|
3946
|
+
if (!config2) throw notInitialized();
|
|
3947
|
+
const project = options.project ? resolveProject(config2, options.project) : getActiveProject(config2);
|
|
3948
|
+
if (!project) {
|
|
3949
|
+
if (options.project) {
|
|
3950
|
+
error(`Project not found in this directory: ${options.project}`);
|
|
3951
|
+
console.log();
|
|
3952
|
+
console.log("Linked projects:");
|
|
3953
|
+
for (const p of config2.projects) {
|
|
3954
|
+
console.log(` ${p.projectName || p.projectId} (${p.environment})`);
|
|
3955
|
+
}
|
|
3956
|
+
process.exit(1);
|
|
3957
|
+
}
|
|
3958
|
+
throw notInitialized();
|
|
3959
|
+
}
|
|
3960
|
+
const environment = options.env || project.environment;
|
|
3961
|
+
const organizationId = options.organization || project.organizationId;
|
|
3962
|
+
const ttl = Math.max(
|
|
3963
|
+
1,
|
|
3964
|
+
parseInt(options.cacheTtl ?? String(DEFAULT_TTL), 10) || DEFAULT_TTL
|
|
3965
|
+
);
|
|
3966
|
+
let variables;
|
|
3967
|
+
let cacheHit = false;
|
|
3968
|
+
let cacheAge = "";
|
|
3969
|
+
if (options.cache === false) {
|
|
3970
|
+
variables = await doFetch(
|
|
3971
|
+
project,
|
|
3972
|
+
environment,
|
|
3973
|
+
organizationId,
|
|
3974
|
+
options.quiet
|
|
3975
|
+
);
|
|
3976
|
+
writeCache(project.projectId, environment, organizationId, variables);
|
|
3977
|
+
} else {
|
|
3978
|
+
const probe = probeCache(
|
|
3979
|
+
project.projectId,
|
|
3980
|
+
environment,
|
|
3981
|
+
organizationId,
|
|
3982
|
+
ttl
|
|
3983
|
+
);
|
|
3984
|
+
if (probe.hit && probe.fresh) {
|
|
3985
|
+
variables = probe.entry.variables;
|
|
3986
|
+
cacheHit = true;
|
|
3987
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
3988
|
+
} else if (probe.hit && !probe.fresh) {
|
|
3989
|
+
try {
|
|
3990
|
+
const api = createAPIClient();
|
|
3991
|
+
const serverFingerprint = await api.checkFingerprint(
|
|
3992
|
+
project.projectId,
|
|
3993
|
+
environment,
|
|
3994
|
+
organizationId
|
|
3995
|
+
);
|
|
3996
|
+
if (serverFingerprint === probe.entry.fingerprint) {
|
|
3997
|
+
extendCacheFreshness(
|
|
3998
|
+
project.projectId,
|
|
3999
|
+
environment,
|
|
4000
|
+
organizationId
|
|
4001
|
+
);
|
|
4002
|
+
variables = probe.entry.variables;
|
|
4003
|
+
cacheHit = true;
|
|
4004
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
4005
|
+
} else {
|
|
4006
|
+
variables = await doFetch(
|
|
4007
|
+
project,
|
|
4008
|
+
environment,
|
|
4009
|
+
organizationId,
|
|
4010
|
+
options.quiet,
|
|
4011
|
+
"Secrets updated, refreshing"
|
|
4012
|
+
);
|
|
4013
|
+
writeCache(
|
|
4014
|
+
project.projectId,
|
|
4015
|
+
environment,
|
|
4016
|
+
organizationId,
|
|
4017
|
+
variables
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
} catch {
|
|
4021
|
+
variables = probe.entry.variables;
|
|
4022
|
+
cacheHit = true;
|
|
4023
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
4024
|
+
}
|
|
4025
|
+
} else {
|
|
4026
|
+
variables = await doFetch(
|
|
4027
|
+
project,
|
|
4028
|
+
environment,
|
|
4029
|
+
organizationId,
|
|
4030
|
+
options.quiet
|
|
4031
|
+
);
|
|
4032
|
+
writeCache(project.projectId, environment, organizationId, variables);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
if (variables.length === 0) {
|
|
4036
|
+
warning(
|
|
4037
|
+
`No variables found for ${environment}. Running command without injected secrets.`
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
const secrets = {};
|
|
4041
|
+
for (const v of variables) {
|
|
4042
|
+
secrets[v.key] = v.value;
|
|
4043
|
+
}
|
|
4044
|
+
if (options.print) {
|
|
4045
|
+
printInjectionPreview(secrets, project, environment);
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
const baseEnv = { ...process.env };
|
|
4049
|
+
const finalEnv = options.keepExisting ? { ...secrets, ...baseEnv } : { ...baseEnv, ...secrets };
|
|
4050
|
+
const overridden = [];
|
|
4051
|
+
if (!options.keepExisting) {
|
|
4052
|
+
for (const key of Object.keys(secrets)) {
|
|
4053
|
+
if (process.env[key] !== void 0 && process.env[key] !== secrets[key]) {
|
|
4054
|
+
overridden.push(key);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
if (!options.quiet) {
|
|
4059
|
+
const injectedCount = Object.keys(secrets).length;
|
|
4060
|
+
const cacheTag = cacheHit ? chalk15.dim(` \u26A1 cache (${cacheAge})`) : "";
|
|
4061
|
+
info(
|
|
4062
|
+
`Injected ${chalk15.bold(injectedCount)} ${injectedCount === 1 ? "variable" : "variables"} from ${chalk15.bold(`${project.projectName || project.projectId}/${environment}`)}${cacheTag}`
|
|
4063
|
+
);
|
|
4064
|
+
if (overridden.length > 0) {
|
|
4065
|
+
warning(
|
|
4066
|
+
`Overriding ${overridden.length} shell var${overridden.length === 1 ? "" : "s"}: ${overridden.slice(0, 3).join(", ")}${overridden.length > 3 ? `, +${overridden.length - 3} more` : ""}`
|
|
4067
|
+
);
|
|
4068
|
+
info("Use --keep-existing to keep your shell values instead.");
|
|
4069
|
+
}
|
|
4070
|
+
console.log();
|
|
4071
|
+
}
|
|
4072
|
+
await runChild(commandArgs, finalEnv, options);
|
|
4073
|
+
} catch (err) {
|
|
4074
|
+
await handleError(err);
|
|
4075
|
+
}
|
|
4076
|
+
});
|
|
4077
|
+
async function doFetch(project, environment, organizationId, quiet, labelPrefix = "Loading") {
|
|
4078
|
+
const label = `${labelPrefix} ${chalk15.bold(environment)} secrets for ${chalk15.bold(project.projectName || project.projectId)}...`;
|
|
4079
|
+
const api = createAPIClient();
|
|
4080
|
+
const { variables, decryptionFailures } = quiet ? await api.listVariables(project.projectId, environment, organizationId) : await withSpinner(
|
|
4081
|
+
label,
|
|
4082
|
+
() => api.listVariables(project.projectId, environment, organizationId)
|
|
4083
|
+
);
|
|
4084
|
+
if (decryptionFailures.length > 0) {
|
|
4085
|
+
for (const key of decryptionFailures) {
|
|
4086
|
+
warning(
|
|
4087
|
+
`Could not decrypt ${chalk15.bold(key)} \u2014 skipped (vault error, check server logs)`
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
return variables;
|
|
4092
|
+
}
|
|
4093
|
+
function printInjectionPreview(secrets, project, environment) {
|
|
4094
|
+
const keys = Object.keys(secrets).sort();
|
|
4095
|
+
console.log();
|
|
4096
|
+
console.log(
|
|
4097
|
+
chalk15.bold(
|
|
4098
|
+
`Would inject ${keys.length} ${keys.length === 1 ? "variable" : "variables"} from ${chalk15.cyan(`${project.projectName || project.projectId}/${environment}`)}:`
|
|
4099
|
+
)
|
|
4100
|
+
);
|
|
4101
|
+
console.log();
|
|
4102
|
+
if (keys.length === 0) {
|
|
4103
|
+
console.log(chalk15.dim(" (no variables)"));
|
|
4104
|
+
} else {
|
|
4105
|
+
for (const key of keys) {
|
|
4106
|
+
const value = secrets[key];
|
|
4107
|
+
const masked = maskForPreview(value);
|
|
4108
|
+
console.log(` ${chalk15.cyan(key)}=${chalk15.dim(masked)}`);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
console.log();
|
|
4112
|
+
success("Dry run \u2014 no command executed.");
|
|
4113
|
+
}
|
|
4114
|
+
function maskForPreview(value) {
|
|
4115
|
+
if (value.length <= 6) return "******";
|
|
4116
|
+
return `${value.slice(0, 4)}\u2026${value.slice(-2)} (${value.length} chars)`;
|
|
4117
|
+
}
|
|
4118
|
+
function runChild(commandArgs, env, options) {
|
|
4119
|
+
return new Promise((resolve3) => {
|
|
4120
|
+
const [command, ...args] = commandArgs;
|
|
4121
|
+
if (!command) {
|
|
4122
|
+
process.exit(1);
|
|
4123
|
+
}
|
|
4124
|
+
const isWindows = process.platform === "win32";
|
|
4125
|
+
const useShell = options.shell === true || isWindows;
|
|
4126
|
+
const child = spawn2(command, args, {
|
|
4127
|
+
stdio: "inherit",
|
|
4128
|
+
env,
|
|
4129
|
+
shell: useShell
|
|
4130
|
+
});
|
|
4131
|
+
const signals = [
|
|
4132
|
+
"SIGINT",
|
|
4133
|
+
"SIGTERM",
|
|
4134
|
+
"SIGHUP",
|
|
4135
|
+
"SIGQUIT"
|
|
4136
|
+
];
|
|
4137
|
+
const forward = {};
|
|
4138
|
+
for (const sig of signals) {
|
|
4139
|
+
const handler = () => {
|
|
4140
|
+
if (!child.killed) {
|
|
4141
|
+
try {
|
|
4142
|
+
child.kill(sig);
|
|
4143
|
+
} catch {
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
4147
|
+
forward[sig] = handler;
|
|
4148
|
+
process.on(sig, handler);
|
|
4149
|
+
}
|
|
4150
|
+
const cleanup = () => {
|
|
4151
|
+
for (const sig of signals) {
|
|
4152
|
+
const handler = forward[sig];
|
|
4153
|
+
if (handler) process.off(sig, handler);
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4156
|
+
child.on("error", (err) => {
|
|
4157
|
+
cleanup();
|
|
4158
|
+
const code = err.code;
|
|
4159
|
+
if (code === "ENOENT") {
|
|
4160
|
+
error(
|
|
4161
|
+
`Command not found: ${command}. Make sure it is installed and on your PATH.`
|
|
4162
|
+
);
|
|
4163
|
+
} else if (code === "EACCES") {
|
|
4164
|
+
error(`Permission denied executing: ${command}`);
|
|
4165
|
+
} else {
|
|
4166
|
+
error(`Failed to spawn process: ${err.message}`);
|
|
4167
|
+
}
|
|
4168
|
+
process.exit(1);
|
|
4169
|
+
});
|
|
4170
|
+
child.on("exit", (code, signal) => {
|
|
4171
|
+
cleanup();
|
|
4172
|
+
if (signal) {
|
|
4173
|
+
try {
|
|
4174
|
+
process.kill(process.pid, signal);
|
|
4175
|
+
} catch {
|
|
4176
|
+
process.exit(1);
|
|
4177
|
+
}
|
|
4178
|
+
} else {
|
|
4179
|
+
process.exit(code ?? 0);
|
|
4180
|
+
}
|
|
4181
|
+
resolve3();
|
|
4182
|
+
});
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
// src/commands/man.ts
|
|
4187
|
+
import { Command as Command14 } from "commander";
|
|
4188
|
+
import chalk16 from "chalk";
|
|
3718
4189
|
function printCommandManual(commandName) {
|
|
3719
4190
|
const command = findCommandDefinition(commandName);
|
|
3720
4191
|
if (!command) {
|
|
3721
|
-
console.log(
|
|
4192
|
+
console.log(chalk16.red(`Unknown command reference: ${commandName}`));
|
|
3722
4193
|
console.log();
|
|
3723
4194
|
console.log("Run `envpilot man` to see all supported commands.");
|
|
3724
4195
|
process.exit(1);
|
|
3725
4196
|
}
|
|
3726
|
-
console.log(
|
|
4197
|
+
console.log(chalk16.bold(formatArgv(command.argv)));
|
|
3727
4198
|
if (command.args) {
|
|
3728
|
-
console.log(
|
|
4199
|
+
console.log(chalk16.dim(command.args));
|
|
3729
4200
|
}
|
|
3730
4201
|
blank();
|
|
3731
4202
|
console.log(command.description);
|
|
3732
4203
|
blank();
|
|
3733
|
-
console.log(
|
|
4204
|
+
console.log(chalk16.green("Examples"));
|
|
3734
4205
|
for (const example of command.examples) {
|
|
3735
|
-
console.log(` ${
|
|
4206
|
+
console.log(` ${chalk16.cyan(formatArgv(example))}`);
|
|
3736
4207
|
}
|
|
3737
4208
|
blank();
|
|
3738
|
-
console.log(
|
|
4209
|
+
console.log(chalk16.green("Notes"));
|
|
3739
4210
|
for (const note of command.notes) {
|
|
3740
4211
|
console.log(` - ${note}`);
|
|
3741
4212
|
}
|
|
3742
4213
|
}
|
|
3743
4214
|
function printManualIndex(commands) {
|
|
3744
|
-
console.log(
|
|
4215
|
+
console.log(chalk16.bold("ENVPILOT(1)"));
|
|
3745
4216
|
console.log("TypeScript CLI manual");
|
|
3746
4217
|
blank();
|
|
3747
4218
|
console.log(
|
|
3748
|
-
`Total supported top-level commands: ${
|
|
4219
|
+
`Total supported top-level commands: ${chalk16.green(String(CLI_COMMAND_COUNT))}`
|
|
3749
4220
|
);
|
|
3750
4221
|
blank();
|
|
3751
4222
|
console.log(
|
|
@@ -3753,15 +4224,15 @@ function printManualIndex(commands) {
|
|
|
3753
4224
|
);
|
|
3754
4225
|
blank();
|
|
3755
4226
|
line();
|
|
3756
|
-
console.log(
|
|
4227
|
+
console.log(chalk16.green("Commands"));
|
|
3757
4228
|
for (const command of commands) {
|
|
3758
4229
|
console.log(
|
|
3759
|
-
` ${
|
|
4230
|
+
` ${chalk16.cyan(formatArgv(command.argv).padEnd(24))} ${chalk16.dim(command.description)}`
|
|
3760
4231
|
);
|
|
3761
4232
|
}
|
|
3762
4233
|
blank();
|
|
3763
4234
|
line();
|
|
3764
|
-
console.log(
|
|
4235
|
+
console.log(chalk16.green("Security"));
|
|
3765
4236
|
console.log(" - `.env*` is ignored by the repository `.gitignore`.");
|
|
3766
4237
|
console.log(
|
|
3767
4238
|
" - `envpilot init` and `envpilot sync` ensure env files are added to `.gitignore` locally."
|
|
@@ -3774,7 +4245,7 @@ function printManualIndex(commands) {
|
|
|
3774
4245
|
);
|
|
3775
4246
|
blank();
|
|
3776
4247
|
line();
|
|
3777
|
-
console.log(
|
|
4248
|
+
console.log(chalk16.green("Usage"));
|
|
3778
4249
|
console.log(
|
|
3779
4250
|
" - `envpilot` or `envpilot ui` opens the interactive terminal UI."
|
|
3780
4251
|
);
|
|
@@ -3783,7 +4254,7 @@ function printManualIndex(commands) {
|
|
|
3783
4254
|
);
|
|
3784
4255
|
}
|
|
3785
4256
|
function createManCommand(commands) {
|
|
3786
|
-
return new
|
|
4257
|
+
return new Command14("man").description("Show the CLI manual page").argument("[command]", "Optional command to show detailed manual for").action((command) => {
|
|
3787
4258
|
if (command) {
|
|
3788
4259
|
printCommandManual(command);
|
|
3789
4260
|
return;
|
|
@@ -3793,9 +4264,9 @@ function createManCommand(commands) {
|
|
|
3793
4264
|
}
|
|
3794
4265
|
|
|
3795
4266
|
// src/commands/ui.ts
|
|
3796
|
-
import { Command as
|
|
4267
|
+
import { Command as Command15 } from "commander";
|
|
3797
4268
|
function createUICommand() {
|
|
3798
|
-
return new
|
|
4269
|
+
return new Command15("ui").alias("dashboard").description("Open the interactive Ink-powered terminal UI").action(async () => {
|
|
3799
4270
|
if (process.env.ENVPILOT_TUI_CHILD === "1") {
|
|
3800
4271
|
return;
|
|
3801
4272
|
}
|
|
@@ -3945,6 +4416,42 @@ var COMMAND_CATALOG = [
|
|
|
3945
4416
|
topLevel: true,
|
|
3946
4417
|
createCommand: () => pushCommand
|
|
3947
4418
|
},
|
|
4419
|
+
{
|
|
4420
|
+
id: "run",
|
|
4421
|
+
title: "Run command with secrets",
|
|
4422
|
+
category: "Sync",
|
|
4423
|
+
description: "Inject project secrets into a child process without writing a .env file (envpilot run -- bun dev).",
|
|
4424
|
+
argv: ["run"],
|
|
4425
|
+
args: "[--env <environment>] [--project <name-or-id>] [--keep-existing] [--print] -- <command> [args...]",
|
|
4426
|
+
examples: [
|
|
4427
|
+
["run", "--", "bun", "dev"],
|
|
4428
|
+
["run", "--env", "production", "--", "node", "server.js"],
|
|
4429
|
+
["run", "--project", "api", "--", "pnpm", "test"],
|
|
4430
|
+
["run", "--print"],
|
|
4431
|
+
["run", "--keep-existing", "--", "bun", "dev"]
|
|
4432
|
+
],
|
|
4433
|
+
websiteSurface: "Maps to `/api/cli/variables` for read access.",
|
|
4434
|
+
notes: [
|
|
4435
|
+
"Use `--` to separate envpilot flags from the command to execute.",
|
|
4436
|
+
"Secrets override existing shell vars by default; pass --keep-existing to flip.",
|
|
4437
|
+
"On Windows, the command is run through the shell so .cmd / .bat files resolve.",
|
|
4438
|
+
"Signals (SIGINT, SIGTERM, SIGHUP, SIGQUIT) are forwarded to the child process.",
|
|
4439
|
+
"Use --print to inspect what would be injected without executing anything."
|
|
4440
|
+
],
|
|
4441
|
+
keywords: [
|
|
4442
|
+
"run",
|
|
4443
|
+
"exec",
|
|
4444
|
+
"spawn",
|
|
4445
|
+
"inject",
|
|
4446
|
+
"secrets",
|
|
4447
|
+
"env",
|
|
4448
|
+
"ephemeral",
|
|
4449
|
+
"no-file",
|
|
4450
|
+
"doppler"
|
|
4451
|
+
],
|
|
4452
|
+
topLevel: true,
|
|
4453
|
+
createCommand: () => runCommand
|
|
4454
|
+
},
|
|
3948
4455
|
{
|
|
3949
4456
|
id: "list",
|
|
3950
4457
|
title: "List resources",
|
package/dist/index.js
CHANGED
|
@@ -4,18 +4,18 @@ import {
|
|
|
4
4
|
getTopLevelCommandCatalog,
|
|
5
5
|
initSentry,
|
|
6
6
|
openTUI
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TPYA3DO2.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/program.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/lib/cli-version.ts
|
|
13
|
-
var CLI_VERSION = true ? "1.
|
|
13
|
+
var CLI_VERSION = true ? "1.6.1" : "0.0.0";
|
|
14
14
|
|
|
15
15
|
// src/lib/version-check.ts
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import Conf from "conf";
|
|
18
|
-
var CLI_VERSION2 = true ? "1.
|
|
18
|
+
var CLI_VERSION2 = true ? "1.6.1" : "0.0.0";
|
|
19
19
|
var CHECK_INTERVAL = 60 * 60 * 1e3;
|
|
20
20
|
var _cache = null;
|
|
21
21
|
function getCache() {
|
|
@@ -60,7 +60,7 @@ function checkForUpdate() {
|
|
|
60
60
|
// src/lib/program.ts
|
|
61
61
|
function createProgram() {
|
|
62
62
|
const program = new Command();
|
|
63
|
-
program.name("envpilot").description("Envpilot CLI - Sync, secure, and share environment variables").version(CLI_VERSION);
|
|
63
|
+
program.name("envpilot").description("Envpilot CLI - Sync, secure, and share environment variables").version(CLI_VERSION).enablePositionalOptions();
|
|
64
64
|
for (const command of getTopLevelCommandCatalog()) {
|
|
65
65
|
if (command.createCommand) {
|
|
66
66
|
program.addCommand(command.createCommand());
|