xtra-cli 1.0.4 → 1.0.6

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.
Files changed (45) hide show
  1. package/dist/bin/xtra.js +2 -294
  2. package/dist/commands/access.js +1 -184
  3. package/dist/commands/admin.js +5 -118
  4. package/dist/commands/audit.js +1 -67
  5. package/dist/commands/branch.js +1 -212
  6. package/dist/commands/checkout.js +1 -80
  7. package/dist/commands/ci.js +31 -341
  8. package/dist/commands/completion.js +85 -224
  9. package/dist/commands/diff.js +6 -162
  10. package/dist/commands/doctor.js +2 -164
  11. package/dist/commands/env.js +2 -70
  12. package/dist/commands/export.js +2 -83
  13. package/dist/commands/generate.js +3 -179
  14. package/dist/commands/history.js +4 -77
  15. package/dist/commands/import.js +1 -121
  16. package/dist/commands/init.js +7 -205
  17. package/dist/commands/integration.js +11 -188
  18. package/dist/commands/jit-run.js +5 -0
  19. package/dist/commands/local.js +16 -198
  20. package/dist/commands/login.js +16 -198
  21. package/dist/commands/login.test.js +16 -51
  22. package/dist/commands/logout.js +1 -0
  23. package/dist/commands/logs.js +3 -121
  24. package/dist/commands/profile.js +14 -184
  25. package/dist/commands/project.js +1 -165
  26. package/dist/commands/rollback.js +1 -95
  27. package/dist/commands/rotate.js +3 -93
  28. package/dist/commands/run.js +11 -215
  29. package/dist/commands/scan.js +25 -127
  30. package/dist/commands/secrets.js +14 -305
  31. package/dist/commands/service-account.js +7 -0
  32. package/dist/commands/simulate.js +6 -109
  33. package/dist/commands/status.js +2 -93
  34. package/dist/commands/template.js +22 -276
  35. package/dist/commands/ui.js +2 -289
  36. package/dist/commands/watch.js +17 -123
  37. package/dist/lib/api.js +1 -187
  38. package/dist/lib/api.test.js +1 -89
  39. package/dist/lib/audit.js +1 -136
  40. package/dist/lib/config.js +1 -72
  41. package/dist/lib/config.test.js +1 -47
  42. package/dist/lib/crypto.js +1 -50
  43. package/dist/lib/manifest.js +1 -52
  44. package/dist/lib/profiles.js +1 -103
  45. package/package.json +2 -2
@@ -1,215 +1,11 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
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.runCommand = void 0;
40
- const commander_1 = require("commander");
41
- const api_1 = require("../lib/api");
42
- const child_process_1 = require("child_process");
43
- const chalk_1 = __importDefault(require("chalk"));
44
- const ora_1 = __importDefault(require("ora"));
45
- const config_1 = require("../lib/config");
46
- const crypto_1 = require("../lib/crypto");
47
- const fs = __importStar(require("fs"));
48
- const path = __importStar(require("path"));
49
- const dotenv_1 = __importDefault(require("dotenv"));
50
- exports.runCommand = new commander_1.Command("run")
51
- .description("Run a command with injected secrets")
52
- .option("-p, --project <projectId>", "Project ID")
53
- .option("-e, --env <environment>", "Environment (development, staging, production)", "development")
54
- .option("-b, --branch <branchName>", "Branch Name")
55
- .option("--shell", "Enable shell mode (needed for npm run, shell built-ins on Windows)", false)
56
- .argument("<command>", "Command to run")
57
- .argument("[args...]", "Command arguments")
58
- .addHelpText("after", `
59
- Examples:
60
- $ xtra run npm start
61
- $ xtra run -e production -- npm run build
62
- $ xtra run -p proj_123 -b feature-branch -- python script.py
63
- $ xtra run --shell "echo $SECRET_KEY"
64
- `)
65
- .action(async (command, args, options) => {
66
- // console.log("Run Options:", options);
67
- let { project, env, branch, shell: useShell } = options;
68
- // Load .xtrarc from CWD first (project-local config), then fall back to global conf store
69
- const rc = (0, config_1.getRcConfig)();
70
- // Use active branch from config if not specified
71
- if (!branch)
72
- branch = rc.branch;
73
- // Normalize Env
74
- const envMap = { dev: "development", stg: "staging", prod: "production" };
75
- env = envMap[env] || env || rc.env;
76
- if (!project)
77
- project = rc.project;
78
- if (!project) {
79
- console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or checkout a branch."));
80
- process.exit(1);
81
- }
82
- const spinner = (0, ora_1.default)(`Fetching secrets for ${env} (branch: ${branch})...`).start();
83
- let secrets = null;
84
- // ── Local mode override ──────────────────────────────────────────────────
85
- // If XTRA_LOCAL_MODE=true or localMode config flag is set,
86
- // read from .env.local instead of calling the API.
87
- const isLocal = (0, config_1.getConfigValue)("localMode") === true
88
- || process.env.XTRA_LOCAL_MODE === "true";
89
- if (isLocal) {
90
- const localFile = path.resolve(process.cwd(), ".env.local");
91
- if (!fs.existsSync(localFile)) {
92
- spinner.fail("Local mode is ON but .env.local not found. Run 'xtra local sync' first.");
93
- process.exit(1);
94
- }
95
- const parsed = dotenv_1.default.parse(fs.readFileSync(localFile, "utf8"));
96
- secrets = parsed;
97
- spinner.succeed(chalk_1.default.yellow(`🔌 Local mode: loaded ${Object.keys(secrets).length} secrets from .env.local`));
98
- }
99
- else {
100
- try {
101
- // 1. Fetch Secrets
102
- secrets = await api_1.api.getSecrets(project, env, branch);
103
- if (!secrets || Object.keys(secrets).length === 0) {
104
- spinner.warn(chalk_1.default.yellow("No secrets found for this environment."));
105
- }
106
- else {
107
- spinner.succeed(`Loaded ${Object.keys(secrets).length} secrets (Online).`);
108
- // Update manifest
109
- try {
110
- const { hash } = require("../lib/crypto");
111
- const { updateManifest } = require("../lib/manifest");
112
- updateManifest(project, env, secrets, hash);
113
- }
114
- catch (mErr) {
115
- // Manifest update failed
116
- }
117
- // Cache secrets locally
118
- try {
119
- const cacheKey = `cache.${project}.${env}.${branch}`;
120
- const encrypted = (0, crypto_1.encrypt)(JSON.stringify(secrets));
121
- (0, config_1.setConfig)(cacheKey, encrypted);
122
- // console.log(chalk.gray("Secrets cached successfully."));
123
- }
124
- catch (cacheErr) {
125
- console.error(chalk_1.default.yellow("Warning: Failed to cache secrets."));
126
- }
127
- }
128
- }
129
- catch (error) {
130
- // Offline fallback
131
- const cacheKey = `cache.${project}.${env}.${branch}`;
132
- const cachedData = (0, config_1.getConfigValue)(cacheKey);
133
- if (cachedData) {
134
- try {
135
- const decrypted = (0, crypto_1.decrypt)(cachedData);
136
- secrets = JSON.parse(decrypted);
137
- spinner.warn(chalk_1.default.yellow(`Offline Mode: Loaded ${Object.keys(secrets).length} secrets from cache.`));
138
- }
139
- catch (decryptErr) {
140
- spinner.fail("Failed to fetch secrets and cache is corrupt.");
141
- process.exit(1);
142
- }
143
- }
144
- else {
145
- spinner.fail("Failed to fetch secrets and no local cache available.");
146
- if (error.response) {
147
- if (error.response.status === 404) {
148
- console.error(chalk_1.default.red(`\nError: Project '${project}' not found or you don't have access.`));
149
- }
150
- else if (error.response.status === 401) {
151
- console.error(chalk_1.default.red(`\nError: Unauthorized. Please run 'xtra login' again.`));
152
- }
153
- else {
154
- console.error(chalk_1.default.red(`\nError: ${error.response.data.error || error.message}`));
155
- }
156
- }
157
- else {
158
- console.error(chalk_1.default.red(`\nError: ${error.message}`));
159
- }
160
- process.exit(1);
161
- }
162
- } // end try/catch (cloud mode)
163
- } // end else (cloud mode)
164
- // Log Audit (Always)
165
- try {
166
- const { logAudit } = require("../lib/audit");
167
- // Only log if we have secrets
168
- if (secrets && Object.keys(secrets).length > 0) {
169
- logAudit("SECRET_ACCESS", project, env, { method: "run", keys: Object.keys(secrets), branch });
170
- }
171
- }
172
- catch (e) {
173
- console.error("Audit Error:", e);
174
- }
175
- // 2. Prepare Environment
176
- const envVars = {
177
- ...process.env,
178
- ...secrets, // Overwrite local env with injected secrets
179
- };
180
- // 3. Spawn Child Process — shell: false by default to prevent command injection
181
- // When --shell is passed (e.g. for npm run start on Windows), allow shell mode
182
- const SHELL_UNSAFE = /[;&|`$<>\\\n]/g;
183
- if (!useShell && SHELL_UNSAFE.test(command)) {
184
- console.error(chalk_1.default.red("Error: Command contains unsafe characters. Use --shell if intentional."));
185
- process.exit(1);
186
- }
187
- // Production confirmation gate
188
- if (env === "production") {
189
- const inquirer = require("inquirer");
190
- const { confirm } = await inquirer.prompt([{
191
- type: "confirm",
192
- name: "confirm",
193
- message: chalk_1.default.red(`⚠ You are about to run a command in PRODUCTION with ${Object.keys(secrets || {}).length} injected secrets. Continue?`),
194
- default: false,
195
- }]);
196
- if (!confirm) {
197
- console.log(chalk_1.default.yellow("Aborted."));
198
- return;
199
- }
200
- }
201
- console.log(chalk_1.default.gray(`> ${command} ${args.join(" ")}`));
202
- const isWindows = process.platform === "win32";
203
- const child = (0, child_process_1.spawn)(command, args, {
204
- env: envVars,
205
- stdio: "inherit",
206
- shell: useShell || isWindows, // Auto-enable shell on Windows for better compatibility (e.g. npm, etc)
207
- });
208
- child.on("exit", (code) => {
209
- process.exit(code ?? 0);
210
- });
211
- child.on("error", (err) => {
212
- console.error(chalk_1.default.red(`Failed to start command: ${err.message}`));
213
- process.exit(1);
214
- });
215
- });
1
+ var ke=Object.create;var O=Object.defineProperty;var Ae=Object.getOwnPropertyDescriptor;var Ie=Object.getOwnPropertyNames;var Ee=Object.getPrototypeOf,Ce=Object.prototype.hasOwnProperty;var $=(e,t)=>()=>(e&&(t=e(e=0)),t);var b=(e,t)=>{for(var s in t)O(e,s,{get:t[s],enumerable:!0})},Q=(e,t,s,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Ie(t))!Ce.call(e,n)&&n!==s&&O(e,n,{get:()=>t[n],enumerable:!(r=Ae(t,n))||r.enumerable});return e};var p=(e,t,s)=>(s=e!=null?ke(Ee(e)):{},Q(t||!e||!e.__esModule?O(s,"default",{value:e,enumerable:!0}):s,e)),k=e=>Q(O({},"__esModule",{value:!0}),e);var ee={};b(ee,{clearConfig:()=>Oe,deleteConfig:()=>Re,getAuthToken:()=>q,getBreakGlassSession:()=>Le,getConfig:()=>h,getConfigValue:()=>R,getProjectConfig:()=>Te,getRcConfig:()=>K,setBreakGlassSession:()=>Ne,setConfig:()=>J});var Z,m,G,B,g,M,h,R,J,Oe,Re,q,Le,Ne,Te,K,A=$(()=>{Z=p(require("conf")),m=p(require("fs")),G=p(require("path")),B="https://www.xtrasecurity.in/api",g=new Z.default({projectName:"xtra-cli",defaults:{apiUrl:B}}),M=g.get("apiUrl");M&&!process.env.XTRA_API_URL&&(M.includes("localhost")||M.includes("xtra-security.vercel.app"))&&g.set("apiUrl",B);h=()=>({...g.store,apiUrl:process.env.XTRA_API_URL||g.get("apiUrl")||B}),R=e=>g.get(e),J=(e,t)=>{if(g.set(e,t),e==="token"&&process.platform!=="win32"&&m.default.existsSync(g.path))try{m.default.chmodSync(g.path,384)}catch{}},Oe=()=>g.clear(),Re=e=>g.delete(e),q=()=>g.get("token"),Le=()=>g.get("breakGlassSessionId"),Ne=e=>{e?g.set("breakGlassSessionId",e):g.delete("breakGlassSessionId")},Te=async()=>{try{let e=G.default.join(process.cwd(),"xtra.json");if(m.default.existsSync(e)){let t=m.default.readFileSync(e,"utf-8");return JSON.parse(t)}return null}catch{return null}},K=()=>{let e={};try{let t=G.default.join(process.cwd(),".xtrarc");m.default.existsSync(t)&&(e=JSON.parse(m.default.readFileSync(t,"utf-8")))}catch{}return{project:e.project||g.get("project")||"",env:e.env||g.get("env")||"development",branch:e.branch||g.get("branch")||"main"}}});var te,se,re,Ue,o,L,H=$(()=>{te=p(require("axios"));A();se=p(require("fs")),re=p(require("path")),Ue=JSON.parse(se.readFileSync(re.join(__dirname,"../../package.json"),"utf-8")),o=()=>{let{apiUrl:e}=h(),t=q();return te.default.create({baseURL:e,headers:{"Content-Type":"application/json","X-CLI-Version":Ue.version,...t?{Authorization:`Bearer ${t}`}:{}}})},L={login:async(e,t,s)=>(await o().post("/auth/cli-login",{email:e,password:t,apiKey:s})).data,getSecrets:async(e,t,s="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets?branch=${s}`)).data,getSecretVersions:async(e,t,s="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets?includeVersions=true&branch=${s}`)).data,setSecrets:async(e,t,s,r,n="main")=>(await o().post(`/projects/${e}/envs/${t}/secrets`,{secrets:s,expectedVersions:r,branch:n})).data,getSecretDetails:async(e,t,s,r="main")=>(await o().get(`/projects/${e}/envs/${t}/secrets/${s}?branch=${r}`)).data,syncLogs:async e=>(await o().post("/audit/cli-logs",{logs:e})).data,linkSecret:async(e,t,s,r,n,i)=>(await o().post(`/projects/${e}/envs/${t}/secrets/link`,{key:s,sourceProjectId:r,sourceEnv:n,sourceKey:i})).data,rotateSecret:async(e,t,s,r,n)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${s}/rotate`,{strategy:r,parsedNewValue:n})).data,promoteSecret:async(e,t,s)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${s}/promote`,{})).data,verifyAuditLogs:async()=>(await o().get("/audit/verify")).data,exportAuditLogs:async(e,t,s,r)=>{let n={format:e};return t&&(n.startDate=t),s&&(n.endDate=s),r&&(n.projectId=r),(await o().post("/audit/export",n,{responseType:e==="csv"?"text":"json"})).data},requestAccess:async(e,t,s,r)=>(await o().post("/access/request",{projectId:e,secretId:r,reason:t,duration:s})).data,approveAccess:async(e,t)=>(await o().post("/access/approve",{requestId:e,decision:t})).data,listAccessRequests:async e=>{let{workspace:t}=h(),s=t?`&workspaceId=${t}`:"";return(await o().get(`/access/list?mode=${e}${s}`)).data},getAccessRequestStatus:async e=>(await o().get(`/access/request?id=${e}`)).data,getProjects:async()=>{let{workspace:e}=h(),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,s)=>(await o().post("/branch",{projectId:e,name:t,description:s})).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,s)=>(await o().put("/admin/users/role",{email:e,role:t,teamId:s})).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 s=new URLSearchParams(t).toString();return(await o().get(`/projects/${e}/kubernetes?${s}`)).data},getSecretHistory:async(e,t,s)=>(await o().get(`/projects/${e}/envs/${t}/secrets/${s}/history`)).data,rollbackSecret:async(e,t,s,r)=>(await o().post(`/projects/${e}/envs/${t}/secrets/${s}/history`,{version:r})).data,cloneEnvironment:async(e,t,s,r,n)=>(await o().post(`/projects/${e}/envs/clone`,{fromEnv:t,toEnv:s,overwrite:r,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,getServiceAccounts:async e=>(await o().get(`/projects/${e}/service-accounts`)).data,createServiceAccount:async(e,t)=>(await o().post(`/projects/${e}/service-accounts`,t)).data,deleteServiceAccount:async(e,t)=>(await o().delete(`/projects/${e}/service-accounts/${t}`)).data,getServiceAccountKeys:async(e,t)=>(await o().get(`/projects/${e}/service-accounts/${t}/keys`)).data,createServiceAccountKey:async(e,t,s,r)=>(await o().post(`/projects/${e}/service-accounts/${t}/keys`,{label:s,expiresAt:r})).data,deleteServiceAccountKey:async(e,t,s)=>(await o().delete(`/projects/${e}/service-accounts/${t}/keys/${s}`)).data,post:async(e,t)=>(await o().post(e,t)).data}});var pe={};b(pe,{decrypt:()=>E,encrypt:()=>I,hash:()=>De});function ae(){let e=ne.get("masterKey");e||(e=S.default.randomBytes(32).toString("hex"),ne.set("masterKey",e));let t=(0,ce.machineIdSync)();return S.default.createHash("sha256").update(e+t).digest()}var S,oe,ce,ie,ne,I,E,De,N=$(()=>{S=p(require("crypto")),oe=p(require("conf")),ce=require("node-machine-id"),ie="aes-256-gcm",ne=new oe.default({projectName:"xtra-cli-crypto"});I=e=>{let t=S.default.randomBytes(16),s=ae(),r=S.default.createCipheriv(ie,s,t),n=r.update(e,"utf8","hex");n+=r.final("hex");let i=r.getAuthTag();return{iv:t.toString("hex"),content:n,tag:i.toString("hex")}},E=e=>{let t=ae(),s=Buffer.from(e.iv,"hex"),r=Buffer.from(e.tag,"hex"),n=S.default.createDecipheriv(ie,t,s);n.setAuthTag(r);let i=n.update(e.content,"hex","utf8");return i+=n.final("utf8"),i},De=e=>S.default.createHash("sha256").update(e).digest("hex")});var ue={};b(ue,{MANIFEST_DIR:()=>T,MANIFEST_FILE:()=>U,loadManifest:()=>ge,saveManifest:()=>de,updateManifest:()=>Fe});function ge(){if(!x.default.existsSync(U))return null;try{let e=x.default.readFileSync(U,"utf-8");return JSON.parse(e)}catch{return null}}function de(e){x.default.existsSync(T)||x.default.mkdirSync(T,{recursive:!0}),x.default.writeFileSync(U,JSON.stringify(e,null,2),"utf-8")}function Fe(e,t,s,r){let n=ge()||{projectId:e,environment:t,lastSyncedAt:new Date().toISOString(),secrets:{}};n.projectId=e,n.environment=t,n.lastSyncedAt=new Date().toISOString(),Object.entries(s).forEach(([i,y])=>{let l=r(y);n.secrets[i]={hash:l,updatedAt:new Date().toISOString()}}),de(n)}var x,V,T,U,le=$(()=>{x=p(require("fs")),V=p(require("path")),T=V.default.join(process.cwd(),".xtra"),U=V.default.join(T,"manifest.json")});var me={};b(me,{loadAuditLogs:()=>D,logAudit:()=>Pe,saveAuditLogs:()=>F,syncAuditLogs:()=>Me});function _e(e){if(!e||typeof e!="object")return e;let t={...e},s=["password","token","secret","value","apiKey","secretValue"];for(let r of Object.keys(t))s.some(n=>r.toLowerCase().includes(n))&&(t[r]="[REDACTED]");return t}async function ye(e){try{if(!h().token)return!1;let s={action:e.action,entity:"cli-project",entityId:e.projectId||"global",details:_e(e.details),timestamp:e.timestamp,projectId:e.projectId};return await L.post("/audit/cli-logs",{logs:[s]}),!0}catch(t){return process.env.DEBUG&&console.error("Failed to send audit log to backend:",t),!1}}function Pe(e,t,s,r){let n={id:(0,fe.v4)(),timestamp:new Date().toISOString(),action:e,projectId:t,environment:s,details:r,synced:!1},i=D();i.push(n),F(i),ye(n).then(y=>{if(y){n.synced=!0;let l=D(),P=l.findIndex(u=>u.id===n.id);P!==-1&&(l[P].synced=!0,F(l))}}).catch(()=>{})}function D(){if(!j.default.existsSync(X))return[];try{let e=j.default.readFileSync(X,"utf-8"),t=JSON.parse(e),s=E(t);return JSON.parse(s)}catch{return[]}}function F(e){j.default.existsSync(z)||j.default.mkdirSync(z,{recursive:!0});let t=JSON.stringify(e),s=I(t);j.default.writeFileSync(X,JSON.stringify(s),"utf-8")}async function Me(){let e=D(),t=e.filter(r=>!r.synced);if(t.length===0)return 0;let s=0;for(let r of t)await ye(r)&&(r.synced=!0,s++);return s>0&&F(e),s}var j,W,fe,z,X,he=$(()=>{j=p(require("fs")),W=p(require("path")),fe=require("uuid");N();A();H();z=W.default.join(process.cwd(),".xtra"),X=W.default.join(z,"audit.enc")});var Je={};b(Je,{runCommand:()=>Be});module.exports=k(Je);var Se=require("commander");H();var ve=require("child_process"),d=p(require("chalk")),we=p(require("ora"));A();N();var _=p(require("fs")),xe=p(require("path")),je=p(require("dotenv")),Be=new Se.Command("run").description("Run a command with injected secrets").option("-p, --project <projectId>","Project ID").option("-e, --env <environment>","Environment (development, staging, production)","development").option("-b, --branch <branchName>","Branch Name").option("--shell","Enable shell mode (needed for npm run, shell built-ins on Windows)",!1).argument("<command>","Command to run").argument("[args...]","Command arguments").addHelpText("after",`
2
+ Examples:
3
+ $ xtra run npm start
4
+ $ xtra run -e production -- npm run build
5
+ $ xtra run -p proj_123 -b feature-branch -- python script.py
6
+ $ xtra run --shell "echo $SECRET_KEY"
7
+ `).action(async(e,t,s)=>{let{project:r,env:n,branch:i,shell:y}=s,l=K();i||(i=l.branch),n={dev:"development",stg:"staging",prod:"production"}[n]||n||l.env,r||(r=l.project),r||(console.error(d.default.red("Error: Project ID is required. Use -p <id> or checkout a branch.")),process.exit(1));let u=(0,we.default)(`Fetching secrets for ${n} (branch: ${i})...`).start(),a=null;if(R("localMode")===!0||process.env.XTRA_LOCAL_MODE==="true"){let c=xe.resolve(process.cwd(),".env.local");_.existsSync(c)||(u.fail("Local mode is ON but .env.local not found. Run 'xtra local sync' first."),process.exit(1)),a=je.default.parse(_.readFileSync(c,"utf8")),u.succeed(d.default.yellow(`\u{1F50C} Local mode: loaded ${Object.keys(a).length} secrets from .env.local`))}else try{if(a=await L.getSecrets(r,n,i),!a||Object.keys(a).length===0)u.warn(d.default.yellow("No secrets found for this environment."));else{u.succeed(`Loaded ${Object.keys(a).length} secrets (Online).`);try{let{hash:c}=(N(),k(pe)),{updateManifest:f}=(le(),k(ue));f(r,n,a,c)}catch{}try{let c=`cache.${r}.${n}.${i}`,f=I(JSON.stringify(a));J(c,f)}catch{console.error(d.default.yellow("Warning: Failed to cache secrets."))}}}catch(c){let f=`cache.${r}.${n}.${i}`;if(c.response&&[401,403,404].includes(c.response.status)){try{let{deleteConfig:w}=(A(),k(ee));w&&w(f)}catch{}u.fail("Access Denied."),c.response.status===404?console.error(d.default.red(`
8
+ Error: Project '${r}' not found or your access has been revoked.`)):c.response.status===401?console.error(d.default.red(`
9
+ Error: Unauthorized. Please run 'xtra login' again.`)):console.error(d.default.red(`
10
+ Error: ${c.response.data.error||"You do not have permission to access these secrets."}`)),process.exit(1)}let Y=R(f);if(Y)try{let w=E(Y);a=JSON.parse(w),u.warn(d.default.yellow(`Offline Mode: Loaded ${Object.keys(a).length} secrets from cache.`))}catch{u.fail("Failed to fetch secrets and cache is corrupt."),process.exit(1)}else u.fail("Failed to fetch secrets and no local cache available."),console.error(d.default.dim(`
11
+ Details: ${c?.response?.data?.error||"An unexpected error occurred"}`)),process.exit(1)}try{let{logAudit:c}=(he(),k(me));a&&Object.keys(a).length>0&&c("SECRET_ACCESS",r,n,{method:"run",keys:Object.keys(a),branch:i})}catch(c){console.error("Audit Error:",c)}let $e={...process.env,...a};if(!y&&/[;&|`$<>\\\n]/g.test(e)&&(console.error(d.default.red("Error: Command contains unsafe characters. Use --shell if intentional.")),process.exit(1)),n==="production"){let c=require("inquirer"),{confirm:f}=await c.prompt([{type:"confirm",name:"confirm",message:d.default.red(`\u26A0 You are about to run a command in PRODUCTION with ${Object.keys(a||{}).length} injected secrets. Continue?`),default:!1}]);if(!f){console.log(d.default.yellow("Aborted."));return}}console.log(d.default.gray(`> ${e} ${t.join(" ")}`));let be=process.platform==="win32",v=(0,ve.spawn)(e,t,{env:$e,stdio:"inherit",shell:y||be}),C=()=>{if(a)for(let c in a)a[c]="**********"};process.on("SIGINT",()=>{C(),v&&v.kill("SIGINT"),process.exit(0)}),process.on("SIGTERM",()=>{C(),v&&v.kill("SIGTERM"),process.exit(0)}),v.on("exit",c=>{C(),process.exit(c??0)}),v.on("error",c=>{console.error(d.default.red(`Failed to start command: ${c.message}`)),C(),process.exit(1)})});0&&(module.exports={runCommand});
@@ -1,127 +1,25 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.scanCommand = void 0;
7
- const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const fs_1 = __importDefault(require("fs"));
10
- const path_1 = __importDefault(require("path"));
11
- const child_process_1 = require("child_process");
12
- exports.scanCommand = new commander_1.Command("scan")
13
- .description("Scan project for secrets and configuration leaks")
14
- .option("--staged", "Scan only staged files (for pre-commit hooks)", false)
15
- .option("--install-hook", "Install the git pre-commit hook")
16
- .action(async (options) => {
17
- const { staged, installHook } = options;
18
- // Install Hook
19
- if (installHook) {
20
- installGitHook();
21
- return;
22
- }
23
- // Scanning Logic
24
- console.log(chalk_1.default.bold("XtraSync Secret Scan"));
25
- let issues = 0;
26
- // 1. Check .env tracking
27
- if (isEnvFileTracked()) {
28
- console.error(chalk_1.default.red("CRITICAL: .env file is tracked by git!"));
29
- issues++;
30
- }
31
- else {
32
- // console.log(chalk.green("✔ .env file is safe (not tracked)."));
33
- }
34
- // 2. Scan Files
35
- const filesToScan = staged ? getStagedFiles() : getAllFiles();
36
- // Pattern: xs_ (Looking for API keys or similar tokens if we had a specific format)
37
- // Also naive check for "password", "secret", "key" = "..."
38
- const patterns = [
39
- { name: "Xtra API Key", regex: /xs_[a-zA-Z0-9]{10,}/ },
40
- // Add more patterns here
41
- ];
42
- filesToScan.forEach(file => {
43
- try {
44
- // Skip binary or large files?
45
- if (!fs_1.default.existsSync(file))
46
- return;
47
- const content = fs_1.default.readFileSync(file, "utf-8");
48
- patterns.forEach(p => {
49
- if (p.regex.test(content)) {
50
- console.error(chalk_1.default.red(`[${file}] Potential Secret Detected: ${p.name}`));
51
- issues++;
52
- }
53
- });
54
- }
55
- catch (e) {
56
- // Ignore (e.g. dir or binary)
57
- }
58
- });
59
- if (issues > 0) {
60
- console.error(chalk_1.default.red(`\n✖ Scan failed. found ${issues} issues.`));
61
- process.exit(1);
62
- }
63
- else {
64
- console.log(chalk_1.default.green("\n✔ Scan passed. No secrets found."));
65
- }
66
- });
67
- function installGitHook() {
68
- const hookDir = path_1.default.join(process.cwd(), ".git", "hooks");
69
- const hookPath = path_1.default.join(hookDir, "pre-commit");
70
- if (!fs_1.default.existsSync(hookDir)) {
71
- console.error(chalk_1.default.red("Error: .git directory not found. Is this a git repository?"));
72
- return;
73
- }
74
- const hookContent = `#!/bin/sh
75
- # XtraSync Secret Scan
76
- echo "Running XtraSync Secret Scan..."
77
-
78
- # Check if 'xtra' is globally installed or use local script
79
- if command -v xtra >/dev/null 2>&1; then
80
- xtra scan --staged
81
- elif [ -f "./node_modules/.bin/xtra" ]; then
82
- ./node_modules/.bin/xtra scan --staged
83
- else
84
- # Using the binary directly if running in dev environment (unlikely in prod use case)
85
- # For dev testing:
86
- if [ -f "./dist/bin/xtra.js" ]; then
87
- node ./dist/bin/xtra.js scan --staged
88
- else
89
- echo "Warning: XtraSync CLI not found. Skipping scan."
90
- fi
91
- fi
92
-
93
- # Exit code of scan is passed through
94
- `;
95
- fs_1.default.writeFileSync(hookPath, hookContent, { mode: 0o755 });
96
- console.log(chalk_1.default.green("✔ Pre-commit hook installed successfully."));
97
- }
98
- function isEnvFileTracked() {
99
- try {
100
- // git ls-files --error-unmatch .env
101
- (0, child_process_1.execSync)("git ls-files --error-unmatch .env", { stdio: "ignore" });
102
- return true; // Command succeeded, file is tracked
103
- }
104
- catch (e) {
105
- return false; // Command failed, file not tracked
106
- }
107
- }
108
- function getStagedFiles() {
109
- try {
110
- const output = (0, child_process_1.execSync)("git diff --cached --name-only", { encoding: "utf-8" });
111
- return output.split("\n").filter(f => f.trim().length > 0);
112
- }
113
- catch (e) {
114
- return [];
115
- }
116
- }
117
- function getAllFiles() {
118
- // Naive implementation for now, or recurse.
119
- // Usually 'git ls-files' is better if generic scan.
120
- try {
121
- const output = (0, child_process_1.execSync)("git ls-files", { encoding: "utf-8" });
122
- return output.split("\n").filter(f => f.trim().length > 0);
123
- }
124
- catch (e) {
125
- return [];
126
- }
127
- }
1
+ var y=Object.create;var c=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var v=(e,t)=>{for(var n in t)c(e,n,{get:t[n],enumerable:!0})},h=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of k(t))!b.call(e,s)&&s!==n&&c(e,s,{get:()=>t[s],enumerable:!(r=S(t,s))||r.enumerable});return e};var d=(e,t,n)=>(n=e!=null?y(x(e)):{},h(t||!e||!e.__esModule?c(n,"default",{value:e,enumerable:!0}):n,e)),C=e=>h(c({},"__esModule",{value:!0}),e);var A={};v(A,{scanCommand:()=>F});module.exports=C(A);var m=require("commander"),o=d(require("chalk")),i=d(require("fs")),f=d(require("path")),a=require("child_process"),F=new m.Command("scan").description("Scan project for secrets and configuration leaks").option("--staged","Scan only staged files (for pre-commit hooks)",!1).option("--install-hook","Install the git pre-commit hook").action(async e=>{let{staged:t,installHook:n}=e;if(n){I();return}console.log(o.default.bold("XtraSync Secret Scan"));let r=0;j()&&(console.error(o.default.red("CRITICAL: .env file is tracked by git!")),r++);let s=t?E():X(),p=[{name:"Xtra API Key",regex:/xs_[a-zA-Z0-9]{10,}/}];s.forEach(l=>{try{if(!i.default.existsSync(l))return;let u=i.default.readFileSync(l,"utf-8");p.forEach(g=>{g.regex.test(u)&&(console.error(o.default.red(`[${l}] Potential Secret Detected: ${g.name}`)),r++)})}catch{}}),r>0?(console.error(o.default.red(`
2
+ \u2716 Scan failed. found ${r} issues.`)),process.exit(1)):console.log(o.default.green(`
3
+ \u2714 Scan passed. No secrets found.`))});function I(){let e=f.default.join(process.cwd(),".git","hooks"),t=f.default.join(e,"pre-commit");if(!i.default.existsSync(e)){console.error(o.default.red("Error: .git directory not found. Is this a git repository?"));return}i.default.writeFileSync(t,`#!/bin/sh
4
+ # XtraSync Secret Scan
5
+ echo "Running XtraSync Secret Scan..."
6
+
7
+ # Check if 'xtra' is globally installed or use local script
8
+ if command -v xtra >/dev/null 2>&1; then
9
+ xtra scan --staged
10
+ elif [ -f "./node_modules/.bin/xtra" ]; then
11
+ ./node_modules/.bin/xtra scan --staged
12
+ else
13
+ # Using the binary directly if running in dev environment (unlikely in prod use case)
14
+ # For dev testing:
15
+ if [ -f "./dist/bin/xtra.js" ]; then
16
+ node ./dist/bin/xtra.js scan --staged
17
+ else
18
+ echo "Warning: XtraSync CLI not found. Skipping scan."
19
+ fi
20
+ fi
21
+
22
+ # Exit code of scan is passed through
23
+ `,{mode:493}),console.log(o.default.green("\u2714 Pre-commit hook installed successfully."))}function j(){try{return(0,a.execSync)("git ls-files --error-unmatch .env",{stdio:"ignore"}),!0}catch{return!1}}function E(){try{return(0,a.execSync)("git diff --cached --name-only",{encoding:"utf-8"}).split(`
24
+ `).filter(t=>t.trim().length>0)}catch{return[]}}function X(){try{return(0,a.execSync)("git ls-files",{encoding:"utf-8"}).split(`
25
+ `).filter(t=>t.trim().length>0)}catch{return[]}}0&&(module.exports={scanCommand});