@revstackhq/cli 0.0.0-dev-20260226063200 → 0.0.0-dev-20260227092523
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 +42 -30
- package/CHANGELOG.md +18 -0
- package/README.md +58 -38
- package/dist/cli.js +314 -43
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.js +279 -42
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +3 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.js +32 -0
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/templates/b2b-saas.d.ts +5 -0
- package/dist/commands/templates/b2b-saas.js +104 -0
- package/dist/commands/templates/b2b-saas.js.map +1 -0
- package/dist/commands/templates/index.d.ts +5 -0
- package/dist/commands/templates/index.js +264 -0
- package/dist/commands/templates/index.js.map +1 -0
- package/dist/commands/templates/starter.d.ts +10 -0
- package/dist/commands/templates/starter.js +88 -0
- package/dist/commands/templates/starter.js.map +1 -0
- package/dist/commands/templates/usage-based.d.ts +5 -0
- package/dist/commands/templates/usage-based.js +75 -0
- package/dist/commands/templates/usage-based.js.map +1 -0
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/config-loader.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +32 -32
- package/src/commands/init.ts +187 -210
- package/src/commands/login.ts +39 -39
- package/src/commands/logout.ts +27 -25
- package/src/commands/pull.ts +280 -280
- package/src/commands/push.ts +244 -206
- package/src/commands/templates/b2b-saas.ts +99 -0
- package/src/commands/templates/index.ts +12 -0
- package/src/commands/templates/starter.ts +89 -0
- package/src/commands/templates/usage-based.ts +70 -0
- package/src/utils/auth.ts +59 -59
- package/src/utils/config-loader.ts +57 -57
- package/tests/integration/init.test.ts +12 -2
- package/tests/integration/push.test.ts +20 -4
package/src/commands/push.ts
CHANGED
|
@@ -1,206 +1,244 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file commands/push.ts
|
|
3
|
-
* @description The core deployment command. Loads the local config, sends it
|
|
4
|
-
* to Revstack Cloud for diffing, presents the changes, and (upon confirmation)
|
|
5
|
-
* pushes the config to production.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Command } from "commander";
|
|
9
|
-
import chalk from "chalk";
|
|
10
|
-
import prompts from "prompts";
|
|
11
|
-
import ora from "ora";
|
|
12
|
-
import { getApiKey } from "@/utils/auth";
|
|
13
|
-
import { loadLocalConfig } from "@/utils/config-loader";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
chalk.
|
|
76
|
-
chalk.
|
|
77
|
-
chalk.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file commands/push.ts
|
|
3
|
+
* @description The core deployment command. Loads the local config, sends it
|
|
4
|
+
* to Revstack Cloud for diffing, presents the changes, and (upon confirmation)
|
|
5
|
+
* pushes the config to production.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import prompts from "prompts";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { getApiKey } from "@/utils/auth";
|
|
13
|
+
import { loadLocalConfig } from "@/utils/config-loader";
|
|
14
|
+
import { validateConfig, RevstackValidationError } from "@revstackhq/core";
|
|
15
|
+
|
|
16
|
+
// ─── Types ───────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
interface DiffEntry {
|
|
19
|
+
action: "added" | "removed" | "updated";
|
|
20
|
+
entity: string;
|
|
21
|
+
id: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface DiffResponse {
|
|
26
|
+
diff: DiffEntry[];
|
|
27
|
+
canPush: boolean;
|
|
28
|
+
blockedReason?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Helpers ─────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const API_BASE = "https://app.revstack.dev";
|
|
34
|
+
|
|
35
|
+
const DIFF_ICONS: Record<DiffEntry["action"], string> = {
|
|
36
|
+
added: chalk.green(" + "),
|
|
37
|
+
removed: chalk.red(" − "),
|
|
38
|
+
updated: chalk.yellow(" ~ "),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DIFF_COLORS: Record<DiffEntry["action"], (text: string) => string> = {
|
|
42
|
+
added: chalk.green,
|
|
43
|
+
removed: chalk.red,
|
|
44
|
+
updated: chalk.yellow,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function printDiff(diff: DiffEntry[]): void {
|
|
48
|
+
if (diff.length === 0) {
|
|
49
|
+
console.log(
|
|
50
|
+
chalk.dim("\n No changes detected. Your config is up to date.\n"),
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.bold("\n Changes:\n"));
|
|
56
|
+
|
|
57
|
+
for (const entry of diff) {
|
|
58
|
+
const icon = DIFF_ICONS[entry.action];
|
|
59
|
+
const color = DIFF_COLORS[entry.action];
|
|
60
|
+
const label = chalk.dim(`[${entry.entity}]`);
|
|
61
|
+
console.log(
|
|
62
|
+
`${icon}${color(entry.id)} ${label} ${chalk.white(entry.message)}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireAuth(): string {
|
|
70
|
+
const apiKey = getApiKey();
|
|
71
|
+
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
console.error(
|
|
74
|
+
"\n" +
|
|
75
|
+
chalk.red(" ✖ Not authenticated.\n") +
|
|
76
|
+
chalk.dim(" Run ") +
|
|
77
|
+
chalk.bold("revstack login") +
|
|
78
|
+
chalk.dim(" first.\n"),
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return apiKey;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Command ─────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export const pushCommand = new Command("push")
|
|
89
|
+
.description("Push your local billing config to Revstack Cloud")
|
|
90
|
+
.option("-e, --env <environment>", "Target environment", "test")
|
|
91
|
+
.action(async (options: { env: string }) => {
|
|
92
|
+
const apiKey = requireAuth();
|
|
93
|
+
const config = await loadLocalConfig(process.cwd());
|
|
94
|
+
|
|
95
|
+
// ── Step 1: Validate config ────────────────────────────────
|
|
96
|
+
|
|
97
|
+
const validationSpinner = ora({
|
|
98
|
+
text: "Validating billing configuration...",
|
|
99
|
+
prefixText: " ",
|
|
100
|
+
}).start();
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
validateConfig(config as any); // cast to match RevstackConfig expected by validateConfig
|
|
104
|
+
validationSpinner.succeed("Configuration validated");
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
if (
|
|
107
|
+
error instanceof RevstackValidationError ||
|
|
108
|
+
error?.name === "RevstackValidationError"
|
|
109
|
+
) {
|
|
110
|
+
validationSpinner.fail("Configuration invalid");
|
|
111
|
+
console.error(
|
|
112
|
+
chalk.red(
|
|
113
|
+
"\n ✖ The billing configuration contains business logic errors:\n",
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
for (const err of error.errors || []) {
|
|
117
|
+
console.error(chalk.red(` • ${err}`));
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
validationSpinner.fail("Validation failed");
|
|
124
|
+
console.error(
|
|
125
|
+
chalk.red(
|
|
126
|
+
`\n An unexpected error occurred during validation: ${error?.message || String(error)}\n`,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Step 2: Compute diff ──────────────────────────────────
|
|
133
|
+
|
|
134
|
+
const spinner = ora({
|
|
135
|
+
text: "Calculating diff...",
|
|
136
|
+
prefixText: " ",
|
|
137
|
+
}).start();
|
|
138
|
+
|
|
139
|
+
let diffResponse: DiffResponse;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${API_BASE}/api/v1/cli/diff`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
Authorization: `Bearer ${apiKey}`,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({ env: options.env, config }),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
spinner.fail("Failed to calculate diff");
|
|
153
|
+
console.error(
|
|
154
|
+
chalk.red(`\n API returned ${res.status}: ${res.statusText}\n`),
|
|
155
|
+
);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
diffResponse = (await res.json()) as DiffResponse;
|
|
160
|
+
spinner.succeed("Diff calculated");
|
|
161
|
+
} catch (error: unknown) {
|
|
162
|
+
spinner.fail("Failed to reach Revstack Cloud");
|
|
163
|
+
console.error(chalk.red(`\n ${(error as Error).message}\n`));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Step 2: Present diff ──────────────────────────────────
|
|
168
|
+
|
|
169
|
+
printDiff(diffResponse.diff);
|
|
170
|
+
|
|
171
|
+
if (diffResponse.diff.length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Step 3: Check if push is allowed ──────────────────────
|
|
176
|
+
|
|
177
|
+
if (!diffResponse.canPush) {
|
|
178
|
+
console.log(
|
|
179
|
+
chalk.red(" ✖ Push is blocked.\n") +
|
|
180
|
+
chalk.dim(
|
|
181
|
+
` ${diffResponse.blockedReason ?? "The server rejected this configuration."}\n`,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Step 4: Confirm ───────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
const envLabel =
|
|
190
|
+
options.env === "production"
|
|
191
|
+
? chalk.red.bold(options.env)
|
|
192
|
+
: chalk.cyan.bold(options.env);
|
|
193
|
+
|
|
194
|
+
const { confirm } = await prompts({
|
|
195
|
+
type: "confirm",
|
|
196
|
+
name: "confirm",
|
|
197
|
+
message: `Apply these changes to ${envLabel}?`,
|
|
198
|
+
initial: false,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!confirm) {
|
|
202
|
+
console.log(chalk.dim("\n Push cancelled.\n"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Step 5: Push ──────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
const pushSpinner = ora({
|
|
209
|
+
text: `Pushing to ${options.env}...`,
|
|
210
|
+
prefixText: " ",
|
|
211
|
+
}).start();
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const res = await fetch(`${API_BASE}/api/v1/cli/push`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json",
|
|
218
|
+
Authorization: `Bearer ${apiKey}`,
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({ env: options.env, config }),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
pushSpinner.fail("Push failed");
|
|
225
|
+
console.error(
|
|
226
|
+
chalk.red(`\n API returned ${res.status}: ${res.statusText}\n`),
|
|
227
|
+
);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
pushSpinner.succeed("Pushed successfully");
|
|
232
|
+
console.log(
|
|
233
|
+
"\n" +
|
|
234
|
+
chalk.green(" ✔ Config deployed to ") +
|
|
235
|
+
envLabel +
|
|
236
|
+
"\n" +
|
|
237
|
+
chalk.dim(" Changes are now live.\n"),
|
|
238
|
+
);
|
|
239
|
+
} catch (error: unknown) {
|
|
240
|
+
pushSpinner.fail("Push failed");
|
|
241
|
+
console.error(chalk.red(`\n ${(error as Error).message}\n`));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TemplateConfig } from "./starter";
|
|
2
|
+
|
|
3
|
+
export const b2bSaas: TemplateConfig = {
|
|
4
|
+
features: `import { defineFeature } from "@revstackhq/core";
|
|
5
|
+
|
|
6
|
+
export const features = {
|
|
7
|
+
active_users: defineFeature({ name: "Active Users", type: "static", unit_type: "count" }),
|
|
8
|
+
api_access: defineFeature({ name: "API Access", type: "boolean", unit_type: "custom" }),
|
|
9
|
+
custom_domain: defineFeature({ name: "Custom Domain", type: "boolean", unit_type: "custom" }),
|
|
10
|
+
};
|
|
11
|
+
`,
|
|
12
|
+
addons: `import { defineAddon } from "@revstackhq/core";
|
|
13
|
+
import { features } from "./features";
|
|
14
|
+
|
|
15
|
+
export const addons = {
|
|
16
|
+
extra_users: defineAddon<typeof features>({
|
|
17
|
+
name: "10 Extra Users",
|
|
18
|
+
description: "Add 10 more active users to your workspace.",
|
|
19
|
+
type: "recurring",
|
|
20
|
+
prices: [
|
|
21
|
+
{ amount: 5000, currency: "USD", billing_interval: "monthly" }
|
|
22
|
+
],
|
|
23
|
+
features: {
|
|
24
|
+
active_users: { value_limit: 10, type: "increment", is_hard_limit: true },
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
dedicated_support: defineAddon<typeof features>({
|
|
28
|
+
name: "Dedicated Support",
|
|
29
|
+
description: "Enterprise SLA with 1-hour response time.",
|
|
30
|
+
type: "recurring",
|
|
31
|
+
prices: [
|
|
32
|
+
{ amount: 49900, currency: "USD", billing_interval: "monthly" }
|
|
33
|
+
],
|
|
34
|
+
features: {}
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
`,
|
|
38
|
+
plans: `import { definePlan } from "@revstackhq/core";
|
|
39
|
+
import { features } from "./features";
|
|
40
|
+
|
|
41
|
+
export const plans = {
|
|
42
|
+
default: definePlan<typeof features>({
|
|
43
|
+
name: "Default",
|
|
44
|
+
description: "Automatically created default plan for guests.",
|
|
45
|
+
is_default: true,
|
|
46
|
+
is_public: false,
|
|
47
|
+
type: "free",
|
|
48
|
+
features: {},
|
|
49
|
+
}),
|
|
50
|
+
startup: definePlan<typeof features>({
|
|
51
|
+
name: "Startup",
|
|
52
|
+
description: "For small teams getting started.",
|
|
53
|
+
is_default: false,
|
|
54
|
+
is_public: true,
|
|
55
|
+
type: "paid",
|
|
56
|
+
available_addons: ["extra_users"],
|
|
57
|
+
prices: [
|
|
58
|
+
{ amount: 9900, currency: "USD", billing_interval: "monthly" }
|
|
59
|
+
],
|
|
60
|
+
features: {
|
|
61
|
+
active_users: { value_limit: 10, is_hard_limit: true },
|
|
62
|
+
api_access: { value_bool: false },
|
|
63
|
+
custom_domain: { value_bool: false },
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
enterprise: definePlan<typeof features>({
|
|
67
|
+
name: "Enterprise",
|
|
68
|
+
description: "Advanced features for scale.",
|
|
69
|
+
is_default: false,
|
|
70
|
+
is_public: true,
|
|
71
|
+
type: "paid",
|
|
72
|
+
available_addons: ["extra_users", "dedicated_support"],
|
|
73
|
+
prices: [
|
|
74
|
+
{ amount: 49900, currency: "USD", billing_interval: "monthly" }
|
|
75
|
+
],
|
|
76
|
+
features: {
|
|
77
|
+
active_users: { value_limit: 100, is_hard_limit: false },
|
|
78
|
+
api_access: { value_bool: true },
|
|
79
|
+
custom_domain: { value_bool: true },
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
`,
|
|
84
|
+
index: `import { defineConfig } from "@revstackhq/core";
|
|
85
|
+
import { features } from "./features";
|
|
86
|
+
import { addons } from "./addons";
|
|
87
|
+
import { plans } from "./plans";
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
features,
|
|
91
|
+
addons,
|
|
92
|
+
plans,
|
|
93
|
+
});
|
|
94
|
+
`,
|
|
95
|
+
root: `import config from "./revstack";
|
|
96
|
+
|
|
97
|
+
export default config;
|
|
98
|
+
`,
|
|
99
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { starter } from "./starter";
|
|
2
|
+
import { b2bSaas } from "./b2b-saas";
|
|
3
|
+
import { usageBased } from "./usage-based";
|
|
4
|
+
import type { TemplateConfig } from "./starter";
|
|
5
|
+
|
|
6
|
+
export const TEMPLATES: Record<string, TemplateConfig> = {
|
|
7
|
+
starter: starter,
|
|
8
|
+
"b2b-saas": b2bSaas,
|
|
9
|
+
"usage-based": usageBased,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type { TemplateConfig };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface TemplateConfig {
|
|
2
|
+
features: string;
|
|
3
|
+
addons: string;
|
|
4
|
+
plans: string;
|
|
5
|
+
index: string;
|
|
6
|
+
root: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const starter: TemplateConfig = {
|
|
10
|
+
features: `import { defineFeature } from "@revstackhq/core";
|
|
11
|
+
|
|
12
|
+
export const features = {
|
|
13
|
+
seats: defineFeature({ name: "Seats", type: "static", unit_type: "count" }),
|
|
14
|
+
priority_support: defineFeature({ name: "Priority Support", type: "boolean", unit_type: "custom" }),
|
|
15
|
+
};
|
|
16
|
+
`,
|
|
17
|
+
addons: `import { defineAddon } from "@revstackhq/core";
|
|
18
|
+
import { features } from "./features";
|
|
19
|
+
|
|
20
|
+
export const addons = {
|
|
21
|
+
extra_seats: defineAddon<typeof features>({
|
|
22
|
+
name: "5 Extra Seats",
|
|
23
|
+
description: "Add 5 more team members to your workspace.",
|
|
24
|
+
type: "recurring",
|
|
25
|
+
prices: [
|
|
26
|
+
{ amount: 1500, currency: "USD", billing_interval: "monthly" }
|
|
27
|
+
],
|
|
28
|
+
features: {
|
|
29
|
+
seats: { value_limit: 5, type: "increment", is_hard_limit: false },
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
vip_support: defineAddon<typeof features>({
|
|
33
|
+
name: "Priority Support",
|
|
34
|
+
description: "24/7 Slack channel support.",
|
|
35
|
+
type: "recurring",
|
|
36
|
+
prices: [
|
|
37
|
+
{ amount: 9900, currency: "USD", billing_interval: "monthly" }
|
|
38
|
+
],
|
|
39
|
+
features: {
|
|
40
|
+
priority_support: { has_access: true },
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
`,
|
|
45
|
+
plans: `import { definePlan } from "@revstackhq/core";
|
|
46
|
+
import { features } from "./features";
|
|
47
|
+
|
|
48
|
+
export const plans = {
|
|
49
|
+
// DO NOT DELETE: Automatically created default plan for guests.
|
|
50
|
+
default: definePlan<typeof features>({
|
|
51
|
+
name: "Default",
|
|
52
|
+
description: "Automatically created default plan for guests.",
|
|
53
|
+
is_default: true,
|
|
54
|
+
is_public: false,
|
|
55
|
+
type: "free",
|
|
56
|
+
features: {},
|
|
57
|
+
}),
|
|
58
|
+
pro: definePlan<typeof features>({
|
|
59
|
+
name: "Pro",
|
|
60
|
+
description: "For professional teams.",
|
|
61
|
+
is_default: false,
|
|
62
|
+
is_public: true,
|
|
63
|
+
type: "paid",
|
|
64
|
+
available_addons: ["extra_seats", "vip_support"],
|
|
65
|
+
prices: [
|
|
66
|
+
{ amount: 2900, currency: "USD", billing_interval: "monthly", trial_period_days: 14 }
|
|
67
|
+
],
|
|
68
|
+
features: {
|
|
69
|
+
seats: { value_limit: 5, is_hard_limit: true },
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
`,
|
|
74
|
+
index: `import { defineConfig } from "@revstackhq/core";
|
|
75
|
+
import { features } from "./features";
|
|
76
|
+
import { addons } from "./addons";
|
|
77
|
+
import { plans } from "./plans";
|
|
78
|
+
|
|
79
|
+
export default defineConfig({
|
|
80
|
+
features,
|
|
81
|
+
addons,
|
|
82
|
+
plans,
|
|
83
|
+
});
|
|
84
|
+
`,
|
|
85
|
+
root: `import config from "./revstack";
|
|
86
|
+
|
|
87
|
+
export default config;
|
|
88
|
+
`,
|
|
89
|
+
};
|