Skip to content

Commit 2bbcddc

Browse files
AndreiAndrei
authored andcommitted
new: invoices command
1 parent 82dde97 commit 2bbcddc

File tree

6 files changed

+102
-25
lines changed

6 files changed

+102
-25
lines changed

bun.lockb

36.8 KB
Binary file not shown.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"fs-extra": "^11.2.0",
2121
"inquirer": "^12.0.0",
2222
"minimist": "^1.2.8",
23-
"open": "^10.1.0"
23+
"open": "^10.1.0",
24+
"puppeteer": "^23.5.3"
2425
}
2526
}

src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Command } from "commander";
33
import logger from "./utils/logger";
44
import Invoice from "./utils/invoice";
55
import minimist from "minimist"
6-
import { initConfig, openConfigDirectory, openConfigurationFile, timeout } from "./utils";
6+
import { initConfig, openConfigDirectory, openConfigurationFile, openInvoicesDirectory } from "./utils";
77
import * as os from "os"
88
import inquirer from "inquirer";
99

@@ -84,6 +84,11 @@ const commands = [
8484
description: "Open the configuration directory",
8585
action: openConfigDirectory
8686
},
87+
{
88+
name: "invoices",
89+
description: "Open the configuration directory",
90+
action: openInvoicesDirectory
91+
},
8792
{
8893
name: "defaults",
8994
description: "Setup your default details for creating invoices.",

src/utils/html.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ import logger from "./logger"
22
import path from "path"
33
import fs from "fs-extra"
44
import { load, type CheerioAPI } from "cheerio"
5+
import { DEFAULT_PATH } from ".."
6+
import { deepFindByKey } from "."
7+
import puppeteer from 'puppeteer'
8+
59

610
class HTML {
7-
private requiredIds: string[] = ["wow"]
11+
private requiredIds: string[] = ["createdAt", "fullName", "sortCode", "bankNum"]
812

913
create(path: string) {
1014
return this.parser(path)
1115
}
1216

1317
async parser(filePath: string): Promise<boolean> {
1418
try {
15-
if (path.extname(filePath) !== '.html')
19+
if (path.extname(filePath) !== '.html')
1620
throw new Error('file type')
1721

1822
const fileExists = await fs.pathExists(filePath);
19-
if (!fileExists)
23+
if (!fileExists)
2024
throw new Error('file non-existant')
2125

2226
const htmlContent = await fs.readFile(filePath, 'utf-8');
@@ -34,17 +38,43 @@ class HTML {
3438
logger.debug().success('All required IDs are present in the HTML.');
3539
}
3640

37-
return true
41+
return true
3842
} catch (error) {
3943
logger.debug().error(`An error occured: ${error}`)
4044
return false
4145
}
4246
}
4347

4448
async editor($: CheerioAPI) {
49+
const config = fs.readJsonSync(DEFAULT_PATH)
50+
for (const id of this.requiredIds) {
51+
const value = deepFindByKey(config, id)
52+
if (!value) {
53+
logger.error(`No value was found for ${id}`);
54+
process.exit(0)
55+
}
56+
57+
$(`#${id}`).text(value)
58+
}
4559

60+
const html = $.html()
61+
await this.convertToPdf(html, config.invoices_path)
4662
}
4763

64+
async convertToPdf(html: string, outputPath: string) {
65+
const browser = await puppeteer.launch()
66+
const page = await browser.newPage()
67+
68+
await page.setContent(html, { waitUntil: 'networkidle0' })
69+
70+
await page.pdf({
71+
path: outputPath,
72+
format: "A4",
73+
printBackground: true
74+
})
75+
76+
await browser.close()
77+
}
4878
}
4979

5080
export default HTML

src/utils/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ export async function openConfigurationFile() {
2424
})
2525
}
2626

27+
export async function openInvoicesDirectory() {
28+
const config = fs.readJsonSync(DEFAULT_PATH)
29+
try {
30+
await open(config.invoices_path);
31+
logger.info(`Opened configuration directory: ${config.invoices_path}`);
32+
} catch (error) {
33+
logger.error("Error opening configuration directory")
34+
}
35+
36+
process.exit(0)
37+
}
38+
2739
export async function openConfigDirectory() {
2840
const configDir = path.dirname(DEFAULT_PATH);
2941
try {
@@ -105,4 +117,29 @@ export function configSetup(configPath: string) {
105117
return fs.existsSync(configPath);
106118
}
107119

120+
export function deepFindByKey(obj: any, keyToFind: string) {
121+
if (typeof obj !== 'object' || obj === null) {
122+
return undefined;
123+
}
124+
125+
// Check if the current object contains the key
126+
if (obj.hasOwnProperty(keyToFind)) {
127+
return obj[keyToFind];
128+
}
129+
130+
// Recursively search in each nested object
131+
for (const key in obj) {
132+
if (obj.hasOwnProperty(key)) {
133+
const value = obj[key];
134+
135+
if (typeof value === 'object') {
136+
const result : any = deepFindByKey(value, keyToFind);
137+
if (result !== undefined) {
138+
return result;
139+
}
140+
}
141+
}
142+
}
108143

144+
return undefined;
145+
}

src/utils/invoice.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,28 @@ interface DefaultOption<T extends 'input' | 'number' = 'input'> {
99
type: T
1010
message: string
1111
validate: (value: T extends 'number' ? number : string) => void
12-
}
12+
}
1313

1414
interface DefaultOptionPrompts {
1515
sortCode: DefaultOption
16-
bankNum: DefaultOption<"number">
16+
bankNum: DefaultOption<"number">
1717
fullName: DefaultOption
1818
}
1919

2020
class Invoice {
2121

22-
defaultOptionPrompts : DefaultOptionPrompts = {
23-
sortCode: {
22+
defaultOptionPrompts: DefaultOptionPrompts = {
23+
sortCode: {
2424
type: "input",
2525
message: "Enter your sort code",
2626
validate: val => val.length == 8 && val.includes('-') && !isNaN(parseInt(val.replaceAll('-', '')))
2727
},
28-
bankNum: {
28+
bankNum: {
2929
type: "number",
3030
message: "Enter your bank account number",
3131
validate: num => num.toString().length == 8
3232
},
33-
fullName: {
33+
fullName: {
3434
type: "input",
3535
message: "Enter your fullname",
3636
validate: str => str.includes(' ') && str.length > 5
@@ -130,7 +130,7 @@ class Invoice {
130130
}
131131

132132
async askForDefault(key: keyof DefaultOptionPrompts): Promise<string> {
133-
// @ts-ignore
133+
// @ts-ignore
134134
const value = await inquirer.prompt([
135135
{
136136
name: key,
@@ -145,32 +145,36 @@ class Invoice {
145145

146146

147147
async setupDefaultValues(args?: string[]) {
148-
149148
logger.debug().info(`Args: ${JSON.stringify(args)}`)
150149

151150
const config = fs.readJsonSync(DEFAULT_PATH)
152151

153152
if (args?.length) {
154-
args.forEach(async arg => {
155-
const key = Object.keys(this.defaultOptionPrompts).find(o => o.toLowerCase() == arg)
156-
if (!key) return logger.error(`Can't edit a value that doesn't exist: ${arg}`)
157-
158-
logger.info(key)
153+
for (const arg of args) {
154+
const key = Object.keys(this.defaultOptionPrompts).find(o => o.toLowerCase() === arg)
155+
if (!key) {
156+
logger.error(`Can't edit a value that doesn't exist: ${arg}`)
157+
continue
158+
}
159159

160160
config['default_values'] = {
161+
...config['default_values'], // Preserve existing default values
161162
[key]: await this.askForDefault(key as keyof DefaultOptionPrompts)
162163
}
163-
})
164-
}
165-
else {
164+
}
165+
166+
fs.writeJsonSync(DEFAULT_PATH, config, { spaces: 2 })
167+
process.exit(0)
168+
} else {
166169
config['default_values'] = {
167170
fullName: await this.askForDefault('fullName'),
168171
bankNum: await this.askForDefault('bankNum'),
169-
sortCode: await this.askForDefault('sortCode')
172+
sortCode: await this.askForDefault('sortCode')
170173
}
171-
}
172174

173-
fs.writeJsonSync(DEFAULT_PATH, config, { spaces: 2 })
175+
fs.writeJsonSync(DEFAULT_PATH, config, { spaces: 2 })
176+
process.exit(0)
177+
}
174178
}
175179
}
176180

0 commit comments

Comments
 (0)