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,341 +1,31 @@
1
- "use strict";
2
- /**
3
- * ci.ts - CI/CD Machine User Mode for xtra-cli
4
- *
5
- * This command is designed for headless pipeline execution:
6
- * - No interactive prompts
7
- * - No colors or spinners
8
- * - Output is pure clean JSON (pipe-friendly with `jq`)
9
- * - Auth via XTRA_MACHINE_TOKEN env var (no stored token needed)
10
- * - Non-zero exit codes on any failure
11
- *
12
- * Usage:
13
- * XTRA_MACHINE_TOKEN=tok_... xtra ci secrets --project <id> --env production
14
- * XTRA_MACHINE_TOKEN=tok_... xtra ci secrets --project <id> --env production | jq '.DATABASE_URL'
15
- * XTRA_MACHINE_TOKEN=tok_... xtra ci set --project <id> --env production KEY=VALUE
16
- * XTRA_MACHINE_TOKEN=tok_... xtra ci export --project <id> --env production --format dotenv > .env
17
- * XTRA_MACHINE_TOKEN=tok_... xtra ci run --project <id> --env production node app.js
18
- */
19
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- var desc = Object.getOwnPropertyDescriptor(m, k);
22
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
- desc = { enumerable: true, get: function() { return m[k]; } };
24
- }
25
- Object.defineProperty(o, k2, desc);
26
- }) : (function(o, m, k, k2) {
27
- if (k2 === undefined) k2 = k;
28
- o[k2] = m[k];
29
- }));
30
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
- Object.defineProperty(o, "default", { enumerable: true, value: v });
32
- }) : function(o, v) {
33
- o["default"] = v;
34
- });
35
- var __importStar = (this && this.__importStar) || (function () {
36
- var ownKeys = function(o) {
37
- ownKeys = Object.getOwnPropertyNames || function (o) {
38
- var ar = [];
39
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
- return ar;
41
- };
42
- return ownKeys(o);
43
- };
44
- return function (mod) {
45
- if (mod && mod.__esModule) return mod;
46
- var result = {};
47
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
- __setModuleDefault(result, mod);
49
- return result;
50
- };
51
- })();
52
- var __importDefault = (this && this.__importDefault) || function (mod) {
53
- return (mod && mod.__esModule) ? mod : { "default": mod };
54
- };
55
- Object.defineProperty(exports, "__esModule", { value: true });
56
- exports.ciCommand = void 0;
57
- const commander_1 = require("commander");
58
- const child_process_1 = require("child_process");
59
- const axios_1 = __importDefault(require("axios"));
60
- const fs = __importStar(require("fs"));
61
- const config_1 = require("../lib/config");
62
- // ─── CI API Client ─────────────────────────────────────────────────────────────
63
- // Standalone, no Conf dependency — reads everything from environment variables
64
- function getCiClient() {
65
- const token = process.env.XTRA_MACHINE_TOKEN;
66
- const apiUrl = process.env.XTRA_API_URL || "https://www.xtrasecurity.in/api";
67
- if (!token) {
68
- ciError("XTRA_MACHINE_TOKEN environment variable is required for CI mode.");
69
- }
70
- return axios_1.default.create({
71
- baseURL: apiUrl,
72
- headers: {
73
- "Content-Type": "application/json",
74
- Authorization: `Bearer ${token}`,
75
- "X-CI-Mode": "true",
76
- },
77
- });
78
- }
79
- // ─── Helpers ──────────────────────────────────────────────────────────────────
80
- /** Emit a JSON error to stderr and exit 1 */
81
- function ciError(message, detail) {
82
- process.stderr.write(JSON.stringify({ ok: false, error: message, detail: detail || null }, null, 2) + "\n");
83
- process.exit(1);
84
- }
85
- /** Emit clean JSON to stdout */
86
- function ciOut(data) {
87
- process.stdout.write(JSON.stringify(data, null, 2) + "\n");
88
- }
89
- /** Validate required CLI env vars and options */
90
- function requireMachineToken() {
91
- if (!process.env.XTRA_MACHINE_TOKEN) {
92
- ciError("XTRA_MACHINE_TOKEN is not set. Export it before running CI commands.", {
93
- hint: "export XTRA_MACHINE_TOKEN=your_service_account_token"
94
- });
95
- }
96
- }
97
- function requireProject(project) {
98
- if (!project) {
99
- ciError("--project <id> is required in CI mode. Set it explicitly (no config fallback).");
100
- }
101
- return project;
102
- }
103
- function requireEnv(env) {
104
- if (!env) {
105
- ciError("--env <environment> is required in CI mode.");
106
- }
107
- return env;
108
- }
109
- // ─── xtra ci ──────────────────────────────────────────────────────────────────
110
- exports.ciCommand = new commander_1.Command("ci")
111
- .description("CI/CD headless mode — JSON output, no prompts, XTRA_MACHINE_TOKEN auth")
112
- .addHelpText("after", `
113
- Environment Variables:
114
- XTRA_MACHINE_TOKEN Required. Service account token for authentication.
115
- XTRA_API_URL Optional. Override the API base URL.
116
- XTRA_PROFILE Optional. Use a specific named profile.
117
-
118
- Subcommands:
119
- xtra ci secrets Fetch secrets as JSON
120
- xtra ci set Set one or more secrets KEY=VALUE
121
- xtra ci export Export secrets in dotenv/json/yaml format to a file
122
- xtra ci run Run a command with secrets injected as env vars
123
-
124
- Examples:
125
- # Uses project/env from .xtrarc if available
126
- XTRA_MACHINE_TOKEN=tok_... xtra ci secrets
127
-
128
- # Explicit flags (always prioritized)
129
- XTRA_MACHINE_TOKEN=tok_... xtra ci secrets -p proj123 -e production
130
- XTRA_MACHINE_TOKEN=tok_... xtra ci set -p proj123 -e staging DEPLOY_VERSION=1.2.3
131
- XTRA_MACHINE_TOKEN=tok_... xtra ci run -- node app.js
132
- `);
133
- // ── ci secrets ────────────────────────────────────────────────────────────────
134
- exports.ciCommand
135
- .command("secrets")
136
- .description("Fetch secrets as JSON (pipe-friendly)")
137
- .option("-p, --project <id>", "Project ID")
138
- .option("-e, --env <environment>", "Environment")
139
- .option("-b, --branch <branch>", "Branch name")
140
- .option("--keys <keys>", "Comma-separated list of keys to include (default: all)")
141
- .action(async (options) => {
142
- requireMachineToken();
143
- const rc = (0, config_1.getRcConfig)();
144
- const project = requireProject(options.project || rc.project);
145
- const env = requireEnv(options.env || rc.env);
146
- const branch = options.branch || rc.branch || "main";
147
- try {
148
- const client = getCiClient();
149
- const res = await client.get(`/projects/${project}/envs/${env}/secrets?branch=${branch}`);
150
- let secrets = res.data;
151
- // Filter to specific keys if requested
152
- if (options.keys) {
153
- const allowedKeys = options.keys.split(",").map((k) => k.trim());
154
- const filtered = {};
155
- for (const k of allowedKeys) {
156
- if (secrets[k] !== undefined)
157
- filtered[k] = secrets[k];
158
- else
159
- process.stderr.write(`Warning: key '${k}' not found\n`);
160
- }
161
- secrets = filtered;
162
- }
163
- ciOut({ ok: true, project, env, branch: options.branch, secrets });
164
- }
165
- catch (e) {
166
- const status = e?.response?.status;
167
- const message = e?.response?.data?.error || e.message;
168
- if (status === 401)
169
- ciError("Unauthorized. Check your XTRA_MACHINE_TOKEN.", { status });
170
- if (status === 403)
171
- ciError("Forbidden. This token lacks permission to read secrets.", { status });
172
- if (status === 404)
173
- ciError(`Project '${project}' or environment '${env}' not found.`, { status });
174
- ciError(message, { status });
175
- }
176
- });
177
- // ── ci set ────────────────────────────────────────────────────────────────────
178
- exports.ciCommand
179
- .command("set")
180
- .description("Set one or more secrets in CI mode (KEY=VALUE ...)")
181
- .option("-p, --project <id>", "Project ID")
182
- .option("-e, --env <environment>", "Environment")
183
- .option("-b, --branch <branch>", "Branch name")
184
- .argument("<secrets...>", "Secrets to set (format: KEY=VALUE)")
185
- .action(async (args, options) => {
186
- requireMachineToken();
187
- const rc = (0, config_1.getRcConfig)();
188
- const project = requireProject(options.project || rc.project);
189
- const env = requireEnv(options.env || rc.env);
190
- const branch = options.branch || rc.branch || "main";
191
- const payload = {};
192
- for (const arg of args) {
193
- const idx = arg.indexOf("=");
194
- if (idx === -1) {
195
- ciError(`Invalid format: '${arg}'. Expected KEY=VALUE`);
196
- }
197
- payload[arg.substring(0, idx)] = arg.substring(idx + 1);
198
- }
199
- if (Object.keys(payload).length === 0) {
200
- ciError("No valid KEY=VALUE pairs provided.");
201
- }
202
- try {
203
- const client = getCiClient();
204
- await client.post(`/projects/${project}/envs/${env}/secrets`, {
205
- secrets: payload,
206
- branch: branch,
207
- });
208
- ciOut({
209
- ok: true,
210
- project,
211
- env,
212
- branch: branch,
213
- updated: Object.keys(payload),
214
- count: Object.keys(payload).length,
215
- });
216
- }
217
- catch (e) {
218
- const status = e?.response?.status;
219
- ciError(e?.response?.data?.error || e.message, { status });
220
- }
221
- });
222
- // ── ci export ─────────────────────────────────────────────────────────────────
223
- exports.ciCommand
224
- .command("export")
225
- .description("Export secrets to a file (dotenv, json, or github-actions format)")
226
- .option("-p, --project <id>", "Project ID")
227
- .option("-e, --env <environment>", "Environment")
228
- .option("-b, --branch <branch>", "Branch name")
229
- .option("-f, --format <format>", "Output format: dotenv | json | github", "dotenv")
230
- .option("-o, --output <file>", "Output file path (default: stdout)")
231
- .action(async (options) => {
232
- requireMachineToken();
233
- const rc = (0, config_1.getRcConfig)();
234
- const project = requireProject(options.project || rc.project);
235
- const env = requireEnv(options.env || rc.env);
236
- const branch = options.branch || rc.branch || "main";
237
- const { format, output } = options;
238
- const validFormats = ["dotenv", "json", "github"];
239
- if (!validFormats.includes(format)) {
240
- ciError(`Invalid format '${format}'. Must be one of: ${validFormats.join(", ")}`);
241
- }
242
- try {
243
- const client = getCiClient();
244
- const res = await client.get(`/projects/${project}/envs/${env}/secrets?branch=${branch}`);
245
- const secrets = res.data;
246
- let content;
247
- switch (format) {
248
- case "dotenv":
249
- content = Object.entries(secrets)
250
- .map(([k, v]) => `${k}=${v}`)
251
- .join("\n") + "\n";
252
- break;
253
- case "json":
254
- content = JSON.stringify(secrets, null, 2) + "\n";
255
- break;
256
- case "github":
257
- // Format for GitHub Actions $GITHUB_ENV / $GITHUB_OUTPUT
258
- content = Object.entries(secrets)
259
- .map(([k, v]) => `${k}=${v}`)
260
- .join("\n") + "\n";
261
- break;
262
- default:
263
- ciError("Unreachable format error.");
264
- }
265
- if (output) {
266
- fs.writeFileSync(output, content, "utf8");
267
- // Write metadata to stderr so it doesn't pollute file output
268
- process.stderr.write(JSON.stringify({ ok: true, file: output, keys: Object.keys(secrets).length }) + "\n");
269
- }
270
- else {
271
- // Write raw content to stdout (pipe-ready)
272
- process.stdout.write(content);
273
- }
274
- }
275
- catch (e) {
276
- const status = e?.response?.status;
277
- ciError(e?.response?.data?.error || e.message, { status });
278
- }
279
- });
280
- // ── ci run ────────────────────────────────────────────────────────────────────
281
- exports.ciCommand
282
- .command("run")
283
- .description("Run a command with secrets injected as environment variables")
284
- .option("-p, --project <id>", "Project ID")
285
- .option("-e, --env <environment>", "Environment")
286
- .option("-b, --branch <branch>", "Branch name")
287
- .argument("<command>", "Command to execute")
288
- .argument("[args...]", "Command arguments")
289
- .action(async (command, args, options) => {
290
- requireMachineToken();
291
- const rc = (0, config_1.getRcConfig)();
292
- const project = requireProject(options.project || rc.project);
293
- const env = requireEnv(options.env || rc.env);
294
- const branch = options.branch || rc.branch || "main";
295
- // Security: prevent command injection
296
- const SHELL_UNSAFE = /[;&|`$<>\\\n]/g;
297
- if (SHELL_UNSAFE.test(command)) {
298
- ciError("Command contains unsafe characters. Aborting for security.");
299
- }
300
- try {
301
- const client = getCiClient();
302
- const res = await client.get(`/projects/${project}/envs/${env}/secrets?branch=${branch}`);
303
- const secrets = res.data;
304
- process.stderr.write(`[xtra ci] Injecting ${Object.keys(secrets).length} secrets into process env\n`);
305
- const envVars = { ...process.env, ...secrets };
306
- const child = (0, child_process_1.spawn)(command, args, {
307
- env: envVars,
308
- stdio: "inherit",
309
- shell: false, // NO shell:true — prevents injection
310
- });
311
- child.on("exit", (code) => process.exit(code ?? 0));
312
- child.on("error", (err) => ciError(`Failed to start process: ${err.message}`));
313
- }
314
- catch (e) {
315
- const status = e?.response?.status;
316
- ciError(e?.response?.data?.error || e.message, { status });
317
- }
318
- });
319
- // ── ci validate ───────────────────────────────────────────────────────────────
320
- exports.ciCommand
321
- .command("validate")
322
- .description("Validate that XTRA_MACHINE_TOKEN is set and working")
323
- .action(async () => {
324
- requireMachineToken();
325
- try {
326
- const client = getCiClient();
327
- const res = await client.get("/project");
328
- ciOut({
329
- ok: true,
330
- authenticated: true,
331
- projects: res.data.length,
332
- message: "XTRA_MACHINE_TOKEN is valid and working.",
333
- });
334
- }
335
- catch (e) {
336
- const status = e?.response?.status;
337
- if (status === 401)
338
- ciError("XTRA_MACHINE_TOKEN is invalid or expired.", { status });
339
- ciError(e?.response?.data?.error || e.message, { status });
340
- }
341
- });
1
+ var S=Object.create;var y=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var K=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var P=(e,t)=>{for(var n in t)y(e,n,{get:t[n],enumerable:!0})},k=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $(t))!X.call(e,o)&&o!==n&&y(e,o,{get:()=>t[o],enumerable:!(a=U(t,o))||a.enumerable});return e};var v=(e,t,n)=>(n=e!=null?S(K(e)):{},k(t||!e||!e.__esModule?y(n,"default",{value:e,enumerable:!0}):n,e)),M=e=>k(y({},"__esModule",{value:!0}),e);var L={};P(L,{ciCommand:()=>h});module.exports=M(L);var T=require("commander"),I=require("child_process"),R=v(require("axios")),w=v(require("fs"));var C=v(require("conf")),x=v(require("fs")),O=v(require("path")),N="https://www.xtrasecurity.in/api",b=new C.default({projectName:"xtra-cli",defaults:{apiUrl:N}}),A=b.get("apiUrl");A&&!process.env.XTRA_API_URL&&(A.includes("localhost")||A.includes("xtra-security.vercel.app"))&&b.set("apiUrl",N);var d=()=>{let e={};try{let t=O.default.join(process.cwd(),".xtrarc");x.default.existsSync(t)&&(e=JSON.parse(x.default.readFileSync(t,"utf-8")))}catch{}return{project:e.project||b.get("project")||"",env:e.env||b.get("env")||"development",branch:e.branch||b.get("branch")||"main"}};function g(){let e=process.env.XTRA_MACHINE_TOKEN,t=process.env.XTRA_API_URL||"https://www.xtrasecurity.in/api";return e||c("XTRA_MACHINE_TOKEN environment variable is required for CI mode."),R.default.create({baseURL:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,"X-CI-Mode":"true"}})}function c(e,t){process.stderr.write(JSON.stringify({ok:!1,error:e,detail:t||null},null,2)+`
2
+ `),process.exit(1)}function _(e){process.stdout.write(JSON.stringify(e,null,2)+`
3
+ `)}function H(){process.env.XTRA_MACHINE_TOKEN||c("XTRA_MACHINE_TOKEN is not set. Export it before running CI commands.",{hint:"export XTRA_MACHINE_TOKEN=your_service_account_token"})}async function j(e){if(e)return e;try{let a=await g().get("/service-account/resolve");if(a.data&&a.data.project)return a.data.project.id}catch{}let t=d();if(t.project)return t.project;c("--project <id> is required. Set it explicitly or ensure your token is linked to a project.")}function E(e){return e||c("--env <environment> is required in CI mode."),e}var h=new T.Command("ci").description("CI/CD headless mode \u2014 JSON output, no prompts, XTRA_MACHINE_TOKEN auth").addHelpText("after",`
4
+ Environment Variables:
5
+ XTRA_MACHINE_TOKEN Required. Service account token for authentication.
6
+ XTRA_API_URL Optional. Override the API base URL.
7
+ XTRA_PROFILE Optional. Use a specific named profile.
8
+
9
+ Subcommands:
10
+ xtra ci secrets Fetch secrets as JSON
11
+ xtra ci set Set one or more secrets KEY=VALUE
12
+ xtra ci export Export secrets in dotenv/json/yaml format to a file
13
+ xtra ci run Run a command with secrets injected as env vars
14
+
15
+ Examples:
16
+ # Uses project/env from .xtrarc if available
17
+ XTRA_MACHINE_TOKEN=tok_... xtra ci secrets
18
+
19
+ # Explicit flags (always prioritized)
20
+ XTRA_MACHINE_TOKEN=tok_... xtra ci secrets -p proj123 -e production
21
+ XTRA_MACHINE_TOKEN=tok_... xtra ci set -p proj123 -e staging DEPLOY_VERSION=1.2.3
22
+ XTRA_MACHINE_TOKEN=tok_... xtra ci run -- node app.js
23
+ `);h.command("secrets").description("Fetch secrets as JSON (pipe-friendly)").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment").option("-b, --branch <branch>","Branch name").option("--keys <keys>","Comma-separated list of keys to include (default: all)").action(async e=>{let t=await j(e.project),n=E(e.env||d().env),a=e.branch||d().branch||"main";try{let r=(await g().get(`/projects/${t}/envs/${n}/secrets?branch=${a}`)).data;if(e.keys){let i=e.keys.split(",").map(p=>p.trim()),l={};for(let p of i)r[p]!==void 0?l[p]=r[p]:process.stderr.write(`Warning: key '${p}' not found
24
+ `);r=l}_({ok:!0,project:t,env:n,branch:e.branch,secrets:r})}catch(o){let s=o?.response?.status,r=o?.response?.data?.error||o?.response?.data?.error||"An unexpected error occurred";s===401&&c("Unauthorized. Check your XTRA_MACHINE_TOKEN.",{status:s}),s===403&&c("Forbidden. This token lacks permission to read secrets.",{status:s}),s===404&&c(`Project '${t}' or environment '${n}' not found.`,{status:s}),c(r,{status:s})}});h.command("set").description("Set one or more secrets in CI mode (KEY=VALUE ...)").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment").option("-b, --branch <branch>","Branch name").argument("<secrets...>","Secrets to set (format: KEY=VALUE)").action(async(e,t)=>{let n=await j(t.project),a=E(t.env||d().env),o=t.branch||d().branch||"main",s={};for(let r of e){let i=r.indexOf("=");i===-1&&c(`Invalid format: '${r}'. Expected KEY=VALUE`),s[r.substring(0,i)]=r.substring(i+1)}Object.keys(s).length===0&&c("No valid KEY=VALUE pairs provided.");try{await g().post(`/projects/${n}/envs/${a}/secrets`,{secrets:s,branch:o}),_({ok:!0,project:n,env:a,branch:o,updated:Object.keys(s),count:Object.keys(s).length})}catch(r){let i=r?.response?.status;c(r?.response?.data?.error||r?.response?.data?.error||"An unexpected error occurred",{status:i})}});h.command("export").description("Export secrets to a file (dotenv, json, or github-actions format)").option("-p, --project <id>","Project ID").option("-e, --env <environment>","Environment").option("-b, --branch <branch>","Branch name").option("-f, --format <format>","Output format: dotenv | json | github","dotenv").option("-o, --output <file>","Output file path (default: stdout)").action(async e=>{let t=await j(e.project),n=E(e.env||d().env),a=e.branch||d().branch||"main",{format:o,output:s}=e,r=["dotenv","json","github"];r.includes(o)||c(`Invalid format '${o}'. Must be one of: ${r.join(", ")}`);try{let p=(await g().get(`/projects/${t}/envs/${n}/secrets?branch=${a}`)).data,u;switch(o){case"dotenv":u=Object.entries(p).map(([m,f])=>`${m}=${f}`).join(`
25
+ `)+`
26
+ `;break;case"json":u=JSON.stringify(p,null,2)+`
27
+ `;break;case"github":u=Object.entries(p).map(([m,f])=>`${m}=${f}`).join(`
28
+ `)+`
29
+ `;break;default:c("Unreachable format error.")}s?(w.writeFileSync(s,u,"utf8"),process.stderr.write(JSON.stringify({ok:!0,file:s,keys:Object.keys(p).length})+`
30
+ `)):process.stdout.write(u)}catch(i){let l=i?.response?.status;c(i?.response?.data?.error||i?.response?.data?.error||"An unexpected error occurred",{status:l})}});h.command("run").description("Run a command with secrets injected as environment variables").option("-p, --project <id>","Project ID").option("--env <environment>","Environment").option("-b, --branch <branch>","Branch name").option("--shell","Run command inside a shell (required for npm on Windows)").argument("<command>","Command to execute").argument("[args...]","Command arguments").action(async(e,t,n)=>{let a=await j(n.project),o=E(n.env||d().env),s=n.branch||d().branch||"main",r=/[;&|`$<>\\\n]/g;!n.shell&&r.test(e)&&c("Command contains unsafe characters. Aborting for security. Use --shell if intended.");try{let p=(await g().get(`/projects/${a}/envs/${o}/secrets?branch=${s}`)).data;process.stderr.write(`[xtra ci] Injecting ${Object.keys(p).length} secrets into process env
31
+ `);let u={...process.env,...p},m=(0,I.spawn)(e,t,{env:u,stdio:"inherit",shell:n.shell||!1});m.on("exit",f=>process.exit(f??0)),m.on("error",f=>c(`Failed to start process: ${f.message}`))}catch(i){let l=i?.response?.status;c(i?.response?.data?.error||i?.response?.data?.error||"An unexpected error occurred",{status:l})}});h.command("validate").description("Validate that XTRA_MACHINE_TOKEN is set and working").action(async()=>{H();try{let t=await g().get("/project");_({ok:!0,authenticated:!0,projects:t.data.length,message:"XTRA_MACHINE_TOKEN is valid and working."})}catch(e){let t=e?.response?.status;t===401&&c("XTRA_MACHINE_TOKEN is invalid or expired.",{status:t}),c(e?.response?.data?.error||e?.response?.data?.error||"An unexpected error occurred",{status:t})}});0&&(module.exports={ciCommand});