atmn 0.0.4 → 0.0.6

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/dist/cli.js CHANGED
@@ -1,58 +1,588 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
- import Init from './commands/init.js';
4
- import { loadAutumnConfigFile } from './core/config.js';
5
- import Push from './commands/push.js';
6
- import Pull from './commands/pull.js';
7
- import AuthCommand from './commands/auth.js';
3
+ import chalk6 from 'chalk';
4
+ import axios from 'axios';
5
+ import fs from 'fs';
6
+ import path, { resolve } from 'path';
7
+ import createJiti from 'jiti';
8
+ import { pathToFileURL } from 'url';
8
9
  import open from 'open';
9
- import chalk from 'chalk';
10
- import { writeConfig } from './core/config.js';
11
- import { FRONTEND_URL } from './constants.js';
12
- import { DEFAULT_CONFIG } from './constants.js';
13
- const VERSION = '1.0.0b';
14
- program
15
- .command('push')
16
- .description('Push changes to Autumn')
17
- .option('-p, --prod', 'Push to production')
18
- .option('-y, --yes', 'Confirm all deletions')
19
- .action(async (options) => {
20
- const config = await loadAutumnConfigFile();
21
- await Push({ config, yes: options.yes, prod: options.prod });
22
- });
23
- program
24
- .command('pull')
25
- .description('Pull changes from Autumn')
26
- .option('-p, --prod', 'Pull from production')
27
- .action(async () => {
28
- const config = await loadAutumnConfigFile();
10
+ import { confirm, input, password } from '@inquirer/prompts';
11
+
12
+ // source/constants.ts
13
+ var FRONTEND_URL = "http://app.useautumn.com";
14
+ var BACKEND_URL = "https://api.useautumn.com";
15
+ var DEFAULT_CONFIG = `import {
16
+ feature,
17
+ product,
18
+ priceItem,
19
+ featureItem,
20
+ pricedFeatureItem,
21
+ } from 'atmn';
22
+
23
+ const seats = feature({
24
+ id: 'seats',
25
+ name: 'Seats',
26
+ type: 'continuous_use',
27
+ });
28
+
29
+ const messages = feature({
30
+ id: 'messages',
31
+ name: 'Messages',
32
+ type: 'single_use',
33
+ });
34
+
35
+ const pro = product({
36
+ id: 'pro',
37
+ name: 'Pro',
38
+ items: [
39
+ // 500 messages per month
40
+ featureItem({
41
+ feature_id: messages.id,
42
+ included_usage: 500,
43
+ interval: 'month',
44
+ }),
45
+
46
+ // $10 per seat per month
47
+ pricedFeatureItem({
48
+ feature_id: seats.id,
49
+ price: 10,
50
+ interval: 'month',
51
+ }),
52
+
53
+ // $50 / month
54
+ priceItem({
55
+ price: 50,
56
+ interval: 'month',
57
+ }),
58
+ ],
59
+ });
60
+
61
+ const autumnConfig = {
62
+ features: [seats, messages],
63
+ products: [pro],
64
+ };
65
+
66
+ export default autumnConfig;
67
+ `;
68
+ function snakeCaseToCamelCase(value) {
69
+ return value.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
70
+ }
71
+ function idToVar(id) {
72
+ return id.replace(/[-_](.)/g, (_, letter) => letter.toUpperCase()).replace(/^[^a-zA-Z_$]/, "_").replace(/[^a-zA-Z0-9_$]/g, "");
73
+ }
74
+ function storeToEnv(prodKey, sandboxKey) {
75
+ const envPath = `${process.cwd()}/.env`;
76
+ const envVars = `# AUTUMN_SECRET_KEY=${prodKey}
77
+ AUTUMN_SECRET_KEY=${sandboxKey}
78
+ `;
79
+ if (fs.existsSync(envPath)) {
80
+ fs.appendFileSync(envPath, envVars);
81
+ console.log(chalk6.green(".env file found. Appended keys."));
82
+ } else {
83
+ fs.writeFileSync(envPath, envVars);
84
+ console.log(chalk6.green(".env file not found. Created new .env file and wrote keys."));
85
+ }
86
+ }
87
+ function readFromEnv() {
88
+ const envPath = `${process.cwd()}/.env`;
89
+ if (!fs.existsSync(envPath)) {
90
+ return void 0;
91
+ }
92
+ const envContent = fs.readFileSync(envPath, "utf-8");
93
+ const match = envContent.match(/^AUTUMN_SECRET_KEY=(.*)$/m);
94
+ return match ? match[1] : void 0;
95
+ }
96
+
97
+ // source/core/api.ts
98
+ var INTERNAL_BASE = BACKEND_URL;
99
+ var EXTERNAL_BASE = `${BACKEND_URL}/v1`;
100
+ async function request({
101
+ method,
102
+ base,
103
+ path: path2,
104
+ data,
105
+ headers,
106
+ customAuth,
107
+ throwOnError = true
108
+ }) {
109
+ const apiKey = readFromEnv();
110
+ try {
111
+ const response = await axios.request({
112
+ method,
113
+ url: `${base}${path2}`,
114
+ data,
115
+ headers: {
116
+ "Content-Type": "application/json",
117
+ ...headers,
118
+ Authorization: customAuth || `Bearer ${apiKey}`
119
+ }
120
+ });
121
+ return response.data;
122
+ } catch (error) {
123
+ if (throwOnError) {
124
+ throw error;
125
+ }
126
+ console.error(
127
+ chalk6.red("Error occured when making API request:"),
128
+ chalk6.red(error.response.data.message || error.response.data.error)
129
+ );
130
+ process.exit(1);
131
+ }
132
+ }
133
+ async function internalRequest({
134
+ method,
135
+ path: path2,
136
+ data,
137
+ headers,
138
+ customAuth
139
+ }) {
140
+ return await request({
141
+ method,
142
+ base: INTERNAL_BASE,
143
+ path: path2,
144
+ data,
145
+ headers,
146
+ customAuth
147
+ });
148
+ }
149
+ async function externalRequest({
150
+ method,
151
+ path: path2,
152
+ data,
153
+ headers,
154
+ customAuth,
155
+ throwOnError = false
156
+ }) {
157
+ return await request({
158
+ method,
159
+ base: EXTERNAL_BASE,
160
+ path: path2,
161
+ data,
162
+ headers,
163
+ customAuth,
164
+ throwOnError
165
+ });
166
+ }
167
+ async function deleteFeature(id) {
168
+ return await externalRequest({
169
+ method: "DELETE",
170
+ path: `/features/${id}`
171
+ });
172
+ }
173
+ async function deleteProduct(id) {
174
+ return await externalRequest({
175
+ method: "DELETE",
176
+ path: `/products/${id}`
177
+ });
178
+ }
179
+ async function updateCLIStripeKeys(stripeTestKey, stripeLiveKey, stripeFlowAuthKey) {
180
+ return await internalRequest({
181
+ method: "POST",
182
+ path: "/dev/cli/stripe",
183
+ data: {
184
+ stripeTestKey,
185
+ stripeLiveKey,
186
+ successUrl: "https://useautumn.com",
187
+ defaultCurrency: "usd"
188
+ },
189
+ customAuth: stripeFlowAuthKey
190
+ });
191
+ }
192
+
193
+ // source/core/pull.ts
194
+ async function getAllProducts() {
195
+ const { list } = await externalRequest({
196
+ method: "GET",
197
+ path: "/products"
198
+ });
199
+ return list.map((product) => product);
200
+ }
201
+ async function getFeatures() {
202
+ const { list } = await externalRequest({
203
+ method: "GET",
204
+ path: "/features"
205
+ });
206
+ return list.map((feature) => feature);
207
+ }
208
+
209
+ // source/core/builders/products.ts
210
+ var ItemBuilders = {
211
+ priced_feature: pricedFeatureItemBuilder,
212
+ feature: featureItemBuilder,
213
+ price: priceItemBuilder
214
+ };
215
+ function importBuilder() {
216
+ return `
217
+ import {
218
+ feature,
219
+ product,
220
+ featureItem,
221
+ pricedFeatureItem,
222
+ priceItem,
223
+ } from 'atmn';
224
+ `;
225
+ }
226
+ function exportBuilder(productIds, featureIds) {
227
+ const snippet = `
228
+ const autumnConfig = {
229
+ products: [${productIds.map((id) => `${idToVar(id)}`).join(", ")}],
230
+ features: [${featureIds.map((id) => `${idToVar(id)}`).join(", ")}]
231
+ }
232
+
233
+ export default autumnConfig;
234
+ `;
235
+ return snippet;
236
+ }
237
+ function productBuilder(product) {
238
+ const snippet = `
239
+ export const ${idToVar(product.id)} = product({
240
+ id: '${product.id}',
241
+ name: '${product.name}',
242
+ items: [${product.items.map(
243
+ (item) => `${ItemBuilders[item.type](item)}`
244
+ ).join(" ")} ]
245
+ })
246
+ `;
247
+ return snippet;
248
+ }
249
+ function pricedFeatureItemBuilder(item) {
250
+ const intervalLine = item.interval == null ? "" : `
251
+ interval: '${item.interval}',`;
252
+ const snippet = `
253
+ pricedFeatureItem({
254
+ feature_id: ${idToVar(item.feature_id)}.id,
255
+ price: ${item.price},${intervalLine}
256
+ included_usage: ${item.included_usage},
257
+ billing_units: ${item.billing_units},
258
+ usage_model: '${item.usage_model}',
259
+ }),
260
+ `;
261
+ return snippet;
262
+ }
263
+ function featureItemBuilder(item) {
264
+ const intervalLine = item.interval == null ? "" : `
265
+ interval: '${item.interval}',`;
266
+ const snippet = `
267
+ featureItem({
268
+ feature_id: ${idToVar(item.feature_id)}.id,
269
+ included_usage: ${item.included_usage},${intervalLine}
270
+ }),
271
+ `;
272
+ return snippet;
273
+ }
274
+ function priceItemBuilder(item) {
275
+ const intervalLine = item.interval == null ? "" : `
276
+ interval: '${item.interval}',`;
277
+ const snippet = `
278
+ priceItem({
279
+ price: ${item.price},${intervalLine}
280
+ }),
281
+ `;
282
+ return snippet;
283
+ }
284
+
285
+ // source/core/builders/features.ts
286
+ function featureBuilder(feature) {
287
+ const snippet = `
288
+ export const ${idToVar(feature.id)} = feature({
289
+ id: '${feature.id}',
290
+ name: '${feature.name}',
291
+ type: '${feature.type}',
292
+ })`;
293
+ return snippet;
294
+ }
295
+ async function loadAutumnConfigFile() {
296
+ const configPath = path.join(process.cwd(), "autumn.config.ts");
297
+ const absolutePath = resolve(configPath);
298
+ const fileUrl = pathToFileURL(absolutePath).href;
299
+ const jiti = createJiti(import.meta.url);
300
+ const mod = await jiti.import(fileUrl);
301
+ const def = mod.default || mod;
302
+ if (!def.products || !Array.isArray(def.products)) {
303
+ throw new Error(
304
+ "You must export a products field that is an array of products."
305
+ );
306
+ }
307
+ if (!def.features || !Array.isArray(def.features)) {
308
+ throw new Error(
309
+ "You must export a features field that is an array of products."
310
+ );
311
+ }
312
+ return def;
313
+ }
314
+ function writeConfig(config) {
315
+ const configPath = path.join(process.cwd(), "autumn.config.ts");
316
+ fs.writeFileSync(configPath, config);
317
+ }
318
+
319
+ // source/commands/pull.ts
320
+ async function Pull({ config }) {
321
+ console.log(chalk6.green("Pulling products and features from Autumn..."));
322
+ const products = await getAllProducts();
323
+ const features = await getFeatures();
324
+ const productSnippets = products.map(
325
+ (product) => productBuilder(product)
326
+ );
327
+ const featureSnippets = features.map(
328
+ (feature) => featureBuilder(feature)
329
+ );
330
+ const autumnConfig = `
331
+ ${importBuilder()}
332
+
333
+ // Features
334
+ ${featureSnippets.join("\n")}
335
+
336
+ // Products
337
+ ${productSnippets.join("\n")}
338
+
339
+ // Remember to update this when you make changes!
340
+ ${exportBuilder(
341
+ products.map((product) => product.id),
342
+ features.map((feature) => snakeCaseToCamelCase(feature.id))
343
+ )}
344
+ `;
345
+ writeConfig(autumnConfig);
346
+ console.log(chalk6.green("Success! Config has been updated."));
347
+ }
348
+
349
+ // source/core/auth.ts
350
+ async function getOTP(otp) {
351
+ const response = await internalRequest({
352
+ method: "GET",
353
+ path: `/dev/otp/${otp}`
354
+ });
355
+ return response;
356
+ }
357
+
358
+ // source/commands/auth.ts
359
+ var passwordTheme = {
360
+ style: {
361
+ answer: (text) => {
362
+ return chalk6.magenta("*".repeat(text.length));
363
+ }
364
+ }
365
+ };
366
+ var inputTheme = {
367
+ style: {
368
+ answer: (text) => {
369
+ return chalk6.magenta(text);
370
+ }
371
+ }
372
+ };
373
+ async function AuthCommand() {
374
+ if (readFromEnv()) {
375
+ let shouldReauth = await confirm({
376
+ message: "You are already authenticated. Would you like to re-authenticate?",
377
+ theme: inputTheme
378
+ });
379
+ if (!shouldReauth) {
380
+ return;
381
+ }
382
+ }
383
+ open(`${FRONTEND_URL}/dev/cli`);
384
+ const otp = await input({
385
+ message: "Enter OTP:",
386
+ theme: inputTheme
387
+ });
388
+ const keyInfo = await getOTP(otp);
389
+ if (!keyInfo.stripe_connected) {
390
+ let connectStripe = await confirm({
391
+ message: "It seems like your organization doesn't have any Stripe keys connected. Would you like to connect them now?",
392
+ theme: inputTheme
393
+ });
394
+ if (connectStripe) {
395
+ let stripeTestKey = await password({
396
+ message: "Enter Stripe Test Secret Key:",
397
+ mask: "*",
398
+ theme: passwordTheme
399
+ });
400
+ await updateCLIStripeKeys(
401
+ stripeTestKey,
402
+ stripeTestKey,
403
+ keyInfo.stripeFlowAuthKey
404
+ );
405
+ } else {
406
+ console.log(
407
+ chalk6.yellow(
408
+ "Okay, no worries. Go to the Autumn dashboard when you're ready!"
409
+ )
410
+ );
411
+ }
412
+ }
413
+ storeToEnv(keyInfo.prodKey, keyInfo.sandboxKey);
414
+ console.log(
415
+ chalk6.green(
416
+ "Success! Keys have been stored locally. You may now use the CLI."
417
+ )
418
+ );
419
+ }
420
+
421
+ // source/commands/init.ts
422
+ async function Init({ config }) {
423
+ let apiKey = readFromEnv();
424
+ if (apiKey) {
425
+ console.log(chalk6.green("API key found. Pulling latest config..."));
426
+ await Pull({ config });
427
+ console.log(chalk6.green("Project initialized and config pulled successfully!"));
428
+ return;
429
+ }
430
+ console.log(chalk6.yellow("No API key found. Running authentication..."));
431
+ await AuthCommand();
432
+ apiKey = readFromEnv();
433
+ if (apiKey) {
29
434
  await Pull({ config });
435
+ console.log(chalk6.green("Project initialized! You are now authenticated and config has been pulled."));
436
+ } else {
437
+ console.log(chalk6.red("Authentication did not yield an API key. Please check your setup."));
438
+ }
439
+ }
440
+
441
+ // source/core/push.ts
442
+ async function checkForDeletables(currentFeatures, currentProducts) {
443
+ const features = await getFeatures();
444
+ const featureIds = features.map((feature) => feature.id);
445
+ const currentFeatureIds = currentFeatures.map((feature) => feature.id);
446
+ const featuresToDelete = featureIds.filter(
447
+ (featureId) => !currentFeatureIds.includes(featureId)
448
+ );
449
+ const products = await getAllProducts();
450
+ const productIds = products.map((product) => product.id);
451
+ const currentProductIds = currentProducts.map((product) => product.id);
452
+ const productsToDelete = productIds.filter(
453
+ (productId) => !currentProductIds.includes(productId)
454
+ );
455
+ return { featuresToDelete, productsToDelete };
456
+ }
457
+ async function upsertFeature(feature) {
458
+ try {
459
+ const response = await externalRequest({
460
+ method: "POST",
461
+ path: `/features`,
462
+ data: feature,
463
+ throwOnError: true
464
+ // data: {
465
+ // ...feature,
466
+ // config: {
467
+ // filters: [{property: '', operator: '', value: []}],
468
+ // usage_type: 'single_use',
469
+ // },
470
+ // },
471
+ });
472
+ return response.data;
473
+ } catch (error) {
474
+ const response = await externalRequest({
475
+ method: "POST",
476
+ path: `/features/${feature.id}`,
477
+ data: feature
478
+ // data: {
479
+ // ...feature,
480
+ // config: {
481
+ // filters: [{property: '', operator: '', value: []}],
482
+ // usage_type: 'single_use',
483
+ // },
484
+ // },
485
+ });
486
+ return response.data;
487
+ }
488
+ }
489
+ async function upsertProduct(product) {
490
+ try {
491
+ const response = await externalRequest({
492
+ method: "POST",
493
+ path: `/products`,
494
+ data: product,
495
+ throwOnError: true
496
+ });
497
+ return response.data;
498
+ } catch (error) {
499
+ const response = await externalRequest({
500
+ method: "POST",
501
+ path: `/products/${product.id}`,
502
+ data: product
503
+ });
504
+ return response.data;
505
+ }
506
+ }
507
+
508
+ // source/commands/push.ts
509
+ async function Push({
510
+ config,
511
+ yes,
512
+ prod
513
+ }) {
514
+ let { features, products } = config;
515
+ let { featuresToDelete, productsToDelete } = await checkForDeletables(
516
+ features,
517
+ products
518
+ );
519
+ for (let productId of productsToDelete) {
520
+ let shouldDelete = yes || await confirm({
521
+ message: `Delete product [${productId}]?`
522
+ });
523
+ if (shouldDelete) {
524
+ await deleteProduct(productId);
525
+ console.log(chalk6.green(`Product [${productId}] deleted successfully!`));
526
+ }
527
+ }
528
+ for (let feature of features) {
529
+ console.log(chalk6.green(`Pushing feature [${feature.id}]`));
530
+ await upsertFeature(feature);
531
+ console.log(chalk6.green(`Pushed feature [${feature.id}]`));
532
+ }
533
+ for (let product of products) {
534
+ console.log(chalk6.green(`Pushing product [${product.id}]`));
535
+ await upsertProduct(product);
536
+ console.log(chalk6.green(`Pushed product [${product.id}]`));
537
+ }
538
+ for (let featureId of featuresToDelete) {
539
+ let shouldDelete = yes || await confirm({
540
+ message: `Delete feature [${featureId}]?`
541
+ });
542
+ if (shouldDelete) {
543
+ await deleteFeature(featureId);
544
+ console.log(chalk6.green(`Feature [${featureId}] deleted successfully!`));
545
+ }
546
+ }
547
+ const env = prod ? "prod" : "sandbox";
548
+ console.log(
549
+ chalk6.magentaBright(`Success! Changes have been pushed to ${env}.`)
550
+ );
551
+ if (prod) {
552
+ console.log(
553
+ chalk6.magentaBright(
554
+ `You can view the products at ${FRONTEND_URL}/products`
555
+ )
556
+ );
557
+ } else {
558
+ console.log(
559
+ chalk6.magentaBright(
560
+ `You can view the products at ${FRONTEND_URL}/sandbox/products`
561
+ )
562
+ );
563
+ }
564
+ }
565
+ var VERSION = "1.0.0b";
566
+ program.command("push").description("Push changes to Autumn").option("-p, --prod", "Push to production").option("-y, --yes", "Confirm all deletions").action(async (options) => {
567
+ const config = await loadAutumnConfigFile();
568
+ await Push({ config, yes: options.yes, prod: options.prod });
569
+ });
570
+ program.command("pull").description("Pull changes from Autumn").option("-p, --prod", "Pull from production").action(async () => {
571
+ const config = await loadAutumnConfigFile();
572
+ await Pull({ config });
573
+ });
574
+ program.command("init").description("Initialize an Autumn project.").action(async () => {
575
+ writeConfig(DEFAULT_CONFIG);
576
+ const config = await loadAutumnConfigFile();
577
+ await Init({ config });
578
+ });
579
+ program.command("login").description("Authenticate with Autumn").option("-p, --prod", "Authenticate with production").action(async () => {
580
+ await AuthCommand();
581
+ });
582
+ program.command("dashboard").description("Open the Autumn dashboard in your browser").action(() => {
583
+ open(`${FRONTEND_URL}`);
30
584
  });
31
- program
32
- .command('init')
33
- .description('Initialize an Autumn project.')
34
- .action(async () => {
35
- writeConfig(DEFAULT_CONFIG); // just write an empty config to make the config file.
36
- const config = await loadAutumnConfigFile();
37
- await Init({ config });
38
- });
39
- program
40
- .command('login')
41
- .description('Authenticate with Autumn')
42
- .option('-p, --prod', 'Authenticate with production')
43
- .action(async () => {
44
- await AuthCommand();
45
- });
46
- program
47
- .command('dashboard')
48
- .description('Open the Autumn dashboard in your browser')
49
- .action(() => {
50
- open(`${FRONTEND_URL}`);
51
- });
52
- program
53
- .command('version')
54
- .description('Show the version of Autumn')
55
- .action(() => {
56
- console.log(chalk.green(`Autumn v${VERSION}`));
585
+ program.command("version").description("Show the version of Autumn").action(() => {
586
+ console.log(chalk6.green(`Autumn v${VERSION}`));
57
587
  });
58
588
  program.parse();
package/dist/index.cjs ADDED
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ // source/compose/builders/builderFunctions.ts
4
+ var product = (p) => p;
5
+ var feature = (f) => f;
6
+ var featureItem = ({
7
+ feature_id,
8
+ included_usage,
9
+ interval
10
+ }) => {
11
+ return {
12
+ included_usage,
13
+ feature_id,
14
+ interval
15
+ };
16
+ };
17
+ var priceItem = ({
18
+ price,
19
+ interval
20
+ }) => {
21
+ return {
22
+ price,
23
+ interval
24
+ };
25
+ };
26
+ var pricedFeatureItem = ({
27
+ feature_id,
28
+ price,
29
+ interval,
30
+ included_usage = void 0,
31
+ billing_units = 1,
32
+ usage_model = "pay_per_use"
33
+ }) => {
34
+ return {
35
+ price,
36
+ interval,
37
+ billing_units,
38
+ feature_id,
39
+ usage_model,
40
+ included_usage
41
+ };
42
+ };
43
+
44
+ exports.feature = feature;
45
+ exports.featureItem = featureItem;
46
+ exports.priceItem = priceItem;
47
+ exports.pricedFeatureItem = pricedFeatureItem;
48
+ exports.product = product;