create-kvitton 0.4.1 → 0.4.5
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/index.js +3352 -231
- package/dist/templates/AGENTS.md +94 -12
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,14 +1,795 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/eta.js
|
|
23
|
+
var require_eta = __commonJS((exports, module) => {
|
|
24
|
+
class ETA {
|
|
25
|
+
constructor(length, initTime, initValue) {
|
|
26
|
+
this.etaBufferLength = length || 100;
|
|
27
|
+
this.valueBuffer = [initValue];
|
|
28
|
+
this.timeBuffer = [initTime];
|
|
29
|
+
this.eta = "0";
|
|
30
|
+
}
|
|
31
|
+
update(time, value, total) {
|
|
32
|
+
this.valueBuffer.push(value);
|
|
33
|
+
this.timeBuffer.push(time);
|
|
34
|
+
this.calculate(total - value);
|
|
35
|
+
}
|
|
36
|
+
getTime() {
|
|
37
|
+
return this.eta;
|
|
38
|
+
}
|
|
39
|
+
calculate(remaining) {
|
|
40
|
+
const currentBufferSize = this.valueBuffer.length;
|
|
41
|
+
const buffer = Math.min(this.etaBufferLength, currentBufferSize);
|
|
42
|
+
const v_diff = this.valueBuffer[currentBufferSize - 1] - this.valueBuffer[currentBufferSize - buffer];
|
|
43
|
+
const t_diff = this.timeBuffer[currentBufferSize - 1] - this.timeBuffer[currentBufferSize - buffer];
|
|
44
|
+
const vt_rate = v_diff / t_diff;
|
|
45
|
+
this.valueBuffer = this.valueBuffer.slice(-this.etaBufferLength);
|
|
46
|
+
this.timeBuffer = this.timeBuffer.slice(-this.etaBufferLength);
|
|
47
|
+
const eta = Math.ceil(remaining / vt_rate / 1000);
|
|
48
|
+
if (isNaN(eta)) {
|
|
49
|
+
this.eta = "NULL";
|
|
50
|
+
} else if (!isFinite(eta)) {
|
|
51
|
+
this.eta = "INF";
|
|
52
|
+
} else if (eta > 1e7) {
|
|
53
|
+
this.eta = "INF";
|
|
54
|
+
} else if (eta < 0) {
|
|
55
|
+
this.eta = 0;
|
|
56
|
+
} else {
|
|
57
|
+
this.eta = eta;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
module.exports = ETA;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/terminal.js
|
|
65
|
+
var require_terminal = __commonJS((exports, module) => {
|
|
66
|
+
var _readline = __require("readline");
|
|
67
|
+
|
|
68
|
+
class Terminal {
|
|
69
|
+
constructor(outputStream) {
|
|
70
|
+
this.stream = outputStream;
|
|
71
|
+
this.linewrap = true;
|
|
72
|
+
this.dy = 0;
|
|
73
|
+
}
|
|
74
|
+
cursorSave() {
|
|
75
|
+
if (!this.stream.isTTY) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.stream.write("\x1B7");
|
|
79
|
+
}
|
|
80
|
+
cursorRestore() {
|
|
81
|
+
if (!this.stream.isTTY) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.stream.write("\x1B8");
|
|
85
|
+
}
|
|
86
|
+
cursor(enabled) {
|
|
87
|
+
if (!this.stream.isTTY) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (enabled) {
|
|
91
|
+
this.stream.write("\x1B[?25h");
|
|
92
|
+
} else {
|
|
93
|
+
this.stream.write("\x1B[?25l");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
cursorTo(x = null, y = null) {
|
|
97
|
+
if (!this.stream.isTTY) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
_readline.cursorTo(this.stream, x, y);
|
|
101
|
+
}
|
|
102
|
+
cursorRelative(dx = null, dy = null) {
|
|
103
|
+
if (!this.stream.isTTY) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.dy = this.dy + dy;
|
|
107
|
+
_readline.moveCursor(this.stream, dx, dy);
|
|
108
|
+
}
|
|
109
|
+
cursorRelativeReset() {
|
|
110
|
+
if (!this.stream.isTTY) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
_readline.moveCursor(this.stream, 0, -this.dy);
|
|
114
|
+
_readline.cursorTo(this.stream, 0, null);
|
|
115
|
+
this.dy = 0;
|
|
116
|
+
}
|
|
117
|
+
clearRight() {
|
|
118
|
+
if (!this.stream.isTTY) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
_readline.clearLine(this.stream, 1);
|
|
122
|
+
}
|
|
123
|
+
clearLine() {
|
|
124
|
+
if (!this.stream.isTTY) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
_readline.clearLine(this.stream, 0);
|
|
128
|
+
}
|
|
129
|
+
clearBottom() {
|
|
130
|
+
if (!this.stream.isTTY) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
_readline.clearScreenDown(this.stream);
|
|
134
|
+
}
|
|
135
|
+
newline() {
|
|
136
|
+
this.stream.write(`
|
|
137
|
+
`);
|
|
138
|
+
this.dy++;
|
|
139
|
+
}
|
|
140
|
+
write(s, rawWrite = false) {
|
|
141
|
+
if (this.linewrap === true && rawWrite === false) {
|
|
142
|
+
this.stream.write(s.substr(0, this.getWidth()));
|
|
143
|
+
} else {
|
|
144
|
+
this.stream.write(s);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
lineWrapping(enabled) {
|
|
148
|
+
if (!this.stream.isTTY) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.linewrap = enabled;
|
|
152
|
+
if (enabled) {
|
|
153
|
+
this.stream.write("\x1B[?7h");
|
|
154
|
+
} else {
|
|
155
|
+
this.stream.write("\x1B[?7l");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
isTTY() {
|
|
159
|
+
return this.stream.isTTY === true;
|
|
160
|
+
}
|
|
161
|
+
getWidth() {
|
|
162
|
+
return this.stream.columns || (this.stream.isTTY ? 80 : 200);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
module.exports = Terminal;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ../../node_modules/.bun/ansi-regex@5.0.1/node_modules/ansi-regex/index.js
|
|
169
|
+
var require_ansi_regex = __commonJS((exports, module) => {
|
|
170
|
+
module.exports = ({ onlyFirst = false } = {}) => {
|
|
171
|
+
const pattern = [
|
|
172
|
+
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
173
|
+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
|
|
174
|
+
].join("|");
|
|
175
|
+
return new RegExp(pattern, onlyFirst ? undefined : "g");
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ../../node_modules/.bun/strip-ansi@6.0.1/node_modules/strip-ansi/index.js
|
|
180
|
+
var require_strip_ansi = __commonJS((exports, module) => {
|
|
181
|
+
var ansiRegex = require_ansi_regex();
|
|
182
|
+
module.exports = (string) => typeof string === "string" ? string.replace(ansiRegex(), "") : string;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ../../node_modules/.bun/is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point/index.js
|
|
186
|
+
var require_is_fullwidth_code_point = __commonJS((exports, module) => {
|
|
187
|
+
var isFullwidthCodePoint = (codePoint) => {
|
|
188
|
+
if (Number.isNaN(codePoint)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || 11904 <= codePoint && codePoint <= 12871 && codePoint !== 12351 || 12880 <= codePoint && codePoint <= 19903 || 19968 <= codePoint && codePoint <= 42182 || 43360 <= codePoint && codePoint <= 43388 || 44032 <= codePoint && codePoint <= 55203 || 63744 <= codePoint && codePoint <= 64255 || 65040 <= codePoint && codePoint <= 65049 || 65072 <= codePoint && codePoint <= 65131 || 65281 <= codePoint && codePoint <= 65376 || 65504 <= codePoint && codePoint <= 65510 || 110592 <= codePoint && codePoint <= 110593 || 127488 <= codePoint && codePoint <= 127569 || 131072 <= codePoint && codePoint <= 262141)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
module.exports = isFullwidthCodePoint;
|
|
197
|
+
module.exports.default = isFullwidthCodePoint;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ../../node_modules/.bun/emoji-regex@8.0.0/node_modules/emoji-regex/index.js
|
|
201
|
+
var require_emoji_regex = __commonJS((exports, module) => {
|
|
202
|
+
module.exports = function() {
|
|
203
|
+
return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g;
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ../../node_modules/.bun/string-width@4.2.3/node_modules/string-width/index.js
|
|
208
|
+
var require_string_width = __commonJS((exports, module) => {
|
|
209
|
+
var stripAnsi = require_strip_ansi();
|
|
210
|
+
var isFullwidthCodePoint = require_is_fullwidth_code_point();
|
|
211
|
+
var emojiRegex = require_emoji_regex();
|
|
212
|
+
var stringWidth = (string) => {
|
|
213
|
+
if (typeof string !== "string" || string.length === 0) {
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
string = stripAnsi(string);
|
|
217
|
+
if (string.length === 0) {
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
string = string.replace(emojiRegex(), " ");
|
|
221
|
+
let width = 0;
|
|
222
|
+
for (let i = 0;i < string.length; i++) {
|
|
223
|
+
const code = string.codePointAt(i);
|
|
224
|
+
if (code <= 31 || code >= 127 && code <= 159) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (code >= 768 && code <= 879) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (code > 65535) {
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
width += isFullwidthCodePoint(code) ? 2 : 1;
|
|
234
|
+
}
|
|
235
|
+
return width;
|
|
236
|
+
};
|
|
237
|
+
module.exports = stringWidth;
|
|
238
|
+
module.exports.default = stringWidth;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-value.js
|
|
242
|
+
var require_format_value = __commonJS((exports, module) => {
|
|
243
|
+
module.exports = function formatValue(v, options, type) {
|
|
244
|
+
if (options.autopadding !== true) {
|
|
245
|
+
return v;
|
|
246
|
+
}
|
|
247
|
+
function autopadding(value, length) {
|
|
248
|
+
return (options.autopaddingChar + value).slice(-length);
|
|
249
|
+
}
|
|
250
|
+
switch (type) {
|
|
251
|
+
case "percentage":
|
|
252
|
+
return autopadding(v, 3);
|
|
253
|
+
default:
|
|
254
|
+
return v;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-bar.js
|
|
260
|
+
var require_format_bar = __commonJS((exports, module) => {
|
|
261
|
+
module.exports = function formatBar(progress, options) {
|
|
262
|
+
const completeSize = Math.round(progress * options.barsize);
|
|
263
|
+
const incompleteSize = options.barsize - completeSize;
|
|
264
|
+
return options.barCompleteString.substr(0, completeSize) + options.barGlue + options.barIncompleteString.substr(0, incompleteSize);
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-time.js
|
|
269
|
+
var require_format_time = __commonJS((exports, module) => {
|
|
270
|
+
module.exports = function formatTime(t, options, roundToMultipleOf) {
|
|
271
|
+
function round(input) {
|
|
272
|
+
if (roundToMultipleOf) {
|
|
273
|
+
return roundToMultipleOf * Math.round(input / roundToMultipleOf);
|
|
274
|
+
} else {
|
|
275
|
+
return input;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function autopadding(v) {
|
|
279
|
+
return (options.autopaddingChar + v).slice(-2);
|
|
280
|
+
}
|
|
281
|
+
if (t > 3600) {
|
|
282
|
+
return autopadding(Math.floor(t / 3600)) + "h" + autopadding(round(t % 3600 / 60)) + "m";
|
|
283
|
+
} else if (t > 60) {
|
|
284
|
+
return autopadding(Math.floor(t / 60)) + "m" + autopadding(round(t % 60)) + "s";
|
|
285
|
+
} else if (t > 10) {
|
|
286
|
+
return autopadding(round(t)) + "s";
|
|
287
|
+
} else {
|
|
288
|
+
return autopadding(t) + "s";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/formatter.js
|
|
294
|
+
var require_formatter = __commonJS((exports, module) => {
|
|
295
|
+
var _stringWidth = require_string_width();
|
|
296
|
+
var _defaultFormatValue = require_format_value();
|
|
297
|
+
var _defaultFormatBar = require_format_bar();
|
|
298
|
+
var _defaultFormatTime = require_format_time();
|
|
299
|
+
module.exports = function defaultFormatter(options, params, payload) {
|
|
300
|
+
let s = options.format;
|
|
301
|
+
const formatTime = options.formatTime || _defaultFormatTime;
|
|
302
|
+
const formatValue = options.formatValue || _defaultFormatValue;
|
|
303
|
+
const formatBar = options.formatBar || _defaultFormatBar;
|
|
304
|
+
const percentage = Math.floor(params.progress * 100) + "";
|
|
305
|
+
const stopTime = params.stopTime || Date.now();
|
|
306
|
+
const elapsedTime = Math.round((stopTime - params.startTime) / 1000);
|
|
307
|
+
const context = Object.assign({}, payload, {
|
|
308
|
+
bar: formatBar(params.progress, options),
|
|
309
|
+
percentage: formatValue(percentage, options, "percentage"),
|
|
310
|
+
total: formatValue(params.total, options, "total"),
|
|
311
|
+
value: formatValue(params.value, options, "value"),
|
|
312
|
+
eta: formatValue(params.eta, options, "eta"),
|
|
313
|
+
eta_formatted: formatTime(params.eta, options, 5),
|
|
314
|
+
duration: formatValue(elapsedTime, options, "duration"),
|
|
315
|
+
duration_formatted: formatTime(elapsedTime, options, 1)
|
|
316
|
+
});
|
|
317
|
+
s = s.replace(/\{(\w+)\}/g, function(match, key) {
|
|
318
|
+
if (typeof context[key] !== "undefined") {
|
|
319
|
+
return context[key];
|
|
320
|
+
}
|
|
321
|
+
return match;
|
|
322
|
+
});
|
|
323
|
+
const fullMargin = Math.max(0, params.maxWidth - _stringWidth(s) - 2);
|
|
324
|
+
const halfMargin = Math.floor(fullMargin / 2);
|
|
325
|
+
switch (options.align) {
|
|
326
|
+
case "right":
|
|
327
|
+
s = fullMargin > 0 ? " ".repeat(fullMargin) + s : s;
|
|
328
|
+
break;
|
|
329
|
+
case "center":
|
|
330
|
+
s = halfMargin > 0 ? " ".repeat(halfMargin) + s : s;
|
|
331
|
+
break;
|
|
332
|
+
case "left":
|
|
333
|
+
default:
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
return s;
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/options.js
|
|
341
|
+
var require_options = __commonJS((exports, module) => {
|
|
342
|
+
function mergeOption(v, defaultValue) {
|
|
343
|
+
if (typeof v === "undefined" || v === null) {
|
|
344
|
+
return defaultValue;
|
|
345
|
+
} else {
|
|
346
|
+
return v;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
module.exports = {
|
|
350
|
+
parse: function parse(rawOptions, preset) {
|
|
351
|
+
const options = {};
|
|
352
|
+
const opt = Object.assign({}, preset, rawOptions);
|
|
353
|
+
options.throttleTime = 1000 / mergeOption(opt.fps, 10);
|
|
354
|
+
options.stream = mergeOption(opt.stream, process.stderr);
|
|
355
|
+
options.terminal = mergeOption(opt.terminal, null);
|
|
356
|
+
options.clearOnComplete = mergeOption(opt.clearOnComplete, false);
|
|
357
|
+
options.stopOnComplete = mergeOption(opt.stopOnComplete, false);
|
|
358
|
+
options.barsize = mergeOption(opt.barsize, 40);
|
|
359
|
+
options.align = mergeOption(opt.align, "left");
|
|
360
|
+
options.hideCursor = mergeOption(opt.hideCursor, false);
|
|
361
|
+
options.linewrap = mergeOption(opt.linewrap, false);
|
|
362
|
+
options.barGlue = mergeOption(opt.barGlue, "");
|
|
363
|
+
options.barCompleteChar = mergeOption(opt.barCompleteChar, "=");
|
|
364
|
+
options.barIncompleteChar = mergeOption(opt.barIncompleteChar, "-");
|
|
365
|
+
options.format = mergeOption(opt.format, "progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}");
|
|
366
|
+
options.formatTime = mergeOption(opt.formatTime, null);
|
|
367
|
+
options.formatValue = mergeOption(opt.formatValue, null);
|
|
368
|
+
options.formatBar = mergeOption(opt.formatBar, null);
|
|
369
|
+
options.etaBufferLength = mergeOption(opt.etaBuffer, 10);
|
|
370
|
+
options.etaAsynchronousUpdate = mergeOption(opt.etaAsynchronousUpdate, false);
|
|
371
|
+
options.progressCalculationRelative = mergeOption(opt.progressCalculationRelative, false);
|
|
372
|
+
options.synchronousUpdate = mergeOption(opt.synchronousUpdate, true);
|
|
373
|
+
options.noTTYOutput = mergeOption(opt.noTTYOutput, false);
|
|
374
|
+
options.notTTYSchedule = mergeOption(opt.notTTYSchedule, 2000);
|
|
375
|
+
options.emptyOnZero = mergeOption(opt.emptyOnZero, false);
|
|
376
|
+
options.forceRedraw = mergeOption(opt.forceRedraw, false);
|
|
377
|
+
options.autopadding = mergeOption(opt.autopadding, false);
|
|
378
|
+
options.gracefulExit = mergeOption(opt.gracefulExit, false);
|
|
379
|
+
return options;
|
|
380
|
+
},
|
|
381
|
+
assignDerivedOptions: function assignDerivedOptions(options) {
|
|
382
|
+
options.barCompleteString = options.barCompleteChar.repeat(options.barsize + 1);
|
|
383
|
+
options.barIncompleteString = options.barIncompleteChar.repeat(options.barsize + 1);
|
|
384
|
+
options.autopaddingChar = options.autopadding ? mergeOption(options.autopaddingChar, " ") : "";
|
|
385
|
+
return options;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/generic-bar.js
|
|
391
|
+
var require_generic_bar = __commonJS((exports, module) => {
|
|
392
|
+
var _ETA = require_eta();
|
|
393
|
+
var _Terminal = require_terminal();
|
|
394
|
+
var _formatter = require_formatter();
|
|
395
|
+
var _options = require_options();
|
|
396
|
+
var _EventEmitter = __require("events");
|
|
397
|
+
module.exports = class GenericBar extends _EventEmitter {
|
|
398
|
+
constructor(options) {
|
|
399
|
+
super();
|
|
400
|
+
this.options = _options.assignDerivedOptions(options);
|
|
401
|
+
this.terminal = this.options.terminal ? this.options.terminal : new _Terminal(this.options.stream);
|
|
402
|
+
this.value = 0;
|
|
403
|
+
this.startValue = 0;
|
|
404
|
+
this.total = 100;
|
|
405
|
+
this.lastDrawnString = null;
|
|
406
|
+
this.startTime = null;
|
|
407
|
+
this.stopTime = null;
|
|
408
|
+
this.lastRedraw = Date.now();
|
|
409
|
+
this.eta = new _ETA(this.options.etaBufferLength, 0, 0);
|
|
410
|
+
this.payload = {};
|
|
411
|
+
this.isActive = false;
|
|
412
|
+
this.formatter = typeof this.options.format === "function" ? this.options.format : _formatter;
|
|
413
|
+
}
|
|
414
|
+
render(forceRendering = false) {
|
|
415
|
+
const params = {
|
|
416
|
+
progress: this.getProgress(),
|
|
417
|
+
eta: this.eta.getTime(),
|
|
418
|
+
startTime: this.startTime,
|
|
419
|
+
stopTime: this.stopTime,
|
|
420
|
+
total: this.total,
|
|
421
|
+
value: this.value,
|
|
422
|
+
maxWidth: this.terminal.getWidth()
|
|
423
|
+
};
|
|
424
|
+
if (this.options.etaAsynchronousUpdate) {
|
|
425
|
+
this.updateETA();
|
|
426
|
+
}
|
|
427
|
+
const s = this.formatter(this.options, params, this.payload);
|
|
428
|
+
const forceRedraw = forceRendering || this.options.forceRedraw || this.options.noTTYOutput && !this.terminal.isTTY();
|
|
429
|
+
if (forceRedraw || this.lastDrawnString != s) {
|
|
430
|
+
this.emit("redraw-pre");
|
|
431
|
+
this.terminal.cursorTo(0, null);
|
|
432
|
+
this.terminal.write(s);
|
|
433
|
+
this.terminal.clearRight();
|
|
434
|
+
this.lastDrawnString = s;
|
|
435
|
+
this.lastRedraw = Date.now();
|
|
436
|
+
this.emit("redraw-post");
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
start(total, startValue, payload) {
|
|
440
|
+
this.value = startValue || 0;
|
|
441
|
+
this.total = typeof total !== "undefined" && total >= 0 ? total : 100;
|
|
442
|
+
this.startValue = startValue || 0;
|
|
443
|
+
this.payload = payload || {};
|
|
444
|
+
this.startTime = Date.now();
|
|
445
|
+
this.stopTime = null;
|
|
446
|
+
this.lastDrawnString = "";
|
|
447
|
+
this.eta = new _ETA(this.options.etaBufferLength, this.startTime, this.value);
|
|
448
|
+
this.isActive = true;
|
|
449
|
+
this.emit("start", total, startValue);
|
|
450
|
+
}
|
|
451
|
+
stop() {
|
|
452
|
+
this.isActive = false;
|
|
453
|
+
this.stopTime = Date.now();
|
|
454
|
+
this.emit("stop", this.total, this.value);
|
|
455
|
+
}
|
|
456
|
+
update(arg0, arg1 = {}) {
|
|
457
|
+
if (typeof arg0 === "number") {
|
|
458
|
+
this.value = arg0;
|
|
459
|
+
this.eta.update(Date.now(), arg0, this.total);
|
|
460
|
+
}
|
|
461
|
+
const payloadData = (typeof arg0 === "object" ? arg0 : arg1) || {};
|
|
462
|
+
this.emit("update", this.total, this.value);
|
|
463
|
+
for (const key in payloadData) {
|
|
464
|
+
this.payload[key] = payloadData[key];
|
|
465
|
+
}
|
|
466
|
+
if (this.value >= this.getTotal() && this.options.stopOnComplete) {
|
|
467
|
+
this.stop();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
getProgress() {
|
|
471
|
+
let progress = this.value / this.total;
|
|
472
|
+
if (this.options.progressCalculationRelative) {
|
|
473
|
+
progress = (this.value - this.startValue) / (this.total - this.startValue);
|
|
474
|
+
}
|
|
475
|
+
if (isNaN(progress)) {
|
|
476
|
+
progress = this.options && this.options.emptyOnZero ? 0 : 1;
|
|
477
|
+
}
|
|
478
|
+
progress = Math.min(Math.max(progress, 0), 1);
|
|
479
|
+
return progress;
|
|
480
|
+
}
|
|
481
|
+
increment(arg0 = 1, arg1 = {}) {
|
|
482
|
+
if (typeof arg0 === "object") {
|
|
483
|
+
this.update(this.value + 1, arg0);
|
|
484
|
+
} else {
|
|
485
|
+
this.update(this.value + arg0, arg1);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
getTotal() {
|
|
489
|
+
return this.total;
|
|
490
|
+
}
|
|
491
|
+
setTotal(total) {
|
|
492
|
+
if (typeof total !== "undefined" && total >= 0) {
|
|
493
|
+
this.total = total;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
updateETA() {
|
|
497
|
+
this.eta.update(Date.now(), this.value, this.total);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/single-bar.js
|
|
503
|
+
var require_single_bar = __commonJS((exports, module) => {
|
|
504
|
+
var _GenericBar = require_generic_bar();
|
|
505
|
+
var _options = require_options();
|
|
506
|
+
module.exports = class SingleBar extends _GenericBar {
|
|
507
|
+
constructor(options, preset) {
|
|
508
|
+
super(_options.parse(options, preset));
|
|
509
|
+
this.timer = null;
|
|
510
|
+
if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
|
|
511
|
+
this.options.synchronousUpdate = false;
|
|
512
|
+
}
|
|
513
|
+
this.schedulingRate = this.terminal.isTTY() ? this.options.throttleTime : this.options.notTTYSchedule;
|
|
514
|
+
this.sigintCallback = null;
|
|
515
|
+
}
|
|
516
|
+
render() {
|
|
517
|
+
if (this.timer) {
|
|
518
|
+
clearTimeout(this.timer);
|
|
519
|
+
this.timer = null;
|
|
520
|
+
}
|
|
521
|
+
super.render();
|
|
522
|
+
if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
|
|
523
|
+
this.terminal.newline();
|
|
524
|
+
}
|
|
525
|
+
this.timer = setTimeout(this.render.bind(this), this.schedulingRate);
|
|
526
|
+
}
|
|
527
|
+
update(current, payload) {
|
|
528
|
+
if (!this.timer) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
super.update(current, payload);
|
|
532
|
+
if (this.options.synchronousUpdate && this.lastRedraw + this.options.throttleTime * 2 < Date.now()) {
|
|
533
|
+
this.render();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
start(total, startValue, payload) {
|
|
537
|
+
if (this.options.noTTYOutput === false && this.terminal.isTTY() === false) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (this.sigintCallback === null && this.options.gracefulExit) {
|
|
541
|
+
this.sigintCallback = this.stop.bind(this);
|
|
542
|
+
process.once("SIGINT", this.sigintCallback);
|
|
543
|
+
process.once("SIGTERM", this.sigintCallback);
|
|
544
|
+
}
|
|
545
|
+
this.terminal.cursorSave();
|
|
546
|
+
if (this.options.hideCursor === true) {
|
|
547
|
+
this.terminal.cursor(false);
|
|
548
|
+
}
|
|
549
|
+
if (this.options.linewrap === false) {
|
|
550
|
+
this.terminal.lineWrapping(false);
|
|
551
|
+
}
|
|
552
|
+
super.start(total, startValue, payload);
|
|
553
|
+
this.render();
|
|
554
|
+
}
|
|
555
|
+
stop() {
|
|
556
|
+
if (!this.timer) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (this.sigintCallback) {
|
|
560
|
+
process.removeListener("SIGINT", this.sigintCallback);
|
|
561
|
+
process.removeListener("SIGTERM", this.sigintCallback);
|
|
562
|
+
this.sigintCallback = null;
|
|
563
|
+
}
|
|
564
|
+
this.render();
|
|
565
|
+
super.stop();
|
|
566
|
+
clearTimeout(this.timer);
|
|
567
|
+
this.timer = null;
|
|
568
|
+
if (this.options.hideCursor === true) {
|
|
569
|
+
this.terminal.cursor(true);
|
|
570
|
+
}
|
|
571
|
+
if (this.options.linewrap === false) {
|
|
572
|
+
this.terminal.lineWrapping(true);
|
|
573
|
+
}
|
|
574
|
+
this.terminal.cursorRestore();
|
|
575
|
+
if (this.options.clearOnComplete) {
|
|
576
|
+
this.terminal.cursorTo(0, null);
|
|
577
|
+
this.terminal.clearLine();
|
|
578
|
+
} else {
|
|
579
|
+
this.terminal.newline();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/multi-bar.js
|
|
586
|
+
var require_multi_bar = __commonJS((exports, module) => {
|
|
587
|
+
var _Terminal = require_terminal();
|
|
588
|
+
var _BarElement = require_generic_bar();
|
|
589
|
+
var _options = require_options();
|
|
590
|
+
var _EventEmitter = __require("events");
|
|
591
|
+
module.exports = class MultiBar extends _EventEmitter {
|
|
592
|
+
constructor(options, preset) {
|
|
593
|
+
super();
|
|
594
|
+
this.bars = [];
|
|
595
|
+
this.options = _options.parse(options, preset);
|
|
596
|
+
this.options.synchronousUpdate = false;
|
|
597
|
+
this.terminal = this.options.terminal ? this.options.terminal : new _Terminal(this.options.stream);
|
|
598
|
+
this.timer = null;
|
|
599
|
+
this.isActive = false;
|
|
600
|
+
this.schedulingRate = this.terminal.isTTY() ? this.options.throttleTime : this.options.notTTYSchedule;
|
|
601
|
+
this.loggingBuffer = [];
|
|
602
|
+
this.sigintCallback = null;
|
|
603
|
+
}
|
|
604
|
+
create(total, startValue, payload, barOptions = {}) {
|
|
605
|
+
const bar = new _BarElement(Object.assign({}, this.options, {
|
|
606
|
+
terminal: this.terminal
|
|
607
|
+
}, barOptions));
|
|
608
|
+
this.bars.push(bar);
|
|
609
|
+
if (this.options.noTTYOutput === false && this.terminal.isTTY() === false) {
|
|
610
|
+
return bar;
|
|
611
|
+
}
|
|
612
|
+
if (this.sigintCallback === null && this.options.gracefulExit) {
|
|
613
|
+
this.sigintCallback = this.stop.bind(this);
|
|
614
|
+
process.once("SIGINT", this.sigintCallback);
|
|
615
|
+
process.once("SIGTERM", this.sigintCallback);
|
|
616
|
+
}
|
|
617
|
+
if (!this.isActive) {
|
|
618
|
+
if (this.options.hideCursor === true) {
|
|
619
|
+
this.terminal.cursor(false);
|
|
620
|
+
}
|
|
621
|
+
if (this.options.linewrap === false) {
|
|
622
|
+
this.terminal.lineWrapping(false);
|
|
623
|
+
}
|
|
624
|
+
this.timer = setTimeout(this.update.bind(this), this.schedulingRate);
|
|
625
|
+
}
|
|
626
|
+
this.isActive = true;
|
|
627
|
+
bar.start(total, startValue, payload);
|
|
628
|
+
this.emit("start");
|
|
629
|
+
return bar;
|
|
630
|
+
}
|
|
631
|
+
remove(bar) {
|
|
632
|
+
const index = this.bars.indexOf(bar);
|
|
633
|
+
if (index < 0) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
this.bars.splice(index, 1);
|
|
637
|
+
this.update();
|
|
638
|
+
this.terminal.newline();
|
|
639
|
+
this.terminal.clearBottom();
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
update() {
|
|
643
|
+
if (this.timer) {
|
|
644
|
+
clearTimeout(this.timer);
|
|
645
|
+
this.timer = null;
|
|
646
|
+
}
|
|
647
|
+
this.emit("update-pre");
|
|
648
|
+
this.terminal.cursorRelativeReset();
|
|
649
|
+
this.emit("redraw-pre");
|
|
650
|
+
if (this.loggingBuffer.length > 0) {
|
|
651
|
+
this.terminal.clearLine();
|
|
652
|
+
while (this.loggingBuffer.length > 0) {
|
|
653
|
+
this.terminal.write(this.loggingBuffer.shift(), true);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (let i = 0;i < this.bars.length; i++) {
|
|
657
|
+
if (i > 0) {
|
|
658
|
+
this.terminal.newline();
|
|
659
|
+
}
|
|
660
|
+
this.bars[i].render();
|
|
661
|
+
}
|
|
662
|
+
this.emit("redraw-post");
|
|
663
|
+
if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
|
|
664
|
+
this.terminal.newline();
|
|
665
|
+
this.terminal.newline();
|
|
666
|
+
}
|
|
667
|
+
this.timer = setTimeout(this.update.bind(this), this.schedulingRate);
|
|
668
|
+
this.emit("update-post");
|
|
669
|
+
if (this.options.stopOnComplete && !this.bars.find((bar) => bar.isActive)) {
|
|
670
|
+
this.stop();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
stop() {
|
|
674
|
+
clearTimeout(this.timer);
|
|
675
|
+
this.timer = null;
|
|
676
|
+
if (this.sigintCallback) {
|
|
677
|
+
process.removeListener("SIGINT", this.sigintCallback);
|
|
678
|
+
process.removeListener("SIGTERM", this.sigintCallback);
|
|
679
|
+
this.sigintCallback = null;
|
|
680
|
+
}
|
|
681
|
+
this.isActive = false;
|
|
682
|
+
if (this.options.hideCursor === true) {
|
|
683
|
+
this.terminal.cursor(true);
|
|
684
|
+
}
|
|
685
|
+
if (this.options.linewrap === false) {
|
|
686
|
+
this.terminal.lineWrapping(true);
|
|
687
|
+
}
|
|
688
|
+
this.terminal.cursorRelativeReset();
|
|
689
|
+
this.emit("stop-pre-clear");
|
|
690
|
+
if (this.options.clearOnComplete) {
|
|
691
|
+
this.terminal.clearBottom();
|
|
692
|
+
} else {
|
|
693
|
+
for (let i = 0;i < this.bars.length; i++) {
|
|
694
|
+
if (i > 0) {
|
|
695
|
+
this.terminal.newline();
|
|
696
|
+
}
|
|
697
|
+
this.bars[i].render();
|
|
698
|
+
this.bars[i].stop();
|
|
699
|
+
}
|
|
700
|
+
this.terminal.newline();
|
|
701
|
+
}
|
|
702
|
+
this.emit("stop");
|
|
703
|
+
}
|
|
704
|
+
log(s) {
|
|
705
|
+
this.loggingBuffer.push(s);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/legacy.js
|
|
711
|
+
var require_legacy = __commonJS((exports, module) => {
|
|
712
|
+
module.exports = {
|
|
713
|
+
format: "progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}",
|
|
714
|
+
barCompleteChar: "=",
|
|
715
|
+
barIncompleteChar: "-"
|
|
716
|
+
};
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/shades-classic.js
|
|
720
|
+
var require_shades_classic = __commonJS((exports, module) => {
|
|
721
|
+
module.exports = {
|
|
722
|
+
format: " {bar} {percentage}% | ETA: {eta}s | {value}/{total}",
|
|
723
|
+
barCompleteChar: "█",
|
|
724
|
+
barIncompleteChar: "░"
|
|
725
|
+
};
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/shades-grey.js
|
|
729
|
+
var require_shades_grey = __commonJS((exports, module) => {
|
|
730
|
+
module.exports = {
|
|
731
|
+
format: " \x1B[90m{bar}\x1B[0m {percentage}% | ETA: {eta}s | {value}/{total}",
|
|
732
|
+
barCompleteChar: "█",
|
|
733
|
+
barIncompleteChar: "░"
|
|
734
|
+
};
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/rect.js
|
|
738
|
+
var require_rect = __commonJS((exports, module) => {
|
|
739
|
+
module.exports = {
|
|
740
|
+
format: " {bar}■ {percentage}% | ETA: {eta}s | {value}/{total}",
|
|
741
|
+
barCompleteChar: "■",
|
|
742
|
+
barIncompleteChar: " "
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/index.js
|
|
747
|
+
var require_presets = __commonJS((exports, module) => {
|
|
748
|
+
var _legacy = require_legacy();
|
|
749
|
+
var _shades_classic = require_shades_classic();
|
|
750
|
+
var _shades_grey = require_shades_grey();
|
|
751
|
+
var _rect = require_rect();
|
|
752
|
+
module.exports = {
|
|
753
|
+
legacy: _legacy,
|
|
754
|
+
shades_classic: _shades_classic,
|
|
755
|
+
shades_grey: _shades_grey,
|
|
756
|
+
rect: _rect
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/cli-progress.js
|
|
761
|
+
var require_cli_progress = __commonJS((exports, module) => {
|
|
762
|
+
var _SingleBar = require_single_bar();
|
|
763
|
+
var _MultiBar = require_multi_bar();
|
|
764
|
+
var _Presets = require_presets();
|
|
765
|
+
var _Formatter = require_formatter();
|
|
766
|
+
var _defaultFormatValue = require_format_value();
|
|
767
|
+
var _defaultFormatBar = require_format_bar();
|
|
768
|
+
var _defaultFormatTime = require_format_time();
|
|
769
|
+
module.exports = {
|
|
770
|
+
Bar: _SingleBar,
|
|
771
|
+
SingleBar: _SingleBar,
|
|
772
|
+
MultiBar: _MultiBar,
|
|
773
|
+
Presets: _Presets,
|
|
774
|
+
Format: {
|
|
775
|
+
Formatter: _Formatter,
|
|
776
|
+
BarFormat: _defaultFormatBar,
|
|
777
|
+
ValueFormat: _defaultFormatValue,
|
|
778
|
+
TimeFormat: _defaultFormatTime
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
});
|
|
2
782
|
|
|
3
783
|
// src/index.ts
|
|
4
784
|
import { Command } from "commander";
|
|
5
785
|
|
|
6
786
|
// src/commands/create.ts
|
|
7
|
-
import { select, input, password, confirm } from "@inquirer/prompts";
|
|
787
|
+
import { select, input, password, confirm, checkbox } from "@inquirer/prompts";
|
|
8
788
|
import ora from "ora";
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
11
|
-
import
|
|
789
|
+
import * as path7 from "node:path";
|
|
790
|
+
import * as fs11 from "node:fs/promises";
|
|
791
|
+
import * as os2 from "node:os";
|
|
792
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12
793
|
|
|
13
794
|
// ../../integrations/bokio/dist/config.js
|
|
14
795
|
var BOKIO_CONFIG = {
|
|
@@ -123,8 +904,10 @@ class IntegrationError extends Error {
|
|
|
123
904
|
}
|
|
124
905
|
|
|
125
906
|
class AuthenticationError extends IntegrationError {
|
|
126
|
-
|
|
907
|
+
response;
|
|
908
|
+
constructor(message, response, cause) {
|
|
127
909
|
super(message, "AUTHENTICATION_ERROR", 401, cause);
|
|
910
|
+
this.response = response;
|
|
128
911
|
this.name = "AuthenticationError";
|
|
129
912
|
}
|
|
130
913
|
}
|
|
@@ -220,13 +1003,15 @@ class HttpClient {
|
|
|
220
1003
|
return await response.json();
|
|
221
1004
|
}
|
|
222
1005
|
if (response.status === 401) {
|
|
223
|
-
|
|
1006
|
+
const errorBody2 = await response.text().catch(() => {
|
|
1007
|
+
return;
|
|
1008
|
+
});
|
|
1009
|
+
throw new AuthenticationError("Authentication failed", errorBody2);
|
|
224
1010
|
}
|
|
225
1011
|
if (response.status === 429) {
|
|
226
1012
|
const retryAfter = response.headers.get("Retry-After");
|
|
227
1013
|
const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000;
|
|
228
1014
|
if (attempt < this.retryConfig.maxRetries) {
|
|
229
|
-
console.log(`[HttpClient] Rate limited, waiting ${retryAfterMs}ms before retry ${attempt + 1}/${this.retryConfig.maxRetries}`);
|
|
230
1015
|
await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
|
|
231
1016
|
continue;
|
|
232
1017
|
}
|
|
@@ -308,7 +1093,10 @@ class HttpClient {
|
|
|
308
1093
|
return await response.json();
|
|
309
1094
|
}
|
|
310
1095
|
if (response.status === 401) {
|
|
311
|
-
|
|
1096
|
+
const errorBody2 = await response.text().catch(() => {
|
|
1097
|
+
return;
|
|
1098
|
+
});
|
|
1099
|
+
throw new AuthenticationError("Authentication failed", errorBody2);
|
|
312
1100
|
}
|
|
313
1101
|
if (response.status === 429) {
|
|
314
1102
|
const retryAfter = response.headers.get("Retry-After");
|
|
@@ -353,7 +1141,10 @@ class HttpClient {
|
|
|
353
1141
|
clearTimeout(timeoutId);
|
|
354
1142
|
if (!response.ok) {
|
|
355
1143
|
if (response.status === 401) {
|
|
356
|
-
|
|
1144
|
+
const errorBody2 = await response.text().catch(() => {
|
|
1145
|
+
return;
|
|
1146
|
+
});
|
|
1147
|
+
throw new AuthenticationError("Authentication failed", errorBody2);
|
|
357
1148
|
}
|
|
358
1149
|
if (response.status === 429) {
|
|
359
1150
|
const retryAfter = response.headers.get("Retry-After");
|
|
@@ -666,54 +1457,820 @@ class BokioClient {
|
|
|
666
1457
|
return validate(UploadSchema, raw, "Upload");
|
|
667
1458
|
}
|
|
668
1459
|
}
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1460
|
+
// ../../integrations/fortnox/dist/config.js
|
|
1461
|
+
var FORTNOX_CONFIG = {
|
|
1462
|
+
API_BASE_URL: "https://api.fortnox.se",
|
|
1463
|
+
AUTH_BASE_URL: "https://apps.fortnox.se",
|
|
1464
|
+
TOKEN_ENDPOINT: "/oauth-v1/token",
|
|
1465
|
+
AUTHORIZE_ENDPOINT: "/oauth-v1/auth",
|
|
1466
|
+
API_VERSION: "3",
|
|
1467
|
+
RATE_LIMIT: {
|
|
1468
|
+
MAX_REQUESTS: 25,
|
|
1469
|
+
WINDOW_SECONDS: 5
|
|
1470
|
+
},
|
|
1471
|
+
TOKEN_EXPIRY_SECONDS: 3600,
|
|
1472
|
+
REFRESH_TOKEN_EXPIRY_DAYS: 45
|
|
1473
|
+
};
|
|
1474
|
+
var FORTNOX_SCOPES = {
|
|
1475
|
+
INVOICE: "invoice",
|
|
1476
|
+
CUSTOMER: "customer",
|
|
1477
|
+
ARTICLE: "article",
|
|
1478
|
+
ORDER: "order",
|
|
1479
|
+
OFFER: "offer",
|
|
1480
|
+
SUPPLIER: "supplier",
|
|
1481
|
+
SUPPLIERINVOICE: "supplierinvoice",
|
|
1482
|
+
ACCOUNT: "account",
|
|
1483
|
+
VOUCHER: "voucher",
|
|
1484
|
+
BOOKKEEPING: "bookkeeping",
|
|
1485
|
+
SETTINGS: "settings",
|
|
1486
|
+
SALARY: "salary",
|
|
1487
|
+
PRINT: "print",
|
|
1488
|
+
CURRENCY: "currency",
|
|
1489
|
+
PROJECT: "project",
|
|
1490
|
+
PRICE: "price",
|
|
1491
|
+
INBOX: "inbox",
|
|
1492
|
+
ARCHIVE: "archive",
|
|
1493
|
+
COMPANYINFORMATION: "companyinformation",
|
|
1494
|
+
CONNECTFILE: "connectfile",
|
|
1495
|
+
COSTCENTER: "costcenter",
|
|
1496
|
+
NOXFINANS: "noxfinans",
|
|
1497
|
+
PAYMENT: "payment",
|
|
1498
|
+
WAREHOUSE: "warehouse"
|
|
1499
|
+
};
|
|
1500
|
+
// ../../integrations/fortnox/dist/schemas.js
|
|
1501
|
+
import { z as z2 } from "zod";
|
|
1502
|
+
var FortnoxMetaInformationSchema = z2.object({
|
|
1503
|
+
"@TotalResources": z2.number(),
|
|
1504
|
+
"@TotalPages": z2.number(),
|
|
1505
|
+
"@CurrentPage": z2.number()
|
|
1506
|
+
}).passthrough();
|
|
1507
|
+
var FortnoxErrorResponseSchema = z2.object({
|
|
1508
|
+
ErrorInformation: z2.object({
|
|
1509
|
+
Error: z2.number(),
|
|
1510
|
+
Message: z2.string(),
|
|
1511
|
+
Code: z2.number()
|
|
1512
|
+
})
|
|
1513
|
+
}).passthrough();
|
|
1514
|
+
var FinancialYearSchema = z2.object({
|
|
1515
|
+
Id: z2.number(),
|
|
1516
|
+
FromDate: z2.string(),
|
|
1517
|
+
ToDate: z2.string()
|
|
1518
|
+
}).passthrough();
|
|
1519
|
+
var FinancialYearResponseSchema = z2.object({
|
|
1520
|
+
FinancialYear: FinancialYearSchema
|
|
1521
|
+
}).passthrough();
|
|
1522
|
+
var FinancialYearsResponseSchema = z2.object({
|
|
1523
|
+
MetaInformation: FortnoxMetaInformationSchema,
|
|
1524
|
+
FinancialYears: z2.array(FinancialYearSchema)
|
|
1525
|
+
}).passthrough();
|
|
1526
|
+
var AccountSchema2 = z2.object({
|
|
1527
|
+
Number: z2.union([z2.string(), z2.number()]).transform((v) => String(v)),
|
|
1528
|
+
Description: z2.string()
|
|
1529
|
+
}).passthrough();
|
|
1530
|
+
var AccountResponseSchema = z2.object({
|
|
1531
|
+
Account: AccountSchema2
|
|
1532
|
+
}).passthrough();
|
|
1533
|
+
var AccountsResponseSchema = z2.object({
|
|
1534
|
+
MetaInformation: FortnoxMetaInformationSchema,
|
|
1535
|
+
Accounts: z2.array(AccountSchema2)
|
|
1536
|
+
}).passthrough();
|
|
1537
|
+
var CreateAccountRequestSchema = z2.object({
|
|
1538
|
+
Account: z2.object({
|
|
1539
|
+
Number: z2.string(),
|
|
1540
|
+
Description: z2.string()
|
|
1541
|
+
})
|
|
1542
|
+
});
|
|
1543
|
+
var VoucherSeriesSchema = z2.object({
|
|
1544
|
+
Code: z2.string(),
|
|
1545
|
+
Description: z2.string()
|
|
1546
|
+
}).passthrough();
|
|
1547
|
+
var VoucherSeriesResponseSchema = z2.object({
|
|
1548
|
+
VoucherSeries: VoucherSeriesSchema
|
|
1549
|
+
}).passthrough();
|
|
1550
|
+
var VoucherSeriesListResponseSchema = z2.object({
|
|
1551
|
+
MetaInformation: FortnoxMetaInformationSchema
|
|
1552
|
+
}).and(z2.union([
|
|
1553
|
+
z2.object({ VoucherSeriesList: z2.array(VoucherSeriesSchema) }),
|
|
1554
|
+
z2.object({ VoucherSeriesCollection: z2.array(VoucherSeriesSchema) })
|
|
1555
|
+
])).transform((data) => ({
|
|
1556
|
+
...data,
|
|
1557
|
+
VoucherSeriesList: "VoucherSeriesList" in data ? data.VoucherSeriesList : data.VoucherSeriesCollection
|
|
1558
|
+
}));
|
|
1559
|
+
var CreateVoucherSeriesRequestSchema = z2.object({
|
|
1560
|
+
VoucherSeries: z2.object({
|
|
1561
|
+
Code: z2.string(),
|
|
1562
|
+
Description: z2.string()
|
|
1563
|
+
})
|
|
1564
|
+
});
|
|
1565
|
+
var CompanyInformationSchema = z2.object({
|
|
1566
|
+
CompanyName: z2.string(),
|
|
1567
|
+
Address: z2.string().nullish(),
|
|
1568
|
+
City: z2.string().nullish(),
|
|
1569
|
+
CountryCode: z2.string().nullish(),
|
|
1570
|
+
DatabaseNumber: z2.number().nullish(),
|
|
1571
|
+
OrganizationNumber: z2.string().nullish(),
|
|
1572
|
+
VisitAddress: z2.string().nullish(),
|
|
1573
|
+
VisitCity: z2.string().nullish(),
|
|
1574
|
+
VisitCountryCode: z2.string().nullish(),
|
|
1575
|
+
VisitZipCode: z2.string().nullish(),
|
|
1576
|
+
ZipCode: z2.string().nullish()
|
|
1577
|
+
}).passthrough();
|
|
1578
|
+
var CompanyInformationResponseSchema = z2.object({
|
|
1579
|
+
CompanyInformation: CompanyInformationSchema
|
|
1580
|
+
}).passthrough();
|
|
1581
|
+
var CostCenterSchema = z2.object({
|
|
1582
|
+
Code: z2.string(),
|
|
1583
|
+
Description: z2.string().optional()
|
|
1584
|
+
}).passthrough();
|
|
1585
|
+
var CostCenterResponseSchema = z2.object({
|
|
1586
|
+
CostCenter: CostCenterSchema
|
|
1587
|
+
}).passthrough();
|
|
1588
|
+
var CostCentersResponseSchema = z2.object({
|
|
1589
|
+
MetaInformation: FortnoxMetaInformationSchema.optional(),
|
|
1590
|
+
CostCenters: z2.array(CostCenterSchema)
|
|
1591
|
+
}).passthrough();
|
|
1592
|
+
var FortnoxFileSchema = z2.object({
|
|
1593
|
+
Id: z2.string(),
|
|
1594
|
+
Name: z2.string(),
|
|
1595
|
+
Path: z2.string().optional(),
|
|
1596
|
+
Size: z2.number().optional()
|
|
1597
|
+
}).passthrough();
|
|
1598
|
+
var FortnoxFolderSchema = z2.object({
|
|
1599
|
+
Id: z2.string().optional(),
|
|
1600
|
+
Name: z2.string(),
|
|
1601
|
+
Files: z2.array(FortnoxFileSchema).optional(),
|
|
1602
|
+
Folders: z2.array(z2.any()).optional()
|
|
1603
|
+
}).passthrough();
|
|
1604
|
+
var InboxFileResponseSchema = z2.object({
|
|
1605
|
+
File: FortnoxFileSchema
|
|
1606
|
+
}).passthrough();
|
|
1607
|
+
var InboxFilesResponseSchema = z2.object({
|
|
1608
|
+
Inbox: FortnoxFolderSchema.optional(),
|
|
1609
|
+
Folder: FortnoxFolderSchema.optional(),
|
|
1610
|
+
MetaInformation: FortnoxMetaInformationSchema.optional(),
|
|
1611
|
+
Files: z2.array(FortnoxFileSchema).optional()
|
|
1612
|
+
}).passthrough();
|
|
1613
|
+
var ArchiveFileResponseSchema = z2.object({
|
|
1614
|
+
File: FortnoxFileSchema
|
|
1615
|
+
}).passthrough();
|
|
1616
|
+
var ArchiveFilesResponseSchema = z2.object({
|
|
1617
|
+
Archive: FortnoxFolderSchema.optional(),
|
|
1618
|
+
Folder: FortnoxFolderSchema.optional(),
|
|
1619
|
+
MetaInformation: FortnoxMetaInformationSchema.optional(),
|
|
1620
|
+
Files: z2.array(FortnoxFileSchema).optional()
|
|
1621
|
+
}).passthrough();
|
|
1622
|
+
var VoucherRowSchema = z2.object({
|
|
1623
|
+
Account: z2.number(),
|
|
1624
|
+
Debit: z2.number().default(0),
|
|
1625
|
+
Credit: z2.number().default(0),
|
|
1626
|
+
CostCenter: z2.string().optional(),
|
|
1627
|
+
Project: z2.string().optional(),
|
|
1628
|
+
TransactionInformation: z2.string().optional(),
|
|
1629
|
+
Quantity: z2.number().optional(),
|
|
1630
|
+
Removed: z2.boolean().optional()
|
|
1631
|
+
}).passthrough();
|
|
1632
|
+
var VoucherSchema = z2.object({
|
|
1633
|
+
VoucherNumber: z2.number().optional(),
|
|
1634
|
+
VoucherSeries: z2.string(),
|
|
1635
|
+
VoucherDate: z2.string().optional(),
|
|
1636
|
+
Year: z2.number().optional(),
|
|
1637
|
+
Description: z2.string().nullish(),
|
|
1638
|
+
Comments: z2.string().nullish(),
|
|
1639
|
+
CostCenter: z2.string().nullish(),
|
|
1640
|
+
Project: z2.string().nullish(),
|
|
1641
|
+
VoucherRows: z2.array(VoucherRowSchema).optional(),
|
|
1642
|
+
ReferenceNumber: z2.string().nullish(),
|
|
1643
|
+
ReferenceType: z2.string().nullish(),
|
|
1644
|
+
TransactionDate: z2.string().nullish(),
|
|
1645
|
+
ApprovalState: z2.number().optional()
|
|
1646
|
+
}).passthrough();
|
|
1647
|
+
var VoucherResponseSchema = z2.object({
|
|
1648
|
+
Voucher: VoucherSchema
|
|
1649
|
+
}).passthrough();
|
|
1650
|
+
var VouchersResponseSchema = z2.object({
|
|
1651
|
+
MetaInformation: FortnoxMetaInformationSchema,
|
|
1652
|
+
Vouchers: z2.array(VoucherSchema)
|
|
1653
|
+
}).passthrough();
|
|
1654
|
+
var CreateVoucherRequestSchema = z2.object({
|
|
1655
|
+
Voucher: z2.object({
|
|
1656
|
+
VoucherSeries: z2.string(),
|
|
1657
|
+
TransactionDate: z2.string(),
|
|
1658
|
+
Description: z2.string().optional(),
|
|
1659
|
+
CostCenter: z2.string().optional(),
|
|
1660
|
+
Project: z2.string().optional(),
|
|
1661
|
+
Comments: z2.string().optional(),
|
|
1662
|
+
VoucherRows: z2.array(z2.object({
|
|
1663
|
+
Account: z2.number(),
|
|
1664
|
+
Debit: z2.number().optional(),
|
|
1665
|
+
Credit: z2.number().optional(),
|
|
1666
|
+
CostCenter: z2.string().optional(),
|
|
1667
|
+
Project: z2.string().optional(),
|
|
1668
|
+
TransactionInformation: z2.string().optional(),
|
|
1669
|
+
Quantity: z2.number().optional()
|
|
1670
|
+
}))
|
|
1671
|
+
})
|
|
1672
|
+
});
|
|
1673
|
+
var VoucherFileConnectionSchema = z2.object({
|
|
1674
|
+
FileId: z2.string(),
|
|
1675
|
+
VoucherNumber: z2.number().optional(),
|
|
1676
|
+
VoucherSeries: z2.string(),
|
|
1677
|
+
VoucherYear: z2.number().optional(),
|
|
1678
|
+
VoucherDescription: z2.string().optional()
|
|
1679
|
+
}).passthrough();
|
|
1680
|
+
var VoucherFileConnectionResponseSchema = z2.object({
|
|
1681
|
+
VoucherFileConnection: VoucherFileConnectionSchema
|
|
1682
|
+
}).passthrough();
|
|
1683
|
+
var CreateVoucherFileConnectionRequestSchema = z2.object({
|
|
1684
|
+
VoucherFileConnection: z2.object({
|
|
1685
|
+
FileId: z2.string(),
|
|
1686
|
+
VoucherNumber: z2.number(),
|
|
1687
|
+
VoucherSeries: z2.string(),
|
|
1688
|
+
VoucherYear: z2.number()
|
|
1689
|
+
})
|
|
1690
|
+
});
|
|
1691
|
+
var VoucherFileConnectionsResponseSchema = z2.object({
|
|
1692
|
+
VoucherFileConnections: z2.array(VoucherFileConnectionSchema)
|
|
1693
|
+
}).passthrough();
|
|
1694
|
+
// ../../integrations/fortnox/dist/auth.js
|
|
1695
|
+
function buildAuthorizationUrl(config, state) {
|
|
1696
|
+
const params = new URLSearchParams({
|
|
1697
|
+
client_id: config.clientId,
|
|
1698
|
+
redirect_uri: config.redirectUri,
|
|
1699
|
+
scope: config.scopes?.join(" ") ?? "",
|
|
1700
|
+
state,
|
|
1701
|
+
access_type: "offline",
|
|
1702
|
+
response_type: "code"
|
|
1703
|
+
});
|
|
1704
|
+
return `${FORTNOX_CONFIG.AUTH_BASE_URL}${FORTNOX_CONFIG.AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
1705
|
+
}
|
|
1706
|
+
async function fetchToken(config, body) {
|
|
1707
|
+
const credentials = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
|
|
1708
|
+
const response = await fetch(`${FORTNOX_CONFIG.AUTH_BASE_URL}${FORTNOX_CONFIG.TOKEN_ENDPOINT}`, {
|
|
1709
|
+
method: "POST",
|
|
1710
|
+
headers: {
|
|
1711
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1712
|
+
Authorization: `Basic ${credentials}`
|
|
1713
|
+
},
|
|
1714
|
+
body: body.toString()
|
|
1715
|
+
});
|
|
1716
|
+
if (!response.ok) {
|
|
1717
|
+
const error = await response.json().catch(() => ({}));
|
|
1718
|
+
throw new Error(error.error_description ?? error.error ?? `Token request failed: ${response.status}`);
|
|
1719
|
+
}
|
|
1720
|
+
return response.json();
|
|
1721
|
+
}
|
|
1722
|
+
async function exchangeCodeForToken(config, code) {
|
|
1723
|
+
const body = new URLSearchParams({
|
|
1724
|
+
grant_type: "authorization_code",
|
|
1725
|
+
code,
|
|
1726
|
+
redirect_uri: config.redirectUri
|
|
1727
|
+
});
|
|
1728
|
+
return fetchToken(config, body);
|
|
1729
|
+
}
|
|
1730
|
+
// ../../integrations/fortnox/dist/pagination.js
|
|
1731
|
+
var DEFAULT_LIMIT = 100;
|
|
1732
|
+
var MAX_LIMIT = 500;
|
|
1733
|
+
function toFortnoxParams(params) {
|
|
1734
|
+
if (!params)
|
|
1735
|
+
return {};
|
|
1736
|
+
const pageSize = Math.min(params.pageSize ?? DEFAULT_LIMIT, MAX_LIMIT);
|
|
1737
|
+
const page = Math.max(params.page ?? 1, 1);
|
|
1738
|
+
return {
|
|
1739
|
+
limit: pageSize,
|
|
1740
|
+
offset: (page - 1) * pageSize
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
function fromFortnoxMeta(meta, pageSize = DEFAULT_LIMIT) {
|
|
1744
|
+
const totalItems = meta["@TotalResources"];
|
|
1745
|
+
const totalPages = meta["@TotalPages"];
|
|
1746
|
+
const currentPage = meta["@CurrentPage"];
|
|
1747
|
+
return {
|
|
1748
|
+
totalItems,
|
|
1749
|
+
totalPages,
|
|
1750
|
+
currentPage,
|
|
1751
|
+
pageSize,
|
|
1752
|
+
hasNextPage: currentPage < totalPages,
|
|
1753
|
+
hasPreviousPage: currentPage > 1
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
function buildQueryString(params) {
|
|
1757
|
+
const entries = Object.entries(params).filter(([_, v]) => v !== undefined);
|
|
1758
|
+
if (entries.length === 0)
|
|
1759
|
+
return "";
|
|
1760
|
+
return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
1761
|
+
}
|
|
1762
|
+
// ../../integrations/fortnox/dist/client.js
|
|
1763
|
+
class FortnoxClient {
|
|
1764
|
+
httpClient;
|
|
1765
|
+
constructor(config) {
|
|
1766
|
+
const rateLimiter = TokenBucketRateLimiter.forFortnox();
|
|
1767
|
+
this.httpClient = new HttpClient({
|
|
1768
|
+
baseUrl: FORTNOX_CONFIG.API_BASE_URL,
|
|
1769
|
+
getAccessToken: config.getAccessToken,
|
|
1770
|
+
rateLimiter,
|
|
1771
|
+
timeout: 600000,
|
|
1772
|
+
defaultHeaders: {
|
|
1773
|
+
Accept: "application/json"
|
|
1774
|
+
},
|
|
1775
|
+
silent: config.silent
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
async request(path, options) {
|
|
1779
|
+
return this.httpClient.request(path, {
|
|
1780
|
+
method: options?.method ?? "GET",
|
|
1781
|
+
body: options?.body
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
async get(path) {
|
|
1785
|
+
return this.httpClient.get(path);
|
|
1786
|
+
}
|
|
1787
|
+
async post(path, body) {
|
|
1788
|
+
return this.httpClient.post(path, body);
|
|
1789
|
+
}
|
|
1790
|
+
async put(path, body) {
|
|
1791
|
+
return this.httpClient.put(path, body);
|
|
1792
|
+
}
|
|
1793
|
+
async delete(path) {
|
|
1794
|
+
return this.httpClient.delete(path);
|
|
1795
|
+
}
|
|
1796
|
+
async getFinancialYears(params) {
|
|
1797
|
+
const { limit, offset } = toFortnoxParams(params);
|
|
1798
|
+
const query = buildQueryString({ limit, offset });
|
|
1799
|
+
const raw = await this.get(`/3/financialyears${query}`);
|
|
1800
|
+
const response = validate(FinancialYearsResponseSchema, raw, "FinancialYearsResponse");
|
|
1801
|
+
return {
|
|
1802
|
+
data: response.FinancialYears,
|
|
1803
|
+
pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
async getFinancialYearByDate(date) {
|
|
1807
|
+
const raw = await this.get(`/3/financialyears/?date=${date}`);
|
|
1808
|
+
return validate(FinancialYearsResponseSchema, raw, "FinancialYearsResponse");
|
|
1809
|
+
}
|
|
1810
|
+
async getFinancialYearById(id) {
|
|
1811
|
+
const raw = await this.get(`/3/financialyears/${id}`);
|
|
1812
|
+
return validate(FinancialYearResponseSchema, raw, "FinancialYearResponse");
|
|
1813
|
+
}
|
|
1814
|
+
async getAccounts(params) {
|
|
1815
|
+
const { limit, offset } = toFortnoxParams(params);
|
|
1816
|
+
const query = buildQueryString({ limit, offset });
|
|
1817
|
+
const raw = await this.get(`/3/accounts${query}`);
|
|
1818
|
+
const response = validate(AccountsResponseSchema, raw, "AccountsResponse");
|
|
1819
|
+
return {
|
|
1820
|
+
data: response.Accounts,
|
|
1821
|
+
pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async getAccount(accountNumber) {
|
|
1825
|
+
const raw = await this.get(`/3/accounts/${accountNumber}`);
|
|
1826
|
+
return validate(AccountResponseSchema, raw, "AccountResponse");
|
|
1827
|
+
}
|
|
1828
|
+
async createAccount(request) {
|
|
1829
|
+
const raw = await this.post("/3/accounts", request);
|
|
1830
|
+
return validate(AccountResponseSchema, raw, "AccountResponse");
|
|
1831
|
+
}
|
|
1832
|
+
async getVoucherSeriesList(params) {
|
|
1833
|
+
const { limit, offset } = toFortnoxParams(params);
|
|
1834
|
+
const query = buildQueryString({ limit, offset });
|
|
1835
|
+
const raw = await this.get(`/3/voucherseries${query}`);
|
|
1836
|
+
const response = validate(VoucherSeriesListResponseSchema, raw, "VoucherSeriesListResponse");
|
|
1837
|
+
return {
|
|
1838
|
+
data: response.VoucherSeriesList,
|
|
1839
|
+
pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
async getVoucherSeries(code, financialYearDate) {
|
|
1843
|
+
const raw = await this.get(`/3/voucherseries/${code}?financialyeardate=${financialYearDate}`);
|
|
1844
|
+
return validate(VoucherSeriesResponseSchema, raw, "VoucherSeriesResponse");
|
|
1845
|
+
}
|
|
1846
|
+
async createVoucherSeries(request) {
|
|
1847
|
+
const raw = await this.post("/3/voucherseries", request);
|
|
1848
|
+
return validate(VoucherSeriesResponseSchema, raw, "VoucherSeriesResponse");
|
|
1849
|
+
}
|
|
1850
|
+
async getCompanyInformation() {
|
|
1851
|
+
const raw = await this.get("/3/companyinformation");
|
|
1852
|
+
return validate(CompanyInformationResponseSchema, raw, "CompanyInformationResponse");
|
|
1853
|
+
}
|
|
1854
|
+
async getCostCenters(params) {
|
|
1855
|
+
const { limit, offset } = toFortnoxParams(params);
|
|
1856
|
+
const query = buildQueryString({ limit, offset });
|
|
1857
|
+
const raw = await this.get(`/3/costcenters${query}`);
|
|
1858
|
+
const response = validate(CostCentersResponseSchema, raw, "CostCentersResponse");
|
|
1859
|
+
const meta = response.MetaInformation ?? {
|
|
1860
|
+
"@TotalResources": response.CostCenters.length,
|
|
1861
|
+
"@TotalPages": 1,
|
|
1862
|
+
"@CurrentPage": 1
|
|
1863
|
+
};
|
|
1864
|
+
return {
|
|
1865
|
+
data: response.CostCenters,
|
|
1866
|
+
pagination: fromFortnoxMeta(meta, limit ?? 100)
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
async getCostCenter(code) {
|
|
1870
|
+
const raw = await this.get(`/3/costcenters/${code}`);
|
|
1871
|
+
return validate(CostCenterResponseSchema, raw, "CostCenterResponse");
|
|
1872
|
+
}
|
|
1873
|
+
async getInboxFiles() {
|
|
1874
|
+
const raw = await this.get("/3/inbox");
|
|
1875
|
+
return validate(InboxFilesResponseSchema, raw, "InboxFilesResponse");
|
|
1876
|
+
}
|
|
1877
|
+
async getInboxFolder(folderId) {
|
|
1878
|
+
const raw = await this.get(`/3/inbox/${folderId}`);
|
|
1879
|
+
const response = validate(InboxFilesResponseSchema, raw, "InboxFilesResponse");
|
|
1880
|
+
const files = response.Folder?.Files ?? response.Files ?? [];
|
|
1881
|
+
if (files.length > 0) {
|
|
1882
|
+
console.info(`[FortnoxClient] Inbox folder "${folderId}" contains ${files.length} file(s):`, files.map((f) => ({ id: f.Id, name: f.Name, size: f.Size })));
|
|
1883
|
+
}
|
|
1884
|
+
return response;
|
|
1885
|
+
}
|
|
1886
|
+
async getInboxFile(id) {
|
|
1887
|
+
const raw = await this.get(`/3/inbox/${id}`);
|
|
1888
|
+
return validate(InboxFileResponseSchema, raw, "InboxFileResponse");
|
|
1889
|
+
}
|
|
1890
|
+
async uploadToInbox(options) {
|
|
1891
|
+
const formData = new FormData;
|
|
1892
|
+
formData.append("file", options.file, options.filename);
|
|
1893
|
+
const path = options.folderId ? `/3/inbox?path=${options.folderId}` : "/3/inbox";
|
|
1894
|
+
const raw = await this.httpClient.uploadFormData(path, formData);
|
|
1895
|
+
return validate(InboxFileResponseSchema, raw, "InboxFileResponse");
|
|
1896
|
+
}
|
|
1897
|
+
async downloadFromInbox(id) {
|
|
1898
|
+
return this.httpClient.downloadBinary(`/3/inbox/${id}`);
|
|
1899
|
+
}
|
|
1900
|
+
async getArchiveFiles() {
|
|
1901
|
+
const raw = await this.get("/3/archive");
|
|
1902
|
+
return validate(ArchiveFilesResponseSchema, raw, "ArchiveFilesResponse");
|
|
1903
|
+
}
|
|
1904
|
+
async getArchiveFile(id) {
|
|
1905
|
+
const raw = await this.get(`/3/archive/${id}`);
|
|
1906
|
+
return validate(ArchiveFileResponseSchema, raw, "ArchiveFileResponse");
|
|
1907
|
+
}
|
|
1908
|
+
async uploadToArchive(options) {
|
|
1909
|
+
const formData = new FormData;
|
|
1910
|
+
formData.append("file", options.file, options.filename);
|
|
1911
|
+
const path = options.folderId ? `/3/archive?path=${options.folderId}` : "/3/archive";
|
|
1912
|
+
const raw = await this.httpClient.uploadFormData(path, formData);
|
|
1913
|
+
return validate(ArchiveFileResponseSchema, raw, "ArchiveFileResponse");
|
|
1914
|
+
}
|
|
1915
|
+
async downloadFromArchive(id) {
|
|
1916
|
+
return this.httpClient.downloadBinary(`/3/archive/${id}`);
|
|
1917
|
+
}
|
|
1918
|
+
async deleteFromArchive(id) {
|
|
1919
|
+
await this.delete(`/3/archive/${id}`);
|
|
1920
|
+
}
|
|
1921
|
+
async deleteFromInbox(id) {
|
|
1922
|
+
await this.delete(`/3/inbox/${id}`);
|
|
1923
|
+
}
|
|
1924
|
+
async getVouchers(financialYear, params) {
|
|
1925
|
+
const { limit, offset } = toFortnoxParams(params);
|
|
1926
|
+
const query = buildQueryString({
|
|
1927
|
+
financialyear: financialYear,
|
|
1928
|
+
limit,
|
|
1929
|
+
offset
|
|
1930
|
+
});
|
|
1931
|
+
const raw = await this.get(`/3/vouchers${query}`);
|
|
1932
|
+
const response = validate(VouchersResponseSchema, raw, "VouchersResponse");
|
|
1933
|
+
return {
|
|
1934
|
+
data: response.Vouchers,
|
|
1935
|
+
pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
async getVoucher(series, number, financialYear) {
|
|
1939
|
+
const raw = await this.get(`/3/vouchers/${series}/${number}?financialyear=${financialYear}`);
|
|
1940
|
+
return validate(VoucherResponseSchema, raw, "VoucherResponse");
|
|
1941
|
+
}
|
|
1942
|
+
async createVoucher(request) {
|
|
1943
|
+
const raw = await this.post("/3/vouchers", request);
|
|
1944
|
+
return validate(VoucherResponseSchema, raw, "VoucherResponse");
|
|
1945
|
+
}
|
|
1946
|
+
async getVoucherFileConnection(fileId) {
|
|
1947
|
+
const raw = await this.get(`/3/voucherfileconnections/${fileId}`);
|
|
1948
|
+
return validate(VoucherFileConnectionResponseSchema, raw, "VoucherFileConnectionResponse");
|
|
1949
|
+
}
|
|
1950
|
+
async createVoucherFileConnection(request) {
|
|
1951
|
+
const raw = await this.post("/3/voucherfileconnections", request);
|
|
1952
|
+
return validate(VoucherFileConnectionResponseSchema, raw, "VoucherFileConnectionResponse");
|
|
1953
|
+
}
|
|
1954
|
+
async deleteVoucherFileConnection(fileId) {
|
|
1955
|
+
await this.delete(`/3/voucherfileconnections/${fileId}`);
|
|
1956
|
+
}
|
|
1957
|
+
async getVoucherFileConnections() {
|
|
1958
|
+
const raw = await this.get("/3/voucherfileconnections");
|
|
1959
|
+
return validate(VoucherFileConnectionsResponseSchema, raw, "VoucherFileConnectionsResponse");
|
|
1960
|
+
}
|
|
1961
|
+
async getVoucherFileConnectionsForVoucher(series, number, year) {
|
|
1962
|
+
const response = await this.getVoucherFileConnections();
|
|
1963
|
+
return response.VoucherFileConnections.filter((conn) => conn.VoucherSeries === series && conn.VoucherNumber === number && conn.VoucherYear === year);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
// ../accounting/dist/accounting/tax-codes.js
|
|
1967
|
+
var SE_TAX_CODES = [
|
|
1968
|
+
{
|
|
1969
|
+
code: "SE_VAT_25_PURCHASE_DOMESTIC",
|
|
1970
|
+
description: "Inköp i Sverige, moms 25 %",
|
|
1971
|
+
kind: "VAT",
|
|
1972
|
+
direction: "PURCHASE",
|
|
1973
|
+
territory: "DOMESTIC",
|
|
1974
|
+
ratePercent: 25,
|
|
1975
|
+
isReverseCharge: false,
|
|
1976
|
+
isZeroRated: false,
|
|
1977
|
+
isExempt: false,
|
|
1978
|
+
posting: {
|
|
1979
|
+
inputVatAccountCode: "2641"
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
code: "SE_VAT_12_PURCHASE_DOMESTIC",
|
|
1984
|
+
description: "Inköp i Sverige, moms 12 %",
|
|
1985
|
+
kind: "VAT",
|
|
1986
|
+
direction: "PURCHASE",
|
|
1987
|
+
territory: "DOMESTIC",
|
|
1988
|
+
ratePercent: 12,
|
|
1989
|
+
isReverseCharge: false,
|
|
1990
|
+
isZeroRated: false,
|
|
1991
|
+
isExempt: false,
|
|
1992
|
+
posting: {
|
|
1993
|
+
inputVatAccountCode: "2641"
|
|
1994
|
+
}
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
code: "SE_VAT_6_PURCHASE_DOMESTIC",
|
|
1998
|
+
description: "Inköp i Sverige, moms 6 %",
|
|
1999
|
+
kind: "VAT",
|
|
2000
|
+
direction: "PURCHASE",
|
|
2001
|
+
territory: "DOMESTIC",
|
|
2002
|
+
ratePercent: 6,
|
|
2003
|
+
isReverseCharge: false,
|
|
2004
|
+
isZeroRated: false,
|
|
2005
|
+
isExempt: false,
|
|
2006
|
+
posting: {
|
|
2007
|
+
inputVatAccountCode: "2641"
|
|
2008
|
+
}
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
code: "SE_VAT_0_PURCHASE_DOMESTIC",
|
|
2012
|
+
description: "Inköp i Sverige, 0 % moms (momspliktigt men 0 %)",
|
|
2013
|
+
kind: "VAT",
|
|
2014
|
+
direction: "PURCHASE",
|
|
2015
|
+
territory: "DOMESTIC",
|
|
2016
|
+
ratePercent: 0,
|
|
2017
|
+
isReverseCharge: false,
|
|
2018
|
+
isZeroRated: true,
|
|
2019
|
+
isExempt: false,
|
|
2020
|
+
posting: {}
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
code: "SE_VAT_EXEMPT_PURCHASE",
|
|
2024
|
+
description: "Momsfria inköp (utanför momsens tillämpningsområde)",
|
|
2025
|
+
kind: "VAT",
|
|
2026
|
+
direction: "PURCHASE",
|
|
2027
|
+
territory: "DOMESTIC",
|
|
2028
|
+
ratePercent: 0,
|
|
2029
|
+
isReverseCharge: false,
|
|
2030
|
+
isZeroRated: false,
|
|
2031
|
+
isExempt: true,
|
|
2032
|
+
posting: {}
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
code: "SE_VAT_25_SALE_DOMESTIC",
|
|
2036
|
+
description: "Försäljning i Sverige, moms 25 %",
|
|
2037
|
+
kind: "VAT",
|
|
2038
|
+
direction: "SALE",
|
|
2039
|
+
territory: "DOMESTIC",
|
|
2040
|
+
ratePercent: 25,
|
|
2041
|
+
isReverseCharge: false,
|
|
2042
|
+
isZeroRated: false,
|
|
2043
|
+
isExempt: false,
|
|
2044
|
+
posting: {
|
|
2045
|
+
outputVatAccountCode: "2611"
|
|
2046
|
+
}
|
|
2047
|
+
},
|
|
2048
|
+
{
|
|
2049
|
+
code: "SE_VAT_12_SALE_DOMESTIC",
|
|
2050
|
+
description: "Försäljning i Sverige, moms 12 %",
|
|
2051
|
+
kind: "VAT",
|
|
2052
|
+
direction: "SALE",
|
|
2053
|
+
territory: "DOMESTIC",
|
|
2054
|
+
ratePercent: 12,
|
|
2055
|
+
isReverseCharge: false,
|
|
2056
|
+
isZeroRated: false,
|
|
2057
|
+
isExempt: false,
|
|
2058
|
+
posting: {
|
|
2059
|
+
outputVatAccountCode: "2611"
|
|
2060
|
+
}
|
|
2061
|
+
},
|
|
2062
|
+
{
|
|
2063
|
+
code: "SE_VAT_6_SALE_DOMESTIC",
|
|
2064
|
+
description: "Försäljning i Sverige, moms 6 %",
|
|
2065
|
+
kind: "VAT",
|
|
2066
|
+
direction: "SALE",
|
|
2067
|
+
territory: "DOMESTIC",
|
|
2068
|
+
ratePercent: 6,
|
|
2069
|
+
isReverseCharge: false,
|
|
2070
|
+
isZeroRated: false,
|
|
2071
|
+
isExempt: false,
|
|
2072
|
+
posting: {
|
|
2073
|
+
outputVatAccountCode: "2611"
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
code: "SE_VAT_0_SALE_DOMESTIC",
|
|
2078
|
+
description: "Momsfri försäljning i Sverige (undantagen/0 %)",
|
|
2079
|
+
kind: "VAT",
|
|
2080
|
+
direction: "SALE",
|
|
2081
|
+
territory: "DOMESTIC",
|
|
2082
|
+
ratePercent: 0,
|
|
2083
|
+
isReverseCharge: false,
|
|
2084
|
+
isZeroRated: false,
|
|
2085
|
+
isExempt: true,
|
|
2086
|
+
posting: {}
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
code: "SE_VAT_25_PURCHASE_EU_GOODS_RC",
|
|
2090
|
+
description: "Inköp av varor från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
2091
|
+
kind: "VAT",
|
|
2092
|
+
direction: "PURCHASE",
|
|
2093
|
+
territory: "EU",
|
|
2094
|
+
ratePercent: 25,
|
|
2095
|
+
isReverseCharge: true,
|
|
2096
|
+
isZeroRated: false,
|
|
2097
|
+
isExempt: false,
|
|
2098
|
+
posting: {
|
|
2099
|
+
reverseChargeOutputAccountCode: "2614",
|
|
2100
|
+
reverseChargeInputAccountCode: "2645"
|
|
2101
|
+
}
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
code: "SE_VAT_25_PURCHASE_EU_SERVICES_RC",
|
|
2105
|
+
description: "Inköp av tjänster från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
2106
|
+
kind: "VAT",
|
|
2107
|
+
direction: "PURCHASE",
|
|
2108
|
+
territory: "EU",
|
|
2109
|
+
ratePercent: 25,
|
|
2110
|
+
isReverseCharge: true,
|
|
2111
|
+
isZeroRated: false,
|
|
2112
|
+
isExempt: false,
|
|
2113
|
+
posting: {
|
|
2114
|
+
reverseChargeOutputAccountCode: "2614",
|
|
2115
|
+
reverseChargeInputAccountCode: "2645"
|
|
2116
|
+
}
|
|
2117
|
+
},
|
|
2118
|
+
{
|
|
2119
|
+
code: "SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC",
|
|
2120
|
+
description: "Inköp av tjänster från land utanför EU, 25 %, omvänd skattskyldighet",
|
|
2121
|
+
kind: "VAT",
|
|
2122
|
+
direction: "PURCHASE",
|
|
2123
|
+
territory: "OUTSIDE_EU",
|
|
2124
|
+
ratePercent: 25,
|
|
2125
|
+
isReverseCharge: true,
|
|
2126
|
+
isZeroRated: false,
|
|
2127
|
+
isExempt: false,
|
|
2128
|
+
posting: {
|
|
2129
|
+
reverseChargeOutputAccountCode: "2614",
|
|
2130
|
+
reverseChargeInputAccountCode: "2645"
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
code: "SE_VAT_0_SALE_EU_GOODS_B2B",
|
|
2135
|
+
description: "Varuförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
2136
|
+
kind: "VAT",
|
|
2137
|
+
direction: "SALE",
|
|
2138
|
+
territory: "EU",
|
|
2139
|
+
ratePercent: 0,
|
|
2140
|
+
isReverseCharge: false,
|
|
2141
|
+
isZeroRated: true,
|
|
2142
|
+
isExempt: false,
|
|
2143
|
+
posting: {}
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
code: "SE_VAT_0_SALE_EU_SERVICES_B2B",
|
|
2147
|
+
description: "Tjänsteförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
2148
|
+
kind: "VAT",
|
|
2149
|
+
direction: "SALE",
|
|
2150
|
+
territory: "EU",
|
|
2151
|
+
ratePercent: 0,
|
|
2152
|
+
isReverseCharge: false,
|
|
2153
|
+
isZeroRated: true,
|
|
2154
|
+
isExempt: false,
|
|
2155
|
+
posting: {}
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
code: "SE_VAT_RC_SALE_DOMESTIC_CONSTRUCTION",
|
|
2159
|
+
description: "Försäljning av byggtjänster med omvänd skattskyldighet (ingen moms på fakturan)",
|
|
2160
|
+
kind: "VAT",
|
|
2161
|
+
direction: "SALE",
|
|
2162
|
+
territory: "DOMESTIC",
|
|
2163
|
+
ratePercent: 0,
|
|
2164
|
+
isReverseCharge: true,
|
|
2165
|
+
isZeroRated: false,
|
|
2166
|
+
isExempt: false,
|
|
2167
|
+
posting: {}
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
code: "SE_VAT_RC_PURCHASE_DOMESTIC_CONSTRUCTION",
|
|
2171
|
+
description: "Inköp av byggtjänster med omvänd skattskyldighet (du beräknar svensk moms)",
|
|
2172
|
+
kind: "VAT",
|
|
2173
|
+
direction: "PURCHASE",
|
|
2174
|
+
territory: "DOMESTIC",
|
|
2175
|
+
ratePercent: 25,
|
|
2176
|
+
isReverseCharge: true,
|
|
2177
|
+
isZeroRated: false,
|
|
2178
|
+
isExempt: false,
|
|
2179
|
+
posting: {
|
|
2180
|
+
reverseChargeOutputAccountCode: "2617",
|
|
2181
|
+
reverseChargeInputAccountCode: "2647"
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
];
|
|
2185
|
+
var TAX_CODE_MAP = new Map(SE_TAX_CODES.map((code) => [code.code, code]));
|
|
2186
|
+
// ../accounting/dist/yaml/yaml-serializer.js
|
|
2187
|
+
import YAML from "yaml";
|
|
2188
|
+
// ../accounting/dist/transformers/fortnox.js
|
|
2189
|
+
function mapFortnoxVoucherToJournalEntry(voucher, fiscalYear) {
|
|
2190
|
+
const series = voucher.VoucherSeries;
|
|
2191
|
+
const externalId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}`;
|
|
2192
|
+
let totalDebit = 0;
|
|
2193
|
+
let totalCredit = 0;
|
|
2194
|
+
const lines = (voucher.VoucherRows ?? []).map((row, idx) => {
|
|
2195
|
+
totalDebit += row.Debit ?? 0;
|
|
2196
|
+
totalCredit += row.Credit ?? 0;
|
|
2197
|
+
const memo = row.TransactionInformation || row.Description || undefined;
|
|
2198
|
+
return {
|
|
2199
|
+
lineNumber: idx + 1,
|
|
2200
|
+
account: String(row.Account),
|
|
2201
|
+
debit: { amount: row.Debit ?? 0, currency: "SEK" },
|
|
2202
|
+
credit: { amount: row.Credit ?? 0, currency: "SEK" },
|
|
2203
|
+
memo: memo ? String(memo) : undefined,
|
|
2204
|
+
costCenter: row.CostCenter ?? undefined
|
|
2205
|
+
};
|
|
2206
|
+
});
|
|
2207
|
+
return {
|
|
2208
|
+
series,
|
|
2209
|
+
entryNumber: voucher.VoucherNumber ?? 0,
|
|
2210
|
+
entryDate: voucher.TransactionDate ?? voucher.VoucherDate ?? "",
|
|
2211
|
+
description: voucher.Description ?? "",
|
|
2212
|
+
status: "POSTED",
|
|
2213
|
+
currency: "SEK",
|
|
2214
|
+
externalId,
|
|
2215
|
+
totalDebit: { amount: totalDebit, currency: "SEK" },
|
|
2216
|
+
totalCredit: { amount: totalCredit, currency: "SEK" },
|
|
2217
|
+
lines,
|
|
2218
|
+
sourceIntegration: "fortnox",
|
|
2219
|
+
sourceSyncedAt: new Date().toISOString(),
|
|
2220
|
+
voucherNumber: voucher.VoucherNumber,
|
|
2221
|
+
voucherSeriesCode: voucher.VoucherSeries
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
// ../accounting/dist/transformers/bokio.js
|
|
2225
|
+
function mapBokioEntryToJournalEntry(entry) {
|
|
2226
|
+
const voucherNumberMatch = entry.journalEntryNumber.match(/\d+/);
|
|
2227
|
+
const voucherNumber = voucherNumberMatch ? parseInt(voucherNumberMatch[0], 10) : 0;
|
|
2228
|
+
const seriesCode = entry.journalEntryNumber.replace(/\d+/g, "") || undefined;
|
|
2229
|
+
const lines = entry.items.map((item, idx) => ({
|
|
2230
|
+
lineNumber: idx + 1,
|
|
2231
|
+
account: String(item.account),
|
|
2232
|
+
debit: { amount: item.debit, currency: "SEK" },
|
|
2233
|
+
credit: { amount: item.credit, currency: "SEK" }
|
|
2234
|
+
}));
|
|
2235
|
+
const totalDebit = entry.items.reduce((sum, item) => sum + item.debit, 0);
|
|
2236
|
+
const totalCredit = entry.items.reduce((sum, item) => sum + item.credit, 0);
|
|
2237
|
+
return {
|
|
2238
|
+
series: seriesCode,
|
|
2239
|
+
entryNumber: voucherNumber,
|
|
2240
|
+
entryDate: entry.date,
|
|
2241
|
+
description: entry.title,
|
|
2242
|
+
status: "POSTED",
|
|
2243
|
+
currency: "SEK",
|
|
2244
|
+
externalId: entry.id,
|
|
2245
|
+
totalDebit: { amount: totalDebit, currency: "SEK" },
|
|
2246
|
+
totalCredit: { amount: totalCredit, currency: "SEK" },
|
|
2247
|
+
lines,
|
|
2248
|
+
sourceIntegration: "bokio",
|
|
2249
|
+
sourceSyncedAt: new Date().toISOString(),
|
|
2250
|
+
reversingEntryExternalId: entry.reversingJournalEntryId ?? undefined,
|
|
2251
|
+
reversedByEntryExternalId: entry.reversedByJournalEntryId ?? undefined
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
// ../accounting/dist/storage/filesystem.js
|
|
2255
|
+
import * as fs from "node:fs/promises";
|
|
2256
|
+
import * as path from "node:path";
|
|
2257
|
+
import * as crypto2 from "node:crypto";
|
|
2258
|
+
|
|
2259
|
+
class FilesystemStorageService {
|
|
2260
|
+
basePath;
|
|
2261
|
+
constructor(basePath) {
|
|
2262
|
+
this.basePath = basePath;
|
|
2263
|
+
}
|
|
2264
|
+
resolvePath(relativePath) {
|
|
2265
|
+
return path.join(this.basePath, relativePath);
|
|
2266
|
+
}
|
|
2267
|
+
contentHash(content) {
|
|
2268
|
+
return crypto2.createHash("sha1").update(content).digest("hex");
|
|
2269
|
+
}
|
|
2270
|
+
async readFile(filePath) {
|
|
2271
|
+
const absolutePath = this.resolvePath(filePath);
|
|
2272
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
2273
|
+
return {
|
|
717
2274
|
content,
|
|
718
2275
|
sha: this.contentHash(content)
|
|
719
2276
|
};
|
|
@@ -773,7 +2330,7 @@ class FilesystemStorageService {
|
|
|
773
2330
|
await fs.rename(absoluteOldPath, absoluteNewPath);
|
|
774
2331
|
}
|
|
775
2332
|
}
|
|
776
|
-
// ../
|
|
2333
|
+
// ../accounting/dist/utils/file-namer.js
|
|
777
2334
|
function journalEntryDirName(entry) {
|
|
778
2335
|
const series = entry.voucher_series_code || "";
|
|
779
2336
|
const voucher = String(entry.voucher_number || "000").padStart(3, "0");
|
|
@@ -807,14 +2364,14 @@ function slugify(text, maxLength = 30) {
|
|
|
807
2364
|
function sanitizeOrgName(name) {
|
|
808
2365
|
return slugify(name, 50);
|
|
809
2366
|
}
|
|
810
|
-
// ../
|
|
811
|
-
import
|
|
812
|
-
function
|
|
813
|
-
return
|
|
2367
|
+
// ../accounting/dist/utils/yaml.js
|
|
2368
|
+
import YAML2 from "yaml";
|
|
2369
|
+
function parseYaml2(yaml) {
|
|
2370
|
+
return YAML2.parse(yaml);
|
|
814
2371
|
}
|
|
815
|
-
function
|
|
2372
|
+
function toYaml2(data) {
|
|
816
2373
|
const cleanData = removeNulls(data);
|
|
817
|
-
return
|
|
2374
|
+
return YAML2.stringify(cleanData, {
|
|
818
2375
|
indent: 2,
|
|
819
2376
|
lineWidth: 120,
|
|
820
2377
|
minContentWidth: 0,
|
|
@@ -840,9 +2397,257 @@ function removeNulls(obj) {
|
|
|
840
2397
|
}
|
|
841
2398
|
return obj;
|
|
842
2399
|
}
|
|
843
|
-
// ../
|
|
2400
|
+
// ../accounting/dist/utils/git.js
|
|
2401
|
+
import simpleGit from "simple-git";
|
|
844
2402
|
import * as fs2 from "node:fs/promises";
|
|
845
2403
|
import * as path2 from "node:path";
|
|
2404
|
+
var DEFAULT_GITIGNORE = `# Environment files with secrets
|
|
2405
|
+
.env
|
|
2406
|
+
.env.*
|
|
2407
|
+
|
|
2408
|
+
# CLI cache
|
|
2409
|
+
.kvitton/
|
|
2410
|
+
|
|
2411
|
+
# OS files
|
|
2412
|
+
.DS_Store
|
|
2413
|
+
Thumbs.db
|
|
2414
|
+
|
|
2415
|
+
# Editor files
|
|
2416
|
+
.vscode/
|
|
2417
|
+
.idea/
|
|
2418
|
+
*.swp
|
|
2419
|
+
`;
|
|
2420
|
+
async function initGitRepo(repoPath) {
|
|
2421
|
+
const git = simpleGit(repoPath);
|
|
2422
|
+
await git.init();
|
|
2423
|
+
await fs2.writeFile(path2.join(repoPath, ".gitignore"), DEFAULT_GITIGNORE);
|
|
2424
|
+
}
|
|
2425
|
+
async function commitAll(repoPath, message) {
|
|
2426
|
+
const git = simpleGit(repoPath);
|
|
2427
|
+
await git.add(".");
|
|
2428
|
+
const status = await git.status();
|
|
2429
|
+
if (status.files.length > 0) {
|
|
2430
|
+
await git.commit(message);
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2433
|
+
return false;
|
|
2434
|
+
}
|
|
2435
|
+
// ../accounting/dist/utils/templates.js
|
|
2436
|
+
var AGENTS_TEMPLATE = `# AGENTS.md
|
|
2437
|
+
|
|
2438
|
+
This is an accounting data repository for {{COMPANY_NAME}} (Swedish company), storing financial records, journal entries, and documents. It integrates with {{PROVIDER}} (Swedish accounting software) for document management.
|
|
2439
|
+
|
|
2440
|
+
## Repository Structure
|
|
2441
|
+
|
|
2442
|
+
\`\`\`
|
|
2443
|
+
/
|
|
2444
|
+
├── accounts.yaml # Swedish chart of accounts (account codes 1000-9999)
|
|
2445
|
+
├── journal-entries/ # Main accounting data organized by fiscal year
|
|
2446
|
+
│ └── FY-YYYY/ # Fiscal year folders (FY-2014 through FY-2026)
|
|
2447
|
+
│ ├── _fiscal-year.yaml
|
|
2448
|
+
│ └── [SERIES]-[NUM]-[DATE]-[DESC]/
|
|
2449
|
+
│ ├── entry.yaml
|
|
2450
|
+
│ └── *.pdf # Supporting documents
|
|
2451
|
+
│
|
|
2452
|
+
├── inbox/ # Raw incoming documents (no entry yet)
|
|
2453
|
+
│ └── [DATE]-[NAME]/
|
|
2454
|
+
│ ├── documents.yaml
|
|
2455
|
+
│ └── *.pdf
|
|
2456
|
+
└── drafts/ # Documents with entries, ready to post
|
|
2457
|
+
└── [DATE]-[NAME]/ # e.g., "2025-12-24-anthropic"
|
|
2458
|
+
├── documents.yaml
|
|
2459
|
+
├── entry.yaml
|
|
2460
|
+
└── *.pdf
|
|
2461
|
+
\`\`\`
|
|
2462
|
+
|
|
2463
|
+
## CLI Commands
|
|
2464
|
+
|
|
2465
|
+
Use the \`npx kvitton-cli\` CLI to interact with this repository:
|
|
2466
|
+
|
|
2467
|
+
\`\`\`bash
|
|
2468
|
+
# Sync data from provider
|
|
2469
|
+
npx kvitton-cli sync-journal # Sync journal entries from accounting provider
|
|
2470
|
+
npx kvitton-cli sync-inbox # Download inbox files from accounting provider
|
|
2471
|
+
npx kvitton-cli company-info # Display company information
|
|
2472
|
+
|
|
2473
|
+
# Create journal entries
|
|
2474
|
+
npx kvitton-cli create-entry --path <inbox-dir-or-file> \\
|
|
2475
|
+
--tax-code <code> --base-account <acc> --balancing-account <acc> \\
|
|
2476
|
+
--series <A-K> --entry-number <num>
|
|
2477
|
+
|
|
2478
|
+
# For raw files (not from inbox), also provide:
|
|
2479
|
+
# --description "Vendor name" --document-date 2025-01-15 --amount 1234.50
|
|
2480
|
+
|
|
2481
|
+
# Manage inbox
|
|
2482
|
+
npx kvitton-cli discard <inbox-directory> # Remove item, mark for deletion from provider
|
|
2483
|
+
npx kvitton-cli parse-pdf -f <file.pdf> # Extract text from PDF (for analysis)
|
|
2484
|
+
|
|
2485
|
+
# Currency conversion
|
|
2486
|
+
npx kvitton-cli convert-currency --amount <num> --currency <code> [--date YYYY-MM-DD]
|
|
2487
|
+
# Converts foreign currency to SEK using Riksbank exchange rates
|
|
2488
|
+
# Caches rates locally for efficiency
|
|
2489
|
+
|
|
2490
|
+
# Generate journal entry lines (alternative to create-entry for adding lines)
|
|
2491
|
+
npx kvitton-cli generate-lines --inbox-dir <path> \\
|
|
2492
|
+
--tax-code <code> --base-amount <num> --base-account <acc> \\
|
|
2493
|
+
--balancing-account <acc> [--memo "description"] [--currency USD]
|
|
2494
|
+
|
|
2495
|
+
# List available tax codes
|
|
2496
|
+
npx kvitton-cli list-tax-codes
|
|
2497
|
+
|
|
2498
|
+
# Update instructions
|
|
2499
|
+
npx kvitton-cli update # Update AGENTS.md to latest version
|
|
2500
|
+
\`\`\`
|
|
2501
|
+
|
|
2502
|
+
**Currency Conversion:** Both \`create-entry\` and \`generate-lines\` automatically convert to SEK when \`--currency\` is different from SEK, using Riksbank exchange rates for the entry date.
|
|
2503
|
+
|
|
2504
|
+
## Workflow Overview
|
|
2505
|
+
|
|
2506
|
+
\`\`\`
|
|
2507
|
+
inbox/ → (create-entry) → drafts/ → (user confirms) → journal-entries/ → (sync-journal) → Provider
|
|
2508
|
+
\`\`\`
|
|
2509
|
+
|
|
2510
|
+
## Bookkeeping New Entries
|
|
2511
|
+
|
|
2512
|
+
**Important:** Always ask the user for clarification if anything is unclear about the transaction before creating an entry.
|
|
2513
|
+
|
|
2514
|
+
To create a journal entry from an inbox document:
|
|
2515
|
+
|
|
2516
|
+
1. **Read the document** - Use \`npx kvitton-cli parse-pdf\` for PDFs to understand the content
|
|
2517
|
+
2. **Update documents.yaml** - After parsing, populate missing metadata in \`documents.yaml\`:
|
|
2518
|
+
- \`documentDate\`: Invoice/receipt date (YYYY-MM-DD)
|
|
2519
|
+
- \`description\`: Vendor or document name
|
|
2520
|
+
- \`totalAmount\`: With \`amount\` (string) and \`currency\` (e.g., EUR, SEK)
|
|
2521
|
+
3. **Look for similar entries** - Search \`journal-entries/\` and \`drafts/\` for entries from the same vendor or similar transaction types to learn which accounts and tax codes were used previously
|
|
2522
|
+
4. **Check accounts** - Review \`accounts.yaml\` for appropriate expense/revenue accounts
|
|
2523
|
+
5. **Determine tax code** - Based on vendor location and transaction type (see Tax Codes below). Explain your reasoning and describe the tax code meaning to the user
|
|
2524
|
+
6. **Ask for confirmation** - Before creating, confirm with the user: vendor, amount, accounts (with names), and tax code (with description)
|
|
2525
|
+
7. **Create entry** - Run \`npx kvitton-cli create-entry\` with the appropriate parameters
|
|
2526
|
+
8. **Files moved to drafts** - The inbox item is automatically moved to \`drafts/\` with \`entry.yaml\`
|
|
2527
|
+
|
|
2528
|
+
To discard unwanted inbox items:
|
|
2529
|
+
\`\`\`bash
|
|
2530
|
+
npx kvitton-cli discard inbox/2025-01-15-spam-document
|
|
2531
|
+
\`\`\`
|
|
2532
|
+
This removes the directory and marks the item for deletion from the provider on next sync.
|
|
2533
|
+
|
|
2534
|
+
## Posting Entries
|
|
2535
|
+
|
|
2536
|
+
**Important:** Moving entries to \`journal-entries/\` posts them to the real ledger. Always confirm with the user before posting.
|
|
2537
|
+
|
|
2538
|
+
When the user confirms draft entries are ready to post:
|
|
2539
|
+
|
|
2540
|
+
1. **Determine entry number** - Check existing entries in \`journal-entries/FY-YYYY/\` for the series to find the next available number
|
|
2541
|
+
2. **Move to journal-entries** - Rename the draft directory to the proper format:
|
|
2542
|
+
\`\`\`
|
|
2543
|
+
drafts/2025-01-15-anthropic/
|
|
2544
|
+
→ journal-entries/FY-2025/A-042-2025-01-15-anthropic/
|
|
2545
|
+
\`\`\`
|
|
2546
|
+
Format: \`{SERIES}-{NUM}-{DATE}-{DESC}/\`
|
|
2547
|
+
3. **Update entry.yaml** - Ensure \`entryNumber\` matches the directory and \`status\` is set appropriately
|
|
2548
|
+
4. **Sync to provider** - Run \`npx kvitton-cli sync-journal\` to post the entries to {{PROVIDER}}
|
|
2549
|
+
|
|
2550
|
+
## Tax Codes
|
|
2551
|
+
|
|
2552
|
+
### Common Tax Code Patterns
|
|
2553
|
+
|
|
2554
|
+
| Scenario | Tax Code | Accounts |
|
|
2555
|
+
|----------|----------|----------|
|
|
2556
|
+
| Domestic purchase 25% | \`SE_VAT_25_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
|
|
2557
|
+
| Domestic purchase 12% | \`SE_VAT_12_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
|
|
2558
|
+
| Non-EU services (US SaaS) | \`SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC\` | 5422 + 2645 + 2614 + 2820 |
|
|
2559
|
+
| EU services | \`SE_VAT_25_PURCHASE_EU_SERVICES_RC\` | Expense + 2645 + 2614 + 2440 |
|
|
2560
|
+
| Exempt purchase | \`SE_VAT_EXEMPT_PURCHASE\` | Expense + balancing |
|
|
2561
|
+
|
|
2562
|
+
**Notes:**
|
|
2563
|
+
- \`RC\` = Reverse Charge (buyer reports VAT instead of seller)
|
|
2564
|
+
- Use \`--list-tax-codes\` to see all available codes with descriptions
|
|
2565
|
+
- Account 2641 = Incoming VAT (domestic), 2645 = Incoming VAT (reverse charge)
|
|
2566
|
+
- Account 2614 = Outgoing VAT (reverse charge)
|
|
2567
|
+
|
|
2568
|
+
## Entities
|
|
2569
|
+
|
|
2570
|
+
### Journal Entry (entry.yaml)
|
|
2571
|
+
|
|
2572
|
+
\`\`\`yaml
|
|
2573
|
+
series: A # A=Admin, B=Customer invoices, C=Customer payments,
|
|
2574
|
+
# D=Supplier invoices, E=Supplier payments, K=Salary
|
|
2575
|
+
entryNumber: 1
|
|
2576
|
+
entryDate: 2024-01-03
|
|
2577
|
+
description: Transaction description
|
|
2578
|
+
status: POSTED # or DRAFT
|
|
2579
|
+
currency: SEK
|
|
2580
|
+
lines:
|
|
2581
|
+
- account: "1930" # Account code from accounts.yaml
|
|
2582
|
+
debit: 1000.00 # or credit:
|
|
2583
|
+
memo: Account description
|
|
2584
|
+
\`\`\`
|
|
2585
|
+
|
|
2586
|
+
### Document Metadata (documents.yaml)
|
|
2587
|
+
|
|
2588
|
+
One entry can have multiple supporting documents (e.g., invoice + receipt, contract + amendment).
|
|
2589
|
+
|
|
2590
|
+
\`\`\`yaml
|
|
2591
|
+
- fileName: invoice.pdf
|
|
2592
|
+
mimeType: application/pdf
|
|
2593
|
+
sourceIntegration: {{PROVIDER_LOWER}}
|
|
2594
|
+
sourceId: UUID-1
|
|
2595
|
+
description: Vendor invoice
|
|
2596
|
+
documentDate: 2024-01-15
|
|
2597
|
+
totalAmount:
|
|
2598
|
+
amount: "1250.00"
|
|
2599
|
+
currency: SEK
|
|
2600
|
+
- fileName: receipt.pdf
|
|
2601
|
+
mimeType: application/pdf
|
|
2602
|
+
sourceIntegration: {{PROVIDER_LOWER}}
|
|
2603
|
+
sourceId: UUID-2
|
|
2604
|
+
description: Payment receipt
|
|
2605
|
+
documentDate: 2024-01-16
|
|
2606
|
+
\`\`\`
|
|
2607
|
+
|
|
2608
|
+
### Fiscal Year (_fiscal-year.yaml)
|
|
2609
|
+
|
|
2610
|
+
\`\`\`yaml
|
|
2611
|
+
name: FY 2024
|
|
2612
|
+
startDate: 2024-01-01
|
|
2613
|
+
endDate: 2024-12-31
|
|
2614
|
+
status: OPEN
|
|
2615
|
+
externalId: "12" # Provider reference
|
|
2616
|
+
\`\`\`
|
|
2617
|
+
|
|
2618
|
+
## Account Codes
|
|
2619
|
+
|
|
2620
|
+
Swedish BAS account codes are organized by ranges:
|
|
2621
|
+
|
|
2622
|
+
- **1xxx** - Assets (Tillgångar)
|
|
2623
|
+
- **2xxx** - Liabilities & Equity (Skulder och eget kapital)
|
|
2624
|
+
- **3xxx** - Revenue (Intäkter)
|
|
2625
|
+
- **4xxx** - Cost of goods sold (Inköp)
|
|
2626
|
+
- **5xxx-6xxx** - Operating expenses (Kostnader)
|
|
2627
|
+
- **7xxx** - Personnel costs (Personalkostnader)
|
|
2628
|
+
- **8xxx** - Financial items (Finansiella poster)
|
|
2629
|
+
- **9xxx** - Year-end allocations (Bokslutsdispositioner)
|
|
2630
|
+
|
|
2631
|
+
Common accounts:
|
|
2632
|
+
- \`1930\` - Business bank account
|
|
2633
|
+
- \`2440\` - Supplier payables
|
|
2634
|
+
- \`2610\` - Outgoing VAT 25%
|
|
2635
|
+
- \`2640\` - Incoming VAT
|
|
2636
|
+
- \`2820\` - Short-term liabilities (credit card, etc.)
|
|
2637
|
+
- \`4000\` - Cost of goods sold
|
|
2638
|
+
- \`5420\` - Software and IT services
|
|
2639
|
+
- \`6570\` - Bank charges
|
|
2640
|
+
`;
|
|
2641
|
+
function getAgentsTemplate() {
|
|
2642
|
+
return AGENTS_TEMPLATE;
|
|
2643
|
+
}
|
|
2644
|
+
function renderAgentsTemplate(options) {
|
|
2645
|
+
const template = getAgentsTemplate();
|
|
2646
|
+
return template.replace(/\{\{COMPANY_NAME\}\}/g, options.companyName).replace(/\{\{PROVIDER\}\}/g, options.provider).replace(/\{\{PROVIDER_LOWER\}\}/g, options.provider.toLowerCase());
|
|
2647
|
+
}
|
|
2648
|
+
// ../accounting/dist/services/document-download.js
|
|
2649
|
+
import * as fs3 from "node:fs/promises";
|
|
2650
|
+
import * as path3 from "node:path";
|
|
846
2651
|
function slugify2(text, maxLength = 40) {
|
|
847
2652
|
return text.toLowerCase().slice(0, maxLength).replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
848
2653
|
}
|
|
@@ -865,12 +2670,12 @@ async function downloadFilesForEntry(options) {
|
|
|
865
2670
|
let existingDocs = [];
|
|
866
2671
|
try {
|
|
867
2672
|
const { content } = await storage.readFile(documentsPath);
|
|
868
|
-
existingDocs =
|
|
2673
|
+
existingDocs = parseYaml2(content) || [];
|
|
869
2674
|
} catch {}
|
|
870
2675
|
const downloadedSourceIds = new Set(existingDocs.filter((d) => d.sourceIntegration === sourceIntegration).map((d) => d.sourceId));
|
|
871
2676
|
let filesDownloaded = 0;
|
|
872
|
-
const absoluteDir =
|
|
873
|
-
await
|
|
2677
|
+
const absoluteDir = path3.join(repoPath, entryDir);
|
|
2678
|
+
await fs3.mkdir(absoluteDir, { recursive: true });
|
|
874
2679
|
for (const file of linkedFiles) {
|
|
875
2680
|
if (downloadedSourceIds.has(file.id)) {
|
|
876
2681
|
continue;
|
|
@@ -886,9 +2691,9 @@ async function downloadFilesForEntry(options) {
|
|
|
886
2691
|
filename = `${baseName}-${counter}${ext}`;
|
|
887
2692
|
counter++;
|
|
888
2693
|
}
|
|
889
|
-
const filePath =
|
|
2694
|
+
const filePath = path3.join(absoluteDir, filename);
|
|
890
2695
|
const buffer = Buffer.from(result.data);
|
|
891
|
-
await
|
|
2696
|
+
await fs3.writeFile(filePath, buffer);
|
|
892
2697
|
existingDocs.push({
|
|
893
2698
|
fileName: filename,
|
|
894
2699
|
mimeType: file.contentType || result.contentType,
|
|
@@ -902,55 +2707,33 @@ async function downloadFilesForEntry(options) {
|
|
|
902
2707
|
}
|
|
903
2708
|
}
|
|
904
2709
|
if (filesDownloaded > 0) {
|
|
905
|
-
await storage.writeFile(documentsPath,
|
|
2710
|
+
await storage.writeFile(documentsPath, toYaml2(existingDocs));
|
|
906
2711
|
}
|
|
907
2712
|
return filesDownloaded;
|
|
908
2713
|
}
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
.vscode/
|
|
926
|
-
.idea/
|
|
927
|
-
*.swp
|
|
928
|
-
`;
|
|
929
|
-
async function initGitRepo(repoPath) {
|
|
930
|
-
const git = simpleGit(repoPath);
|
|
931
|
-
await git.init();
|
|
932
|
-
await fs3.writeFile(path3.join(repoPath, ".gitignore"), GITIGNORE);
|
|
933
|
-
}
|
|
934
|
-
async function commitAll(repoPath, message) {
|
|
935
|
-
const git = simpleGit(repoPath);
|
|
936
|
-
await git.add(".");
|
|
937
|
-
const status = await git.status();
|
|
938
|
-
if (status.files.length > 0) {
|
|
939
|
-
await git.commit(message);
|
|
2714
|
+
// ../accounting/dist/services/fortnox-journal.js
|
|
2715
|
+
async function syncFortnoxChartOfAccounts(client2, storage) {
|
|
2716
|
+
const allAccounts = [];
|
|
2717
|
+
let hasMore = true;
|
|
2718
|
+
let page = 1;
|
|
2719
|
+
while (hasMore) {
|
|
2720
|
+
const response = await client2.getAccounts({ page, pageSize: 500 });
|
|
2721
|
+
for (const account of response.data) {
|
|
2722
|
+
allAccounts.push({
|
|
2723
|
+
code: account.Number.toString(),
|
|
2724
|
+
name: account.Description,
|
|
2725
|
+
description: account.Description
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
hasMore = response.pagination.hasNextPage;
|
|
2729
|
+
page++;
|
|
940
2730
|
}
|
|
2731
|
+
allAccounts.sort((a, b) => parseInt(a.code) - parseInt(b.code));
|
|
2732
|
+
const yamlContent = toYaml2({ accounts: allAccounts });
|
|
2733
|
+
await storage.writeFile("accounts.yaml", yamlContent);
|
|
2734
|
+
return { accountsCount: allAccounts.length };
|
|
941
2735
|
}
|
|
942
|
-
|
|
943
|
-
// src/lib/env.ts
|
|
944
|
-
import * as fs4 from "node:fs/promises";
|
|
945
|
-
import * as path4 from "node:path";
|
|
946
|
-
async function createEnvFile(repoPath, variables) {
|
|
947
|
-
const lines = Object.entries(variables).map(([key, value]) => `${key}=${value}`).join(`
|
|
948
|
-
`);
|
|
949
|
-
await fs4.writeFile(path4.join(repoPath, ".env"), `${lines}
|
|
950
|
-
`);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// src/sync/bokio-sync.ts
|
|
2736
|
+
// ../accounting/dist/services/bokio-journal.js
|
|
954
2737
|
function createBokioDownloader(client2) {
|
|
955
2738
|
return {
|
|
956
2739
|
async getFilesForEntry(journalEntryId) {
|
|
@@ -968,17 +2751,32 @@ function createBokioDownloader(client2) {
|
|
|
968
2751
|
}
|
|
969
2752
|
};
|
|
970
2753
|
}
|
|
971
|
-
|
|
972
|
-
|
|
2754
|
+
function findFiscalYear(date, fiscalYears) {
|
|
2755
|
+
return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
|
|
2756
|
+
}
|
|
2757
|
+
async function syncBokioJournalEntries(client2, storage, options = {}) {
|
|
2758
|
+
const { downloadFiles = false, targetYear, onProgress = () => {} } = options;
|
|
973
2759
|
const fiscalYearsResponse = await client2.getFiscalYears();
|
|
974
|
-
const
|
|
2760
|
+
const allFiscalYears = fiscalYearsResponse.data;
|
|
2761
|
+
const fiscalYears = targetYear ? allFiscalYears.filter((fy) => parseInt(fy.startDate.slice(0, 4), 10) === targetYear) : allFiscalYears;
|
|
2762
|
+
if (targetYear && fiscalYears.length === 0) {
|
|
2763
|
+
return {
|
|
2764
|
+
entriesCount: 0,
|
|
2765
|
+
newEntries: 0,
|
|
2766
|
+
existingEntries: 0,
|
|
2767
|
+
fiscalYearsCount: 0,
|
|
2768
|
+
entriesWithFilesDownloaded: 0
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
975
2771
|
const firstPage = await client2.getJournalEntries({ page: 1, pageSize: 1 });
|
|
976
|
-
const
|
|
977
|
-
onProgress({ current: 0, total:
|
|
978
|
-
if (
|
|
979
|
-
await
|
|
2772
|
+
const totalFromApi = firstPage.pagination.totalItems;
|
|
2773
|
+
onProgress({ current: 0, total: totalFromApi, message: "Starting sync..." });
|
|
2774
|
+
if (totalFromApi === 0) {
|
|
2775
|
+
await writeBokioFiscalYearsMetadata(storage, fiscalYears);
|
|
980
2776
|
return {
|
|
981
2777
|
entriesCount: 0,
|
|
2778
|
+
newEntries: 0,
|
|
2779
|
+
existingEntries: 0,
|
|
982
2780
|
fiscalYearsCount: fiscalYears.length,
|
|
983
2781
|
entriesWithFilesDownloaded: 0
|
|
984
2782
|
};
|
|
@@ -989,23 +2787,42 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
|
|
|
989
2787
|
while (true) {
|
|
990
2788
|
const response = await client2.getJournalEntries({ page, pageSize });
|
|
991
2789
|
allEntries.push(...response.data);
|
|
992
|
-
onProgress({ current: allEntries.length, total:
|
|
2790
|
+
onProgress({ current: allEntries.length, total: totalFromApi });
|
|
993
2791
|
if (!response.pagination.hasNextPage)
|
|
994
2792
|
break;
|
|
995
2793
|
page++;
|
|
996
2794
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
2795
|
+
const entriesToSync = targetYear ? allEntries.filter((entry) => {
|
|
2796
|
+
const fy = findFiscalYear(entry.date, fiscalYears);
|
|
2797
|
+
return fy !== undefined;
|
|
2798
|
+
}) : allEntries;
|
|
2799
|
+
let newEntries = 0;
|
|
2800
|
+
let existingEntries = 0;
|
|
2801
|
+
const entryDirs = new Map;
|
|
2802
|
+
for (const entry of entriesToSync) {
|
|
2803
|
+
const fiscalYear = findFiscalYear(entry.date, allFiscalYears);
|
|
1001
2804
|
if (!fiscalYear)
|
|
1002
2805
|
continue;
|
|
1003
2806
|
const fyYear = parseInt(fiscalYear.startDate.slice(0, 4), 10);
|
|
1004
|
-
const
|
|
1005
|
-
if (
|
|
2807
|
+
const result = await writeBokioJournalEntry(storage, fyYear, entry);
|
|
2808
|
+
if (result.isNew) {
|
|
2809
|
+
newEntries++;
|
|
2810
|
+
} else {
|
|
2811
|
+
existingEntries++;
|
|
2812
|
+
}
|
|
2813
|
+
entryDirs.set(entry.id, result.entryDir);
|
|
2814
|
+
}
|
|
2815
|
+
await writeBokioFiscalYearsMetadata(storage, fiscalYears);
|
|
2816
|
+
let entriesWithFilesDownloaded = 0;
|
|
2817
|
+
if (downloadFiles) {
|
|
2818
|
+
const downloader = createBokioDownloader(client2);
|
|
2819
|
+
for (const entry of entriesToSync) {
|
|
2820
|
+
const entryDir = entryDirs.get(entry.id);
|
|
2821
|
+
if (!entryDir)
|
|
2822
|
+
continue;
|
|
1006
2823
|
const filesDownloaded = await downloadFilesForEntry({
|
|
1007
|
-
storage
|
|
1008
|
-
repoPath,
|
|
2824
|
+
storage,
|
|
2825
|
+
repoPath: "",
|
|
1009
2826
|
entryDir,
|
|
1010
2827
|
journalEntryId: entry.id,
|
|
1011
2828
|
downloader,
|
|
@@ -1016,17 +2833,15 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
|
|
|
1016
2833
|
}
|
|
1017
2834
|
}
|
|
1018
2835
|
}
|
|
1019
|
-
await writeFiscalYearsMetadata(storage2, fiscalYears);
|
|
1020
2836
|
return {
|
|
1021
|
-
entriesCount:
|
|
2837
|
+
entriesCount: entriesToSync.length,
|
|
2838
|
+
newEntries,
|
|
2839
|
+
existingEntries,
|
|
1022
2840
|
fiscalYearsCount: fiscalYears.length,
|
|
1023
2841
|
entriesWithFilesDownloaded
|
|
1024
2842
|
};
|
|
1025
2843
|
}
|
|
1026
|
-
function
|
|
1027
|
-
return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
|
|
1028
|
-
}
|
|
1029
|
-
async function writeJournalEntry(storage2, fyYear, entry) {
|
|
2844
|
+
async function writeBokioJournalEntry(storage, fyYear, entry) {
|
|
1030
2845
|
const journalEntry = mapBokioEntryToJournalEntry({
|
|
1031
2846
|
id: entry.id,
|
|
1032
2847
|
journalEntryNumber: entry.journalEntryNumber,
|
|
@@ -1039,11 +2854,16 @@ async function writeJournalEntry(storage2, fyYear, entry) {
|
|
|
1039
2854
|
}))
|
|
1040
2855
|
});
|
|
1041
2856
|
const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
|
|
1042
|
-
const
|
|
1043
|
-
await
|
|
1044
|
-
|
|
2857
|
+
const entryDir = journalEntryDirFromPath(entryPath);
|
|
2858
|
+
const exists = await storage.exists(entryPath);
|
|
2859
|
+
if (exists) {
|
|
2860
|
+
return { isNew: false, entryDir };
|
|
2861
|
+
}
|
|
2862
|
+
const yamlContent = toYaml2(journalEntry);
|
|
2863
|
+
await storage.writeFile(entryPath, yamlContent);
|
|
2864
|
+
return { isNew: true, entryDir };
|
|
1045
2865
|
}
|
|
1046
|
-
async function
|
|
2866
|
+
async function writeBokioFiscalYearsMetadata(storage, fiscalYears) {
|
|
1047
2867
|
for (const fy of fiscalYears) {
|
|
1048
2868
|
const fyDir = fiscalYearDirName({ start_date: fy.startDate });
|
|
1049
2869
|
const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
|
|
@@ -1053,38 +2873,1235 @@ async function writeFiscalYearsMetadata(storage2, fiscalYears) {
|
|
|
1053
2873
|
endDate: fy.endDate,
|
|
1054
2874
|
status: fy.status
|
|
1055
2875
|
};
|
|
1056
|
-
const yamlContent =
|
|
1057
|
-
await
|
|
2876
|
+
const yamlContent = toYaml2(metadata);
|
|
2877
|
+
await storage.writeFile(metadataPath, yamlContent);
|
|
1058
2878
|
}
|
|
1059
2879
|
}
|
|
1060
|
-
async function
|
|
1061
|
-
const storage2 = new FilesystemStorageService(repoPath);
|
|
2880
|
+
async function syncBokioChartOfAccounts(client2, storage) {
|
|
1062
2881
|
const bokioAccounts = await client2.getChartOfAccounts();
|
|
1063
2882
|
const accounts = [...bokioAccounts].sort((a, b) => a.account - b.account).map((account) => ({
|
|
1064
2883
|
code: account.account.toString(),
|
|
1065
2884
|
name: account.name,
|
|
1066
2885
|
description: account.name
|
|
1067
2886
|
}));
|
|
1068
|
-
const yamlContent =
|
|
1069
|
-
await
|
|
2887
|
+
const yamlContent = toYaml2({ accounts });
|
|
2888
|
+
await storage.writeFile("accounts.yaml", yamlContent);
|
|
1070
2889
|
return { accountsCount: accounts.length };
|
|
1071
2890
|
}
|
|
2891
|
+
// ../shared/dist/accounting/tax-codes.js
|
|
2892
|
+
var SE_TAX_CODES2 = [
|
|
2893
|
+
{
|
|
2894
|
+
code: "SE_VAT_25_PURCHASE_DOMESTIC",
|
|
2895
|
+
description: "Inköp i Sverige, moms 25 %",
|
|
2896
|
+
kind: "VAT",
|
|
2897
|
+
direction: "PURCHASE",
|
|
2898
|
+
territory: "DOMESTIC",
|
|
2899
|
+
ratePercent: 25,
|
|
2900
|
+
isReverseCharge: false,
|
|
2901
|
+
isZeroRated: false,
|
|
2902
|
+
isExempt: false,
|
|
2903
|
+
posting: {
|
|
2904
|
+
inputVatAccountCode: "2641"
|
|
2905
|
+
}
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
code: "SE_VAT_12_PURCHASE_DOMESTIC",
|
|
2909
|
+
description: "Inköp i Sverige, moms 12 %",
|
|
2910
|
+
kind: "VAT",
|
|
2911
|
+
direction: "PURCHASE",
|
|
2912
|
+
territory: "DOMESTIC",
|
|
2913
|
+
ratePercent: 12,
|
|
2914
|
+
isReverseCharge: false,
|
|
2915
|
+
isZeroRated: false,
|
|
2916
|
+
isExempt: false,
|
|
2917
|
+
posting: {
|
|
2918
|
+
inputVatAccountCode: "2641"
|
|
2919
|
+
}
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
code: "SE_VAT_6_PURCHASE_DOMESTIC",
|
|
2923
|
+
description: "Inköp i Sverige, moms 6 %",
|
|
2924
|
+
kind: "VAT",
|
|
2925
|
+
direction: "PURCHASE",
|
|
2926
|
+
territory: "DOMESTIC",
|
|
2927
|
+
ratePercent: 6,
|
|
2928
|
+
isReverseCharge: false,
|
|
2929
|
+
isZeroRated: false,
|
|
2930
|
+
isExempt: false,
|
|
2931
|
+
posting: {
|
|
2932
|
+
inputVatAccountCode: "2641"
|
|
2933
|
+
}
|
|
2934
|
+
},
|
|
2935
|
+
{
|
|
2936
|
+
code: "SE_VAT_0_PURCHASE_DOMESTIC",
|
|
2937
|
+
description: "Inköp i Sverige, 0 % moms (momspliktigt men 0 %)",
|
|
2938
|
+
kind: "VAT",
|
|
2939
|
+
direction: "PURCHASE",
|
|
2940
|
+
territory: "DOMESTIC",
|
|
2941
|
+
ratePercent: 0,
|
|
2942
|
+
isReverseCharge: false,
|
|
2943
|
+
isZeroRated: true,
|
|
2944
|
+
isExempt: false,
|
|
2945
|
+
posting: {}
|
|
2946
|
+
},
|
|
2947
|
+
{
|
|
2948
|
+
code: "SE_VAT_EXEMPT_PURCHASE",
|
|
2949
|
+
description: "Momsfria inköp (utanför momsens tillämpningsområde)",
|
|
2950
|
+
kind: "VAT",
|
|
2951
|
+
direction: "PURCHASE",
|
|
2952
|
+
territory: "DOMESTIC",
|
|
2953
|
+
ratePercent: 0,
|
|
2954
|
+
isReverseCharge: false,
|
|
2955
|
+
isZeroRated: false,
|
|
2956
|
+
isExempt: true,
|
|
2957
|
+
posting: {}
|
|
2958
|
+
},
|
|
2959
|
+
{
|
|
2960
|
+
code: "SE_VAT_25_SALE_DOMESTIC",
|
|
2961
|
+
description: "Försäljning i Sverige, moms 25 %",
|
|
2962
|
+
kind: "VAT",
|
|
2963
|
+
direction: "SALE",
|
|
2964
|
+
territory: "DOMESTIC",
|
|
2965
|
+
ratePercent: 25,
|
|
2966
|
+
isReverseCharge: false,
|
|
2967
|
+
isZeroRated: false,
|
|
2968
|
+
isExempt: false,
|
|
2969
|
+
posting: {
|
|
2970
|
+
outputVatAccountCode: "2611"
|
|
2971
|
+
}
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
code: "SE_VAT_12_SALE_DOMESTIC",
|
|
2975
|
+
description: "Försäljning i Sverige, moms 12 %",
|
|
2976
|
+
kind: "VAT",
|
|
2977
|
+
direction: "SALE",
|
|
2978
|
+
territory: "DOMESTIC",
|
|
2979
|
+
ratePercent: 12,
|
|
2980
|
+
isReverseCharge: false,
|
|
2981
|
+
isZeroRated: false,
|
|
2982
|
+
isExempt: false,
|
|
2983
|
+
posting: {
|
|
2984
|
+
outputVatAccountCode: "2611"
|
|
2985
|
+
}
|
|
2986
|
+
},
|
|
2987
|
+
{
|
|
2988
|
+
code: "SE_VAT_6_SALE_DOMESTIC",
|
|
2989
|
+
description: "Försäljning i Sverige, moms 6 %",
|
|
2990
|
+
kind: "VAT",
|
|
2991
|
+
direction: "SALE",
|
|
2992
|
+
territory: "DOMESTIC",
|
|
2993
|
+
ratePercent: 6,
|
|
2994
|
+
isReverseCharge: false,
|
|
2995
|
+
isZeroRated: false,
|
|
2996
|
+
isExempt: false,
|
|
2997
|
+
posting: {
|
|
2998
|
+
outputVatAccountCode: "2611"
|
|
2999
|
+
}
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
code: "SE_VAT_0_SALE_DOMESTIC",
|
|
3003
|
+
description: "Momsfri försäljning i Sverige (undantagen/0 %)",
|
|
3004
|
+
kind: "VAT",
|
|
3005
|
+
direction: "SALE",
|
|
3006
|
+
territory: "DOMESTIC",
|
|
3007
|
+
ratePercent: 0,
|
|
3008
|
+
isReverseCharge: false,
|
|
3009
|
+
isZeroRated: false,
|
|
3010
|
+
isExempt: true,
|
|
3011
|
+
posting: {}
|
|
3012
|
+
},
|
|
3013
|
+
{
|
|
3014
|
+
code: "SE_VAT_25_PURCHASE_EU_GOODS_RC",
|
|
3015
|
+
description: "Inköp av varor från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
3016
|
+
kind: "VAT",
|
|
3017
|
+
direction: "PURCHASE",
|
|
3018
|
+
territory: "EU",
|
|
3019
|
+
ratePercent: 25,
|
|
3020
|
+
isReverseCharge: true,
|
|
3021
|
+
isZeroRated: false,
|
|
3022
|
+
isExempt: false,
|
|
3023
|
+
posting: {
|
|
3024
|
+
reverseChargeOutputAccountCode: "2614",
|
|
3025
|
+
reverseChargeInputAccountCode: "2645"
|
|
3026
|
+
}
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
code: "SE_VAT_25_PURCHASE_EU_SERVICES_RC",
|
|
3030
|
+
description: "Inköp av tjänster från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
3031
|
+
kind: "VAT",
|
|
3032
|
+
direction: "PURCHASE",
|
|
3033
|
+
territory: "EU",
|
|
3034
|
+
ratePercent: 25,
|
|
3035
|
+
isReverseCharge: true,
|
|
3036
|
+
isZeroRated: false,
|
|
3037
|
+
isExempt: false,
|
|
3038
|
+
posting: {
|
|
3039
|
+
reverseChargeOutputAccountCode: "2614",
|
|
3040
|
+
reverseChargeInputAccountCode: "2645"
|
|
3041
|
+
}
|
|
3042
|
+
},
|
|
3043
|
+
{
|
|
3044
|
+
code: "SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC",
|
|
3045
|
+
description: "Inköp av tjänster från land utanför EU, 25 %, omvänd skattskyldighet",
|
|
3046
|
+
kind: "VAT",
|
|
3047
|
+
direction: "PURCHASE",
|
|
3048
|
+
territory: "OUTSIDE_EU",
|
|
3049
|
+
ratePercent: 25,
|
|
3050
|
+
isReverseCharge: true,
|
|
3051
|
+
isZeroRated: false,
|
|
3052
|
+
isExempt: false,
|
|
3053
|
+
posting: {
|
|
3054
|
+
reverseChargeOutputAccountCode: "2614",
|
|
3055
|
+
reverseChargeInputAccountCode: "2645"
|
|
3056
|
+
}
|
|
3057
|
+
},
|
|
3058
|
+
{
|
|
3059
|
+
code: "SE_VAT_0_SALE_EU_GOODS_B2B",
|
|
3060
|
+
description: "Varuförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
3061
|
+
kind: "VAT",
|
|
3062
|
+
direction: "SALE",
|
|
3063
|
+
territory: "EU",
|
|
3064
|
+
ratePercent: 0,
|
|
3065
|
+
isReverseCharge: false,
|
|
3066
|
+
isZeroRated: true,
|
|
3067
|
+
isExempt: false,
|
|
3068
|
+
posting: {}
|
|
3069
|
+
},
|
|
3070
|
+
{
|
|
3071
|
+
code: "SE_VAT_0_SALE_EU_SERVICES_B2B",
|
|
3072
|
+
description: "Tjänsteförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
3073
|
+
kind: "VAT",
|
|
3074
|
+
direction: "SALE",
|
|
3075
|
+
territory: "EU",
|
|
3076
|
+
ratePercent: 0,
|
|
3077
|
+
isReverseCharge: false,
|
|
3078
|
+
isZeroRated: true,
|
|
3079
|
+
isExempt: false,
|
|
3080
|
+
posting: {}
|
|
3081
|
+
},
|
|
3082
|
+
{
|
|
3083
|
+
code: "SE_VAT_RC_SALE_DOMESTIC_CONSTRUCTION",
|
|
3084
|
+
description: "Försäljning av byggtjänster med omvänd skattskyldighet (ingen moms på fakturan)",
|
|
3085
|
+
kind: "VAT",
|
|
3086
|
+
direction: "SALE",
|
|
3087
|
+
territory: "DOMESTIC",
|
|
3088
|
+
ratePercent: 0,
|
|
3089
|
+
isReverseCharge: true,
|
|
3090
|
+
isZeroRated: false,
|
|
3091
|
+
isExempt: false,
|
|
3092
|
+
posting: {}
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
code: "SE_VAT_RC_PURCHASE_DOMESTIC_CONSTRUCTION",
|
|
3096
|
+
description: "Inköp av byggtjänster med omvänd skattskyldighet (du beräknar svensk moms)",
|
|
3097
|
+
kind: "VAT",
|
|
3098
|
+
direction: "PURCHASE",
|
|
3099
|
+
territory: "DOMESTIC",
|
|
3100
|
+
ratePercent: 25,
|
|
3101
|
+
isReverseCharge: true,
|
|
3102
|
+
isZeroRated: false,
|
|
3103
|
+
isExempt: false,
|
|
3104
|
+
posting: {
|
|
3105
|
+
reverseChargeOutputAccountCode: "2617",
|
|
3106
|
+
reverseChargeInputAccountCode: "2647"
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
];
|
|
3110
|
+
var TAX_CODE_MAP2 = new Map(SE_TAX_CODES2.map((code) => [code.code, code]));
|
|
3111
|
+
// ../shared/dist/supabase/client.js
|
|
3112
|
+
import { createClient } from "@supabase/supabase-js";
|
|
3113
|
+
// ../shared/dist/auth/fortnox-login.js
|
|
3114
|
+
import * as http from "node:http";
|
|
3115
|
+
|
|
3116
|
+
// ../../node_modules/.bun/open@10.2.0/node_modules/open/index.js
|
|
3117
|
+
import process7 from "node:process";
|
|
3118
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
3119
|
+
import path4 from "node:path";
|
|
3120
|
+
import { fileURLToPath } from "node:url";
|
|
3121
|
+
import { promisify as promisify5 } from "node:util";
|
|
3122
|
+
import childProcess from "node:child_process";
|
|
3123
|
+
import fs8, { constants as fsConstants2 } from "node:fs/promises";
|
|
3124
|
+
|
|
3125
|
+
// ../../node_modules/.bun/wsl-utils@0.1.0/node_modules/wsl-utils/index.js
|
|
3126
|
+
import process3 from "node:process";
|
|
3127
|
+
import fs7, { constants as fsConstants } from "node:fs/promises";
|
|
3128
|
+
|
|
3129
|
+
// ../../node_modules/.bun/is-wsl@3.1.0/node_modules/is-wsl/index.js
|
|
3130
|
+
import process2 from "node:process";
|
|
3131
|
+
import os from "node:os";
|
|
3132
|
+
import fs6 from "node:fs";
|
|
3133
|
+
|
|
3134
|
+
// ../../node_modules/.bun/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
|
|
3135
|
+
import fs5 from "node:fs";
|
|
3136
|
+
|
|
3137
|
+
// ../../node_modules/.bun/is-docker@3.0.0/node_modules/is-docker/index.js
|
|
3138
|
+
import fs4 from "node:fs";
|
|
3139
|
+
var isDockerCached;
|
|
3140
|
+
function hasDockerEnv() {
|
|
3141
|
+
try {
|
|
3142
|
+
fs4.statSync("/.dockerenv");
|
|
3143
|
+
return true;
|
|
3144
|
+
} catch {
|
|
3145
|
+
return false;
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
function hasDockerCGroup() {
|
|
3149
|
+
try {
|
|
3150
|
+
return fs4.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
3151
|
+
} catch {
|
|
3152
|
+
return false;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
function isDocker() {
|
|
3156
|
+
if (isDockerCached === undefined) {
|
|
3157
|
+
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
|
3158
|
+
}
|
|
3159
|
+
return isDockerCached;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// ../../node_modules/.bun/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
|
|
3163
|
+
var cachedResult;
|
|
3164
|
+
var hasContainerEnv = () => {
|
|
3165
|
+
try {
|
|
3166
|
+
fs5.statSync("/run/.containerenv");
|
|
3167
|
+
return true;
|
|
3168
|
+
} catch {
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
function isInsideContainer() {
|
|
3173
|
+
if (cachedResult === undefined) {
|
|
3174
|
+
cachedResult = hasContainerEnv() || isDocker();
|
|
3175
|
+
}
|
|
3176
|
+
return cachedResult;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// ../../node_modules/.bun/is-wsl@3.1.0/node_modules/is-wsl/index.js
|
|
3180
|
+
var isWsl = () => {
|
|
3181
|
+
if (process2.platform !== "linux") {
|
|
3182
|
+
return false;
|
|
3183
|
+
}
|
|
3184
|
+
if (os.release().toLowerCase().includes("microsoft")) {
|
|
3185
|
+
if (isInsideContainer()) {
|
|
3186
|
+
return false;
|
|
3187
|
+
}
|
|
3188
|
+
return true;
|
|
3189
|
+
}
|
|
3190
|
+
try {
|
|
3191
|
+
return fs6.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
|
|
3192
|
+
} catch {
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
|
|
3197
|
+
|
|
3198
|
+
// ../../node_modules/.bun/wsl-utils@0.1.0/node_modules/wsl-utils/index.js
|
|
3199
|
+
var wslDrivesMountPoint = (() => {
|
|
3200
|
+
const defaultMountPoint = "/mnt/";
|
|
3201
|
+
let mountPoint;
|
|
3202
|
+
return async function() {
|
|
3203
|
+
if (mountPoint) {
|
|
3204
|
+
return mountPoint;
|
|
3205
|
+
}
|
|
3206
|
+
const configFilePath = "/etc/wsl.conf";
|
|
3207
|
+
let isConfigFileExists = false;
|
|
3208
|
+
try {
|
|
3209
|
+
await fs7.access(configFilePath, fsConstants.F_OK);
|
|
3210
|
+
isConfigFileExists = true;
|
|
3211
|
+
} catch {}
|
|
3212
|
+
if (!isConfigFileExists) {
|
|
3213
|
+
return defaultMountPoint;
|
|
3214
|
+
}
|
|
3215
|
+
const configContent = await fs7.readFile(configFilePath, { encoding: "utf8" });
|
|
3216
|
+
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
|
|
3217
|
+
if (!configMountPoint) {
|
|
3218
|
+
return defaultMountPoint;
|
|
3219
|
+
}
|
|
3220
|
+
mountPoint = configMountPoint.groups.mountPoint.trim();
|
|
3221
|
+
mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
|
|
3222
|
+
return mountPoint;
|
|
3223
|
+
};
|
|
3224
|
+
})();
|
|
3225
|
+
var powerShellPathFromWsl = async () => {
|
|
3226
|
+
const mountPoint = await wslDrivesMountPoint();
|
|
3227
|
+
return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
|
|
3228
|
+
};
|
|
3229
|
+
var powerShellPath = async () => {
|
|
3230
|
+
if (is_wsl_default) {
|
|
3231
|
+
return powerShellPathFromWsl();
|
|
3232
|
+
}
|
|
3233
|
+
return `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
3234
|
+
};
|
|
3235
|
+
|
|
3236
|
+
// ../../node_modules/.bun/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js
|
|
3237
|
+
function defineLazyProperty(object, propertyName, valueGetter) {
|
|
3238
|
+
const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
|
|
3239
|
+
Object.defineProperty(object, propertyName, {
|
|
3240
|
+
configurable: true,
|
|
3241
|
+
enumerable: true,
|
|
3242
|
+
get() {
|
|
3243
|
+
const result = valueGetter();
|
|
3244
|
+
define(result);
|
|
3245
|
+
return result;
|
|
3246
|
+
},
|
|
3247
|
+
set(value) {
|
|
3248
|
+
define(value);
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
return object;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/index.js
|
|
3255
|
+
import { promisify as promisify4 } from "node:util";
|
|
3256
|
+
import process6 from "node:process";
|
|
3257
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
3258
|
+
|
|
3259
|
+
// ../../node_modules/.bun/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
|
|
3260
|
+
import { promisify } from "node:util";
|
|
3261
|
+
import process4 from "node:process";
|
|
3262
|
+
import { execFile } from "node:child_process";
|
|
3263
|
+
var execFileAsync = promisify(execFile);
|
|
3264
|
+
async function defaultBrowserId() {
|
|
3265
|
+
if (process4.platform !== "darwin") {
|
|
3266
|
+
throw new Error("macOS only");
|
|
3267
|
+
}
|
|
3268
|
+
const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
|
|
3269
|
+
const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
|
|
3270
|
+
const browserId = match?.groups.id ?? "com.apple.Safari";
|
|
3271
|
+
if (browserId === "com.apple.safari") {
|
|
3272
|
+
return "com.apple.Safari";
|
|
3273
|
+
}
|
|
3274
|
+
return browserId;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// ../../node_modules/.bun/run-applescript@7.1.0/node_modules/run-applescript/index.js
|
|
3278
|
+
import process5 from "node:process";
|
|
3279
|
+
import { promisify as promisify2 } from "node:util";
|
|
3280
|
+
import { execFile as execFile2, execFileSync } from "node:child_process";
|
|
3281
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
3282
|
+
async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
|
|
3283
|
+
if (process5.platform !== "darwin") {
|
|
3284
|
+
throw new Error("macOS only");
|
|
3285
|
+
}
|
|
3286
|
+
const outputArguments = humanReadableOutput ? [] : ["-ss"];
|
|
3287
|
+
const execOptions = {};
|
|
3288
|
+
if (signal) {
|
|
3289
|
+
execOptions.signal = signal;
|
|
3290
|
+
}
|
|
3291
|
+
const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
|
|
3292
|
+
return stdout.trim();
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// ../../node_modules/.bun/bundle-name@4.1.0/node_modules/bundle-name/index.js
|
|
3296
|
+
async function bundleName(bundleId) {
|
|
3297
|
+
return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
|
|
3298
|
+
tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/windows.js
|
|
3302
|
+
import { promisify as promisify3 } from "node:util";
|
|
3303
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
3304
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
3305
|
+
var windowsBrowserProgIds = {
|
|
3306
|
+
MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
|
|
3307
|
+
MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
|
|
3308
|
+
MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
|
|
3309
|
+
AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
|
|
3310
|
+
ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
|
|
3311
|
+
ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
|
|
3312
|
+
ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
|
|
3313
|
+
ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
|
|
3314
|
+
BraveHTML: { name: "Brave", id: "com.brave.Browser" },
|
|
3315
|
+
BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
|
|
3316
|
+
BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
|
|
3317
|
+
BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
|
|
3318
|
+
FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
|
|
3319
|
+
OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
|
|
3320
|
+
VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
|
|
3321
|
+
"IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
|
|
3322
|
+
};
|
|
3323
|
+
var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
|
|
3324
|
+
|
|
3325
|
+
class UnknownBrowserError extends Error {
|
|
3326
|
+
}
|
|
3327
|
+
async function defaultBrowser(_execFileAsync = execFileAsync3) {
|
|
3328
|
+
const { stdout } = await _execFileAsync("reg", [
|
|
3329
|
+
"QUERY",
|
|
3330
|
+
" HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
|
|
3331
|
+
"/v",
|
|
3332
|
+
"ProgId"
|
|
3333
|
+
]);
|
|
3334
|
+
const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
|
|
3335
|
+
if (!match) {
|
|
3336
|
+
throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
|
|
3337
|
+
}
|
|
3338
|
+
const { id } = match.groups;
|
|
3339
|
+
const browser = windowsBrowserProgIds[id];
|
|
3340
|
+
if (!browser) {
|
|
3341
|
+
throw new UnknownBrowserError(`Unknown browser ID: ${id}`);
|
|
3342
|
+
}
|
|
3343
|
+
return browser;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/index.js
|
|
3347
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
3348
|
+
var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
|
|
3349
|
+
async function defaultBrowser2() {
|
|
3350
|
+
if (process6.platform === "darwin") {
|
|
3351
|
+
const id = await defaultBrowserId();
|
|
3352
|
+
const name = await bundleName(id);
|
|
3353
|
+
return { name, id };
|
|
3354
|
+
}
|
|
3355
|
+
if (process6.platform === "linux") {
|
|
3356
|
+
const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
|
|
3357
|
+
const id = stdout.trim();
|
|
3358
|
+
const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
|
|
3359
|
+
return { name, id };
|
|
3360
|
+
}
|
|
3361
|
+
if (process6.platform === "win32") {
|
|
3362
|
+
return defaultBrowser();
|
|
3363
|
+
}
|
|
3364
|
+
throw new Error("Only macOS, Linux, and Windows are supported");
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// ../../node_modules/.bun/open@10.2.0/node_modules/open/index.js
|
|
3368
|
+
var execFile5 = promisify5(childProcess.execFile);
|
|
3369
|
+
var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
|
|
3370
|
+
var localXdgOpenPath = path4.join(__dirname2, "xdg-open");
|
|
3371
|
+
var { platform, arch } = process7;
|
|
3372
|
+
async function getWindowsDefaultBrowserFromWsl() {
|
|
3373
|
+
const powershellPath = await powerShellPath();
|
|
3374
|
+
const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
|
|
3375
|
+
const encodedCommand = Buffer2.from(rawCommand, "utf16le").toString("base64");
|
|
3376
|
+
const { stdout } = await execFile5(powershellPath, [
|
|
3377
|
+
"-NoProfile",
|
|
3378
|
+
"-NonInteractive",
|
|
3379
|
+
"-ExecutionPolicy",
|
|
3380
|
+
"Bypass",
|
|
3381
|
+
"-EncodedCommand",
|
|
3382
|
+
encodedCommand
|
|
3383
|
+
], { encoding: "utf8" });
|
|
3384
|
+
const progId = stdout.trim();
|
|
3385
|
+
const browserMap = {
|
|
3386
|
+
ChromeHTML: "com.google.chrome",
|
|
3387
|
+
BraveHTML: "com.brave.Browser",
|
|
3388
|
+
MSEdgeHTM: "com.microsoft.edge",
|
|
3389
|
+
FirefoxURL: "org.mozilla.firefox"
|
|
3390
|
+
};
|
|
3391
|
+
return browserMap[progId] ? { id: browserMap[progId] } : {};
|
|
3392
|
+
}
|
|
3393
|
+
var pTryEach = async (array, mapper) => {
|
|
3394
|
+
let latestError;
|
|
3395
|
+
for (const item of array) {
|
|
3396
|
+
try {
|
|
3397
|
+
return await mapper(item);
|
|
3398
|
+
} catch (error) {
|
|
3399
|
+
latestError = error;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
throw latestError;
|
|
3403
|
+
};
|
|
3404
|
+
var baseOpen = async (options) => {
|
|
3405
|
+
options = {
|
|
3406
|
+
wait: false,
|
|
3407
|
+
background: false,
|
|
3408
|
+
newInstance: false,
|
|
3409
|
+
allowNonzeroExitCode: false,
|
|
3410
|
+
...options
|
|
3411
|
+
};
|
|
3412
|
+
if (Array.isArray(options.app)) {
|
|
3413
|
+
return pTryEach(options.app, (singleApp) => baseOpen({
|
|
3414
|
+
...options,
|
|
3415
|
+
app: singleApp
|
|
3416
|
+
}));
|
|
3417
|
+
}
|
|
3418
|
+
let { name: app, arguments: appArguments = [] } = options.app ?? {};
|
|
3419
|
+
appArguments = [...appArguments];
|
|
3420
|
+
if (Array.isArray(app)) {
|
|
3421
|
+
return pTryEach(app, (appName) => baseOpen({
|
|
3422
|
+
...options,
|
|
3423
|
+
app: {
|
|
3424
|
+
name: appName,
|
|
3425
|
+
arguments: appArguments
|
|
3426
|
+
}
|
|
3427
|
+
}));
|
|
3428
|
+
}
|
|
3429
|
+
if (app === "browser" || app === "browserPrivate") {
|
|
3430
|
+
const ids = {
|
|
3431
|
+
"com.google.chrome": "chrome",
|
|
3432
|
+
"google-chrome.desktop": "chrome",
|
|
3433
|
+
"com.brave.Browser": "brave",
|
|
3434
|
+
"org.mozilla.firefox": "firefox",
|
|
3435
|
+
"firefox.desktop": "firefox",
|
|
3436
|
+
"com.microsoft.msedge": "edge",
|
|
3437
|
+
"com.microsoft.edge": "edge",
|
|
3438
|
+
"com.microsoft.edgemac": "edge",
|
|
3439
|
+
"microsoft-edge.desktop": "edge"
|
|
3440
|
+
};
|
|
3441
|
+
const flags = {
|
|
3442
|
+
chrome: "--incognito",
|
|
3443
|
+
brave: "--incognito",
|
|
3444
|
+
firefox: "--private-window",
|
|
3445
|
+
edge: "--inPrivate"
|
|
3446
|
+
};
|
|
3447
|
+
const browser = is_wsl_default ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser2();
|
|
3448
|
+
if (browser.id in ids) {
|
|
3449
|
+
const browserName = ids[browser.id];
|
|
3450
|
+
if (app === "browserPrivate") {
|
|
3451
|
+
appArguments.push(flags[browserName]);
|
|
3452
|
+
}
|
|
3453
|
+
return baseOpen({
|
|
3454
|
+
...options,
|
|
3455
|
+
app: {
|
|
3456
|
+
name: apps[browserName],
|
|
3457
|
+
arguments: appArguments
|
|
3458
|
+
}
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
throw new Error(`${browser.name} is not supported as a default browser`);
|
|
3462
|
+
}
|
|
3463
|
+
let command;
|
|
3464
|
+
const cliArguments = [];
|
|
3465
|
+
const childProcessOptions = {};
|
|
3466
|
+
if (platform === "darwin") {
|
|
3467
|
+
command = "open";
|
|
3468
|
+
if (options.wait) {
|
|
3469
|
+
cliArguments.push("--wait-apps");
|
|
3470
|
+
}
|
|
3471
|
+
if (options.background) {
|
|
3472
|
+
cliArguments.push("--background");
|
|
3473
|
+
}
|
|
3474
|
+
if (options.newInstance) {
|
|
3475
|
+
cliArguments.push("--new");
|
|
3476
|
+
}
|
|
3477
|
+
if (app) {
|
|
3478
|
+
cliArguments.push("-a", app);
|
|
3479
|
+
}
|
|
3480
|
+
} else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
|
|
3481
|
+
command = await powerShellPath();
|
|
3482
|
+
cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
|
|
3483
|
+
if (!is_wsl_default) {
|
|
3484
|
+
childProcessOptions.windowsVerbatimArguments = true;
|
|
3485
|
+
}
|
|
3486
|
+
const encodedArguments = ["Start"];
|
|
3487
|
+
if (options.wait) {
|
|
3488
|
+
encodedArguments.push("-Wait");
|
|
3489
|
+
}
|
|
3490
|
+
if (app) {
|
|
3491
|
+
encodedArguments.push(`"\`"${app}\`""`);
|
|
3492
|
+
if (options.target) {
|
|
3493
|
+
appArguments.push(options.target);
|
|
3494
|
+
}
|
|
3495
|
+
} else if (options.target) {
|
|
3496
|
+
encodedArguments.push(`"${options.target}"`);
|
|
3497
|
+
}
|
|
3498
|
+
if (appArguments.length > 0) {
|
|
3499
|
+
appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
|
|
3500
|
+
encodedArguments.push("-ArgumentList", appArguments.join(","));
|
|
3501
|
+
}
|
|
3502
|
+
options.target = Buffer2.from(encodedArguments.join(" "), "utf16le").toString("base64");
|
|
3503
|
+
} else {
|
|
3504
|
+
if (app) {
|
|
3505
|
+
command = app;
|
|
3506
|
+
} else {
|
|
3507
|
+
const isBundled = !__dirname2 || __dirname2 === "/";
|
|
3508
|
+
let exeLocalXdgOpen = false;
|
|
3509
|
+
try {
|
|
3510
|
+
await fs8.access(localXdgOpenPath, fsConstants2.X_OK);
|
|
3511
|
+
exeLocalXdgOpen = true;
|
|
3512
|
+
} catch {}
|
|
3513
|
+
const useSystemXdgOpen = process7.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
|
|
3514
|
+
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
3515
|
+
}
|
|
3516
|
+
if (appArguments.length > 0) {
|
|
3517
|
+
cliArguments.push(...appArguments);
|
|
3518
|
+
}
|
|
3519
|
+
if (!options.wait) {
|
|
3520
|
+
childProcessOptions.stdio = "ignore";
|
|
3521
|
+
childProcessOptions.detached = true;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
if (platform === "darwin" && appArguments.length > 0) {
|
|
3525
|
+
cliArguments.push("--args", ...appArguments);
|
|
3526
|
+
}
|
|
3527
|
+
if (options.target) {
|
|
3528
|
+
cliArguments.push(options.target);
|
|
3529
|
+
}
|
|
3530
|
+
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
3531
|
+
if (options.wait) {
|
|
3532
|
+
return new Promise((resolve, reject) => {
|
|
3533
|
+
subprocess.once("error", reject);
|
|
3534
|
+
subprocess.once("close", (exitCode) => {
|
|
3535
|
+
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
3536
|
+
reject(new Error(`Exited with code ${exitCode}`));
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
resolve(subprocess);
|
|
3540
|
+
});
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
subprocess.unref();
|
|
3544
|
+
return subprocess;
|
|
3545
|
+
};
|
|
3546
|
+
var open = (target, options) => {
|
|
3547
|
+
if (typeof target !== "string") {
|
|
3548
|
+
throw new TypeError("Expected a `target`");
|
|
3549
|
+
}
|
|
3550
|
+
return baseOpen({
|
|
3551
|
+
...options,
|
|
3552
|
+
target
|
|
3553
|
+
});
|
|
3554
|
+
};
|
|
3555
|
+
function detectArchBinary(binary) {
|
|
3556
|
+
if (typeof binary === "string" || Array.isArray(binary)) {
|
|
3557
|
+
return binary;
|
|
3558
|
+
}
|
|
3559
|
+
const { [arch]: archBinary } = binary;
|
|
3560
|
+
if (!archBinary) {
|
|
3561
|
+
throw new Error(`${arch} is not supported`);
|
|
3562
|
+
}
|
|
3563
|
+
return archBinary;
|
|
3564
|
+
}
|
|
3565
|
+
function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
3566
|
+
if (wsl && is_wsl_default) {
|
|
3567
|
+
return detectArchBinary(wsl);
|
|
3568
|
+
}
|
|
3569
|
+
if (!platformBinary) {
|
|
3570
|
+
throw new Error(`${platform} is not supported`);
|
|
3571
|
+
}
|
|
3572
|
+
return detectArchBinary(platformBinary);
|
|
3573
|
+
}
|
|
3574
|
+
var apps = {};
|
|
3575
|
+
defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
|
|
3576
|
+
darwin: "google chrome",
|
|
3577
|
+
win32: "chrome",
|
|
3578
|
+
linux: ["google-chrome", "google-chrome-stable", "chromium"]
|
|
3579
|
+
}, {
|
|
3580
|
+
wsl: {
|
|
3581
|
+
ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
3582
|
+
x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
|
|
3583
|
+
}
|
|
3584
|
+
}));
|
|
3585
|
+
defineLazyProperty(apps, "brave", () => detectPlatformBinary({
|
|
3586
|
+
darwin: "brave browser",
|
|
3587
|
+
win32: "brave",
|
|
3588
|
+
linux: ["brave-browser", "brave"]
|
|
3589
|
+
}, {
|
|
3590
|
+
wsl: {
|
|
3591
|
+
ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
|
|
3592
|
+
x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
|
|
3593
|
+
}
|
|
3594
|
+
}));
|
|
3595
|
+
defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
|
|
3596
|
+
darwin: "firefox",
|
|
3597
|
+
win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
|
|
3598
|
+
linux: "firefox"
|
|
3599
|
+
}, {
|
|
3600
|
+
wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
|
|
3601
|
+
}));
|
|
3602
|
+
defineLazyProperty(apps, "edge", () => detectPlatformBinary({
|
|
3603
|
+
darwin: "microsoft edge",
|
|
3604
|
+
win32: "msedge",
|
|
3605
|
+
linux: ["microsoft-edge", "microsoft-edge-dev"]
|
|
3606
|
+
}, {
|
|
3607
|
+
wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
|
|
3608
|
+
}));
|
|
3609
|
+
defineLazyProperty(apps, "browser", () => "browser");
|
|
3610
|
+
defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
|
|
3611
|
+
var open_default = open;
|
|
3612
|
+
|
|
3613
|
+
// ../shared/dist/auth/token-store.js
|
|
3614
|
+
import * as fs9 from "node:fs";
|
|
3615
|
+
import * as path5 from "node:path";
|
|
3616
|
+
var TOKENS_DIR = ".kvitton/tokens";
|
|
3617
|
+
var FORTNOX_TOKEN_FILE = "fortnox.json";
|
|
3618
|
+
function getTokenPath(cwd) {
|
|
3619
|
+
return path5.join(cwd, TOKENS_DIR, FORTNOX_TOKEN_FILE);
|
|
3620
|
+
}
|
|
3621
|
+
function ensureTokenDir(cwd) {
|
|
3622
|
+
const dir = path5.join(cwd, TOKENS_DIR);
|
|
3623
|
+
if (!fs9.existsSync(dir)) {
|
|
3624
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function saveFortnoxToken(cwd, token) {
|
|
3628
|
+
ensureTokenDir(cwd);
|
|
3629
|
+
const expiresAt = new Date(Date.now() + token.expires_in * 1000);
|
|
3630
|
+
const stored = {
|
|
3631
|
+
access_token: token.access_token,
|
|
3632
|
+
refresh_token: token.refresh_token,
|
|
3633
|
+
scope: token.scope,
|
|
3634
|
+
expires_at: expiresAt.toISOString(),
|
|
3635
|
+
created_at: new Date().toISOString()
|
|
3636
|
+
};
|
|
3637
|
+
fs9.writeFileSync(getTokenPath(cwd), JSON.stringify(stored, null, 2), "utf-8");
|
|
3638
|
+
return stored;
|
|
3639
|
+
}
|
|
3640
|
+
function loadFortnoxToken(cwd) {
|
|
3641
|
+
const tokenPath = getTokenPath(cwd);
|
|
3642
|
+
if (!fs9.existsSync(tokenPath)) {
|
|
3643
|
+
return null;
|
|
3644
|
+
}
|
|
3645
|
+
try {
|
|
3646
|
+
const content = fs9.readFileSync(tokenPath, "utf-8");
|
|
3647
|
+
return JSON.parse(content);
|
|
3648
|
+
} catch {
|
|
3649
|
+
return null;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
function tokenExistsAt(cwd) {
|
|
3653
|
+
return fs9.existsSync(getTokenPath(cwd));
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
// ../shared/dist/auth/prompts.js
|
|
3657
|
+
import * as readline from "node:readline";
|
|
3658
|
+
function createInterface2() {
|
|
3659
|
+
return readline.createInterface({
|
|
3660
|
+
input: process.stdin,
|
|
3661
|
+
output: process.stdout
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
function question(rl, prompt) {
|
|
3665
|
+
return new Promise((resolve) => {
|
|
3666
|
+
rl.question(prompt, (answer) => {
|
|
3667
|
+
resolve(answer);
|
|
3668
|
+
});
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
async function promptForCredentials() {
|
|
3672
|
+
const rl = createInterface2();
|
|
3673
|
+
try {
|
|
3674
|
+
console.log(`
|
|
3675
|
+
Fortnox OAuth credentials required.`);
|
|
3676
|
+
console.log(`Get these from: https://developer.fortnox.se/my-apps
|
|
3677
|
+
`);
|
|
3678
|
+
const clientId = await question(rl, "FORTNOX_CLIENT_ID: ");
|
|
3679
|
+
if (!clientId.trim()) {
|
|
3680
|
+
throw new Error("Client ID is required");
|
|
3681
|
+
}
|
|
3682
|
+
const clientSecret = await question(rl, "FORTNOX_CLIENT_SECRET: ");
|
|
3683
|
+
if (!clientSecret.trim()) {
|
|
3684
|
+
throw new Error("Client Secret is required");
|
|
3685
|
+
}
|
|
3686
|
+
return {
|
|
3687
|
+
clientId: clientId.trim(),
|
|
3688
|
+
clientSecret: clientSecret.trim()
|
|
3689
|
+
};
|
|
3690
|
+
} finally {
|
|
3691
|
+
rl.close();
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
async function promptConfirm(message) {
|
|
3695
|
+
const rl = createInterface2();
|
|
3696
|
+
try {
|
|
3697
|
+
const answer = await question(rl, `${message} (y/N): `);
|
|
3698
|
+
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
3699
|
+
} finally {
|
|
3700
|
+
rl.close();
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// ../shared/dist/auth/fortnox-login.js
|
|
3705
|
+
var DEFAULT_PORT = 8585;
|
|
3706
|
+
var DEFAULT_SCOPES = [
|
|
3707
|
+
FORTNOX_SCOPES.BOOKKEEPING,
|
|
3708
|
+
FORTNOX_SCOPES.COMPANYINFORMATION,
|
|
3709
|
+
FORTNOX_SCOPES.INBOX,
|
|
3710
|
+
FORTNOX_SCOPES.ARCHIVE,
|
|
3711
|
+
FORTNOX_SCOPES.CONNECTFILE
|
|
3712
|
+
];
|
|
3713
|
+
var SUCCESS_HTML = `
|
|
3714
|
+
<!DOCTYPE html>
|
|
3715
|
+
<html>
|
|
3716
|
+
<head>
|
|
3717
|
+
<title>Authentication Complete</title>
|
|
3718
|
+
<style>
|
|
3719
|
+
body {
|
|
3720
|
+
font-family: system-ui, sans-serif;
|
|
3721
|
+
display: flex;
|
|
3722
|
+
justify-content: center;
|
|
3723
|
+
align-items: center;
|
|
3724
|
+
height: 100vh;
|
|
3725
|
+
margin: 0;
|
|
3726
|
+
background: #fff;
|
|
3727
|
+
color: #111;
|
|
3728
|
+
}
|
|
3729
|
+
.container { text-align: center; }
|
|
3730
|
+
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
3731
|
+
p { color: #666; }
|
|
3732
|
+
</style>
|
|
3733
|
+
</head>
|
|
3734
|
+
<body>
|
|
3735
|
+
<div class="container">
|
|
3736
|
+
<h1>✓ Authentication complete</h1>
|
|
3737
|
+
<p>You can close this window.</p>
|
|
3738
|
+
</div>
|
|
3739
|
+
</body>
|
|
3740
|
+
</html>
|
|
3741
|
+
`;
|
|
3742
|
+
var ERROR_HTML = (error) => `
|
|
3743
|
+
<!DOCTYPE html>
|
|
3744
|
+
<html>
|
|
3745
|
+
<head>
|
|
3746
|
+
<title>Authentication Failed</title>
|
|
3747
|
+
<style>
|
|
3748
|
+
body {
|
|
3749
|
+
font-family: system-ui, sans-serif;
|
|
3750
|
+
display: flex;
|
|
3751
|
+
justify-content: center;
|
|
3752
|
+
align-items: center;
|
|
3753
|
+
height: 100vh;
|
|
3754
|
+
margin: 0;
|
|
3755
|
+
background: #fff;
|
|
3756
|
+
color: #111;
|
|
3757
|
+
}
|
|
3758
|
+
.container { text-align: center; }
|
|
3759
|
+
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #c00; }
|
|
3760
|
+
p { color: #666; }
|
|
3761
|
+
</style>
|
|
3762
|
+
</head>
|
|
3763
|
+
<body>
|
|
3764
|
+
<div class="container">
|
|
3765
|
+
<h1>Authentication failed</h1>
|
|
3766
|
+
<p>${error}</p>
|
|
3767
|
+
</div>
|
|
3768
|
+
</body>
|
|
3769
|
+
</html>
|
|
3770
|
+
`;
|
|
3771
|
+
async function startCallbackServer(port) {
|
|
3772
|
+
let resolveCallback;
|
|
3773
|
+
const resultPromise = new Promise((resolve) => {
|
|
3774
|
+
resolveCallback = resolve;
|
|
3775
|
+
});
|
|
3776
|
+
const server = http.createServer((req, res) => {
|
|
3777
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
3778
|
+
if (url.pathname === "/callback") {
|
|
3779
|
+
const code = url.searchParams.get("code");
|
|
3780
|
+
const error = url.searchParams.get("error");
|
|
3781
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
3782
|
+
res.setHeader("Content-Type", "text/html");
|
|
3783
|
+
if (error) {
|
|
3784
|
+
resolveCallback({ error, errorDescription: errorDescription ?? undefined });
|
|
3785
|
+
res.statusCode = 200;
|
|
3786
|
+
res.end(ERROR_HTML(errorDescription ?? error));
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
if (code) {
|
|
3790
|
+
resolveCallback({ code });
|
|
3791
|
+
res.statusCode = 200;
|
|
3792
|
+
res.end(SUCCESS_HTML);
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
resolveCallback({ error: "No authorization code received" });
|
|
3796
|
+
res.statusCode = 200;
|
|
3797
|
+
res.end(ERROR_HTML("No authorization code received"));
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
res.statusCode = 404;
|
|
3801
|
+
res.end("Not found");
|
|
3802
|
+
});
|
|
3803
|
+
await new Promise((resolve) => {
|
|
3804
|
+
server.listen(port, () => resolve());
|
|
3805
|
+
});
|
|
3806
|
+
return {
|
|
3807
|
+
result: resultPromise,
|
|
3808
|
+
stop: () => server.close()
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
async function loginFortnox(options) {
|
|
3812
|
+
const { cwd, scopes = DEFAULT_SCOPES, port = DEFAULT_PORT, force = false, onStatus = console.log } = options;
|
|
3813
|
+
if (!force && tokenExistsAt(cwd)) {
|
|
3814
|
+
const existingToken = loadFortnoxToken(cwd);
|
|
3815
|
+
if (existingToken) {
|
|
3816
|
+
const overwrite = await promptConfirm("Fortnox token already exists. Overwrite?");
|
|
3817
|
+
if (!overwrite) {
|
|
3818
|
+
onStatus("Login cancelled.");
|
|
3819
|
+
return existingToken;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
let clientId = options.clientId;
|
|
3824
|
+
let clientSecret = options.clientSecret;
|
|
3825
|
+
if (!clientId || !clientSecret) {
|
|
3826
|
+
const credentials = await promptForCredentials();
|
|
3827
|
+
clientId = credentials.clientId;
|
|
3828
|
+
clientSecret = credentials.clientSecret;
|
|
3829
|
+
}
|
|
3830
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
3831
|
+
const config2 = {
|
|
3832
|
+
clientId,
|
|
3833
|
+
clientSecret,
|
|
3834
|
+
redirectUri,
|
|
3835
|
+
scopes
|
|
3836
|
+
};
|
|
3837
|
+
onStatus(`Starting OAuth callback server on port ${port}...`);
|
|
3838
|
+
const { result, stop } = await startCallbackServer(port);
|
|
3839
|
+
try {
|
|
3840
|
+
const state = crypto.randomUUID();
|
|
3841
|
+
const authUrl = buildAuthorizationUrl(config2, state);
|
|
3842
|
+
onStatus("Opening browser for Fortnox authorization...");
|
|
3843
|
+
await open_default(authUrl);
|
|
3844
|
+
onStatus("Waiting for authorization (press Ctrl+C to cancel)...");
|
|
3845
|
+
const callbackResult = await result;
|
|
3846
|
+
if (callbackResult.error) {
|
|
3847
|
+
throw new Error(callbackResult.errorDescription ?? callbackResult.error);
|
|
3848
|
+
}
|
|
3849
|
+
if (!callbackResult.code) {
|
|
3850
|
+
throw new Error("No authorization code received");
|
|
3851
|
+
}
|
|
3852
|
+
onStatus("Exchanging authorization code for token...");
|
|
3853
|
+
const tokenResponse = await exchangeCodeForToken(config2, callbackResult.code);
|
|
3854
|
+
const storedToken = saveFortnoxToken(cwd, tokenResponse);
|
|
3855
|
+
onStatus("Token saved to .kvitton/tokens/fortnox.json");
|
|
3856
|
+
return storedToken;
|
|
3857
|
+
} finally {
|
|
3858
|
+
stop();
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
// ../shared/dist/progress/sync-progress.js
|
|
3862
|
+
var import_cli_progress = __toESM(require_cli_progress(), 1);
|
|
3863
|
+
|
|
3864
|
+
class SyncProgressBar {
|
|
3865
|
+
bar;
|
|
3866
|
+
currentPhase = "";
|
|
3867
|
+
started = false;
|
|
3868
|
+
constructor() {
|
|
3869
|
+
this.bar = new import_cli_progress.default.SingleBar({
|
|
3870
|
+
format: " {phase} [{bar}] {percentage}% | {value}/{total}",
|
|
3871
|
+
barCompleteChar: "█",
|
|
3872
|
+
barIncompleteChar: "░",
|
|
3873
|
+
barsize: 20,
|
|
3874
|
+
hideCursor: true,
|
|
3875
|
+
clearOnComplete: false
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
start(total, phase) {
|
|
3879
|
+
this.currentPhase = phase ?? "Syncing...";
|
|
3880
|
+
this.started = true;
|
|
3881
|
+
this.bar.start(total, 0, { phase: this.currentPhase });
|
|
3882
|
+
}
|
|
3883
|
+
update(current, phase) {
|
|
3884
|
+
if (phase && phase !== this.currentPhase) {
|
|
3885
|
+
this.currentPhase = phase;
|
|
3886
|
+
}
|
|
3887
|
+
this.bar.update(current, { phase: this.currentPhase });
|
|
3888
|
+
}
|
|
3889
|
+
stop() {
|
|
3890
|
+
if (this.started) {
|
|
3891
|
+
this.bar.stop();
|
|
3892
|
+
this.started = false;
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
static createCallback() {
|
|
3896
|
+
const bar = new SyncProgressBar;
|
|
3897
|
+
let barStarted = false;
|
|
3898
|
+
let discoveryShown = false;
|
|
3899
|
+
return {
|
|
3900
|
+
bar,
|
|
3901
|
+
onProgress: (progress) => {
|
|
3902
|
+
if (progress.phase === "discovering") {
|
|
3903
|
+
process.stdout.write(`\r ${progress.message ?? "Discovering..."} ${progress.current.toLocaleString()} found`);
|
|
3904
|
+
discoveryShown = true;
|
|
3905
|
+
} else {
|
|
3906
|
+
if (!barStarted && progress.total > 0) {
|
|
3907
|
+
if (discoveryShown) {
|
|
3908
|
+
console.log();
|
|
3909
|
+
}
|
|
3910
|
+
bar.start(progress.total, progress.message);
|
|
3911
|
+
barStarted = true;
|
|
3912
|
+
}
|
|
3913
|
+
if (barStarted) {
|
|
3914
|
+
bar.update(progress.current, progress.message);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
// src/lib/env.ts
|
|
3922
|
+
import * as fs10 from "node:fs/promises";
|
|
3923
|
+
import * as path6 from "node:path";
|
|
3924
|
+
async function createEnvFile(repoPath, variables) {
|
|
3925
|
+
const lines = Object.entries(variables).map(([key, value]) => `${key}=${value}`).join(`
|
|
3926
|
+
`);
|
|
3927
|
+
await fs10.writeFile(path6.join(repoPath, ".env"), `${lines}
|
|
3928
|
+
`);
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
// src/sync/bokio-sync.ts
|
|
3932
|
+
async function syncJournalEntries2(client2, repoPath, options, onProgress) {
|
|
3933
|
+
const storage2 = new FilesystemStorageService(repoPath);
|
|
3934
|
+
const result = await syncBokioJournalEntries(client2, storage2, {
|
|
3935
|
+
downloadFiles: options.downloadFiles,
|
|
3936
|
+
onProgress
|
|
3937
|
+
});
|
|
3938
|
+
return {
|
|
3939
|
+
entriesCount: result.entriesCount,
|
|
3940
|
+
fiscalYearsCount: result.fiscalYearsCount,
|
|
3941
|
+
entriesWithFilesDownloaded: result.entriesWithFilesDownloaded
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
async function syncChartOfAccounts(client2, repoPath) {
|
|
3945
|
+
const storage2 = new FilesystemStorageService(repoPath);
|
|
3946
|
+
return syncBokioChartOfAccounts(client2, storage2);
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
// src/sync/fortnox-sync.ts
|
|
3950
|
+
async function syncJournalEntries3(client2, repoPath, options, onProgress) {
|
|
3951
|
+
const storage2 = new FilesystemStorageService(repoPath);
|
|
3952
|
+
const fiscalYearsResponse = await client2.getFinancialYears();
|
|
3953
|
+
const fiscalYears = fiscalYearsResponse.data;
|
|
3954
|
+
const voucherRefs = [];
|
|
3955
|
+
const entriesPerFiscalYear = new Map;
|
|
3956
|
+
for (const fy of fiscalYears) {
|
|
3957
|
+
const fyYear = fy.Id;
|
|
3958
|
+
let hasMore = true;
|
|
3959
|
+
let page = 1;
|
|
3960
|
+
let fyEntryCount = 0;
|
|
3961
|
+
while (hasMore) {
|
|
3962
|
+
const response = await client2.getVouchers(fyYear, { page, pageSize: 100 });
|
|
3963
|
+
for (const voucher of response.data) {
|
|
3964
|
+
voucherRefs.push({
|
|
3965
|
+
series: voucher.VoucherSeries,
|
|
3966
|
+
number: voucher.VoucherNumber ?? 0,
|
|
3967
|
+
year: voucher.Year ?? fyYear,
|
|
3968
|
+
fiscalYear: fy
|
|
3969
|
+
});
|
|
3970
|
+
fyEntryCount++;
|
|
3971
|
+
}
|
|
3972
|
+
hasMore = response.pagination.hasNextPage;
|
|
3973
|
+
page++;
|
|
3974
|
+
onProgress({
|
|
3975
|
+
current: voucherRefs.length,
|
|
3976
|
+
total: 0,
|
|
3977
|
+
message: "Discovering entries...",
|
|
3978
|
+
phase: "discovering"
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
entriesPerFiscalYear.set(fyYear, fyEntryCount);
|
|
3982
|
+
}
|
|
3983
|
+
const fiscalYearSummaries = fiscalYears.map((fy) => ({
|
|
3984
|
+
id: fy.Id,
|
|
3985
|
+
year: parseInt(fy.FromDate.slice(0, 4), 10),
|
|
3986
|
+
fromDate: fy.FromDate,
|
|
3987
|
+
toDate: fy.ToDate,
|
|
3988
|
+
entryCount: entriesPerFiscalYear.get(fy.Id) ?? 0
|
|
3989
|
+
}));
|
|
3990
|
+
let selectedYearIds;
|
|
3991
|
+
if (options.onSelectFiscalYears) {
|
|
3992
|
+
const selected = await options.onSelectFiscalYears(fiscalYearSummaries);
|
|
3993
|
+
if (selected.length === 0) {
|
|
3994
|
+
return {
|
|
3995
|
+
entriesCount: 0,
|
|
3996
|
+
fiscalYearsCount: 0,
|
|
3997
|
+
entriesWithFilesDownloaded: 0,
|
|
3998
|
+
skipped: true
|
|
3999
|
+
};
|
|
4000
|
+
}
|
|
4001
|
+
selectedYearIds = new Set(selected);
|
|
4002
|
+
} else {
|
|
4003
|
+
selectedYearIds = new Set(fiscalYears.map((fy) => fy.Id));
|
|
4004
|
+
}
|
|
4005
|
+
const filteredRefs = voucherRefs.filter((ref) => selectedYearIds.has(ref.fiscalYear.Id));
|
|
4006
|
+
const selectedFiscalYears = fiscalYears.filter((fy) => selectedYearIds.has(fy.Id));
|
|
4007
|
+
const totalEntries = filteredRefs.length;
|
|
4008
|
+
onProgress({ current: 0, total: totalEntries, message: "Fetching entry details...", phase: "fetching" });
|
|
4009
|
+
if (totalEntries === 0) {
|
|
4010
|
+
await writeFiscalYearsMetadata(storage2, selectedFiscalYears);
|
|
4011
|
+
return {
|
|
4012
|
+
entriesCount: 0,
|
|
4013
|
+
fiscalYearsCount: selectedFiscalYears.length,
|
|
4014
|
+
entriesWithFilesDownloaded: 0
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
const entryDirs = new Map;
|
|
4018
|
+
const allVouchers = [];
|
|
4019
|
+
let processed = 0;
|
|
4020
|
+
for (const ref of filteredRefs) {
|
|
4021
|
+
const fullVoucherResponse = await client2.getVoucher(ref.series, ref.number, ref.year);
|
|
4022
|
+
const voucher = fullVoucherResponse.Voucher;
|
|
4023
|
+
const fyYear = parseInt(ref.fiscalYear.FromDate.slice(0, 4), 10);
|
|
4024
|
+
const entryDir = await writeJournalEntry(storage2, fyYear, voucher);
|
|
4025
|
+
const entryId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}-${voucher.Year}`;
|
|
4026
|
+
entryDirs.set(entryId, entryDir);
|
|
4027
|
+
allVouchers.push({ voucher, fiscalYear: ref.fiscalYear });
|
|
4028
|
+
processed++;
|
|
4029
|
+
onProgress({ current: processed, total: totalEntries, phase: "fetching" });
|
|
4030
|
+
}
|
|
4031
|
+
await writeFiscalYearsMetadata(storage2, selectedFiscalYears);
|
|
4032
|
+
let entriesWithFilesDownloaded = 0;
|
|
4033
|
+
if (options.downloadFiles !== false) {
|
|
4034
|
+
for (const { voucher } of allVouchers) {
|
|
4035
|
+
const entryId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}-${voucher.Year}`;
|
|
4036
|
+
const entryDir = entryDirs.get(entryId);
|
|
4037
|
+
if (!entryDir)
|
|
4038
|
+
continue;
|
|
4039
|
+
try {
|
|
4040
|
+
const connections = await client2.getVoucherFileConnectionsForVoucher(voucher.VoucherSeries, voucher.VoucherNumber ?? 0, voucher.Year ?? 0);
|
|
4041
|
+
for (const conn of connections) {
|
|
4042
|
+
try {
|
|
4043
|
+
const fileResult = await client2.downloadFromArchive(conn.FileId);
|
|
4044
|
+
const filename = `attachment-${conn.FileId}.${getExtension(fileResult.contentType)}`;
|
|
4045
|
+
const filePath = `${entryDir}/${filename}`;
|
|
4046
|
+
await storage2.writeFile(filePath, Buffer.from(fileResult.data).toString("base64"));
|
|
4047
|
+
entriesWithFilesDownloaded++;
|
|
4048
|
+
} catch {}
|
|
4049
|
+
}
|
|
4050
|
+
} catch {}
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
return {
|
|
4054
|
+
entriesCount: allVouchers.length,
|
|
4055
|
+
fiscalYearsCount: selectedFiscalYears.length,
|
|
4056
|
+
entriesWithFilesDownloaded
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
function getExtension(contentType) {
|
|
4060
|
+
const map = {
|
|
4061
|
+
"application/pdf": "pdf",
|
|
4062
|
+
"image/jpeg": "jpg",
|
|
4063
|
+
"image/png": "png",
|
|
4064
|
+
"image/gif": "gif"
|
|
4065
|
+
};
|
|
4066
|
+
return map[contentType] ?? "bin";
|
|
4067
|
+
}
|
|
4068
|
+
async function writeJournalEntry(storage2, fyYear, voucher) {
|
|
4069
|
+
const journalEntry = mapFortnoxVoucherToJournalEntry(voucher);
|
|
4070
|
+
const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
|
|
4071
|
+
const yamlContent = toYaml2(journalEntry);
|
|
4072
|
+
await storage2.writeFile(entryPath, yamlContent);
|
|
4073
|
+
return journalEntryDirFromPath(entryPath);
|
|
4074
|
+
}
|
|
4075
|
+
async function writeFiscalYearsMetadata(storage2, fiscalYears) {
|
|
4076
|
+
for (const fy of fiscalYears) {
|
|
4077
|
+
const fyDir = fiscalYearDirName({ start_date: fy.FromDate });
|
|
4078
|
+
const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
|
|
4079
|
+
const metadata = {
|
|
4080
|
+
id: fy.Id,
|
|
4081
|
+
startDate: fy.FromDate,
|
|
4082
|
+
endDate: fy.ToDate
|
|
4083
|
+
};
|
|
4084
|
+
const yamlContent = toYaml2(metadata);
|
|
4085
|
+
await storage2.writeFile(metadataPath, yamlContent);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
async function syncChartOfAccounts2(client2, repoPath) {
|
|
4089
|
+
const storage2 = new FilesystemStorageService(repoPath);
|
|
4090
|
+
return syncFortnoxChartOfAccounts(client2, storage2);
|
|
4091
|
+
}
|
|
1072
4092
|
|
|
1073
4093
|
// src/commands/create.ts
|
|
1074
|
-
var __filename2 =
|
|
1075
|
-
var
|
|
4094
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
4095
|
+
var __dirname3 = path7.dirname(__filename2);
|
|
1076
4096
|
async function createAgentsFile(targetDir, options) {
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
await fs5.writeFile(agentsPath, template, "utf-8");
|
|
1086
|
-
const claudePath = path5.join(targetDir, "CLAUDE.md");
|
|
1087
|
-
await fs5.symlink("AGENTS.md", claudePath);
|
|
4097
|
+
const content = renderAgentsTemplate({
|
|
4098
|
+
companyName: options.companyName,
|
|
4099
|
+
provider: options.provider
|
|
4100
|
+
});
|
|
4101
|
+
const agentsPath = path7.join(targetDir, "AGENTS.md");
|
|
4102
|
+
await fs11.writeFile(agentsPath, content, "utf-8");
|
|
4103
|
+
const claudePath = path7.join(targetDir, "CLAUDE.md");
|
|
4104
|
+
await fs11.symlink("AGENTS.md", claudePath);
|
|
1088
4105
|
}
|
|
1089
4106
|
function isValidUUID(value) {
|
|
1090
4107
|
return /^[0-9a-f-]{36}$/i.test(value.trim());
|
|
@@ -1103,60 +4120,110 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1103
4120
|
message: "Select your accounting provider:",
|
|
1104
4121
|
choices: [
|
|
1105
4122
|
{ name: "Bokio", value: "bokio" },
|
|
1106
|
-
{ name: "Fortnox
|
|
4123
|
+
{ name: "Fortnox", value: "fortnox" }
|
|
1107
4124
|
]
|
|
1108
4125
|
});
|
|
1109
4126
|
}
|
|
1110
|
-
if (provider !== "bokio") {
|
|
1111
|
-
console.log(
|
|
4127
|
+
if (provider !== "bokio" && provider !== "fortnox") {
|
|
4128
|
+
console.log(`Unknown provider: ${provider}. Supported: bokio, fortnox`);
|
|
1112
4129
|
process.exit(1);
|
|
1113
4130
|
}
|
|
1114
|
-
|
|
1115
|
-
let
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
4131
|
+
let companyName;
|
|
4132
|
+
let bokioClient;
|
|
4133
|
+
let fortnoxClient;
|
|
4134
|
+
let bokioCompanyId;
|
|
4135
|
+
let bokioToken;
|
|
4136
|
+
let fortnoxTempDir;
|
|
4137
|
+
if (provider === "bokio") {
|
|
4138
|
+
const envToken = process.env.BOKIO_TOKEN;
|
|
4139
|
+
let token;
|
|
4140
|
+
if (envToken) {
|
|
4141
|
+
token = envToken;
|
|
4142
|
+
console.log(" Using BOKIO_TOKEN from environment");
|
|
4143
|
+
} else {
|
|
4144
|
+
console.log(`
|
|
1121
4145
|
To connect to Bokio, you need:`);
|
|
1122
|
-
|
|
1123
|
-
|
|
4146
|
+
console.log(" - API Token: Bokio app > Settings > Integrations > Create Integration");
|
|
4147
|
+
console.log(` - Company ID: From your Bokio URL (app.bokio.se/{companyId}/...)
|
|
1124
4148
|
`);
|
|
1125
|
-
|
|
4149
|
+
console.log(` Tip: Set BOKIO_TOKEN env var to avoid entering token each time
|
|
1126
4150
|
`);
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
companyId
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
4151
|
+
token = await password({
|
|
4152
|
+
message: "Enter your Bokio API token:",
|
|
4153
|
+
mask: "*"
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
4156
|
+
bokioToken = token;
|
|
4157
|
+
let companyId = options.companyId;
|
|
4158
|
+
if (!companyId) {
|
|
4159
|
+
companyId = await input({
|
|
4160
|
+
message: "Enter your Bokio Company ID:",
|
|
4161
|
+
validate: (value) => {
|
|
4162
|
+
if (!value.trim())
|
|
4163
|
+
return "Company ID is required";
|
|
4164
|
+
if (!isValidUUID(value)) {
|
|
4165
|
+
return "Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)";
|
|
4166
|
+
}
|
|
4167
|
+
return true;
|
|
1141
4168
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
4169
|
+
});
|
|
4170
|
+
} else if (!isValidUUID(companyId)) {
|
|
4171
|
+
console.error("Error: Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)");
|
|
4172
|
+
process.exit(1);
|
|
4173
|
+
}
|
|
4174
|
+
const spinner = ora("Validating credentials...").start();
|
|
4175
|
+
bokioClient = new BokioClient({ token, companyId: companyId.trim() });
|
|
4176
|
+
bokioCompanyId = companyId.trim();
|
|
4177
|
+
try {
|
|
4178
|
+
const companyInfo = await bokioClient.getCompanyInformation();
|
|
4179
|
+
companyName = companyInfo.name;
|
|
4180
|
+
spinner.succeed(`Connected to: ${companyName}`);
|
|
4181
|
+
} catch (error) {
|
|
4182
|
+
spinner.fail("Failed to connect to Bokio");
|
|
4183
|
+
console.error(error instanceof Error ? error.message : "Unknown error");
|
|
4184
|
+
process.exit(1);
|
|
4185
|
+
}
|
|
4186
|
+
} else {
|
|
4187
|
+
console.log(`
|
|
4188
|
+
Fortnox requires OAuth authentication.
|
|
4189
|
+
`);
|
|
4190
|
+
fortnoxTempDir = await fs11.mkdtemp(path7.join(os2.tmpdir(), "kvitton-oauth-"));
|
|
4191
|
+
try {
|
|
4192
|
+
await loginFortnox({
|
|
4193
|
+
cwd: fortnoxTempDir,
|
|
4194
|
+
clientId: process.env.FORTNOX_CLIENT_ID,
|
|
4195
|
+
clientSecret: process.env.FORTNOX_CLIENT_SECRET,
|
|
4196
|
+
force: true,
|
|
4197
|
+
onStatus: (msg) => console.log(` ${msg}`)
|
|
4198
|
+
});
|
|
4199
|
+
} catch (error) {
|
|
4200
|
+
await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
|
|
4201
|
+
console.error(`
|
|
4202
|
+
Failed to authenticate with Fortnox: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4203
|
+
process.exit(1);
|
|
4204
|
+
}
|
|
4205
|
+
const token = loadFortnoxToken(fortnoxTempDir);
|
|
4206
|
+
if (!token) {
|
|
4207
|
+
await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
|
|
4208
|
+
console.error(" Failed to load Fortnox token after login");
|
|
4209
|
+
process.exit(1);
|
|
4210
|
+
}
|
|
4211
|
+
fortnoxClient = new FortnoxClient({
|
|
4212
|
+
clientId: process.env.FORTNOX_CLIENT_ID ?? "",
|
|
4213
|
+
clientSecret: process.env.FORTNOX_CLIENT_SECRET ?? "",
|
|
4214
|
+
getAccessToken: async () => token.access_token,
|
|
4215
|
+
silent: true
|
|
1144
4216
|
});
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
spinner.succeed(`Connected to: ${companyName}`);
|
|
1156
|
-
} catch (error) {
|
|
1157
|
-
spinner.fail("Failed to connect to Bokio");
|
|
1158
|
-
console.error(error instanceof Error ? error.message : "Unknown error");
|
|
1159
|
-
process.exit(1);
|
|
4217
|
+
const spinner = ora("Validating credentials...").start();
|
|
4218
|
+
try {
|
|
4219
|
+
const companyInfo = await fortnoxClient.getCompanyInformation();
|
|
4220
|
+
companyName = companyInfo.CompanyInformation.CompanyName;
|
|
4221
|
+
spinner.succeed(`Connected to: ${companyName}`);
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
spinner.fail("Failed to connect to Fortnox");
|
|
4224
|
+
console.error(error instanceof Error ? error.message : "Unknown error");
|
|
4225
|
+
process.exit(1);
|
|
4226
|
+
}
|
|
1160
4227
|
}
|
|
1161
4228
|
const suggestedName = `kvitton-${sanitizeOrgName(companyName)}`;
|
|
1162
4229
|
let folderName;
|
|
@@ -1183,9 +4250,9 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1183
4250
|
}
|
|
1184
4251
|
});
|
|
1185
4252
|
}
|
|
1186
|
-
const targetDir =
|
|
4253
|
+
const targetDir = path7.resolve(process.cwd(), folderName.trim());
|
|
1187
4254
|
try {
|
|
1188
|
-
await
|
|
4255
|
+
await fs11.access(targetDir);
|
|
1189
4256
|
if (acceptDefaults) {
|
|
1190
4257
|
console.error(`Error: Directory ${folderName} already exists`);
|
|
1191
4258
|
process.exit(1);
|
|
@@ -1200,22 +4267,46 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1200
4267
|
}
|
|
1201
4268
|
} catch {}
|
|
1202
4269
|
const gitSpinner = ora("Initializing repository...").start();
|
|
1203
|
-
await
|
|
4270
|
+
await fs11.mkdir(targetDir, { recursive: true });
|
|
1204
4271
|
await initGitRepo(targetDir);
|
|
1205
4272
|
gitSpinner.succeed("Repository initialized");
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
4273
|
+
if (provider === "bokio") {
|
|
4274
|
+
if (!bokioToken) {
|
|
4275
|
+
console.error("Error: Bokio token is required but not available");
|
|
4276
|
+
process.exit(1);
|
|
4277
|
+
}
|
|
4278
|
+
await createEnvFile(targetDir, {
|
|
4279
|
+
PROVIDER: "bokio",
|
|
4280
|
+
BOKIO_TOKEN: bokioToken,
|
|
4281
|
+
BOKIO_COMPANY_ID: bokioCompanyId ?? ""
|
|
4282
|
+
});
|
|
4283
|
+
} else {
|
|
4284
|
+
await createEnvFile(targetDir, {
|
|
4285
|
+
PROVIDER: "fortnox"
|
|
4286
|
+
});
|
|
4287
|
+
const sourcePath = path7.join(fortnoxTempDir, ".kvitton", "tokens");
|
|
4288
|
+
const targetPath = path7.join(targetDir, ".kvitton", "tokens");
|
|
4289
|
+
await fs11.mkdir(path7.dirname(targetPath), { recursive: true });
|
|
4290
|
+
await fs11.cp(sourcePath, targetPath, { recursive: true });
|
|
4291
|
+
await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
|
|
4292
|
+
}
|
|
1211
4293
|
console.log(" Created .env with credentials");
|
|
1212
4294
|
await createAgentsFile(targetDir, {
|
|
1213
4295
|
companyName,
|
|
1214
|
-
provider: "Bokio"
|
|
4296
|
+
provider: provider === "bokio" ? "Bokio" : "Fortnox"
|
|
1215
4297
|
});
|
|
1216
4298
|
console.log(" Created AGENTS.md and CLAUDE.md symlink");
|
|
1217
4299
|
const accountsSpinner = ora("Syncing chart of accounts...").start();
|
|
1218
|
-
|
|
4300
|
+
let accountsCount;
|
|
4301
|
+
if (provider === "bokio" && bokioClient) {
|
|
4302
|
+
const result = await syncChartOfAccounts(bokioClient, targetDir);
|
|
4303
|
+
accountsCount = result.accountsCount;
|
|
4304
|
+
} else if (fortnoxClient) {
|
|
4305
|
+
const result = await syncChartOfAccounts2(fortnoxClient, targetDir);
|
|
4306
|
+
accountsCount = result.accountsCount;
|
|
4307
|
+
} else {
|
|
4308
|
+
throw new Error("No client available");
|
|
4309
|
+
}
|
|
1219
4310
|
accountsSpinner.succeed(`Synced ${accountsCount} accounts to accounts.yaml`);
|
|
1220
4311
|
let shouldSync;
|
|
1221
4312
|
if (options.sync !== undefined) {
|
|
@@ -1224,28 +4315,57 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1224
4315
|
shouldSync = true;
|
|
1225
4316
|
} else {
|
|
1226
4317
|
shouldSync = await confirm({
|
|
1227
|
-
message:
|
|
4318
|
+
message: `Sync existing journal entries from ${provider === "bokio" ? "Bokio" : "Fortnox"}?`,
|
|
1228
4319
|
default: true
|
|
1229
4320
|
});
|
|
1230
4321
|
}
|
|
1231
4322
|
let entriesCount = 0;
|
|
1232
4323
|
let fiscalYearsCount = 0;
|
|
1233
4324
|
if (shouldSync) {
|
|
1234
|
-
const shouldDownloadFiles = options.downloadFiles
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
|
|
4325
|
+
const shouldDownloadFiles = options.downloadFiles === true;
|
|
4326
|
+
const { bar, onProgress } = SyncProgressBar.createCallback();
|
|
4327
|
+
if (provider === "bokio" && bokioClient) {
|
|
4328
|
+
const result = await syncJournalEntries2(bokioClient, targetDir, { downloadFiles: shouldDownloadFiles }, onProgress);
|
|
4329
|
+
bar.stop();
|
|
4330
|
+
entriesCount = result.entriesCount;
|
|
4331
|
+
fiscalYearsCount = result.fiscalYearsCount;
|
|
4332
|
+
if (result.entriesWithFilesDownloaded > 0) {
|
|
4333
|
+
console.log(` Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
|
|
4334
|
+
}
|
|
4335
|
+
} else if (fortnoxClient) {
|
|
4336
|
+
const result = await syncJournalEntries3(fortnoxClient, targetDir, {
|
|
4337
|
+
downloadFiles: shouldDownloadFiles,
|
|
4338
|
+
onSelectFiscalYears: async (years) => {
|
|
4339
|
+
console.log();
|
|
4340
|
+
const sorted = [...years].sort((a, b) => b.year - a.year);
|
|
4341
|
+
const selected = await checkbox({
|
|
4342
|
+
message: "Select fiscal years to sync:",
|
|
4343
|
+
choices: sorted.map((fy) => ({
|
|
4344
|
+
name: `FY-${fy.year} (${fy.entryCount.toLocaleString()} entries)`,
|
|
4345
|
+
value: fy.id,
|
|
4346
|
+
checked: true
|
|
4347
|
+
}))
|
|
4348
|
+
});
|
|
4349
|
+
return selected;
|
|
4350
|
+
}
|
|
4351
|
+
}, onProgress);
|
|
4352
|
+
bar.stop();
|
|
4353
|
+
if (result.skipped) {
|
|
4354
|
+
console.log(" Sync skipped (no fiscal years selected)");
|
|
4355
|
+
} else {
|
|
4356
|
+
entriesCount = result.entriesCount;
|
|
4357
|
+
fiscalYearsCount = result.fiscalYearsCount;
|
|
4358
|
+
if (result.entriesWithFilesDownloaded > 0) {
|
|
4359
|
+
console.log(` Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
1243
4362
|
}
|
|
1244
4363
|
console.log(" Sync complete!");
|
|
1245
4364
|
}
|
|
1246
4365
|
const commitSpinner = ora("Creating initial commit...").start();
|
|
1247
|
-
await commitAll(targetDir, "Initial
|
|
4366
|
+
await commitAll(targetDir, "Initial commit");
|
|
1248
4367
|
commitSpinner.succeed("Initial commit created");
|
|
4368
|
+
const providerName = provider === "bokio" ? "Bokio" : "Fortnox";
|
|
1249
4369
|
console.log(`
|
|
1250
4370
|
Success! Created ${folderName} at ${targetDir}
|
|
1251
4371
|
|
|
@@ -1261,24 +4381,25 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1261
4381
|
journal-entries/FY-2024/A-001-2024-01-15-invoice/entry.yaml
|
|
1262
4382
|
|
|
1263
4383
|
Commands:
|
|
1264
|
-
kvitton sync-journal Sync journal entries from
|
|
1265
|
-
kvitton sync-inbox Download inbox files from
|
|
4384
|
+
kvitton sync-journal Sync journal entries from ${providerName}
|
|
4385
|
+
kvitton sync-inbox Download inbox files from ${providerName}
|
|
1266
4386
|
kvitton company-info Display company information
|
|
1267
4387
|
`);
|
|
1268
4388
|
}
|
|
1269
4389
|
|
|
1270
4390
|
// src/index.ts
|
|
1271
4391
|
var program = new Command;
|
|
1272
|
-
program.name("create-kvitton").description("Create a new kvitton bookkeeping git repository").version("0.4.0").argument("[name]", "Repository folder name").option("-p, --provider <provider>", "Accounting provider (bokio
|
|
4392
|
+
program.name("create-kvitton").description("Create a new kvitton bookkeeping git repository").version("0.4.0").argument("[name]", "Repository folder name").option("-p, --provider <provider>", "Accounting provider (bokio, fortnox)").option("-c, --company-id <id>", "Company ID (UUID from provider)").option("-s, --sync", "Sync journal entries after creation", true).option("--no-sync", "Skip syncing journal entries").option("-d, --download-files", "Download files for journal entries", false).option("--no-download-files", "Skip downloading files").option("-y, --yes", "Accept defaults without prompting").addHelpText("after", `
|
|
1273
4393
|
Environment Variables:
|
|
1274
|
-
BOKIO_TOKEN
|
|
4394
|
+
BOKIO_TOKEN Bokio API token (for Bokio provider)
|
|
4395
|
+
FORTNOX_CLIENT_ID Fortnox OAuth client ID (for Fortnox provider)
|
|
4396
|
+
FORTNOX_CLIENT_SECRET Fortnox OAuth client secret (for Fortnox provider)
|
|
1275
4397
|
|
|
1276
4398
|
Examples:
|
|
1277
4399
|
$ create-kvitton
|
|
1278
4400
|
$ create-kvitton my-company-books
|
|
1279
|
-
$ create-kvitton
|
|
1280
|
-
$ create-kvitton my-repo -c abc-123
|
|
1281
|
-
$ BOKIO_TOKEN=xxx create-kvitton my-repo -c abc-123 -y
|
|
4401
|
+
$ create-kvitton -p fortnox
|
|
4402
|
+
$ create-kvitton my-repo -p bokio -c abc-123 -y
|
|
1282
4403
|
`).action((name, options) => {
|
|
1283
4404
|
createBookkeepingRepo(name, options);
|
|
1284
4405
|
});
|