@envpilot/cli 1.5.0 → 1.6.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.
|
@@ -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.0" : "0.0.0",
|
|
11
11
|
// Free tier: disable performance monitoring
|
|
12
12
|
tracesSampleRate: 0,
|
|
13
13
|
beforeSend(event) {
|
|
@@ -157,7 +157,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
157
157
|
async function openTUI() {
|
|
158
158
|
const [{ render }, { CLIApp }] = await Promise.all([
|
|
159
159
|
import("ink"),
|
|
160
|
-
import("./app-
|
|
160
|
+
import("./app-UJRUWQKL.js")
|
|
161
161
|
]);
|
|
162
162
|
while (true) {
|
|
163
163
|
let selectedArgv = null;
|
|
@@ -429,6 +429,9 @@ function notInitialized() {
|
|
|
429
429
|
function fileNotFound(path) {
|
|
430
430
|
return new CLIError(`File not found: ${path}`, ErrorCodes.FILE_NOT_FOUND);
|
|
431
431
|
}
|
|
432
|
+
function invalidInput(message) {
|
|
433
|
+
return new CLIError(message, ErrorCodes.INVALID_INPUT);
|
|
434
|
+
}
|
|
432
435
|
|
|
433
436
|
// src/lib/auth-flow.ts
|
|
434
437
|
import open from "open";
|
|
@@ -673,7 +676,12 @@ var APIClient = class {
|
|
|
673
676
|
return this.get(`/api/cli/projects/${projectId}`);
|
|
674
677
|
}
|
|
675
678
|
/**
|
|
676
|
-
* List variables in a project
|
|
679
|
+
* List variables in a project (with decrypted values).
|
|
680
|
+
*
|
|
681
|
+
* Returns both the variable list and any keys that failed vault decryption.
|
|
682
|
+
* Decryption failures are skipped server-side — they will NOT appear in
|
|
683
|
+
* `variables`. Callers should warn the user about `decryptionFailures`
|
|
684
|
+
* so they know those secrets weren't injected.
|
|
677
685
|
*/
|
|
678
686
|
async listVariables(projectId, environment, organizationId) {
|
|
679
687
|
const params = { projectId };
|
|
@@ -683,11 +691,29 @@ var APIClient = class {
|
|
|
683
691
|
if (organizationId) {
|
|
684
692
|
params.organizationId = organizationId;
|
|
685
693
|
}
|
|
694
|
+
const response = await this.get("/api/cli/variables", params);
|
|
695
|
+
return {
|
|
696
|
+
variables: response.data || [],
|
|
697
|
+
decryptionFailures: response.meta?.decryptionFailures ?? []
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Check the variable fingerprint for a project/environment.
|
|
702
|
+
*
|
|
703
|
+
* Returns a short hash of variable metadata (id + version + updatedAt)
|
|
704
|
+
* WITHOUT decrypting vault secrets. The CLI uses this to decide whether
|
|
705
|
+
* a cached variable set is still current before doing a full (expensive)
|
|
706
|
+
* fetch. If the fingerprint matches, the cache can be extended for free.
|
|
707
|
+
*/
|
|
708
|
+
async checkFingerprint(projectId, environment, organizationId) {
|
|
709
|
+
const params = { projectId };
|
|
710
|
+
if (environment) params.environment = environment;
|
|
711
|
+
if (organizationId) params.organizationId = organizationId;
|
|
686
712
|
const response = await this.get(
|
|
687
|
-
"/api/cli/variables",
|
|
713
|
+
"/api/cli/variables/fingerprint",
|
|
688
714
|
params
|
|
689
715
|
);
|
|
690
|
-
return response.
|
|
716
|
+
return response.fingerprint;
|
|
691
717
|
}
|
|
692
718
|
/**
|
|
693
719
|
* Get a variable by ID (with decrypted value)
|
|
@@ -904,6 +930,8 @@ var variableSchema = z.object({
|
|
|
904
930
|
description: z.string().optional(),
|
|
905
931
|
isSensitive: z.boolean().optional(),
|
|
906
932
|
version: z.number().optional(),
|
|
933
|
+
updatedAt: z.number().optional(),
|
|
934
|
+
createdAt: z.number().optional(),
|
|
907
935
|
tags: z.array(variableTagSchema).optional()
|
|
908
936
|
});
|
|
909
937
|
var environmentSchema = z.enum([
|
|
@@ -2264,11 +2292,11 @@ var pushCommand = new Command4("push").description("Upload local .env file to cl
|
|
|
2264
2292
|
if (fmt === "env") {
|
|
2265
2293
|
localVars = readEnvFile(inputPath);
|
|
2266
2294
|
} else {
|
|
2267
|
-
const { readFileSync:
|
|
2268
|
-
if (!
|
|
2295
|
+
const { readFileSync: readFileSync5, existsSync: existsSync5 } = await import("fs");
|
|
2296
|
+
if (!existsSync5(inputPath)) {
|
|
2269
2297
|
localVars = null;
|
|
2270
2298
|
} else {
|
|
2271
|
-
const content =
|
|
2299
|
+
const content = readFileSync5(inputPath, "utf-8");
|
|
2272
2300
|
localVars = parse(content, fmt, { prefix: options.prefix });
|
|
2273
2301
|
}
|
|
2274
2302
|
}
|
|
@@ -3712,40 +3740,417 @@ var whoamiCommand = new Command12("whoami").description("Show the current authen
|
|
|
3712
3740
|
}
|
|
3713
3741
|
});
|
|
3714
3742
|
|
|
3715
|
-
// src/commands/
|
|
3743
|
+
// src/commands/run.ts
|
|
3716
3744
|
import { Command as Command13 } from "commander";
|
|
3745
|
+
import { spawn as spawn2 } from "child_process";
|
|
3717
3746
|
import chalk15 from "chalk";
|
|
3747
|
+
|
|
3748
|
+
// src/lib/variables-cache.ts
|
|
3749
|
+
import { createHash } from "crypto";
|
|
3750
|
+
import {
|
|
3751
|
+
readFileSync as readFileSync4,
|
|
3752
|
+
writeFileSync as writeFileSync4,
|
|
3753
|
+
mkdirSync as mkdirSync2,
|
|
3754
|
+
existsSync as existsSync4,
|
|
3755
|
+
readdirSync,
|
|
3756
|
+
unlinkSync as unlinkSync3,
|
|
3757
|
+
statSync as statSync2
|
|
3758
|
+
} from "fs";
|
|
3759
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
3760
|
+
function getCacheDir() {
|
|
3761
|
+
return join4(dirname2(getConfigPath()), "run-cache");
|
|
3762
|
+
}
|
|
3763
|
+
function getCacheKey(projectId, environment, organizationId) {
|
|
3764
|
+
const tokenSlice = (getAccessToken() ?? "").slice(0, 16);
|
|
3765
|
+
return createHash("sha256").update(`${projectId}:${environment}:${organizationId}:${tokenSlice}`).digest("hex").slice(0, 20);
|
|
3766
|
+
}
|
|
3767
|
+
function getCachePath(key) {
|
|
3768
|
+
return join4(getCacheDir(), `${key}.json`);
|
|
3769
|
+
}
|
|
3770
|
+
function computeFingerprint(variables) {
|
|
3771
|
+
return createHash("sha256").update(
|
|
3772
|
+
variables.map((v) => `${v._id}:${v.version ?? 0}:${v.updatedAt ?? 0}`).sort().join("|")
|
|
3773
|
+
).digest("hex").slice(0, 16);
|
|
3774
|
+
}
|
|
3775
|
+
function readCache(projectId, environment, organizationId) {
|
|
3776
|
+
try {
|
|
3777
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3778
|
+
const path = getCachePath(key);
|
|
3779
|
+
if (!existsSync4(path)) return null;
|
|
3780
|
+
const raw = readFileSync4(path, "utf-8");
|
|
3781
|
+
const entry = JSON.parse(raw);
|
|
3782
|
+
if (entry.apiUrl !== getApiUrl()) return null;
|
|
3783
|
+
return entry;
|
|
3784
|
+
} catch {
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
function probeCache(projectId, environment, organizationId, ttlSeconds) {
|
|
3789
|
+
const entry = readCache(projectId, environment, organizationId);
|
|
3790
|
+
if (!entry) return { hit: false };
|
|
3791
|
+
const fresh = isFresh(entry, ttlSeconds);
|
|
3792
|
+
return { hit: true, fresh, entry };
|
|
3793
|
+
}
|
|
3794
|
+
function writeCache(projectId, environment, organizationId, variables) {
|
|
3795
|
+
try {
|
|
3796
|
+
const cacheDir = getCacheDir();
|
|
3797
|
+
mkdirSync2(cacheDir, { recursive: true, mode: 448 });
|
|
3798
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3799
|
+
const path = getCachePath(key);
|
|
3800
|
+
const entry = {
|
|
3801
|
+
variables,
|
|
3802
|
+
fetchedAt: Date.now(),
|
|
3803
|
+
projectId,
|
|
3804
|
+
environment,
|
|
3805
|
+
organizationId,
|
|
3806
|
+
apiUrl: getApiUrl(),
|
|
3807
|
+
fingerprint: computeFingerprint(variables)
|
|
3808
|
+
};
|
|
3809
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2), {
|
|
3810
|
+
encoding: "utf-8",
|
|
3811
|
+
mode: 384
|
|
3812
|
+
// owner read/write only — same as .env
|
|
3813
|
+
});
|
|
3814
|
+
} catch {
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
function extendCacheFreshness(projectId, environment, organizationId) {
|
|
3818
|
+
try {
|
|
3819
|
+
const key = getCacheKey(projectId, environment, organizationId);
|
|
3820
|
+
const path = getCachePath(key);
|
|
3821
|
+
if (!existsSync4(path)) return;
|
|
3822
|
+
const raw = readFileSync4(path, "utf-8");
|
|
3823
|
+
const entry = JSON.parse(raw);
|
|
3824
|
+
entry.fetchedAt = Date.now();
|
|
3825
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2), {
|
|
3826
|
+
encoding: "utf-8",
|
|
3827
|
+
mode: 384
|
|
3828
|
+
});
|
|
3829
|
+
} catch {
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
function isFresh(entry, ttlSeconds) {
|
|
3833
|
+
return Date.now() - entry.fetchedAt < ttlSeconds * 1e3;
|
|
3834
|
+
}
|
|
3835
|
+
function formatAge(fetchedAt) {
|
|
3836
|
+
const ageSec = Math.floor((Date.now() - fetchedAt) / 1e3);
|
|
3837
|
+
if (ageSec < 60) return `${ageSec}s ago`;
|
|
3838
|
+
const ageMin = Math.floor(ageSec / 60);
|
|
3839
|
+
if (ageMin < 60) return `${ageMin}m ago`;
|
|
3840
|
+
return `${Math.floor(ageMin / 60)}h ago`;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
// src/commands/run.ts
|
|
3844
|
+
var DEFAULT_TTL = 3600;
|
|
3845
|
+
var runCommand = new Command13("run").description(
|
|
3846
|
+
"Run a command with project secrets injected as environment variables (no .env file written)"
|
|
3847
|
+
).argument("[command...]", "Command and arguments to execute").option(
|
|
3848
|
+
"-e, --env <environment>",
|
|
3849
|
+
"Environment to load (development, staging, production)"
|
|
3850
|
+
).option(
|
|
3851
|
+
"-p, --project <name-or-id>",
|
|
3852
|
+
"Linked project to load secrets from (defaults to active)"
|
|
3853
|
+
).option(
|
|
3854
|
+
"-o, --organization <id>",
|
|
3855
|
+
"Organization id (overrides linked project's org)"
|
|
3856
|
+
).option(
|
|
3857
|
+
"--keep-existing",
|
|
3858
|
+
"Don't overwrite env vars that are already set in the parent shell"
|
|
3859
|
+
).option(
|
|
3860
|
+
"--print",
|
|
3861
|
+
"Print the variables that would be injected and exit (no command runs)"
|
|
3862
|
+
).option(
|
|
3863
|
+
"--shell",
|
|
3864
|
+
"Run the command through the user's shell (enables pipes, &&, $VAR expansion)"
|
|
3865
|
+
).option("-q, --quiet", "Suppress informational messages").option("--no-cache", "Skip cache and always fetch fresh secrets from server").option(
|
|
3866
|
+
"--cache-ttl <seconds>",
|
|
3867
|
+
"How long cached secrets stay fresh before a fingerprint check (default: 3600s / 1h)",
|
|
3868
|
+
String(DEFAULT_TTL)
|
|
3869
|
+
).passThroughOptions().allowExcessArguments().action(async (commandArgs, options) => {
|
|
3870
|
+
try {
|
|
3871
|
+
if (!options.print && (!commandArgs || commandArgs.length === 0)) {
|
|
3872
|
+
throw invalidInput(
|
|
3873
|
+
"No command provided. Usage: envpilot run -- <command> [args...]"
|
|
3874
|
+
);
|
|
3875
|
+
}
|
|
3876
|
+
if (!isAuthenticated()) {
|
|
3877
|
+
throw notAuthenticated();
|
|
3878
|
+
}
|
|
3879
|
+
const config2 = readProjectConfigV2();
|
|
3880
|
+
if (!config2) throw notInitialized();
|
|
3881
|
+
const project = options.project ? resolveProject(config2, options.project) : getActiveProject(config2);
|
|
3882
|
+
if (!project) {
|
|
3883
|
+
if (options.project) {
|
|
3884
|
+
error(`Project not found in this directory: ${options.project}`);
|
|
3885
|
+
console.log();
|
|
3886
|
+
console.log("Linked projects:");
|
|
3887
|
+
for (const p of config2.projects) {
|
|
3888
|
+
console.log(` ${p.projectName || p.projectId} (${p.environment})`);
|
|
3889
|
+
}
|
|
3890
|
+
process.exit(1);
|
|
3891
|
+
}
|
|
3892
|
+
throw notInitialized();
|
|
3893
|
+
}
|
|
3894
|
+
const environment = options.env || project.environment;
|
|
3895
|
+
const organizationId = options.organization || project.organizationId;
|
|
3896
|
+
const ttl = Math.max(
|
|
3897
|
+
1,
|
|
3898
|
+
parseInt(options.cacheTtl ?? String(DEFAULT_TTL), 10) || DEFAULT_TTL
|
|
3899
|
+
);
|
|
3900
|
+
let variables;
|
|
3901
|
+
let cacheHit = false;
|
|
3902
|
+
let cacheAge = "";
|
|
3903
|
+
if (options.cache === false) {
|
|
3904
|
+
variables = await doFetch(
|
|
3905
|
+
project,
|
|
3906
|
+
environment,
|
|
3907
|
+
organizationId,
|
|
3908
|
+
options.quiet
|
|
3909
|
+
);
|
|
3910
|
+
writeCache(project.projectId, environment, organizationId, variables);
|
|
3911
|
+
} else {
|
|
3912
|
+
const probe = probeCache(
|
|
3913
|
+
project.projectId,
|
|
3914
|
+
environment,
|
|
3915
|
+
organizationId,
|
|
3916
|
+
ttl
|
|
3917
|
+
);
|
|
3918
|
+
if (probe.hit && probe.fresh) {
|
|
3919
|
+
variables = probe.entry.variables;
|
|
3920
|
+
cacheHit = true;
|
|
3921
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
3922
|
+
} else if (probe.hit && !probe.fresh) {
|
|
3923
|
+
try {
|
|
3924
|
+
const api = createAPIClient();
|
|
3925
|
+
const serverFingerprint = await api.checkFingerprint(
|
|
3926
|
+
project.projectId,
|
|
3927
|
+
environment,
|
|
3928
|
+
organizationId
|
|
3929
|
+
);
|
|
3930
|
+
if (serverFingerprint === probe.entry.fingerprint) {
|
|
3931
|
+
extendCacheFreshness(
|
|
3932
|
+
project.projectId,
|
|
3933
|
+
environment,
|
|
3934
|
+
organizationId
|
|
3935
|
+
);
|
|
3936
|
+
variables = probe.entry.variables;
|
|
3937
|
+
cacheHit = true;
|
|
3938
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
3939
|
+
} else {
|
|
3940
|
+
variables = await doFetch(
|
|
3941
|
+
project,
|
|
3942
|
+
environment,
|
|
3943
|
+
organizationId,
|
|
3944
|
+
options.quiet,
|
|
3945
|
+
"Secrets updated, refreshing"
|
|
3946
|
+
);
|
|
3947
|
+
writeCache(
|
|
3948
|
+
project.projectId,
|
|
3949
|
+
environment,
|
|
3950
|
+
organizationId,
|
|
3951
|
+
variables
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
} catch {
|
|
3955
|
+
variables = probe.entry.variables;
|
|
3956
|
+
cacheHit = true;
|
|
3957
|
+
cacheAge = formatAge(probe.entry.fetchedAt);
|
|
3958
|
+
}
|
|
3959
|
+
} else {
|
|
3960
|
+
variables = await doFetch(
|
|
3961
|
+
project,
|
|
3962
|
+
environment,
|
|
3963
|
+
organizationId,
|
|
3964
|
+
options.quiet
|
|
3965
|
+
);
|
|
3966
|
+
writeCache(project.projectId, environment, organizationId, variables);
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
if (variables.length === 0) {
|
|
3970
|
+
warning(
|
|
3971
|
+
`No variables found for ${environment}. Running command without injected secrets.`
|
|
3972
|
+
);
|
|
3973
|
+
}
|
|
3974
|
+
const secrets = {};
|
|
3975
|
+
for (const v of variables) {
|
|
3976
|
+
secrets[v.key] = v.value;
|
|
3977
|
+
}
|
|
3978
|
+
if (options.print) {
|
|
3979
|
+
printInjectionPreview(secrets, project, environment);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
const baseEnv = { ...process.env };
|
|
3983
|
+
const finalEnv = options.keepExisting ? { ...secrets, ...baseEnv } : { ...baseEnv, ...secrets };
|
|
3984
|
+
const overridden = [];
|
|
3985
|
+
if (!options.keepExisting) {
|
|
3986
|
+
for (const key of Object.keys(secrets)) {
|
|
3987
|
+
if (process.env[key] !== void 0 && process.env[key] !== secrets[key]) {
|
|
3988
|
+
overridden.push(key);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
if (!options.quiet) {
|
|
3993
|
+
const injectedCount = Object.keys(secrets).length;
|
|
3994
|
+
const cacheTag = cacheHit ? chalk15.dim(` \u26A1 cache (${cacheAge})`) : "";
|
|
3995
|
+
info(
|
|
3996
|
+
`Injected ${chalk15.bold(injectedCount)} ${injectedCount === 1 ? "variable" : "variables"} from ${chalk15.bold(`${project.projectName || project.projectId}/${environment}`)}${cacheTag}`
|
|
3997
|
+
);
|
|
3998
|
+
if (overridden.length > 0) {
|
|
3999
|
+
warning(
|
|
4000
|
+
`Overriding ${overridden.length} shell var${overridden.length === 1 ? "" : "s"}: ${overridden.slice(0, 3).join(", ")}${overridden.length > 3 ? `, +${overridden.length - 3} more` : ""}`
|
|
4001
|
+
);
|
|
4002
|
+
info("Use --keep-existing to keep your shell values instead.");
|
|
4003
|
+
}
|
|
4004
|
+
console.log();
|
|
4005
|
+
}
|
|
4006
|
+
await runChild(commandArgs, finalEnv, options);
|
|
4007
|
+
} catch (err) {
|
|
4008
|
+
await handleError(err);
|
|
4009
|
+
}
|
|
4010
|
+
});
|
|
4011
|
+
async function doFetch(project, environment, organizationId, quiet, labelPrefix = "Loading") {
|
|
4012
|
+
const label = `${labelPrefix} ${chalk15.bold(environment)} secrets for ${chalk15.bold(project.projectName || project.projectId)}...`;
|
|
4013
|
+
const api = createAPIClient();
|
|
4014
|
+
const { variables, decryptionFailures } = quiet ? await api.listVariables(project.projectId, environment, organizationId) : await withSpinner(
|
|
4015
|
+
label,
|
|
4016
|
+
() => api.listVariables(project.projectId, environment, organizationId)
|
|
4017
|
+
);
|
|
4018
|
+
if (decryptionFailures.length > 0) {
|
|
4019
|
+
for (const key of decryptionFailures) {
|
|
4020
|
+
warning(
|
|
4021
|
+
`Could not decrypt ${chalk15.bold(key)} \u2014 skipped (vault error, check server logs)`
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
return variables;
|
|
4026
|
+
}
|
|
4027
|
+
function printInjectionPreview(secrets, project, environment) {
|
|
4028
|
+
const keys = Object.keys(secrets).sort();
|
|
4029
|
+
console.log();
|
|
4030
|
+
console.log(
|
|
4031
|
+
chalk15.bold(
|
|
4032
|
+
`Would inject ${keys.length} ${keys.length === 1 ? "variable" : "variables"} from ${chalk15.cyan(`${project.projectName || project.projectId}/${environment}`)}:`
|
|
4033
|
+
)
|
|
4034
|
+
);
|
|
4035
|
+
console.log();
|
|
4036
|
+
if (keys.length === 0) {
|
|
4037
|
+
console.log(chalk15.dim(" (no variables)"));
|
|
4038
|
+
} else {
|
|
4039
|
+
for (const key of keys) {
|
|
4040
|
+
const value = secrets[key];
|
|
4041
|
+
const masked = maskForPreview(value);
|
|
4042
|
+
console.log(` ${chalk15.cyan(key)}=${chalk15.dim(masked)}`);
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
console.log();
|
|
4046
|
+
success("Dry run \u2014 no command executed.");
|
|
4047
|
+
}
|
|
4048
|
+
function maskForPreview(value) {
|
|
4049
|
+
if (value.length <= 6) return "******";
|
|
4050
|
+
return `${value.slice(0, 4)}\u2026${value.slice(-2)} (${value.length} chars)`;
|
|
4051
|
+
}
|
|
4052
|
+
function runChild(commandArgs, env, options) {
|
|
4053
|
+
return new Promise((resolve3) => {
|
|
4054
|
+
const [command, ...args] = commandArgs;
|
|
4055
|
+
if (!command) {
|
|
4056
|
+
process.exit(1);
|
|
4057
|
+
}
|
|
4058
|
+
const isWindows = process.platform === "win32";
|
|
4059
|
+
const useShell = options.shell === true || isWindows;
|
|
4060
|
+
const child = spawn2(command, args, {
|
|
4061
|
+
stdio: "inherit",
|
|
4062
|
+
env,
|
|
4063
|
+
shell: useShell
|
|
4064
|
+
});
|
|
4065
|
+
const signals = [
|
|
4066
|
+
"SIGINT",
|
|
4067
|
+
"SIGTERM",
|
|
4068
|
+
"SIGHUP",
|
|
4069
|
+
"SIGQUIT"
|
|
4070
|
+
];
|
|
4071
|
+
const forward = {};
|
|
4072
|
+
for (const sig of signals) {
|
|
4073
|
+
const handler = () => {
|
|
4074
|
+
if (!child.killed) {
|
|
4075
|
+
try {
|
|
4076
|
+
child.kill(sig);
|
|
4077
|
+
} catch {
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
};
|
|
4081
|
+
forward[sig] = handler;
|
|
4082
|
+
process.on(sig, handler);
|
|
4083
|
+
}
|
|
4084
|
+
const cleanup = () => {
|
|
4085
|
+
for (const sig of signals) {
|
|
4086
|
+
const handler = forward[sig];
|
|
4087
|
+
if (handler) process.off(sig, handler);
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
4090
|
+
child.on("error", (err) => {
|
|
4091
|
+
cleanup();
|
|
4092
|
+
const code = err.code;
|
|
4093
|
+
if (code === "ENOENT") {
|
|
4094
|
+
error(
|
|
4095
|
+
`Command not found: ${command}. Make sure it is installed and on your PATH.`
|
|
4096
|
+
);
|
|
4097
|
+
} else if (code === "EACCES") {
|
|
4098
|
+
error(`Permission denied executing: ${command}`);
|
|
4099
|
+
} else {
|
|
4100
|
+
error(`Failed to spawn process: ${err.message}`);
|
|
4101
|
+
}
|
|
4102
|
+
process.exit(1);
|
|
4103
|
+
});
|
|
4104
|
+
child.on("exit", (code, signal) => {
|
|
4105
|
+
cleanup();
|
|
4106
|
+
if (signal) {
|
|
4107
|
+
try {
|
|
4108
|
+
process.kill(process.pid, signal);
|
|
4109
|
+
} catch {
|
|
4110
|
+
process.exit(1);
|
|
4111
|
+
}
|
|
4112
|
+
} else {
|
|
4113
|
+
process.exit(code ?? 0);
|
|
4114
|
+
}
|
|
4115
|
+
resolve3();
|
|
4116
|
+
});
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
// src/commands/man.ts
|
|
4121
|
+
import { Command as Command14 } from "commander";
|
|
4122
|
+
import chalk16 from "chalk";
|
|
3718
4123
|
function printCommandManual(commandName) {
|
|
3719
4124
|
const command = findCommandDefinition(commandName);
|
|
3720
4125
|
if (!command) {
|
|
3721
|
-
console.log(
|
|
4126
|
+
console.log(chalk16.red(`Unknown command reference: ${commandName}`));
|
|
3722
4127
|
console.log();
|
|
3723
4128
|
console.log("Run `envpilot man` to see all supported commands.");
|
|
3724
4129
|
process.exit(1);
|
|
3725
4130
|
}
|
|
3726
|
-
console.log(
|
|
4131
|
+
console.log(chalk16.bold(formatArgv(command.argv)));
|
|
3727
4132
|
if (command.args) {
|
|
3728
|
-
console.log(
|
|
4133
|
+
console.log(chalk16.dim(command.args));
|
|
3729
4134
|
}
|
|
3730
4135
|
blank();
|
|
3731
4136
|
console.log(command.description);
|
|
3732
4137
|
blank();
|
|
3733
|
-
console.log(
|
|
4138
|
+
console.log(chalk16.green("Examples"));
|
|
3734
4139
|
for (const example of command.examples) {
|
|
3735
|
-
console.log(` ${
|
|
4140
|
+
console.log(` ${chalk16.cyan(formatArgv(example))}`);
|
|
3736
4141
|
}
|
|
3737
4142
|
blank();
|
|
3738
|
-
console.log(
|
|
4143
|
+
console.log(chalk16.green("Notes"));
|
|
3739
4144
|
for (const note of command.notes) {
|
|
3740
4145
|
console.log(` - ${note}`);
|
|
3741
4146
|
}
|
|
3742
4147
|
}
|
|
3743
4148
|
function printManualIndex(commands) {
|
|
3744
|
-
console.log(
|
|
4149
|
+
console.log(chalk16.bold("ENVPILOT(1)"));
|
|
3745
4150
|
console.log("TypeScript CLI manual");
|
|
3746
4151
|
blank();
|
|
3747
4152
|
console.log(
|
|
3748
|
-
`Total supported top-level commands: ${
|
|
4153
|
+
`Total supported top-level commands: ${chalk16.green(String(CLI_COMMAND_COUNT))}`
|
|
3749
4154
|
);
|
|
3750
4155
|
blank();
|
|
3751
4156
|
console.log(
|
|
@@ -3753,15 +4158,15 @@ function printManualIndex(commands) {
|
|
|
3753
4158
|
);
|
|
3754
4159
|
blank();
|
|
3755
4160
|
line();
|
|
3756
|
-
console.log(
|
|
4161
|
+
console.log(chalk16.green("Commands"));
|
|
3757
4162
|
for (const command of commands) {
|
|
3758
4163
|
console.log(
|
|
3759
|
-
` ${
|
|
4164
|
+
` ${chalk16.cyan(formatArgv(command.argv).padEnd(24))} ${chalk16.dim(command.description)}`
|
|
3760
4165
|
);
|
|
3761
4166
|
}
|
|
3762
4167
|
blank();
|
|
3763
4168
|
line();
|
|
3764
|
-
console.log(
|
|
4169
|
+
console.log(chalk16.green("Security"));
|
|
3765
4170
|
console.log(" - `.env*` is ignored by the repository `.gitignore`.");
|
|
3766
4171
|
console.log(
|
|
3767
4172
|
" - `envpilot init` and `envpilot sync` ensure env files are added to `.gitignore` locally."
|
|
@@ -3774,7 +4179,7 @@ function printManualIndex(commands) {
|
|
|
3774
4179
|
);
|
|
3775
4180
|
blank();
|
|
3776
4181
|
line();
|
|
3777
|
-
console.log(
|
|
4182
|
+
console.log(chalk16.green("Usage"));
|
|
3778
4183
|
console.log(
|
|
3779
4184
|
" - `envpilot` or `envpilot ui` opens the interactive terminal UI."
|
|
3780
4185
|
);
|
|
@@ -3783,7 +4188,7 @@ function printManualIndex(commands) {
|
|
|
3783
4188
|
);
|
|
3784
4189
|
}
|
|
3785
4190
|
function createManCommand(commands) {
|
|
3786
|
-
return new
|
|
4191
|
+
return new Command14("man").description("Show the CLI manual page").argument("[command]", "Optional command to show detailed manual for").action((command) => {
|
|
3787
4192
|
if (command) {
|
|
3788
4193
|
printCommandManual(command);
|
|
3789
4194
|
return;
|
|
@@ -3793,9 +4198,9 @@ function createManCommand(commands) {
|
|
|
3793
4198
|
}
|
|
3794
4199
|
|
|
3795
4200
|
// src/commands/ui.ts
|
|
3796
|
-
import { Command as
|
|
4201
|
+
import { Command as Command15 } from "commander";
|
|
3797
4202
|
function createUICommand() {
|
|
3798
|
-
return new
|
|
4203
|
+
return new Command15("ui").alias("dashboard").description("Open the interactive Ink-powered terminal UI").action(async () => {
|
|
3799
4204
|
if (process.env.ENVPILOT_TUI_CHILD === "1") {
|
|
3800
4205
|
return;
|
|
3801
4206
|
}
|
|
@@ -3945,6 +4350,42 @@ var COMMAND_CATALOG = [
|
|
|
3945
4350
|
topLevel: true,
|
|
3946
4351
|
createCommand: () => pushCommand
|
|
3947
4352
|
},
|
|
4353
|
+
{
|
|
4354
|
+
id: "run",
|
|
4355
|
+
title: "Run command with secrets",
|
|
4356
|
+
category: "Sync",
|
|
4357
|
+
description: "Inject project secrets into a child process without writing a .env file (envpilot run -- bun dev).",
|
|
4358
|
+
argv: ["run"],
|
|
4359
|
+
args: "[--env <environment>] [--project <name-or-id>] [--keep-existing] [--print] -- <command> [args...]",
|
|
4360
|
+
examples: [
|
|
4361
|
+
["run", "--", "bun", "dev"],
|
|
4362
|
+
["run", "--env", "production", "--", "node", "server.js"],
|
|
4363
|
+
["run", "--project", "api", "--", "pnpm", "test"],
|
|
4364
|
+
["run", "--print"],
|
|
4365
|
+
["run", "--keep-existing", "--", "bun", "dev"]
|
|
4366
|
+
],
|
|
4367
|
+
websiteSurface: "Maps to `/api/cli/variables` for read access.",
|
|
4368
|
+
notes: [
|
|
4369
|
+
"Use `--` to separate envpilot flags from the command to execute.",
|
|
4370
|
+
"Secrets override existing shell vars by default; pass --keep-existing to flip.",
|
|
4371
|
+
"On Windows, the command is run through the shell so .cmd / .bat files resolve.",
|
|
4372
|
+
"Signals (SIGINT, SIGTERM, SIGHUP, SIGQUIT) are forwarded to the child process.",
|
|
4373
|
+
"Use --print to inspect what would be injected without executing anything."
|
|
4374
|
+
],
|
|
4375
|
+
keywords: [
|
|
4376
|
+
"run",
|
|
4377
|
+
"exec",
|
|
4378
|
+
"spawn",
|
|
4379
|
+
"inject",
|
|
4380
|
+
"secrets",
|
|
4381
|
+
"env",
|
|
4382
|
+
"ephemeral",
|
|
4383
|
+
"no-file",
|
|
4384
|
+
"doppler"
|
|
4385
|
+
],
|
|
4386
|
+
topLevel: true,
|
|
4387
|
+
createCommand: () => runCommand
|
|
4388
|
+
},
|
|
3948
4389
|
{
|
|
3949
4390
|
id: "list",
|
|
3950
4391
|
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-RUYGAIH5.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.0" : "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.0" : "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());
|