atmn 0.0.1 → 0.0.2
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/app.d.ts +6 -0
- package/dist/app.js +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +58 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +63 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.js +28 -0
- package/dist/commands/push.d.ts +5 -0
- package/dist/commands/push.js +47 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +55 -0
- package/dist/core/api.d.ts +27 -0
- package/dist/core/api.js +75 -0
- package/dist/core/auth.d.ts +1 -0
- package/dist/core/auth.js +8 -0
- package/dist/core/builders/features.d.ts +2 -0
- package/dist/core/builders/features.js +10 -0
- package/dist/core/builders/products.d.ts +7 -0
- package/dist/core/builders/products.js +71 -0
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +25 -0
- package/dist/core/pull.d.ts +17 -0
- package/dist/core/pull.js +22 -0
- package/dist/core/push.d.ts +7 -0
- package/dist/core/push.js +67 -0
- package/dist/core/utils.d.ts +3 -0
- package/dist/core/utils.js +26 -0
- package/package.json +63 -10
- package/readme.md +25 -0
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import Init from './commands/init.js';
|
|
4
|
+
import { loadAutumnConfigFile } from './core/config.js';
|
|
5
|
+
import Push from './commands/push.js';
|
|
6
|
+
import Pull from './commands/pull.js';
|
|
7
|
+
import AuthCommand from './commands/auth.js';
|
|
8
|
+
import open from 'open';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { writeConfig } from './core/config.js';
|
|
11
|
+
import { FRONTEND_URL } from './constants.js';
|
|
12
|
+
import { DEFAULT_CONFIG } from './constants.js';
|
|
13
|
+
const VERSION = '1.0.0b';
|
|
14
|
+
program
|
|
15
|
+
.command('push')
|
|
16
|
+
.description('Push changes to the remote repository')
|
|
17
|
+
.option('-p, --prod', 'Push to production')
|
|
18
|
+
.option('-y, --yes', 'Confirm all deletions')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const config = await loadAutumnConfigFile();
|
|
21
|
+
await Push({ config, yes: options.yes, prod: options.prod });
|
|
22
|
+
});
|
|
23
|
+
program
|
|
24
|
+
.command('pull')
|
|
25
|
+
.description('Pull changes from Autumn')
|
|
26
|
+
.option('-p, --prod', 'Pull from production')
|
|
27
|
+
.action(async () => {
|
|
28
|
+
const config = await loadAutumnConfigFile();
|
|
29
|
+
await Pull({ config });
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command('init')
|
|
33
|
+
.description('Initialize an Autumn project.')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
writeConfig(DEFAULT_CONFIG); // just write an empty config to make the config file.
|
|
36
|
+
const config = await loadAutumnConfigFile();
|
|
37
|
+
await Init({ config });
|
|
38
|
+
});
|
|
39
|
+
program
|
|
40
|
+
.command('login')
|
|
41
|
+
.description('Authenticate with Autumn')
|
|
42
|
+
.option('-p, --prod', 'Authenticate with production')
|
|
43
|
+
.action(async () => {
|
|
44
|
+
await AuthCommand();
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command('dashboard')
|
|
48
|
+
.description('Open the Autumn dashboard in your browser')
|
|
49
|
+
.action(() => {
|
|
50
|
+
open(`${FRONTEND_URL}`);
|
|
51
|
+
});
|
|
52
|
+
program
|
|
53
|
+
.command('version')
|
|
54
|
+
.description('Show the version of Autumn')
|
|
55
|
+
.action(() => {
|
|
56
|
+
console.log(chalk.green(`Autumn v${VERSION}`));
|
|
57
|
+
});
|
|
58
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function AuthCommand(): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { input, password, confirm } from '@inquirer/prompts';
|
|
4
|
+
import { storeToEnv, readFromEnv } from '../core/utils.js';
|
|
5
|
+
import { getOTP } from '../core/auth.js';
|
|
6
|
+
import { updateCLIStripeKeys } from '../core/api.js';
|
|
7
|
+
import { FRONTEND_URL } from '../constants.js';
|
|
8
|
+
const passwordTheme = {
|
|
9
|
+
style: {
|
|
10
|
+
answer: (text) => {
|
|
11
|
+
return chalk.magenta('*'.repeat(text.length));
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
const inputTheme = {
|
|
16
|
+
style: {
|
|
17
|
+
answer: (text) => {
|
|
18
|
+
return chalk.magenta(text);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export default async function AuthCommand() {
|
|
23
|
+
if (readFromEnv()) {
|
|
24
|
+
let shouldReauth = await confirm({
|
|
25
|
+
message: 'You are already authenticated. Would you like to re-authenticate?',
|
|
26
|
+
theme: inputTheme,
|
|
27
|
+
});
|
|
28
|
+
if (!shouldReauth) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
open(`${FRONTEND_URL}/dev/cli`);
|
|
33
|
+
const otp = await input({
|
|
34
|
+
message: 'Enter OTP:',
|
|
35
|
+
theme: inputTheme,
|
|
36
|
+
});
|
|
37
|
+
const keyInfo = await getOTP(otp);
|
|
38
|
+
if (!keyInfo.stripe_connected) {
|
|
39
|
+
let connectStripe = await confirm({
|
|
40
|
+
message: "It seems like your organization doesn't have any Stripe keys connected. Would you like to connect them now?",
|
|
41
|
+
theme: inputTheme,
|
|
42
|
+
});
|
|
43
|
+
if (connectStripe) {
|
|
44
|
+
// Ask for stripe Keys
|
|
45
|
+
let stripeTestKey = await password({
|
|
46
|
+
message: 'Enter Stripe Test Secret Key:',
|
|
47
|
+
mask: '*',
|
|
48
|
+
theme: passwordTheme,
|
|
49
|
+
});
|
|
50
|
+
// let stripeLiveKey = await password({
|
|
51
|
+
// message: 'Enter Stripe Live Key:',
|
|
52
|
+
// mask: '*',
|
|
53
|
+
// theme: passwordTheme,
|
|
54
|
+
// })
|
|
55
|
+
await updateCLIStripeKeys(stripeTestKey, stripeTestKey, keyInfo.stripeFlowAuthKey);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk.yellow("Okay, no worries. Go to the Autumn dashboard when you're ready!"));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
storeToEnv(keyInfo.prodKey, keyInfo.sandboxKey);
|
|
62
|
+
console.log(chalk.green('Success! Keys have been stored locally. You may now use the CLI.'));
|
|
63
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Pull from './pull.js';
|
|
3
|
+
import AuthCommand from './auth.js';
|
|
4
|
+
import { readFromEnv } from '../core/utils.js';
|
|
5
|
+
export default async function Init({ config }) {
|
|
6
|
+
// Try to read API key from .env first
|
|
7
|
+
let apiKey = readFromEnv();
|
|
8
|
+
if (apiKey) {
|
|
9
|
+
console.log(chalk.green('API key found. Pulling latest config...'));
|
|
10
|
+
await Pull({ config });
|
|
11
|
+
console.log(chalk.green('Project initialized and config pulled successfully!'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// If not found, run authentication
|
|
15
|
+
console.log(chalk.yellow('No API key found. Running authentication...'));
|
|
16
|
+
await AuthCommand();
|
|
17
|
+
// After authentication, try to read the key again
|
|
18
|
+
apiKey = readFromEnv();
|
|
19
|
+
if (apiKey) {
|
|
20
|
+
await Pull({ config });
|
|
21
|
+
console.log(chalk.green('Project initialized! You are now authenticated and config has been pulled.'));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(chalk.red('Authentication did not yield an API key. Please check your setup.'));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getAllProducts, getFeatures, } from '../core/pull.js';
|
|
3
|
+
import { productBuilder } from '../core/builders/products.js';
|
|
4
|
+
import { featureBuilder } from '../core/builders/features.js';
|
|
5
|
+
import { writeConfig } from '../core/config.js';
|
|
6
|
+
import { importBuilder, exportBuilder } from '../core/builders/products.js';
|
|
7
|
+
import { snakeCaseToCamelCase } from '../core/utils.js';
|
|
8
|
+
export default async function Pull({ config }) {
|
|
9
|
+
console.log(chalk.green('Pulling products and features from Autumn...'));
|
|
10
|
+
const products = await getAllProducts();
|
|
11
|
+
const features = await getFeatures();
|
|
12
|
+
const productSnippets = products.map(product => productBuilder(product));
|
|
13
|
+
const featureSnippets = features.map(feature => featureBuilder(feature));
|
|
14
|
+
const autumnConfig = `
|
|
15
|
+
${importBuilder()}
|
|
16
|
+
|
|
17
|
+
// Features
|
|
18
|
+
${featureSnippets.join('\n')}
|
|
19
|
+
|
|
20
|
+
// Products
|
|
21
|
+
${productSnippets.join('\n')}
|
|
22
|
+
|
|
23
|
+
// Remember to update this when you make changes!
|
|
24
|
+
${exportBuilder(products.map(product => product.id), features.map(feature => snakeCaseToCamelCase(feature.id)))}
|
|
25
|
+
`;
|
|
26
|
+
writeConfig(autumnConfig);
|
|
27
|
+
console.log(chalk.green('Success! Config has been updated.'));
|
|
28
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { upsertProduct, checkForDeletables, upsertFeature, } from '../core/push.js';
|
|
4
|
+
import { deleteFeature, deleteProduct } from '../core/api.js';
|
|
5
|
+
import { FRONTEND_URL } from '../constants.js';
|
|
6
|
+
export default async function Push({ config, yes, prod, }) {
|
|
7
|
+
let { features, products } = config;
|
|
8
|
+
let { featuresToDelete, productsToDelete } = await checkForDeletables(features, products);
|
|
9
|
+
for (let productId of productsToDelete) {
|
|
10
|
+
let shouldDelete = yes ||
|
|
11
|
+
(await confirm({
|
|
12
|
+
message: `Delete product [${productId}]?`,
|
|
13
|
+
}));
|
|
14
|
+
if (shouldDelete) {
|
|
15
|
+
await deleteProduct(productId);
|
|
16
|
+
console.log(chalk.green(`Product [${productId}] deleted successfully!`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (let feature of features) {
|
|
20
|
+
console.log(chalk.green(`Pushing feature [${feature.id}]`));
|
|
21
|
+
await upsertFeature(feature);
|
|
22
|
+
console.log(chalk.green(`Pushed feature [${feature.id}]`));
|
|
23
|
+
}
|
|
24
|
+
for (let product of products) {
|
|
25
|
+
console.log(chalk.green(`Pushing product [${product.id}]`));
|
|
26
|
+
await upsertProduct(product);
|
|
27
|
+
console.log(chalk.green(`Pushed product [${product.id}]`));
|
|
28
|
+
}
|
|
29
|
+
for (let featureId of featuresToDelete) {
|
|
30
|
+
let shouldDelete = yes ||
|
|
31
|
+
(await confirm({
|
|
32
|
+
message: `Delete feature [${featureId}]?`,
|
|
33
|
+
}));
|
|
34
|
+
if (shouldDelete) {
|
|
35
|
+
await deleteFeature(featureId);
|
|
36
|
+
console.log(chalk.green(`Feature [${featureId}] deleted successfully!`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const env = prod ? 'prod' : 'sandbox';
|
|
40
|
+
console.log(chalk.magentaBright(`Success! Changes have been pushed to ${env}.`));
|
|
41
|
+
if (prod) {
|
|
42
|
+
console.log(chalk.magentaBright(`You can view the products at ${FRONTEND_URL}/products`));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(chalk.magentaBright(`You can view the products at ${FRONTEND_URL}/sandbox/products`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const FRONTEND_URL = "http://app.useautumn.com";
|
|
2
|
+
export declare const BACKEND_URL = "https://api.useautumn.com";
|
|
3
|
+
export declare const DEFAULT_CONFIG = "import {\n\tfeature,\n\tproduct,\n\tpriceItem,\n\tfeatureItem,\n\tpricedFeatureItem,\n} from 'autumn-js/compose';\n\nconst seats = feature({\n\tid: 'seats',\n\tname: 'Seats',\n\ttype: 'continuous_use',\n});\n\nconst messages = feature({\n\tid: 'messages',\n\tname: 'Messages',\n\ttype: 'single_use',\n});\n\nconst pro = product({\n\tid: 'pro',\n\tname: 'Pro',\n\titems: [\n\t\t// 500 messages per month\n\t\tfeatureItem({\n\t\t\tfeature_id: messages.id,\n\t\t\tincluded_usage: 500,\n\t\t\tinterval: 'month',\n\t\t}),\n\n\t\t// $10 per seat per month\n\t\tpricedFeatureItem({\n\t\t\tfeature_id: seats.id,\n\t\t\tprice: 10,\n\t\t\tinterval: 'month',\n\t\t}),\n\n\t\t// $50 / month\n\t\tpriceItem({\n\t\t\tprice: 50,\n\t\t\tinterval: 'month',\n\t\t}),\n\t],\n});\n\nexport default {\n\tfeatures: [seats, messages],\n\tproducts: [pro],\n};\n";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// export const FRONTEND_URL = 'http://localhost:3000';
|
|
2
|
+
// export const BACKEND_URL = 'http://localhost:8080';
|
|
3
|
+
export const FRONTEND_URL = 'http://app.useautumn.com';
|
|
4
|
+
export const BACKEND_URL = 'https://api.useautumn.com';
|
|
5
|
+
export const DEFAULT_CONFIG = `import {
|
|
6
|
+
feature,
|
|
7
|
+
product,
|
|
8
|
+
priceItem,
|
|
9
|
+
featureItem,
|
|
10
|
+
pricedFeatureItem,
|
|
11
|
+
} from 'autumn-js/compose';
|
|
12
|
+
|
|
13
|
+
const seats = feature({
|
|
14
|
+
id: 'seats',
|
|
15
|
+
name: 'Seats',
|
|
16
|
+
type: 'continuous_use',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const messages = feature({
|
|
20
|
+
id: 'messages',
|
|
21
|
+
name: 'Messages',
|
|
22
|
+
type: 'single_use',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const pro = product({
|
|
26
|
+
id: 'pro',
|
|
27
|
+
name: 'Pro',
|
|
28
|
+
items: [
|
|
29
|
+
// 500 messages per month
|
|
30
|
+
featureItem({
|
|
31
|
+
feature_id: messages.id,
|
|
32
|
+
included_usage: 500,
|
|
33
|
+
interval: 'month',
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// $10 per seat per month
|
|
37
|
+
pricedFeatureItem({
|
|
38
|
+
feature_id: seats.id,
|
|
39
|
+
price: 10,
|
|
40
|
+
interval: 'month',
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
// $50 / month
|
|
44
|
+
priceItem({
|
|
45
|
+
price: 50,
|
|
46
|
+
interval: 'month',
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
features: [seats, messages],
|
|
53
|
+
products: [pro],
|
|
54
|
+
};
|
|
55
|
+
`;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function request({ method, base, path, data, headers, customAuth, throwOnError, }: {
|
|
2
|
+
method: string;
|
|
3
|
+
base: string;
|
|
4
|
+
path: string;
|
|
5
|
+
data?: any;
|
|
6
|
+
headers?: any;
|
|
7
|
+
customAuth?: string;
|
|
8
|
+
throwOnError?: boolean;
|
|
9
|
+
}): Promise<any>;
|
|
10
|
+
export declare function internalRequest({ method, path, data, headers, customAuth, }: {
|
|
11
|
+
method: string;
|
|
12
|
+
path: string;
|
|
13
|
+
data?: any;
|
|
14
|
+
headers?: any;
|
|
15
|
+
customAuth?: string;
|
|
16
|
+
}): Promise<any>;
|
|
17
|
+
export declare function externalRequest({ method, path, data, headers, customAuth, throwOnError, }: {
|
|
18
|
+
method: string;
|
|
19
|
+
path: string;
|
|
20
|
+
data?: any;
|
|
21
|
+
headers?: any;
|
|
22
|
+
customAuth?: string;
|
|
23
|
+
throwOnError?: boolean;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
export declare function deleteFeature(id: string): Promise<any>;
|
|
26
|
+
export declare function deleteProduct(id: string): Promise<any>;
|
|
27
|
+
export declare function updateCLIStripeKeys(stripeTestKey: string, stripeLiveKey: string, stripeFlowAuthKey: string): Promise<any>;
|
package/dist/core/api.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { BACKEND_URL } from '../constants.js';
|
|
4
|
+
import { readFromEnv } from './utils.js';
|
|
5
|
+
const INTERNAL_BASE = BACKEND_URL;
|
|
6
|
+
const EXTERNAL_BASE = `${BACKEND_URL}/v1`;
|
|
7
|
+
export async function request({ method, base, path, data, headers, customAuth, throwOnError = true, }) {
|
|
8
|
+
const apiKey = readFromEnv();
|
|
9
|
+
try {
|
|
10
|
+
const response = await axios.request({
|
|
11
|
+
method,
|
|
12
|
+
url: `${base}${path}`,
|
|
13
|
+
data,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
...headers,
|
|
17
|
+
Authorization: customAuth || `Bearer ${apiKey}`,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (throwOnError) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
console.error(chalk.red('Error occured when making API request:'), chalk.red(error.response.data.message || error.response.data.error));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function internalRequest({ method, path, data, headers, customAuth, }) {
|
|
31
|
+
return await request({
|
|
32
|
+
method,
|
|
33
|
+
base: INTERNAL_BASE,
|
|
34
|
+
path,
|
|
35
|
+
data,
|
|
36
|
+
headers,
|
|
37
|
+
customAuth,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function externalRequest({ method, path, data, headers, customAuth, throwOnError = false, }) {
|
|
41
|
+
return await request({
|
|
42
|
+
method,
|
|
43
|
+
base: EXTERNAL_BASE,
|
|
44
|
+
path,
|
|
45
|
+
data,
|
|
46
|
+
headers,
|
|
47
|
+
customAuth,
|
|
48
|
+
throwOnError,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function deleteFeature(id) {
|
|
52
|
+
return await externalRequest({
|
|
53
|
+
method: 'DELETE',
|
|
54
|
+
path: `/features/${id}`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export async function deleteProduct(id) {
|
|
58
|
+
return await externalRequest({
|
|
59
|
+
method: 'DELETE',
|
|
60
|
+
path: `/products/${id}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export async function updateCLIStripeKeys(stripeTestKey, stripeLiveKey, stripeFlowAuthKey) {
|
|
64
|
+
return await internalRequest({
|
|
65
|
+
method: 'POST',
|
|
66
|
+
path: '/dev/cli/stripe',
|
|
67
|
+
data: {
|
|
68
|
+
stripeTestKey,
|
|
69
|
+
stripeLiveKey,
|
|
70
|
+
successUrl: 'https://useautumn.com',
|
|
71
|
+
defaultCurrency: 'usd',
|
|
72
|
+
},
|
|
73
|
+
customAuth: stripeFlowAuthKey,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getOTP(otp: string): Promise<any>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { snakeCaseToCamelCase } from '../utils.js';
|
|
2
|
+
export function featureBuilder(feature) {
|
|
3
|
+
const snippet = `
|
|
4
|
+
export const ${snakeCaseToCamelCase(feature.id)} = feature({
|
|
5
|
+
id: '${feature.id}',
|
|
6
|
+
name: '${feature.name}',
|
|
7
|
+
type: '${feature.type}',
|
|
8
|
+
})`;
|
|
9
|
+
return snippet;
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ProductItem, Product } from 'autumn-js/compose';
|
|
2
|
+
export declare function importBuilder(): string;
|
|
3
|
+
export declare function exportBuilder(productIds: string[], featureIds: string[]): string;
|
|
4
|
+
export declare function productBuilder(product: Product): string;
|
|
5
|
+
export declare function pricedFeatureItemBuilder(item: ProductItem): string;
|
|
6
|
+
export declare function featureItemBuilder(item: ProductItem): string;
|
|
7
|
+
export declare function priceItemBuilder(item: ProductItem): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { snakeCaseToCamelCase } from '../utils.js';
|
|
2
|
+
const ItemBuilders = {
|
|
3
|
+
priced_feature: pricedFeatureItemBuilder,
|
|
4
|
+
feature: featureItemBuilder,
|
|
5
|
+
price: priceItemBuilder,
|
|
6
|
+
};
|
|
7
|
+
export function importBuilder() {
|
|
8
|
+
return `
|
|
9
|
+
import {
|
|
10
|
+
feature,
|
|
11
|
+
product,
|
|
12
|
+
featureItem,
|
|
13
|
+
pricedFeatureItem,
|
|
14
|
+
priceItem,
|
|
15
|
+
} from 'autumn-js/compose';
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
export function exportBuilder(productIds, featureIds) {
|
|
19
|
+
const snippet = `
|
|
20
|
+
export default {
|
|
21
|
+
products: [${productIds.map(id => `${id}Plan`).join(', ')}],
|
|
22
|
+
features: [${featureIds.map(id => `${id}`).join(', ')}]
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
return snippet;
|
|
26
|
+
}
|
|
27
|
+
export function productBuilder(product) {
|
|
28
|
+
const snippet = `
|
|
29
|
+
export const ${product.id}Plan = product({
|
|
30
|
+
id: '${product.id}',
|
|
31
|
+
name: '${product.name}',
|
|
32
|
+
items: [${product.items
|
|
33
|
+
.map((item) => `${ItemBuilders[item.type](item)}`)
|
|
34
|
+
.join(' ')} ]
|
|
35
|
+
})
|
|
36
|
+
`;
|
|
37
|
+
return snippet;
|
|
38
|
+
}
|
|
39
|
+
// Item Builders
|
|
40
|
+
export function pricedFeatureItemBuilder(item) {
|
|
41
|
+
const intervalLine = item.interval == null ? '' : `\n interval: '${item.interval}',`;
|
|
42
|
+
const snippet = `
|
|
43
|
+
pricedFeatureItem({
|
|
44
|
+
feature_id: ${snakeCaseToCamelCase(item.feature_id)}.id,
|
|
45
|
+
price: ${item.price},${intervalLine}
|
|
46
|
+
included_usage: ${item.included_usage},
|
|
47
|
+
billing_units: ${item.billing_units},
|
|
48
|
+
usage_model: '${item.usage_model}',
|
|
49
|
+
}),
|
|
50
|
+
`;
|
|
51
|
+
return snippet;
|
|
52
|
+
}
|
|
53
|
+
export function featureItemBuilder(item) {
|
|
54
|
+
const intervalLine = item.interval == null ? '' : `\n interval: '${item.interval}',`;
|
|
55
|
+
const snippet = `
|
|
56
|
+
featureItem({
|
|
57
|
+
feature_id: ${snakeCaseToCamelCase(item.feature_id)}.id,
|
|
58
|
+
included_usage: ${item.included_usage},${intervalLine}
|
|
59
|
+
}),
|
|
60
|
+
`;
|
|
61
|
+
return snippet;
|
|
62
|
+
}
|
|
63
|
+
export function priceItemBuilder(item) {
|
|
64
|
+
const intervalLine = item.interval == null ? '' : `\n interval: '${item.interval}',`;
|
|
65
|
+
const snippet = `
|
|
66
|
+
priceItem({
|
|
67
|
+
price: ${item.price},${intervalLine}
|
|
68
|
+
}),
|
|
69
|
+
`;
|
|
70
|
+
return snippet;
|
|
71
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import createJiti from 'jiti';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
export async function loadAutumnConfigFile() {
|
|
7
|
+
const configPath = path.join(process.cwd(), 'autumn.config.ts');
|
|
8
|
+
const absolutePath = resolve(configPath);
|
|
9
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
10
|
+
// Dynamic import the TypeScript config file
|
|
11
|
+
const jiti = createJiti(import.meta.url);
|
|
12
|
+
const mod = await jiti.import(fileUrl);
|
|
13
|
+
const def = mod.default || mod;
|
|
14
|
+
if (!def.products || !Array.isArray(def.products)) {
|
|
15
|
+
throw new Error("You must export a products field that is an array of products.");
|
|
16
|
+
}
|
|
17
|
+
if (!def.features || !Array.isArray(def.features)) {
|
|
18
|
+
throw new Error("You must export a features field that is an array of products.");
|
|
19
|
+
}
|
|
20
|
+
return def;
|
|
21
|
+
}
|
|
22
|
+
export function writeConfig(config) {
|
|
23
|
+
const configPath = path.join(process.cwd(), 'autumn.config.ts');
|
|
24
|
+
fs.writeFileSync(configPath, config);
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function getProducts(ids: string[]): Promise<{
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
items: {
|
|
5
|
+
type?: "feature" | "priced_feature" | null | undefined;
|
|
6
|
+
feature_id?: string | null | undefined;
|
|
7
|
+
included_usage?: number | "inf" | null | undefined;
|
|
8
|
+
interval?: "minute" | "hour" | "day" | "week" | "month" | "quarter" | "semi_annual" | "year" | null | undefined;
|
|
9
|
+
usage_model?: "prepaid" | "pay_per_use" | null | undefined;
|
|
10
|
+
price?: number | null | undefined;
|
|
11
|
+
billing_units?: number | null | undefined;
|
|
12
|
+
}[];
|
|
13
|
+
is_add_on?: boolean | undefined;
|
|
14
|
+
is_default?: boolean | undefined;
|
|
15
|
+
}[]>;
|
|
16
|
+
export declare function getAllProducts(): Promise<any>;
|
|
17
|
+
export declare function getFeatures(): Promise<any>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { externalRequest } from './api.js';
|
|
2
|
+
export async function getProducts(ids) {
|
|
3
|
+
const products = await Promise.all(ids.map(id => externalRequest({
|
|
4
|
+
method: 'GET',
|
|
5
|
+
path: `/products/${id}`,
|
|
6
|
+
})));
|
|
7
|
+
return products.map(product => product);
|
|
8
|
+
}
|
|
9
|
+
export async function getAllProducts() {
|
|
10
|
+
const { list } = await externalRequest({
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/products',
|
|
13
|
+
});
|
|
14
|
+
return list.map(product => product);
|
|
15
|
+
}
|
|
16
|
+
export async function getFeatures() {
|
|
17
|
+
const { list } = await externalRequest({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
path: '/features',
|
|
20
|
+
});
|
|
21
|
+
return list.map(feature => feature);
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Feature, Product } from 'autumn-js/compose';
|
|
2
|
+
export declare function checkForDeletables(currentFeatures: Feature[], currentProducts: Product[]): Promise<{
|
|
3
|
+
featuresToDelete: any;
|
|
4
|
+
productsToDelete: any;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function upsertFeature(feature: Feature): Promise<any>;
|
|
7
|
+
export declare function upsertProduct(product: Product): Promise<any>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { externalRequest } from './api.js';
|
|
2
|
+
import { getFeatures, getAllProducts } from './pull.js';
|
|
3
|
+
export async function checkForDeletables(currentFeatures, currentProducts) {
|
|
4
|
+
const features = await getFeatures(); // Get from AUTUMN
|
|
5
|
+
const featureIds = features.map(feature => feature.id);
|
|
6
|
+
const currentFeatureIds = currentFeatures.map(feature => feature.id);
|
|
7
|
+
const featuresToDelete = featureIds.filter(featureId => !currentFeatureIds.includes(featureId));
|
|
8
|
+
const products = await getAllProducts();
|
|
9
|
+
const productIds = products.map(product => product.id);
|
|
10
|
+
const currentProductIds = currentProducts.map(product => product.id);
|
|
11
|
+
const productsToDelete = productIds.filter(productId => !currentProductIds.includes(productId));
|
|
12
|
+
return { featuresToDelete, productsToDelete };
|
|
13
|
+
}
|
|
14
|
+
export async function upsertFeature(feature) {
|
|
15
|
+
try {
|
|
16
|
+
const response = await externalRequest({
|
|
17
|
+
method: 'POST',
|
|
18
|
+
path: `/features`,
|
|
19
|
+
data: feature,
|
|
20
|
+
throwOnError: true,
|
|
21
|
+
// data: {
|
|
22
|
+
// ...feature,
|
|
23
|
+
// config: {
|
|
24
|
+
// filters: [{property: '', operator: '', value: []}],
|
|
25
|
+
// usage_type: 'single_use',
|
|
26
|
+
// },
|
|
27
|
+
// },
|
|
28
|
+
});
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// If the first request fails, try posting to the specific feature ID endpoint
|
|
33
|
+
const response = await externalRequest({
|
|
34
|
+
method: 'POST',
|
|
35
|
+
path: `/features/${feature.id}`,
|
|
36
|
+
data: feature,
|
|
37
|
+
// data: {
|
|
38
|
+
// ...feature,
|
|
39
|
+
// config: {
|
|
40
|
+
// filters: [{property: '', operator: '', value: []}],
|
|
41
|
+
// usage_type: 'single_use',
|
|
42
|
+
// },
|
|
43
|
+
// },
|
|
44
|
+
});
|
|
45
|
+
return response.data;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function upsertProduct(product) {
|
|
49
|
+
try {
|
|
50
|
+
const response = await externalRequest({
|
|
51
|
+
method: 'POST',
|
|
52
|
+
path: `/products`,
|
|
53
|
+
data: product,
|
|
54
|
+
throwOnError: true,
|
|
55
|
+
});
|
|
56
|
+
return response.data;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// If the first request fails, try posting to the specific product ID endpoint
|
|
60
|
+
const response = await externalRequest({
|
|
61
|
+
method: 'POST',
|
|
62
|
+
path: `/products/${product.id}`,
|
|
63
|
+
data: product,
|
|
64
|
+
});
|
|
65
|
+
return response.data;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export function snakeCaseToCamelCase(value) {
|
|
4
|
+
return value.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
5
|
+
}
|
|
6
|
+
export function storeToEnv(prodKey, sandboxKey) {
|
|
7
|
+
const envPath = `${process.cwd()}/.env`;
|
|
8
|
+
const envVars = `# AUTUMN_SECRET_KEY=${prodKey}\nAUTUMN_SECRET_KEY=${sandboxKey}\n`;
|
|
9
|
+
if (fs.existsSync(envPath)) {
|
|
10
|
+
fs.appendFileSync(envPath, envVars);
|
|
11
|
+
console.log(chalk.green('.env file found. Appended keys.'));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
fs.writeFileSync(envPath, envVars);
|
|
15
|
+
console.log(chalk.green('.env file not found. Created new .env file and wrote keys.'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function readFromEnv() {
|
|
19
|
+
const envPath = `${process.cwd()}/.env`;
|
|
20
|
+
if (!fs.existsSync(envPath)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
24
|
+
const match = envContent.match(/^AUTUMN_SECRET_KEY=(.*)$/m);
|
|
25
|
+
return match ? match[1] : undefined;
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
"name": "atmn",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"bin": "dist/cli.js",
|
|
6
|
+
"main": "dist/cli.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=16"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"test2": "prettier --check . && xo && ava",
|
|
15
|
+
"test": "node ./dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"package.json",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@inquirer/prompts": "^7.6.0",
|
|
24
|
+
"autumn-js": "workspace:*",
|
|
25
|
+
"axios": "^1.10.0",
|
|
26
|
+
"commander": "^14.0.0",
|
|
27
|
+
"dotenv": "^17.2.0",
|
|
28
|
+
"inquirer": "^12.7.0",
|
|
29
|
+
"jiti": "^2.4.2",
|
|
30
|
+
"open": "^10.1.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@sindresorhus/tsconfig": "^3.0.1",
|
|
34
|
+
"@types/node": "^24.0.10",
|
|
35
|
+
"@types/react": "^18.0.32",
|
|
36
|
+
"@vdemedes/prettier-config": "^2.0.1",
|
|
37
|
+
"ava": "^5.2.0",
|
|
38
|
+
"chalk": "^5.2.0",
|
|
39
|
+
"eslint-config-xo-react": "^0.27.0",
|
|
40
|
+
"eslint-plugin-react": "^7.32.2",
|
|
41
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
42
|
+
"ink-testing-library": "^3.0.0",
|
|
43
|
+
"prettier": "^2.8.7",
|
|
44
|
+
"ts-node": "^10.9.1",
|
|
45
|
+
"typescript": "^5.0.3",
|
|
46
|
+
"xo": "^0.53.1"
|
|
47
|
+
},
|
|
48
|
+
"ava": {
|
|
49
|
+
"extensions": {
|
|
50
|
+
"ts": "module",
|
|
51
|
+
"tsx": "module"
|
|
52
|
+
},
|
|
53
|
+
"nodeArguments": [
|
|
54
|
+
"--loader=ts-node/esm"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"xo": {
|
|
58
|
+
"extends": "xo-react",
|
|
59
|
+
"prettier": true,
|
|
60
|
+
"rules": {
|
|
61
|
+
"react/prop-types": "off"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"prettier": "@vdemedes/prettier-config"
|
|
12
65
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# cli
|
|
2
|
+
|
|
3
|
+
> This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app)
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
$ npm install --global cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ cli --help
|
|
15
|
+
|
|
16
|
+
Usage
|
|
17
|
+
$ cli
|
|
18
|
+
|
|
19
|
+
Options
|
|
20
|
+
--name Your name
|
|
21
|
+
|
|
22
|
+
Examples
|
|
23
|
+
$ cli --name=Jane
|
|
24
|
+
Hello, Jane
|
|
25
|
+
```
|