atmn 0.0.21 → 0.0.23

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.cjs ADDED
@@ -0,0 +1,1455 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var chalk8 = require('chalk');
5
+ var commander = require('commander');
6
+ var open = require('open');
7
+ var prompts = require('@inquirer/prompts');
8
+ var dotenv = require('dotenv');
9
+ var fs = require('fs');
10
+ var yoctoSpinner = require('yocto-spinner');
11
+ var axios = require('axios');
12
+ var prettier = require('prettier');
13
+ var path = require('path');
14
+ var createJiti = require('jiti');
15
+ var url = require('url');
16
+ var child_process = require('child_process');
17
+ var v4 = require('zod/v4');
18
+
19
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
+
21
+ var chalk8__default = /*#__PURE__*/_interopDefault(chalk8);
22
+ var open__default = /*#__PURE__*/_interopDefault(open);
23
+ var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
24
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
25
+ var yoctoSpinner__default = /*#__PURE__*/_interopDefault(yoctoSpinner);
26
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
27
+ var prettier__default = /*#__PURE__*/_interopDefault(prettier);
28
+ var path__default = /*#__PURE__*/_interopDefault(path);
29
+ var createJiti__default = /*#__PURE__*/_interopDefault(createJiti);
30
+
31
+ // ../node_modules/.pnpm/tsup@8.5.0_jiti@2.5.1_postcss@8.5.6_tsx@4.20.4_typescript@5.9.2_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js
32
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
33
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
34
+ var notNullish = (value) => value !== null && value !== void 0;
35
+ var nullish = (value) => value === null || value === void 0;
36
+ function idToVar({
37
+ id,
38
+ prefix = "product"
39
+ }) {
40
+ const processed = id.replace(/[-_](.)/g, (_, letter) => letter.toUpperCase()).replace(/[^a-zA-Z0-9_$]/g, "");
41
+ if (/^[0-9]/.test(processed)) {
42
+ return `${prefix}${processed}`;
43
+ }
44
+ if (/^[^a-zA-Z_$]/.test(processed)) {
45
+ return `${prefix}${processed}`;
46
+ }
47
+ return processed;
48
+ }
49
+ async function upsertEnvVar(filePath, varName, newValue) {
50
+ const content = fs__default.default.readFileSync(filePath, "utf-8");
51
+ const lines = content.split("\n");
52
+ let found = false;
53
+ for (let i = 0; i < lines.length; i++) {
54
+ if (lines[i]?.startsWith(`${varName}=`)) {
55
+ const shouldOverwrite = await prompts.confirm({
56
+ message: `${varName} already exists in .env. Overwrite?`,
57
+ default: false
58
+ });
59
+ if (shouldOverwrite) {
60
+ lines[i] = `${varName}=${newValue}`;
61
+ found = true;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ if (!found) {
67
+ lines.push(`${varName}=${newValue}`);
68
+ }
69
+ fs__default.default.writeFileSync(filePath, lines.join("\n"));
70
+ }
71
+ async function storeToEnv(prodKey, sandboxKey) {
72
+ const envPath = `${process.cwd()}/.env`;
73
+ const envLocalPath = `${process.cwd()}/.env.local`;
74
+ const envVars = `AUTUMN_PROD_SECRET_KEY=${prodKey}
75
+ AUTUMN_SECRET_KEY=${sandboxKey}
76
+ `;
77
+ if (fs__default.default.existsSync(envPath)) {
78
+ await upsertEnvVar(envPath, "AUTUMN_PROD_SECRET_KEY", prodKey);
79
+ await upsertEnvVar(envPath, "AUTUMN_SECRET_KEY", sandboxKey);
80
+ console.log(chalk8__default.default.green(".env file found. Updated keys."));
81
+ } else if (fs__default.default.existsSync(envLocalPath)) {
82
+ fs__default.default.writeFileSync(envPath, envVars);
83
+ console.log(
84
+ chalk8__default.default.green(
85
+ ".env.local found but .env not found. Created new .env file and wrote keys."
86
+ )
87
+ );
88
+ } else {
89
+ fs__default.default.writeFileSync(envPath, envVars);
90
+ console.log(
91
+ chalk8__default.default.green(
92
+ "No .env or .env.local file found. Created new .env file and wrote keys."
93
+ )
94
+ );
95
+ }
96
+ }
97
+ function getEnvVar(parsed, prodFlag) {
98
+ if (prodFlag) return parsed["AUTUMN_PROD_SECRET_KEY"];
99
+ return parsed["AUTUMN_SECRET_KEY"];
100
+ }
101
+ function readFromEnv(options) {
102
+ const envPath = `${process.cwd()}/.env`;
103
+ const envLocalPath = `${process.cwd()}/.env.local`;
104
+ const prodFlag = process.argv.includes("--prod") || process.argv.includes("-p");
105
+ if (prodFlag && process.env["AUTUMN_PROD_SECRET_KEY"]) {
106
+ return process.env["AUTUMN_PROD_SECRET_KEY"];
107
+ }
108
+ if (!prodFlag && process.env["AUTUMN_SECRET_KEY"]) {
109
+ return process.env["AUTUMN_SECRET_KEY"];
110
+ }
111
+ let secretKey = void 0;
112
+ if (fs__default.default.existsSync(envPath))
113
+ secretKey = getEnvVar(
114
+ dotenv__default.default.parse(fs__default.default.readFileSync(envPath, "utf-8")),
115
+ prodFlag
116
+ );
117
+ if (fs__default.default.existsSync(envLocalPath))
118
+ secretKey = getEnvVar(
119
+ dotenv__default.default.parse(fs__default.default.readFileSync(envLocalPath, "utf-8")),
120
+ prodFlag
121
+ );
122
+ if (!secretKey && !options?.bypass) {
123
+ if (prodFlag) {
124
+ console.error(
125
+ "[Error] atmn uses the AUTUMN_PROD_SECRET_KEY to call the Autumn production API. Please add it to your .env file or run `atmn login` to authenticate."
126
+ );
127
+ process.exit(1);
128
+ } else {
129
+ console.error(
130
+ "[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."
131
+ );
132
+ process.exit(1);
133
+ }
134
+ }
135
+ return secretKey;
136
+ }
137
+ function initSpinner(message) {
138
+ const spinner = yoctoSpinner__default.default({
139
+ text: message
140
+ });
141
+ spinner.start();
142
+ return spinner;
143
+ }
144
+ async function isSandboxKey(apiKey) {
145
+ const prefix = apiKey.split("am_sk_")[1]?.split("_")[0];
146
+ if (prefix === "live") {
147
+ return false;
148
+ } else if (prefix === "test") return true;
149
+ else throw new Error("Invalid API key");
150
+ }
151
+
152
+ // source/constants.ts
153
+ var FRONTEND_URL = "http://app.useautumn.com";
154
+ var BACKEND_URL = "https://api.useautumn.com";
155
+ var DEFAULT_CONFIG = `import {
156
+ feature,
157
+ product,
158
+ priceItem,
159
+ featureItem,
160
+ pricedFeatureItem,
161
+ } from 'atmn';
162
+
163
+ export const seats = feature({
164
+ id: 'seats',
165
+ name: 'Seats',
166
+ type: 'continuous_use',
167
+ });
168
+
169
+ export const messages = feature({
170
+ id: 'messages',
171
+ name: 'Messages',
172
+ type: 'single_use',
173
+ });
174
+
175
+ export const pro = product({
176
+ id: 'pro',
177
+ name: 'Pro',
178
+ items: [
179
+ // 500 messages per month
180
+ featureItem({
181
+ feature_id: messages.id,
182
+ included_usage: 500,
183
+ interval: 'month',
184
+ }),
185
+
186
+ // $10 per seat per month
187
+ pricedFeatureItem({
188
+ feature_id: seats.id,
189
+ price: 10,
190
+ interval: 'month',
191
+ }),
192
+
193
+ // $50 / month
194
+ priceItem({
195
+ price: 50,
196
+ interval: 'month',
197
+ }),
198
+ ],
199
+ });
200
+ `;
201
+
202
+ // source/core/api.ts
203
+ var INTERNAL_BASE = BACKEND_URL;
204
+ var EXTERNAL_BASE = `${BACKEND_URL}/v1`;
205
+ async function request({
206
+ method,
207
+ base,
208
+ path: path2,
209
+ data,
210
+ headers,
211
+ customAuth,
212
+ throwOnError = true,
213
+ secretKey,
214
+ queryParams,
215
+ bypass
216
+ }) {
217
+ const apiKey = secretKey || readFromEnv({ bypass });
218
+ try {
219
+ const response = await axios__default.default.request({
220
+ method,
221
+ url: `${base}${path2}`,
222
+ data,
223
+ params: queryParams,
224
+ headers: {
225
+ "Content-Type": "application/json",
226
+ ...headers,
227
+ Authorization: customAuth || `Bearer ${apiKey}`
228
+ }
229
+ });
230
+ return response.data;
231
+ } catch (error) {
232
+ if (throwOnError) {
233
+ throw error;
234
+ }
235
+ console.error("\n" + chalk8__default.default.bgRed.white.bold(" API REQUEST FAILED "));
236
+ const methodPath = `${method.toUpperCase()} ${base}${path2}`;
237
+ console.error(chalk8__default.default.red(methodPath));
238
+ if (error instanceof axios.AxiosError) {
239
+ const status = error.response?.status;
240
+ const data2 = error.response?.data;
241
+ const code = data2?.code || data2?.error || "unknown_error";
242
+ const message = data2?.message || error.message || "An unknown error occurred";
243
+ if (status) {
244
+ console.error(chalk8__default.default.redBright(`[${status}] ${code}`));
245
+ }
246
+ console.error(chalk8__default.default.red(message));
247
+ } else if (error instanceof Error) {
248
+ console.error(chalk8__default.default.red(error.message));
249
+ } else {
250
+ console.error(chalk8__default.default.red(String(error)));
251
+ }
252
+ process.exit(1);
253
+ }
254
+ }
255
+ async function internalRequest({
256
+ method,
257
+ path: path2,
258
+ data,
259
+ headers,
260
+ customAuth
261
+ }) {
262
+ return await request({
263
+ method,
264
+ base: INTERNAL_BASE,
265
+ path: path2,
266
+ data,
267
+ headers,
268
+ customAuth,
269
+ bypass: true
270
+ });
271
+ }
272
+ async function externalRequest({
273
+ method,
274
+ path: path2,
275
+ data,
276
+ headers,
277
+ customAuth,
278
+ throwOnError = false,
279
+ queryParams
280
+ }) {
281
+ return await request({
282
+ method,
283
+ base: EXTERNAL_BASE,
284
+ path: path2,
285
+ data,
286
+ headers,
287
+ customAuth,
288
+ throwOnError,
289
+ queryParams
290
+ });
291
+ }
292
+ async function deleteFeature({ id }) {
293
+ return await externalRequest({
294
+ method: "DELETE",
295
+ path: `/features/${id}`
296
+ });
297
+ }
298
+ async function deleteProduct({
299
+ id,
300
+ allVersions
301
+ }) {
302
+ return await externalRequest({
303
+ method: "DELETE",
304
+ path: `/products/${id}`,
305
+ queryParams: { all_versions: allVersions ? true : false }
306
+ });
307
+ }
308
+ async function updateCLIStripeKeys({
309
+ stripeSecretKey,
310
+ autumnSecretKey
311
+ }) {
312
+ return await request({
313
+ base: EXTERNAL_BASE,
314
+ method: "POST",
315
+ path: "/organization/stripe",
316
+ data: { secret_key: stripeSecretKey },
317
+ secretKey: autumnSecretKey
318
+ });
319
+ }
320
+
321
+ // source/core/auth.ts
322
+ async function getOTP(otp) {
323
+ const response = await internalRequest({
324
+ method: "GET",
325
+ path: `/dev/otp/${otp}`
326
+ });
327
+ return response;
328
+ }
329
+
330
+ // source/commands/auth.ts
331
+ var passwordTheme = {
332
+ style: {
333
+ answer: (text) => {
334
+ return chalk8__default.default.magenta("*".repeat(text.length));
335
+ }
336
+ }
337
+ };
338
+ var inputTheme = {
339
+ style: {
340
+ answer: (text) => {
341
+ return chalk8__default.default.magenta(text);
342
+ }
343
+ }
344
+ };
345
+ async function AuthCommand() {
346
+ if (readFromEnv({ bypass: true })) {
347
+ let shouldReauth = await prompts.confirm({
348
+ message: "You are already authenticated. Would you like to re-authenticate?",
349
+ theme: inputTheme
350
+ });
351
+ if (!shouldReauth) return;
352
+ }
353
+ open__default.default(`${FRONTEND_URL}/dev/cli`);
354
+ const otp = await prompts.input({
355
+ message: "Enter OTP:",
356
+ theme: inputTheme
357
+ });
358
+ const keyInfo = await getOTP(otp);
359
+ if (!keyInfo.stripe_connected) {
360
+ let connectStripe = await prompts.confirm({
361
+ message: "It seems like your organization doesn't have any Stripe keys connected. Would you like to connect your Stripe test secret key now?",
362
+ theme: inputTheme
363
+ });
364
+ if (connectStripe) {
365
+ let stripeTestKey = await prompts.password({
366
+ message: "Enter Stripe Test Secret Key:",
367
+ mask: "*",
368
+ theme: passwordTheme
369
+ });
370
+ await updateCLIStripeKeys({
371
+ stripeSecretKey: stripeTestKey,
372
+ autumnSecretKey: keyInfo.sandboxKey
373
+ });
374
+ console.log(
375
+ chalk8__default.default.green(
376
+ "Stripe test secret key has been saved to your .env file. To connect your Stripe live secret key, please visit the Autumn dashboard here: https://app.useautumn.com/dev?tab=stripe"
377
+ )
378
+ );
379
+ } else {
380
+ console.log(
381
+ chalk8__default.default.yellow(
382
+ "Okay, no worries. Go to the Autumn dashboard when you're ready!"
383
+ )
384
+ );
385
+ }
386
+ }
387
+ await storeToEnv(keyInfo.prodKey, keyInfo.sandboxKey);
388
+ console.log(
389
+ chalk8__default.default.green(
390
+ "Success! Sandbox and production keys have been saved to your .env file.\n`atmn` uses the AUTUMN_SECRET_KEY to authenticate with the Autumn API."
391
+ )
392
+ );
393
+ }
394
+ async function getAllProducts(params) {
395
+ const { list: products } = await externalRequest({
396
+ method: "GET",
397
+ path: `/products`,
398
+ queryParams: { include_archived: params?.archived ? true : false }
399
+ });
400
+ return [...products];
401
+ }
402
+ async function getFeatures(params) {
403
+ const { list } = await externalRequest({
404
+ method: "GET",
405
+ path: "/features",
406
+ queryParams: { include_archived: params?.includeArchived ? true : false }
407
+ });
408
+ return list.map((feature) => feature);
409
+ }
410
+ var MAX_RECURSION_LIMIT = 500;
411
+ async function getCustomers(limit = 100, offset = 0) {
412
+ const { list, total } = await externalRequest({
413
+ method: "GET",
414
+ path: `/customers?limit=${limit}&offset=${offset}`
415
+ });
416
+ const customers = list.map(
417
+ (customer) => ({
418
+ id: customer.id,
419
+ text: customer.name || customer.email || customer.id
420
+ })
421
+ );
422
+ if (offset + limit < total && offset < MAX_RECURSION_LIMIT) {
423
+ const remainingCustomers = await getCustomers(limit, offset + limit);
424
+ return [...customers, ...remainingCustomers];
425
+ } else if (offset >= MAX_RECURSION_LIMIT) {
426
+ console.log(
427
+ chalk8__default.default.red(
428
+ `Reached maximum recursion limit of ${MAX_RECURSION_LIMIT} customers. Exiting.`
429
+ )
430
+ );
431
+ process.exit(1);
432
+ }
433
+ return customers;
434
+ }
435
+
436
+ // source/core/builders/freeTrialBuilder.ts
437
+ function freeTrialBuilder({ freeTrial }) {
438
+ return `free_trial: {
439
+ duration: '${freeTrial.duration}',
440
+ length: ${freeTrial.length},
441
+ unique_fingerprint: ${freeTrial.unique_fingerprint},
442
+ card_required: ${freeTrial.card_required},
443
+ },`;
444
+ }
445
+
446
+ // source/core/builders/productBuilder.ts
447
+ var ItemBuilders = {
448
+ priced_feature: pricedFeatureItemBuilder,
449
+ feature: featureItemBuilder,
450
+ price: priceItemBuilder
451
+ };
452
+ function importBuilder() {
453
+ return `
454
+ import {
455
+ feature,
456
+ product,
457
+ featureItem,
458
+ pricedFeatureItem,
459
+ priceItem,
460
+ } from 'atmn';
461
+ `;
462
+ }
463
+ function productBuilder({
464
+ product,
465
+ features
466
+ }) {
467
+ const snippet = `
468
+ export const ${idToVar({ id: product.id, prefix: "product" })} = product({
469
+ id: '${product.id}',
470
+ name: '${product.name}',
471
+ items: [${product.items.map(
472
+ (item) => `${ItemBuilders[item.type]({
473
+ item,
474
+ features
475
+ })}`
476
+ ).join(" ")} ],
477
+ ${product.free_trial ? `${freeTrialBuilder({ freeTrial: product.free_trial })}` : ""}
478
+ })
479
+ `;
480
+ return snippet;
481
+ }
482
+ var getFeatureIdStr = ({
483
+ featureId,
484
+ features
485
+ }) => {
486
+ if (nullish(featureId)) return "";
487
+ let feature = features.find((f) => f.id === featureId);
488
+ if (feature?.archived) return `"${featureId}"`;
489
+ return `${idToVar({ id: featureId, prefix: "feature" })}.id`;
490
+ };
491
+ var getItemFieldPrefix = () => {
492
+ return `
493
+ `;
494
+ };
495
+ var getResetUsageStr = ({
496
+ item,
497
+ features
498
+ }) => {
499
+ if (!item.feature_id) return "";
500
+ const feature = features.find((f) => f.id === item.feature_id);
501
+ if (feature.type === "boolean" || feature.type === "credit_system") return "";
502
+ const defaultResetUsage = feature.type === "single_use" ? true : false;
503
+ if (notNullish(item.reset_usage_when_enabled) && item.reset_usage_when_enabled !== defaultResetUsage) {
504
+ return `${getItemFieldPrefix()}reset_usage_when_enabled: ${item.reset_usage_when_enabled},`;
505
+ }
506
+ return "";
507
+ };
508
+ var getIntervalStr = ({ item }) => {
509
+ if (item.interval == null) return ``;
510
+ return `${getItemFieldPrefix()}interval: '${item.interval}',`;
511
+ };
512
+ var getEntityFeatureIdStr = ({
513
+ item,
514
+ features
515
+ }) => {
516
+ if (nullish(item.entity_feature_id)) return "";
517
+ const featureIdStr = getFeatureIdStr({
518
+ featureId: item.entity_feature_id,
519
+ features
520
+ });
521
+ return `${getItemFieldPrefix()}entity_feature_id: ${featureIdStr},`;
522
+ };
523
+ var getPriceStr = ({ item }) => {
524
+ if (item.tiers) {
525
+ return `
526
+ tiers: [
527
+ ${item.tiers.map(
528
+ (tier) => `{ to: ${tier.to == "inf" ? "'inf'" : tier.to}, amount: ${tier.amount} }`
529
+ ).join(",\n ")}
530
+ ],`;
531
+ }
532
+ if (item.price == null) return "";
533
+ return `price: ${item.price},`;
534
+ };
535
+ function pricedFeatureItemBuilder({
536
+ item,
537
+ features
538
+ }) {
539
+ const intervalStr = getIntervalStr({ item });
540
+ const entityFeatureIdStr = getEntityFeatureIdStr({ item, features });
541
+ const resetUsageStr = getResetUsageStr({ item, features });
542
+ const priceStr = getPriceStr({ item });
543
+ const featureIdStr = getFeatureIdStr({ featureId: item.feature_id, features });
544
+ const snippet = `
545
+ pricedFeatureItem({
546
+ feature_id: ${featureIdStr},
547
+ ${priceStr}${intervalStr}
548
+ included_usage: ${item.included_usage == "inf" ? `"inf"` : item.included_usage},
549
+ billing_units: ${item.billing_units},
550
+ usage_model: '${item.usage_model}',${resetUsageStr}${entityFeatureIdStr}
551
+ }),
552
+ `;
553
+ return snippet;
554
+ }
555
+ function featureItemBuilder({
556
+ item,
557
+ features
558
+ }) {
559
+ const featureIdStr = getFeatureIdStr({ featureId: item.feature_id, features });
560
+ const entityFeatureIdStr = getEntityFeatureIdStr({ item, features });
561
+ const intervalStr = getIntervalStr({ item });
562
+ const resetUsageStr = getResetUsageStr({ item, features });
563
+ const snippet = `
564
+ featureItem({
565
+ feature_id: ${featureIdStr},
566
+ included_usage: ${item.included_usage == "inf" ? `"inf"` : item.included_usage},${intervalStr}${resetUsageStr}${entityFeatureIdStr}
567
+ }),
568
+ `;
569
+ return snippet;
570
+ }
571
+ function priceItemBuilder({ item }) {
572
+ const intervalStr = getIntervalStr({ item });
573
+ const snippet = `
574
+ priceItem({
575
+ price: ${item.price},${intervalStr}
576
+ }),
577
+ `;
578
+ return snippet;
579
+ }
580
+
581
+ // source/core/builders/featureBuilder.ts
582
+ var creditSchemaBuilder = (feature) => {
583
+ if (feature.type == "credit_system") {
584
+ let creditSchema = feature.credit_schema || [];
585
+ return `
586
+ credit_schema: [
587
+ ${creditSchema.map(
588
+ (credit) => `{
589
+ metered_feature_id: '${credit.metered_feature_id}',
590
+ credit_cost: ${credit.credit_cost},
591
+ }`
592
+ ).join(",\n ")}
593
+ ]`;
594
+ }
595
+ return "";
596
+ };
597
+ function featureBuilder(feature) {
598
+ const snippet = `
599
+ export const ${idToVar({ id: feature.id, prefix: "feature" })} = feature({
600
+ id: '${feature.id}',
601
+ name: '${feature.name}',
602
+ type: '${feature.type}',${creditSchemaBuilder(feature)}
603
+ })`;
604
+ return snippet;
605
+ }
606
+ var ProductItemIntervalEnum = v4.z.enum([
607
+ "minute",
608
+ "hour",
609
+ "day",
610
+ "week",
611
+ "month",
612
+ "quarter",
613
+ "semi_annual",
614
+ "year"
615
+ ]);
616
+ var UsageModelEnum = v4.z.enum(["prepaid", "pay_per_use"]);
617
+ var ProductItemSchema = v4.z.object({
618
+ type: v4.z.enum(["feature", "priced_feature"]).nullish(),
619
+ feature_id: v4.z.string().nullish(),
620
+ included_usage: v4.z.union([v4.z.number(), v4.z.literal("inf")]).nullish(),
621
+ interval: ProductItemIntervalEnum.nullish(),
622
+ usage_model: UsageModelEnum.nullish(),
623
+ price: v4.z.number().nullish(),
624
+ tiers: v4.z.array(
625
+ v4.z.object({
626
+ amount: v4.z.number(),
627
+ to: v4.z.union([v4.z.number(), v4.z.literal("inf")])
628
+ })
629
+ ).nullish(),
630
+ billing_units: v4.z.number().nullish(),
631
+ // amount per billing unit (eg. $9 / 250 units)
632
+ reset_usage_when_enabled: v4.z.boolean().optional(),
633
+ entity_feature_id: v4.z.string().optional()
634
+ });
635
+ v4.z.object({
636
+ feature_id: v4.z.string(),
637
+ included_usage: v4.z.number().nullish(),
638
+ interval: ProductItemIntervalEnum.nullish()
639
+ });
640
+ v4.z.object({
641
+ price: v4.z.number().gt(0),
642
+ interval: ProductItemIntervalEnum.nullish()
643
+ });
644
+
645
+ // source/compose/models/composeModels.ts
646
+ var FreeTrialSchema = v4.z.object({
647
+ duration: v4.z.enum(["day", "month", "year"]),
648
+ length: v4.z.number(),
649
+ unique_fingerprint: v4.z.boolean(),
650
+ card_required: v4.z.boolean()
651
+ });
652
+ var ProductSchema = v4.z.object({
653
+ id: v4.z.string().min(1),
654
+ name: v4.z.string().min(1),
655
+ is_add_on: v4.z.boolean().prefault(false).optional(),
656
+ is_default: v4.z.boolean().prefault(false).optional(),
657
+ items: v4.z.array(ProductItemSchema),
658
+ free_trial: FreeTrialSchema.optional()
659
+ });
660
+ var FeatureSchema = v4.z.object({
661
+ id: v4.z.string().min(1),
662
+ name: v4.z.string().optional(),
663
+ type: v4.z.enum(["boolean", "single_use", "continuous_use", "credit_system"]),
664
+ credit_schema: v4.z.array(
665
+ v4.z.object({
666
+ metered_feature_id: v4.z.string(),
667
+ credit_cost: v4.z.number()
668
+ })
669
+ ).optional(),
670
+ archived: v4.z.boolean().optional()
671
+ });
672
+
673
+ // source/core/config.ts
674
+ function checkAtmnInstalled() {
675
+ try {
676
+ const packageJsonPath = path__default.default.join(process.cwd(), "package.json");
677
+ if (fs__default.default.existsSync(packageJsonPath)) {
678
+ const packageJson = JSON.parse(fs__default.default.readFileSync(packageJsonPath, "utf-8"));
679
+ return !!(packageJson.dependencies?.atmn || packageJson.devDependencies?.atmn);
680
+ }
681
+ child_process.execSync(`node -e "require.resolve('atmn')"`, { stdio: "ignore" });
682
+ return true;
683
+ } catch {
684
+ return false;
685
+ }
686
+ }
687
+ async function installAtmn() {
688
+ const shouldInstall = await prompts.confirm({
689
+ message: "The atmn package is not installed. Would you like to install it now?",
690
+ default: true
691
+ });
692
+ if (!shouldInstall) {
693
+ console.log(
694
+ chalk8__default.default.yellow(
695
+ "Skipping installation. You can install atmn manually with your preferred package manager."
696
+ )
697
+ );
698
+ return false;
699
+ }
700
+ const packageManager = await prompts.select({
701
+ message: "Which package manager would you like to use?",
702
+ choices: [
703
+ { name: "npm", value: "npm" },
704
+ { name: "pnpm", value: "pnpm" },
705
+ { name: "bun", value: "bun" }
706
+ ],
707
+ default: "npm"
708
+ });
709
+ try {
710
+ console.log(chalk8__default.default.blue(`Installing atmn with ${packageManager}...`));
711
+ const installCommand = packageManager === "npm" ? "npm install atmn" : packageManager === "pnpm" ? "pnpm add atmn" : "bun add atmn";
712
+ child_process.execSync(installCommand, { stdio: "inherit" });
713
+ console.log(chalk8__default.default.green("atmn installed successfully!"));
714
+ return true;
715
+ } catch (error) {
716
+ console.error(chalk8__default.default.red("Failed to install atmn:"), error);
717
+ return false;
718
+ }
719
+ }
720
+ function isProduct(value) {
721
+ try {
722
+ ProductSchema.parse(value);
723
+ return true;
724
+ } catch {
725
+ return false;
726
+ }
727
+ }
728
+ function isFeature(value) {
729
+ try {
730
+ FeatureSchema.parse(value);
731
+ return true;
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+ async function loadAutumnConfigFile() {
737
+ const configPath = path__default.default.join(process.cwd(), "autumn.config.ts");
738
+ const absolutePath = path.resolve(configPath);
739
+ const fileUrl = url.pathToFileURL(absolutePath).href;
740
+ if (!checkAtmnInstalled()) {
741
+ const installed = await installAtmn();
742
+ if (!installed) {
743
+ throw new Error(
744
+ "atmn package is required but not installed. Please install it manually."
745
+ );
746
+ }
747
+ }
748
+ const jiti = createJiti__default.default(importMetaUrl);
749
+ const mod = await jiti.import(fileUrl);
750
+ const products = [];
751
+ const features = [];
752
+ const defaultExport = mod.default;
753
+ if (defaultExport && defaultExport.products && defaultExport.features) {
754
+ if (Array.isArray(defaultExport.products)) {
755
+ products.push(...defaultExport.products);
756
+ }
757
+ if (Array.isArray(defaultExport.features)) {
758
+ features.push(...defaultExport.features);
759
+ }
760
+ } else {
761
+ for (const [key, value] of Object.entries(mod)) {
762
+ if (key === "default") continue;
763
+ if (isProduct(value)) {
764
+ products.push(value);
765
+ } else if (isFeature(value)) {
766
+ features.push(value);
767
+ }
768
+ }
769
+ }
770
+ const secretKey = readFromEnv();
771
+ if (secretKey?.includes("live")) {
772
+ console.log(chalk8__default.default.magentaBright("Running in production environment..."));
773
+ } else {
774
+ console.log(chalk8__default.default.yellow("Running in sandbox environment..."));
775
+ }
776
+ return {
777
+ products,
778
+ features,
779
+ env: secretKey?.includes("live") ? "prod" : "sandbox"
780
+ };
781
+ }
782
+ function writeConfig(config) {
783
+ const configPath = path__default.default.join(process.cwd(), "autumn.config.ts");
784
+ fs__default.default.writeFileSync(configPath, config);
785
+ }
786
+
787
+ // source/commands/pull.ts
788
+ async function Pull(options) {
789
+ console.log(chalk8__default.default.green("Pulling products and features from Autumn..."));
790
+ const products = await getAllProducts({ archived: options?.archived ?? false });
791
+ const features = await getFeatures({ includeArchived: true });
792
+ const productSnippets = products.map(
793
+ (product) => productBuilder({ product, features })
794
+ );
795
+ const featureSnippets = features.filter((feature) => !feature.archived).map((feature) => featureBuilder(feature));
796
+ const autumnConfig = `
797
+ ${importBuilder()}
798
+
799
+ // Features${featureSnippets.join("\n")}
800
+
801
+ // Products${productSnippets.join("\n")}
802
+ `;
803
+ const formattedConfig = await prettier__default.default.format(autumnConfig, {
804
+ parser: "typescript",
805
+ useTabs: true,
806
+ singleQuote: false
807
+ });
808
+ writeConfig(formattedConfig);
809
+ console.log(chalk8__default.default.green("Success! Config has been updated."));
810
+ }
811
+
812
+ // source/commands/init.ts
813
+ async function Init() {
814
+ let apiKey = readFromEnv();
815
+ if (apiKey) {
816
+ console.log(chalk8__default.default.green("API key found. Pulling latest config..."));
817
+ await Pull();
818
+ console.log(
819
+ chalk8__default.default.green("Project initialized and config pulled successfully!")
820
+ );
821
+ return;
822
+ }
823
+ console.log(chalk8__default.default.yellow("No API key found. Running authentication..."));
824
+ await AuthCommand();
825
+ apiKey = readFromEnv();
826
+ if (apiKey) {
827
+ await Pull();
828
+ console.log(
829
+ chalk8__default.default.green(
830
+ "Project initialized! You are now authenticated and config has been pulled."
831
+ )
832
+ );
833
+ } else {
834
+ console.log(
835
+ chalk8__default.default.red(
836
+ "Authentication did not yield an API key. Please check your setup."
837
+ )
838
+ );
839
+ }
840
+ }
841
+
842
+ // source/core/nuke.ts
843
+ async function nukeCustomers(customers) {
844
+ const s = initSpinner("Deleting customers");
845
+ const total = customers.length;
846
+ if (total === 0) {
847
+ s.success("Customers deleted successfully!");
848
+ return;
849
+ }
850
+ const concurrency = Math.max(
851
+ 1,
852
+ Math.min(total, Number(process.env["ATM_DELETE_CONCURRENCY"] ?? 5) || 5)
853
+ );
854
+ let completed = 0;
855
+ const updateSpinner = () => {
856
+ s.text = `Deleting customers: ${completed} / ${total}`;
857
+ };
858
+ updateSpinner();
859
+ for (let i = 0; i < total; i += concurrency) {
860
+ const batch = customers.slice(i, i + concurrency);
861
+ await Promise.all(
862
+ batch.map(async (customer) => {
863
+ try {
864
+ await deleteCustomer(customer.id);
865
+ } finally {
866
+ completed++;
867
+ updateSpinner();
868
+ }
869
+ })
870
+ );
871
+ }
872
+ s.success("Customers deleted successfully!");
873
+ }
874
+ async function deleteCustomer(id) {
875
+ await externalRequest({
876
+ method: "DELETE",
877
+ path: `/customers/${id}`
878
+ });
879
+ }
880
+ async function nukeProducts(ids) {
881
+ const s = initSpinner("Deleting products");
882
+ for (const id of ids) {
883
+ s.text = `Deleting product [${id}] ${ids.indexOf(id) + 1} / ${ids.length}`;
884
+ await deleteProduct({ id, allVersions: true });
885
+ }
886
+ s.success("Products deleted successfully!");
887
+ }
888
+ async function nukeFeatures(ids) {
889
+ const s = initSpinner("Deleting features");
890
+ for (const id of ids) {
891
+ s.text = `Deleting feature [${id}] ${ids.indexOf(id) + 1} / ${ids.length}`;
892
+ await deleteFeature({ id });
893
+ }
894
+ s.success("Features deleted successfully!");
895
+ }
896
+
897
+ // source/core/requests/orgRequests.ts
898
+ var getOrg = async () => {
899
+ const response = await externalRequest({
900
+ method: "GET",
901
+ path: "/organization"
902
+ });
903
+ return response;
904
+ };
905
+
906
+ // source/commands/nuke.ts
907
+ async function promptAndConfirmNuke(orgName) {
908
+ console.log("\n" + chalk8__default.default.bgRed.white.bold(" DANGER: SANDBOX NUKE "));
909
+ console.log(
910
+ chalk8__default.default.red(
911
+ `This is irreversible. You are about to permanently delete all data from the organization ` + chalk8__default.default.redBright.bold(orgName) + `
912
+
913
+ Items to be deleted:
914
+ \u2022 ` + chalk8__default.default.yellowBright("customers") + `
915
+ \u2022 ` + chalk8__default.default.yellowBright("features") + `
916
+ \u2022 ` + chalk8__default.default.yellowBright("products") + `
917
+ `
918
+ )
919
+ );
920
+ const shouldProceed = await prompts.confirm({
921
+ message: `Confirm to continue. This will delete ${chalk8__default.default.redBright.bold("all")} your ${chalk8__default.default.redBright.bold("products")}, ${chalk8__default.default.redBright.bold("features")} and ${chalk8__default.default.redBright.bold("customers")} from your sandbox environment. You will confirm twice.`,
922
+ default: false
923
+ });
924
+ if (!shouldProceed) {
925
+ console.log(chalk8__default.default.red("Aborting..."));
926
+ process.exit(1);
927
+ }
928
+ const finalConfirm = await prompts.confirm({
929
+ message: "Final confirmation: Are you absolutely sure? This action is irreversible.",
930
+ default: false
931
+ });
932
+ if (!finalConfirm) {
933
+ console.log(chalk8__default.default.red("Aborting..."));
934
+ process.exit(1);
935
+ }
936
+ const backupConfirm = await prompts.confirm({
937
+ message: `Would you like to backup your ${chalk8__default.default.magentaBright.bold("autumn.config.ts")} file before proceeding? (Recommended)`,
938
+ default: true
939
+ });
940
+ return backupConfirm;
941
+ }
942
+ async function Nuke() {
943
+ const apiKey = readFromEnv();
944
+ const isSandbox = await isSandboxKey(apiKey);
945
+ if (isSandbox) {
946
+ const org = await getOrg();
947
+ const backupConfirm = await promptAndConfirmNuke(org.name);
948
+ if (backupConfirm) {
949
+ fs__default.default.copyFileSync("autumn.config.ts", "autumn.config.ts.backup");
950
+ console.log(chalk8__default.default.green("Backup created successfully!"));
951
+ }
952
+ console.log(chalk8__default.default.red("Nuking sandbox..."));
953
+ const s = initSpinner(
954
+ `Preparing ${chalk8__default.default.yellowBright("customers")}, ${chalk8__default.default.yellowBright("features")} and ${chalk8__default.default.yellowBright("products")} for deletion...`
955
+ );
956
+ const products = await getAllProducts({ archived: true });
957
+ const features = await getFeatures();
958
+ const customers = await getCustomers();
959
+ s.success(
960
+ `Loaded all ${chalk8__default.default.yellowBright("customers")}, ${chalk8__default.default.yellowBright("features")} and ${chalk8__default.default.yellowBright("products")} for deletion`
961
+ );
962
+ features.sort((a, b) => {
963
+ if (a.type === "credit_system") {
964
+ return -1;
965
+ }
966
+ return 1;
967
+ });
968
+ try {
969
+ await nukeCustomers(customers);
970
+ await nukeProducts(products.map((product) => product.id));
971
+ await nukeFeatures(features.map((feature) => feature.id));
972
+ } catch (e) {
973
+ console.error(chalk8__default.default.red("Failed to nuke sandbox:"));
974
+ console.error(e);
975
+ process.exit(1);
976
+ }
977
+ console.log(chalk8__default.default.green("Sandbox nuked successfully!"));
978
+ } else {
979
+ console.log(chalk8__default.default.red`You can't nuke a prod environment!`);
980
+ process.exit(1);
981
+ }
982
+ }
983
+
984
+ // source/core/push.ts
985
+ async function checkForDeletables(currentFeatures, currentProducts) {
986
+ const features = await getFeatures({ includeArchived: true });
987
+ const featureIds = features.map((feature) => feature.id);
988
+ const currentFeatureIds = currentFeatures.map((feature) => feature.id);
989
+ const featuresToDelete = featureIds.filter(
990
+ (featureId) => !currentFeatureIds.includes(featureId) && !features.archived
991
+ );
992
+ const products = await getAllProducts();
993
+ const productIds = products.map((product) => product.id);
994
+ const currentProductIds = currentProducts.map(
995
+ (product) => product.id
996
+ );
997
+ const productsToDelete = productIds.filter(
998
+ (productId) => !currentProductIds.includes(productId)
999
+ );
1000
+ return {
1001
+ allFeatures: features,
1002
+ curFeatures: features.filter((feature) => !feature.archived),
1003
+ curProducts: products,
1004
+ featuresToDelete,
1005
+ productsToDelete
1006
+ };
1007
+ }
1008
+ var isDuplicate = (error) => {
1009
+ return error.response && error.response.data && (error.response.data.code === "duplicate_feature_id" || error.response.data.code === "product_already_exists");
1010
+ };
1011
+ async function upsertFeature(feature, s) {
1012
+ try {
1013
+ const response = await externalRequest({
1014
+ method: "POST",
1015
+ path: `/features`,
1016
+ data: feature,
1017
+ throwOnError: true
1018
+ });
1019
+ return response.data;
1020
+ } catch (error) {
1021
+ if (isDuplicate(error)) {
1022
+ const response = await externalRequest({
1023
+ method: "POST",
1024
+ path: `/features/${feature.id}`,
1025
+ data: feature
1026
+ });
1027
+ return response.data;
1028
+ }
1029
+ console.error(
1030
+ `
1031
+ Failed to push feature ${feature.id}: ${error.response?.data?.message || "Unknown error"}`
1032
+ );
1033
+ process.exit(1);
1034
+ } finally {
1035
+ s.text = `Pushed feature [${feature.id}]`;
1036
+ }
1037
+ }
1038
+ async function checkProductForConfirmation({
1039
+ curProducts,
1040
+ product
1041
+ }) {
1042
+ const curProduct = curProducts.find((p) => p.id === product.id);
1043
+ if (!curProduct) {
1044
+ return {
1045
+ id: product.id,
1046
+ will_version: false
1047
+ };
1048
+ }
1049
+ const res1 = await externalRequest({
1050
+ method: "GET",
1051
+ path: `/products/${product.id}/has_customers`,
1052
+ data: product
1053
+ });
1054
+ return {
1055
+ id: product.id,
1056
+ will_version: res1.will_version,
1057
+ archived: res1.archived
1058
+ };
1059
+ }
1060
+ async function upsertProduct({
1061
+ curProducts,
1062
+ product,
1063
+ spinner,
1064
+ shouldUpdate = true
1065
+ }) {
1066
+ if (!shouldUpdate) {
1067
+ spinner.text = `Skipping update to product ${product.id}`;
1068
+ return {
1069
+ id: product.id,
1070
+ action: "skipped"
1071
+ };
1072
+ }
1073
+ const curProduct = curProducts.find((p) => p.id === product.id);
1074
+ if (!curProduct) {
1075
+ await externalRequest({
1076
+ method: "POST",
1077
+ path: `/products`,
1078
+ data: product
1079
+ });
1080
+ spinner.text = `Created product [${product.id}]`;
1081
+ return {
1082
+ id: product.id,
1083
+ action: "create"
1084
+ };
1085
+ } else {
1086
+ await externalRequest({
1087
+ method: "POST",
1088
+ path: `/products/${product.id}`,
1089
+ data: product
1090
+ });
1091
+ spinner.text = `Updated product [${product.id}]`;
1092
+ return {
1093
+ id: product.id,
1094
+ action: "updated"
1095
+ };
1096
+ }
1097
+ }
1098
+
1099
+ // source/core/requests/featureRequests.ts
1100
+ var updateFeature = async ({
1101
+ id,
1102
+ update
1103
+ }) => {
1104
+ return await externalRequest({
1105
+ method: "POST",
1106
+ path: `/features/${id}`,
1107
+ data: update
1108
+ });
1109
+ };
1110
+ var checkFeatureDeletionData = async ({
1111
+ featureId
1112
+ }) => {
1113
+ const res = await externalRequest({
1114
+ method: "GET",
1115
+ path: `/features/${featureId}/deletion_info`
1116
+ });
1117
+ return res;
1118
+ };
1119
+
1120
+ // source/core/requests/prodRequests.ts
1121
+ var getProductDeleteInfo = async ({
1122
+ productId
1123
+ }) => {
1124
+ const response = await externalRequest({
1125
+ method: "GET",
1126
+ path: `/products/${productId}/deletion_info`
1127
+ });
1128
+ return response;
1129
+ };
1130
+ var updateProduct = async ({
1131
+ productId,
1132
+ update
1133
+ }) => {
1134
+ const response = await externalRequest({
1135
+ method: "POST",
1136
+ path: `/products/${productId}`,
1137
+ data: update
1138
+ });
1139
+ return response;
1140
+ };
1141
+
1142
+ // source/commands/push.ts
1143
+ var createSpinner = ({ message }) => {
1144
+ const spinner = yoctoSpinner__default.default({
1145
+ text: message ?? ""
1146
+ });
1147
+ spinner.start();
1148
+ return spinner;
1149
+ };
1150
+ var gatherProductDeletionDecisions = async ({
1151
+ productsToDelete,
1152
+ yes
1153
+ }) => {
1154
+ const productDeletionDecisions = /* @__PURE__ */ new Map();
1155
+ const batchCheckProducts = [];
1156
+ for (const productId of productsToDelete) {
1157
+ batchCheckProducts.push(getProductDeleteInfo({ productId }));
1158
+ }
1159
+ const checkProductResults = await Promise.all(batchCheckProducts);
1160
+ for (let i = 0; i < productsToDelete.length; i++) {
1161
+ const productId = productsToDelete[i];
1162
+ const result = checkProductResults[i];
1163
+ if (!productId) continue;
1164
+ if (result && result.totalCount > 0) {
1165
+ const otherCustomersText = result.totalCount > 1 ? ` and ${result.totalCount - 1} other customer(s)` : "";
1166
+ const customerNameText = result.customerName || "Unknown Customer";
1167
+ const shouldArchive = yes || await prompts.confirm({
1168
+ message: `Product ${productId} has customer ${customerNameText}${otherCustomersText}. As such, you cannot delete it. Would you like to archive the product instead?`
1169
+ });
1170
+ productDeletionDecisions.set(
1171
+ productId,
1172
+ shouldArchive ? "archive" : "skip"
1173
+ );
1174
+ } else {
1175
+ productDeletionDecisions.set(productId, "delete");
1176
+ }
1177
+ }
1178
+ return productDeletionDecisions;
1179
+ };
1180
+ var handleProductDeletion = async ({
1181
+ productsToDelete,
1182
+ yes
1183
+ }) => {
1184
+ const productDeletionDecisions = await gatherProductDeletionDecisions({
1185
+ productsToDelete,
1186
+ yes
1187
+ });
1188
+ for (const productId of productsToDelete) {
1189
+ const decision = productDeletionDecisions.get(productId);
1190
+ if (decision === "delete") {
1191
+ const shouldDelete = yes || await prompts.confirm({
1192
+ message: `Delete product [${productId}]?`
1193
+ });
1194
+ if (shouldDelete) {
1195
+ const s = createSpinner({ message: `Deleting product [${productId}]` });
1196
+ await deleteProduct({ id: productId });
1197
+ s.success(`Product [${productId}] deleted successfully!`);
1198
+ }
1199
+ } else if (decision === "archive") {
1200
+ const s = createSpinner({ message: `Archiving product [${productId}]` });
1201
+ await updateProduct({ productId, update: { archived: true } });
1202
+ s.success(`Product [${productId}] archived successfully!`);
1203
+ }
1204
+ }
1205
+ };
1206
+ var pushFeatures = async ({
1207
+ features,
1208
+ allFeatures,
1209
+ yes
1210
+ }) => {
1211
+ for (const feature of features) {
1212
+ const isArchived = allFeatures.find(
1213
+ (f) => f.id === feature.id
1214
+ )?.archived;
1215
+ if (isArchived) {
1216
+ const shouldUnarchive = yes || await prompts.confirm({
1217
+ message: `Feature ${feature.id} is currently archived. Would you like to un-archive it before pushing?`
1218
+ });
1219
+ if (shouldUnarchive) {
1220
+ const s2 = createSpinner({
1221
+ message: `Un-archiving feature [${feature.id}]`
1222
+ });
1223
+ await updateFeature({ id: feature.id, update: { archived: false } });
1224
+ s2.success(`Feature [${feature.id}] un-archived successfully!`);
1225
+ }
1226
+ }
1227
+ }
1228
+ const batchFeatures = [];
1229
+ const s = initSpinner(`Pushing features`);
1230
+ for (const feature of features) {
1231
+ batchFeatures.push(upsertFeature(feature, s));
1232
+ }
1233
+ await Promise.all(batchFeatures);
1234
+ s.success(`Features pushed successfully!`);
1235
+ console.log(chalk8__default.default.dim("\nFeatures pushed:"));
1236
+ features.forEach((feature) => {
1237
+ console.log(chalk8__default.default.cyan(` \u2022 ${feature.id}`));
1238
+ });
1239
+ console.log();
1240
+ };
1241
+ var gatherProductDecisions = async ({
1242
+ products,
1243
+ curProducts,
1244
+ yes
1245
+ }) => {
1246
+ const productDecisions = /* @__PURE__ */ new Map();
1247
+ const batchCheckProducts = [];
1248
+ for (const product of products) {
1249
+ batchCheckProducts.push(
1250
+ checkProductForConfirmation({
1251
+ curProducts,
1252
+ product
1253
+ })
1254
+ );
1255
+ }
1256
+ const checkProductResults = await Promise.all(batchCheckProducts);
1257
+ for (const result of checkProductResults) {
1258
+ if (result.archived) {
1259
+ const shouldUnarchive = yes || await prompts.confirm({
1260
+ message: `Product ${result.id} is currently archived. Would you like to un-archive it before pushing?`
1261
+ });
1262
+ if (shouldUnarchive) {
1263
+ const s = createSpinner({
1264
+ message: `Un-archiving product [${result.id}]`
1265
+ });
1266
+ await updateProduct({ productId: result.id, update: { archived: false } });
1267
+ s.success(`Product [${result.id}] un-archived successfully!`);
1268
+ productDecisions.set(result.id, true);
1269
+ } else {
1270
+ productDecisions.set(result.id, false);
1271
+ }
1272
+ }
1273
+ if (result.will_version) {
1274
+ const shouldUpdate = yes || await prompts.confirm({
1275
+ message: `Product ${result.id} has customers on it and updating it will create a new version.
1276
+ Are you sure you'd like to continue? `
1277
+ });
1278
+ productDecisions.set(result.id, shouldUpdate);
1279
+ } else {
1280
+ productDecisions.set(result.id, true);
1281
+ }
1282
+ }
1283
+ return productDecisions;
1284
+ };
1285
+ var pushProducts = async ({
1286
+ products,
1287
+ curProducts,
1288
+ productDecisions,
1289
+ yes
1290
+ }) => {
1291
+ const s2 = initSpinner(`Pushing products`);
1292
+ const batchProducts = [];
1293
+ for (const product of products) {
1294
+ const shouldUpdate = productDecisions.get(product.id);
1295
+ batchProducts.push(
1296
+ upsertProduct({ curProducts, product, spinner: s2, shouldUpdate })
1297
+ );
1298
+ }
1299
+ const prodResults = await Promise.all(batchProducts);
1300
+ s2.success(`Products pushed successfully!`);
1301
+ console.log(chalk8__default.default.dim("\nProducts pushed:"));
1302
+ prodResults.forEach((result) => {
1303
+ const action = result.action;
1304
+ console.log(
1305
+ chalk8__default.default.cyan(
1306
+ ` \u2022 ${result.id} ${action === "skipped" ? `(${action})` : ""}`
1307
+ )
1308
+ );
1309
+ });
1310
+ console.log();
1311
+ return prodResults;
1312
+ };
1313
+ var gatherFeatureDeletionDecisions = async ({
1314
+ featuresToDelete,
1315
+ yes
1316
+ }) => {
1317
+ const featureDeletionDecisions = /* @__PURE__ */ new Map();
1318
+ const batchCheckFeatures = [];
1319
+ for (const featureId of featuresToDelete) {
1320
+ batchCheckFeatures.push(checkFeatureDeletionData({ featureId }));
1321
+ }
1322
+ const checkFeatureResults = await Promise.all(batchCheckFeatures);
1323
+ for (let i = 0; i < featuresToDelete.length; i++) {
1324
+ const featureId = featuresToDelete[i];
1325
+ const result = checkFeatureResults[i];
1326
+ if (!featureId) continue;
1327
+ if (result && result.totalCount > 0) {
1328
+ const otherProductsText = result.totalCount > 1 ? ` and ${result.totalCount - 1} other products` : "";
1329
+ const productNameText = result.productName || "Unknown Product";
1330
+ const shouldArchive = yes || await prompts.confirm({
1331
+ message: `Feature ${featureId} is being used by product ${productNameText}${otherProductsText}. As such, you cannot delete it. Would you like to archive the feature instead?`
1332
+ });
1333
+ featureDeletionDecisions.set(
1334
+ featureId,
1335
+ shouldArchive ? "archive" : "skip"
1336
+ );
1337
+ } else {
1338
+ featureDeletionDecisions.set(featureId, "delete");
1339
+ }
1340
+ }
1341
+ return featureDeletionDecisions;
1342
+ };
1343
+ var handleFeatureDeletion = async ({
1344
+ featuresToDelete,
1345
+ yes
1346
+ }) => {
1347
+ const featureDeletionDecisions = await gatherFeatureDeletionDecisions({
1348
+ featuresToDelete,
1349
+ yes
1350
+ });
1351
+ for (const featureId of featuresToDelete) {
1352
+ const decision = featureDeletionDecisions.get(featureId);
1353
+ if (decision === "delete") {
1354
+ const shouldDelete = yes || await prompts.confirm({
1355
+ message: `Delete feature [${featureId}]?`
1356
+ });
1357
+ if (shouldDelete) {
1358
+ const s = createSpinner({ message: `Deleting feature [${featureId}]` });
1359
+ await deleteFeature({ id: featureId });
1360
+ s.success(`Feature [${featureId}] deleted successfully!`);
1361
+ }
1362
+ } else if (decision === "archive") {
1363
+ const s = createSpinner({ message: `Archiving feature [${featureId}]` });
1364
+ await updateFeature({ id: featureId, update: { archived: true } });
1365
+ s.success(`Feature [${featureId}] archived successfully!`);
1366
+ }
1367
+ }
1368
+ };
1369
+ var showSuccessMessage = ({ env, prod }) => {
1370
+ console.log(
1371
+ chalk8__default.default.magentaBright(`Success! Changes have been pushed to ${env}.`)
1372
+ );
1373
+ if (prod) {
1374
+ console.log(
1375
+ chalk8__default.default.magentaBright(
1376
+ `You can view the products at ${FRONTEND_URL}/products`
1377
+ )
1378
+ );
1379
+ } else {
1380
+ console.log(
1381
+ chalk8__default.default.magentaBright(
1382
+ `You can view the products at ${FRONTEND_URL}/sandbox/products`
1383
+ )
1384
+ );
1385
+ }
1386
+ };
1387
+ async function Push({
1388
+ config,
1389
+ yes,
1390
+ prod
1391
+ }) {
1392
+ const { features, products, env } = config;
1393
+ if (env === "prod") {
1394
+ const shouldProceed = yes || await prompts.confirm({
1395
+ message: "You are about to push products to your prod environment. Are you sure you want to proceed?",
1396
+ default: false
1397
+ });
1398
+ if (!shouldProceed) {
1399
+ console.log(chalk8__default.default.yellow("Aborting..."));
1400
+ process.exit(1);
1401
+ }
1402
+ }
1403
+ const { allFeatures, curProducts, featuresToDelete, productsToDelete } = await checkForDeletables(features, products);
1404
+ await handleProductDeletion({ productsToDelete, yes });
1405
+ await pushFeatures({ features, allFeatures, yes });
1406
+ const productDecisions = await gatherProductDecisions({
1407
+ products,
1408
+ curProducts,
1409
+ yes
1410
+ });
1411
+ await pushProducts({ products, curProducts, productDecisions, yes });
1412
+ await handleFeatureDeletion({ featuresToDelete, yes });
1413
+ showSuccessMessage({ env, prod });
1414
+ }
1415
+
1416
+ // source/cli.ts
1417
+ var computedVersion = typeof VERSION !== "undefined" && VERSION ? VERSION : "dev";
1418
+ commander.program.version(computedVersion);
1419
+ commander.program.option("-p, --prod", "Push to production");
1420
+ commander.program.command("env").description("Check the environment of your API key").action(async () => {
1421
+ const env = await isSandboxKey(readFromEnv() ?? "");
1422
+ console.log(chalk8__default.default.green(`Environment: ${env ? "Sandbox" : "Production"}`));
1423
+ });
1424
+ commander.program.command("nuke").description("Permannently nuke your sandbox.").action(async () => {
1425
+ await Nuke();
1426
+ });
1427
+ commander.program.command("push").description("Push changes to Autumn").option("-p, --prod", "Push to production").option("-y, --yes", "Confirm all deletions").action(async (options) => {
1428
+ const config = await loadAutumnConfigFile();
1429
+ await Push({ config, yes: options.yes, prod: options.prod });
1430
+ });
1431
+ commander.program.command("pull").description("Pull changes from Autumn").option("-p, --prod", "Pull from production").action(async (options) => {
1432
+ await Pull({ archived: options.archived ?? false });
1433
+ });
1434
+ commander.program.command("init").description("Initialize an Autumn project.").action(async () => {
1435
+ writeConfig(DEFAULT_CONFIG);
1436
+ await Init();
1437
+ });
1438
+ commander.program.command("login").description("Authenticate with Autumn").option("-p, --prod", "Authenticate with production").action(async () => {
1439
+ await AuthCommand();
1440
+ });
1441
+ commander.program.command("dashboard").description("Open the Autumn dashboard in your browser").action(() => {
1442
+ open__default.default(`${FRONTEND_URL}`);
1443
+ });
1444
+ commander.program.command("version").alias("v").description("Show the version of Autumn").action(() => {
1445
+ console.log(computedVersion);
1446
+ });
1447
+ var originalEmit = process.emitWarning;
1448
+ process.emitWarning = (warning, ...args) => {
1449
+ const msg = typeof warning === "string" ? warning : warning.message;
1450
+ if (msg.includes("url.parse()")) {
1451
+ return;
1452
+ }
1453
+ return originalEmit(warning, ...args);
1454
+ };
1455
+ commander.program.parse();