@revstackhq/cli 0.0.0-dev-20260226054346 → 0.0.0-dev-20260226061807
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/.turbo/turbo-build.log +31 -7
- package/CHANGELOG.md +12 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +95 -61
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +146 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +11 -0
- package/dist/commands/login.js +49 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +10 -0
- package/dist/commands/logout.js +45 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/pull.d.ts +12 -0
- package/dist/commands/pull.js +194 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +12 -0
- package/dist/commands/push.js +192 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/utils/auth.d.ts +20 -0
- package/dist/utils/auth.js +42 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/config-loader.d.ts +15 -0
- package/dist/utils/config-loader.js +33 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/package.json +10 -3
- package/src/commands/init.ts +61 -46
- package/src/commands/pull.ts +51 -37
- package/tests/integration/init.test.ts +36 -3
- package/tests/integration/pull.test.ts +25 -5
- package/tsup.config.ts +8 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/push.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk2 from "chalk";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
|
|
9
|
+
// src/utils/auth.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".revstack");
|
|
14
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
15
|
+
function getApiKey() {
|
|
16
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
21
|
+
const credentials = JSON.parse(raw);
|
|
22
|
+
return credentials.apiKey ?? null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/utils/config-loader.ts
|
|
29
|
+
import { createJiti } from "jiti";
|
|
30
|
+
import path2 from "path";
|
|
31
|
+
import chalk from "chalk";
|
|
32
|
+
async function loadLocalConfig(cwd) {
|
|
33
|
+
const configPath = path2.resolve(cwd, "revstack.config.ts");
|
|
34
|
+
try {
|
|
35
|
+
const jiti = createJiti(cwd);
|
|
36
|
+
const module = await jiti.import(configPath);
|
|
37
|
+
const config = module.default ?? module;
|
|
38
|
+
return JSON.parse(JSON.stringify(config));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const err = error;
|
|
41
|
+
if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "ENOENT" || err.code === "MODULE_NOT_FOUND") {
|
|
42
|
+
console.error(
|
|
43
|
+
chalk.red(
|
|
44
|
+
"\n \u2716 Could not find revstack.config.ts in the current directory.\n"
|
|
45
|
+
) + chalk.dim(" Run ") + chalk.bold("revstack init") + chalk.dim(" to create one.\n")
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
console.error(
|
|
49
|
+
chalk.red("\n \u2716 Failed to parse revstack.config.ts\n") + chalk.dim(" " + (err.message ?? String(error))) + "\n"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/commands/push.ts
|
|
57
|
+
var API_BASE = "https://app.revstack.dev";
|
|
58
|
+
var DIFF_ICONS = {
|
|
59
|
+
added: chalk2.green(" + "),
|
|
60
|
+
removed: chalk2.red(" \u2212 "),
|
|
61
|
+
updated: chalk2.yellow(" ~ ")
|
|
62
|
+
};
|
|
63
|
+
var DIFF_COLORS = {
|
|
64
|
+
added: chalk2.green,
|
|
65
|
+
removed: chalk2.red,
|
|
66
|
+
updated: chalk2.yellow
|
|
67
|
+
};
|
|
68
|
+
function printDiff(diff) {
|
|
69
|
+
if (diff.length === 0) {
|
|
70
|
+
console.log(
|
|
71
|
+
chalk2.dim("\n No changes detected. Your config is up to date.\n")
|
|
72
|
+
);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(chalk2.bold("\n Changes:\n"));
|
|
76
|
+
for (const entry of diff) {
|
|
77
|
+
const icon = DIFF_ICONS[entry.action];
|
|
78
|
+
const color = DIFF_COLORS[entry.action];
|
|
79
|
+
const label = chalk2.dim(`[${entry.entity}]`);
|
|
80
|
+
console.log(
|
|
81
|
+
`${icon}${color(entry.id)} ${label} ${chalk2.white(entry.message)}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
console.log();
|
|
85
|
+
}
|
|
86
|
+
function requireAuth() {
|
|
87
|
+
const apiKey = getApiKey();
|
|
88
|
+
if (!apiKey) {
|
|
89
|
+
console.error(
|
|
90
|
+
"\n" + chalk2.red(" \u2716 Not authenticated.\n") + chalk2.dim(" Run ") + chalk2.bold("revstack login") + chalk2.dim(" first.\n")
|
|
91
|
+
);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
return apiKey;
|
|
95
|
+
}
|
|
96
|
+
var pushCommand = new Command("push").description("Push your local billing config to Revstack Cloud").option("-e, --env <environment>", "Target environment", "test").action(async (options) => {
|
|
97
|
+
const apiKey = requireAuth();
|
|
98
|
+
const config = await loadLocalConfig(process.cwd());
|
|
99
|
+
const spinner = ora({
|
|
100
|
+
text: "Calculating diff...",
|
|
101
|
+
prefixText: " "
|
|
102
|
+
}).start();
|
|
103
|
+
let diffResponse;
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(`${API_BASE}/api/v1/cli/diff`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
Authorization: `Bearer ${apiKey}`
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({ env: options.env, config })
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
spinner.fail("Failed to calculate diff");
|
|
115
|
+
console.error(
|
|
116
|
+
chalk2.red(`
|
|
117
|
+
API returned ${res.status}: ${res.statusText}
|
|
118
|
+
`)
|
|
119
|
+
);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
diffResponse = await res.json();
|
|
123
|
+
spinner.succeed("Diff calculated");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
spinner.fail("Failed to reach Revstack Cloud");
|
|
126
|
+
console.error(chalk2.red(`
|
|
127
|
+
${error.message}
|
|
128
|
+
`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
printDiff(diffResponse.diff);
|
|
132
|
+
if (diffResponse.diff.length === 0) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!diffResponse.canPush) {
|
|
136
|
+
console.log(
|
|
137
|
+
chalk2.red(" \u2716 Push is blocked.\n") + chalk2.dim(
|
|
138
|
+
` ${diffResponse.blockedReason ?? "The server rejected this configuration."}
|
|
139
|
+
`
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const envLabel = options.env === "production" ? chalk2.red.bold(options.env) : chalk2.cyan.bold(options.env);
|
|
145
|
+
const { confirm } = await prompts({
|
|
146
|
+
type: "confirm",
|
|
147
|
+
name: "confirm",
|
|
148
|
+
message: `Apply these changes to ${envLabel}?`,
|
|
149
|
+
initial: false
|
|
150
|
+
});
|
|
151
|
+
if (!confirm) {
|
|
152
|
+
console.log(chalk2.dim("\n Push cancelled.\n"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const pushSpinner = ora({
|
|
156
|
+
text: `Pushing to ${options.env}...`,
|
|
157
|
+
prefixText: " "
|
|
158
|
+
}).start();
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch(`${API_BASE}/api/v1/cli/push`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
Authorization: `Bearer ${apiKey}`
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({ env: options.env, config })
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
pushSpinner.fail("Push failed");
|
|
170
|
+
console.error(
|
|
171
|
+
chalk2.red(`
|
|
172
|
+
API returned ${res.status}: ${res.statusText}
|
|
173
|
+
`)
|
|
174
|
+
);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
pushSpinner.succeed("Pushed successfully");
|
|
178
|
+
console.log(
|
|
179
|
+
"\n" + chalk2.green(" \u2714 Config deployed to ") + envLabel + "\n" + chalk2.dim(" Changes are now live.\n")
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
pushSpinner.fail("Push failed");
|
|
183
|
+
console.error(chalk2.red(`
|
|
184
|
+
${error.message}
|
|
185
|
+
`));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
export {
|
|
190
|
+
pushCommand
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/push.ts","../../src/utils/auth.ts","../../src/utils/config-loader.ts"],"sourcesContent":["/**\r\n * @file commands/push.ts\r\n * @description The core deployment command. Loads the local config, sends it\r\n * to Revstack Cloud for diffing, presents the changes, and (upon confirmation)\r\n * pushes the config to production.\r\n */\r\n\r\nimport { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport prompts from \"prompts\";\r\nimport ora from \"ora\";\r\nimport { getApiKey } from \"@/utils/auth\";\r\nimport { loadLocalConfig } from \"@/utils/config-loader\";\r\n\r\n// ─── Types ───────────────────────────────────────────────────\r\n\r\ninterface DiffEntry {\r\n action: \"added\" | \"removed\" | \"updated\";\r\n entity: string;\r\n id: string;\r\n message: string;\r\n}\r\n\r\ninterface DiffResponse {\r\n diff: DiffEntry[];\r\n canPush: boolean;\r\n blockedReason?: string;\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────\r\n\r\nconst API_BASE = \"https://app.revstack.dev\";\r\n\r\nconst DIFF_ICONS: Record<DiffEntry[\"action\"], string> = {\r\n added: chalk.green(\" + \"),\r\n removed: chalk.red(\" − \"),\r\n updated: chalk.yellow(\" ~ \"),\r\n};\r\n\r\nconst DIFF_COLORS: Record<DiffEntry[\"action\"], (text: string) => string> = {\r\n added: chalk.green,\r\n removed: chalk.red,\r\n updated: chalk.yellow,\r\n};\r\n\r\nfunction printDiff(diff: DiffEntry[]): void {\r\n if (diff.length === 0) {\r\n console.log(\r\n chalk.dim(\"\\n No changes detected. Your config is up to date.\\n\")\r\n );\r\n return;\r\n }\r\n\r\n console.log(chalk.bold(\"\\n Changes:\\n\"));\r\n\r\n for (const entry of diff) {\r\n const icon = DIFF_ICONS[entry.action];\r\n const color = DIFF_COLORS[entry.action];\r\n const label = chalk.dim(`[${entry.entity}]`);\r\n console.log(\r\n `${icon}${color(entry.id)} ${label} ${chalk.white(entry.message)}`\r\n );\r\n }\r\n\r\n console.log();\r\n}\r\n\r\nfunction requireAuth(): string {\r\n const apiKey = getApiKey();\r\n\r\n if (!apiKey) {\r\n console.error(\r\n \"\\n\" +\r\n chalk.red(\" ✖ Not authenticated.\\n\") +\r\n chalk.dim(\" Run \") +\r\n chalk.bold(\"revstack login\") +\r\n chalk.dim(\" first.\\n\")\r\n );\r\n process.exit(1);\r\n }\r\n\r\n return apiKey;\r\n}\r\n\r\n// ─── Command ─────────────────────────────────────────────────\r\n\r\nexport const pushCommand = new Command(\"push\")\r\n .description(\"Push your local billing config to Revstack Cloud\")\r\n .option(\"-e, --env <environment>\", \"Target environment\", \"test\")\r\n .action(async (options: { env: string }) => {\r\n const apiKey = requireAuth();\r\n const config = await loadLocalConfig(process.cwd());\r\n\r\n // ── Step 1: Compute diff ──────────────────────────────────\r\n\r\n const spinner = ora({\r\n text: \"Calculating diff...\",\r\n prefixText: \" \",\r\n }).start();\r\n\r\n let diffResponse: DiffResponse;\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/api/v1/cli/diff`, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n body: JSON.stringify({ env: options.env, config }),\r\n });\r\n\r\n if (!res.ok) {\r\n spinner.fail(\"Failed to calculate diff\");\r\n console.error(\r\n chalk.red(`\\n API returned ${res.status}: ${res.statusText}\\n`)\r\n );\r\n process.exit(1);\r\n }\r\n\r\n diffResponse = (await res.json()) as DiffResponse;\r\n spinner.succeed(\"Diff calculated\");\r\n } catch (error: unknown) {\r\n spinner.fail(\"Failed to reach Revstack Cloud\");\r\n console.error(chalk.red(`\\n ${(error as Error).message}\\n`));\r\n process.exit(1);\r\n }\r\n\r\n // ── Step 2: Present diff ──────────────────────────────────\r\n\r\n printDiff(diffResponse.diff);\r\n\r\n if (diffResponse.diff.length === 0) {\r\n return;\r\n }\r\n\r\n // ── Step 3: Check if push is allowed ──────────────────────\r\n\r\n if (!diffResponse.canPush) {\r\n console.log(\r\n chalk.red(\" ✖ Push is blocked.\\n\") +\r\n chalk.dim(\r\n ` ${diffResponse.blockedReason ?? \"The server rejected this configuration.\"}\\n`\r\n )\r\n );\r\n process.exit(1);\r\n }\r\n\r\n // ── Step 4: Confirm ───────────────────────────────────────\r\n\r\n const envLabel =\r\n options.env === \"production\"\r\n ? chalk.red.bold(options.env)\r\n : chalk.cyan.bold(options.env);\r\n\r\n const { confirm } = await prompts({\r\n type: \"confirm\",\r\n name: \"confirm\",\r\n message: `Apply these changes to ${envLabel}?`,\r\n initial: false,\r\n });\r\n\r\n if (!confirm) {\r\n console.log(chalk.dim(\"\\n Push cancelled.\\n\"));\r\n return;\r\n }\r\n\r\n // ── Step 5: Push ──────────────────────────────────────────\r\n\r\n const pushSpinner = ora({\r\n text: `Pushing to ${options.env}...`,\r\n prefixText: \" \",\r\n }).start();\r\n\r\n try {\r\n const res = await fetch(`${API_BASE}/api/v1/cli/push`, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n body: JSON.stringify({ env: options.env, config }),\r\n });\r\n\r\n if (!res.ok) {\r\n pushSpinner.fail(\"Push failed\");\r\n console.error(\r\n chalk.red(`\\n API returned ${res.status}: ${res.statusText}\\n`)\r\n );\r\n process.exit(1);\r\n }\r\n\r\n pushSpinner.succeed(\"Pushed successfully\");\r\n console.log(\r\n \"\\n\" +\r\n chalk.green(\" ✔ Config deployed to \") +\r\n envLabel +\r\n \"\\n\" +\r\n chalk.dim(\" Changes are now live.\\n\")\r\n );\r\n } catch (error: unknown) {\r\n pushSpinner.fail(\"Push failed\");\r\n console.error(chalk.red(`\\n ${(error as Error).message}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n","/**\r\n * @file utils/auth.ts\r\n * @description Manages global Revstack credentials stored at ~/.revstack/credentials.json.\r\n * Provides simple get/set helpers for the API key used by all authenticated commands.\r\n */\r\n\r\nimport fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport os from \"node:os\";\r\n\r\nconst CREDENTIALS_DIR = path.join(os.homedir(), \".revstack\");\r\nconst CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, \"credentials.json\");\r\n\r\ninterface Credentials {\r\n apiKey: string;\r\n}\r\n\r\n/**\r\n * Persist an API key to the global credentials file.\r\n * Creates `~/.revstack/` if it doesn't exist.\r\n */\r\nexport function setApiKey(key: string): void {\r\n if (!fs.existsSync(CREDENTIALS_DIR)) {\r\n fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });\r\n }\r\n\r\n const credentials: Credentials = { apiKey: key };\r\n fs.writeFileSync(\r\n CREDENTIALS_FILE,\r\n JSON.stringify(credentials, null, 2),\r\n \"utf-8\"\r\n );\r\n}\r\n\r\n/**\r\n * Read the stored API key, or return `null` if none is configured.\r\n */\r\nexport function getApiKey(): string | null {\r\n if (!fs.existsSync(CREDENTIALS_FILE)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const raw = fs.readFileSync(CREDENTIALS_FILE, \"utf-8\");\r\n const credentials: Credentials = JSON.parse(raw);\r\n return credentials.apiKey ?? null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Remove stored credentials. Used by `revstack logout`.\r\n */\r\nexport function clearApiKey(): void {\r\n if (fs.existsSync(CREDENTIALS_FILE)) {\r\n fs.unlinkSync(CREDENTIALS_FILE);\r\n }\r\n}\r\n","/**\r\n * @file utils/config-loader.ts\r\n * @description Loads and evaluates the user's `revstack.config.ts` at runtime\r\n * using jiti (just-in-time TypeScript compilation). Returns a sanitized,\r\n * JSON-safe representation of the config for network transmission.\r\n */\r\n\r\nimport { createJiti } from \"jiti\";\r\nimport path from \"node:path\";\r\nimport chalk from \"chalk\";\r\n\r\n/**\r\n * Load the `revstack.config.ts` from the given directory.\r\n *\r\n * @param cwd - The directory to search for `revstack.config.ts`.\r\n * @returns The parsed and sanitized configuration object.\r\n */\r\nexport async function loadLocalConfig(\r\n cwd: string\r\n): Promise<Record<string, unknown>> {\r\n const configPath = path.resolve(cwd, \"revstack.config.ts\");\r\n\r\n try {\r\n const jiti = createJiti(cwd);\r\n const module = (await jiti.import(configPath)) as Record<string, unknown>;\r\n const config = (module.default ?? module) as Record<string, unknown>;\r\n\r\n // Sanitize: strip functions, class instances, and non-serializable data.\r\n // This ensures we only send plain JSON to the Revstack Cloud API.\r\n return JSON.parse(JSON.stringify(config));\r\n } catch (error: unknown) {\r\n const err = error as NodeJS.ErrnoException;\r\n\r\n if (\r\n err.code === \"ERR_MODULE_NOT_FOUND\" ||\r\n err.code === \"ENOENT\" ||\r\n err.code === \"MODULE_NOT_FOUND\"\r\n ) {\r\n console.error(\r\n chalk.red(\r\n \"\\n ✖ Could not find revstack.config.ts in the current directory.\\n\"\r\n ) +\r\n chalk.dim(\" Run \") +\r\n chalk.bold(\"revstack init\") +\r\n chalk.dim(\" to create one.\\n\")\r\n );\r\n } else {\r\n console.error(\r\n chalk.red(\"\\n ✖ Failed to parse revstack.config.ts\\n\") +\r\n chalk.dim(\" \" + (err.message ?? String(error))) +\r\n \"\\n\"\r\n );\r\n }\r\n\r\n process.exit(1);\r\n }\r\n}\r\n"],"mappings":"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","names":["chalk","path","chalk"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file utils/auth.ts
|
|
3
|
+
* @description Manages global Revstack credentials stored at ~/.revstack/credentials.json.
|
|
4
|
+
* Provides simple get/set helpers for the API key used by all authenticated commands.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Persist an API key to the global credentials file.
|
|
8
|
+
* Creates `~/.revstack/` if it doesn't exist.
|
|
9
|
+
*/
|
|
10
|
+
declare function setApiKey(key: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Read the stored API key, or return `null` if none is configured.
|
|
13
|
+
*/
|
|
14
|
+
declare function getApiKey(): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Remove stored credentials. Used by `revstack logout`.
|
|
17
|
+
*/
|
|
18
|
+
declare function clearApiKey(): void;
|
|
19
|
+
|
|
20
|
+
export { clearApiKey, getApiKey, setApiKey };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/auth.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".revstack");
|
|
8
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
9
|
+
function setApiKey(key) {
|
|
10
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
11
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
const credentials = { apiKey: key };
|
|
14
|
+
fs.writeFileSync(
|
|
15
|
+
CREDENTIALS_FILE,
|
|
16
|
+
JSON.stringify(credentials, null, 2),
|
|
17
|
+
"utf-8"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function getApiKey() {
|
|
21
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
26
|
+
const credentials = JSON.parse(raw);
|
|
27
|
+
return credentials.apiKey ?? null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function clearApiKey() {
|
|
33
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
34
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
clearApiKey,
|
|
39
|
+
getApiKey,
|
|
40
|
+
setApiKey
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/auth.ts"],"sourcesContent":["/**\r\n * @file utils/auth.ts\r\n * @description Manages global Revstack credentials stored at ~/.revstack/credentials.json.\r\n * Provides simple get/set helpers for the API key used by all authenticated commands.\r\n */\r\n\r\nimport fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport os from \"node:os\";\r\n\r\nconst CREDENTIALS_DIR = path.join(os.homedir(), \".revstack\");\r\nconst CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, \"credentials.json\");\r\n\r\ninterface Credentials {\r\n apiKey: string;\r\n}\r\n\r\n/**\r\n * Persist an API key to the global credentials file.\r\n * Creates `~/.revstack/` if it doesn't exist.\r\n */\r\nexport function setApiKey(key: string): void {\r\n if (!fs.existsSync(CREDENTIALS_DIR)) {\r\n fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });\r\n }\r\n\r\n const credentials: Credentials = { apiKey: key };\r\n fs.writeFileSync(\r\n CREDENTIALS_FILE,\r\n JSON.stringify(credentials, null, 2),\r\n \"utf-8\"\r\n );\r\n}\r\n\r\n/**\r\n * Read the stored API key, or return `null` if none is configured.\r\n */\r\nexport function getApiKey(): string | null {\r\n if (!fs.existsSync(CREDENTIALS_FILE)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const raw = fs.readFileSync(CREDENTIALS_FILE, \"utf-8\");\r\n const credentials: Credentials = JSON.parse(raw);\r\n return credentials.apiKey ?? null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Remove stored credentials. Used by `revstack logout`.\r\n */\r\nexport function clearApiKey(): void {\r\n if (fs.existsSync(CREDENTIALS_FILE)) {\r\n fs.unlinkSync(CREDENTIALS_FILE);\r\n }\r\n}\r\n"],"mappings":";;;AAMA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAEf,IAAM,kBAAkB,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW;AAC3D,IAAM,mBAAmB,KAAK,KAAK,iBAAiB,kBAAkB;AAU/D,SAAS,UAAU,KAAmB;AAC3C,MAAI,CAAC,GAAG,WAAW,eAAe,GAAG;AACnC,OAAG,UAAU,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAAA,EACnD;AAEA,QAAM,cAA2B,EAAE,QAAQ,IAAI;AAC/C,KAAG;AAAA,IACD;AAAA,IACA,KAAK,UAAU,aAAa,MAAM,CAAC;AAAA,IACnC;AAAA,EACF;AACF;AAKO,SAAS,YAA2B;AACzC,MAAI,CAAC,GAAG,WAAW,gBAAgB,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,kBAAkB,OAAO;AACrD,UAAM,cAA2B,KAAK,MAAM,GAAG;AAC/C,WAAO,YAAY,UAAU;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,cAAoB;AAClC,MAAI,GAAG,WAAW,gBAAgB,GAAG;AACnC,OAAG,WAAW,gBAAgB;AAAA,EAChC;AACF;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file utils/config-loader.ts
|
|
3
|
+
* @description Loads and evaluates the user's `revstack.config.ts` at runtime
|
|
4
|
+
* using jiti (just-in-time TypeScript compilation). Returns a sanitized,
|
|
5
|
+
* JSON-safe representation of the config for network transmission.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Load the `revstack.config.ts` from the given directory.
|
|
9
|
+
*
|
|
10
|
+
* @param cwd - The directory to search for `revstack.config.ts`.
|
|
11
|
+
* @returns The parsed and sanitized configuration object.
|
|
12
|
+
*/
|
|
13
|
+
declare function loadLocalConfig(cwd: string): Promise<Record<string, unknown>>;
|
|
14
|
+
|
|
15
|
+
export { loadLocalConfig };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/config-loader.ts
|
|
4
|
+
import { createJiti } from "jiti";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
async function loadLocalConfig(cwd) {
|
|
8
|
+
const configPath = path.resolve(cwd, "revstack.config.ts");
|
|
9
|
+
try {
|
|
10
|
+
const jiti = createJiti(cwd);
|
|
11
|
+
const module = await jiti.import(configPath);
|
|
12
|
+
const config = module.default ?? module;
|
|
13
|
+
return JSON.parse(JSON.stringify(config));
|
|
14
|
+
} catch (error) {
|
|
15
|
+
const err = error;
|
|
16
|
+
if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "ENOENT" || err.code === "MODULE_NOT_FOUND") {
|
|
17
|
+
console.error(
|
|
18
|
+
chalk.red(
|
|
19
|
+
"\n \u2716 Could not find revstack.config.ts in the current directory.\n"
|
|
20
|
+
) + chalk.dim(" Run ") + chalk.bold("revstack init") + chalk.dim(" to create one.\n")
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
console.error(
|
|
24
|
+
chalk.red("\n \u2716 Failed to parse revstack.config.ts\n") + chalk.dim(" " + (err.message ?? String(error))) + "\n"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
loadLocalConfig
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/config-loader.ts"],"sourcesContent":["/**\r\n * @file utils/config-loader.ts\r\n * @description Loads and evaluates the user's `revstack.config.ts` at runtime\r\n * using jiti (just-in-time TypeScript compilation). Returns a sanitized,\r\n * JSON-safe representation of the config for network transmission.\r\n */\r\n\r\nimport { createJiti } from \"jiti\";\r\nimport path from \"node:path\";\r\nimport chalk from \"chalk\";\r\n\r\n/**\r\n * Load the `revstack.config.ts` from the given directory.\r\n *\r\n * @param cwd - The directory to search for `revstack.config.ts`.\r\n * @returns The parsed and sanitized configuration object.\r\n */\r\nexport async function loadLocalConfig(\r\n cwd: string\r\n): Promise<Record<string, unknown>> {\r\n const configPath = path.resolve(cwd, \"revstack.config.ts\");\r\n\r\n try {\r\n const jiti = createJiti(cwd);\r\n const module = (await jiti.import(configPath)) as Record<string, unknown>;\r\n const config = (module.default ?? module) as Record<string, unknown>;\r\n\r\n // Sanitize: strip functions, class instances, and non-serializable data.\r\n // This ensures we only send plain JSON to the Revstack Cloud API.\r\n return JSON.parse(JSON.stringify(config));\r\n } catch (error: unknown) {\r\n const err = error as NodeJS.ErrnoException;\r\n\r\n if (\r\n err.code === \"ERR_MODULE_NOT_FOUND\" ||\r\n err.code === \"ENOENT\" ||\r\n err.code === \"MODULE_NOT_FOUND\"\r\n ) {\r\n console.error(\r\n chalk.red(\r\n \"\\n ✖ Could not find revstack.config.ts in the current directory.\\n\"\r\n ) +\r\n chalk.dim(\" Run \") +\r\n chalk.bold(\"revstack init\") +\r\n chalk.dim(\" to create one.\\n\")\r\n );\r\n } else {\r\n console.error(\r\n chalk.red(\"\\n ✖ Failed to parse revstack.config.ts\\n\") +\r\n chalk.dim(\" \" + (err.message ?? String(error))) +\r\n \"\\n\"\r\n );\r\n }\r\n\r\n process.exit(1);\r\n }\r\n}\r\n"],"mappings":";;;AAOA,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,OAAO,WAAW;AAQlB,eAAsB,gBACpB,KACkC;AAClC,QAAM,aAAa,KAAK,QAAQ,KAAK,oBAAoB;AAEzD,MAAI;AACF,UAAM,OAAO,WAAW,GAAG;AAC3B,UAAM,SAAU,MAAM,KAAK,OAAO,UAAU;AAC5C,UAAM,SAAU,OAAO,WAAW;AAIlC,WAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;AAAA,EAC1C,SAAS,OAAgB;AACvB,UAAM,MAAM;AAEZ,QACE,IAAI,SAAS,0BACb,IAAI,SAAS,YACb,IAAI,SAAS,oBACb;AACA,cAAQ;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,QACF,IACE,MAAM,IAAI,UAAU,IACpB,MAAM,KAAK,eAAe,IAC1B,MAAM,IAAI,mBAAmB;AAAA,MACjC;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,MAAM,IAAI,iDAA4C,IACpD,MAAM,IAAI,UAAU,IAAI,WAAW,OAAO,KAAK,EAAE,IACjD;AAAA,MACJ;AAAA,IACF;AAEA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revstackhq/cli",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260226061807",
|
|
4
4
|
"description": "The official CLI for Revstack — Billing as Code",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "^5.4.1",
|
|
13
13
|
"commander": "^13.1.0",
|
|
14
|
+
"execa": "^9.6.1",
|
|
14
15
|
"jiti": "^2.4.2",
|
|
16
|
+
"ora": "^8.2.0",
|
|
15
17
|
"prompts": "^2.4.2"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
18
20
|
"@types/node": "^20.11.0",
|
|
19
21
|
"@types/ora": "^3.2.0",
|
|
20
22
|
"@types/prompts": "^2.4.9",
|
|
21
|
-
"
|
|
22
|
-
"ora": "^8.2.0",
|
|
23
|
+
"tsc-alias": "^1.8.16",
|
|
23
24
|
"tsup": "^8.1.0",
|
|
24
25
|
"typescript": "5.9.2",
|
|
25
26
|
"vitest": "^4.0.18"
|
|
@@ -27,6 +28,12 @@
|
|
|
27
28
|
"publishConfig": {
|
|
28
29
|
"access": "public"
|
|
29
30
|
},
|
|
31
|
+
"directories": {
|
|
32
|
+
"test": "tests"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [],
|
|
35
|
+
"author": "",
|
|
36
|
+
"types": "./dist/cli.d.ts",
|
|
30
37
|
"scripts": {
|
|
31
38
|
"build": "tsup",
|
|
32
39
|
"dev": "tsup --watch",
|
package/src/commands/init.ts
CHANGED
|
@@ -12,11 +12,9 @@ import path from "node:path";
|
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
13
|
import ora from "ora";
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const STARTER_FEATURES = `import { defineFeature } from "@revstackhq/core";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const features = {
|
|
17
|
+
export const features = {
|
|
20
18
|
seats: defineFeature({
|
|
21
19
|
name: "Seats",
|
|
22
20
|
type: "static",
|
|
@@ -28,47 +26,56 @@ const features = {
|
|
|
28
26
|
unit_type: "count",
|
|
29
27
|
}),
|
|
30
28
|
};
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const STARTER_PLANS = `import { definePlan } from "@revstackhq/core";
|
|
32
|
+
import { features } from "./features";
|
|
33
|
+
|
|
34
|
+
export const plans = {
|
|
35
|
+
// DO NOT DELETE: Automatically created default plan for guests.
|
|
36
|
+
default: definePlan<typeof features>({
|
|
37
|
+
name: "Default",
|
|
38
|
+
description: "Automatically created default plan for guests.",
|
|
39
|
+
is_default: true,
|
|
40
|
+
is_public: false,
|
|
41
|
+
type: "free",
|
|
42
|
+
features: {},
|
|
43
|
+
}),
|
|
44
|
+
pro: definePlan<typeof features>({
|
|
45
|
+
name: "Pro",
|
|
46
|
+
description: "For professional teams.",
|
|
47
|
+
is_default: false,
|
|
48
|
+
is_public: true,
|
|
49
|
+
type: "paid",
|
|
50
|
+
prices: [
|
|
51
|
+
{
|
|
52
|
+
amount: 2900,
|
|
53
|
+
currency: "USD",
|
|
54
|
+
billing_interval: "monthly",
|
|
55
|
+
trial_period_days: 14,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
amount: 29000,
|
|
59
|
+
currency: "USD",
|
|
60
|
+
billing_interval: "yearly",
|
|
61
|
+
trial_period_days: 14,
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
features: {
|
|
65
|
+
seats: { value_limit: 5, is_hard_limit: true },
|
|
66
|
+
ai_tokens: { value_limit: 1000, reset_period: "monthly" },
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
`;
|
|
31
71
|
|
|
32
|
-
|
|
72
|
+
const STARTER_CONFIG = `import { defineConfig } from "@revstackhq/core";
|
|
73
|
+
import { features } from "./revstack/features";
|
|
74
|
+
import { plans } from "./revstack/plans";
|
|
33
75
|
|
|
34
76
|
export default defineConfig({
|
|
35
77
|
features,
|
|
36
|
-
plans
|
|
37
|
-
// DO NOT DELETE: Automatically created default plan for guests.
|
|
38
|
-
default: definePlan<typeof features>({
|
|
39
|
-
name: "Default",
|
|
40
|
-
description: "Automatically created default plan for guests.",
|
|
41
|
-
is_default: true,
|
|
42
|
-
is_public: false,
|
|
43
|
-
type: "free",
|
|
44
|
-
features: {},
|
|
45
|
-
}),
|
|
46
|
-
pro: definePlan<typeof features>({
|
|
47
|
-
name: "Pro",
|
|
48
|
-
description: "For professional teams.",
|
|
49
|
-
is_default: false,
|
|
50
|
-
is_public: true,
|
|
51
|
-
type: "paid",
|
|
52
|
-
prices: [
|
|
53
|
-
{
|
|
54
|
-
amount: 2900,
|
|
55
|
-
currency: "USD",
|
|
56
|
-
billing_interval: "monthly",
|
|
57
|
-
trial_period_days: 14,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
amount: 29000,
|
|
61
|
-
currency: "USD",
|
|
62
|
-
billing_interval: "yearly",
|
|
63
|
-
trial_period_days: 14,
|
|
64
|
-
}
|
|
65
|
-
],
|
|
66
|
-
features: {
|
|
67
|
-
seats: { value_limit: 5, is_hard_limit: true },
|
|
68
|
-
ai_tokens: { value_limit: 1000, reset_period: "monthly" },
|
|
69
|
-
},
|
|
70
|
-
}),
|
|
71
|
-
},
|
|
78
|
+
plans,
|
|
72
79
|
});
|
|
73
80
|
`;
|
|
74
81
|
|
|
@@ -77,6 +84,9 @@ export const initCommand = new Command("init")
|
|
|
77
84
|
.action(async () => {
|
|
78
85
|
const cwd = process.cwd();
|
|
79
86
|
const configPath = path.resolve(cwd, "revstack.config.ts");
|
|
87
|
+
const revstackDir = path.resolve(cwd, "revstack");
|
|
88
|
+
const featuresPath = path.resolve(revstackDir, "features.ts");
|
|
89
|
+
const plansPath = path.resolve(revstackDir, "plans.ts");
|
|
80
90
|
|
|
81
91
|
if (fs.existsSync(configPath)) {
|
|
82
92
|
console.log(
|
|
@@ -87,7 +97,12 @@ export const initCommand = new Command("init")
|
|
|
87
97
|
process.exit(1);
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
// Step 1: Create revstack
|
|
100
|
+
// Step 1: Create revstack directory and files
|
|
101
|
+
if (!fs.existsSync(revstackDir)) {
|
|
102
|
+
fs.mkdirSync(revstackDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
fs.writeFileSync(featuresPath, STARTER_FEATURES, "utf-8");
|
|
105
|
+
fs.writeFileSync(plansPath, STARTER_PLANS, "utf-8");
|
|
91
106
|
fs.writeFileSync(configPath, STARTER_CONFIG, "utf-8");
|
|
92
107
|
|
|
93
108
|
// Step 2: Detect package manager & verify package.json
|
|
@@ -160,12 +175,12 @@ export const initCommand = new Command("init")
|
|
|
160
175
|
|
|
161
176
|
// Step 4: Final Success Message
|
|
162
177
|
console.log(
|
|
163
|
-
"
|
|
164
|
-
chalk.green(" ✔ Created revstack
|
|
165
|
-
"
|
|
178
|
+
"\n" +
|
|
179
|
+
chalk.green(" ✔ Created revstack config structure\n") +
|
|
180
|
+
"\n" +
|
|
166
181
|
chalk.dim(" Includes the ") +
|
|
167
182
|
chalk.white("Default Guest Plan") +
|
|
168
|
-
chalk.dim(" (required by Revstack)
|
|
183
|
+
chalk.dim(" (required by Revstack).\n") +
|
|
169
184
|
"\\n" +
|
|
170
185
|
chalk.dim(" Next steps:\\n") +
|
|
171
186
|
(installFailed
|