@seethruhead/cra-payroll 0.5.2 → 0.6.1
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/cra-payroll.js +113 -36
- package/package.json +2 -2
package/dist/cra-payroll.js
CHANGED
|
@@ -79630,7 +79630,7 @@ import { existsSync as existsSync4, renameSync, unlinkSync, chmodSync } from "fs
|
|
|
79630
79630
|
// package.json
|
|
79631
79631
|
var package_default = {
|
|
79632
79632
|
name: "@seethruhead/cra-payroll",
|
|
79633
|
-
version: "0.
|
|
79633
|
+
version: "0.6.1",
|
|
79634
79634
|
description: "Calculate Canadian payroll deductions using CRA's Payroll Deductions Online Calculator",
|
|
79635
79635
|
type: "module",
|
|
79636
79636
|
bin: {
|
|
@@ -79644,7 +79644,7 @@ var package_default = {
|
|
|
79644
79644
|
build: "bun build --compile src/cli.ts --outfile cra-payroll --external electron",
|
|
79645
79645
|
"build:npm": "bun build src/cli.ts --outfile dist/cra-payroll.js --target=node",
|
|
79646
79646
|
release: "bash release.sh",
|
|
79647
|
-
test: "bun test src/unit.test.ts src/cache.test.ts src/monthly.test.ts src/views.test.ts",
|
|
79647
|
+
test: "bun test src/unit.test.ts src/cache.test.ts src/monthly.test.ts src/views.test.ts src/brackets.test.ts",
|
|
79648
79648
|
"test:integration": "bun test src/integration.test.ts --timeout 120000 --max-concurrency 1",
|
|
79649
79649
|
"test:all": "bun test --timeout 120000 --max-concurrency 1"
|
|
79650
79650
|
},
|
|
@@ -79707,7 +79707,7 @@ var checkForUpdate = async () => {
|
|
|
79707
79707
|
const data = await resp.json();
|
|
79708
79708
|
const latestTag = data.tag_name;
|
|
79709
79709
|
const latestVersion = latestTag.replace(/^v/, "");
|
|
79710
|
-
if (!isNewer(latestVersion,
|
|
79710
|
+
if (!isNewer(latestVersion, currentVersion()))
|
|
79711
79711
|
return $ok(null);
|
|
79712
79712
|
const target = getTarget();
|
|
79713
79713
|
if (target.isErr())
|
|
@@ -79755,7 +79755,7 @@ Need sudo to replace binary...`);
|
|
|
79755
79755
|
}
|
|
79756
79756
|
};
|
|
79757
79757
|
var selfUpdate = async () => {
|
|
79758
|
-
console.log(`Current version: v${
|
|
79758
|
+
console.log(`Current version: v${currentVersion()}`);
|
|
79759
79759
|
console.log(`Checking for updates...
|
|
79760
79760
|
`);
|
|
79761
79761
|
const updateResult = await checkForUpdate();
|
|
@@ -79763,9 +79763,9 @@ var selfUpdate = async () => {
|
|
|
79763
79763
|
return $err(updateResult.error);
|
|
79764
79764
|
const update = updateResult.value;
|
|
79765
79765
|
if (!update) {
|
|
79766
|
-
return $ok(`Already on the latest version (v${
|
|
79766
|
+
return $ok(`Already on the latest version (v${currentVersion()})`);
|
|
79767
79767
|
}
|
|
79768
|
-
console.log(`New version available: ${update.tag} (current: v${
|
|
79768
|
+
console.log(`New version available: ${update.tag} (current: v${currentVersion()})`);
|
|
79769
79769
|
console.log(`Downloading ${update.downloadUrl}...
|
|
79770
79770
|
`);
|
|
79771
79771
|
let binaryPath = "";
|
|
@@ -79826,6 +79826,55 @@ ${line("═", W)}
|
|
|
79826
79826
|
Net Pay: $${f(r2.net)}${when(totalEmployeeRrsp, ` (after RRSP): $${f(r2.net - totalEmployeeRrsp)}`)}`;
|
|
79827
79827
|
};
|
|
79828
79828
|
|
|
79829
|
+
// src/brackets.ts
|
|
79830
|
+
var brackets = (...bands) => (income) => (bands.find(([upTo]) => income <= upTo) ?? bands[bands.length - 1])[1];
|
|
79831
|
+
var cumulativeTax = (bands) => (income) => bands.reduce(({ tax, prev }, [cap, rate]) => ({
|
|
79832
|
+
tax: income > prev ? tax + (Math.min(income, cap) - prev) * rate : tax,
|
|
79833
|
+
prev: cap
|
|
79834
|
+
}), { tax: 0, prev: 0 }).tax;
|
|
79835
|
+
var withSurtax = (baseFn, taxFn, threshold1, threshold2) => (income) => {
|
|
79836
|
+
const rate = baseFn(income);
|
|
79837
|
+
const tax = taxFn(income);
|
|
79838
|
+
let mult = 1;
|
|
79839
|
+
if (tax > threshold1)
|
|
79840
|
+
mult += 0.2;
|
|
79841
|
+
if (tax > threshold2)
|
|
79842
|
+
mult += 0.36;
|
|
79843
|
+
return rate * mult;
|
|
79844
|
+
};
|
|
79845
|
+
var on2025bands = [[52886, 0.0505], [105775, 0.0915], [150000, 0.1116], [220000, 0.1216], [Infinity, 0.1316]];
|
|
79846
|
+
var on2026bands = [[54000, 0.0505], [108000, 0.0915], [150000, 0.1116], [220000, 0.1216], [Infinity, 0.1316]];
|
|
79847
|
+
var RATES = {
|
|
79848
|
+
2025: {
|
|
79849
|
+
federal: brackets([57375, 0.15], [114750, 0.205], [158468, 0.26], [220000, 0.29], [Infinity, 0.33]),
|
|
79850
|
+
provincial: {
|
|
79851
|
+
Ontario: withSurtax(brackets(...on2025bands), cumulativeTax(on2025bands), 4991, 6387),
|
|
79852
|
+
Alberta: brackets([148269, 0.1], [177922, 0.12], [237230, 0.13], [355845, 0.14], [Infinity, 0.15]),
|
|
79853
|
+
"British Columbia": brackets([47937, 0.0506], [95875, 0.077], [110076, 0.105], [133664, 0.1229], [181232, 0.147], [252752, 0.168], [Infinity, 0.205])
|
|
79854
|
+
}
|
|
79855
|
+
},
|
|
79856
|
+
2026: {
|
|
79857
|
+
federal: brackets([59000, 0.15], [118000, 0.205], [163000, 0.26], [227000, 0.29], [Infinity, 0.33]),
|
|
79858
|
+
provincial: {
|
|
79859
|
+
Ontario: withSurtax(brackets(...on2026bands), cumulativeTax(on2026bands), 5100, 6525),
|
|
79860
|
+
Alberta: brackets([152000, 0.1], [182000, 0.12], [243000, 0.13], [365000, 0.14], [Infinity, 0.15]),
|
|
79861
|
+
"British Columbia": brackets([49000, 0.0506], [98000, 0.077], [113000, 0.105], [137000, 0.1229], [186000, 0.147], [259000, 0.168], [Infinity, 0.205])
|
|
79862
|
+
}
|
|
79863
|
+
}
|
|
79864
|
+
};
|
|
79865
|
+
var supportedYears = () => Object.keys(RATES).map(Number);
|
|
79866
|
+
var marginalRate = (year, province, taxableIncome) => {
|
|
79867
|
+
const yearRates = RATES[year];
|
|
79868
|
+
if (!yearRates)
|
|
79869
|
+
return $err(`No tax bracket data for ${year}. Supported years: ${supportedYears().join(", ")}`);
|
|
79870
|
+
const provFn = yearRates.provincial[province];
|
|
79871
|
+
if (!provFn)
|
|
79872
|
+
return $err(`No provincial bracket data for ${province} in ${year}. Supported: ${Object.keys(yearRates.provincial).join(", ")}`);
|
|
79873
|
+
const fed = yearRates.federal(taxableIncome);
|
|
79874
|
+
const prov = provFn(taxableIncome);
|
|
79875
|
+
return $ok({ federal: fed, provincial: prov, combined: fed + prov });
|
|
79876
|
+
};
|
|
79877
|
+
|
|
79829
79878
|
// src/views/table.ts
|
|
79830
79879
|
var col = (v, w) => typeof v === "number" ? money(v).padStart(w) : v.padStart(w);
|
|
79831
79880
|
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 ?? ""}`;
|
|
@@ -79871,7 +79920,7 @@ var totalsRow = (totals, rrsp) => ({
|
|
|
79871
79920
|
...rrspFields(rrsp, { rrspYou: totalEmployeeRrspTotals(totals), rrspEr: totals.rrspEmployer, takeHome: totals.netPay - totalEmployeeRrspTotals(totals) }),
|
|
79872
79921
|
cumCppEi: ""
|
|
79873
79922
|
});
|
|
79874
|
-
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) => {
|
|
79923
|
+
var renderTable = (yearly, periodsPerYear, year = 2026, province = "Ontario", annualSalary = 0, rrspPercent = 0) => 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) => {
|
|
79875
79924
|
const t8 = ctx.totals;
|
|
79876
79925
|
const empTotal = totalEmployeeRrspTotals(t8);
|
|
79877
79926
|
const rrspSummary = !ctx.rrsp ? "" : `
|
|
@@ -79879,6 +79928,9 @@ var renderTable = (yearly, periodsPerYear, year = 2026) => t3(yearly, (ctx) => (
|
|
|
79879
79928
|
RRSP Er: $${money(t8.rrspEmployer)}/yr ($${money(t8.rrspEmployer / periodsPerYear)}/period)
|
|
79880
79929
|
RRSP Total (You + Er): $${money(empTotal + t8.rrspEmployer)}/yr`;
|
|
79881
79930
|
const taxRate = t8.grossIncome > 0 ? t8.totalDeductions / t8.grossIncome * 100 : 0;
|
|
79931
|
+
const taxableIncome = annualSalary * (1 - rrspPercent / 100);
|
|
79932
|
+
const mr = marginalRate(year, province, taxableIncome);
|
|
79933
|
+
const marginalLine = mr.isOk() ? ` Marginal tax rate: ${pct(mr.value.combined * 100)} (fed ${pct(mr.value.federal * 100)} + prov ${pct(mr.value.provincial * 100)})` : "";
|
|
79882
79934
|
return `Per-Paycheck Table (${year})
|
|
79883
79935
|
${line("═", ctx.W)}
|
|
79884
79936
|
${ctx.header}
|
|
@@ -79888,7 +79940,8 @@ ${ctx.bodyRows.join(`
|
|
|
79888
79940
|
${line("─", ctx.W)}
|
|
79889
79941
|
${ctx.totalsStr}
|
|
79890
79942
|
${line("═", ctx.W)}
|
|
79891
|
-
Effective tax rate: ${pct(taxRate)}${
|
|
79943
|
+
Effective tax rate: ${pct(taxRate)}${marginalLine ? `
|
|
79944
|
+
${marginalLine}` : ""}${rrspSummary}`;
|
|
79892
79945
|
});
|
|
79893
79946
|
|
|
79894
79947
|
// src/views/monthlyTable.ts
|
|
@@ -79931,7 +79984,7 @@ var totalsRow2 = (totals, rrsp) => ({
|
|
|
79931
79984
|
netPay: totals.netPay,
|
|
79932
79985
|
...rrspFields2(rrsp, { rrspYou: totalEmployeeRrspTotals2(totals), rrspEr: totals.rrspEmployer, takeHome: totals.netPay - totalEmployeeRrspTotals2(totals) })
|
|
79933
79986
|
});
|
|
79934
|
-
var renderMonthlyTable = (monthly, year = 2026) => t3(monthly, (ctx) => ({ ...ctx, rrsp: showRrsp2(ctx.totals) }), (ctx) => ({ ...ctx, header: renderRow2(headerRow2(ctx.rrsp)) }), (ctx) => ({ ...ctx, bodyRows: ctx.rows.map((r2) => renderRow2(toRow2(r2, ctx.rrsp))) }), (ctx) => ({ ...ctx, totalsStr: renderRow2(totalsRow2(ctx.totals, ctx.rrsp)), W: ctx.header.length }), (ctx) => {
|
|
79987
|
+
var renderMonthlyTable = (monthly, year = 2026, province = "Ontario", annualSalary = 0, rrspPercent = 0) => t3(monthly, (ctx) => ({ ...ctx, rrsp: showRrsp2(ctx.totals) }), (ctx) => ({ ...ctx, header: renderRow2(headerRow2(ctx.rrsp)) }), (ctx) => ({ ...ctx, bodyRows: ctx.rows.map((r2) => renderRow2(toRow2(r2, ctx.rrsp))) }), (ctx) => ({ ...ctx, totalsStr: renderRow2(totalsRow2(ctx.totals, ctx.rrsp)), W: ctx.header.length }), (ctx) => {
|
|
79935
79988
|
const t8 = ctx.totals;
|
|
79936
79989
|
const empTotal = totalEmployeeRrspTotals2(t8);
|
|
79937
79990
|
const rrspSummary = !ctx.rrsp ? "" : `
|
|
@@ -79939,6 +79992,9 @@ var renderMonthlyTable = (monthly, year = 2026) => t3(monthly, (ctx) => ({ ...ct
|
|
|
79939
79992
|
RRSP Er: $${money(t8.rrspEmployer)}/yr
|
|
79940
79993
|
RRSP Total (You + Er): $${money(empTotal + t8.rrspEmployer)}/yr`;
|
|
79941
79994
|
const taxRate = t8.grossIncome > 0 ? t8.totalDeductions / t8.grossIncome * 100 : 0;
|
|
79995
|
+
const taxableIncome = annualSalary * (1 - rrspPercent / 100);
|
|
79996
|
+
const mr = marginalRate(year, province, taxableIncome);
|
|
79997
|
+
const marginalLine = mr.isOk() ? ` Marginal tax rate: ${pct(mr.value.combined * 100)} (fed ${pct(mr.value.federal * 100)} + prov ${pct(mr.value.provincial * 100)})` : "";
|
|
79942
79998
|
return `Monthly Table (${year})
|
|
79943
79999
|
${line("═", ctx.W)}
|
|
79944
80000
|
${ctx.header}
|
|
@@ -79948,7 +80004,8 @@ ${ctx.bodyRows.join(`
|
|
|
79948
80004
|
${line("─", ctx.W)}
|
|
79949
80005
|
${ctx.totalsStr}
|
|
79950
80006
|
${line("═", ctx.W)}
|
|
79951
|
-
Effective tax rate: ${pct(taxRate)}${
|
|
80007
|
+
Effective tax rate: ${pct(taxRate)}${marginalLine ? `
|
|
80008
|
+
${marginalLine}` : ""}${rrspSummary}`;
|
|
79952
80009
|
});
|
|
79953
80010
|
|
|
79954
80011
|
// src/monthly.ts
|
|
@@ -80082,31 +80139,50 @@ var PAY_PERIODS2 = [
|
|
|
80082
80139
|
"Semi-monthly (24 pay periods a year)",
|
|
80083
80140
|
"Monthly (12 pay periods a year)"
|
|
80084
80141
|
];
|
|
80085
|
-
|
|
80086
|
-
|
|
80087
|
-
|
|
80088
|
-
|
|
80089
|
-
salary: { type: "string", short: "s" },
|
|
80090
|
-
province: { type: "string", short: "p" },
|
|
80091
|
-
"pay-period": { type: "string" },
|
|
80092
|
-
year: { type: "string", short: "y" },
|
|
80093
|
-
"rrsp-match": { type: "string" },
|
|
80094
|
-
"rrsp-unmatched": { type: "string" },
|
|
80095
|
-
"cpp-maxed": { type: "boolean", default: false },
|
|
80096
|
-
"ei-maxed": { type: "boolean", default: false },
|
|
80097
|
-
table: { type: "boolean", short: "t", default: false },
|
|
80098
|
-
"month-table": { type: "boolean", short: "M", default: false },
|
|
80099
|
-
annual: { type: "boolean", short: "a", default: false },
|
|
80100
|
-
monthly: { type: "boolean", short: "m", default: false },
|
|
80101
|
-
"no-cache": { type: "boolean", default: false },
|
|
80102
|
-
update: { type: "boolean", default: false },
|
|
80103
|
-
version: { type: "boolean", default: false },
|
|
80104
|
-
headless: { type: "boolean", default: false },
|
|
80105
|
-
verbose: { type: "boolean", short: "v", default: false },
|
|
80106
|
-
help: { type: "boolean", short: "h", default: false }
|
|
80107
|
-
},
|
|
80108
|
-
strict: true
|
|
80142
|
+
process.on("SIGINT", () => {
|
|
80143
|
+
console.error(`
|
|
80144
|
+
Interrupted.`);
|
|
80145
|
+
process.exit(130);
|
|
80109
80146
|
});
|
|
80147
|
+
var values;
|
|
80148
|
+
try {
|
|
80149
|
+
({ values } = parseArgs({
|
|
80150
|
+
args: process.argv.slice(2),
|
|
80151
|
+
options: {
|
|
80152
|
+
config: { type: "string", short: "c", default: "" },
|
|
80153
|
+
salary: { type: "string", short: "s" },
|
|
80154
|
+
province: { type: "string", short: "p" },
|
|
80155
|
+
"pay-period": { type: "string" },
|
|
80156
|
+
year: { type: "string", short: "y" },
|
|
80157
|
+
"rrsp-match": { type: "string" },
|
|
80158
|
+
"rrsp-unmatched": { type: "string" },
|
|
80159
|
+
"cpp-maxed": { type: "boolean", default: false },
|
|
80160
|
+
"ei-maxed": { type: "boolean", default: false },
|
|
80161
|
+
table: { type: "boolean", short: "t", default: false },
|
|
80162
|
+
"month-table": { type: "boolean", short: "M", default: false },
|
|
80163
|
+
annual: { type: "boolean", short: "a", default: false },
|
|
80164
|
+
monthly: { type: "boolean", short: "m", default: false },
|
|
80165
|
+
"no-cache": { type: "boolean", default: false },
|
|
80166
|
+
update: { type: "boolean", default: false },
|
|
80167
|
+
version: { type: "boolean", default: false },
|
|
80168
|
+
headless: { type: "boolean", default: false },
|
|
80169
|
+
verbose: { type: "boolean", short: "v", default: false },
|
|
80170
|
+
help: { type: "boolean", short: "h", default: false }
|
|
80171
|
+
},
|
|
80172
|
+
strict: true
|
|
80173
|
+
}));
|
|
80174
|
+
} catch (e2) {
|
|
80175
|
+
if (e2.code === "ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL") {
|
|
80176
|
+
const arg = e2.message.match(/Unexpected argument '([^']+)'/)?.[1];
|
|
80177
|
+
console.error(`❌ Unexpected argument '${arg}'. Did you mean '--${arg}'?`);
|
|
80178
|
+
} else if (e2.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
80179
|
+
const opt = e2.message.match(/Unknown option '([^']+)'/)?.[1];
|
|
80180
|
+
console.error(`❌ Unknown option '${opt}'. Run 'cra-payroll --help' for usage.`);
|
|
80181
|
+
} else {
|
|
80182
|
+
console.error(`❌ ${e2.message}`);
|
|
80183
|
+
}
|
|
80184
|
+
process.exit(1);
|
|
80185
|
+
}
|
|
80110
80186
|
if (values.help) {
|
|
80111
80187
|
console.log(`
|
|
80112
80188
|
cra-payroll - Calculate Canadian payroll deductions using CRA's PDOC
|
|
@@ -80306,11 +80382,12 @@ var runYearlyMode = async (config2, headless, svc, flags) => {
|
|
|
80306
80382
|
}
|
|
80307
80383
|
const yearly = yearlyResult.value;
|
|
80308
80384
|
const periodsPerYear = PAY_PERIODS[config2.payPeriod];
|
|
80385
|
+
const rrspPercent = config2.rrspMatchPercent + config2.rrspUnmatchedPercent;
|
|
80309
80386
|
if (flags.table)
|
|
80310
|
-
console.log(renderTable(yearly, periodsPerYear, config2.year));
|
|
80387
|
+
console.log(renderTable(yearly, periodsPerYear, config2.year, config2.province, config2.annualSalary, rrspPercent));
|
|
80311
80388
|
if (flags.monthTable) {
|
|
80312
80389
|
const monthly = groupByMonth(yearly, config2.year, config2.payPeriod, periodsPerYear);
|
|
80313
|
-
console.log(renderMonthlyTable(monthly, config2.year));
|
|
80390
|
+
console.log(renderMonthlyTable(monthly, config2.year, config2.province, config2.annualSalary, rrspPercent));
|
|
80314
80391
|
}
|
|
80315
80392
|
if (flags.annual)
|
|
80316
80393
|
console.log(renderAnnual(yearly.totals));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seethruhead/cra-payroll",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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 src/cache.test.ts src/monthly.test.ts src/views.test.ts",
|
|
17
|
+
"test": "bun test src/unit.test.ts src/cache.test.ts src/monthly.test.ts src/views.test.ts src/brackets.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
|
},
|