xtra-cli 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/xtra.js +3 -137
- package/dist/commands/access.js +1 -184
- package/dist/commands/admin.js +5 -118
- package/dist/commands/audit.js +1 -67
- package/dist/commands/branch.js +1 -212
- package/dist/commands/checkout.js +1 -80
- package/dist/commands/ci.js +31 -341
- package/dist/commands/completion.js +85 -224
- package/dist/commands/diff.js +6 -162
- package/dist/commands/doctor.js +2 -164
- package/dist/commands/env.js +2 -70
- package/dist/commands/export.js +2 -83
- package/dist/commands/generate.js +3 -179
- package/dist/commands/history.js +4 -77
- package/dist/commands/import.js +1 -121
- package/dist/commands/init.js +7 -205
- package/dist/commands/integration.js +11 -188
- package/dist/commands/jit-run.js +5 -0
- package/dist/commands/local.js +16 -198
- package/dist/commands/login.js +16 -198
- package/dist/commands/login.test.js +16 -51
- package/dist/commands/logout.js +1 -0
- package/dist/commands/logs.js +3 -121
- package/dist/commands/profile.js +14 -184
- package/dist/commands/project.js +1 -165
- package/dist/commands/rollback.js +1 -95
- package/dist/commands/rotate.js +3 -93
- package/dist/commands/run.js +11 -215
- package/dist/commands/scan.js +25 -127
- package/dist/commands/secrets.js +14 -305
- package/dist/commands/simulate.js +6 -109
- package/dist/commands/status.js +2 -93
- package/dist/commands/template.js +22 -276
- package/dist/commands/ui.js +2 -289
- package/dist/commands/watch.js +17 -123
- package/dist/lib/api.js +1 -187
- package/dist/lib/api.test.js +1 -89
- package/dist/lib/audit.js +1 -136
- package/dist/lib/config.js +1 -72
- package/dist/lib/config.test.js +1 -47
- package/dist/lib/crypto.js +1 -50
- package/dist/lib/manifest.js +1 -52
- package/dist/lib/profiles.js +1 -103
- package/package.json +7 -2
|
@@ -1,188 +1,11 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const fs_1 = __importDefault(require("fs"));
|
|
13
|
-
exports.integrationCommand = new commander_1.Command("integration");
|
|
14
|
-
exports.integrationCommand
|
|
15
|
-
.command("sync")
|
|
16
|
-
.description("Sync secrets to external integrations (GitHub)")
|
|
17
|
-
.option("-p, --project <id>", "Project ID (defaults to current directory config)")
|
|
18
|
-
.option("-e, --env <environment>", "Environment (development, staging, production)", "development")
|
|
19
|
-
.option("--github", "Sync to GitHub")
|
|
20
|
-
.option("--repo <owner/repo>", "GitHub repository (e.g., owner/repo)")
|
|
21
|
-
.option("--prefix <prefix>", "Prefix for secret keys")
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
try {
|
|
24
|
-
let projectId = options.project;
|
|
25
|
-
if (!projectId) {
|
|
26
|
-
projectId = (0, config_1.getRcConfig)().project;
|
|
27
|
-
if (!projectId) {
|
|
28
|
-
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// Check if GitHub connected
|
|
33
|
-
const status = await api_1.api.getIntegrationStatus("github");
|
|
34
|
-
if (!status.connected) {
|
|
35
|
-
console.log(chalk_1.default.yellow("\nGitHub not connected. Please connect via the web dashboard first."));
|
|
36
|
-
console.log(chalk_1.default.blue("Visit: https://www.xtrasecurity.in/integrations\n"));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
console.log(chalk_1.default.green(`\nConnected as: ${status.username}`));
|
|
40
|
-
let repo = options.repo;
|
|
41
|
-
// If repo not provided, fetch list and ask user
|
|
42
|
-
if (!repo) {
|
|
43
|
-
console.log("Fetching repositories...");
|
|
44
|
-
const repos = await api_1.api.getIntegrationRepos("github");
|
|
45
|
-
const answer = await inquirer_1.default.prompt([
|
|
46
|
-
{
|
|
47
|
-
type: "list",
|
|
48
|
-
name: "repo",
|
|
49
|
-
message: "Select a repository to sync to:",
|
|
50
|
-
choices: repos.map((r) => ({
|
|
51
|
-
name: `${r.fullName} ${r.private ? "(🔒)" : ""}`,
|
|
52
|
-
value: r.id.toString(),
|
|
53
|
-
short: r.fullName
|
|
54
|
-
}))
|
|
55
|
-
}
|
|
56
|
-
]);
|
|
57
|
-
// Find selected repo details
|
|
58
|
-
const selectedRepo = repos.find((r) => r.id.toString() === answer.repo);
|
|
59
|
-
if (selectedRepo) {
|
|
60
|
-
repo = { owner: selectedRepo.owner, name: selectedRepo.name };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
// Parse repo string
|
|
65
|
-
const [owner, name] = repo.split("/");
|
|
66
|
-
if (!owner || !name) {
|
|
67
|
-
console.error(chalk_1.default.red("Error: Invalid repository format. Use owner/repo"));
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
repo = { owner, name };
|
|
71
|
-
}
|
|
72
|
-
console.log(chalk_1.default.blue(`\nSyncing secrets from ${options.env} to GitHub repo: ${repo.owner}/${repo.name}...`));
|
|
73
|
-
const result = await api_1.api.syncSecretsToGithub({
|
|
74
|
-
projectId,
|
|
75
|
-
environment: options.env,
|
|
76
|
-
repoOwner: repo.owner,
|
|
77
|
-
repoName: repo.name,
|
|
78
|
-
secretPrefix: options.prefix
|
|
79
|
-
});
|
|
80
|
-
console.log(chalk_1.default.green(`\nSuccessfully synced ${result.summary.synced} secrets!`));
|
|
81
|
-
if (result.summary.failed > 0) {
|
|
82
|
-
console.log(chalk_1.default.red(`${result.summary.failed} secrets failed to sync.`));
|
|
83
|
-
}
|
|
84
|
-
// Show details
|
|
85
|
-
result.results.forEach((r) => {
|
|
86
|
-
if (r.success) {
|
|
87
|
-
console.log(chalk_1.default.green(`✓ ${r.key}`));
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.log(chalk_1.default.red(`✗ ${r.key}: ${r.error}`));
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
console.error(chalk_1.default.red("\nError syncing secrets:"), error.message || error);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
exports.kubernetesCommand = new commander_1.Command("kubernetes");
|
|
100
|
-
exports.kubernetesCommand
|
|
101
|
-
.command("export")
|
|
102
|
-
.description("Export secrets as Kubernetes manifest")
|
|
103
|
-
.option("-p, --project <id>", "Project ID")
|
|
104
|
-
.option("-e, --env <environment>", "Environment", "development")
|
|
105
|
-
.option("-n, --namespace <namespace>", "Kubernetes namespace", "default")
|
|
106
|
-
.option("-o, --output <file>", "Output file path")
|
|
107
|
-
.option("--name <name>", "Secret resource name")
|
|
108
|
-
.action(async (options) => {
|
|
109
|
-
try {
|
|
110
|
-
let projectId = options.project;
|
|
111
|
-
if (!projectId) {
|
|
112
|
-
projectId = (0, config_1.getRcConfig)().project;
|
|
113
|
-
if (!projectId) {
|
|
114
|
-
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
console.log(chalk_1.default.blue(`Generating Kubernetes manifest for ${options.env}...`));
|
|
119
|
-
const manifest = await api_1.api.exportKubernetesSecret(projectId, {
|
|
120
|
-
environment: options.env,
|
|
121
|
-
namespace: options.namespace,
|
|
122
|
-
name: options.name
|
|
123
|
-
});
|
|
124
|
-
if (options.output) {
|
|
125
|
-
fs_1.default.writeFileSync(options.output, manifest);
|
|
126
|
-
console.log(chalk_1.default.green(`\nManifest saved to ${options.output}`));
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
console.log("\n" + manifest);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
console.error(chalk_1.default.red("\nError generating manifest:"), error.message || error);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
exports.kubernetesCommand
|
|
138
|
-
.command("apply")
|
|
139
|
-
.description("Apply secrets directly to Kubernetes cluster (requires kubectl)")
|
|
140
|
-
.option("-p, --project <id>", "Project ID")
|
|
141
|
-
.option("-e, --env <environment>", "Environment", "development")
|
|
142
|
-
.option("-n, --namespace <namespace>", "Kubernetes namespace", "default")
|
|
143
|
-
.action(async (options) => {
|
|
144
|
-
try {
|
|
145
|
-
// Check for kubectl
|
|
146
|
-
const { execSync } = require("child_process");
|
|
147
|
-
try {
|
|
148
|
-
execSync("kubectl version --client", { stdio: "ignore" });
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
console.error(chalk_1.default.red("Error: kubectl not found or not configured."));
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
let projectId = options.project;
|
|
155
|
-
if (!projectId) {
|
|
156
|
-
projectId = (0, config_1.getRcConfig)().project;
|
|
157
|
-
if (!projectId) {
|
|
158
|
-
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
console.log(chalk_1.default.blue(`Fetching secrets for ${options.env}...`));
|
|
163
|
-
const manifest = await api_1.api.exportKubernetesSecret(projectId, {
|
|
164
|
-
environment: options.env,
|
|
165
|
-
namespace: options.namespace,
|
|
166
|
-
// No custom name, let backend generate it
|
|
167
|
-
});
|
|
168
|
-
console.log(chalk_1.default.blue("Applying to Kubernetes..."));
|
|
169
|
-
try {
|
|
170
|
-
const child = require("child_process").spawn("kubectl", ["apply", "-f", "-"]);
|
|
171
|
-
child.stdin.write(manifest);
|
|
172
|
-
child.stdin.end();
|
|
173
|
-
child.stdout.on("data", (data) => {
|
|
174
|
-
console.log(data.toString());
|
|
175
|
-
});
|
|
176
|
-
child.stderr.on("data", (data) => {
|
|
177
|
-
console.error(chalk_1.default.yellow(data.toString()));
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
console.error(chalk_1.default.red("Failed to apply manifest:"), e.message);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
console.error(chalk_1.default.red("\nError:"), error.message || error);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
1
|
+
var E=Object.create;var d=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var L=Object.getPrototypeOf,_=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var r in t)d(e,r,{get:t[r],enumerable:!0})},$=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of q(t))!_.call(e,n)&&n!==r&&d(e,n,{get:()=>t[n],enumerable:!(s=G(t,n))||s.enumerable});return e};var c=(e,t,r)=>(r=e!=null?E(L(e)):{},$(t||!e||!e.__esModule?d(r,"default",{value:e,enumerable:!0}):r,e)),K=e=>$(d({},"__esModule",{value:!0}),e);var T={};D(T,{integrationCommand:()=>A,kubernetesCommand:()=>j});module.exports=K(T);var v=require("commander"),a=c(require("chalk"));var I=c(require("axios"));var x=c(require("conf")),h=c(require("fs")),k=c(require("path")),b="https://www.xtrasecurity.in/api",p=new x.default({projectName:"xtra-cli",defaults:{apiUrl:b}}),w=p.get("apiUrl");w&&!process.env.XTRA_API_URL&&(w.includes("localhost")||w.includes("xtra-security.vercel.app"))&&p.set("apiUrl",b);var m=()=>({...p.store,apiUrl:process.env.XTRA_API_URL||p.get("apiUrl")||b});var S=()=>p.get("token");var y=()=>{let e={};try{let t=k.default.join(process.cwd(),".xtrarc");h.default.existsSync(t)&&(e=JSON.parse(h.default.readFileSync(t,"utf-8")))}catch{}return{project:e.project||p.get("project")||"",env:e.env||p.get("env")||"development",branch:e.branch||p.get("branch")||"main"}};var C=c(require("fs")),U=c(require("path")),N=JSON.parse(C.readFileSync(U.join(__dirname,"../../package.json"),"utf-8")),o=()=>{let{apiUrl:e}=m(),t=S();return I.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":N.version,...t?{Authorization:`Bearer ${t}`}:{}}})},l={login:async(e,t,r)=>(await o().post("/auth/cli-login",{email:e,password:t,apiKey:r})).data,getSecrets:async(e,t,r="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets?branch=${r}`)).data,getSecretVersions:async(e,t,r="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets?includeVersions=true&branch=${r}`)).data,setSecrets:async(e,t,r,s,n="main")=>(await o().post(`/projects/${e}/envs/${t}/secrets`,{secrets:r,expectedVersions:s,branch:n})).data,getSecretDetails:async(e,t,r,s="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets/${r}?branch=${s}`)).data,syncLogs:async e=>(await o().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,t,r,s,n,i)=>(await o().post(`/projects/${e}/envs/${t}/secrets/link`,{key:r,sourceProjectId:s,sourceEnv:n,sourceKey:i})).data,rotateSecret:async(e,t,r,s,n)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${r}/rotate`,{strategy:s,parsedNewValue:n})).data,promoteSecret:async(e,t,r)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${r}/promote`,{})).data,verifyAuditLogs:async()=>(await o().get("/audit/verify")).data,exportAuditLogs:async(e,t,r,s)=>{let n={format:e};return t&&(n.startDate=t),r&&(n.endDate=r),s&&(n.projectId=s),(await o().post("/audit/export",n,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,t,r,s)=>(await o().post("/access/request",{projectId:e,secretId:s,reason:t,duration:r})).data,approveAccess:async(e,t)=>(await o().post("/access/approve",{requestId:e,decision:t})).data,listAccessRequests:async e=>{let{workspace:t}=m(),r=t?`&workspaceId=${t}`:"";return(await o().get(`/access/list?mode=${e}${r}`)).data},getAccessRequestStatus:async e=>(await o().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=m(),t=e?`/project?workspaceId=${e}`:"/project";return(await o().get(t)).data},getBranches:async e=>(await o().get(`/branch?projectId=${e}`)).data,createBranch:async(e,t,r)=>(await o().post("/branch",{projectId:e,name:t,description:r})).data,deleteBranch:async e=>(await o().delete(`/branch/${e}`)).data,updateBranch:async(e,t)=>(await o().put("/branch",{id:e,...t})).data,getRoles:async()=>(await o().get("/admin/roles")).data,getUsers:async e=>{let t=e?`?teamId=${e}`:"";return(await o().get(`/admin/users${t}`)).data},setUserRole:async(e,t,r)=>(await o().put("/admin/users/role",{email:e,role:t,teamId:r})).data,getIntegrationStatus:async e=>(await o().get(`/integrations/${e}`)).data,getIntegrationRepos:async e=>(await o().get(`/integrations/${e}/sync`)).data.repos,syncSecretsToGithub:async e=>(await o().post("/integrations/github/sync",e)).data,exportKubernetesSecret:async(e,t)=>{let r=new URLSearchParams(t).toString();return(await o().get(`/projects/${e}/kubernetes?${r}`)).data},getSecretHistory:async(e,t,r)=>(await o().get(`/projects/${e}/envs/${t}/secrets/${r}/history`)).data,rollbackSecret:async(e,t,r,s)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${r}/history`,{version:s})).data,cloneEnvironment:async(e,t,r,s,n)=>(await o().post(`/projects/${e}/envs/clone`,{fromEnv:t,toEnv:r,overwrite:s,branch:n})).data,generateJitLink:async e=>(await o().post("/jit/generate",e)).data,claimJitLink:async e=>(await o().post("/jit/claim",{token:e})).data,getJitInfo:async e=>(await o().get(`/jit/${e}`)).data,post:async(e,t)=>(await o().post(e,t)).data};var P=c(require("inquirer")),R=c(require("fs")),A=new v.Command("integration");A.command("sync").description("Sync secrets to external integrations (GitHub)").option("-p, --project <id>","Project ID (defaults to current directory config)").option("-e, --env <environment>","Environment (development, staging, production)","development").option("--github","Sync to GitHub").option("--repo <owner/repo>","GitHub repository (e.g., owner/repo)").option("--prefix <prefix>","Prefix for secret keys").action(async e=>{try{let t=e.project;t||(t=y().project,t||(console.error(a.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first.")),process.exit(1)));let r=await l.getIntegrationStatus("github");r.connected||(console.log(a.default.yellow(`
|
|
2
|
+
GitHub not connected. Please connect via the web dashboard first.`)),console.log(a.default.blue(`Visit: https://www.xtrasecurity.in/integrations
|
|
3
|
+
`)),process.exit(1)),console.log(a.default.green(`
|
|
4
|
+
Connected as: ${r.username}`));let s=e.repo;if(s){let[i,u]=s.split("/");(!i||!u)&&(console.error(a.default.red("Error: Invalid repository format. Use owner/repo")),process.exit(1)),s={owner:i,name:u}}else{console.log("Fetching repositories...");let i=await l.getIntegrationRepos("github"),u=await P.default.prompt([{type:"list",name:"repo",message:"Select a repository to sync to:",choices:i.map(g=>({name:`${g.fullName} ${g.private?"(\u{1F512})":""}`,value:g.id.toString(),short:g.fullName}))}]),f=i.find(g=>g.id.toString()===u.repo);f&&(s={owner:f.owner,name:f.name})}console.log(a.default.blue(`
|
|
5
|
+
Syncing secrets from ${e.env} to GitHub repo: ${s.owner}/${s.name}...`));let n=await l.syncSecretsToGithub({projectId:t,environment:e.env,repoOwner:s.owner,repoName:s.name,secretPrefix:e.prefix});console.log(a.default.green(`
|
|
6
|
+
Successfully synced ${n.summary.synced} secrets!`)),n.summary.failed>0&&console.log(a.default.red(`${n.summary.failed} secrets failed to sync.`)),n.results.forEach(i=>{i.success?console.log(a.default.green(`\u2713 ${i.key}`)):console.log(a.default.red(`\u2717 ${i.key}: ${i.error}`))})}catch(t){console.error(a.default.red(`
|
|
7
|
+
Error syncing secrets:`),t.message||t),process.exit(1)}});var j=new v.Command("kubernetes");j.command("export").description("Export secrets as Kubernetes manifest").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment","development").option("-n, --namespace <namespace>","Kubernetes namespace","default").option("-o, --output <file>","Output file path").option("--name <name>","Secret resource name").action(async e=>{try{let t=e.project;t||(t=y().project,t||(console.error(a.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first.")),process.exit(1))),console.log(a.default.blue(`Generating Kubernetes manifest for ${e.env}...`));let r=await l.exportKubernetesSecret(t,{environment:e.env,namespace:e.namespace,name:e.name});e.output?(R.default.writeFileSync(e.output,r),console.log(a.default.green(`
|
|
8
|
+
Manifest saved to ${e.output}`))):console.log(`
|
|
9
|
+
`+r)}catch(t){console.error(a.default.red(`
|
|
10
|
+
Error generating manifest:`),t.message||t),process.exit(1)}});j.command("apply").description("Apply secrets directly to Kubernetes cluster (requires kubectl)").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment","development").option("-n, --namespace <namespace>","Kubernetes namespace","default").action(async e=>{try{let{execSync:t}=require("child_process");try{t("kubectl version --client",{stdio:"ignore"})}catch{console.error(a.default.red("Error: kubectl not found or not configured.")),process.exit(1)}let r=e.project;r||(r=y().project,r||(console.error(a.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first.")),process.exit(1))),console.log(a.default.blue(`Fetching secrets for ${e.env}...`));let s=await l.exportKubernetesSecret(r,{environment:e.env,namespace:e.namespace});console.log(a.default.blue("Applying to Kubernetes..."));try{let n=require("child_process").spawn("kubectl",["apply","-f","-"]);n.stdin.write(s),n.stdin.end(),n.stdout.on("data",i=>{console.log(i.toString())}),n.stderr.on("data",i=>{console.error(a.default.yellow(i.toString()))})}catch(n){console.error(a.default.red("Failed to apply manifest:"),n.message)}}catch(t){console.error(a.default.red(`
|
|
11
|
+
Error:`),t.message||t),process.exit(1)}});0&&(module.exports={integrationCommand,kubernetesCommand});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
var _=Object.create;var f=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var D=(e,s)=>{for(var t in s)f(e,t,{get:s[t],enumerable:!0})},x=(e,s,t,r)=>{if(s&&typeof s=="object"||typeof s=="function")for(let o of B(s))!N.call(e,o)&&o!==t&&f(e,o,{get:()=>s[o],enumerable:!(r=O(s,o))||r.enumerable});return e};var i=(e,s,t)=>(t=e!=null?_(G(e)):{},x(s||!e||!e.__esModule?f(t,"default",{value:e,enumerable:!0}):t,e)),E=e=>x(f({},"__esModule",{value:!0}),e);var X={};D(X,{jitRunCommand:()=>F});module.exports=E(X);var U=require("commander");var A=i(require("axios"));var S=i(require("conf")),j=i(require("fs")),I=i(require("path")),v="https://www.xtrasecurity.in/api",c=new S.default({projectName:"xtra-cli",defaults:{apiUrl:v}}),$=c.get("apiUrl");$&&!process.env.XTRA_API_URL&&($.includes("localhost")||$.includes("xtra-security.vercel.app"))&&c.set("apiUrl",v);var w=()=>({...c.store,apiUrl:process.env.XTRA_API_URL||c.get("apiUrl")||v});var b=()=>c.get("token");var C=()=>{let e={};try{let s=I.default.join(process.cwd(),".xtrarc");j.default.existsSync(s)&&(e=JSON.parse(j.default.readFileSync(s,"utf-8")))}catch{}return{project:e.project||c.get("project")||"",env:e.env||c.get("env")||"development",branch:e.branch||c.get("branch")||"main"}};var R=i(require("fs")),T=i(require("path")),V=JSON.parse(R.readFileSync(T.join(__dirname,"../../package.json"),"utf-8")),n=()=>{let{apiUrl:e}=w(),s=b();return A.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":V.version,...s?{Authorization:`Bearer ${s}`}:{}}})},h={login:async(e,s,t)=>(await n().post("/auth/cli-login",{email:e,password:s,apiKey:t})).data,getSecrets:async(e,s,t="main")=>(await n().get(`/projects/${e}/envs/${s}/secrets?branch=${t}`)).data,getSecretVersions:async(e,s,t="main")=>(await n().get(`/projects/${e}/envs/${s}/secrets?includeVersions=true&branch=${t}`)).data,setSecrets:async(e,s,t,r,o="main")=>(await n().post(`/projects/${e}/envs/${s}/secrets`,{secrets:t,expectedVersions:r,branch:o})).data,getSecretDetails:async(e,s,t,r="main")=>(await n().get(`/projects/${e}/envs/${s}/secrets/${t}?branch=${r}`)).data,syncLogs:async e=>(await n().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,s,t,r,o,p)=>(await n().post(`/projects/${e}/envs/${s}/secrets/link`,{key:t,sourceProjectId:r,sourceEnv:o,sourceKey:p})).data,rotateSecret:async(e,s,t,r,o)=>(await n().post(`/projects/${e}/envs/${s}/secrets/${t}/rotate`,{strategy:r,parsedNewValue:o})).data,promoteSecret:async(e,s,t)=>(await n().post(`/projects/${e}/envs/${s}/secrets/${t}/promote`,{})).data,verifyAuditLogs:async()=>(await n().get("/audit/verify")).data,exportAuditLogs:async(e,s,t,r)=>{let o={format:e};return s&&(o.startDate=s),t&&(o.endDate=t),r&&(o.projectId=r),(await n().post("/audit/export",o,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,s,t,r)=>(await n().post("/access/request",{projectId:e,secretId:r,reason:s,duration:t})).data,approveAccess:async(e,s)=>(await n().post("/access/approve",{requestId:e,decision:s})).data,listAccessRequests:async e=>{let{workspace:s}=w(),t=s?`&workspaceId=${s}`:"";return(await n().get(`/access/list?mode=${e}${t}`)).data},getAccessRequestStatus:async e=>(await n().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=w(),s=e?`/project?workspaceId=${e}`:"/project";return(await n().get(s)).data},getBranches:async e=>(await n().get(`/branch?projectId=${e}`)).data,createBranch:async(e,s,t)=>(await n().post("/branch",{projectId:e,name:s,description:t})).data,deleteBranch:async e=>(await n().delete(`/branch/${e}`)).data,updateBranch:async(e,s)=>(await n().put("/branch",{id:e,...s})).data,getRoles:async()=>(await n().get("/admin/roles")).data,getUsers:async e=>{let s=e?`?teamId=${e}`:"";return(await n().get(`/admin/users${s}`)).data},setUserRole:async(e,s,t)=>(await n().put("/admin/users/role",{email:e,role:s,teamId:t})).data,getIntegrationStatus:async e=>(await n().get(`/integrations/${e}`)).data,getIntegrationRepos:async e=>(await n().get(`/integrations/${e}/sync`)).data.repos,syncSecretsToGithub:async e=>(await n().post("/integrations/github/sync",e)).data,exportKubernetesSecret:async(e,s)=>{let t=new URLSearchParams(s).toString();return(await n().get(`/projects/${e}/kubernetes?${t}`)).data},getSecretHistory:async(e,s,t)=>(await n().get(`/projects/${e}/envs/${s}/secrets/${t}/history`)).data,rollbackSecret:async(e,s,t,r)=>(await n().post(`/projects/${e}/envs/${s}/secrets/${t}/history`,{version:r})).data,cloneEnvironment:async(e,s,t,r,o)=>(await n().post(`/projects/${e}/envs/clone`,{fromEnv:s,toEnv:t,overwrite:r,branch:o})).data,generateJitLink:async e=>(await n().post("/jit/generate",e)).data,claimJitLink:async e=>(await n().post("/jit/claim",{token:e})).data,getJitInfo:async e=>(await n().get(`/jit/${e}`)).data,post:async(e,s)=>(await n().post(e,s)).data};var J=require("child_process"),a=i(require("chalk")),q=i(require("ora"));var F=new U.Command("jit-run").description("Claim a JIT link and run a command once approved").requiredOption("-t, --token <token>","JIT Access Token").option("-e, --env <environment>","Environment (development, staging, production)","development").argument("<command>","Command to run").argument("[args...]","Command arguments").action(async(e,s,t)=>{let{token:r,env:o}=t,p=C(),g=(0,q.default)("Claiming JIT link...").start();try{let d=await h.claimJitLink(r),k=d.requestId;g.text=`Access request submitted (ID: ${k}). Waiting for admin approval...`;let u=d.status||"pending",l="";for(;u==="pending";){await new Promise(P=>setTimeout(P,5e3));let y=await h.getAccessRequestStatus(k);u=y.status,l=y.projectId,u==="rejected"&&(g.fail(a.default.red("Access request rejected by admin.")),process.exit(1)),(u==="revoked"||u==="expired")&&(g.fail(a.default.red(`Access request ${u}.`)),process.exit(1))}g.succeed(a.default.green("Access approved! Fetching secrets...")),l||(l=p.project),l||(g.fail("Error: Could not determine project ID for JIT session."),process.exit(1));let m=await h.getSecrets(l,o);!m||Object.keys(m).length===0?console.warn(a.default.yellow(`
|
|
2
|
+
Warning: No secrets returned. Ensure JIT link allowed specific secrets.`)):console.log(a.default.cyan(`
|
|
3
|
+
\u221A Loaded ${Object.keys(m).length} JIT-authorized secrets.`));let L={...process.env,...m};console.log(a.default.gray(`> ${e} ${s.join(" ")}`)),(0,J.spawn)(e,s,{env:L,stdio:"inherit",shell:process.platform==="win32"}).on("exit",y=>{process.exit(y??0)})}catch(d){g.fail("JIT Run failed."),d.response?console.error(a.default.red(`
|
|
4
|
+
Error: ${d.response.data.error||d.message}`)):console.error(a.default.red(`
|
|
5
|
+
Error: ${d.message}`)),process.exit(1)}});0&&(module.exports={jitRunCommand});
|
package/dist/commands/local.js
CHANGED
|
@@ -1,198 +1,16 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.localCommand = void 0;
|
|
40
|
-
/**
|
|
41
|
-
* local.ts — Toggle between cloud/local-only mode for offline development
|
|
42
|
-
*
|
|
43
|
-
* In "local" mode:
|
|
44
|
-
* - xtra run reads from .env.local instead of calling the API
|
|
45
|
-
* - A flag XTRA_LOCAL_MODE=true is written to the CLI config
|
|
46
|
-
* - All API calls are bypassed — completely offline capable
|
|
47
|
-
*
|
|
48
|
-
* Usage:
|
|
49
|
-
* xtra local on # Enable local mode (reads from .env.local)
|
|
50
|
-
* xtra local off # Disable local mode (back to cloud)
|
|
51
|
-
* xtra local status # Show current mode
|
|
52
|
-
* xtra local sync # Pull cloud secrets and write to .env.local
|
|
53
|
-
*/
|
|
54
|
-
const commander_1 = require("commander");
|
|
55
|
-
const api_1 = require("../lib/api");
|
|
56
|
-
const config_1 = require("../lib/config");
|
|
57
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
58
|
-
const ora_1 = __importDefault(require("ora"));
|
|
59
|
-
const fs = __importStar(require("fs"));
|
|
60
|
-
const path = __importStar(require("path"));
|
|
61
|
-
const readline = __importStar(require("readline"));
|
|
62
|
-
const LOCAL_ENV_FILE = ".env.local";
|
|
63
|
-
function isLocalMode() {
|
|
64
|
-
return (0, config_1.getConfigValue)("localMode") === true
|
|
65
|
-
|| process.env.XTRA_LOCAL_MODE === "true";
|
|
66
|
-
}
|
|
67
|
-
exports.localCommand = new commander_1.Command("local")
|
|
68
|
-
.description("Toggle cloud/local mode for offline development")
|
|
69
|
-
.addHelpText("after", `
|
|
70
|
-
In local mode, 'xtra run' reads secrets from .env.local instead of the cloud API.
|
|
71
|
-
This allows fully offline development without any API calls.
|
|
72
|
-
|
|
73
|
-
Examples:
|
|
74
|
-
$ xtra local status # Check current mode
|
|
75
|
-
$ xtra local on # Enable offline (local) mode
|
|
76
|
-
$ xtra local off # Disable — back to cloud mode
|
|
77
|
-
$ xtra local sync # Pull cloud secrets → .env.local
|
|
78
|
-
$ xtra local sync -p proj -e production # Pull production to .env.local
|
|
79
|
-
`);
|
|
80
|
-
// ── status ────────────────────────────────────────────────────────────────────
|
|
81
|
-
exports.localCommand
|
|
82
|
-
.command("status")
|
|
83
|
-
.description("Show current cloud/local mode")
|
|
84
|
-
.action(() => {
|
|
85
|
-
const mode = isLocalMode();
|
|
86
|
-
const envFilePath = path.join(process.cwd(), LOCAL_ENV_FILE);
|
|
87
|
-
const hasLocalFile = fs.existsSync(envFilePath);
|
|
88
|
-
console.log(chalk_1.default.bold("\nMode Status:\n"));
|
|
89
|
-
console.log(` Mode : ${mode ? chalk_1.default.yellow("🔌 LOCAL (offline)") : chalk_1.default.green("☠CLOUD")}`);
|
|
90
|
-
console.log(` .env.local : ${hasLocalFile ? chalk_1.default.green("Found") : chalk_1.default.gray("Not found")}`);
|
|
91
|
-
console.log(` Config flag : ${chalk_1.default.gray((0, config_1.getConfigValue)("localMode") ? "true" : "false")}`);
|
|
92
|
-
console.log(` Env var : ${chalk_1.default.gray(process.env.XTRA_LOCAL_MODE || "(not set)")}`);
|
|
93
|
-
console.log();
|
|
94
|
-
if (!mode) {
|
|
95
|
-
console.log(chalk_1.default.gray(" Run 'xtra local on' to switch to offline mode."));
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
console.log(chalk_1.default.gray(" Run 'xtra local off' to switch back to cloud mode."));
|
|
99
|
-
if (!hasLocalFile) {
|
|
100
|
-
console.log(chalk_1.default.yellow(" âš No .env.local file found. Run 'xtra local sync' to pull secrets."));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
console.log();
|
|
104
|
-
});
|
|
105
|
-
// ── on ────────────────────────────────────────────────────────────────────────
|
|
106
|
-
exports.localCommand
|
|
107
|
-
.command("on")
|
|
108
|
-
.description("Enable local mode — secrets read from .env.local")
|
|
109
|
-
.action(() => {
|
|
110
|
-
(0, config_1.setConfig)("localMode", true);
|
|
111
|
-
console.log(chalk_1.default.yellow("🔌 Local mode ENABLED."));
|
|
112
|
-
console.log(chalk_1.default.gray(" 'xtra run' will now read from .env.local instead of the cloud."));
|
|
113
|
-
const localFilePath = path.join(process.cwd(), LOCAL_ENV_FILE);
|
|
114
|
-
if (!fs.existsSync(localFilePath)) {
|
|
115
|
-
console.log(chalk_1.default.yellow(` âš No .env.local file found. Run 'xtra local sync' to populate it.`));
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
// ── off ───────────────────────────────────────────────────────────────────────
|
|
119
|
-
exports.localCommand
|
|
120
|
-
.command("off")
|
|
121
|
-
.description("Disable local mode — secrets fetched from cloud again")
|
|
122
|
-
.action(() => {
|
|
123
|
-
(0, config_1.setConfig)("localMode", false);
|
|
124
|
-
console.log(chalk_1.default.green("☠Cloud mode ENABLED."));
|
|
125
|
-
console.log(chalk_1.default.gray(" 'xtra run' will now fetch secrets from XtraSecurity Cloud."));
|
|
126
|
-
});
|
|
127
|
-
// ── sync ─────────────────────────────────────────────────────────────────────
|
|
128
|
-
exports.localCommand
|
|
129
|
-
.command("sync")
|
|
130
|
-
.description("Pull cloud secrets to .env.local for offline use")
|
|
131
|
-
.option("-p, --project <id>", "Project ID")
|
|
132
|
-
.option("-e, --env <environment>", "Environment", "development")
|
|
133
|
-
.option("-b, --branch <branch>", "Branch", "main")
|
|
134
|
-
.option("-o, --output <file>", "Output file", LOCAL_ENV_FILE)
|
|
135
|
-
.option("--overwrite", "Overwrite existing file without prompt", false)
|
|
136
|
-
.action(async (options) => {
|
|
137
|
-
let { project, env, branch, output, overwrite } = options;
|
|
138
|
-
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
139
|
-
env = envMap[env] || env;
|
|
140
|
-
if (!project)
|
|
141
|
-
project = (0, config_1.getRcConfig)().project;
|
|
142
|
-
if (!project) {
|
|
143
|
-
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id>."));
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
const outputPath = path.resolve(process.cwd(), output);
|
|
147
|
-
// Warn if production
|
|
148
|
-
if (env === "production" && !overwrite) {
|
|
149
|
-
console.log(chalk_1.default.red(`âš You are syncing PRODUCTION secrets to ${output}.`));
|
|
150
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
151
|
-
const confirmed = await new Promise(resolve => {
|
|
152
|
-
rl.question(chalk_1.default.yellow("Type 'yes' to confirm: "), (ans) => {
|
|
153
|
-
rl.close();
|
|
154
|
-
resolve(ans.trim().toLowerCase() === "yes");
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
if (!confirmed) {
|
|
158
|
-
console.log(chalk_1.default.gray("Sync cancelled."));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const spinner = (0, ora_1.default)(`Fetching ${env} secrets from cloud...`).start();
|
|
163
|
-
try {
|
|
164
|
-
const secrets = await api_1.api.getSecrets(project, env, branch);
|
|
165
|
-
spinner.stop();
|
|
166
|
-
const count = Object.keys(secrets).length;
|
|
167
|
-
if (count === 0) {
|
|
168
|
-
console.log(chalk_1.default.yellow("No secrets found."));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
// Write dotenv format
|
|
172
|
-
const lines = [
|
|
173
|
-
`# xtra local sync — ${env}/${branch}`,
|
|
174
|
-
`# Generated: ${new Date().toISOString()}`,
|
|
175
|
-
`# DO NOT COMMIT THIS FILE`,
|
|
176
|
-
"",
|
|
177
|
-
...Object.entries(secrets).map(([k, v]) => `${k}=${v}`)
|
|
178
|
-
];
|
|
179
|
-
fs.writeFileSync(outputPath, lines.join("\n") + "\n", "utf8");
|
|
180
|
-
console.log(chalk_1.default.green(`✅ Synced ${count} secrets to ${output}`));
|
|
181
|
-
console.log(chalk_1.default.gray(" Run 'xtra local on' to switch to local mode."));
|
|
182
|
-
// Remind about .gitignore
|
|
183
|
-
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
184
|
-
let gitignore = "";
|
|
185
|
-
try {
|
|
186
|
-
gitignore = fs.readFileSync(gitignorePath, "utf8");
|
|
187
|
-
}
|
|
188
|
-
catch (_) { }
|
|
189
|
-
if (!gitignore.includes(output)) {
|
|
190
|
-
console.log(chalk_1.default.yellow(`\n âš Remember to add '${output}' to your .gitignore!`));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
spinner.fail("Sync failed.");
|
|
195
|
-
console.error(chalk_1.default.red(e?.response?.data?.error || e.message));
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
1
|
+
var G=Object.create;var m=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var s in t)m(e,s,{get:t[s],enumerable:!0})},L=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of V(t))!H.call(e,r)&&r!==s&&m(e,r,{get:()=>t[r],enumerable:!(o=J(t,r))||o.enumerable});return e};var i=(e,t,s)=>(s=e!=null?G(X(e)):{},L(t||!e||!e.__esModule?m(s,"default",{value:e,enumerable:!0}):s,e)),K=e=>L(m({},"__esModule",{value:!0}),e);var W={};z(W,{localCommand:()=>f});module.exports=K(W);var E=require("commander");var U=i(require("axios"));var R=i(require("conf")),u=i(require("fs")),P=i(require("path")),x="https://www.xtrasecurity.in/api",c=new R.default({projectName:"xtra-cli",defaults:{apiUrl:x}}),$=c.get("apiUrl");$&&!process.env.XTRA_API_URL&&($.includes("localhost")||$.includes("xtra-security.vercel.app"))&&c.set("apiUrl",x);var y=()=>({...c.store,apiUrl:process.env.XTRA_API_URL||c.get("apiUrl")||x}),j=e=>c.get(e),S=(e,t)=>{if(c.set(e,t),e==="token"&&process.platform!=="win32"&&u.default.existsSync(c.path))try{u.default.chmodSync(c.path,384)}catch{}};var A=()=>c.get("token");var O=()=>{let e={};try{let t=P.default.join(process.cwd(),".xtrarc");u.default.existsSync(t)&&(e=JSON.parse(u.default.readFileSync(t,"utf-8")))}catch{}return{project:e.project||c.get("project")||"",env:e.env||c.get("env")||"development",branch:e.branch||c.get("branch")||"main"}};var T=i(require("fs")),_=i(require("path")),Y=JSON.parse(T.readFileSync(_.join(__dirname,"../../package.json"),"utf-8")),n=()=>{let{apiUrl:e}=y(),t=A();return U.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":Y.version,...t?{Authorization:`Bearer ${t}`}:{}}})},D={login:async(e,t,s)=>(await n().post("/auth/cli-login",{email:e,password:t,apiKey:s})).data,getSecrets:async(e,t,s="main")=>(await n().get(`/projects/${e}/envs/${t}/secrets?branch=${s}`)).data,getSecretVersions:async(e,t,s="main")=>(await n().get(`/projects/${e}/envs/${t}/secrets?includeVersions=true&branch=${s}`)).data,setSecrets:async(e,t,s,o,r="main")=>(await n().post(`/projects/${e}/envs/${t}/secrets`,{secrets:s,expectedVersions:o,branch:r})).data,getSecretDetails:async(e,t,s,o="main")=>(await n().get(`/projects/${e}/envs/${t}/secrets/${s}?branch=${o}`)).data,syncLogs:async e=>(await n().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,t,s,o,r,p)=>(await n().post(`/projects/${e}/envs/${t}/secrets/link`,{key:s,sourceProjectId:o,sourceEnv:r,sourceKey:p})).data,rotateSecret:async(e,t,s,o,r)=>(await n().post(`/projects/${e}/envs/${t}/secrets/${s}/rotate`,{strategy:o,parsedNewValue:r})).data,promoteSecret:async(e,t,s)=>(await n().post(`/projects/${e}/envs/${t}/secrets/${s}/promote`,{})).data,verifyAuditLogs:async()=>(await n().get("/audit/verify")).data,exportAuditLogs:async(e,t,s,o)=>{let r={format:e};return t&&(r.startDate=t),s&&(r.endDate=s),o&&(r.projectId=o),(await n().post("/audit/export",r,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,t,s,o)=>(await n().post("/access/request",{projectId:e,secretId:o,reason:t,duration:s})).data,approveAccess:async(e,t)=>(await n().post("/access/approve",{requestId:e,decision:t})).data,listAccessRequests:async e=>{let{workspace:t}=y(),s=t?`&workspaceId=${t}`:"";return(await n().get(`/access/list?mode=${e}${s}`)).data},getAccessRequestStatus:async e=>(await n().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=y(),t=e?`/project?workspaceId=${e}`:"/project";return(await n().get(t)).data},getBranches:async e=>(await n().get(`/branch?projectId=${e}`)).data,createBranch:async(e,t,s)=>(await n().post("/branch",{projectId:e,name:t,description:s})).data,deleteBranch:async e=>(await n().delete(`/branch/${e}`)).data,updateBranch:async(e,t)=>(await n().put("/branch",{id:e,...t})).data,getRoles:async()=>(await n().get("/admin/roles")).data,getUsers:async e=>{let t=e?`?teamId=${e}`:"";return(await n().get(`/admin/users${t}`)).data},setUserRole:async(e,t,s)=>(await n().put("/admin/users/role",{email:e,role:t,teamId:s})).data,getIntegrationStatus:async e=>(await n().get(`/integrations/${e}`)).data,getIntegrationRepos:async e=>(await n().get(`/integrations/${e}/sync`)).data.repos,syncSecretsToGithub:async e=>(await n().post("/integrations/github/sync",e)).data,exportKubernetesSecret:async(e,t)=>{let s=new URLSearchParams(t).toString();return(await n().get(`/projects/${e}/kubernetes?${s}`)).data},getSecretHistory:async(e,t,s)=>(await n().get(`/projects/${e}/envs/${t}/secrets/${s}/history`)).data,rollbackSecret:async(e,t,s,o)=>(await n().post(`/projects/${e}/envs/${t}/secrets/${s}/history`,{version:o})).data,cloneEnvironment:async(e,t,s,o,r)=>(await n().post(`/projects/${e}/envs/clone`,{fromEnv:t,toEnv:s,overwrite:o,branch:r})).data,generateJitLink:async e=>(await n().post("/jit/generate",e)).data,claimJitLink:async e=>(await n().post("/jit/claim",{token:e})).data,getJitInfo:async e=>(await n().get(`/jit/${e}`)).data,post:async(e,t)=>(await n().post(e,t)).data};var a=i(require("chalk")),N=i(require("ora")),g=i(require("fs")),d=i(require("path")),M=i(require("readline")),b=".env.local";function Q(){return j("localMode")===!0||process.env.XTRA_LOCAL_MODE==="true"}var f=new E.Command("local").description("Toggle cloud/local mode for offline development").addHelpText("after",`
|
|
2
|
+
In local mode, 'xtra run' reads secrets from .env.local instead of the cloud API.
|
|
3
|
+
This allows fully offline development without any API calls.
|
|
4
|
+
|
|
5
|
+
Examples:
|
|
6
|
+
$ xtra local status # Check current mode
|
|
7
|
+
$ xtra local on # Enable offline (local) mode
|
|
8
|
+
$ xtra local off # Disable \xE2\u20AC\u201D back to cloud mode
|
|
9
|
+
$ xtra local sync # Pull cloud secrets \xE2\u2020\u2019 .env.local
|
|
10
|
+
$ xtra local sync -p proj -e production # Pull production to .env.local
|
|
11
|
+
`);f.command("status").description("Show current cloud/local mode").action(()=>{let e=Q(),t=d.join(process.cwd(),b),s=g.existsSync(t);console.log(a.default.bold(`
|
|
12
|
+
Mode Status:
|
|
13
|
+
`)),console.log(` Mode : ${e?a.default.yellow("\u{1F50C} LOCAL (offline)"):a.default.green("\u2601 CLOUD")}`),console.log(` .env.local : ${s?a.default.green("Found"):a.default.gray("Not found")}`),console.log(` Config flag : ${a.default.gray(j("localMode")?"true":"false")}`),console.log(` Env var : ${a.default.gray(process.env.XTRA_LOCAL_MODE||"(not set)")}`),console.log(),e?(console.log(a.default.gray(" Run 'xtra local off' to switch back to cloud mode.")),s||console.log(a.default.yellow(" \u26A0 No .env.local file found. Run 'xtra local sync' to pull secrets."))):console.log(a.default.gray(" Run 'xtra local on' to switch to offline mode.")),console.log()});f.command("on").description("Enable local mode \xE2\u20AC\u201D secrets read from .env.local").action(()=>{S("localMode",!0),console.log(a.default.yellow("\u{1F50C} Local mode ENABLED.")),console.log(a.default.gray(" 'xtra run' will now read from .env.local instead of the cloud."));let e=d.join(process.cwd(),b);g.existsSync(e)||console.log(a.default.yellow(" \u26A0 No .env.local file found. Run 'xtra local sync' to populate it."))});f.command("off").description("Disable local mode \xE2\u20AC\u201D secrets fetched from cloud again").action(()=>{S("localMode",!1),console.log(a.default.green("\xE2\u02DC\x81 Cloud mode ENABLED.")),console.log(a.default.gray(" 'xtra run' will now fetch secrets from XtraSecurity Cloud."))});f.command("sync").description("Pull cloud secrets to .env.local for offline use").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment","development").option("-b, --branch <branch>","Branch","main").option("-o, --output <file>","Output file",b).option("--overwrite","Overwrite existing file without prompt",!1).action(async e=>{let{project:t,env:s,branch:o,output:r,overwrite:p}=e;s={dev:"development",stg:"staging",prod:"production"}[s]||s,t||(t=O().project),t||(console.error(a.default.red("Error: Project ID required. Use -p <id>.")),process.exit(1));let B=d.resolve(process.cwd(),r);if(s==="production"&&!p){console.log(a.default.red(`\u26A0 You are syncing PRODUCTION secrets to ${r}.`));let l=M.createInterface({input:process.stdin,output:process.stdout});if(!await new Promise(h=>{l.question(a.default.yellow("Type 'yes' to confirm: "),v=>{l.close(),h(v.trim().toLowerCase()==="yes")})})){console.log(a.default.gray("Sync cancelled."));return}}let C=(0,N.default)(`Fetching ${s} secrets from cloud...`).start();try{let l=await D.getSecrets(t,s,o);C.stop();let w=Object.keys(l).length;if(w===0){console.log(a.default.yellow("No secrets found."));return}let h=[`# xtra local sync \xE2\u20AC\u201D ${s}/${o}`,`# Generated: ${new Date().toISOString()}`,"# DO NOT COMMIT THIS FILE","",...Object.entries(l).map(([I,q])=>`${I}=${q}`)];g.writeFileSync(B,h.join(`
|
|
14
|
+
`)+`
|
|
15
|
+
`,"utf8"),console.log(a.default.green(`\u2705 Synced ${w} secrets to ${r}`)),console.log(a.default.gray(" Run 'xtra local on' to switch to local mode."));let v=d.join(process.cwd(),".gitignore"),k="";try{k=g.readFileSync(v,"utf8")}catch{}k.includes(r)||console.log(a.default.yellow(`
|
|
16
|
+
\u26A0 Remember to add '${r}' to your .gitignore!`))}catch(l){C.fail("Sync failed."),console.error(a.default.red(l?.response?.data?.error||l.message)),process.exit(1)}});0&&(module.exports={localCommand});
|