Skip to content

Commit

Permalink
Apply Prettier formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
timrogers committed Jul 29, 2023
1 parent 6903497 commit 73164bc
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 241 deletions.
18 changes: 9 additions & 9 deletions dist/commands/benefits.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import * as commander from "commander";
import Table from "cli-table";
import { actionRunner } from "../utils.js";
import { getAccessToken } from "../config.js";
import { getBenefits } from "../forma.js";
import * as commander from 'commander';
import Table from 'cli-table';
import { actionRunner } from '../utils.js';
import { getAccessToken } from '../config.js';
import { getBenefits } from '../forma.js';
const command = new commander.Command();
command
.name("benefits")
.description("List benefits in your Forma account and their remaining balances")
.option("--access_token <access_token>", "Access token used to authenticate with Forma")
.name('benefits')
.description('List benefits in your Forma account and their remaining balances')
.option('--access_token <access_token>', 'Access token used to authenticate with Forma')
.action(actionRunner(async (opts) => {
const accessToken = opts.accessToken ?? getAccessToken();
if (!accessToken) {
throw new Error("You aren't logged in to Forma. Please run `formanator login` first.");
}
const benefits = await getBenefits(accessToken);
const table = new Table({
head: ["Name", "Remaining Amount"],
head: ['Name', 'Remaining Amount'],
});
for (const benefit of benefits) {
table.push([
Expand Down
20 changes: 10 additions & 10 deletions dist/commands/categories.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import * as commander from "commander";
import Table from "cli-table";
import { actionRunner } from "../utils.js";
import { getAccessToken } from "../config.js";
import { getCategoriesForBenefitName } from "../forma.js";
import * as commander from 'commander';
import Table from 'cli-table';
import { actionRunner } from '../utils.js';
import { getAccessToken } from '../config.js';
import { getCategoriesForBenefitName } from '../forma.js';
const command = new commander.Command();
command
.name("categories")
.description("List categories available for a Forma benefit")
.requiredOption("--benefit <benefit>", "The benefit to list categories for")
.option("--access_token <access_token>", "Access token used to authenticate with Forma")
.name('categories')
.description('List categories available for a Forma benefit')
.requiredOption('--benefit <benefit>', 'The benefit to list categories for')
.option('--access_token <access_token>', 'Access token used to authenticate with Forma')
.action(actionRunner(async (opts) => {
const accessToken = opts.accessToken ?? getAccessToken();
if (!accessToken) {
throw new Error("You aren't logged in to Forma. Please run `formanator login` first.");
}
const categories = await getCategoriesForBenefitName(accessToken, opts.benefit);
const table = new Table({
head: ["Parent Category", "Category"],
head: ['Parent Category', 'Category'],
});
for (const category of categories) {
table.push([
Expand Down
70 changes: 35 additions & 35 deletions dist/commands/claim.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as commander from "commander";
import { existsSync, readFileSync } from "fs";
import { lookup } from "mime-types";
import path from "path";
import { actionRunner } from "../utils.js";
import { getAccessToken } from "../config.js";
import { getCategoriesForBenefitName } from "../forma.js";
import * as commander from 'commander';
import { existsSync, readFileSync } from 'fs';
import { lookup } from 'mime-types';
import path from 'path';
import { actionRunner } from '../utils.js';
import { getAccessToken } from '../config.js';
import { getCategoriesForBenefitName } from '../forma.js';
const command = new commander.Command();
const PURCHASE_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
const isValidPurchaseDate = (purchaseDate) => PURCHASE_DATE_REGEX.test(purchaseDate);
Expand All @@ -13,25 +13,25 @@ const isValidAmount = (amount) => AMOUNT_REGEX.test(amount);
const createClaim = async (opts) => {
const { accessToken, amount, merchant, purchaseDate, description, receiptPath, benefitId, categoryId, subcategoryAlias, subcategoryValue, } = opts;
const formData = new FormData();
formData.append("type", "transaction");
formData.append("is_recurring", "false");
formData.append("amount", amount);
formData.append("transaction_date", purchaseDate);
formData.append("default_employee_wallet_id", benefitId);
formData.append("note", description);
formData.append("category", categoryId);
formData.append("category_alias", "");
formData.append("subcategory", subcategoryValue);
formData.append("subcategory_alias", subcategoryAlias ?? "");
formData.append("reimbursement_vendor", merchant);
formData.append('type', 'transaction');
formData.append('is_recurring', 'false');
formData.append('amount', amount);
formData.append('transaction_date', purchaseDate);
formData.append('default_employee_wallet_id', benefitId);
formData.append('note', description);
formData.append('category', categoryId);
formData.append('category_alias', '');
formData.append('subcategory', subcategoryValue);
formData.append('subcategory_alias', subcategoryAlias ?? '');
formData.append('reimbursement_vendor', merchant);
const receiptData = readFileSync(receiptPath);
const receiptBlob = new Blob([receiptData], { type: lookup(receiptPath) });
const receiptFilename = path.basename(receiptPath);
formData.set("file[]", receiptBlob, receiptFilename);
const response = await fetch("https://api.joinforma.com/client/api/v2/claims?is_mobile=true", {
method: "POST",
formData.set('file[]', receiptBlob, receiptFilename);
const response = await fetch('https://api.joinforma.com/client/api/v2/claims?is_mobile=true', {
method: 'POST',
headers: {
"x-auth-token": accessToken,
'x-auth-token': accessToken,
},
body: formData,
});
Expand All @@ -45,16 +45,16 @@ const createClaim = async (opts) => {
}
};
command
.name("claim")
.description("Submit a claim for a Forma benefit")
.requiredOption("--benefit <benefit>", "The benefit you are claiming for")
.requiredOption("--amount <amount>", "The amount of the claim")
.requiredOption("--merchant <merchant>", "The name of the merchant")
.requiredOption("--category <category>", "The category of the claim")
.requiredOption("--purchase-date <purchase-date>", "The date of the purchase in YYYY-MM-DD format")
.requiredOption("--description <description>", "The description of the claim")
.requiredOption("--receipt-path <receipt-path>", "The path of the receipt. JPEG, PNG, PDF and HEIC files up to 10MB are accepted.")
.option("--access_token <access_token>", "Access token used to authenticate with Forma")
.name('claim')
.description('Submit a claim for a Forma benefit')
.requiredOption('--benefit <benefit>', 'The benefit you are claiming for')
.requiredOption('--amount <amount>', 'The amount of the claim')
.requiredOption('--merchant <merchant>', 'The name of the merchant')
.requiredOption('--category <category>', 'The category of the claim')
.requiredOption('--purchase-date <purchase-date>', 'The date of the purchase in YYYY-MM-DD format')
.requiredOption('--description <description>', 'The description of the claim')
.requiredOption('--receipt-path <receipt-path>', 'The path of the receipt. JPEG, PNG, PDF and HEIC files up to 10MB are accepted.')
.option('--access_token <access_token>', 'Access token used to authenticate with Forma')
.action(actionRunner(async (opts) => {
const accessToken = opts.accessToken ?? getAccessToken();
if (!accessToken) {
Expand All @@ -67,9 +67,9 @@ command
throw new Error(`No category '${opts.category}' found for benefit '${opts.benefit}'.`);
}
if (!isValidPurchaseDate(opts.purchaseDate))
throw new Error("Purchase date must be in YYYY-MM-DD format.");
throw new Error('Purchase date must be in YYYY-MM-DD format.');
if (!isValidAmount(opts.amount))
throw new Error("Amount must be in the format 0.00.");
throw new Error('Amount must be in the format 0.00.');
if (!existsSync(opts.receiptPath))
throw new Error(`Receipt path '${opts.receiptPath}' does not exist.`);
await createClaim({
Expand All @@ -80,6 +80,6 @@ command
subcategoryAlias: matchingCategory.subcategory_alias,
subcategoryValue: matchingCategory.subcategory_value,
});
console.log("Claim submitted successfully.");
console.log('Claim submitted successfully.');
}));
export default command;
58 changes: 29 additions & 29 deletions dist/commands/login.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,65 @@
import * as commander from "commander";
import { actionRunner, prompt } from "../utils.js";
import { setAccessToken } from "../config.js";
import * as commander from 'commander';
import { actionRunner, prompt } from '../utils.js';
import { setAccessToken } from '../config.js';
const command = new commander.Command();
const requestMagicLink = async (email) => {
const response = await fetch("https://api.joinforma.com/client/auth/v2/login/magic?is_mobile=true", {
method: "POST",
const response = await fetch('https://api.joinforma.com/client/auth/v2/login/magic?is_mobile=true', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Error("Unable to request magic link");
throw new Error('Unable to request magic link');
}
const parsedResponse = (await response.json());
if (!parsedResponse.success) {
throw new Error("Unable to request magic link");
throw new Error('Unable to request magic link');
}
};
const isEmailedFormaMagicLink = (emailedMagicLink) => emailedMagicLink.hostname === "joinforma.page.link" &&
emailedMagicLink.protocol === "https:" &&
emailedMagicLink.pathname === "/" &&
emailedMagicLink.searchParams.has("link");
const isEmailedFormaMagicLink = (emailedMagicLink) => emailedMagicLink.hostname === 'joinforma.page.link' &&
emailedMagicLink.protocol === 'https:' &&
emailedMagicLink.pathname === '/' &&
emailedMagicLink.searchParams.has('link');
const exchangeEmailedMagicLinkForToken = async (emailedMagicLink) => {
const parsedEmailedMagicLink = new URL(emailedMagicLink);
if (!isEmailedFormaMagicLink(parsedEmailedMagicLink)) {
throw new Error("The provided link doesn't look like a real Forma magic link.");
}
const urlEncodedMagicLink = parsedEmailedMagicLink.searchParams.get("link");
const urlEncodedMagicLink = parsedEmailedMagicLink.searchParams.get('link');
const realMagicLinkAsString = decodeURIComponent(urlEncodedMagicLink);
const realMagicLink = new URL(realMagicLinkAsString);
const idFromMagicLink = realMagicLink.searchParams.get("id");
const tkFromMagicLink = realMagicLink.searchParams.get("tk");
const idFromMagicLink = realMagicLink.searchParams.get('id');
const tkFromMagicLink = realMagicLink.searchParams.get('tk');
if (!idFromMagicLink || !tkFromMagicLink) {
throw new Error("The provided link doesn't look like a real Forma magic link.");
}
const requestUrl = new URL("https://api.joinforma.com/client/auth/v2/login/magic");
const requestUrl = new URL('https://api.joinforma.com/client/auth/v2/login/magic');
requestUrl.search = new URLSearchParams({
id: idFromMagicLink,
tk: tkFromMagicLink,
return_token: "true",
is_mobile: "true",
return_token: 'true',
is_mobile: 'true',
}).toString();
const response = await fetch(requestUrl);
if (!response.ok) {
throw new Error(`Something went wrong when exchanging the magic link for a token - expected \`200 OK\` response, got \`${response.status} ${response.statusText}\`.`);
}
const parsedResponse = (await response.json());
if (!parsedResponse.success) {
throw new Error("Something went wrong when exchanging the magic link for a token. Received a `200 OK` response, but the response body indicated that the request was not successful");
throw new Error('Something went wrong when exchanging the magic link for a token. Received a `200 OK` response, but the response body indicated that the request was not successful');
}
return parsedResponse.data.auth_token;
};
command
.name("login")
.description("Connect Formanator to your Forma account with a magic link")
.option("--email <email>", "Email address used to log in to Forma")
.option("--magic-link-url <magic_link_url>", "Magic link received by email for logging in to Forma")
.name('login')
.description('Connect Formanator to your Forma account with a magic link')
.option('--email <email>', 'Email address used to log in to Forma')
.option('--magic-link-url <magic_link_url>', 'Magic link received by email for logging in to Forma')
.action(actionRunner(async (opts) => {
if (opts.email && opts.magicLinkUrl) {
throw new Error("You must provide either --email or --magic-link-url, not both");
throw new Error('You must provide either --email or --magic-link-url, not both');
}
if (opts.magicLinkUrl) {
const accessToken = await exchangeEmailedMagicLinkForToken(opts.magicLinkUrl);
Expand All @@ -68,19 +68,19 @@ command
else if (opts.email) {
await requestMagicLink(opts.email);
console.log(`Copy and paste the magic link sent to you at ${opts.email}, then press Enter.`);
const magicLink = prompt("> ");
const magicLink = prompt('> ');
const accessToken = await exchangeEmailedMagicLinkForToken(magicLink);
setAccessToken(accessToken);
}
else {
console.log("Enter the email address you use to log on to Forma, then press Enter.");
const email = prompt("> ");
console.log('Enter the email address you use to log on to Forma, then press Enter.');
const email = prompt('> ');
await requestMagicLink(email);
console.log(`Copy and paste the magic link sent to you at ${email}, then press Enter.`);
const magicLink = prompt("> ");
const magicLink = prompt('> ');
const accessToken = await exchangeEmailedMagicLinkForToken(magicLink);
setAccessToken(accessToken);
}
console.log("You are now logged in! 🥳");
console.log('You are now logged in! 🥳');
}));
export default command;
10 changes: 5 additions & 5 deletions dist/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os from "os";
import path from "path";
import { readFileSync, writeFileSync, existsSync } from "fs";
const CONFIG_FILENAME = ".formanatorrc.json";
import os from 'os';
import path from 'path';
import { readFileSync, writeFileSync, existsSync } from 'fs';
const CONFIG_FILENAME = '.formanatorrc.json';
const CONFIG_PATH = path.join(os.homedir(), CONFIG_FILENAME);
export const maybeGetAccessToken = (maybeAccessToken) => {
if (maybeAccessToken)
Expand All @@ -12,7 +12,7 @@ export const getAccessToken = () => {
if (!existsSync(CONFIG_PATH)) {
return null;
}
const rawConfig = readFileSync(CONFIG_PATH, { encoding: "utf-8" });
const rawConfig = readFileSync(CONFIG_PATH, { encoding: 'utf-8' });
const parsedConfig = JSON.parse(rawConfig);
return parsedConfig.accessToken;
};
Expand Down
4 changes: 2 additions & 2 deletions dist/forma.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export const getCategoriesForBenefitName = async (accessToken, benefitName) => {
});
};
const getProfile = async (accessToken) => {
const response = await fetch("https://api.joinforma.com/client/api/v3/settings/profile?is_mobile=true", {
const response = await fetch('https://api.joinforma.com/client/api/v3/settings/profile?is_mobile=true', {
headers: {
"x-auth-token": accessToken,
'x-auth-token': accessToken,
},
});
if (!response.ok) {
Expand Down
16 changes: 6 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
#!/usr/bin/env npx ts-node --esm
import * as commander from "commander";
import login from "./commands/login.js";
import benefits from "./commands/benefits.js";
import categories from "./commands/categories.js";
import claim from "./commands/claim.js";
import * as commander from 'commander';
import login from './commands/login.js';
import benefits from './commands/benefits.js';
import categories from './commands/categories.js';
import claim from './commands/claim.js';
const program = new commander.Command();
program
.addCommand(login)
.addCommand(benefits)
.addCommand(categories)
.addCommand(claim);
program.addCommand(login).addCommand(benefits).addCommand(categories).addCommand(claim);
program.parse();
2 changes: 1 addition & 1 deletion dist/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import promptSync from "prompt-sync";
import promptSync from 'prompt-sync';
export const prompt = promptSync({ sigint: true });
const actionErrorHandler = (error) => {
console.error(error.message);
Expand Down
23 changes: 9 additions & 14 deletions src/commands/benefits.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as commander from "commander";
import Table from "cli-table";
import * as commander from 'commander';
import Table from 'cli-table';

import { actionRunner } from "../utils.js";
import { getAccessToken } from "../config.js";
import { getBenefits } from "../forma.js";
import { actionRunner } from '../utils.js';
import { getAccessToken } from '../config.js';
import { getBenefits } from '../forma.js';

const command = new commander.Command();

Expand All @@ -12,14 +12,9 @@ interface Arguments {
}

command
.name("benefits")
.description(
"List benefits in your Forma account and their remaining balances",
)
.option(
"--access_token <access_token>",
"Access token used to authenticate with Forma",
)
.name('benefits')
.description('List benefits in your Forma account and their remaining balances')
.option('--access_token <access_token>', 'Access token used to authenticate with Forma')
.action(
actionRunner(async (opts: Arguments) => {
const accessToken = opts.accessToken ?? getAccessToken();
Expand All @@ -33,7 +28,7 @@ command
const benefits = await getBenefits(accessToken);

const table = new Table({
head: ["Name", "Remaining Amount"],
head: ['Name', 'Remaining Amount'],
});

for (const benefit of benefits) {
Expand Down
Loading

0 comments on commit 73164bc

Please sign in to comment.