xtra-cli 0.1.10 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/access.js +2 -2
- package/dist/commands/branch.js +8 -12
- package/dist/commands/checkout.js +3 -4
- package/dist/commands/diff.js +4 -5
- package/dist/commands/doctor.js +25 -25
- package/dist/commands/env.js +2 -2
- package/dist/commands/export.js +4 -5
- package/dist/commands/generate.js +3 -4
- package/dist/commands/history.js +2 -2
- package/dist/commands/import.js +3 -4
- package/dist/commands/integration.js +6 -6
- package/dist/commands/local.js +20 -20
- package/dist/commands/rollback.js +2 -3
- package/dist/commands/rotate.js +3 -4
- package/dist/commands/run.js +7 -17
- package/dist/commands/secrets.js +9 -8
- package/dist/commands/simulate.js +8 -8
- package/dist/commands/status.js +8 -9
- package/dist/commands/template.js +21 -21
- package/dist/commands/ui.js +24 -24
- package/dist/commands/watch.js +9 -9
- package/dist/lib/config.js +22 -1
- package/package.json +1 -1
package/dist/commands/access.js
CHANGED
|
@@ -29,7 +29,7 @@ exports.accessCommand = new commander_1.Command("access")
|
|
|
29
29
|
.action(async (options) => {
|
|
30
30
|
let { project, secret, duration, reason } = options;
|
|
31
31
|
if (!project) {
|
|
32
|
-
project = (0, config_1.
|
|
32
|
+
project = (0, config_1.getRcConfig)().project;
|
|
33
33
|
}
|
|
34
34
|
if (!project) {
|
|
35
35
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
@@ -97,7 +97,7 @@ exports.accessCommand = new commander_1.Command("access")
|
|
|
97
97
|
if (result.expiresAt) {
|
|
98
98
|
console.log(chalk_1.default.blue(`Access granted until: ${new Date(result.expiresAt).toLocaleString()}`));
|
|
99
99
|
}
|
|
100
|
-
// Audit log
|
|
100
|
+
// Audit log — privileged action
|
|
101
101
|
logAuditSafe(options.decision === "approved" ? "ACCESS_APPROVED" : "ACCESS_REJECTED", null, { requestId, decision: options.decision, expiresAt: result.expiresAt });
|
|
102
102
|
}
|
|
103
103
|
catch (error) {
|
package/dist/commands/branch.js
CHANGED
|
@@ -22,9 +22,8 @@ exports.branchCommand
|
|
|
22
22
|
// Access parent options (project ID)
|
|
23
23
|
const parentOpts = exports.branchCommand.opts();
|
|
24
24
|
let { project } = parentOpts;
|
|
25
|
-
if (!project)
|
|
26
|
-
project = (0, config_1.
|
|
27
|
-
}
|
|
25
|
+
if (!project)
|
|
26
|
+
project = (0, config_1.getRcConfig)().project;
|
|
28
27
|
if (!project) {
|
|
29
28
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or checkout a branch."));
|
|
30
29
|
process.exit(1);
|
|
@@ -78,9 +77,8 @@ exports.branchCommand
|
|
|
78
77
|
const parentOpts = exports.branchCommand.opts();
|
|
79
78
|
let { project } = parentOpts;
|
|
80
79
|
const { description } = options;
|
|
81
|
-
if (!project)
|
|
82
|
-
project = (0, config_1.
|
|
83
|
-
}
|
|
80
|
+
if (!project)
|
|
81
|
+
project = (0, config_1.getRcConfig)().project;
|
|
84
82
|
if (!project) {
|
|
85
83
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
86
84
|
process.exit(1);
|
|
@@ -110,9 +108,8 @@ exports.branchCommand
|
|
|
110
108
|
const parentOpts = exports.branchCommand.opts();
|
|
111
109
|
let { project } = parentOpts;
|
|
112
110
|
const { yes } = options;
|
|
113
|
-
if (!project)
|
|
114
|
-
project = (0, config_1.
|
|
115
|
-
}
|
|
111
|
+
if (!project)
|
|
112
|
+
project = (0, config_1.getRcConfig)().project;
|
|
116
113
|
if (!project) {
|
|
117
114
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
118
115
|
process.exit(1);
|
|
@@ -176,9 +173,8 @@ exports.branchCommand
|
|
|
176
173
|
const parentOpts = exports.branchCommand.opts();
|
|
177
174
|
let { project } = parentOpts;
|
|
178
175
|
const { newName, description } = options;
|
|
179
|
-
if (!project)
|
|
180
|
-
project = (0, config_1.
|
|
181
|
-
}
|
|
176
|
+
if (!project)
|
|
177
|
+
project = (0, config_1.getRcConfig)().project;
|
|
182
178
|
if (!project) {
|
|
183
179
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or checkout a branch."));
|
|
184
180
|
process.exit(1);
|
|
@@ -17,9 +17,8 @@ exports.checkoutCommand = new commander_1.Command("checkout")
|
|
|
17
17
|
.action(async (branchName, options) => {
|
|
18
18
|
let { project } = options;
|
|
19
19
|
// Try to get project from config if not provided
|
|
20
|
-
if (!project)
|
|
21
|
-
project = (0, config_1.
|
|
22
|
-
}
|
|
20
|
+
if (!project)
|
|
21
|
+
project = (0, config_1.getRcConfig)().project;
|
|
23
22
|
if (!project) {
|
|
24
23
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
25
24
|
process.exit(1);
|
|
@@ -60,7 +59,7 @@ exports.checkoutCommand = new commander_1.Command("checkout")
|
|
|
60
59
|
// Save to config
|
|
61
60
|
(0, config_1.setConfig)("branch", selectedBranch);
|
|
62
61
|
(0, config_1.setConfig)("project", project);
|
|
63
|
-
console.log(chalk_1.default.green(
|
|
62
|
+
console.log(chalk_1.default.green(`✔ Switched to branch '${selectedBranch}'`));
|
|
64
63
|
}
|
|
65
64
|
catch (error) {
|
|
66
65
|
spinner.fail("Failed to fetch branches");
|
package/dist/commands/diff.js
CHANGED
|
@@ -55,11 +55,10 @@ exports.diffCommand = new commander_1.Command("diff")
|
|
|
55
55
|
.action(async (env1, env2, options) => {
|
|
56
56
|
let { project, env, branch, show } = options;
|
|
57
57
|
// Use config fallback
|
|
58
|
-
if (!project)
|
|
59
|
-
project = (0, config_1.
|
|
60
|
-
}
|
|
58
|
+
if (!project)
|
|
59
|
+
project = (0, config_1.getRcConfig)().project;
|
|
61
60
|
if (!branch) {
|
|
62
|
-
branch = (0, config_1.
|
|
61
|
+
branch = (0, config_1.getRcConfig)().branch || "main";
|
|
63
62
|
}
|
|
64
63
|
if (!project) {
|
|
65
64
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
@@ -155,7 +154,7 @@ function compareSecrets(source, target, show) {
|
|
|
155
154
|
}
|
|
156
155
|
});
|
|
157
156
|
if (!hasDiff) {
|
|
158
|
-
console.log(chalk_1.default.green("
|
|
157
|
+
console.log(chalk_1.default.green("✔ No differences found."));
|
|
159
158
|
}
|
|
160
159
|
else if (!show) {
|
|
161
160
|
console.log(chalk_1.default.gray("\n(Use --show to reveal secret values)"));
|
package/dist/commands/doctor.js
CHANGED
|
@@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.doctorCommand = void 0;
|
|
40
40
|
/**
|
|
41
|
-
* doctor.ts
|
|
41
|
+
* doctor.ts — Diagnose common xtra-cli setup issues
|
|
42
42
|
*
|
|
43
43
|
* Runs a series of self-diagnostic checks and reports
|
|
44
44
|
* pass / warn / fail for each one.
|
|
@@ -49,65 +49,65 @@ const fs = __importStar(require("fs"));
|
|
|
49
49
|
const path = __importStar(require("path"));
|
|
50
50
|
const axios_1 = __importDefault(require("axios"));
|
|
51
51
|
const config_1 = require("../lib/config");
|
|
52
|
-
const PASS = chalk_1.default.green("
|
|
53
|
-
const FAIL = chalk_1.default.red("
|
|
54
|
-
const WARN = chalk_1.default.yellow("
|
|
52
|
+
const PASS = chalk_1.default.green(" ✔");
|
|
53
|
+
const FAIL = chalk_1.default.red(" ✗");
|
|
54
|
+
const WARN = chalk_1.default.yellow(" âš ");
|
|
55
55
|
async function runChecks() {
|
|
56
56
|
const checks = [];
|
|
57
57
|
const config = (0, config_1.getConfig)();
|
|
58
|
-
//
|
|
58
|
+
// ── 1. Node Version ─────────────────────────────────────────────────────────
|
|
59
59
|
const nodeMajor = parseInt(process.versions.node.split(".")[0]);
|
|
60
60
|
checks.push({
|
|
61
61
|
name: "Node.js Version",
|
|
62
62
|
status: nodeMajor >= 18 ? "pass" : "fail",
|
|
63
63
|
message: `v${process.versions.node} ${nodeMajor >= 18 ? "(OK)" : "(requires >=18)"}`,
|
|
64
64
|
});
|
|
65
|
-
//
|
|
65
|
+
// ── 2. Auth Token ────────────────────────────────────────────────────────────
|
|
66
66
|
const token = (0, config_1.getAuthToken)();
|
|
67
67
|
checks.push({
|
|
68
68
|
name: "Auth Token",
|
|
69
69
|
status: token ? "pass" : "fail",
|
|
70
|
-
message: token ? `Set (${token.substring(0, 8)}...)` : "Not set
|
|
70
|
+
message: token ? `Set (${token.substring(0, 8)}...)` : "Not set — run 'xtra login'",
|
|
71
71
|
});
|
|
72
|
-
//
|
|
72
|
+
// ── 3. API URL Configured ────────────────────────────────────────────────────
|
|
73
73
|
const apiUrl = config.apiUrl || process.env.XTRA_API_URL;
|
|
74
74
|
checks.push({
|
|
75
75
|
name: "API URL",
|
|
76
76
|
status: apiUrl ? "pass" : "warn",
|
|
77
77
|
message: apiUrl || "Using default (https://xtra-security.vercel.app/api)",
|
|
78
78
|
});
|
|
79
|
-
//
|
|
79
|
+
// ── 4. API Reachability ──────────────────────────────────────────────────────
|
|
80
80
|
try {
|
|
81
81
|
const res = await axios_1.default.get(`${config.apiUrl}/health`, { timeout: 4000 });
|
|
82
82
|
checks.push({
|
|
83
83
|
name: "API Connectivity",
|
|
84
84
|
status: res.status === 200 ? "pass" : "warn",
|
|
85
|
-
message: `${config.apiUrl}
|
|
85
|
+
message: `${config.apiUrl} → ${res.status} ${res.statusText}`,
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
catch (e) {
|
|
89
89
|
const msg = e.code === "ECONNREFUSED"
|
|
90
|
-
? "Connection refused
|
|
90
|
+
? "Connection refused — is the server running?"
|
|
91
91
|
: e.code === "ETIMEDOUT"
|
|
92
|
-
? "Timed out
|
|
92
|
+
? "Timed out — server may be slow or unreachable"
|
|
93
93
|
: e.message;
|
|
94
94
|
checks.push({ name: "API Connectivity", status: "fail", message: msg });
|
|
95
95
|
}
|
|
96
|
-
//
|
|
97
|
-
const project = (0, config_1.
|
|
96
|
+
// ── 5. Active Project ────────────────────────────────────────────────────────
|
|
97
|
+
const project = (0, config_1.getRcConfig)().project;
|
|
98
98
|
checks.push({
|
|
99
99
|
name: "Active Project",
|
|
100
100
|
status: project ? "pass" : "warn",
|
|
101
|
-
message: project ? project : "Not set
|
|
101
|
+
message: project ? project : "Not set — run 'xtra project set <id>'",
|
|
102
102
|
});
|
|
103
|
-
//
|
|
104
|
-
const branch = (0, config_1.
|
|
103
|
+
// ── 6. Active Branch ─────────────────────────────────────────────────────────
|
|
104
|
+
const branch = (0, config_1.getRcConfig)().branch;
|
|
105
105
|
checks.push({
|
|
106
106
|
name: "Active Branch",
|
|
107
107
|
status: "pass",
|
|
108
108
|
message: branch || "main (default)",
|
|
109
109
|
});
|
|
110
|
-
//
|
|
110
|
+
// ── 7. .xtrarc / xtra.json ─────────────────────────────────────────────────
|
|
111
111
|
const rcPath = path.join(process.cwd(), ".xtrarc");
|
|
112
112
|
const jsonPath = path.join(process.cwd(), "xtra.json");
|
|
113
113
|
const hasProjectConfig = fs.existsSync(rcPath) || fs.existsSync(jsonPath);
|
|
@@ -116,21 +116,21 @@ async function runChecks() {
|
|
|
116
116
|
status: hasProjectConfig ? "pass" : "warn",
|
|
117
117
|
message: hasProjectConfig
|
|
118
118
|
? fs.existsSync(rcPath) ? ".xtrarc found" : "xtra.json found"
|
|
119
|
-
: "Not found
|
|
119
|
+
: "Not found — run 'xtra init' to create one",
|
|
120
120
|
});
|
|
121
|
-
//
|
|
121
|
+
// ── 8. .env file present ────────────────────────────────────────────────────
|
|
122
122
|
const envPath = path.join(process.cwd(), ".env");
|
|
123
123
|
checks.push({
|
|
124
124
|
name: ".env File",
|
|
125
125
|
status: "pass",
|
|
126
126
|
message: fs.existsSync(envPath) ? ".env found" : "No .env (not required if using xtra run)",
|
|
127
127
|
});
|
|
128
|
-
//
|
|
128
|
+
// ── 9. XTRA_MACHINE_TOKEN (for CI) ─────────────────────────────────────────
|
|
129
129
|
const machineToken = process.env.XTRA_MACHINE_TOKEN;
|
|
130
130
|
checks.push({
|
|
131
131
|
name: "CI Machine Token",
|
|
132
|
-
status: "pass", // Always pass
|
|
133
|
-
message: machineToken ? "XTRA_MACHINE_TOKEN is set
|
|
132
|
+
status: "pass", // Always pass — optional
|
|
133
|
+
message: machineToken ? "XTRA_MACHINE_TOKEN is set ✔" : "Not set (only needed for CI/CD pipelines)",
|
|
134
134
|
});
|
|
135
135
|
return checks;
|
|
136
136
|
}
|
|
@@ -139,7 +139,7 @@ exports.doctorCommand = new commander_1.Command("doctor")
|
|
|
139
139
|
.option("--json", "Output results as JSON", false)
|
|
140
140
|
.action(async (options) => {
|
|
141
141
|
if (!options.json) {
|
|
142
|
-
console.log(chalk_1.default.bold("\n
|
|
142
|
+
console.log(chalk_1.default.bold("\n🩺 xtra doctor — running diagnostics...\n"));
|
|
143
143
|
}
|
|
144
144
|
const checks = await runChecks();
|
|
145
145
|
if (options.json) {
|
|
@@ -164,7 +164,7 @@ exports.doctorCommand = new commander_1.Command("doctor")
|
|
|
164
164
|
}
|
|
165
165
|
console.log();
|
|
166
166
|
if (failures === 0 && warnings === 0) {
|
|
167
|
-
console.log(chalk_1.default.green.bold("
|
|
167
|
+
console.log(chalk_1.default.green.bold(" ✅ All checks passed! Your CLI is ready to use."));
|
|
168
168
|
}
|
|
169
169
|
else {
|
|
170
170
|
if (failures > 0)
|
package/dist/commands/env.js
CHANGED
|
@@ -21,9 +21,9 @@ exports.envCommand
|
|
|
21
21
|
.action(async (options) => {
|
|
22
22
|
try {
|
|
23
23
|
let projectId = options.project;
|
|
24
|
-
const branch = options.branch || (0, config_1.
|
|
24
|
+
const branch = options.branch || (0, config_1.getRcConfig)().branch || "main";
|
|
25
25
|
if (!projectId) {
|
|
26
|
-
projectId = (0, config_1.
|
|
26
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
27
27
|
if (!projectId) {
|
|
28
28
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
29
29
|
process.exit(1);
|
package/dist/commands/export.js
CHANGED
|
@@ -21,11 +21,10 @@ exports.exportCommand = new commander_1.Command("export")
|
|
|
21
21
|
.action(async (options) => {
|
|
22
22
|
let { project, env, branch, format, output } = options;
|
|
23
23
|
// Use config fallback
|
|
24
|
-
if (!project)
|
|
25
|
-
project = (0, config_1.
|
|
26
|
-
}
|
|
24
|
+
if (!project)
|
|
25
|
+
project = (0, config_1.getRcConfig)().project;
|
|
27
26
|
if (!branch) {
|
|
28
|
-
branch = (0, config_1.
|
|
27
|
+
branch = (0, config_1.getRcConfig)().branch || "main";
|
|
29
28
|
}
|
|
30
29
|
// Normalize Env
|
|
31
30
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
@@ -63,7 +62,7 @@ exports.exportCommand = new commander_1.Command("export")
|
|
|
63
62
|
if (output) {
|
|
64
63
|
const outputPath = path_1.default.resolve(process.cwd(), output);
|
|
65
64
|
fs_1.default.writeFileSync(outputPath, content, "utf-8");
|
|
66
|
-
console.log(chalk_1.default.green(
|
|
65
|
+
console.log(chalk_1.default.green(`✔ Secrets exported to ${outputPath}`));
|
|
67
66
|
}
|
|
68
67
|
else {
|
|
69
68
|
console.log(content);
|
|
@@ -80,11 +80,10 @@ exports.generateCommand = new commander_1.Command("generate")
|
|
|
80
80
|
.action(async (options) => {
|
|
81
81
|
let { project, env, branch, output, format, force } = options;
|
|
82
82
|
// Use config fallback
|
|
83
|
-
if (!project)
|
|
84
|
-
project = (0, config_1.
|
|
85
|
-
}
|
|
83
|
+
if (!project)
|
|
84
|
+
project = (0, config_1.getRcConfig)().project;
|
|
86
85
|
if (!branch) {
|
|
87
|
-
branch = (0, config_1.
|
|
86
|
+
branch = (0, config_1.getRcConfig)().branch || "main";
|
|
88
87
|
}
|
|
89
88
|
// Normalize Env
|
|
90
89
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
package/dist/commands/history.js
CHANGED
|
@@ -18,7 +18,7 @@ exports.historyCommand
|
|
|
18
18
|
try {
|
|
19
19
|
let projectId = options.project;
|
|
20
20
|
if (!projectId) {
|
|
21
|
-
projectId = (0, config_1.
|
|
21
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
22
22
|
if (!projectId) {
|
|
23
23
|
console.error(chalk_1.default.red("Error: Project ID not found. Use -p <id> or run 'xtra project set' first."));
|
|
24
24
|
process.exit(1);
|
|
@@ -56,7 +56,7 @@ exports.rollbackCommand
|
|
|
56
56
|
try {
|
|
57
57
|
let projectId = options.project;
|
|
58
58
|
if (!projectId) {
|
|
59
|
-
projectId = (0, config_1.
|
|
59
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
60
60
|
if (!projectId) {
|
|
61
61
|
console.error(chalk_1.default.red("Error: Project ID not found. Use -p <id> or run 'xtra project set' first."));
|
|
62
62
|
process.exit(1);
|
package/dist/commands/import.js
CHANGED
|
@@ -24,11 +24,10 @@ exports.importCommand = new commander_1.Command("import")
|
|
|
24
24
|
.action(async (file, options) => {
|
|
25
25
|
let { project, env, branch, format, prefix } = options;
|
|
26
26
|
// Use config fallback
|
|
27
|
-
if (!project)
|
|
28
|
-
project = (0, config_1.
|
|
29
|
-
}
|
|
27
|
+
if (!project)
|
|
28
|
+
project = (0, config_1.getRcConfig)().project;
|
|
30
29
|
if (!branch) {
|
|
31
|
-
branch = (0, config_1.
|
|
30
|
+
branch = (0, config_1.getRcConfig)().branch || "main";
|
|
32
31
|
}
|
|
33
32
|
// Normalize Env
|
|
34
33
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
@@ -23,7 +23,7 @@ exports.integrationCommand
|
|
|
23
23
|
try {
|
|
24
24
|
let projectId = options.project;
|
|
25
25
|
if (!projectId) {
|
|
26
|
-
projectId = (0, config_1.
|
|
26
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
27
27
|
if (!projectId) {
|
|
28
28
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
29
29
|
process.exit(1);
|
|
@@ -48,7 +48,7 @@ exports.integrationCommand
|
|
|
48
48
|
name: "repo",
|
|
49
49
|
message: "Select a repository to sync to:",
|
|
50
50
|
choices: repos.map((r) => ({
|
|
51
|
-
name: `${r.fullName} ${r.private ? "(
|
|
51
|
+
name: `${r.fullName} ${r.private ? "(🔒)" : ""}`,
|
|
52
52
|
value: r.id.toString(),
|
|
53
53
|
short: r.fullName
|
|
54
54
|
}))
|
|
@@ -84,10 +84,10 @@ exports.integrationCommand
|
|
|
84
84
|
// Show details
|
|
85
85
|
result.results.forEach((r) => {
|
|
86
86
|
if (r.success) {
|
|
87
|
-
console.log(chalk_1.default.green(
|
|
87
|
+
console.log(chalk_1.default.green(`✓ ${r.key}`));
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
console.log(chalk_1.default.red(
|
|
90
|
+
console.log(chalk_1.default.red(`✗ ${r.key}: ${r.error}`));
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -109,7 +109,7 @@ exports.kubernetesCommand
|
|
|
109
109
|
try {
|
|
110
110
|
let projectId = options.project;
|
|
111
111
|
if (!projectId) {
|
|
112
|
-
projectId = (0, config_1.
|
|
112
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
113
113
|
if (!projectId) {
|
|
114
114
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
115
115
|
process.exit(1);
|
|
@@ -153,7 +153,7 @@ exports.kubernetesCommand
|
|
|
153
153
|
}
|
|
154
154
|
let projectId = options.project;
|
|
155
155
|
if (!projectId) {
|
|
156
|
-
projectId = (0, config_1.
|
|
156
|
+
projectId = (0, config_1.getRcConfig)().project;
|
|
157
157
|
if (!projectId) {
|
|
158
158
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
159
159
|
process.exit(1);
|
package/dist/commands/local.js
CHANGED
|
@@ -38,12 +38,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.localCommand = void 0;
|
|
40
40
|
/**
|
|
41
|
-
* local.ts
|
|
41
|
+
* local.ts — Toggle between cloud/local-only mode for offline development
|
|
42
42
|
*
|
|
43
43
|
* In "local" mode:
|
|
44
44
|
* - xtra run reads from .env.local instead of calling the API
|
|
45
45
|
* - A flag XTRA_LOCAL_MODE=true is written to the CLI config
|
|
46
|
-
* - All API calls are bypassed
|
|
46
|
+
* - All API calls are bypassed — completely offline capable
|
|
47
47
|
*
|
|
48
48
|
* Usage:
|
|
49
49
|
* xtra local on # Enable local mode (reads from .env.local)
|
|
@@ -73,11 +73,11 @@ This allows fully offline development without any API calls.
|
|
|
73
73
|
Examples:
|
|
74
74
|
$ xtra local status # Check current mode
|
|
75
75
|
$ xtra local on # Enable offline (local) mode
|
|
76
|
-
$ xtra local off # Disable
|
|
77
|
-
$ xtra local sync # Pull cloud secrets
|
|
76
|
+
$ xtra local off # Disable — back to cloud mode
|
|
77
|
+
$ xtra local sync # Pull cloud secrets → .env.local
|
|
78
78
|
$ xtra local sync -p proj -e production # Pull production to .env.local
|
|
79
79
|
`);
|
|
80
|
-
//
|
|
80
|
+
// ── status ────────────────────────────────────────────────────────────────────
|
|
81
81
|
exports.localCommand
|
|
82
82
|
.command("status")
|
|
83
83
|
.description("Show current cloud/local mode")
|
|
@@ -86,7 +86,7 @@ exports.localCommand
|
|
|
86
86
|
const envFilePath = path.join(process.cwd(), LOCAL_ENV_FILE);
|
|
87
87
|
const hasLocalFile = fs.existsSync(envFilePath);
|
|
88
88
|
console.log(chalk_1.default.bold("\nMode Status:\n"));
|
|
89
|
-
console.log(` Mode : ${mode ? chalk_1.default.yellow("
|
|
89
|
+
console.log(` Mode : ${mode ? chalk_1.default.yellow("🔌 LOCAL (offline)") : chalk_1.default.green("☠CLOUD")}`);
|
|
90
90
|
console.log(` .env.local : ${hasLocalFile ? chalk_1.default.green("Found") : chalk_1.default.gray("Not found")}`);
|
|
91
91
|
console.log(` Config flag : ${chalk_1.default.gray((0, config_1.getConfigValue)("localMode") ? "true" : "false")}`);
|
|
92
92
|
console.log(` Env var : ${chalk_1.default.gray(process.env.XTRA_LOCAL_MODE || "(not set)")}`);
|
|
@@ -97,34 +97,34 @@ exports.localCommand
|
|
|
97
97
|
else {
|
|
98
98
|
console.log(chalk_1.default.gray(" Run 'xtra local off' to switch back to cloud mode."));
|
|
99
99
|
if (!hasLocalFile) {
|
|
100
|
-
console.log(chalk_1.default.yellow("
|
|
100
|
+
console.log(chalk_1.default.yellow(" âš No .env.local file found. Run 'xtra local sync' to pull secrets."));
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
console.log();
|
|
104
104
|
});
|
|
105
|
-
//
|
|
105
|
+
// ── on ────────────────────────────────────────────────────────────────────────
|
|
106
106
|
exports.localCommand
|
|
107
107
|
.command("on")
|
|
108
|
-
.description("Enable local mode
|
|
108
|
+
.description("Enable local mode — secrets read from .env.local")
|
|
109
109
|
.action(() => {
|
|
110
110
|
(0, config_1.setConfig)("localMode", true);
|
|
111
|
-
console.log(chalk_1.default.yellow("
|
|
111
|
+
console.log(chalk_1.default.yellow("🔌 Local mode ENABLED."));
|
|
112
112
|
console.log(chalk_1.default.gray(" 'xtra run' will now read from .env.local instead of the cloud."));
|
|
113
113
|
const localFilePath = path.join(process.cwd(), LOCAL_ENV_FILE);
|
|
114
114
|
if (!fs.existsSync(localFilePath)) {
|
|
115
|
-
console.log(chalk_1.default.yellow(`
|
|
115
|
+
console.log(chalk_1.default.yellow(` âš No .env.local file found. Run 'xtra local sync' to populate it.`));
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
|
-
//
|
|
118
|
+
// ── off ───────────────────────────────────────────────────────────────────────
|
|
119
119
|
exports.localCommand
|
|
120
120
|
.command("off")
|
|
121
|
-
.description("Disable local mode
|
|
121
|
+
.description("Disable local mode — secrets fetched from cloud again")
|
|
122
122
|
.action(() => {
|
|
123
123
|
(0, config_1.setConfig)("localMode", false);
|
|
124
|
-
console.log(chalk_1.default.green("
|
|
124
|
+
console.log(chalk_1.default.green("☠Cloud mode ENABLED."));
|
|
125
125
|
console.log(chalk_1.default.gray(" 'xtra run' will now fetch secrets from XtraSecurity Cloud."));
|
|
126
126
|
});
|
|
127
|
-
//
|
|
127
|
+
// ── sync ─────────────────────────────────────────────────────────────────────
|
|
128
128
|
exports.localCommand
|
|
129
129
|
.command("sync")
|
|
130
130
|
.description("Pull cloud secrets to .env.local for offline use")
|
|
@@ -138,7 +138,7 @@ exports.localCommand
|
|
|
138
138
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
139
139
|
env = envMap[env] || env;
|
|
140
140
|
if (!project)
|
|
141
|
-
project = (0, config_1.
|
|
141
|
+
project = (0, config_1.getRcConfig)().project;
|
|
142
142
|
if (!project) {
|
|
143
143
|
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id>."));
|
|
144
144
|
process.exit(1);
|
|
@@ -146,7 +146,7 @@ exports.localCommand
|
|
|
146
146
|
const outputPath = path.resolve(process.cwd(), output);
|
|
147
147
|
// Warn if production
|
|
148
148
|
if (env === "production" && !overwrite) {
|
|
149
|
-
console.log(chalk_1.default.red(
|
|
149
|
+
console.log(chalk_1.default.red(`âš You are syncing PRODUCTION secrets to ${output}.`));
|
|
150
150
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
151
151
|
const confirmed = await new Promise(resolve => {
|
|
152
152
|
rl.question(chalk_1.default.yellow("Type 'yes' to confirm: "), (ans) => {
|
|
@@ -170,14 +170,14 @@ exports.localCommand
|
|
|
170
170
|
}
|
|
171
171
|
// Write dotenv format
|
|
172
172
|
const lines = [
|
|
173
|
-
`# xtra local sync
|
|
173
|
+
`# xtra local sync — ${env}/${branch}`,
|
|
174
174
|
`# Generated: ${new Date().toISOString()}`,
|
|
175
175
|
`# DO NOT COMMIT THIS FILE`,
|
|
176
176
|
"",
|
|
177
177
|
...Object.entries(secrets).map(([k, v]) => `${k}=${v}`)
|
|
178
178
|
];
|
|
179
179
|
fs.writeFileSync(outputPath, lines.join("\n") + "\n", "utf8");
|
|
180
|
-
console.log(chalk_1.default.green(
|
|
180
|
+
console.log(chalk_1.default.green(`✅ Synced ${count} secrets to ${output}`));
|
|
181
181
|
console.log(chalk_1.default.gray(" Run 'xtra local on' to switch to local mode."));
|
|
182
182
|
// Remind about .gitignore
|
|
183
183
|
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
@@ -187,7 +187,7 @@ exports.localCommand
|
|
|
187
187
|
}
|
|
188
188
|
catch (_) { }
|
|
189
189
|
if (!gitignore.includes(output)) {
|
|
190
|
-
console.log(chalk_1.default.yellow(`\n
|
|
190
|
+
console.log(chalk_1.default.yellow(`\n âš Remember to add '${output}' to your .gitignore!`));
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
catch (e) {
|
|
@@ -19,9 +19,8 @@ exports.rollbackCommand = new commander_1.Command("rollback")
|
|
|
19
19
|
.action(async (key, options) => {
|
|
20
20
|
let { project, env } = options;
|
|
21
21
|
// Use config fallback
|
|
22
|
-
if (!project)
|
|
23
|
-
project = (0, config_1.
|
|
24
|
-
}
|
|
22
|
+
if (!project)
|
|
23
|
+
project = (0, config_1.getRcConfig)().project;
|
|
25
24
|
if (!project) {
|
|
26
25
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
27
26
|
process.exit(1);
|
package/dist/commands/rotate.js
CHANGED
|
@@ -20,9 +20,8 @@ exports.rotateCommand = new commander_1.Command("rotate")
|
|
|
20
20
|
.action(async (key, options) => {
|
|
21
21
|
let { project, env, strategy, promote, value } = options;
|
|
22
22
|
// Use config fallback
|
|
23
|
-
if (!project)
|
|
24
|
-
project = (0, config_1.
|
|
25
|
-
}
|
|
23
|
+
if (!project)
|
|
24
|
+
project = (0, config_1.getRcConfig)().project;
|
|
26
25
|
// Normalize Env
|
|
27
26
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
28
27
|
env = envMap[env] || env;
|
|
@@ -36,7 +35,7 @@ exports.rotateCommand = new commander_1.Command("rotate")
|
|
|
36
35
|
const { confirmProd } = await inquirer.prompt([{
|
|
37
36
|
type: "confirm",
|
|
38
37
|
name: "confirmProd",
|
|
39
|
-
message: chalk_1.default.red(
|
|
38
|
+
message: chalk_1.default.red(`âš You are about to rotate a secret in PRODUCTION (${key}). Proceed?`),
|
|
40
39
|
default: false,
|
|
41
40
|
}]);
|
|
42
41
|
if (!confirmProd) {
|
package/dist/commands/run.js
CHANGED
|
@@ -65,26 +65,16 @@ Examples:
|
|
|
65
65
|
.action(async (command, args, options) => {
|
|
66
66
|
// console.log("Run Options:", options);
|
|
67
67
|
let { project, env, branch, shell: useShell } = options;
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
let rcConfig = {};
|
|
71
|
-
if (fs.existsSync(rcPath)) {
|
|
72
|
-
try {
|
|
73
|
-
rcConfig = JSON.parse(fs.readFileSync(rcPath, "utf8"));
|
|
74
|
-
}
|
|
75
|
-
catch (_) { }
|
|
76
|
-
}
|
|
68
|
+
// Load .xtrarc from CWD first (project-local config), then fall back to global conf store
|
|
69
|
+
const rc = (0, config_1.getRcConfig)();
|
|
77
70
|
// Use active branch from config if not specified
|
|
78
|
-
if (!branch)
|
|
79
|
-
branch =
|
|
80
|
-
}
|
|
71
|
+
if (!branch)
|
|
72
|
+
branch = rc.branch;
|
|
81
73
|
// Normalize Env
|
|
82
74
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
83
|
-
env = envMap[env] || env ||
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
project = rcConfig.project || (0, config_1.getConfigValue)("project");
|
|
87
|
-
}
|
|
75
|
+
env = envMap[env] || env || rc.env;
|
|
76
|
+
if (!project)
|
|
77
|
+
project = rc.project;
|
|
88
78
|
if (!project) {
|
|
89
79
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or checkout a branch."));
|
|
90
80
|
process.exit(1);
|
package/dist/commands/secrets.js
CHANGED
|
@@ -28,19 +28,20 @@ Examples:
|
|
|
28
28
|
.action(async (options) => {
|
|
29
29
|
const parentOpts = exports.secretsCommand.opts();
|
|
30
30
|
let { project, env, branch } = parentOpts;
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// Load .xtrarc from CWD (local project config has priority over global conf store)
|
|
32
|
+
const rc = (0, config_1.getRcConfig)();
|
|
33
|
+
if (!project)
|
|
34
|
+
project = rc.project;
|
|
35
|
+
if (!branch)
|
|
36
|
+
branch = rc.branch;
|
|
37
|
+
if (!env || env === "development")
|
|
38
|
+
env = rc.env; // only override if still at default
|
|
35
39
|
// Normalize Env
|
|
36
40
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
37
41
|
env = envMap[env] || env;
|
|
38
42
|
const { show } = options;
|
|
39
43
|
if (!project) {
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
if (!project) {
|
|
43
|
-
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or checkout a branch."));
|
|
44
|
+
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run `xtra init` first."));
|
|
44
45
|
process.exit(1);
|
|
45
46
|
}
|
|
46
47
|
const spinner = (0, ora_1.default)(`Fetching secrets for ${env} (branch: ${branch})...`).start();
|
|
@@ -5,10 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.simulateCommand = void 0;
|
|
7
7
|
/**
|
|
8
|
-
* simulate.ts
|
|
8
|
+
* simulate.ts — Dry-run mode for xtra run
|
|
9
9
|
*
|
|
10
10
|
* Fetches secrets and shows exactly what `xtra run` would inject into
|
|
11
|
-
* the process environment
|
|
11
|
+
* the process environment — without actually executing the command.
|
|
12
12
|
* Safe to use in any environment.
|
|
13
13
|
*/
|
|
14
14
|
const commander_1 = require("commander");
|
|
@@ -30,7 +30,7 @@ exports.simulateCommand = new commander_1.Command("simulate")
|
|
|
30
30
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
31
31
|
env = envMap[env] || env;
|
|
32
32
|
if (!project)
|
|
33
|
-
project = (0, config_1.
|
|
33
|
+
project = (0, config_1.getRcConfig)().project;
|
|
34
34
|
if (!project) {
|
|
35
35
|
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id> or run 'xtra project set'."));
|
|
36
36
|
process.exit(1);
|
|
@@ -47,7 +47,7 @@ exports.simulateCommand = new commander_1.Command("simulate")
|
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
const count = Object.keys(secrets).length;
|
|
50
|
-
console.log(chalk_1.default.bold(`\n
|
|
50
|
+
console.log(chalk_1.default.bold(`\n🔬 Simulation — xtra run ${command ? chalk_1.default.cyan(command) : "(no command specified)"}\n`));
|
|
51
51
|
console.log(chalk_1.default.gray(` Project : ${project}`));
|
|
52
52
|
console.log(chalk_1.default.gray(` Env : ${env}`));
|
|
53
53
|
console.log(chalk_1.default.gray(` Branch : ${branch}`));
|
|
@@ -61,16 +61,16 @@ exports.simulateCommand = new commander_1.Command("simulate")
|
|
|
61
61
|
];
|
|
62
62
|
for (const [key, value] of Object.entries(secrets)) {
|
|
63
63
|
const localVal = process.env[key];
|
|
64
|
-
const displayVal = showValues ? chalk_1.default.cyan(value) : chalk_1.default.gray("
|
|
64
|
+
const displayVal = showValues ? chalk_1.default.cyan(value) : chalk_1.default.gray("••••••••");
|
|
65
65
|
let status = chalk_1.default.green("Injected");
|
|
66
66
|
let localDisplay = "-";
|
|
67
67
|
if (showDiff && localVal !== undefined) {
|
|
68
|
-
localDisplay = showValues ? chalk_1.default.yellow(localVal) : chalk_1.default.gray("
|
|
68
|
+
localDisplay = showValues ? chalk_1.default.yellow(localVal) : chalk_1.default.gray("••••••••");
|
|
69
69
|
if (localVal === value) {
|
|
70
70
|
status = chalk_1.default.gray("Same");
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
status = chalk_1.default.yellow("
|
|
73
|
+
status = chalk_1.default.yellow("âš¡ Override");
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
else if (showDiff) {
|
|
@@ -87,6 +87,6 @@ exports.simulateCommand = new commander_1.Command("simulate")
|
|
|
87
87
|
if (!showDiff) {
|
|
88
88
|
console.log(chalk_1.default.gray(" Tip: use --diff to compare against your local process.env"));
|
|
89
89
|
}
|
|
90
|
-
console.log(chalk_1.default.bold(`\n
|
|
90
|
+
console.log(chalk_1.default.bold(`\n ✅ Simulation complete. ${count} secret(s) would be injected.`));
|
|
91
91
|
console.log(chalk_1.default.gray(" Run without 'simulate' to actually execute the command.\n"));
|
|
92
92
|
});
|
package/dist/commands/status.js
CHANGED
|
@@ -20,11 +20,10 @@ exports.statusCommand = new commander_1.Command("status")
|
|
|
20
20
|
.action(async (options) => {
|
|
21
21
|
let { project, env, branch } = options;
|
|
22
22
|
// Use config fallback
|
|
23
|
-
if (!project)
|
|
24
|
-
project = (0, config_1.
|
|
25
|
-
}
|
|
23
|
+
if (!project)
|
|
24
|
+
project = (0, config_1.getRcConfig)().project;
|
|
26
25
|
if (!branch) {
|
|
27
|
-
branch = (0, config_1.
|
|
26
|
+
branch = (0, config_1.getRcConfig)().branch || "main";
|
|
28
27
|
}
|
|
29
28
|
if (!project) {
|
|
30
29
|
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
@@ -76,15 +75,15 @@ exports.statusCommand = new commander_1.Command("status")
|
|
|
76
75
|
console.log("");
|
|
77
76
|
console.log((0, table_1.table)(rows));
|
|
78
77
|
if (diffCount === 0) {
|
|
79
|
-
console.log(chalk_1.default.green("
|
|
78
|
+
console.log(chalk_1.default.green("✔ Everything is in sync."));
|
|
80
79
|
}
|
|
81
80
|
else {
|
|
82
|
-
console.log(chalk_1.default.yellow(
|
|
81
|
+
console.log(chalk_1.default.yellow(`âš Found ${diffCount} difference(s).\n`));
|
|
83
82
|
console.log(chalk_1.default.bold(" Next steps:"));
|
|
84
|
-
console.log(chalk_1.default.gray(" xtra generate ") + chalk_1.default.white("# pull cloud secrets
|
|
85
|
-
console.log(chalk_1.default.gray(" xtra generate -f json ") + chalk_1.default.white("# pull cloud secrets
|
|
83
|
+
console.log(chalk_1.default.gray(" xtra generate ") + chalk_1.default.white("# pull cloud secrets → .env (merge)"));
|
|
84
|
+
console.log(chalk_1.default.gray(" xtra generate -f json ") + chalk_1.default.white("# pull cloud secrets → secrets.json"));
|
|
86
85
|
console.log(chalk_1.default.gray(" xtra run node app.js ") + chalk_1.default.white("# inject secrets at runtime (no file)"));
|
|
87
|
-
console.log(chalk_1.default.gray(" xtra local sync ") + chalk_1.default.white("# pull cloud secrets
|
|
86
|
+
console.log(chalk_1.default.gray(" xtra local sync ") + chalk_1.default.white("# pull cloud secrets → .env.local (offline mode)"));
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
89
|
catch (error) {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* template.ts
|
|
3
|
+
* template.ts — Secret Templating Engine for xtra-cli
|
|
4
4
|
*
|
|
5
5
|
* Reads a template file containing {{ secrets.KEY }} placeholders,
|
|
6
6
|
* fetches secrets from XtraSecurity Cloud, substitutes all placeholders,
|
|
7
7
|
* and writes the rendered output to the specified file (or stdout).
|
|
8
8
|
*
|
|
9
9
|
* Supported placeholder syntaxes:
|
|
10
|
-
* {{ secrets.DATABASE_URL }}
|
|
11
|
-
* {{ secrets.PORT | 3000 }}
|
|
12
|
-
* {{ env.NODE_ENV }}
|
|
10
|
+
* {{ secrets.DATABASE_URL }} → fetches DATABASE_URL from secrets
|
|
11
|
+
* {{ secrets.PORT | 3000 }} → with fallback default value "3000"
|
|
12
|
+
* {{ env.NODE_ENV }} → reads from local process environment
|
|
13
13
|
*
|
|
14
14
|
* Example:
|
|
15
15
|
* # config.yaml.tpl
|
|
@@ -70,7 +70,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
70
70
|
const ora_1 = __importDefault(require("ora"));
|
|
71
71
|
const fs = __importStar(require("fs"));
|
|
72
72
|
const path = __importStar(require("path"));
|
|
73
|
-
//
|
|
73
|
+
// ─── Placeholder Regex ────────────────────────────────────────────────────────
|
|
74
74
|
// Matches: {{ secrets.KEY }}, {{ secrets.KEY | default }}, {{ env.KEY | default }}
|
|
75
75
|
const PLACEHOLDER_RE = /\{\{\s*(secrets|env)\.([A-Z0-9_a-z]+)(?:\s*\|\s*([^}]*?))?\s*\}\}/g;
|
|
76
76
|
function renderTemplate(template, secrets, processEnv) {
|
|
@@ -91,18 +91,18 @@ function renderTemplate(template, secrets, processEnv) {
|
|
|
91
91
|
}
|
|
92
92
|
if (defaultVal !== undefined) {
|
|
93
93
|
const trimmedDefault = defaultVal.trim();
|
|
94
|
-
usedDefaults.push(`${source}.${key}
|
|
94
|
+
usedDefaults.push(`${source}.${key} → "${trimmedDefault}"`);
|
|
95
95
|
replacedCount++;
|
|
96
96
|
return trimmedDefault;
|
|
97
97
|
}
|
|
98
98
|
missingKeys.push(`${source}.${key}`);
|
|
99
|
-
return `{{ ${source}.${key} }}`; // Leave unreplaced
|
|
99
|
+
return `{{ ${source}.${key} }}`; // Leave unreplaced — user will see it clearly
|
|
100
100
|
});
|
|
101
101
|
return { output, replacedCount, missingKeys, usedDefaults };
|
|
102
102
|
}
|
|
103
|
-
//
|
|
103
|
+
// ─── Command ──────────────────────────────────────────────────────────────────
|
|
104
104
|
exports.templateCommand = new commander_1.Command("template")
|
|
105
|
-
.description("Secret templating engine
|
|
105
|
+
.description("Secret templating engine — inject secrets into config file templates")
|
|
106
106
|
.addHelpText("after", `
|
|
107
107
|
Template Syntax:
|
|
108
108
|
{{ secrets.KEY }} Replace with secret value
|
|
@@ -116,7 +116,7 @@ Examples:
|
|
|
116
116
|
$ xtra template check config.yaml.tpl -p proj123 -e production
|
|
117
117
|
$ xtra template list config.yaml.tpl
|
|
118
118
|
`);
|
|
119
|
-
//
|
|
119
|
+
// ── render ────────────────────────────────────────────────────────────────────
|
|
120
120
|
exports.templateCommand
|
|
121
121
|
.command("render <templateFile>")
|
|
122
122
|
.description("Render a template file by injecting secrets and environment variables")
|
|
@@ -131,7 +131,7 @@ exports.templateCommand
|
|
|
131
131
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
132
132
|
env = envMap[env] || env;
|
|
133
133
|
if (!project)
|
|
134
|
-
project = (0, config_1.
|
|
134
|
+
project = (0, config_1.getRcConfig)().project;
|
|
135
135
|
if (!project) {
|
|
136
136
|
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id> or run 'xtra project set'."));
|
|
137
137
|
process.exit(1);
|
|
@@ -163,12 +163,12 @@ exports.templateCommand
|
|
|
163
163
|
// Render
|
|
164
164
|
const { output: rendered, replacedCount, missingKeys, usedDefaults } = renderTemplate(template, secrets, process.env);
|
|
165
165
|
// Report
|
|
166
|
-
console.log(chalk_1.default.green(
|
|
166
|
+
console.log(chalk_1.default.green(`✅ Replaced ${replacedCount} placeholder(s).`));
|
|
167
167
|
if (usedDefaults.length > 0) {
|
|
168
|
-
console.log(chalk_1.default.yellow(
|
|
168
|
+
console.log(chalk_1.default.yellow(`âš Used defaults for: ${usedDefaults.join(", ")}`));
|
|
169
169
|
}
|
|
170
170
|
if (missingKeys.length > 0) {
|
|
171
|
-
console.log(chalk_1.default.red(
|
|
171
|
+
console.log(chalk_1.default.red(`✗ Unresolved placeholders: ${missingKeys.join(", ")}`));
|
|
172
172
|
if (strict) {
|
|
173
173
|
console.error(chalk_1.default.red("Aborting due to --strict mode."));
|
|
174
174
|
process.exit(1);
|
|
@@ -178,7 +178,7 @@ exports.templateCommand
|
|
|
178
178
|
if (output) {
|
|
179
179
|
const outPath = path.resolve(process.cwd(), output);
|
|
180
180
|
fs.writeFileSync(outPath, rendered, "utf8");
|
|
181
|
-
console.log(chalk_1.default.blue(
|
|
181
|
+
console.log(chalk_1.default.blue(`→ Written to: ${outPath}`));
|
|
182
182
|
}
|
|
183
183
|
else {
|
|
184
184
|
// Print to stdout (so user can pipe it)
|
|
@@ -196,7 +196,7 @@ exports.templateCommand
|
|
|
196
196
|
}
|
|
197
197
|
catch (e) { }
|
|
198
198
|
});
|
|
199
|
-
//
|
|
199
|
+
// ── check ─────────────────────────────────────────────────────────────────────
|
|
200
200
|
exports.templateCommand
|
|
201
201
|
.command("check <templateFile>")
|
|
202
202
|
.description("Validate that all template placeholders have matching secrets (dry-run)")
|
|
@@ -208,7 +208,7 @@ exports.templateCommand
|
|
|
208
208
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
209
209
|
env = envMap[env] || env;
|
|
210
210
|
if (!project)
|
|
211
|
-
project = (0, config_1.
|
|
211
|
+
project = (0, config_1.getRcConfig)().project;
|
|
212
212
|
if (!project) {
|
|
213
213
|
console.error(chalk_1.default.red("Error: Project ID required."));
|
|
214
214
|
process.exit(1);
|
|
@@ -242,14 +242,14 @@ exports.templateCommand
|
|
|
242
242
|
}
|
|
243
243
|
if (missingKeys.length > 0) {
|
|
244
244
|
console.log(chalk_1.default.red(`\n Missing secrets (no default):`));
|
|
245
|
-
missingKeys.forEach(k => console.log(chalk_1.default.red(`
|
|
245
|
+
missingKeys.forEach(k => console.log(chalk_1.default.red(` ✗ ${k}`)));
|
|
246
246
|
process.exit(1);
|
|
247
247
|
}
|
|
248
248
|
else {
|
|
249
|
-
console.log(chalk_1.default.green("\n
|
|
249
|
+
console.log(chalk_1.default.green("\n ✅ All placeholders are resolvable!"));
|
|
250
250
|
}
|
|
251
251
|
});
|
|
252
|
-
//
|
|
252
|
+
// ── list ──────────────────────────────────────────────────────────────────────
|
|
253
253
|
exports.templateCommand
|
|
254
254
|
.command("list <templateFile>")
|
|
255
255
|
.description("List all placeholders found in a template file (no API call needed)")
|
|
@@ -269,7 +269,7 @@ exports.templateCommand
|
|
|
269
269
|
matches.forEach(m => {
|
|
270
270
|
const [, source, key, def] = m;
|
|
271
271
|
const defaultHint = def ? chalk_1.default.gray(` (default: "${def.trim()}")`) : "";
|
|
272
|
-
const icon = source === "secrets" ? "
|
|
272
|
+
const icon = source === "secrets" ? "🔒" : "📦";
|
|
273
273
|
console.log(` ${icon} ${chalk_1.default.cyan(`{{ ${source}.${key} }}`)}${defaultHint}`);
|
|
274
274
|
});
|
|
275
275
|
console.log(`\n Total: ${chalk_1.default.bold(matches.length)} placeholder(s)\n`);
|
package/dist/commands/ui.js
CHANGED
|
@@ -42,39 +42,39 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
42
42
|
const readline = __importStar(require("readline"));
|
|
43
43
|
const api_1 = require("../lib/api");
|
|
44
44
|
const ENVS = ["development", "staging", "production"];
|
|
45
|
-
//
|
|
45
|
+
// ─── Rendering ────────────────────────────────────────────────────────────────
|
|
46
46
|
function clear() { process.stdout.write("\x1Bc"); }
|
|
47
47
|
function renderHeader() {
|
|
48
|
-
console.log(chalk_1.default.cyan("
|
|
49
|
-
console.log(chalk_1.default.cyan("
|
|
50
|
-
console.log(chalk_1.default.cyan("
|
|
51
|
-
console.log(chalk_1.default.gray("
|
|
48
|
+
console.log(chalk_1.default.cyan("â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•—"));
|
|
49
|
+
console.log(chalk_1.default.cyan("║") + chalk_1.default.bold.cyan(" 🔒 XtraSecurity — Interactive Shell ") + chalk_1.default.cyan("║"));
|
|
50
|
+
console.log(chalk_1.default.cyan("╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•"));
|
|
51
|
+
console.log(chalk_1.default.gray(" ↑↓ Navigate • Enter Select • Tab Switch Panel • Q Quit\n"));
|
|
52
52
|
}
|
|
53
53
|
function renderList(title, items, selectedIdx, active) {
|
|
54
54
|
const border = active ? chalk_1.default.cyan : chalk_1.default.gray;
|
|
55
|
-
console.log(border(
|
|
55
|
+
console.log(border(`┌─ ${title} ${"─".repeat(Math.max(0, 28 - title.length))}â”`));
|
|
56
56
|
items.forEach((item, i) => {
|
|
57
57
|
const isSelected = i === selectedIdx;
|
|
58
|
-
const icon = isSelected ? (active ? chalk_1.default.cyan("
|
|
58
|
+
const icon = isSelected ? (active ? chalk_1.default.cyan("â–¶ ") : chalk_1.default.gray("â–¶ ")) : " ";
|
|
59
59
|
const text = isSelected && active ? chalk_1.default.bold.cyan(item) : isSelected ? chalk_1.default.cyan(item) : chalk_1.default.white(item);
|
|
60
|
-
console.log(border("
|
|
60
|
+
console.log(border("│") + ` ${icon}${text}`.padEnd(30) + border("│"));
|
|
61
61
|
});
|
|
62
|
-
console.log(border(
|
|
62
|
+
console.log(border(`└${"─".repeat(32)}┘`));
|
|
63
63
|
}
|
|
64
64
|
function renderSecrets(secrets, selectedIdx, active) {
|
|
65
65
|
const border = active ? chalk_1.default.cyan : chalk_1.default.gray;
|
|
66
66
|
const COLS = [36, 12, 20];
|
|
67
|
-
const sep = "
|
|
68
|
-
console.log(border(
|
|
67
|
+
const sep = "─".repeat(COLS[0]) + "┬" + "─".repeat(COLS[1]) + "┬" + "─".repeat(COLS[2]);
|
|
68
|
+
console.log(border(`┌${sep}â”`));
|
|
69
69
|
const header = [
|
|
70
70
|
chalk_1.default.bold.yellow("KEY".padEnd(COLS[0])),
|
|
71
71
|
chalk_1.default.bold.yellow("VALUE".padEnd(COLS[1])),
|
|
72
72
|
chalk_1.default.bold.yellow("UPDATED".padEnd(COLS[2]))
|
|
73
|
-
].join(border("
|
|
74
|
-
console.log(border("
|
|
75
|
-
console.log(border(
|
|
73
|
+
].join(border("│"));
|
|
74
|
+
console.log(border("│") + header + border("│"));
|
|
75
|
+
console.log(border(`├${sep}┤`));
|
|
76
76
|
if (secrets.length === 0) {
|
|
77
|
-
console.log(border("
|
|
77
|
+
console.log(border("│") + chalk_1.default.gray(" No secrets found.".padEnd(COLS[0] + COLS[1] + COLS[2] + 2)) + border("│"));
|
|
78
78
|
}
|
|
79
79
|
else {
|
|
80
80
|
secrets.forEach((s, i) => {
|
|
@@ -87,16 +87,16 @@ function renderSecrets(secrets, selectedIdx, active) {
|
|
|
87
87
|
fmt(s.key, COLS[0]),
|
|
88
88
|
fmt("*".repeat(8), COLS[1]),
|
|
89
89
|
fmt(new Date(s.updatedAt).toLocaleDateString(), COLS[2]),
|
|
90
|
-
].join(border("
|
|
91
|
-
console.log(border("
|
|
90
|
+
].join(border("│"));
|
|
91
|
+
console.log(border("│") + row + border("│"));
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
console.log(border(
|
|
94
|
+
console.log(border(`└${"─".repeat(COLS[0])}┴${"─".repeat(COLS[1])}┴${"─".repeat(COLS[2])}┘`));
|
|
95
95
|
}
|
|
96
96
|
function renderStatus(msg) {
|
|
97
|
-
console.log("\n" + chalk_1.default.gray("
|
|
97
|
+
console.log("\n" + chalk_1.default.gray(" â— ") + chalk_1.default.white(msg));
|
|
98
98
|
}
|
|
99
|
-
//
|
|
99
|
+
// ─── Main TUI Loop ────────────────────────────────────────────────────────────
|
|
100
100
|
async function runUI() {
|
|
101
101
|
let screen = "project";
|
|
102
102
|
let projects = [];
|
|
@@ -110,7 +110,7 @@ async function runUI() {
|
|
|
110
110
|
try {
|
|
111
111
|
projects = await api_1.api.getProjects();
|
|
112
112
|
status = projects.length > 0
|
|
113
|
-
? `Loaded ${projects.length} projects. Use
|
|
113
|
+
? `Loaded ${projects.length} projects. Use ↑↓ and Enter to navigate.`
|
|
114
114
|
: "No projects found. Try running `xtra login`.";
|
|
115
115
|
}
|
|
116
116
|
catch (e) {
|
|
@@ -145,7 +145,7 @@ async function runUI() {
|
|
|
145
145
|
renderList("ENVIRONMENT", ENVS, envIdx, screen === "env");
|
|
146
146
|
console.log();
|
|
147
147
|
if (loading) {
|
|
148
|
-
console.log(chalk_1.default.cyan("
|
|
148
|
+
console.log(chalk_1.default.cyan(" â ‹ Loading secrets..."));
|
|
149
149
|
}
|
|
150
150
|
else {
|
|
151
151
|
renderSecrets(secrets, secretIdx, screen === "secrets");
|
|
@@ -164,7 +164,7 @@ async function runUI() {
|
|
|
164
164
|
if (process.stdin.isTTY)
|
|
165
165
|
process.stdin.setRawMode(false);
|
|
166
166
|
clear();
|
|
167
|
-
console.log(chalk_1.default.cyan("Goodbye!
|
|
167
|
+
console.log(chalk_1.default.cyan("Goodbye! 👋"));
|
|
168
168
|
process.exit(0);
|
|
169
169
|
}
|
|
170
170
|
// Tab to cycle panels
|
|
@@ -210,7 +210,7 @@ async function runUI() {
|
|
|
210
210
|
else
|
|
211
211
|
draw();
|
|
212
212
|
}
|
|
213
|
-
//
|
|
213
|
+
// ─── Commander Command ────────────────────────────────────────────────────────
|
|
214
214
|
exports.uiCommand = new commander_1.Command("ui")
|
|
215
215
|
.description("Launch interactive TUI secrets dashboard (arrow keys to navigate, Q to quit)")
|
|
216
216
|
.action(async () => {
|
package/dist/commands/watch.js
CHANGED
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.watchCommand = void 0;
|
|
7
7
|
/**
|
|
8
|
-
* watch.ts
|
|
8
|
+
* watch.ts — Live reload secrets in dev mode
|
|
9
9
|
*
|
|
10
10
|
* Polls XtraSecurity Cloud for secret changes at a configurable interval.
|
|
11
11
|
* When a change is detected, restarts the child process with fresh secrets.
|
|
@@ -26,7 +26,7 @@ function hashSecrets(secrets) {
|
|
|
26
26
|
}
|
|
27
27
|
async function startProcess(command, args, secrets, useShell) {
|
|
28
28
|
if (child) {
|
|
29
|
-
process.stdout.write(chalk_1.default.yellow("\n [watch] Secret change detected
|
|
29
|
+
process.stdout.write(chalk_1.default.yellow("\n [watch] Secret change detected — restarting...\n"));
|
|
30
30
|
child.kill("SIGTERM");
|
|
31
31
|
// Give it 500ms to terminate gracefully
|
|
32
32
|
await new Promise(r => setTimeout(r, 500));
|
|
@@ -50,7 +50,7 @@ async function startProcess(command, args, secrets, useShell) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
exports.watchCommand = new commander_1.Command("watch")
|
|
53
|
-
.description("Live reload
|
|
53
|
+
.description("Live reload — auto-restart process when secrets change in cloud")
|
|
54
54
|
.option("-p, --project <id>", "Project ID")
|
|
55
55
|
.option("-e, --env <environment>", "Environment", "development")
|
|
56
56
|
.option("-b, --branch <branch>", "Branch", "main")
|
|
@@ -69,19 +69,19 @@ Examples:
|
|
|
69
69
|
const envMap = { dev: "development", stg: "staging", prod: "production" };
|
|
70
70
|
env = envMap[env] || env;
|
|
71
71
|
if (!project)
|
|
72
|
-
project = (0, config_1.
|
|
72
|
+
project = (0, config_1.getRcConfig)().project;
|
|
73
73
|
if (!project) {
|
|
74
74
|
console.error(chalk_1.default.red("Error: Project ID required. Use -p <id> or run 'xtra project set'."));
|
|
75
75
|
process.exit(1);
|
|
76
76
|
}
|
|
77
|
-
// Block production watch
|
|
77
|
+
// Block production watch — too dangerous
|
|
78
78
|
if (env === "production") {
|
|
79
|
-
console.error(chalk_1.default.red("
|
|
79
|
+
console.error(chalk_1.default.red("âš xtra watch is not allowed in PRODUCTION for safety reasons."));
|
|
80
80
|
console.error(chalk_1.default.gray(" Use xtra run for one-shot production injection."));
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
const pollMs = Math.max(3, parseInt(interval)) * 1000;
|
|
84
|
-
console.log(chalk_1.default.bold(`\n
|
|
84
|
+
console.log(chalk_1.default.bold(`\n👠xtra watch — watching ${env}/${branch} (every ${interval}s)\n`));
|
|
85
85
|
console.log(chalk_1.default.gray(` Press Ctrl+C to stop.\n`));
|
|
86
86
|
// Graceful shutdown
|
|
87
87
|
process.on("SIGINT", () => {
|
|
@@ -110,12 +110,12 @@ Examples:
|
|
|
110
110
|
await startProcess(command, args, secrets, useShell);
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
process.stdout.write(chalk_1.default.gray(` [watch] ${new Date().toLocaleTimeString()}
|
|
113
|
+
process.stdout.write(chalk_1.default.gray(` [watch] ${new Date().toLocaleTimeString()} — no changes\r`));
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
catch (e) {
|
|
117
117
|
process.stdout.write(chalk_1.default.yellow(`\n [watch] Poll failed: ${e.message}\n`));
|
|
118
|
-
// Don't exit
|
|
118
|
+
// Don't exit — keep trying
|
|
119
119
|
}
|
|
120
120
|
}, pollMs);
|
|
121
121
|
});
|
package/dist/lib/config.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getProjectConfig = exports.getAuthToken = exports.clearConfig = exports.setConfig = exports.getConfigValue = exports.getConfig = void 0;
|
|
6
|
+
exports.getRcConfig = exports.getProjectConfig = exports.getAuthToken = exports.clearConfig = exports.setConfig = exports.getConfigValue = exports.getConfig = void 0;
|
|
7
7
|
const conf_1 = __importDefault(require("conf"));
|
|
8
8
|
const PRODUCTION_API_URL = "https://xtra-security.vercel.app/api";
|
|
9
9
|
const config = new conf_1.default({
|
|
@@ -47,3 +47,24 @@ const getProjectConfig = async () => {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
exports.getProjectConfig = getProjectConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Reads .xtrarc from the current working directory.
|
|
52
|
+
* Returns project/env/branch with fallback to the global conf store.
|
|
53
|
+
* All commands should use this instead of calling getConfigValue() directly.
|
|
54
|
+
*/
|
|
55
|
+
const getRcConfig = () => {
|
|
56
|
+
let rc = {};
|
|
57
|
+
try {
|
|
58
|
+
const rcPath = path_1.default.join(process.cwd(), ".xtrarc");
|
|
59
|
+
if (fs_1.default.existsSync(rcPath)) {
|
|
60
|
+
rc = JSON.parse(fs_1.default.readFileSync(rcPath, "utf-8"));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (_) { }
|
|
64
|
+
return {
|
|
65
|
+
project: rc.project || config.get("project") || "",
|
|
66
|
+
env: rc.env || config.get("env") || "development",
|
|
67
|
+
branch: rc.branch || config.get("branch") || "main",
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
exports.getRcConfig = getRcConfig;
|