@revstackhq/cli 0.0.0-dev-20260226055348 → 0.0.0-dev-20260226063200
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 +32 -8
- package/CHANGELOG.md +12 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +101 -64
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +149 -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 +8 -1
- package/src/commands/init.ts +71 -51
- 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,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/init.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { spawnSync } from "child_process";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
var STARTER_FEATURES = `import { defineFeature } from "@revstackhq/core";
|
|
11
|
+
|
|
12
|
+
export const features = {
|
|
13
|
+
seats: defineFeature({
|
|
14
|
+
name: "Seats",
|
|
15
|
+
type: "static",
|
|
16
|
+
unit_type: "count",
|
|
17
|
+
}),
|
|
18
|
+
ai_tokens: defineFeature({
|
|
19
|
+
name: "AI Tokens",
|
|
20
|
+
type: "metered",
|
|
21
|
+
unit_type: "count",
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
`;
|
|
25
|
+
var STARTER_PLANS = `import { definePlan } from "@revstackhq/core";
|
|
26
|
+
import { features } from "./features";
|
|
27
|
+
|
|
28
|
+
export const plans = {
|
|
29
|
+
// DO NOT DELETE: Automatically created default plan for guests.
|
|
30
|
+
default: definePlan<typeof features>({
|
|
31
|
+
name: "Default",
|
|
32
|
+
description: "Automatically created default plan for guests.",
|
|
33
|
+
is_default: true,
|
|
34
|
+
is_public: false,
|
|
35
|
+
type: "free",
|
|
36
|
+
features: {},
|
|
37
|
+
}),
|
|
38
|
+
pro: definePlan<typeof features>({
|
|
39
|
+
name: "Pro",
|
|
40
|
+
description: "For professional teams.",
|
|
41
|
+
is_default: false,
|
|
42
|
+
is_public: true,
|
|
43
|
+
type: "paid",
|
|
44
|
+
prices: [
|
|
45
|
+
{
|
|
46
|
+
amount: 2900,
|
|
47
|
+
currency: "USD",
|
|
48
|
+
billing_interval: "monthly",
|
|
49
|
+
trial_period_days: 14,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
amount: 29000,
|
|
53
|
+
currency: "USD",
|
|
54
|
+
billing_interval: "yearly",
|
|
55
|
+
trial_period_days: 14,
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
features: {
|
|
59
|
+
seats: { value_limit: 5, is_hard_limit: true },
|
|
60
|
+
ai_tokens: { value_limit: 1000, reset_period: "monthly" },
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
`;
|
|
65
|
+
var STARTER_CONFIG = `import { defineConfig } from "@revstackhq/core";
|
|
66
|
+
import { features } from "./revstack/features";
|
|
67
|
+
import { plans } from "./revstack/plans";
|
|
68
|
+
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
features,
|
|
71
|
+
plans,
|
|
72
|
+
});
|
|
73
|
+
`;
|
|
74
|
+
var initCommand = new Command("init").description("Scaffold a new revstack.config.ts in the current directory").action(async () => {
|
|
75
|
+
const cwd = process.cwd();
|
|
76
|
+
const configPath = path.resolve(cwd, "revstack.config.ts");
|
|
77
|
+
const revstackDir = path.resolve(cwd, "revstack");
|
|
78
|
+
const featuresPath = path.resolve(revstackDir, "features.ts");
|
|
79
|
+
const plansPath = path.resolve(revstackDir, "plans.ts");
|
|
80
|
+
if (fs.existsSync(configPath)) {
|
|
81
|
+
console.log(
|
|
82
|
+
"\n" + chalk.yellow(" \u26A0 revstack.config.ts already exists.\n") + chalk.dim(" Delete it first if you want to start fresh.\n")
|
|
83
|
+
);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (!fs.existsSync(revstackDir)) {
|
|
87
|
+
fs.mkdirSync(revstackDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
fs.writeFileSync(featuresPath, STARTER_FEATURES, "utf-8");
|
|
90
|
+
fs.writeFileSync(plansPath, STARTER_PLANS, "utf-8");
|
|
91
|
+
fs.writeFileSync(configPath, STARTER_CONFIG, "utf-8");
|
|
92
|
+
let packageManager = "npm";
|
|
93
|
+
if (fs.existsSync(path.resolve(cwd, "pnpm-lock.yaml"))) {
|
|
94
|
+
packageManager = "pnpm";
|
|
95
|
+
} else if (fs.existsSync(path.resolve(cwd, "yarn.lock"))) {
|
|
96
|
+
packageManager = "yarn";
|
|
97
|
+
} else if (fs.existsSync(path.resolve(cwd, "package-lock.json"))) {
|
|
98
|
+
packageManager = "npm";
|
|
99
|
+
}
|
|
100
|
+
const packageJsonPath = path.resolve(cwd, "package.json");
|
|
101
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
102
|
+
try {
|
|
103
|
+
spawnSync("npm", ["init", "-y"], { cwd, stdio: "ignore", shell: true });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const spinner = ora("Installing @revstackhq/core...").start();
|
|
108
|
+
let installFailed = false;
|
|
109
|
+
try {
|
|
110
|
+
const pkgVersion = process.env.npm_package_version || "dev";
|
|
111
|
+
const tag = pkgVersion.includes("dev") ? "@dev" : "@latest";
|
|
112
|
+
const pkgName = `@revstackhq/core${tag}`;
|
|
113
|
+
const installArgs = packageManager === "yarn" ? ["add", pkgName] : packageManager === "pnpm" ? ["add", pkgName] : ["install", pkgName];
|
|
114
|
+
let result = spawnSync(packageManager, installArgs, { cwd, shell: true });
|
|
115
|
+
if (result.error || result.status !== 0) {
|
|
116
|
+
if (packageManager === "pnpm") {
|
|
117
|
+
result = spawnSync("pnpm", ["add", "-w", pkgName], {
|
|
118
|
+
cwd,
|
|
119
|
+
shell: true
|
|
120
|
+
});
|
|
121
|
+
} else if (packageManager === "yarn") {
|
|
122
|
+
result = spawnSync("yarn", ["add", "-W", pkgName], {
|
|
123
|
+
cwd,
|
|
124
|
+
shell: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (result.error || result.status !== 0) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"Install failed: " + (result.stderr ? result.stderr.toString() : result.stdout ? result.stdout.toString() : "Unknown error")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
spinner.succeed("Dependencies installed");
|
|
134
|
+
} catch (err) {
|
|
135
|
+
installFailed = true;
|
|
136
|
+
spinner.fail(
|
|
137
|
+
"Failed to install @revstackhq/core automatically (" + packageManager + "). Reason: " + err.message
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
console.log(
|
|
141
|
+
"\n" + chalk.green(" \u2714 Created revstack config structure\n") + "\n" + chalk.dim(" Includes the ") + chalk.white("Default Guest Plan") + chalk.dim(" (required by Revstack).\n") + "\\n" + chalk.dim(" Next steps:\\n") + (installFailed ? chalk.dim(" 0. ") + chalk.white(
|
|
142
|
+
"Run " + chalk.bold(packageManager + " install @revstackhq/core") + " manually\\n"
|
|
143
|
+
) : "") + chalk.dim(" 1. ") + chalk.white("Edit the config to match your billing model\\n") + chalk.dim(" 2. ") + chalk.white("Run ") + chalk.bold("revstack login") + chalk.white(" to authenticate\\n") + chalk.dim(" 3. ") + chalk.white("Run ") + chalk.bold("revstack push") + chalk.white(" to deploy\\n")
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
export {
|
|
147
|
+
initCommand
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/init.ts"],"sourcesContent":["/**\r\n * @file commands/init.ts\r\n * @description Scaffolds a new `revstack.config.ts` in the current directory.\r\n * Generates a starter config with the immutable Default Guest Plan and\r\n * a sample Pro plan using type-safe helpers from @revstackhq/core.\r\n */\r\n\r\nimport { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { spawnSync } from \"node:child_process\";\r\nimport ora from \"ora\";\r\n\r\nconst STARTER_FEATURES = `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n seats: defineFeature({\r\n name: \"Seats\",\r\n type: \"static\",\r\n unit_type: \"count\",\r\n }),\r\n ai_tokens: defineFeature({\r\n name: \"AI Tokens\",\r\n type: \"metered\",\r\n unit_type: \"count\",\r\n }),\r\n};\r\n`;\r\n\r\nconst STARTER_PLANS = `import { definePlan } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const plans = {\r\n // DO NOT DELETE: Automatically created default plan for guests.\r\n default: definePlan<typeof features>({\r\n name: \"Default\",\r\n description: \"Automatically created default plan for guests.\",\r\n is_default: true,\r\n is_public: false,\r\n type: \"free\",\r\n features: {},\r\n }),\r\n pro: definePlan<typeof features>({\r\n name: \"Pro\",\r\n description: \"For professional teams.\",\r\n is_default: false,\r\n is_public: true,\r\n type: \"paid\",\r\n prices: [\r\n {\r\n amount: 2900,\r\n currency: \"USD\",\r\n billing_interval: \"monthly\",\r\n trial_period_days: 14,\r\n },\r\n {\r\n amount: 29000,\r\n currency: \"USD\",\r\n billing_interval: \"yearly\",\r\n trial_period_days: 14,\r\n }\r\n ],\r\n features: {\r\n seats: { value_limit: 5, is_hard_limit: true },\r\n ai_tokens: { value_limit: 1000, reset_period: \"monthly\" },\r\n },\r\n }),\r\n};\r\n`;\r\n\r\nconst STARTER_CONFIG = `import { defineConfig } from \"@revstackhq/core\";\r\nimport { features } from \"./revstack/features\";\r\nimport { plans } from \"./revstack/plans\";\r\n\r\nexport default defineConfig({\r\n features,\r\n plans,\r\n});\r\n`;\r\n\r\nexport const initCommand = new Command(\"init\")\r\n .description(\"Scaffold a new revstack.config.ts in the current directory\")\r\n .action(async () => {\r\n const cwd = process.cwd();\r\n const configPath = path.resolve(cwd, \"revstack.config.ts\");\r\n const revstackDir = path.resolve(cwd, \"revstack\");\r\n const featuresPath = path.resolve(revstackDir, \"features.ts\");\r\n const plansPath = path.resolve(revstackDir, \"plans.ts\");\r\n\r\n if (fs.existsSync(configPath)) {\r\n console.log(\r\n \"\\n\" +\r\n chalk.yellow(\" ⚠ revstack.config.ts already exists.\\n\") +\r\n chalk.dim(\" Delete it first if you want to start fresh.\\n\"),\r\n );\r\n process.exit(1);\r\n }\r\n\r\n // Step 1: Create revstack directory and files\r\n if (!fs.existsSync(revstackDir)) {\r\n fs.mkdirSync(revstackDir, { recursive: true });\r\n }\r\n fs.writeFileSync(featuresPath, STARTER_FEATURES, \"utf-8\");\r\n fs.writeFileSync(plansPath, STARTER_PLANS, \"utf-8\");\r\n fs.writeFileSync(configPath, STARTER_CONFIG, \"utf-8\");\r\n\r\n // Step 2: Detect package manager & verify package.json\r\n let packageManager = \"npm\";\r\n if (fs.existsSync(path.resolve(cwd, \"pnpm-lock.yaml\"))) {\r\n packageManager = \"pnpm\";\r\n } else if (fs.existsSync(path.resolve(cwd, \"yarn.lock\"))) {\r\n packageManager = \"yarn\";\r\n } else if (fs.existsSync(path.resolve(cwd, \"package-lock.json\"))) {\r\n packageManager = \"npm\";\r\n }\r\n\r\n const packageJsonPath = path.resolve(cwd, \"package.json\");\r\n if (!fs.existsSync(packageJsonPath)) {\r\n // Create a default package.json if it doesn't exist\r\n try {\r\n spawnSync(\"npm\", [\"init\", \"-y\"], { cwd, stdio: \"ignore\", shell: true });\r\n } catch (err) {\r\n // Ignore initialization errors; the install command may still work or provide a better error.\r\n }\r\n }\r\n\r\n // Step 3: Install @revstackhq/core\r\n const spinner = ora(\"Installing @revstackhq/core...\").start();\r\n let installFailed = false;\r\n\r\n try {\r\n // Use @dev tag if the CLI itself is a dev snapshot\r\n const pkgVersion = process.env.npm_package_version || \"dev\";\r\n const tag = pkgVersion.includes(\"dev\") ? \"@dev\" : \"@latest\";\r\n const pkgName = `@revstackhq/core${tag}`;\r\n\r\n const installArgs =\r\n packageManager === \"yarn\"\r\n ? [\"add\", pkgName]\r\n : packageManager === \"pnpm\"\r\n ? [\"add\", pkgName]\r\n : [\"install\", pkgName];\r\n\r\n let result = spawnSync(packageManager, installArgs, { cwd, shell: true });\r\n if (result.error || result.status !== 0) {\r\n if (packageManager === \"pnpm\") {\r\n result = spawnSync(\"pnpm\", [\"add\", \"-w\", pkgName], {\r\n cwd,\r\n shell: true,\r\n });\r\n } else if (packageManager === \"yarn\") {\r\n result = spawnSync(\"yarn\", [\"add\", \"-W\", pkgName], {\r\n cwd,\r\n shell: true,\r\n });\r\n }\r\n }\r\n\r\n if (result.error || result.status !== 0) {\r\n throw new Error(\r\n \"Install failed: \" +\r\n (result.stderr\r\n ? result.stderr.toString()\r\n : result.stdout\r\n ? result.stdout.toString()\r\n : \"Unknown error\"),\r\n );\r\n }\r\n spinner.succeed(\"Dependencies installed\");\r\n } catch (err: any) {\r\n installFailed = true;\r\n spinner.fail(\r\n \"Failed to install @revstackhq/core automatically (\" +\r\n packageManager +\r\n \"). Reason: \" +\r\n err.message,\r\n );\r\n }\r\n\r\n // Step 4: Final Success Message\r\n console.log(\r\n \"\\n\" +\r\n chalk.green(\" ✔ Created revstack config structure\\n\") +\r\n \"\\n\" +\r\n chalk.dim(\" Includes the \") +\r\n chalk.white(\"Default Guest Plan\") +\r\n chalk.dim(\" (required by Revstack).\\n\") +\r\n \"\\\\n\" +\r\n chalk.dim(\" Next steps:\\\\n\") +\r\n (installFailed\r\n ? chalk.dim(\" 0. \") +\r\n chalk.white(\r\n \"Run \" +\r\n chalk.bold(packageManager + \" install @revstackhq/core\") +\r\n \" manually\\\\n\",\r\n )\r\n : \"\") +\r\n chalk.dim(\" 1. \") +\r\n chalk.white(\"Edit the config to match your billing model\\\\n\") +\r\n chalk.dim(\" 2. \") +\r\n chalk.white(\"Run \") +\r\n chalk.bold(\"revstack login\") +\r\n chalk.white(\" to authenticate\\\\n\") +\r\n chalk.dim(\" 3. \") +\r\n chalk.white(\"Run \") +\r\n chalk.bold(\"revstack push\") +\r\n chalk.white(\" to deploy\\\\n\"),\r\n );\r\n });\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file commands/login.ts
|
|
5
|
+
* @description Interactive authentication flow. Prompts the user for their
|
|
6
|
+
* Revstack Secret Key and stores it globally for subsequent CLI commands.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const loginCommand: Command;
|
|
10
|
+
|
|
11
|
+
export { loginCommand };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/login.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
|
|
8
|
+
// src/utils/auth.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
12
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".revstack");
|
|
13
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
14
|
+
function setApiKey(key) {
|
|
15
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
16
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const credentials = { apiKey: key };
|
|
19
|
+
fs.writeFileSync(
|
|
20
|
+
CREDENTIALS_FILE,
|
|
21
|
+
JSON.stringify(credentials, null, 2),
|
|
22
|
+
"utf-8"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/commands/login.ts
|
|
27
|
+
var loginCommand = new Command("login").description("Authenticate with your Revstack Secret Key").action(async () => {
|
|
28
|
+
console.log(
|
|
29
|
+
"\n" + chalk.bold(" Revstack ") + chalk.dim("\u2014 Authentication\n")
|
|
30
|
+
);
|
|
31
|
+
const response = await prompts({
|
|
32
|
+
type: "password",
|
|
33
|
+
name: "secretKey",
|
|
34
|
+
message: "Enter your Revstack Secret Key",
|
|
35
|
+
validate: (value) => value.startsWith("sk_") ? true : "Secret key must start with sk_"
|
|
36
|
+
});
|
|
37
|
+
if (!response.secretKey) {
|
|
38
|
+
console.log(chalk.dim("\n Login cancelled.\n"));
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
setApiKey(response.secretKey);
|
|
42
|
+
console.log(
|
|
43
|
+
"\n" + chalk.green(" \u2714 Authenticated successfully!\n") + chalk.dim(" Credentials saved to ~/.revstack/credentials.json\n")
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
export {
|
|
47
|
+
loginCommand
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/login.ts","../../src/utils/auth.ts"],"sourcesContent":["/**\r\n * @file commands/login.ts\r\n * @description Interactive authentication flow. Prompts the user for their\r\n * Revstack Secret Key and stores it globally for subsequent CLI commands.\r\n */\r\n\r\nimport { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport prompts from \"prompts\";\r\nimport { setApiKey } from \"@/utils/auth.js\";\r\n\r\nexport const loginCommand = new Command(\"login\")\r\n .description(\"Authenticate with your Revstack Secret Key\")\r\n .action(async () => {\r\n console.log(\r\n \"\\n\" + chalk.bold(\" Revstack \") + chalk.dim(\"— Authentication\\n\")\r\n );\r\n\r\n const response = await prompts({\r\n type: \"password\",\r\n name: \"secretKey\",\r\n message: \"Enter your Revstack Secret Key\",\r\n validate: (value: string) =>\r\n value.startsWith(\"sk_\") ? true : \"Secret key must start with sk_\",\r\n });\r\n\r\n if (!response.secretKey) {\r\n console.log(chalk.dim(\"\\n Login cancelled.\\n\"));\r\n process.exit(0);\r\n }\r\n\r\n setApiKey(response.secretKey);\r\n\r\n console.log(\r\n \"\\n\" +\r\n chalk.green(\" ✔ Authenticated successfully!\\n\") +\r\n chalk.dim(\" Credentials saved to ~/.revstack/credentials.json\\n\")\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"],"mappings":";;;AAMA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,aAAa;;;ACFpB,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;;;ADrBO,IAAM,eAAe,IAAI,QAAQ,OAAO,EAC5C,YAAY,4CAA4C,EACxD,OAAO,YAAY;AAClB,UAAQ;AAAA,IACN,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,IAAI,yBAAoB;AAAA,EACnE;AAEA,QAAM,WAAW,MAAM,QAAQ;AAAA,IAC7B,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU,CAAC,UACT,MAAM,WAAW,KAAK,IAAI,OAAO;AAAA,EACrC,CAAC;AAED,MAAI,CAAC,SAAS,WAAW;AACvB,YAAQ,IAAI,MAAM,IAAI,wBAAwB,CAAC;AAC/C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,YAAU,SAAS,SAAS;AAE5B,UAAQ;AAAA,IACN,OACE,MAAM,MAAM,wCAAmC,IAC/C,MAAM,IAAI,yDAAyD;AAAA,EACvE;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/logout.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
// src/utils/auth.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".revstack");
|
|
12
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
13
|
+
function getApiKey() {
|
|
14
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
19
|
+
const credentials = JSON.parse(raw);
|
|
20
|
+
return credentials.apiKey ?? null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function clearApiKey() {
|
|
26
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
27
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/commands/logout.ts
|
|
32
|
+
var logoutCommand = new Command("logout").description("Clear stored Revstack credentials").action(() => {
|
|
33
|
+
if (!getApiKey()) {
|
|
34
|
+
console.log(chalk.dim("\n Not currently logged in.\n"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
clearApiKey();
|
|
38
|
+
console.log(
|
|
39
|
+
"\n" + chalk.green(" \u2714 Successfully logged out.\n") + chalk.dim(" Credentials removed from ~/.revstack/credentials.json\n")
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
export {
|
|
43
|
+
logoutCommand
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/logout.ts","../../src/utils/auth.ts"],"sourcesContent":["/**\r\n * @file commands/logout.ts\r\n * @description Clears stored Revstack credentials.\r\n */\r\n\r\nimport { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport { clearApiKey, getApiKey } from \"@/utils/auth.js\";\r\n\r\nexport const logoutCommand = new Command(\"logout\")\r\n .description(\"Clear stored Revstack credentials\")\r\n .action(() => {\r\n if (!getApiKey()) {\r\n console.log(chalk.dim(\"\\n Not currently logged in.\\n\"));\r\n return;\r\n }\r\n\r\n clearApiKey();\r\n\r\n console.log(\r\n \"\\n\" +\r\n chalk.green(\" ✔ Successfully logged out.\\n\") +\r\n chalk.dim(\" Credentials removed from ~/.revstack/credentials.json\\n\")\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"],"mappings":";;;AAKA,SAAS,eAAe;AACxB,OAAO,WAAW;;;ACAlB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAEf,IAAM,kBAAkB,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW;AAC3D,IAAM,mBAAmB,KAAK,KAAK,iBAAiB,kBAAkB;AA0B/D,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;;;ADjDO,IAAM,gBAAgB,IAAI,QAAQ,QAAQ,EAC9C,YAAY,mCAAmC,EAC/C,OAAO,MAAM;AACZ,MAAI,CAAC,UAAU,GAAG;AAChB,YAAQ,IAAI,MAAM,IAAI,gCAAgC,CAAC;AACvD;AAAA,EACF;AAEA,cAAY;AAEZ,UAAQ;AAAA,IACN,OACE,MAAM,MAAM,qCAAgC,IAC5C,MAAM,IAAI,6DAA6D;AAAA,EAC3E;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file commands/pull.ts
|
|
5
|
+
* @description Fetches the current billing configuration from Revstack Cloud
|
|
6
|
+
* and writes it back to the local `revstack.config.ts` file, overwriting
|
|
7
|
+
* the existing config after user confirmation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const pullCommand: Command;
|
|
11
|
+
|
|
12
|
+
export { pullCommand };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/pull.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import fs2 from "fs";
|
|
9
|
+
import path2 from "path";
|
|
10
|
+
|
|
11
|
+
// src/utils/auth.ts
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import os from "os";
|
|
15
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".revstack");
|
|
16
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
17
|
+
function getApiKey() {
|
|
18
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
23
|
+
const credentials = JSON.parse(raw);
|
|
24
|
+
return credentials.apiKey ?? null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/commands/pull.ts
|
|
31
|
+
function serializeObject(obj, depth = 0) {
|
|
32
|
+
const entries = Object.entries(obj);
|
|
33
|
+
if (entries.length === 0) return "{}";
|
|
34
|
+
const pad = " ".repeat(depth + 1);
|
|
35
|
+
const closePad = " ".repeat(depth);
|
|
36
|
+
const lines = entries.map(([key, value]) => {
|
|
37
|
+
if (value === void 0) return null;
|
|
38
|
+
const formattedValue = typeof value === "string" ? `"${value}"` : typeof value === "number" || typeof value === "boolean" ? String(value) : Array.isArray(value) ? serializeArray(value, depth + 1) : typeof value === "object" && value !== null ? serializeObject(value, depth + 1) : String(value);
|
|
39
|
+
return `${pad}${key}: ${formattedValue},`;
|
|
40
|
+
}).filter(Boolean);
|
|
41
|
+
return `{
|
|
42
|
+
${lines.join("\n")}
|
|
43
|
+
${closePad}}`;
|
|
44
|
+
}
|
|
45
|
+
function serializeArray(arr, depth) {
|
|
46
|
+
if (arr.length === 0) return "[]";
|
|
47
|
+
const pad = " ".repeat(depth + 1);
|
|
48
|
+
const closePad = " ".repeat(depth);
|
|
49
|
+
const items = arr.map((item) => {
|
|
50
|
+
if (typeof item === "object" && item !== null) {
|
|
51
|
+
return `${pad}${serializeObject(item, depth + 1)},`;
|
|
52
|
+
}
|
|
53
|
+
return `${pad}${JSON.stringify(item)},`;
|
|
54
|
+
});
|
|
55
|
+
return `[
|
|
56
|
+
${items.join("\n")}
|
|
57
|
+
${closePad}]`;
|
|
58
|
+
}
|
|
59
|
+
function generateFeaturesSource(config) {
|
|
60
|
+
const featureEntries = Object.entries(config.features).map(([slug, f]) => {
|
|
61
|
+
const props = {
|
|
62
|
+
name: f.name,
|
|
63
|
+
type: f.type,
|
|
64
|
+
unit_type: f.unit_type
|
|
65
|
+
};
|
|
66
|
+
if (f.description) props.description = f.description;
|
|
67
|
+
return ` ${slug}: defineFeature(${serializeObject(props, 2)}),`;
|
|
68
|
+
});
|
|
69
|
+
return `import { defineFeature } from "@revstackhq/core";
|
|
70
|
+
|
|
71
|
+
export const features = {
|
|
72
|
+
${featureEntries.join("\n")}
|
|
73
|
+
};
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
function generatePlansSource(config) {
|
|
77
|
+
const planEntries = Object.entries(config.plans).map(([slug, plan]) => {
|
|
78
|
+
const props = {
|
|
79
|
+
name: plan.name
|
|
80
|
+
};
|
|
81
|
+
if (plan.description) props.description = plan.description;
|
|
82
|
+
props.is_default = plan.is_default;
|
|
83
|
+
props.is_public = plan.is_public;
|
|
84
|
+
props.type = plan.type;
|
|
85
|
+
if (plan.prices && plan.prices.length > 0) {
|
|
86
|
+
props.prices = plan.prices;
|
|
87
|
+
}
|
|
88
|
+
props.features = plan.features;
|
|
89
|
+
const comment = plan.is_default ? ` // DO NOT DELETE: Automatically created default plan for guests.
|
|
90
|
+
` : "";
|
|
91
|
+
return `${comment} ${slug}: definePlan<typeof features>(${serializeObject(props, 3)}),`;
|
|
92
|
+
});
|
|
93
|
+
return `import { definePlan } from "@revstackhq/core";
|
|
94
|
+
import { features } from "./features";
|
|
95
|
+
|
|
96
|
+
export const plans = {
|
|
97
|
+
${planEntries.join("\n")}
|
|
98
|
+
};
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
function generateRootConfigSource() {
|
|
102
|
+
return `import { defineConfig } from "@revstackhq/core";
|
|
103
|
+
import { features } from "./revstack/features";
|
|
104
|
+
import { plans } from "./revstack/plans";
|
|
105
|
+
|
|
106
|
+
export default defineConfig({
|
|
107
|
+
features,
|
|
108
|
+
plans,
|
|
109
|
+
});
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
var API_BASE = "https://app.revstack.dev";
|
|
113
|
+
var pullCommand = new Command("pull").description(
|
|
114
|
+
"Pull the remote billing config and overwrite local revstack.config.ts and revstack/ files"
|
|
115
|
+
).option("-e, --env <environment>", "Target environment", "test").action(async (options) => {
|
|
116
|
+
const apiKey = getApiKey();
|
|
117
|
+
if (!apiKey) {
|
|
118
|
+
console.error(
|
|
119
|
+
"\n" + chalk.red(" \u2716 Not authenticated.\n") + chalk.dim(" Run ") + chalk.bold("revstack login") + chalk.dim(" first.\n")
|
|
120
|
+
);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const spinner = ora({
|
|
124
|
+
text: "Fetching remote configuration...",
|
|
125
|
+
prefixText: " "
|
|
126
|
+
}).start();
|
|
127
|
+
let remoteConfig;
|
|
128
|
+
try {
|
|
129
|
+
const res = await fetch(
|
|
130
|
+
`${API_BASE}/api/v1/cli/pull?env=${encodeURIComponent(options.env)}`,
|
|
131
|
+
{
|
|
132
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
spinner.fail("Failed to fetch remote config");
|
|
137
|
+
console.error(
|
|
138
|
+
chalk.red(`
|
|
139
|
+
API returned ${res.status}: ${res.statusText}
|
|
140
|
+
`)
|
|
141
|
+
);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
remoteConfig = await res.json();
|
|
145
|
+
spinner.succeed("Remote config fetched");
|
|
146
|
+
} catch (error) {
|
|
147
|
+
spinner.fail("Failed to reach Revstack Cloud");
|
|
148
|
+
console.error(chalk.red(`
|
|
149
|
+
${error.message}
|
|
150
|
+
`));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const featureCount = Object.keys(remoteConfig.features).length;
|
|
154
|
+
const planCount = Object.keys(remoteConfig.plans).length;
|
|
155
|
+
console.log(
|
|
156
|
+
"\n" + chalk.dim(" Remote state: ") + chalk.white(`${featureCount} features, ${planCount} plans`) + chalk.dim(` (${options.env})
|
|
157
|
+
`)
|
|
158
|
+
);
|
|
159
|
+
const cwd = process.cwd();
|
|
160
|
+
const configPath = path2.resolve(cwd, "revstack.config.ts");
|
|
161
|
+
const revstackDir = path2.resolve(cwd, "revstack");
|
|
162
|
+
const featuresPath = path2.resolve(revstackDir, "features.ts");
|
|
163
|
+
const plansPath = path2.resolve(revstackDir, "plans.ts");
|
|
164
|
+
const rootExists = fs2.existsSync(configPath);
|
|
165
|
+
const dirExists = fs2.existsSync(revstackDir);
|
|
166
|
+
if (rootExists || dirExists) {
|
|
167
|
+
const { confirm } = await prompts({
|
|
168
|
+
type: "confirm",
|
|
169
|
+
name: "confirm",
|
|
170
|
+
message: "This will overwrite your local configuration files (revstack.config.ts and revstack/ data). Are you sure?",
|
|
171
|
+
initial: false
|
|
172
|
+
});
|
|
173
|
+
if (!confirm) {
|
|
174
|
+
console.log(chalk.dim("\n Pull cancelled.\n"));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!fs2.existsSync(revstackDir)) {
|
|
179
|
+
fs2.mkdirSync(revstackDir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
const featuresSource = generateFeaturesSource(remoteConfig);
|
|
182
|
+
const plansSource = generatePlansSource(remoteConfig);
|
|
183
|
+
const rootSource = generateRootConfigSource();
|
|
184
|
+
fs2.writeFileSync(featuresPath, featuresSource, "utf-8");
|
|
185
|
+
fs2.writeFileSync(plansPath, plansSource, "utf-8");
|
|
186
|
+
fs2.writeFileSync(configPath, rootSource, "utf-8");
|
|
187
|
+
console.log(
|
|
188
|
+
"\n" + chalk.green(" \u2714 Local files updated from remote.\n") + chalk.dim(" Review the files and run ") + chalk.bold("revstack push") + chalk.dim(" to re-deploy.\n")
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
export {
|
|
192
|
+
pullCommand
|
|
193
|
+
};
|
|
194
|
+
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/pull.ts","../../src/utils/auth.ts"],"sourcesContent":["/**\r\n * @file commands/pull.ts\r\n * @description Fetches the current billing configuration from Revstack Cloud\r\n * and writes it back to the local `revstack.config.ts` file, overwriting\r\n * the existing config after user confirmation.\r\n */\r\n\r\nimport { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport ora from \"ora\";\r\nimport prompts from \"prompts\";\r\nimport fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { getApiKey } from \"@/utils/auth\";\r\n\r\n// ─── Types ───────────────────────────────────────────────────\r\n\r\ninterface RemoteFeature {\r\n name: string;\r\n type: string;\r\n unit_type: string;\r\n description?: string;\r\n}\r\n\r\ninterface RemotePrice {\r\n amount: number;\r\n currency: string;\r\n billing_interval: string;\r\n trial_period_days?: number;\r\n}\r\n\r\ninterface RemotePlanFeature {\r\n value_limit?: number;\r\n value_bool?: boolean;\r\n value_text?: string;\r\n is_hard_limit?: boolean;\r\n reset_period?: string;\r\n}\r\n\r\ninterface RemotePlan {\r\n name: string;\r\n description?: string;\r\n is_default: boolean;\r\n is_public: boolean;\r\n type: string;\r\n prices?: RemotePrice[];\r\n features: Record<string, RemotePlanFeature>;\r\n}\r\n\r\ninterface RemoteConfig {\r\n features: Record<string, RemoteFeature>;\r\n plans: Record<string, RemotePlan>;\r\n}\r\n\r\nfunction serializeObject(\r\n obj: Record<string, unknown>,\r\n depth: number = 0,\r\n): string {\r\n const entries = Object.entries(obj);\r\n if (entries.length === 0) return \"{}\";\r\n\r\n const pad = \" \".repeat(depth + 1);\r\n const closePad = \" \".repeat(depth);\r\n\r\n const lines = entries\r\n .map(([key, value]) => {\r\n if (value === undefined) return null;\r\n\r\n const formattedValue =\r\n typeof value === \"string\"\r\n ? `\"${value}\"`\r\n : typeof value === \"number\" || typeof value === \"boolean\"\r\n ? String(value)\r\n : Array.isArray(value)\r\n ? serializeArray(value, depth + 1)\r\n : typeof value === \"object\" && value !== null\r\n ? serializeObject(value as Record<string, unknown>, depth + 1)\r\n : String(value);\r\n\r\n return `${pad}${key}: ${formattedValue},`;\r\n })\r\n .filter(Boolean);\r\n\r\n return `{\\n${lines.join(\"\\n\")}\\n${closePad}}`;\r\n}\r\n\r\nfunction serializeArray(arr: unknown[], depth: number): string {\r\n if (arr.length === 0) return \"[]\";\r\n\r\n const pad = \" \".repeat(depth + 1);\r\n const closePad = \" \".repeat(depth);\r\n\r\n const items = arr.map((item) => {\r\n if (typeof item === \"object\" && item !== null) {\r\n return `${pad}${serializeObject(item as Record<string, unknown>, depth + 1)},`;\r\n }\r\n return `${pad}${JSON.stringify(item)},`;\r\n });\r\n\r\n return `[\\n${items.join(\"\\n\")}\\n${closePad}]`;\r\n}\r\n\r\nfunction generateFeaturesSource(config: RemoteConfig): string {\r\n const featureEntries = Object.entries(config.features).map(([slug, f]) => {\r\n const props: Record<string, unknown> = {\r\n name: f.name,\r\n type: f.type,\r\n unit_type: f.unit_type,\r\n };\r\n if (f.description) props.description = f.description;\r\n\r\n return ` ${slug}: defineFeature(${serializeObject(props, 2)}),`;\r\n });\r\n\r\n return `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n${featureEntries.join(\"\\n\")}\r\n};\r\n`;\r\n}\r\n\r\nfunction generatePlansSource(config: RemoteConfig): string {\r\n const planEntries = Object.entries(config.plans).map(([slug, plan]) => {\r\n const props: Record<string, unknown> = {\r\n name: plan.name,\r\n };\r\n if (plan.description) props.description = plan.description;\r\n props.is_default = plan.is_default;\r\n props.is_public = plan.is_public;\r\n props.type = plan.type;\r\n\r\n if (plan.prices && plan.prices.length > 0) {\r\n props.prices = plan.prices;\r\n }\r\n\r\n props.features = plan.features;\r\n\r\n const comment = plan.is_default\r\n ? ` // DO NOT DELETE: Automatically created default plan for guests.\\n`\r\n : \"\";\r\n\r\n return `${comment} ${slug}: definePlan<typeof features>(${serializeObject(props, 3)}),`;\r\n });\r\n\r\n return `import { definePlan } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const plans = {\r\n${planEntries.join(\"\\n\")}\r\n};\r\n`;\r\n}\r\n\r\nfunction generateRootConfigSource(): string {\r\n return `import { defineConfig } from \"@revstackhq/core\";\r\nimport { features } from \"./revstack/features\";\r\nimport { plans } from \"./revstack/plans\";\r\n\r\nexport default defineConfig({\r\n features,\r\n plans,\r\n});\r\n`;\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────\r\n\r\nconst API_BASE = \"https://app.revstack.dev\";\r\n\r\n// ─── Command ─────────────────────────────────────────────────\r\n\r\nexport const pullCommand = new Command(\"pull\")\r\n .description(\r\n \"Pull the remote billing config and overwrite local revstack.config.ts and revstack/ files\",\r\n )\r\n .option(\"-e, --env <environment>\", \"Target environment\", \"test\")\r\n .action(async (options: { env: 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 // ── 1. Fetch remote config ─────────────────────────────\r\n const spinner = ora({\r\n text: \"Fetching remote configuration...\",\r\n prefixText: \" \",\r\n }).start();\r\n\r\n let remoteConfig: RemoteConfig;\r\n\r\n try {\r\n const res = await fetch(\r\n `${API_BASE}/api/v1/cli/pull?env=${encodeURIComponent(options.env)}`,\r\n {\r\n headers: { Authorization: `Bearer ${apiKey}` },\r\n },\r\n );\r\n\r\n if (!res.ok) {\r\n spinner.fail(\"Failed to fetch remote config\");\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 remoteConfig = (await res.json()) as RemoteConfig;\r\n spinner.succeed(\"Remote config fetched\");\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 // ── 2. Show summary ────────────────────────────────────\r\n const featureCount = Object.keys(remoteConfig.features).length;\r\n const planCount = Object.keys(remoteConfig.plans).length;\r\n\r\n console.log(\r\n \"\\n\" +\r\n chalk.dim(\" Remote state: \") +\r\n chalk.white(`${featureCount} features, ${planCount} plans`) +\r\n chalk.dim(` (${options.env})\\n`),\r\n );\r\n\r\n // ── 3. Confirm overwrite ───────────────────────────────\r\n const cwd = process.cwd();\r\n const configPath = path.resolve(cwd, \"revstack.config.ts\");\r\n const revstackDir = path.resolve(cwd, \"revstack\");\r\n const featuresPath = path.resolve(revstackDir, \"features.ts\");\r\n const plansPath = path.resolve(revstackDir, \"plans.ts\");\r\n\r\n const rootExists = fs.existsSync(configPath);\r\n const dirExists = fs.existsSync(revstackDir);\r\n\r\n if (rootExists || dirExists) {\r\n const { confirm } = await prompts({\r\n type: \"confirm\",\r\n name: \"confirm\",\r\n message:\r\n \"This will overwrite your local configuration files (revstack.config.ts and revstack/ data). Are you sure?\",\r\n initial: false,\r\n });\r\n\r\n if (!confirm) {\r\n console.log(chalk.dim(\"\\n Pull cancelled.\\n\"));\r\n return;\r\n }\r\n }\r\n\r\n // ── 4. Generate and write ──────────────────────────────\r\n if (!fs.existsSync(revstackDir)) {\r\n fs.mkdirSync(revstackDir, { recursive: true });\r\n }\r\n\r\n const featuresSource = generateFeaturesSource(remoteConfig);\r\n const plansSource = generatePlansSource(remoteConfig);\r\n const rootSource = generateRootConfigSource();\r\n\r\n fs.writeFileSync(featuresPath, featuresSource, \"utf-8\");\r\n fs.writeFileSync(plansPath, plansSource, \"utf-8\");\r\n fs.writeFileSync(configPath, rootSource, \"utf-8\");\r\n\r\n console.log(\r\n \"\\n\" +\r\n chalk.green(\" ✔ Local files updated from remote.\\n\") +\r\n chalk.dim(\" Review the files and run \") +\r\n chalk.bold(\"revstack push\") +\r\n chalk.dim(\" to re-deploy.\\n\"),\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"],"mappings":"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","names":["fs","path","path","fs"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file commands/push.ts
|
|
5
|
+
* @description The core deployment command. Loads the local config, sends it
|
|
6
|
+
* to Revstack Cloud for diffing, presents the changes, and (upon confirmation)
|
|
7
|
+
* pushes the config to production.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const pushCommand: Command;
|
|
11
|
+
|
|
12
|
+
export { pushCommand };
|