atmn 1.0.0-beta.2 → 1.0.0-beta.3

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.
@@ -1,4 +1,4 @@
1
- import { isLocalFlag, readFromEnv } from './chunk-PKRTTZCX.js';
1
+ import { isLocalFlag, readFromEnv } from './chunk-W6GJPEQT.js';
2
2
  import chalk from 'chalk';
3
3
  import axios, { AxiosError } from 'axios';
4
4
 
@@ -28,7 +28,7 @@ export const pro = plan({
28
28
  name: 'Pro',
29
29
  description: 'Professional plan for growing teams',
30
30
  add_on: false,
31
- default: false,
31
+ auto_enable: false,
32
32
  price: {
33
33
  amount: 50,
34
34
  interval: 'month',
@@ -48,16 +48,13 @@ export const pro = plan({
48
48
  price: {
49
49
  amount: 10,
50
50
  interval: 'month',
51
- usage_model: 'pay_per_use',
51
+ billing_method: 'pay_per_use',
52
52
  billing_units: 1,
53
53
  },
54
54
  }),
55
55
  ],
56
56
  });
57
57
  `;
58
-
59
- // source/core/api.ts
60
- var INTERNAL_BASE = BACKEND_URL;
61
58
  var EXTERNAL_BASE = `${BACKEND_URL}/v1`;
62
59
  async function request({
63
60
  method,
@@ -72,7 +69,6 @@ async function request({
72
69
  bypass
73
70
  }) {
74
71
  if (isLocalFlag()) {
75
- INTERNAL_BASE = "http://localhost:8080";
76
72
  EXTERNAL_BASE = "http://localhost:8080/v1";
77
73
  if (base) {
78
74
  base = base.replace(BACKEND_URL, "http://localhost:8080");
@@ -118,23 +114,6 @@ ${chalk.bgRed.white.bold(" API REQUEST FAILED ")}`);
118
114
  process.exit(1);
119
115
  }
120
116
  }
121
- async function internalRequest({
122
- method,
123
- path,
124
- data,
125
- headers,
126
- customAuth
127
- }) {
128
- return await request({
129
- method,
130
- base: INTERNAL_BASE,
131
- path,
132
- data,
133
- headers,
134
- customAuth,
135
- bypass: true
136
- });
137
- }
138
117
  async function externalRequest({
139
118
  method,
140
119
  path,
@@ -171,18 +150,6 @@ async function deletePlan({
171
150
  queryParams: { all_versions: !!allVersions }
172
151
  });
173
152
  }
174
- async function updateCLIStripeKeys({
175
- stripeSecretKey,
176
- autumnSecretKey
177
- }) {
178
- return await request({
179
- base: EXTERNAL_BASE,
180
- method: "POST",
181
- path: "/organization/stripe",
182
- data: { secret_key: stripeSecretKey },
183
- secretKey: autumnSecretKey
184
- });
185
- }
186
153
 
187
154
  // source/core/pull.ts
188
155
  async function getPlans(ids) {
@@ -236,7 +203,7 @@ async function getFeatures(params) {
236
203
  path: "/features",
237
204
  queryParams: { include_archived: params?.includeArchived ? true : false }
238
205
  });
239
- return list.map((feature) => feature);
206
+ return list;
240
207
  }
241
208
  var MAX_RECURSION_LIMIT = 500;
242
209
  async function getCustomers(limit = 100, offset = 0) {
@@ -264,4 +231,4 @@ async function getCustomers(limit = 100, offset = 0) {
264
231
  return customers;
265
232
  }
266
233
 
267
- export { DEFAULT_CONFIG, FRONTEND_URL, deleteFeature, deletePlan, externalRequest, getAllPlanVariants, getAllPlans, getCustomers, getFeatures, getPlans, internalRequest, updateCLIStripeKeys };
234
+ export { BACKEND_URL, DEFAULT_CONFIG, FRONTEND_URL, deleteFeature, deletePlan, externalRequest, getAllPlanVariants, getAllPlans, getCustomers, getFeatures, getPlans };
@@ -4,16 +4,28 @@ import dotenv from 'dotenv';
4
4
  import fs from 'fs';
5
5
  import yoctoSpinner from 'yocto-spinner';
6
6
 
7
+ // source/core/utils.ts
8
+
9
+ // source/core/cliContext.ts
10
+ var context = {
11
+ prod: false,
12
+ local: false
13
+ };
14
+ function isProd() {
15
+ return context.prod;
16
+ }
17
+ function isLocal() {
18
+ return context.local;
19
+ }
20
+
7
21
  // source/core/utils.ts
8
22
  var notNullish = (value) => value !== null && value !== void 0;
9
23
  var nullish = (value) => value === null || value === void 0;
10
24
  var isProdFlag = () => {
11
- const prodFlag = process.argv.includes("--prod") || process.argv.includes("-p");
12
- return prodFlag;
25
+ return isProd();
13
26
  };
14
27
  var isLocalFlag = () => {
15
- const localFlag = process.argv.includes("--local") || process.argv.includes("-l");
16
- return localFlag;
28
+ return isLocal();
17
29
  };
18
30
  function snakeCaseToCamelCase(value) {
19
31
  return value.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
@@ -34,21 +46,22 @@ function idToVar({
34
46
  async function upsertEnvVar(filePath, varName, newValue) {
35
47
  const content = fs.readFileSync(filePath, "utf-8");
36
48
  const lines = content.split("\n");
37
- let found = false;
49
+ let foundIndex = -1;
38
50
  for (let i = 0; i < lines.length; i++) {
39
51
  if (lines[i]?.startsWith(`${varName}=`)) {
40
- const shouldOverwrite = await confirm({
41
- message: `${varName} already exists in .env. Overwrite?`,
42
- default: false
43
- });
44
- if (shouldOverwrite) {
45
- lines[i] = `${varName}=${newValue}`;
46
- found = true;
47
- break;
48
- }
52
+ foundIndex = i;
53
+ break;
49
54
  }
50
55
  }
51
- if (!found) {
56
+ if (foundIndex !== -1) {
57
+ const shouldOverwrite = await confirm({
58
+ message: `${varName} already exists in .env. Overwrite?`,
59
+ default: false
60
+ });
61
+ if (shouldOverwrite) {
62
+ lines[foundIndex] = `${varName}=${newValue}`;
63
+ }
64
+ } else {
52
65
  lines.push(`${varName}=${newValue}`);
53
66
  }
54
67
  fs.writeFileSync(filePath, lines.join("\n"));
@@ -0,0 +1,234 @@
1
+ import { isLocalFlag, readFromEnv } from './chunk-NBLVYKWE.js';
2
+ import chalk from 'chalk';
3
+ import axios, { AxiosError } from 'axios';
4
+
5
+ // source/constants.ts
6
+ var FRONTEND_URL = "http://localhost:3000";
7
+ var BACKEND_URL = "http://localhost:8080";
8
+ var DEFAULT_CONFIG = `import {
9
+ feature,
10
+ plan,
11
+ planFeature,
12
+ } from 'atmn';
13
+
14
+ export const seats = feature({
15
+ id: 'seats',
16
+ name: 'Seats',
17
+ type: 'continuous_use',
18
+ });
19
+
20
+ export const messages = feature({
21
+ id: 'messages',
22
+ name: 'Messages',
23
+ type: 'single_use',
24
+ });
25
+
26
+ export const pro = plan({
27
+ id: 'pro',
28
+ name: 'Pro',
29
+ description: 'Professional plan for growing teams',
30
+ add_on: false,
31
+ auto_enable: false,
32
+ price: {
33
+ amount: 50,
34
+ interval: 'month',
35
+ },
36
+ features: [
37
+ // 500 messages per month
38
+ planFeature({
39
+ feature_id: messages.id,
40
+ granted: 500,
41
+ reset: { interval: 'month' },
42
+ }),
43
+
44
+ // $10 per seat per month
45
+ planFeature({
46
+ feature_id: seats.id,
47
+ granted: 1,
48
+ price: {
49
+ amount: 10,
50
+ interval: 'month',
51
+ billing_method: 'pay_per_use',
52
+ billing_units: 1,
53
+ },
54
+ }),
55
+ ],
56
+ });
57
+ `;
58
+ var EXTERNAL_BASE = `${BACKEND_URL}/v1`;
59
+ async function request({
60
+ method,
61
+ base,
62
+ path,
63
+ data,
64
+ headers,
65
+ customAuth,
66
+ throwOnError = true,
67
+ secretKey,
68
+ queryParams,
69
+ bypass
70
+ }) {
71
+ if (isLocalFlag()) {
72
+ EXTERNAL_BASE = "http://localhost:8080/v1";
73
+ if (base) {
74
+ base = base.replace(BACKEND_URL, "http://localhost:8080");
75
+ }
76
+ }
77
+ const apiKey = secretKey || readFromEnv({ bypass });
78
+ try {
79
+ const response = await axios.request({
80
+ method,
81
+ url: `${base}${path}`,
82
+ data,
83
+ params: queryParams,
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ "X-API-Version": "2.0.0",
87
+ ...headers,
88
+ Authorization: customAuth || `Bearer ${apiKey}`
89
+ }
90
+ });
91
+ return response.data;
92
+ } catch (error) {
93
+ if (throwOnError) {
94
+ throw error;
95
+ }
96
+ console.error(`
97
+ ${chalk.bgRed.white.bold(" API REQUEST FAILED ")}`);
98
+ const methodPath = `${method.toUpperCase()} ${base}${path}`;
99
+ console.error(chalk.red(methodPath));
100
+ if (error instanceof AxiosError) {
101
+ const status = error.response?.status;
102
+ const data2 = error.response?.data;
103
+ const code = data2?.code || data2?.error || "unknown_error";
104
+ const message = data2?.message || error.message || "An unknown error occurred";
105
+ if (status) {
106
+ console.error(chalk.redBright(`[${status}] ${code}`));
107
+ }
108
+ console.error(chalk.red(message));
109
+ } else if (error instanceof Error) {
110
+ console.error(chalk.red(error.message));
111
+ } else {
112
+ console.error(chalk.red(String(error)));
113
+ }
114
+ process.exit(1);
115
+ }
116
+ }
117
+ async function externalRequest({
118
+ method,
119
+ path,
120
+ data,
121
+ headers,
122
+ customAuth,
123
+ throwOnError = false,
124
+ queryParams
125
+ }) {
126
+ return await request({
127
+ method,
128
+ base: EXTERNAL_BASE,
129
+ path,
130
+ data,
131
+ headers,
132
+ customAuth,
133
+ throwOnError,
134
+ queryParams
135
+ });
136
+ }
137
+ async function deleteFeature({ id }) {
138
+ return await externalRequest({
139
+ method: "DELETE",
140
+ path: `/features/${id}`
141
+ });
142
+ }
143
+ async function deletePlan({
144
+ id,
145
+ allVersions
146
+ }) {
147
+ return await externalRequest({
148
+ method: "DELETE",
149
+ path: `/products/${id}`,
150
+ queryParams: { all_versions: !!allVersions }
151
+ });
152
+ }
153
+
154
+ // source/core/pull.ts
155
+ async function getPlans(ids) {
156
+ return await Promise.all(
157
+ ids.map(
158
+ (id) => externalRequest({
159
+ method: "GET",
160
+ path: `/products/${id}`
161
+ })
162
+ )
163
+ );
164
+ }
165
+ async function getAllPlans(params) {
166
+ const { list: plans } = await externalRequest({
167
+ method: "GET",
168
+ path: `/products`,
169
+ queryParams: { include_archived: params?.archived ? true : false }
170
+ });
171
+ return [...plans];
172
+ }
173
+ async function getAllPlanVariants() {
174
+ const { list } = await externalRequest({
175
+ method: "GET",
176
+ path: "/products"
177
+ });
178
+ const allPlans = [];
179
+ allPlans.push(
180
+ ...list.flatMap((plan) => {
181
+ if (plan.version > 1) {
182
+ return Array.from({ length: plan.version }, (_, i) => ({
183
+ id: plan.id,
184
+ name: plan.name,
185
+ version: i + 1
186
+ }));
187
+ } else {
188
+ return [
189
+ {
190
+ id: plan.id,
191
+ name: plan.name,
192
+ version: plan.version
193
+ }
194
+ ];
195
+ }
196
+ })
197
+ );
198
+ return allPlans;
199
+ }
200
+ async function getFeatures(params) {
201
+ const { list } = await externalRequest({
202
+ method: "GET",
203
+ path: "/features",
204
+ queryParams: { include_archived: params?.includeArchived ? true : false }
205
+ });
206
+ return list;
207
+ }
208
+ var MAX_RECURSION_LIMIT = 500;
209
+ async function getCustomers(limit = 100, offset = 0) {
210
+ const { list, total } = await externalRequest({
211
+ method: "GET",
212
+ path: `/customers?limit=${limit}&offset=${offset}`
213
+ });
214
+ const customers = list.map(
215
+ (customer) => ({
216
+ id: customer.id,
217
+ text: customer.name || customer.email || customer.id
218
+ })
219
+ );
220
+ if (offset + limit < total && offset < MAX_RECURSION_LIMIT) {
221
+ const remainingCustomers = await getCustomers(limit, offset + limit);
222
+ return [...customers, ...remainingCustomers];
223
+ } else if (offset >= MAX_RECURSION_LIMIT) {
224
+ console.log(
225
+ chalk.red(
226
+ `Reached maximum recursion limit of ${MAX_RECURSION_LIMIT} customers. Exiting.`
227
+ )
228
+ );
229
+ process.exit(1);
230
+ }
231
+ return customers;
232
+ }
233
+
234
+ export { BACKEND_URL, DEFAULT_CONFIG, FRONTEND_URL, deleteFeature, deletePlan, externalRequest, getAllPlanVariants, getAllPlans, getCustomers, getFeatures, getPlans };
@@ -0,0 +1,136 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import chalk from 'chalk';
3
+ import dotenv from 'dotenv';
4
+ import fs from 'fs';
5
+ import yoctoSpinner from 'yocto-spinner';
6
+
7
+ // source/core/utils.ts
8
+
9
+ // source/core/cliContext.ts
10
+ var context = {
11
+ prod: false,
12
+ local: false
13
+ };
14
+ function isProd() {
15
+ return context.prod;
16
+ }
17
+ function isLocal() {
18
+ return context.local;
19
+ }
20
+
21
+ // source/core/utils.ts
22
+ var notNullish = (value) => value !== null && value !== void 0;
23
+ var nullish = (value) => value === null || value === void 0;
24
+ var isProdFlag = () => {
25
+ return isProd();
26
+ };
27
+ var isLocalFlag = () => {
28
+ return isLocal();
29
+ };
30
+ function snakeCaseToCamelCase(value) {
31
+ return value.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
32
+ }
33
+ function idToVar({
34
+ id,
35
+ prefix = "product"
36
+ }) {
37
+ const processed = id.replace(/[-_](.)/g, (_, letter) => letter.toUpperCase()).replace(/[^a-zA-Z0-9_$]/g, "");
38
+ if (/^[0-9]/.test(processed)) {
39
+ return `${prefix}${processed}`;
40
+ }
41
+ if (/^[^a-zA-Z_$]/.test(processed)) {
42
+ return `${prefix}${processed}`;
43
+ }
44
+ return processed;
45
+ }
46
+ async function upsertEnvVar(filePath, varName, newValue) {
47
+ const content = fs.readFileSync(filePath, "utf-8");
48
+ const lines = content.split("\n");
49
+ let foundIndex = -1;
50
+ for (let i = 0; i < lines.length; i++) {
51
+ if (lines[i]?.startsWith(`${varName}=`)) {
52
+ foundIndex = i;
53
+ break;
54
+ }
55
+ }
56
+ if (foundIndex !== -1) {
57
+ const shouldOverwrite = await confirm({
58
+ message: `${varName} already exists in .env. Overwrite?`,
59
+ default: false
60
+ });
61
+ if (shouldOverwrite) {
62
+ lines[foundIndex] = `${varName}=${newValue}`;
63
+ }
64
+ } else {
65
+ lines.push(`${varName}=${newValue}`);
66
+ }
67
+ fs.writeFileSync(filePath, lines.join("\n"));
68
+ }
69
+ async function storeToEnv(prodKey, sandboxKey) {
70
+ const envPath = `${process.cwd()}/.env`;
71
+ const envLocalPath = `${process.cwd()}/.env.local`;
72
+ const envVars = `AUTUMN_PROD_SECRET_KEY=${prodKey}
73
+ AUTUMN_SECRET_KEY=${sandboxKey}
74
+ `;
75
+ if (fs.existsSync(envPath)) {
76
+ await upsertEnvVar(envPath, "AUTUMN_PROD_SECRET_KEY", prodKey);
77
+ await upsertEnvVar(envPath, "AUTUMN_SECRET_KEY", sandboxKey);
78
+ console.log(chalk.green(".env file found. Updated keys."));
79
+ } else if (fs.existsSync(envLocalPath)) {
80
+ fs.writeFileSync(envPath, envVars);
81
+ console.log(
82
+ chalk.green(
83
+ ".env.local found but .env not found. Created new .env file and wrote keys."
84
+ )
85
+ );
86
+ } else {
87
+ fs.writeFileSync(envPath, envVars);
88
+ console.log(
89
+ chalk.green(
90
+ "No .env or .env.local file found. Created new .env file and wrote keys."
91
+ )
92
+ );
93
+ }
94
+ }
95
+ function getEnvVar(parsed, prodFlag) {
96
+ return parsed["AUTUMN_SECRET_KEY"];
97
+ }
98
+ function readFromEnv(options) {
99
+ const envPath = `${process.cwd()}/.env`;
100
+ const envLocalPath = `${process.cwd()}/.env.local`;
101
+ if (process.env["AUTUMN_SECRET_KEY"]) {
102
+ return process.env["AUTUMN_SECRET_KEY"];
103
+ }
104
+ let secretKey;
105
+ if (fs.existsSync(envPath) && !secretKey)
106
+ secretKey = getEnvVar(
107
+ dotenv.parse(fs.readFileSync(envPath, "utf-8")));
108
+ if (fs.existsSync(envLocalPath) && !secretKey)
109
+ secretKey = getEnvVar(
110
+ dotenv.parse(fs.readFileSync(envLocalPath, "utf-8")));
111
+ if (!secretKey && !options?.bypass) {
112
+ {
113
+ console.error(
114
+ "[Error] atmn uses the AUTUMN_SECRET_KEY to call the Autumn sandbox API. Please add it to your .env (or .env.local) file or run `atmn login` to authenticate."
115
+ );
116
+ process.exit(1);
117
+ }
118
+ }
119
+ return secretKey;
120
+ }
121
+ function initSpinner(message) {
122
+ const spinner = yoctoSpinner({
123
+ text: message
124
+ });
125
+ spinner.start();
126
+ return spinner;
127
+ }
128
+ async function isSandboxKey(apiKey) {
129
+ const prefix = apiKey.split("am_sk_")[1]?.split("_")[0];
130
+ if (prefix === "live") {
131
+ return false;
132
+ } else if (prefix === "test") return true;
133
+ else throw new Error("Invalid API key");
134
+ }
135
+
136
+ export { idToVar, initSpinner, isLocalFlag, isProdFlag, isSandboxKey, notNullish, nullish, readFromEnv, snakeCaseToCamelCase, storeToEnv };