atmn 0.0.13 → 0.0.16

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.
@@ -0,0 +1,50 @@
1
+ // source/compose/builders/builderFunctions.ts
2
+ var product = (p) => p;
3
+ var feature = (f) => f;
4
+ var featureItem = ({
5
+ feature_id,
6
+ included_usage,
7
+ interval,
8
+ reset_usage_when_enabled,
9
+ entity_feature_id
10
+ }) => {
11
+ return {
12
+ included_usage,
13
+ feature_id,
14
+ interval,
15
+ reset_usage_when_enabled,
16
+ entity_feature_id
17
+ };
18
+ };
19
+ var pricedFeatureItem = ({
20
+ feature_id,
21
+ price,
22
+ interval,
23
+ included_usage = void 0,
24
+ billing_units = 1,
25
+ usage_model = "pay_per_use",
26
+ reset_usage_when_enabled,
27
+ entity_feature_id
28
+ }) => {
29
+ return {
30
+ price,
31
+ interval,
32
+ billing_units,
33
+ feature_id,
34
+ usage_model,
35
+ included_usage,
36
+ reset_usage_when_enabled,
37
+ entity_feature_id
38
+ };
39
+ };
40
+ var priceItem = ({
41
+ price,
42
+ interval
43
+ }) => {
44
+ return {
45
+ price,
46
+ interval
47
+ };
48
+ };
49
+
50
+ export { feature, featureItem, priceItem, pricedFeatureItem, product };
package/dist/cli.cjs CHANGED
@@ -2,29 +2,29 @@
2
2
  'use strict';
3
3
 
4
4
  var commander = require('commander');
5
- var chalk6 = require('chalk');
5
+ var chalk7 = require('chalk');
6
6
  var axios = require('axios');
7
7
  var fs = require('fs');
8
8
  var prompts = require('@inquirer/prompts');
9
9
  var dotenv = require('dotenv');
10
+ var yoctoSpinner = require('yocto-spinner');
10
11
  var path = require('path');
11
12
  var createJiti = require('jiti');
12
13
  var url = require('url');
13
14
  var child_process = require('child_process');
14
15
  var zod = require('zod');
15
16
  var open = require('open');
16
- var yoctoSpinner = require('yocto-spinner');
17
17
 
18
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
19
19
 
20
- var chalk6__default = /*#__PURE__*/_interopDefault(chalk6);
20
+ var chalk7__default = /*#__PURE__*/_interopDefault(chalk7);
21
21
  var axios__default = /*#__PURE__*/_interopDefault(axios);
22
22
  var fs__default = /*#__PURE__*/_interopDefault(fs);
23
23
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
24
+ var yoctoSpinner__default = /*#__PURE__*/_interopDefault(yoctoSpinner);
24
25
  var path__default = /*#__PURE__*/_interopDefault(path);
25
26
  var createJiti__default = /*#__PURE__*/_interopDefault(createJiti);
26
27
  var open__default = /*#__PURE__*/_interopDefault(open);
27
- var yoctoSpinner__default = /*#__PURE__*/_interopDefault(yoctoSpinner);
28
28
 
29
29
  // ../node_modules/.pnpm/tsup@8.5.0_@swc+core@1.13.2_jiti@2.4.2_postcss@8.5.6_tsx@4.20.3_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/cjs_shims.js
30
30
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
@@ -115,18 +115,18 @@ AUTUMN_SECRET_KEY=${sandboxKey}
115
115
  if (fs__default.default.existsSync(envPath)) {
116
116
  upsertEnvVar(envPath, "AUTUMN_PROD_SECRET_KEY", prodKey);
117
117
  upsertEnvVar(envPath, "AUTUMN_SECRET_KEY", sandboxKey);
118
- console.log(chalk6__default.default.green(".env file found. Updated keys."));
118
+ console.log(chalk7__default.default.green(".env file found. Updated keys."));
119
119
  } else if (fs__default.default.existsSync(envLocalPath)) {
120
120
  fs__default.default.writeFileSync(envPath, envVars);
121
121
  console.log(
122
- chalk6__default.default.green(
122
+ chalk7__default.default.green(
123
123
  ".env.local found but .env not found. Created new .env file and wrote keys."
124
124
  )
125
125
  );
126
126
  } else {
127
127
  fs__default.default.writeFileSync(envPath, envVars);
128
128
  console.log(
129
- chalk6__default.default.green(
129
+ chalk7__default.default.green(
130
130
  "No .env or .env.local file found. Created new .env file and wrote keys."
131
131
  )
132
132
  );
@@ -151,6 +151,13 @@ function readFromEnv() {
151
151
  }
152
152
  return void 0;
153
153
  }
154
+ function initSpinner(message) {
155
+ const spinner2 = yoctoSpinner__default.default({
156
+ text: message
157
+ });
158
+ spinner2.start();
159
+ return spinner2;
160
+ }
154
161
 
155
162
  // source/core/api.ts
156
163
  var INTERNAL_BASE = BACKEND_URL;
@@ -182,8 +189,8 @@ async function request({
182
189
  throw error;
183
190
  }
184
191
  console.error(
185
- chalk6__default.default.red("Error occured when making API request:"),
186
- chalk6__default.default.red(error.response.data.message || error.response.data.error)
192
+ chalk7__default.default.red("\nError occured when making API request:"),
193
+ chalk7__default.default.red(error.response.data.message || error.response.data.error)
187
194
  );
188
195
  process.exit(1);
189
196
  }
@@ -317,7 +324,7 @@ var getResetUsageStr = ({
317
324
  return "";
318
325
  };
319
326
  var getIntervalStr = ({ item }) => {
320
- if (item.interval == null) return `${getItemFieldPrefix()}interval: null,`;
327
+ if (item.interval == null) return ``;
321
328
  return `${getItemFieldPrefix()}interval: '${item.interval}',`;
322
329
  };
323
330
  var getEntityFeatureIdStr = ({ item }) => {
@@ -453,7 +460,7 @@ async function installAtmn() {
453
460
  });
454
461
  if (!shouldInstall) {
455
462
  console.log(
456
- chalk6__default.default.yellow(
463
+ chalk7__default.default.yellow(
457
464
  "Skipping installation. You can install atmn manually with your preferred package manager."
458
465
  )
459
466
  );
@@ -469,13 +476,13 @@ async function installAtmn() {
469
476
  default: "npm"
470
477
  });
471
478
  try {
472
- console.log(chalk6__default.default.blue(`Installing atmn with ${packageManager}...`));
479
+ console.log(chalk7__default.default.blue(`Installing atmn with ${packageManager}...`));
473
480
  const installCommand = packageManager === "npm" ? "npm install atmn" : packageManager === "pnpm" ? "pnpm add atmn" : "bun add atmn";
474
481
  child_process.execSync(installCommand, { stdio: "inherit" });
475
- console.log(chalk6__default.default.green("atmn installed successfully!"));
482
+ console.log(chalk7__default.default.green("atmn installed successfully!"));
476
483
  return true;
477
484
  } catch (error) {
478
- console.error(chalk6__default.default.red("Failed to install atmn:"), error);
485
+ console.error(chalk7__default.default.red("Failed to install atmn:"), error);
479
486
  return false;
480
487
  }
481
488
  }
@@ -529,9 +536,16 @@ async function loadAutumnConfigFile() {
529
536
  }
530
537
  }
531
538
  }
539
+ const secretKey = readFromEnv();
540
+ if (secretKey?.includes("live")) {
541
+ console.log(chalk7__default.default.magentaBright("Running in production environment..."));
542
+ } else {
543
+ console.log(chalk7__default.default.yellow("Running in sandbox environment..."));
544
+ }
532
545
  return {
533
546
  products,
534
- features
547
+ features,
548
+ env: secretKey?.includes("live") ? "prod" : "sandbox"
535
549
  };
536
550
  }
537
551
  function writeConfig(config) {
@@ -541,7 +555,7 @@ function writeConfig(config) {
541
555
 
542
556
  // source/commands/pull.ts
543
557
  async function Pull({ config }) {
544
- console.log(chalk6__default.default.green("Pulling products and features from Autumn..."));
558
+ console.log(chalk7__default.default.green("Pulling products and features from Autumn..."));
545
559
  const products = await getAllProducts();
546
560
  const features = await getFeatures();
547
561
  const productSnippets = products.map(
@@ -558,7 +572,7 @@ ${importBuilder()}
558
572
  // Products${productSnippets.join("\n")}
559
573
  `;
560
574
  writeConfig(autumnConfig);
561
- console.log(chalk6__default.default.green("Success! Config has been updated."));
575
+ console.log(chalk7__default.default.green("Success! Config has been updated."));
562
576
  }
563
577
 
564
578
  // source/core/auth.ts
@@ -574,14 +588,14 @@ async function getOTP(otp) {
574
588
  var passwordTheme = {
575
589
  style: {
576
590
  answer: (text) => {
577
- return chalk6__default.default.magenta("*".repeat(text.length));
591
+ return chalk7__default.default.magenta("*".repeat(text.length));
578
592
  }
579
593
  }
580
594
  };
581
595
  var inputTheme = {
582
596
  style: {
583
597
  answer: (text) => {
584
- return chalk6__default.default.magenta(text);
598
+ return chalk7__default.default.magenta(text);
585
599
  }
586
600
  }
587
601
  };
@@ -619,7 +633,7 @@ async function AuthCommand() {
619
633
  );
620
634
  } else {
621
635
  console.log(
622
- chalk6__default.default.yellow(
636
+ chalk7__default.default.yellow(
623
637
  "Okay, no worries. Go to the Autumn dashboard when you're ready!"
624
638
  )
625
639
  );
@@ -627,7 +641,7 @@ async function AuthCommand() {
627
641
  }
628
642
  storeToEnv(keyInfo.prodKey, keyInfo.sandboxKey);
629
643
  console.log(
630
- chalk6__default.default.green(
644
+ chalk7__default.default.green(
631
645
  "Success! Keys have been stored locally. You may now use the CLI."
632
646
  )
633
647
  );
@@ -637,31 +651,33 @@ async function AuthCommand() {
637
651
  async function Init({ config }) {
638
652
  let apiKey = readFromEnv();
639
653
  if (apiKey) {
640
- console.log(chalk6__default.default.green("API key found. Pulling latest config..."));
654
+ console.log(chalk7__default.default.green("API key found. Pulling latest config..."));
641
655
  await Pull({ config });
642
656
  console.log(
643
- chalk6__default.default.green("Project initialized and config pulled successfully!")
657
+ chalk7__default.default.green("Project initialized and config pulled successfully!")
644
658
  );
645
659
  return;
646
660
  }
647
- console.log(chalk6__default.default.yellow("No API key found. Running authentication..."));
661
+ console.log(chalk7__default.default.yellow("No API key found. Running authentication..."));
648
662
  await AuthCommand();
649
663
  apiKey = readFromEnv();
650
664
  if (apiKey) {
651
665
  await Pull({ config });
652
666
  console.log(
653
- chalk6__default.default.green(
667
+ chalk7__default.default.green(
654
668
  "Project initialized! You are now authenticated and config has been pulled."
655
669
  )
656
670
  );
657
671
  } else {
658
672
  console.log(
659
- chalk6__default.default.red(
673
+ chalk7__default.default.red(
660
674
  "Authentication did not yield an API key. Please check your setup."
661
675
  )
662
676
  );
663
677
  }
664
678
  }
679
+
680
+ // source/core/push.ts
665
681
  async function checkForDeletables(currentFeatures, currentProducts) {
666
682
  const features = await getFeatures();
667
683
  const featureIds = features.map((feature) => feature.id);
@@ -677,12 +693,17 @@ async function checkForDeletables(currentFeatures, currentProducts) {
677
693
  const productsToDelete = productIds.filter(
678
694
  (productId) => !currentProductIds.includes(productId)
679
695
  );
680
- return { featuresToDelete, productsToDelete };
696
+ return {
697
+ curFeatures: features,
698
+ curProducts: products,
699
+ featuresToDelete,
700
+ productsToDelete
701
+ };
681
702
  }
682
703
  var isDuplicate = (error) => {
683
704
  return error.response && error.response.data && (error.response.data.code === "duplicate_feature_id" || error.response.data.code === "product_already_exists");
684
705
  };
685
- async function upsertFeature(feature) {
706
+ async function upsertFeature(feature, s) {
686
707
  try {
687
708
  const response = await externalRequest({
688
709
  method: "POST",
@@ -705,56 +726,67 @@ async function upsertFeature(feature) {
705
726
  Failed to push feature ${feature.id}: ${error.response?.data?.message || "Unknown error"}`
706
727
  );
707
728
  process.exit(1);
729
+ } finally {
730
+ s.text = `Pushed feature [${feature.id}]`;
731
+ }
732
+ }
733
+ async function checkProductForConfirmation({
734
+ curProducts,
735
+ product
736
+ }) {
737
+ let curProduct = curProducts.find((p) => p.id === product.id);
738
+ if (!curProduct) {
739
+ return {
740
+ id: product.id,
741
+ will_version: false
742
+ };
708
743
  }
744
+ const res1 = await externalRequest({
745
+ method: "GET",
746
+ path: `/products/${product.id}/has_customers`,
747
+ data: product
748
+ });
749
+ return {
750
+ id: product.id,
751
+ will_version: res1.will_version
752
+ };
709
753
  }
710
754
  async function upsertProduct({
755
+ curProducts,
711
756
  product,
712
- spinner: spinner2
757
+ spinner: spinner2,
758
+ shouldUpdate = true
713
759
  }) {
714
- try {
715
- const response = await externalRequest({
760
+ if (!shouldUpdate) {
761
+ spinner2.text = `Skipping update to product ${product.id}`;
762
+ return {
763
+ id: product.id,
764
+ action: "skipped"
765
+ };
766
+ }
767
+ let curProduct = curProducts.find((p) => p.id === product.id);
768
+ if (!curProduct) {
769
+ await externalRequest({
716
770
  method: "POST",
717
771
  path: `/products`,
718
- data: product,
719
- throwOnError: true
772
+ data: product
720
773
  });
721
- spinner2.success(`Pushed product [${product.id}]`);
722
- } catch (error) {
723
- if (isDuplicate(error)) {
724
- const res1 = await externalRequest({
725
- method: "GET",
726
- path: `/products/${product.id}/has_customers`,
727
- data: product
728
- });
729
- const { current_version, will_version } = res1;
730
- let shouldUpdate = true;
731
- if (will_version) {
732
- spinner2.stop();
733
- process.stdout.write("\r\x1B[K");
734
- shouldUpdate = await prompts.confirm({
735
- message: `Product ${product.id} has customers on it and updating it will create a new version.
736
- Are you sure you'd like to continue? `
737
- });
738
- }
739
- if (shouldUpdate) {
740
- spinner2.start();
741
- const response = await externalRequest({
742
- method: "POST",
743
- path: `/products/${product.id}`,
744
- data: product
745
- });
746
- spinner2.success(`Pushed product [${product.id}]`);
747
- return response;
748
- } else {
749
- spinner2.info(`Skipping update to product ${product.id}`);
750
- return;
751
- }
752
- }
753
- console.error(
754
- `
755
- Failed to push product ${product.id}: ${error.response?.data?.message || "Unknown error"}`
756
- );
757
- process.exit(1);
774
+ spinner2.text = `Created product [${product.id}]`;
775
+ return {
776
+ id: product.id,
777
+ action: "create"
778
+ };
779
+ } else {
780
+ await externalRequest({
781
+ method: "POST",
782
+ path: `/products/${product.id}`,
783
+ data: product
784
+ });
785
+ spinner2.text = `Updated product [${product.id}]`;
786
+ return {
787
+ id: product.id,
788
+ action: "updated"
789
+ };
758
790
  }
759
791
  }
760
792
 
@@ -771,53 +803,104 @@ async function Push({
771
803
  yes,
772
804
  prod
773
805
  }) {
774
- let { features, products } = config;
775
- let { featuresToDelete, productsToDelete } = await checkForDeletables(
776
- features,
777
- products
778
- );
806
+ let { features, products, env } = config;
807
+ if (env === "prod") {
808
+ const shouldProceed = await prompts.confirm({
809
+ message: "You are about to push products to your prod environment. Are you sure you want to proceed?",
810
+ default: false
811
+ });
812
+ if (!shouldProceed) {
813
+ console.log(chalk7__default.default.yellow("Aborting..."));
814
+ process.exit(1);
815
+ }
816
+ }
817
+ let { curFeatures, curProducts, featuresToDelete, productsToDelete } = await checkForDeletables(features, products);
779
818
  for (let productId of productsToDelete) {
780
819
  let shouldDelete = yes || await prompts.confirm({
781
820
  message: `Delete product [${productId}]?`
782
821
  });
783
822
  if (shouldDelete) {
784
- const s = spinner(`Deleting product [${productId}]`);
823
+ const s3 = spinner(`Deleting product [${productId}]`);
785
824
  await deleteProduct(productId);
786
- s.success(`Product [${productId}] deleted successfully!`);
825
+ s3.success(`Product [${productId}] deleted successfully!`);
787
826
  }
788
827
  }
828
+ const batchFeatures = [];
829
+ const s = initSpinner(`Pushing features`);
789
830
  for (let feature of features) {
790
- const s = spinner(`Pushing feature [${feature.id}]`);
791
- await upsertFeature(feature);
792
- s.success(`Pushed feature [${feature.id}]`);
831
+ batchFeatures.push(upsertFeature(feature, s));
793
832
  }
833
+ await Promise.all(batchFeatures);
834
+ s.success(`Features pushed successfully!`);
835
+ console.log(chalk7__default.default.dim("\nFeatures pushed:"));
836
+ features.forEach((feature) => {
837
+ console.log(chalk7__default.default.cyan(` \u2022 ${feature.id}`));
838
+ });
839
+ console.log();
840
+ const productDecisions = /* @__PURE__ */ new Map();
841
+ const batchCheckProducts = [];
794
842
  for (let product of products) {
795
- const s = spinner(`Pushing product [${product.id}]`);
796
- await upsertProduct({ product, spinner: s });
843
+ batchCheckProducts.push(
844
+ checkProductForConfirmation({
845
+ curProducts,
846
+ product
847
+ })
848
+ );
849
+ }
850
+ const checkProductResults = await Promise.all(batchCheckProducts);
851
+ for (let result of checkProductResults) {
852
+ if (result.will_version) {
853
+ const shouldUpdate = await prompts.confirm({
854
+ message: `Product ${result.id} has customers on it and updating it will create a new version.
855
+ Are you sure you'd like to continue? `
856
+ });
857
+ productDecisions.set(result.id, shouldUpdate);
858
+ } else {
859
+ productDecisions.set(result.id, true);
860
+ }
797
861
  }
862
+ const s2 = initSpinner(`Pushing products`);
863
+ const batchProducts = [];
864
+ for (let product of products) {
865
+ const shouldUpdate = productDecisions.get(product.id);
866
+ batchProducts.push(
867
+ upsertProduct({ curProducts, product, spinner: s2, shouldUpdate })
868
+ );
869
+ }
870
+ const prodResults = await Promise.all(batchProducts);
871
+ s2.success(`Products pushed successfully!`);
872
+ console.log(chalk7__default.default.dim("\nProducts pushed:"));
873
+ prodResults.forEach((result) => {
874
+ let action = result.action;
875
+ console.log(
876
+ chalk7__default.default.cyan(
877
+ ` \u2022 ${result.id} ${action == "skipped" ? `(${action})` : ""}`
878
+ )
879
+ );
880
+ });
881
+ console.log();
798
882
  for (let featureId of featuresToDelete) {
799
883
  let shouldDelete = yes || await prompts.confirm({
800
884
  message: `Delete feature [${featureId}]?`
801
885
  });
802
886
  if (shouldDelete) {
803
- const s = spinner(`Deleting feature [${featureId}]`);
887
+ const s3 = spinner(`Deleting feature [${featureId}]`);
804
888
  await deleteFeature(featureId);
805
- s.success(`Feature [${featureId}] deleted successfully!`);
889
+ s3.success(`Feature [${featureId}] deleted successfully!`);
806
890
  }
807
891
  }
808
- const env = prod ? "prod" : "sandbox";
809
892
  console.log(
810
- chalk6__default.default.magentaBright(`Success! Changes have been pushed to ${env}.`)
893
+ chalk7__default.default.magentaBright(`Success! Changes have been pushed to ${env}.`)
811
894
  );
812
895
  if (prod) {
813
896
  console.log(
814
- chalk6__default.default.magentaBright(
897
+ chalk7__default.default.magentaBright(
815
898
  `You can view the products at ${FRONTEND_URL}/products`
816
899
  )
817
900
  );
818
901
  } else {
819
902
  console.log(
820
- chalk6__default.default.magentaBright(
903
+ chalk7__default.default.magentaBright(
821
904
  `You can view the products at ${FRONTEND_URL}/sandbox/products`
822
905
  )
823
906
  );
@@ -844,6 +927,6 @@ commander.program.command("dashboard").description("Open the Autumn dashboard in
844
927
  open__default.default(`${FRONTEND_URL}`);
845
928
  });
846
929
  commander.program.command("version").description("Show the version of Autumn").action(() => {
847
- console.log(chalk6__default.default.green(`Autumn v${VERSION}`));
930
+ console.log(chalk7__default.default.green(`Autumn v${VERSION}`));
848
931
  });
849
932
  commander.program.parse();
package/dist/cli.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
- import chalk6 from 'chalk';
3
+ import chalk7 from 'chalk';
4
4
  import axios from 'axios';
5
5
  import fs from 'fs';
6
6
  import { confirm, input, password, select } from '@inquirer/prompts';
7
7
  import dotenv from 'dotenv';
8
+ import yoctoSpinner from 'yocto-spinner';
8
9
  import path, { resolve } from 'path';
9
10
  import createJiti from 'jiti';
10
11
  import { pathToFileURL } from 'url';
11
12
  import { execSync } from 'child_process';
12
13
  import { z } from 'zod';
13
14
  import open from 'open';
14
- import yoctoSpinner from 'yocto-spinner';
15
15
 
16
16
  // source/constants.ts
17
17
  var FRONTEND_URL = "http://app.useautumn.com";
@@ -98,18 +98,18 @@ AUTUMN_SECRET_KEY=${sandboxKey}
98
98
  if (fs.existsSync(envPath)) {
99
99
  upsertEnvVar(envPath, "AUTUMN_PROD_SECRET_KEY", prodKey);
100
100
  upsertEnvVar(envPath, "AUTUMN_SECRET_KEY", sandboxKey);
101
- console.log(chalk6.green(".env file found. Updated keys."));
101
+ console.log(chalk7.green(".env file found. Updated keys."));
102
102
  } else if (fs.existsSync(envLocalPath)) {
103
103
  fs.writeFileSync(envPath, envVars);
104
104
  console.log(
105
- chalk6.green(
105
+ chalk7.green(
106
106
  ".env.local found but .env not found. Created new .env file and wrote keys."
107
107
  )
108
108
  );
109
109
  } else {
110
110
  fs.writeFileSync(envPath, envVars);
111
111
  console.log(
112
- chalk6.green(
112
+ chalk7.green(
113
113
  "No .env or .env.local file found. Created new .env file and wrote keys."
114
114
  )
115
115
  );
@@ -134,6 +134,13 @@ function readFromEnv() {
134
134
  }
135
135
  return void 0;
136
136
  }
137
+ function initSpinner(message) {
138
+ const spinner2 = yoctoSpinner({
139
+ text: message
140
+ });
141
+ spinner2.start();
142
+ return spinner2;
143
+ }
137
144
 
138
145
  // source/core/api.ts
139
146
  var INTERNAL_BASE = BACKEND_URL;
@@ -165,8 +172,8 @@ async function request({
165
172
  throw error;
166
173
  }
167
174
  console.error(
168
- chalk6.red("Error occured when making API request:"),
169
- chalk6.red(error.response.data.message || error.response.data.error)
175
+ chalk7.red("\nError occured when making API request:"),
176
+ chalk7.red(error.response.data.message || error.response.data.error)
170
177
  );
171
178
  process.exit(1);
172
179
  }
@@ -300,7 +307,7 @@ var getResetUsageStr = ({
300
307
  return "";
301
308
  };
302
309
  var getIntervalStr = ({ item }) => {
303
- if (item.interval == null) return `${getItemFieldPrefix()}interval: null,`;
310
+ if (item.interval == null) return ``;
304
311
  return `${getItemFieldPrefix()}interval: '${item.interval}',`;
305
312
  };
306
313
  var getEntityFeatureIdStr = ({ item }) => {
@@ -436,7 +443,7 @@ async function installAtmn() {
436
443
  });
437
444
  if (!shouldInstall) {
438
445
  console.log(
439
- chalk6.yellow(
446
+ chalk7.yellow(
440
447
  "Skipping installation. You can install atmn manually with your preferred package manager."
441
448
  )
442
449
  );
@@ -452,13 +459,13 @@ async function installAtmn() {
452
459
  default: "npm"
453
460
  });
454
461
  try {
455
- console.log(chalk6.blue(`Installing atmn with ${packageManager}...`));
462
+ console.log(chalk7.blue(`Installing atmn with ${packageManager}...`));
456
463
  const installCommand = packageManager === "npm" ? "npm install atmn" : packageManager === "pnpm" ? "pnpm add atmn" : "bun add atmn";
457
464
  execSync(installCommand, { stdio: "inherit" });
458
- console.log(chalk6.green("atmn installed successfully!"));
465
+ console.log(chalk7.green("atmn installed successfully!"));
459
466
  return true;
460
467
  } catch (error) {
461
- console.error(chalk6.red("Failed to install atmn:"), error);
468
+ console.error(chalk7.red("Failed to install atmn:"), error);
462
469
  return false;
463
470
  }
464
471
  }
@@ -512,9 +519,16 @@ async function loadAutumnConfigFile() {
512
519
  }
513
520
  }
514
521
  }
522
+ const secretKey = readFromEnv();
523
+ if (secretKey?.includes("live")) {
524
+ console.log(chalk7.magentaBright("Running in production environment..."));
525
+ } else {
526
+ console.log(chalk7.yellow("Running in sandbox environment..."));
527
+ }
515
528
  return {
516
529
  products,
517
- features
530
+ features,
531
+ env: secretKey?.includes("live") ? "prod" : "sandbox"
518
532
  };
519
533
  }
520
534
  function writeConfig(config) {
@@ -524,7 +538,7 @@ function writeConfig(config) {
524
538
 
525
539
  // source/commands/pull.ts
526
540
  async function Pull({ config }) {
527
- console.log(chalk6.green("Pulling products and features from Autumn..."));
541
+ console.log(chalk7.green("Pulling products and features from Autumn..."));
528
542
  const products = await getAllProducts();
529
543
  const features = await getFeatures();
530
544
  const productSnippets = products.map(
@@ -541,7 +555,7 @@ ${importBuilder()}
541
555
  // Products${productSnippets.join("\n")}
542
556
  `;
543
557
  writeConfig(autumnConfig);
544
- console.log(chalk6.green("Success! Config has been updated."));
558
+ console.log(chalk7.green("Success! Config has been updated."));
545
559
  }
546
560
 
547
561
  // source/core/auth.ts
@@ -557,14 +571,14 @@ async function getOTP(otp) {
557
571
  var passwordTheme = {
558
572
  style: {
559
573
  answer: (text) => {
560
- return chalk6.magenta("*".repeat(text.length));
574
+ return chalk7.magenta("*".repeat(text.length));
561
575
  }
562
576
  }
563
577
  };
564
578
  var inputTheme = {
565
579
  style: {
566
580
  answer: (text) => {
567
- return chalk6.magenta(text);
581
+ return chalk7.magenta(text);
568
582
  }
569
583
  }
570
584
  };
@@ -602,7 +616,7 @@ async function AuthCommand() {
602
616
  );
603
617
  } else {
604
618
  console.log(
605
- chalk6.yellow(
619
+ chalk7.yellow(
606
620
  "Okay, no worries. Go to the Autumn dashboard when you're ready!"
607
621
  )
608
622
  );
@@ -610,7 +624,7 @@ async function AuthCommand() {
610
624
  }
611
625
  storeToEnv(keyInfo.prodKey, keyInfo.sandboxKey);
612
626
  console.log(
613
- chalk6.green(
627
+ chalk7.green(
614
628
  "Success! Keys have been stored locally. You may now use the CLI."
615
629
  )
616
630
  );
@@ -620,31 +634,33 @@ async function AuthCommand() {
620
634
  async function Init({ config }) {
621
635
  let apiKey = readFromEnv();
622
636
  if (apiKey) {
623
- console.log(chalk6.green("API key found. Pulling latest config..."));
637
+ console.log(chalk7.green("API key found. Pulling latest config..."));
624
638
  await Pull({ config });
625
639
  console.log(
626
- chalk6.green("Project initialized and config pulled successfully!")
640
+ chalk7.green("Project initialized and config pulled successfully!")
627
641
  );
628
642
  return;
629
643
  }
630
- console.log(chalk6.yellow("No API key found. Running authentication..."));
644
+ console.log(chalk7.yellow("No API key found. Running authentication..."));
631
645
  await AuthCommand();
632
646
  apiKey = readFromEnv();
633
647
  if (apiKey) {
634
648
  await Pull({ config });
635
649
  console.log(
636
- chalk6.green(
650
+ chalk7.green(
637
651
  "Project initialized! You are now authenticated and config has been pulled."
638
652
  )
639
653
  );
640
654
  } else {
641
655
  console.log(
642
- chalk6.red(
656
+ chalk7.red(
643
657
  "Authentication did not yield an API key. Please check your setup."
644
658
  )
645
659
  );
646
660
  }
647
661
  }
662
+
663
+ // source/core/push.ts
648
664
  async function checkForDeletables(currentFeatures, currentProducts) {
649
665
  const features = await getFeatures();
650
666
  const featureIds = features.map((feature) => feature.id);
@@ -660,12 +676,17 @@ async function checkForDeletables(currentFeatures, currentProducts) {
660
676
  const productsToDelete = productIds.filter(
661
677
  (productId) => !currentProductIds.includes(productId)
662
678
  );
663
- return { featuresToDelete, productsToDelete };
679
+ return {
680
+ curFeatures: features,
681
+ curProducts: products,
682
+ featuresToDelete,
683
+ productsToDelete
684
+ };
664
685
  }
665
686
  var isDuplicate = (error) => {
666
687
  return error.response && error.response.data && (error.response.data.code === "duplicate_feature_id" || error.response.data.code === "product_already_exists");
667
688
  };
668
- async function upsertFeature(feature) {
689
+ async function upsertFeature(feature, s) {
669
690
  try {
670
691
  const response = await externalRequest({
671
692
  method: "POST",
@@ -688,56 +709,67 @@ async function upsertFeature(feature) {
688
709
  Failed to push feature ${feature.id}: ${error.response?.data?.message || "Unknown error"}`
689
710
  );
690
711
  process.exit(1);
712
+ } finally {
713
+ s.text = `Pushed feature [${feature.id}]`;
691
714
  }
692
715
  }
716
+ async function checkProductForConfirmation({
717
+ curProducts,
718
+ product
719
+ }) {
720
+ let curProduct = curProducts.find((p) => p.id === product.id);
721
+ if (!curProduct) {
722
+ return {
723
+ id: product.id,
724
+ will_version: false
725
+ };
726
+ }
727
+ const res1 = await externalRequest({
728
+ method: "GET",
729
+ path: `/products/${product.id}/has_customers`,
730
+ data: product
731
+ });
732
+ return {
733
+ id: product.id,
734
+ will_version: res1.will_version
735
+ };
736
+ }
693
737
  async function upsertProduct({
738
+ curProducts,
694
739
  product,
695
- spinner: spinner2
740
+ spinner: spinner2,
741
+ shouldUpdate = true
696
742
  }) {
697
- try {
698
- const response = await externalRequest({
743
+ if (!shouldUpdate) {
744
+ spinner2.text = `Skipping update to product ${product.id}`;
745
+ return {
746
+ id: product.id,
747
+ action: "skipped"
748
+ };
749
+ }
750
+ let curProduct = curProducts.find((p) => p.id === product.id);
751
+ if (!curProduct) {
752
+ await externalRequest({
699
753
  method: "POST",
700
754
  path: `/products`,
701
- data: product,
702
- throwOnError: true
755
+ data: product
703
756
  });
704
- spinner2.success(`Pushed product [${product.id}]`);
705
- } catch (error) {
706
- if (isDuplicate(error)) {
707
- const res1 = await externalRequest({
708
- method: "GET",
709
- path: `/products/${product.id}/has_customers`,
710
- data: product
711
- });
712
- const { current_version, will_version } = res1;
713
- let shouldUpdate = true;
714
- if (will_version) {
715
- spinner2.stop();
716
- process.stdout.write("\r\x1B[K");
717
- shouldUpdate = await confirm({
718
- message: `Product ${product.id} has customers on it and updating it will create a new version.
719
- Are you sure you'd like to continue? `
720
- });
721
- }
722
- if (shouldUpdate) {
723
- spinner2.start();
724
- const response = await externalRequest({
725
- method: "POST",
726
- path: `/products/${product.id}`,
727
- data: product
728
- });
729
- spinner2.success(`Pushed product [${product.id}]`);
730
- return response;
731
- } else {
732
- spinner2.info(`Skipping update to product ${product.id}`);
733
- return;
734
- }
735
- }
736
- console.error(
737
- `
738
- Failed to push product ${product.id}: ${error.response?.data?.message || "Unknown error"}`
739
- );
740
- process.exit(1);
757
+ spinner2.text = `Created product [${product.id}]`;
758
+ return {
759
+ id: product.id,
760
+ action: "create"
761
+ };
762
+ } else {
763
+ await externalRequest({
764
+ method: "POST",
765
+ path: `/products/${product.id}`,
766
+ data: product
767
+ });
768
+ spinner2.text = `Updated product [${product.id}]`;
769
+ return {
770
+ id: product.id,
771
+ action: "updated"
772
+ };
741
773
  }
742
774
  }
743
775
 
@@ -754,53 +786,104 @@ async function Push({
754
786
  yes,
755
787
  prod
756
788
  }) {
757
- let { features, products } = config;
758
- let { featuresToDelete, productsToDelete } = await checkForDeletables(
759
- features,
760
- products
761
- );
789
+ let { features, products, env } = config;
790
+ if (env === "prod") {
791
+ const shouldProceed = await confirm({
792
+ message: "You are about to push products to your prod environment. Are you sure you want to proceed?",
793
+ default: false
794
+ });
795
+ if (!shouldProceed) {
796
+ console.log(chalk7.yellow("Aborting..."));
797
+ process.exit(1);
798
+ }
799
+ }
800
+ let { curFeatures, curProducts, featuresToDelete, productsToDelete } = await checkForDeletables(features, products);
762
801
  for (let productId of productsToDelete) {
763
802
  let shouldDelete = yes || await confirm({
764
803
  message: `Delete product [${productId}]?`
765
804
  });
766
805
  if (shouldDelete) {
767
- const s = spinner(`Deleting product [${productId}]`);
806
+ const s3 = spinner(`Deleting product [${productId}]`);
768
807
  await deleteProduct(productId);
769
- s.success(`Product [${productId}] deleted successfully!`);
808
+ s3.success(`Product [${productId}] deleted successfully!`);
770
809
  }
771
810
  }
811
+ const batchFeatures = [];
812
+ const s = initSpinner(`Pushing features`);
772
813
  for (let feature of features) {
773
- const s = spinner(`Pushing feature [${feature.id}]`);
774
- await upsertFeature(feature);
775
- s.success(`Pushed feature [${feature.id}]`);
814
+ batchFeatures.push(upsertFeature(feature, s));
776
815
  }
816
+ await Promise.all(batchFeatures);
817
+ s.success(`Features pushed successfully!`);
818
+ console.log(chalk7.dim("\nFeatures pushed:"));
819
+ features.forEach((feature) => {
820
+ console.log(chalk7.cyan(` \u2022 ${feature.id}`));
821
+ });
822
+ console.log();
823
+ const productDecisions = /* @__PURE__ */ new Map();
824
+ const batchCheckProducts = [];
825
+ for (let product of products) {
826
+ batchCheckProducts.push(
827
+ checkProductForConfirmation({
828
+ curProducts,
829
+ product
830
+ })
831
+ );
832
+ }
833
+ const checkProductResults = await Promise.all(batchCheckProducts);
834
+ for (let result of checkProductResults) {
835
+ if (result.will_version) {
836
+ const shouldUpdate = await confirm({
837
+ message: `Product ${result.id} has customers on it and updating it will create a new version.
838
+ Are you sure you'd like to continue? `
839
+ });
840
+ productDecisions.set(result.id, shouldUpdate);
841
+ } else {
842
+ productDecisions.set(result.id, true);
843
+ }
844
+ }
845
+ const s2 = initSpinner(`Pushing products`);
846
+ const batchProducts = [];
777
847
  for (let product of products) {
778
- const s = spinner(`Pushing product [${product.id}]`);
779
- await upsertProduct({ product, spinner: s });
848
+ const shouldUpdate = productDecisions.get(product.id);
849
+ batchProducts.push(
850
+ upsertProduct({ curProducts, product, spinner: s2, shouldUpdate })
851
+ );
780
852
  }
853
+ const prodResults = await Promise.all(batchProducts);
854
+ s2.success(`Products pushed successfully!`);
855
+ console.log(chalk7.dim("\nProducts pushed:"));
856
+ prodResults.forEach((result) => {
857
+ let action = result.action;
858
+ console.log(
859
+ chalk7.cyan(
860
+ ` \u2022 ${result.id} ${action == "skipped" ? `(${action})` : ""}`
861
+ )
862
+ );
863
+ });
864
+ console.log();
781
865
  for (let featureId of featuresToDelete) {
782
866
  let shouldDelete = yes || await confirm({
783
867
  message: `Delete feature [${featureId}]?`
784
868
  });
785
869
  if (shouldDelete) {
786
- const s = spinner(`Deleting feature [${featureId}]`);
870
+ const s3 = spinner(`Deleting feature [${featureId}]`);
787
871
  await deleteFeature(featureId);
788
- s.success(`Feature [${featureId}] deleted successfully!`);
872
+ s3.success(`Feature [${featureId}] deleted successfully!`);
789
873
  }
790
874
  }
791
- const env = prod ? "prod" : "sandbox";
792
875
  console.log(
793
- chalk6.magentaBright(`Success! Changes have been pushed to ${env}.`)
876
+ chalk7.magentaBright(`Success! Changes have been pushed to ${env}.`)
794
877
  );
795
878
  if (prod) {
796
879
  console.log(
797
- chalk6.magentaBright(
880
+ chalk7.magentaBright(
798
881
  `You can view the products at ${FRONTEND_URL}/products`
799
882
  )
800
883
  );
801
884
  } else {
802
885
  console.log(
803
- chalk6.magentaBright(
886
+ chalk7.magentaBright(
804
887
  `You can view the products at ${FRONTEND_URL}/sandbox/products`
805
888
  )
806
889
  );
@@ -827,6 +910,6 @@ program.command("dashboard").description("Open the Autumn dashboard in your brow
827
910
  open(`${FRONTEND_URL}`);
828
911
  });
829
912
  program.command("version").description("Show the version of Autumn").action(() => {
830
- console.log(chalk6.green(`Autumn v${VERSION}`));
913
+ console.log(chalk7.green(`Autumn v${VERSION}`));
831
914
  });
832
915
  program.parse();
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ var featureItem = ({
7
7
  feature_id,
8
8
  included_usage,
9
9
  interval,
10
- reset_usage_when_enabled = false,
10
+ reset_usage_when_enabled,
11
11
  entity_feature_id
12
12
  }) => {
13
13
  return {
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var featureItem = ({
5
5
  feature_id,
6
6
  included_usage,
7
7
  interval,
8
- reset_usage_when_enabled = false,
8
+ reset_usage_when_enabled,
9
9
  entity_feature_id
10
10
  }) => {
11
11
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atmn",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "license": "MIT",
5
5
  "bin": "dist/cli.js",
6
6
  "main": "dist/index.js",