@revstackhq/cli 0.0.0-dev-20260227103607 → 0.0.0-dev-20260228062053
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 +51 -42
- package/CHANGELOG.md +18 -0
- package/dist/cli.js +622 -49
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +0 -7
- package/dist/commands/init.js +443 -24
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +0 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/pull.d.ts +0 -7
- package/dist/commands/pull.js +120 -11
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.js +59 -14
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/templates/ai-agent-platform.d.ts +5 -0
- package/dist/commands/templates/ai-agent-platform.js +119 -0
- package/dist/commands/templates/ai-agent-platform.js.map +1 -0
- package/dist/commands/templates/b2b-saas.js +23 -10
- package/dist/commands/templates/b2b-saas.js.map +1 -1
- package/dist/commands/templates/developer-tools.d.ts +5 -0
- package/dist/commands/templates/developer-tools.js +131 -0
- package/dist/commands/templates/developer-tools.js.map +1 -0
- package/dist/commands/templates/ecommerce-platform.d.ts +5 -0
- package/dist/commands/templates/ecommerce-platform.js +144 -0
- package/dist/commands/templates/ecommerce-platform.js.map +1 -0
- package/dist/commands/templates/index.js +437 -23
- package/dist/commands/templates/index.js.map +1 -1
- package/dist/commands/templates/starter.d.ts +1 -0
- package/dist/commands/templates/starter.js +13 -8
- package/dist/commands/templates/starter.js.map +1 -1
- package/dist/commands/templates/usage-based.js +18 -4
- package/dist/commands/templates/usage-based.js.map +1 -1
- package/dist/utils/auth.d.ts +0 -5
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/config-loader.d.ts +0 -6
- package/dist/utils/config-loader.js +4 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/init.ts +6 -8
- package/src/commands/login.ts +0 -6
- package/src/commands/logout.ts +0 -5
- package/src/commands/pull.ts +148 -60
- package/src/commands/push.ts +67 -16
- package/src/commands/templates/ai-agent-platform.ts +114 -0
- package/src/commands/templates/b2b-saas.ts +23 -10
- package/src/commands/templates/developer-tools.ts +126 -0
- package/src/commands/templates/ecommerce-platform.ts +139 -0
- package/src/commands/templates/index.ts +6 -0
- package/src/commands/templates/starter.ts +14 -8
- package/src/commands/templates/usage-based.ts +18 -4
- package/src/utils/auth.ts +0 -6
- package/src/utils/config-loader.ts +6 -7
- package/tests/integration/pull.test.ts +6 -0
- package/tests/integration/push.test.ts +3 -0
|
@@ -17,9 +17,9 @@ export const addons = {
|
|
|
17
17
|
name: "5 Extra Seats",
|
|
18
18
|
description: "Add 5 more team members to your workspace.",
|
|
19
19
|
type: "recurring",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
amount: 1500,
|
|
21
|
+
currency: "USD",
|
|
22
|
+
billing_interval: "monthly",
|
|
23
23
|
features: {
|
|
24
24
|
seats: { value_limit: 5, type: "increment", is_hard_limit: false },
|
|
25
25
|
}
|
|
@@ -28,9 +28,9 @@ export const addons = {
|
|
|
28
28
|
name: "Priority Support",
|
|
29
29
|
description: "24/7 Slack channel support.",
|
|
30
30
|
type: "recurring",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
amount: 9900,
|
|
32
|
+
currency: "USD",
|
|
33
|
+
billing_interval: "monthly",
|
|
34
34
|
features: {
|
|
35
35
|
priority_support: { has_access: true },
|
|
36
36
|
}
|
|
@@ -56,25 +56,30 @@ export const plans = {
|
|
|
56
56
|
is_default: false,
|
|
57
57
|
is_public: true,
|
|
58
58
|
type: "paid",
|
|
59
|
-
available_addons: ["extra_seats", "vip_support"],
|
|
60
59
|
prices: [
|
|
61
|
-
{ amount: 2900, currency: "USD", billing_interval: "monthly", trial_period_days: 14 }
|
|
60
|
+
{ amount: 2900, currency: "USD", billing_interval: "monthly", trial_period_days: 14, available_addons: ["extra_seats", "vip_support"] }
|
|
62
61
|
],
|
|
63
62
|
features: {
|
|
64
63
|
seats: { value_limit: 5, is_hard_limit: true },
|
|
65
64
|
},
|
|
66
65
|
}),
|
|
67
66
|
};
|
|
67
|
+
`,
|
|
68
|
+
coupons: `import type { DiscountDef } from "@revstackhq/core";
|
|
69
|
+
|
|
70
|
+
export const coupons: DiscountDef[] = [];
|
|
68
71
|
`,
|
|
69
72
|
index: `import { defineConfig } from "@revstackhq/core";
|
|
70
73
|
import { features } from "./features";
|
|
71
74
|
import { addons } from "./addons";
|
|
72
75
|
import { plans } from "./plans";
|
|
76
|
+
import { coupons } from "./coupons";
|
|
73
77
|
|
|
74
78
|
export default defineConfig({
|
|
75
79
|
features,
|
|
76
80
|
addons,
|
|
77
81
|
plans,
|
|
82
|
+
coupons,
|
|
78
83
|
});
|
|
79
84
|
`,
|
|
80
85
|
root: `import config from "./revstack";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/templates/starter.ts"],"sourcesContent":["export interface TemplateConfig {\r\n features: string;\r\n addons: string;\r\n plans: string;\r\n index: string;\r\n root: string;\r\n}\r\n\r\nexport const starter: TemplateConfig = {\r\n features: `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n seats: defineFeature({ name: \"Seats\", type: \"static\", unit_type: \"count\" }),\r\n priority_support: defineFeature({ name: \"Priority Support\", type: \"boolean\", unit_type: \"custom\" }),\r\n};\r\n`,\r\n addons: `import { defineAddon } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const addons = {\r\n extra_seats: defineAddon<typeof features>({\r\n name: \"5 Extra Seats\",\r\n description: \"Add 5 more team members to your workspace.\",\r\n type: \"recurring\",\r\n
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/templates/starter.ts"],"sourcesContent":["export interface TemplateConfig {\r\n features: string;\r\n addons: string;\r\n plans: string;\r\n coupons: string;\r\n index: string;\r\n root: string;\r\n}\r\n\r\nexport const starter: TemplateConfig = {\r\n features: `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n seats: defineFeature({ name: \"Seats\", type: \"static\", unit_type: \"count\" }),\r\n priority_support: defineFeature({ name: \"Priority Support\", type: \"boolean\", unit_type: \"custom\" }),\r\n};\r\n`,\r\n addons: `import { defineAddon } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const addons = {\r\n extra_seats: defineAddon<typeof features>({\r\n name: \"5 Extra Seats\",\r\n description: \"Add 5 more team members to your workspace.\",\r\n type: \"recurring\",\r\n amount: 1500,\r\n currency: \"USD\",\r\n billing_interval: \"monthly\",\r\n features: {\r\n seats: { value_limit: 5, type: \"increment\", is_hard_limit: false },\r\n }\r\n }),\r\n vip_support: defineAddon<typeof features>({\r\n name: \"Priority Support\",\r\n description: \"24/7 Slack channel support.\",\r\n type: \"recurring\",\r\n amount: 9900,\r\n currency: \"USD\",\r\n billing_interval: \"monthly\",\r\n features: {\r\n priority_support: { has_access: true },\r\n }\r\n })\r\n};\r\n`,\r\n 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 { amount: 2900, currency: \"USD\", billing_interval: \"monthly\", trial_period_days: 14, available_addons: [\"extra_seats\", \"vip_support\"] }\r\n ],\r\n features: {\r\n seats: { value_limit: 5, is_hard_limit: true },\r\n },\r\n }),\r\n};\r\n`,\r\n coupons: `import type { DiscountDef } from \"@revstackhq/core\";\r\n\r\nexport const coupons: DiscountDef[] = [];\r\n`,\r\n index: `import { defineConfig } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\nimport { addons } from \"./addons\";\r\nimport { plans } from \"./plans\";\r\nimport { coupons } from \"./coupons\";\r\n\r\nexport default defineConfig({\r\n features,\r\n addons,\r\n plans,\r\n coupons,\r\n});\r\n`,\r\n root: `import config from \"./revstack\";\r\n\r\nexport default config;\r\n`,\r\n};\r\n"],"mappings":";;;AASO,IAAM,UAA0B;AAAA,EACrC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOV,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BR,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BP,SAAS;AAAA;AAAA;AAAA;AAAA,EAIT,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaP,MAAM;AAAA;AAAA;AAAA;AAIR;","names":[]}
|
|
@@ -17,12 +17,24 @@ export const addons = {
|
|
|
17
17
|
name: "Premium Support",
|
|
18
18
|
description: "24/7 dedicated support.",
|
|
19
19
|
type: "recurring",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
amount: 20000,
|
|
21
|
+
currency: "USD",
|
|
22
|
+
billing_interval: "monthly",
|
|
23
23
|
features: {}
|
|
24
24
|
})
|
|
25
25
|
};
|
|
26
|
+
`,
|
|
27
|
+
coupons: `import type { DiscountDef } from "@revstackhq/core";
|
|
28
|
+
|
|
29
|
+
export const coupons: DiscountDef[] = [
|
|
30
|
+
{
|
|
31
|
+
code: "STARTUP_ACCELERATOR",
|
|
32
|
+
name: "Startup Program Credits",
|
|
33
|
+
type: "amount",
|
|
34
|
+
value: 500000, // $5,000 credit
|
|
35
|
+
duration: "forever",
|
|
36
|
+
}
|
|
37
|
+
];
|
|
26
38
|
`,
|
|
27
39
|
plans: `import { definePlan } from "@revstackhq/core";
|
|
28
40
|
import { features } from "./features";
|
|
@@ -42,12 +54,12 @@ export const plans = {
|
|
|
42
54
|
is_default: false,
|
|
43
55
|
is_public: true,
|
|
44
56
|
type: "paid",
|
|
45
|
-
available_addons: ["premium_support"],
|
|
46
57
|
prices: [
|
|
47
58
|
{
|
|
48
59
|
amount: 0,
|
|
49
60
|
currency: "USD",
|
|
50
61
|
billing_interval: "monthly",
|
|
62
|
+
available_addons: ["premium_support"],
|
|
51
63
|
overage_configuration: {
|
|
52
64
|
api_requests: { overage_amount: 15, overage_unit: 1000 }, // $0.15 per 1k extra requests
|
|
53
65
|
storage_gb: { overage_amount: 50, overage_unit: 1 }, // $0.50 per extra GB
|
|
@@ -65,11 +77,13 @@ export const plans = {
|
|
|
65
77
|
import { features } from "./features";
|
|
66
78
|
import { addons } from "./addons";
|
|
67
79
|
import { plans } from "./plans";
|
|
80
|
+
import { coupons } from "./coupons";
|
|
68
81
|
|
|
69
82
|
export default defineConfig({
|
|
70
83
|
features,
|
|
71
84
|
addons,
|
|
72
85
|
plans,
|
|
86
|
+
coupons,
|
|
73
87
|
});
|
|
74
88
|
`,
|
|
75
89
|
root: `import config from "./revstack";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/templates/usage-based.ts"],"sourcesContent":["import { TemplateConfig } from \"./starter\";\r\n\r\nexport const usageBased: TemplateConfig = {\r\n features: `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n api_requests: defineFeature({ name: \"API Requests\", type: \"metered\", unit_type: \"requests\" }),\r\n storage_gb: defineFeature({ name: \"Storage (GB)\", type: \"metered\", unit_type: \"custom\" }),\r\n};\r\n`,\r\n addons: `import { defineAddon } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const addons = {\r\n premium_support: defineAddon<typeof features>({\r\n name: \"Premium Support\",\r\n description: \"24/7 dedicated support.\",\r\n type: \"recurring\",\r\n
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/templates/usage-based.ts"],"sourcesContent":["import { TemplateConfig } from \"./starter\";\r\n\r\nexport const usageBased: TemplateConfig = {\r\n features: `import { defineFeature } from \"@revstackhq/core\";\r\n\r\nexport const features = {\r\n api_requests: defineFeature({ name: \"API Requests\", type: \"metered\", unit_type: \"requests\" }),\r\n storage_gb: defineFeature({ name: \"Storage (GB)\", type: \"metered\", unit_type: \"custom\" }),\r\n};\r\n`,\r\n addons: `import { defineAddon } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const addons = {\r\n premium_support: defineAddon<typeof features>({\r\n name: \"Premium Support\",\r\n description: \"24/7 dedicated support.\",\r\n type: \"recurring\",\r\n amount: 20000,\r\n currency: \"USD\",\r\n billing_interval: \"monthly\",\r\n features: {}\r\n })\r\n};\r\n`,\r\n coupons: `import type { DiscountDef } from \"@revstackhq/core\";\r\n\r\nexport const coupons: DiscountDef[] = [\r\n {\r\n code: \"STARTUP_ACCELERATOR\",\r\n name: \"Startup Program Credits\",\r\n type: \"amount\",\r\n value: 500000, // $5,000 credit\r\n duration: \"forever\",\r\n }\r\n];\r\n`,\r\n plans: `import { definePlan } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\n\r\nexport const plans = {\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 pay_as_you_go: definePlan<typeof features>({\r\n name: \"Pay As You Go\",\r\n description: \"Flexible usage-based pricing.\",\r\n is_default: false,\r\n is_public: true,\r\n type: \"paid\",\r\n prices: [\r\n {\r\n amount: 0,\r\n currency: \"USD\",\r\n billing_interval: \"monthly\",\r\n available_addons: [\"premium_support\"],\r\n overage_configuration: {\r\n api_requests: { overage_amount: 15, overage_unit: 1000 }, // $0.15 per 1k extra requests\r\n storage_gb: { overage_amount: 50, overage_unit: 1 }, // $0.50 per extra GB\r\n }\r\n } // Base platform fee\r\n ],\r\n features: {\r\n api_requests: { value_limit: 10000, is_hard_limit: false, reset_period: \"monthly\" }, // 10k free requests per month\r\n storage_gb: { value_limit: 5, is_hard_limit: false, reset_period: \"never\" }, // 5GB free storage lifetime\r\n },\r\n }),\r\n};\r\n`,\r\n index: `import { defineConfig } from \"@revstackhq/core\";\r\nimport { features } from \"./features\";\r\nimport { addons } from \"./addons\";\r\nimport { plans } from \"./plans\";\r\nimport { coupons } from \"./coupons\";\r\n\r\nexport default defineConfig({\r\n features,\r\n addons,\r\n plans,\r\n coupons,\r\n});\r\n`,\r\n root: `import config from \"./revstack\";\r\n\r\nexport default config;\r\n`,\r\n};\r\n"],"mappings":";;;AAEO,IAAM,aAA6B;AAAA,EACxC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOV,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYT,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqCP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaP,MAAM;AAAA;AAAA;AAAA;AAIR;","names":[]}
|
package/dist/utils/auth.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
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
1
|
/**
|
|
7
2
|
* Persist an API key to the global credentials file.
|
|
8
3
|
* Creates `~/.revstack/` if it doesn't exist.
|
package/dist/utils/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/auth.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/utils/auth.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nconst CREDENTIALS_DIR = path.join(os.homedir(), \".revstack\");\nconst CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, \"credentials.json\");\n\ninterface Credentials {\n apiKey: string;\n}\n\n/**\n * Persist an API key to the global credentials file.\n * Creates `~/.revstack/` if it doesn't exist.\n */\nexport function setApiKey(key: string): void {\n if (!fs.existsSync(CREDENTIALS_DIR)) {\n fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });\n }\n\n const credentials: Credentials = { apiKey: key };\n fs.writeFileSync(\n CREDENTIALS_FILE,\n JSON.stringify(credentials, null, 2),\n \"utf-8\",\n );\n}\n\n/**\n * Read the stored API key, or return `null` if none is configured.\n */\nexport function getApiKey(): string | null {\n if (!fs.existsSync(CREDENTIALS_FILE)) {\n return null;\n }\n\n try {\n const raw = fs.readFileSync(CREDENTIALS_FILE, \"utf-8\");\n const credentials: Credentials = JSON.parse(raw);\n return credentials.apiKey ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Remove stored credentials. Used by `revstack logout`.\n */\nexport function clearApiKey(): void {\n if (fs.existsSync(CREDENTIALS_FILE)) {\n fs.unlinkSync(CREDENTIALS_FILE);\n }\n}\n"],"mappings":";;;AAAA,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":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
/**
|
|
8
2
|
* Load the `revstack.config.ts` from the given directory.
|
|
9
3
|
*
|
|
@@ -19,6 +19,10 @@ async function loadLocalConfig(cwd) {
|
|
|
19
19
|
"\n \u2716 Could not find revstack.config.ts in the current directory.\n"
|
|
20
20
|
) + chalk.dim(" Run ") + chalk.bold("revstack init") + chalk.dim(" to create one.\n")
|
|
21
21
|
);
|
|
22
|
+
} else if (err.name === "SyntaxError" || error instanceof SyntaxError) {
|
|
23
|
+
console.error(
|
|
24
|
+
chalk.red("\n \u2716 Syntax Error in revstack.config.ts\n") + chalk.dim(" " + (err.message ?? String(error))) + "\n"
|
|
25
|
+
);
|
|
22
26
|
} else {
|
|
23
27
|
console.error(
|
|
24
28
|
chalk.red("\n \u2716 Failed to parse revstack.config.ts\n") + chalk.dim(" " + (err.message ?? String(error))) + "\n"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/config-loader.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/utils/config-loader.ts"],"sourcesContent":["import { createJiti } from \"jiti\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\n\n/**\n * Load the `revstack.config.ts` from the given directory.\n *\n * @param cwd - The directory to search for `revstack.config.ts`.\n * @returns The parsed and sanitized configuration object.\n */\nexport async function loadLocalConfig(\n cwd: string,\n): Promise<Record<string, unknown>> {\n const configPath = path.resolve(cwd, \"revstack.config.ts\");\n\n try {\n const jiti = createJiti(cwd);\n const module = (await jiti.import(configPath)) as Record<string, unknown>;\n const config = (module.default ?? module) as Record<string, unknown>;\n\n // Sanitize: strip functions, class instances, and non-serializable data.\n // This ensures we only send plain JSON to the Revstack Cloud API.\n return JSON.parse(JSON.stringify(config));\n } catch (error: unknown) {\n const err = error as NodeJS.ErrnoException;\n\n if (\n err.code === \"ERR_MODULE_NOT_FOUND\" ||\n err.code === \"ENOENT\" ||\n err.code === \"MODULE_NOT_FOUND\"\n ) {\n console.error(\n chalk.red(\n \"\\n ✖ Could not find revstack.config.ts in the current directory.\\n\",\n ) +\n chalk.dim(\" Run \") +\n chalk.bold(\"revstack init\") +\n chalk.dim(\" to create one.\\n\"),\n );\n } else if (err.name === \"SyntaxError\" || error instanceof SyntaxError) {\n console.error(\n chalk.red(\"\\n ✖ Syntax Error in revstack.config.ts\\n\") +\n chalk.dim(\" \" + (err.message ?? String(error))) +\n \"\\n\",\n );\n } else {\n console.error(\n chalk.red(\"\\n ✖ Failed to parse revstack.config.ts\\n\") +\n chalk.dim(\" \" + (err.message ?? String(error))) +\n \"\\n\",\n );\n }\n\n process.exit(1);\n }\n}\n"],"mappings":";;;AAAA,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,WAAW,IAAI,SAAS,iBAAiB,iBAAiB,aAAa;AACrE,cAAQ;AAAA,QACN,MAAM,IAAI,iDAA4C,IACpD,MAAM,IAAI,UAAU,IAAI,WAAW,OAAO,KAAK,EAAE,IACjD;AAAA,MACJ;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-20260228062053",
|
|
4
4
|
"description": "The official CLI for Revstack — Billing as Code",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"jiti": "^2.4.2",
|
|
16
16
|
"ora": "^8.2.0",
|
|
17
17
|
"prompts": "^2.4.2",
|
|
18
|
-
"@revstackhq/core": "0.0.0-dev-
|
|
18
|
+
"@revstackhq/core": "0.0.0-dev-20260228062053"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.11.0",
|
package/src/commands/init.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file commands/init.ts
|
|
3
|
-
* @description Scaffolds a new `revstack.config.ts` in the current directory.
|
|
4
|
-
* Generates a starter config with the immutable Default Guest Plan and
|
|
5
|
-
* a sample Pro plan using type-safe helpers from @revstackhq/core.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { Command } from "commander";
|
|
9
2
|
import chalk from "chalk";
|
|
10
3
|
import fs from "node:fs";
|
|
@@ -19,7 +12,7 @@ export const initCommand = new Command("init")
|
|
|
19
12
|
.description("Scaffold a new revstack.config.ts in the current directory")
|
|
20
13
|
.option(
|
|
21
14
|
"-t, --template <name>",
|
|
22
|
-
|
|
15
|
+
`Choose a starting template (${Object.keys(TEMPLATES).join(", ")})`,
|
|
23
16
|
"starter",
|
|
24
17
|
)
|
|
25
18
|
.action(async (options) => {
|
|
@@ -62,6 +55,11 @@ export const initCommand = new Command("init")
|
|
|
62
55
|
template.addons,
|
|
63
56
|
"utf-8",
|
|
64
57
|
);
|
|
58
|
+
fs.writeFileSync(
|
|
59
|
+
path.resolve(revstackDir, "coupons.ts"),
|
|
60
|
+
template.coupons,
|
|
61
|
+
"utf-8",
|
|
62
|
+
);
|
|
65
63
|
fs.writeFileSync(
|
|
66
64
|
path.resolve(revstackDir, "plans.ts"),
|
|
67
65
|
template.plans,
|
package/src/commands/login.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file commands/login.ts
|
|
3
|
-
* @description Interactive authentication flow. Prompts the user for their
|
|
4
|
-
* Revstack Secret Key and stores it globally for subsequent CLI commands.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { Command } from "commander";
|
|
8
2
|
import chalk from "chalk";
|
|
9
3
|
import prompts from "prompts";
|
package/src/commands/logout.ts
CHANGED
package/src/commands/pull.ts
CHANGED
|
@@ -1,56 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file commands/pull.ts
|
|
3
|
-
* @description Fetches the current billing configuration from Revstack Cloud
|
|
4
|
-
* and writes it back to the local `revstack.config.ts` file, overwriting
|
|
5
|
-
* the existing config after user confirmation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { Command } from "commander";
|
|
9
2
|
import chalk from "chalk";
|
|
10
3
|
import ora from "ora";
|
|
11
4
|
import prompts from "prompts";
|
|
12
5
|
import fs from "node:fs";
|
|
13
6
|
import path from "node:path";
|
|
7
|
+
import { execa } from "execa";
|
|
14
8
|
import { getApiKey } from "@/utils/auth";
|
|
15
|
-
|
|
16
|
-
// ─── Types ───────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
interface RemoteFeature {
|
|
19
|
-
name: string;
|
|
20
|
-
type: string;
|
|
21
|
-
unit_type: string;
|
|
22
|
-
description?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface RemotePrice {
|
|
26
|
-
amount: number;
|
|
27
|
-
currency: string;
|
|
28
|
-
billing_interval: string;
|
|
29
|
-
trial_period_days?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface RemotePlanFeature {
|
|
33
|
-
value_limit?: number;
|
|
34
|
-
value_bool?: boolean;
|
|
35
|
-
value_text?: string;
|
|
36
|
-
is_hard_limit?: boolean;
|
|
37
|
-
reset_period?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface RemotePlan {
|
|
41
|
-
name: string;
|
|
42
|
-
description?: string;
|
|
43
|
-
is_default: boolean;
|
|
44
|
-
is_public: boolean;
|
|
45
|
-
type: string;
|
|
46
|
-
prices?: RemotePrice[];
|
|
47
|
-
features: Record<string, RemotePlanFeature>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface RemoteConfig {
|
|
51
|
-
features: Record<string, RemoteFeature>;
|
|
52
|
-
plans: Record<string, RemotePlan>;
|
|
53
|
-
}
|
|
9
|
+
import { type RevstackConfig, RevstackConfigSchema } from "@revstackhq/core";
|
|
54
10
|
|
|
55
11
|
function serializeObject(
|
|
56
12
|
obj: Record<string, unknown>,
|
|
@@ -100,7 +56,7 @@ function serializeArray(arr: unknown[], depth: number): string {
|
|
|
100
56
|
return `[\n${items.join("\n")}\n${closePad}]`;
|
|
101
57
|
}
|
|
102
58
|
|
|
103
|
-
function generateFeaturesSource(config:
|
|
59
|
+
function generateFeaturesSource(config: RevstackConfig): string {
|
|
104
60
|
const featureEntries = Object.entries(config.features).map(([slug, f]) => {
|
|
105
61
|
const props: Record<string, unknown> = {
|
|
106
62
|
name: f.name,
|
|
@@ -120,7 +76,7 @@ ${featureEntries.join("\n")}
|
|
|
120
76
|
`;
|
|
121
77
|
}
|
|
122
78
|
|
|
123
|
-
function generatePlansSource(config:
|
|
79
|
+
function generatePlansSource(config: RevstackConfig): string {
|
|
124
80
|
const planEntries = Object.entries(config.plans).map(([slug, plan]) => {
|
|
125
81
|
const props: Record<string, unknown> = {
|
|
126
82
|
name: plan.name,
|
|
@@ -152,15 +108,99 @@ ${planEntries.join("\n")}
|
|
|
152
108
|
`;
|
|
153
109
|
}
|
|
154
110
|
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
import { features } from "./revstack/features";
|
|
158
|
-
import { plans } from "./revstack/plans";
|
|
111
|
+
function generateAddonsSource(config: RevstackConfig): string {
|
|
112
|
+
if (!config.addons) return "";
|
|
159
113
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
114
|
+
const addonEntries = Object.entries(config.addons).map(([slug, addon]) => {
|
|
115
|
+
const props: Record<string, unknown> = {
|
|
116
|
+
name: addon.name,
|
|
117
|
+
type: addon.type,
|
|
118
|
+
amount: addon.amount,
|
|
119
|
+
currency: addon.currency,
|
|
120
|
+
};
|
|
121
|
+
if (addon.description) props.description = addon.description;
|
|
122
|
+
if (addon.billing_interval) props.billing_interval = addon.billing_interval;
|
|
123
|
+
props.features = addon.features;
|
|
124
|
+
|
|
125
|
+
return ` ${slug}: defineAddon<typeof features>(${serializeObject(props, 2)}),`;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return `import { defineAddon } from "@revstackhq/core";
|
|
129
|
+
import { features } from "./features";
|
|
130
|
+
|
|
131
|
+
export const addons = {
|
|
132
|
+
${addonEntries.join("\n")}
|
|
133
|
+
};
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function generateCouponsSource(config: RevstackConfig): string {
|
|
138
|
+
if (!config.coupons || config.coupons.length === 0) return "";
|
|
139
|
+
|
|
140
|
+
// Make sure we format date strings and numbers properly but let serializeArray sort deep nesting
|
|
141
|
+
const couponsArray = config.coupons.map((coupon) => {
|
|
142
|
+
const props: Record<string, unknown> = {
|
|
143
|
+
code: coupon.code,
|
|
144
|
+
type: coupon.type,
|
|
145
|
+
value: coupon.value,
|
|
146
|
+
duration: coupon.duration,
|
|
147
|
+
};
|
|
148
|
+
if (coupon.name) props.name = coupon.name;
|
|
149
|
+
if (coupon.duration_in_months)
|
|
150
|
+
props.duration_in_months = coupon.duration_in_months;
|
|
151
|
+
if (coupon.applies_to_plans)
|
|
152
|
+
props.applies_to_plans = coupon.applies_to_plans;
|
|
153
|
+
if (coupon.max_redemptions) props.max_redemptions = coupon.max_redemptions;
|
|
154
|
+
if (coupon.expires_at) props.expires_at = coupon.expires_at;
|
|
155
|
+
|
|
156
|
+
return props;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return `import type { DiscountDef } from "@revstackhq/core";
|
|
160
|
+
|
|
161
|
+
export const coupons: DiscountDef[] = ${serializeArray(couponsArray, 0)};
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function generateRootConfigSource(config: RevstackConfig): string {
|
|
166
|
+
const hasAddons = !!config.addons && Object.keys(config.addons).length > 0;
|
|
167
|
+
const hasCoupons = !!config.coupons && config.coupons.length > 0;
|
|
168
|
+
|
|
169
|
+
const imports = [
|
|
170
|
+
`import { defineConfig } from "@revstackhq/core";`,
|
|
171
|
+
`import { features } from "./revstack/features";`,
|
|
172
|
+
`import { plans } from "./revstack/plans";`,
|
|
173
|
+
];
|
|
174
|
+
if (hasAddons) {
|
|
175
|
+
imports.push(`import { addons } from "./revstack/addons";`);
|
|
176
|
+
}
|
|
177
|
+
if (hasCoupons) {
|
|
178
|
+
imports.push(`import { coupons } from "./revstack/coupons";`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const configProps: Record<string, unknown> = {
|
|
182
|
+
features: "__RAW_features",
|
|
183
|
+
plans: "__RAW_plans",
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (hasAddons) {
|
|
187
|
+
configProps.addons = "__RAW_addons";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (hasCoupons) {
|
|
191
|
+
configProps.coupons = "__RAW_coupons";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Serialize the config object, but then remove the quotes around our RAW variables
|
|
195
|
+
let configStr = serializeObject(configProps, 0);
|
|
196
|
+
configStr = configStr.replace(/"__RAW_features"/g, "features");
|
|
197
|
+
configStr = configStr.replace(/"__RAW_plans"/g, "plans");
|
|
198
|
+
configStr = configStr.replace(/"__RAW_addons"/g, "addons");
|
|
199
|
+
configStr = configStr.replace(/"__RAW_coupons"/g, "coupons");
|
|
200
|
+
|
|
201
|
+
return `${imports.join("\n")}
|
|
202
|
+
|
|
203
|
+
export default defineConfig(${configStr});
|
|
164
204
|
`;
|
|
165
205
|
}
|
|
166
206
|
|
|
@@ -195,7 +235,7 @@ export const pullCommand = new Command("pull")
|
|
|
195
235
|
prefixText: " ",
|
|
196
236
|
}).start();
|
|
197
237
|
|
|
198
|
-
let remoteConfig:
|
|
238
|
+
let remoteConfig: RevstackConfig;
|
|
199
239
|
|
|
200
240
|
try {
|
|
201
241
|
const res = await fetch(
|
|
@@ -213,7 +253,16 @@ export const pullCommand = new Command("pull")
|
|
|
213
253
|
process.exit(1);
|
|
214
254
|
}
|
|
215
255
|
|
|
216
|
-
|
|
256
|
+
const rawData = await res.json();
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
remoteConfig = RevstackConfigSchema.parse(rawData);
|
|
260
|
+
} catch (validationError: any) {
|
|
261
|
+
spinner.fail("Remote config failed schema validation");
|
|
262
|
+
console.error(chalk.red(`\n ${validationError.message}\n`));
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
217
266
|
spinner.succeed("Remote config fetched");
|
|
218
267
|
} catch (error: unknown) {
|
|
219
268
|
spinner.fail("Failed to reach Revstack Cloud");
|
|
@@ -224,11 +273,17 @@ export const pullCommand = new Command("pull")
|
|
|
224
273
|
// ── 2. Show summary ────────────────────────────────────
|
|
225
274
|
const featureCount = Object.keys(remoteConfig.features).length;
|
|
226
275
|
const planCount = Object.keys(remoteConfig.plans).length;
|
|
276
|
+
const addonCount = remoteConfig.addons
|
|
277
|
+
? Object.keys(remoteConfig.addons).length
|
|
278
|
+
: 0;
|
|
279
|
+
const couponCount = remoteConfig.coupons ? remoteConfig.coupons.length : 0;
|
|
227
280
|
|
|
228
281
|
console.log(
|
|
229
282
|
"\n" +
|
|
230
283
|
chalk.dim(" Remote state: ") +
|
|
231
|
-
chalk.white(
|
|
284
|
+
chalk.white(
|
|
285
|
+
`${featureCount} features, ${planCount} plans, ${addonCount} addons, ${couponCount} coupons`,
|
|
286
|
+
) +
|
|
232
287
|
chalk.dim(` (${options.env})\n`),
|
|
233
288
|
);
|
|
234
289
|
|
|
@@ -264,12 +319,45 @@ export const pullCommand = new Command("pull")
|
|
|
264
319
|
|
|
265
320
|
const featuresSource = generateFeaturesSource(remoteConfig);
|
|
266
321
|
const plansSource = generatePlansSource(remoteConfig);
|
|
267
|
-
const rootSource = generateRootConfigSource();
|
|
322
|
+
const rootSource = generateRootConfigSource(remoteConfig);
|
|
268
323
|
|
|
269
324
|
fs.writeFileSync(featuresPath, featuresSource, "utf-8");
|
|
270
325
|
fs.writeFileSync(plansPath, plansSource, "utf-8");
|
|
271
326
|
fs.writeFileSync(configPath, rootSource, "utf-8");
|
|
272
327
|
|
|
328
|
+
if (remoteConfig.addons && Object.keys(remoteConfig.addons).length > 0) {
|
|
329
|
+
const addonsSource = generateAddonsSource(remoteConfig);
|
|
330
|
+
const addonsPath = path.resolve(revstackDir, "addons.ts");
|
|
331
|
+
fs.writeFileSync(addonsPath, addonsSource, "utf-8");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (remoteConfig.coupons && remoteConfig.coupons.length > 0) {
|
|
335
|
+
const couponsSource = generateCouponsSource(remoteConfig);
|
|
336
|
+
const couponsPath = path.resolve(revstackDir, "coupons.ts");
|
|
337
|
+
fs.writeFileSync(couponsPath, couponsSource, "utf-8");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── 5. Format Files ────────────────────────────────────
|
|
341
|
+
const formatSpinner = ora("Formatting generated files...").start();
|
|
342
|
+
try {
|
|
343
|
+
// Run prettier on the generated files. We use npx to ensure it runs
|
|
344
|
+
// using the locally hoisted version of prettier.
|
|
345
|
+
await execa(
|
|
346
|
+
"npx",
|
|
347
|
+
["prettier", "--write", "revstack.config.ts", "revstack/**/*.ts"],
|
|
348
|
+
{
|
|
349
|
+
cwd,
|
|
350
|
+
stdio: "ignore",
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
formatSpinner.succeed("Files formatted successfully");
|
|
354
|
+
} catch (err) {
|
|
355
|
+
// It's not the end of the world if prettier fails, just notify
|
|
356
|
+
formatSpinner.info(
|
|
357
|
+
"Files written properly, but automatic formatting (prettier) skipped or failed.",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
273
361
|
console.log(
|
|
274
362
|
"\n" +
|
|
275
363
|
chalk.green(" ✔ Local files updated from remote.\n") +
|