atmn 0.0.4 → 0.0.5

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