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 +1455 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +792 -67707
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/pull.d.ts +2 -2
- package/dist/commands/push.d.ts +1 -6
- package/dist/compose/builders/builderFunctions.d.ts +2 -3
- package/dist/compose/index.d.ts +4 -4
- package/dist/compose/models/composeModels.d.ts +0 -1
- package/dist/constants.d.ts +2 -2
- package/dist/core/api.d.ts +10 -17
- package/dist/core/builders/features.d.ts +2 -0
- package/dist/core/builders/productBuilder.d.ts +0 -4
- package/dist/core/builders/products.d.ts +18 -0
- package/dist/core/config.d.ts +0 -1
- package/dist/core/pull.d.ts +27 -11
- package/dist/core/push.d.ts +4 -36
- package/dist/core/utils.d.ts +2 -3
- package/dist/index.cjs +58 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.ts +184 -1
- package/dist/index.js +3 -37
- package/package.json +3 -3
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();
|