@seethruhead/cra-payroll 0.3.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/cra-payroll.js +101 -28
  2. package/package.json +2 -2
@@ -69212,8 +69212,8 @@ var init_LaunchOptions = __esm(() => {
69212
69212
 
69213
69213
  // src/cli.ts
69214
69214
  import { parseArgs } from "util";
69215
- import { resolve as resolve6 } from "path";
69216
- import { existsSync as existsSync4, readFileSync as readFileSync4, fstatSync } from "fs";
69215
+ import { resolve as resolve7 } from "path";
69216
+ import { existsSync as existsSync5, readFileSync as readFileSync5, fstatSync } from "fs";
69217
69217
  import { createInterface as createInterface2 } from "readline";
69218
69218
 
69219
69219
  // node_modules/neverthrow/dist/index.cjs.js
@@ -79486,10 +79486,73 @@ ${text.value}
79486
79486
  await session.close();
79487
79487
  return parsed;
79488
79488
  };
79489
+ // src/cache.ts
79490
+ import { resolve as resolve6 } from "path";
79491
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
79492
+ import { createHash } from "crypto";
79493
+ var DEFAULT_CACHE_DIR = resolve6(process.env.HOME || "~", ".config", "cra-payroll", "cache");
79494
+ var ensureDir = (dir) => {
79495
+ if (!existsSync3(dir))
79496
+ mkdirSync(dir, { recursive: true });
79497
+ };
79498
+ var cacheKey = (config2) => {
79499
+ const data = JSON.stringify({
79500
+ province: config2.province,
79501
+ annualSalary: config2.annualSalary,
79502
+ payPeriod: config2.payPeriod,
79503
+ year: config2.year,
79504
+ rrspMatchPercent: config2.rrspMatchPercent,
79505
+ rrspUnmatchedPercent: config2.rrspUnmatchedPercent,
79506
+ cppMaxedOut: config2.cppMaxedOut,
79507
+ eiMaxedOut: config2.eiMaxedOut
79508
+ });
79509
+ return createHash("sha256").update(data).digest("hex").slice(0, 16);
79510
+ };
79511
+ var cachePath = (config2, dir) => resolve6(dir, `${cacheKey(config2)}.json`);
79512
+ var readCache = (config2, dir) => {
79513
+ const path12 = cachePath(config2, dir);
79514
+ if (!existsSync3(path12))
79515
+ return null;
79516
+ try {
79517
+ const data = JSON.parse(readFileSync4(path12, "utf-8"));
79518
+ if (typeof data.grossIncome === "number" && typeof data.net === "number") {
79519
+ return data;
79520
+ }
79521
+ return null;
79522
+ } catch {
79523
+ return null;
79524
+ }
79525
+ };
79526
+ var writeCache = (config2, result, dir) => {
79527
+ try {
79528
+ ensureDir(dir);
79529
+ writeFileSync(cachePath(config2, dir), JSON.stringify(result, null, 2));
79530
+ } catch (e2) {
79531
+ log(`cache write failed: ${e2.message}`);
79532
+ }
79533
+ };
79534
+ var withCache = (inner, cacheDir = DEFAULT_CACHE_DIR) => ({
79535
+ calculate: async (config2, headless) => {
79536
+ const cached = readCache(config2, cacheDir);
79537
+ if (cached) {
79538
+ log(`cache hit: ${cachePath(config2, cacheDir)}`);
79539
+ return $ok(cached);
79540
+ }
79541
+ log(`cache miss, hitting CRA...`);
79542
+ const result = await inner.calculate(config2, headless);
79543
+ if (result.isOk()) {
79544
+ writeCache(config2, result.value, cacheDir);
79545
+ }
79546
+ return result;
79547
+ }
79548
+ });
79549
+
79489
79550
  // src/calculator.ts
79490
- var craService = {
79551
+ var rawService = {
79491
79552
  calculate: calculatePayroll
79492
79553
  };
79554
+ var craService = withCache(rawService);
79555
+ var craServiceNoCache = rawService;
79493
79556
 
79494
79557
  // src/yearly.ts
79495
79558
  var CPP_MAX_BASE = 4230.45;
@@ -79563,11 +79626,11 @@ var calculateYearly = async (service, config2, headless = false) => {
79563
79626
 
79564
79627
  // src/updater.ts
79565
79628
  import { execSync as execSync2 } from "child_process";
79566
- import { existsSync as existsSync3, renameSync, unlinkSync, chmodSync } from "fs";
79629
+ import { existsSync as existsSync4, renameSync, unlinkSync, chmodSync } from "fs";
79567
79630
  // package.json
79568
79631
  var package_default = {
79569
79632
  name: "@seethruhead/cra-payroll",
79570
- version: "0.3.0",
79633
+ version: "0.4.0",
79571
79634
  description: "Calculate Canadian payroll deductions using CRA's Payroll Deductions Online Calculator",
79572
79635
  type: "module",
79573
79636
  bin: {
@@ -79581,7 +79644,7 @@ var package_default = {
79581
79644
  build: "bun build --compile src/cli.ts --outfile cra-payroll --external electron",
79582
79645
  "build:npm": "bun build src/cli.ts --outfile dist/cra-payroll.js --target=node",
79583
79646
  release: "bash release.sh",
79584
- test: "bun test src/unit.test.ts",
79647
+ test: "bun test src/unit.test.ts src/cache.test.ts",
79585
79648
  "test:integration": "bun test src/integration.test.ts --timeout 120000 --max-concurrency 1",
79586
79649
  "test:all": "bun test --timeout 120000 --max-concurrency 1"
79587
79650
  },
@@ -79706,13 +79769,13 @@ var selfUpdate = async () => {
79706
79769
  console.log(`Downloading ${update.downloadUrl}...
79707
79770
  `);
79708
79771
  let binaryPath = "";
79709
- if (process.execPath && existsSync3(process.execPath) && !process.execPath.endsWith("/bun")) {
79772
+ if (process.execPath && existsSync4(process.execPath) && !process.execPath.endsWith("/bun")) {
79710
79773
  binaryPath = process.execPath;
79711
79774
  }
79712
79775
  if (!binaryPath) {
79713
79776
  try {
79714
79777
  const which = execSync2("which cra-payroll", { encoding: "utf-8" }).trim();
79715
- if (which && existsSync3(which))
79778
+ if (which && existsSync4(which))
79716
79779
  binaryPath = which;
79717
79780
  } catch {}
79718
79781
  }
@@ -79764,7 +79827,7 @@ ${line("═", W)}
79764
79827
 
79765
79828
  // src/views/table.ts
79766
79829
  var col = (v, w) => typeof v === "number" ? money(v).padStart(w) : v.padStart(w);
79767
- var renderRow = (r2) => `${r2.label} │ ${col(r2.gross, 10)} │ ${col(r2.fedTax, 10)} │ ${col(r2.provTax, 10)} │ ${col(r2.cpp, 10)} │ ${col(r2.cpp2, 6)} │ ${col(r2.ei, 10)} │ ${col(r2.netPay, 10)}${r2.rrspEmp !== undefined ? ` │ ${col(r2.rrspEmp, 10)} │ ${col(r2.takeHome, 10)}` : ""} │ ${col(r2.cumCppEi, 10)}${r2.suffix ?? ""}`;
79830
+ var renderRow = (r2) => `${r2.label} │ ${col(r2.gross, 10)} │ ${col(r2.fedTax, 10)} │ ${col(r2.provTax, 10)} │ ${col(r2.cpp, 10)} │ ${col(r2.cpp2, 6)} │ ${col(r2.ei, 10)} │ ${col(r2.netPay, 10)}${r2.rrspYou !== undefined ? ` │ ${col(r2.rrspYou, 10)} │ ${col(r2.rrspEr, 10)} │ ${col(r2.takeHome, 10)}` : ""} │ ${col(r2.cumCppEi, 10)}${r2.suffix ?? ""}`;
79768
79831
  var totalEmployeeRrsp = (r2) => r2.rrspMatched + r2.rrspUnmatched;
79769
79832
  var totalEmployeeRrspTotals = (t8) => t8.rrspMatched + t8.rrspUnmatched;
79770
79833
  var showRrsp = (totals) => totals.rrspMatched > 0 || totals.rrspUnmatched > 0 || totals.rrspEmployer > 0;
@@ -79779,7 +79842,7 @@ var toRow = (r2, first2, rrsp) => ({
79779
79842
  cpp2: r2.cpp2,
79780
79843
  ei: r2.ei,
79781
79844
  netPay: r2.netPay,
79782
- ...rrspFields(rrsp, { rrspEmp: totalEmployeeRrsp(r2), takeHome: r2.netPay - totalEmployeeRrsp(r2) }),
79845
+ ...rrspFields(rrsp, { rrspYou: totalEmployeeRrsp(r2), rrspEr: r2.rrspEmployer, takeHome: r2.netPay - totalEmployeeRrsp(r2) }),
79783
79846
  cumCppEi: r2.cumulativeCpp + r2.cumulativeCpp2 + r2.cumulativeEi,
79784
79847
  suffix: annotate(r2, first2)
79785
79848
  });
@@ -79792,7 +79855,7 @@ var headerRow = (rrsp) => ({
79792
79855
  cpp2: "CPP2",
79793
79856
  ei: "EI",
79794
79857
  netPay: "Net Pay",
79795
- ...rrspFields(rrsp, { rrspEmp: "RRSP Emp", takeHome: "Take Home" }),
79858
+ ...rrspFields(rrsp, { rrspYou: "RRSP You", rrspEr: "RRSP Er", takeHome: "Take Home" }),
79796
79859
  cumCppEi: "Cum CPP/EI"
79797
79860
  });
79798
79861
  var totalsRow = (totals, rrsp) => ({
@@ -79804,10 +79867,17 @@ var totalsRow = (totals, rrsp) => ({
79804
79867
  cpp2: totals.cpp2,
79805
79868
  ei: totals.ei,
79806
79869
  netPay: totals.netPay,
79807
- ...rrspFields(rrsp, { rrspEmp: totalEmployeeRrspTotals(totals), takeHome: totals.netPay - totalEmployeeRrspTotals(totals) }),
79870
+ ...rrspFields(rrsp, { rrspYou: totalEmployeeRrspTotals(totals), rrspEr: totals.rrspEmployer, takeHome: totals.netPay - totalEmployeeRrspTotals(totals) }),
79808
79871
  cumCppEi: ""
79809
79872
  });
79810
- var renderTable = (yearly, periodsPerYear, year = 2026) => t3(yearly, (ctx) => ({ ...ctx, rrsp: showRrsp(ctx.totals) }), (ctx) => ({ ...ctx, header: renderRow(headerRow(ctx.rrsp)) }), (ctx) => ({ ...ctx, bodyRows: ctx.rows.map((r2) => renderRow(toRow(r2, ctx.rows[0], ctx.rrsp))) }), (ctx) => ({ ...ctx, totalsStr: renderRow(totalsRow(ctx.totals, ctx.rrsp)), W: ctx.header.length }), (ctx) => `Per-Paycheck Table (${year})
79873
+ var renderTable = (yearly, periodsPerYear, year = 2026) => t3(yearly, (ctx) => ({ ...ctx, rrsp: showRrsp(ctx.totals) }), (ctx) => ({ ...ctx, header: renderRow(headerRow(ctx.rrsp)) }), (ctx) => ({ ...ctx, bodyRows: ctx.rows.map((r2) => renderRow(toRow(r2, ctx.rows[0], ctx.rrsp))) }), (ctx) => ({ ...ctx, totalsStr: renderRow(totalsRow(ctx.totals, ctx.rrsp)), W: ctx.header.length }), (ctx) => {
79874
+ const t8 = ctx.totals;
79875
+ const empTotal = totalEmployeeRrspTotals(t8);
79876
+ const rrspSummary = !ctx.rrsp ? "" : `
79877
+ RRSP You: $${money(empTotal)}/yr ($${money(empTotal / periodsPerYear)}/period)${t8.rrspMatched > 0 ? ` — matched: $${money(t8.rrspMatched)}/yr` : ""}${t8.rrspUnmatched > 0 ? ` — unmatched: $${money(t8.rrspUnmatched)}/yr` : ""}
79878
+ RRSP Er: $${money(t8.rrspEmployer)}/yr ($${money(t8.rrspEmployer / periodsPerYear)}/period)
79879
+ RRSP Total (You + Er): $${money(empTotal + t8.rrspEmployer)}/yr`;
79880
+ return `Per-Paycheck Table (${year})
79811
79881
  ${line("═", ctx.W)}
79812
79882
  ${ctx.header}
79813
79883
  ${line("─", ctx.W)}
@@ -79815,8 +79885,8 @@ ${ctx.bodyRows.join(`
79815
79885
  `)}
79816
79886
  ${line("─", ctx.W)}
79817
79887
  ${ctx.totalsStr}
79818
- ${line("═", ctx.W)}${when(ctx.rrsp, `
79819
- RRSP (Employer match): $${money(ctx.totals.rrspEmployer)}/yr ($${money(ctx.totals.rrspEmployer / periodsPerYear)}/period)`)}`);
79888
+ ${line("═", ctx.W)}${rrspSummary}`;
79889
+ });
79820
79890
 
79821
79891
  // src/views/summary.ts
79822
79892
  var W2 = 42;
@@ -79878,6 +79948,7 @@ var { values } = parseArgs({
79878
79948
  table: { type: "boolean", short: "t", default: false },
79879
79949
  annual: { type: "boolean", short: "a", default: false },
79880
79950
  monthly: { type: "boolean", short: "m", default: false },
79951
+ "no-cache": { type: "boolean", default: false },
79881
79952
  update: { type: "boolean", default: false },
79882
79953
  version: { type: "boolean", default: false },
79883
79954
  headless: { type: "boolean", default: false },
@@ -79908,6 +79979,7 @@ if (values.help) {
79908
79979
  -t, --table Show per-paycheck table for the year (tracks CPP/EI max)
79909
79980
  -a, --annual Show annualized totals
79910
79981
  -m, --monthly Show monthly averages
79982
+ --no-cache Skip cache and force a fresh CRA lookup
79911
79983
  --headless Run browser headless (may be blocked by CRA)
79912
79984
  --update Self-update to the latest release
79913
79985
  --version Show current version
@@ -80002,14 +80074,14 @@ var readStdinConfig = async () => {
80002
80074
  };
80003
80075
  var readFileConfig = (configFlag) => {
80004
80076
  const configPaths = [
80005
- configFlag ? resolve6(configFlag) : "",
80006
- resolve6("config.json"),
80007
- resolve6(process.env.HOME || "~", ".config", "cra-payroll.json"),
80008
- resolve6(process.env.HOME || "~", ".cra-payroll.json")
80077
+ configFlag ? resolve7(configFlag) : "",
80078
+ resolve7("config.json"),
80079
+ resolve7(process.env.HOME || "~", ".config", "cra-payroll.json"),
80080
+ resolve7(process.env.HOME || "~", ".cra-payroll.json")
80009
80081
  ].filter(Boolean);
80010
80082
  for (const p of configPaths) {
80011
- if (p && existsSync4(p)) {
80012
- return JSON.parse(readFileSync4(p, "utf-8"));
80083
+ if (p && existsSync5(p)) {
80084
+ return JSON.parse(readFileSync5(p, "utf-8"));
80013
80085
  }
80014
80086
  }
80015
80087
  return {};
@@ -80025,7 +80097,7 @@ var loadFileConfig = async (configFlag, isPiped) => {
80025
80097
  try {
80026
80098
  const config2 = readFileConfig(configFlag);
80027
80099
  if (configFlag && Object.keys(config2).length === 0) {
80028
- return $err(`Config file not found: ${resolve6(configFlag)}`);
80100
+ return $err(`Config file not found: ${resolve7(configFlag)}`);
80029
80101
  }
80030
80102
  return $ok(config2);
80031
80103
  } catch (e2) {
@@ -80075,8 +80147,8 @@ var resolveConfig = async (vals, fileConfig, isPiped) => {
80075
80147
  eiMaxedOut
80076
80148
  });
80077
80149
  };
80078
- var runYearlyMode = async (config2, headless, flags) => {
80079
- const yearlyResult = await calculateYearly(craService, config2, headless);
80150
+ var runYearlyMode = async (config2, headless, svc, flags) => {
80151
+ const yearlyResult = await calculateYearly(svc, config2, headless);
80080
80152
  if (yearlyResult.isErr()) {
80081
80153
  console.error(`Error: ${yearlyResult.error}`);
80082
80154
  process.exit(1);
@@ -80090,8 +80162,8 @@ var runYearlyMode = async (config2, headless, flags) => {
80090
80162
  if (flags.monthly)
80091
80163
  console.log(renderMonthly(yearly.totals));
80092
80164
  };
80093
- var runSingleMode = async (config2, headless) => {
80094
- const calcResult = await calculatePayroll(config2, headless);
80165
+ var runSingleMode = async (config2, headless, svc) => {
80166
+ const calcResult = await svc.calculate(config2, headless);
80095
80167
  if (calcResult.isErr()) {
80096
80168
  console.error(`Error: ${calcResult.error}`);
80097
80169
  process.exit(1);
@@ -80122,6 +80194,7 @@ if (configResult.isErr()) {
80122
80194
  }
80123
80195
  var config2 = configResult.value;
80124
80196
  var headless = values.headless ?? false;
80197
+ var service = values["no-cache"] ? craServiceNoCache : craService;
80125
80198
  if (values.verbose)
80126
80199
  setVerbose(true);
80127
80200
  var wantTable = values.table ?? false;
@@ -80129,8 +80202,8 @@ var wantAnnual = values.annual ?? false;
80129
80202
  var wantMonthly = values.monthly ?? false;
80130
80203
  console.log(renderConfig(config2, !wantTable && !wantAnnual && !wantMonthly));
80131
80204
  if (wantTable || wantAnnual || wantMonthly) {
80132
- await runYearlyMode(config2, headless, { table: wantTable, annual: wantAnnual, monthly: wantMonthly });
80205
+ await runYearlyMode(config2, headless, service, { table: wantTable, annual: wantAnnual, monthly: wantMonthly });
80133
80206
  } else {
80134
- await runSingleMode(config2, headless);
80207
+ await runSingleMode(config2, headless, service);
80135
80208
  }
80136
80209
  await showUpdateNag();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seethruhead/cra-payroll",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Calculate Canadian payroll deductions using CRA's Payroll Deductions Online Calculator",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "build": "bun build --compile src/cli.ts --outfile cra-payroll --external electron",
15
15
  "build:npm": "bun build src/cli.ts --outfile dist/cra-payroll.js --target=node",
16
16
  "release": "bash release.sh",
17
- "test": "bun test src/unit.test.ts",
17
+ "test": "bun test src/unit.test.ts src/cache.test.ts",
18
18
  "test:integration": "bun test src/integration.test.ts --timeout 120000 --max-concurrency 1",
19
19
  "test:all": "bun test --timeout 120000 --max-concurrency 1"
20
20
  },