create-kvitton 0.4.2 → 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 +3342 -228
- 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,52 +1457,818 @@ 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
|
-
|
|
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);
|
|
715
2272
|
const content = await fs.readFile(absolutePath, "utf-8");
|
|
716
2273
|
return {
|
|
717
2274
|
content,
|
|
@@ -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,31 +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
|
}
|
|
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;
|
|
997
2801
|
const entryDirs = new Map;
|
|
998
|
-
for (const entry of
|
|
999
|
-
const fiscalYear = findFiscalYear(entry.date,
|
|
2802
|
+
for (const entry of entriesToSync) {
|
|
2803
|
+
const fiscalYear = findFiscalYear(entry.date, allFiscalYears);
|
|
1000
2804
|
if (!fiscalYear)
|
|
1001
2805
|
continue;
|
|
1002
2806
|
const fyYear = parseInt(fiscalYear.startDate.slice(0, 4), 10);
|
|
1003
|
-
const
|
|
1004
|
-
|
|
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);
|
|
1005
2814
|
}
|
|
1006
|
-
await
|
|
2815
|
+
await writeBokioFiscalYearsMetadata(storage, fiscalYears);
|
|
1007
2816
|
let entriesWithFilesDownloaded = 0;
|
|
1008
|
-
if (
|
|
2817
|
+
if (downloadFiles) {
|
|
1009
2818
|
const downloader = createBokioDownloader(client2);
|
|
1010
|
-
for (const entry of
|
|
2819
|
+
for (const entry of entriesToSync) {
|
|
1011
2820
|
const entryDir = entryDirs.get(entry.id);
|
|
1012
2821
|
if (!entryDir)
|
|
1013
2822
|
continue;
|
|
1014
2823
|
const filesDownloaded = await downloadFilesForEntry({
|
|
1015
|
-
storage
|
|
1016
|
-
repoPath,
|
|
2824
|
+
storage,
|
|
2825
|
+
repoPath: "",
|
|
1017
2826
|
entryDir,
|
|
1018
2827
|
journalEntryId: entry.id,
|
|
1019
2828
|
downloader,
|
|
@@ -1025,15 +2834,14 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
|
|
|
1025
2834
|
}
|
|
1026
2835
|
}
|
|
1027
2836
|
return {
|
|
1028
|
-
entriesCount:
|
|
2837
|
+
entriesCount: entriesToSync.length,
|
|
2838
|
+
newEntries,
|
|
2839
|
+
existingEntries,
|
|
1029
2840
|
fiscalYearsCount: fiscalYears.length,
|
|
1030
2841
|
entriesWithFilesDownloaded
|
|
1031
2842
|
};
|
|
1032
2843
|
}
|
|
1033
|
-
function
|
|
1034
|
-
return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
|
|
1035
|
-
}
|
|
1036
|
-
async function writeJournalEntry(storage2, fyYear, entry) {
|
|
2844
|
+
async function writeBokioJournalEntry(storage, fyYear, entry) {
|
|
1037
2845
|
const journalEntry = mapBokioEntryToJournalEntry({
|
|
1038
2846
|
id: entry.id,
|
|
1039
2847
|
journalEntryNumber: entry.journalEntryNumber,
|
|
@@ -1046,11 +2854,16 @@ async function writeJournalEntry(storage2, fyYear, entry) {
|
|
|
1046
2854
|
}))
|
|
1047
2855
|
});
|
|
1048
2856
|
const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
|
|
1049
|
-
const
|
|
1050
|
-
await
|
|
1051
|
-
|
|
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 };
|
|
1052
2865
|
}
|
|
1053
|
-
async function
|
|
2866
|
+
async function writeBokioFiscalYearsMetadata(storage, fiscalYears) {
|
|
1054
2867
|
for (const fy of fiscalYears) {
|
|
1055
2868
|
const fyDir = fiscalYearDirName({ start_date: fy.startDate });
|
|
1056
2869
|
const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
|
|
@@ -1060,38 +2873,1235 @@ async function writeFiscalYearsMetadata(storage2, fiscalYears) {
|
|
|
1060
2873
|
endDate: fy.endDate,
|
|
1061
2874
|
status: fy.status
|
|
1062
2875
|
};
|
|
1063
|
-
const yamlContent =
|
|
1064
|
-
await
|
|
2876
|
+
const yamlContent = toYaml2(metadata);
|
|
2877
|
+
await storage.writeFile(metadataPath, yamlContent);
|
|
1065
2878
|
}
|
|
1066
2879
|
}
|
|
1067
|
-
async function
|
|
1068
|
-
const storage2 = new FilesystemStorageService(repoPath);
|
|
2880
|
+
async function syncBokioChartOfAccounts(client2, storage) {
|
|
1069
2881
|
const bokioAccounts = await client2.getChartOfAccounts();
|
|
1070
2882
|
const accounts = [...bokioAccounts].sort((a, b) => a.account - b.account).map((account) => ({
|
|
1071
2883
|
code: account.account.toString(),
|
|
1072
2884
|
name: account.name,
|
|
1073
2885
|
description: account.name
|
|
1074
2886
|
}));
|
|
1075
|
-
const yamlContent =
|
|
1076
|
-
await
|
|
2887
|
+
const yamlContent = toYaml2({ accounts });
|
|
2888
|
+
await storage.writeFile("accounts.yaml", yamlContent);
|
|
1077
2889
|
return { accountsCount: accounts.length };
|
|
1078
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
|
+
}
|
|
1079
4092
|
|
|
1080
4093
|
// src/commands/create.ts
|
|
1081
|
-
var __filename2 =
|
|
1082
|
-
var
|
|
4094
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
4095
|
+
var __dirname3 = path7.dirname(__filename2);
|
|
1083
4096
|
async function createAgentsFile(targetDir, options) {
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
await fs5.writeFile(agentsPath, template, "utf-8");
|
|
1093
|
-
const claudePath = path5.join(targetDir, "CLAUDE.md");
|
|
1094
|
-
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);
|
|
1095
4105
|
}
|
|
1096
4106
|
function isValidUUID(value) {
|
|
1097
4107
|
return /^[0-9a-f-]{36}$/i.test(value.trim());
|
|
@@ -1110,60 +4120,110 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1110
4120
|
message: "Select your accounting provider:",
|
|
1111
4121
|
choices: [
|
|
1112
4122
|
{ name: "Bokio", value: "bokio" },
|
|
1113
|
-
{ name: "Fortnox
|
|
4123
|
+
{ name: "Fortnox", value: "fortnox" }
|
|
1114
4124
|
]
|
|
1115
4125
|
});
|
|
1116
4126
|
}
|
|
1117
|
-
if (provider !== "bokio") {
|
|
1118
|
-
console.log(
|
|
4127
|
+
if (provider !== "bokio" && provider !== "fortnox") {
|
|
4128
|
+
console.log(`Unknown provider: ${provider}. Supported: bokio, fortnox`);
|
|
1119
4129
|
process.exit(1);
|
|
1120
4130
|
}
|
|
1121
|
-
|
|
1122
|
-
let
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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(`
|
|
1128
4145
|
To connect to Bokio, you need:`);
|
|
1129
|
-
|
|
1130
|
-
|
|
4146
|
+
console.log(" - API Token: Bokio app > Settings > Integrations > Create Integration");
|
|
4147
|
+
console.log(` - Company ID: From your Bokio URL (app.bokio.se/{companyId}/...)
|
|
1131
4148
|
`);
|
|
1132
|
-
|
|
4149
|
+
console.log(` Tip: Set BOKIO_TOKEN env var to avoid entering token each time
|
|
1133
4150
|
`);
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
companyId
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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;
|
|
1148
4168
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
|
1151
4216
|
});
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
spinner.succeed(`Connected to: ${companyName}`);
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
spinner.fail("Failed to connect to Bokio");
|
|
1165
|
-
console.error(error instanceof Error ? error.message : "Unknown error");
|
|
1166
|
-
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
|
+
}
|
|
1167
4227
|
}
|
|
1168
4228
|
const suggestedName = `kvitton-${sanitizeOrgName(companyName)}`;
|
|
1169
4229
|
let folderName;
|
|
@@ -1190,9 +4250,9 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1190
4250
|
}
|
|
1191
4251
|
});
|
|
1192
4252
|
}
|
|
1193
|
-
const targetDir =
|
|
4253
|
+
const targetDir = path7.resolve(process.cwd(), folderName.trim());
|
|
1194
4254
|
try {
|
|
1195
|
-
await
|
|
4255
|
+
await fs11.access(targetDir);
|
|
1196
4256
|
if (acceptDefaults) {
|
|
1197
4257
|
console.error(`Error: Directory ${folderName} already exists`);
|
|
1198
4258
|
process.exit(1);
|
|
@@ -1207,22 +4267,46 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1207
4267
|
}
|
|
1208
4268
|
} catch {}
|
|
1209
4269
|
const gitSpinner = ora("Initializing repository...").start();
|
|
1210
|
-
await
|
|
4270
|
+
await fs11.mkdir(targetDir, { recursive: true });
|
|
1211
4271
|
await initGitRepo(targetDir);
|
|
1212
4272
|
gitSpinner.succeed("Repository initialized");
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
+
}
|
|
1218
4293
|
console.log(" Created .env with credentials");
|
|
1219
4294
|
await createAgentsFile(targetDir, {
|
|
1220
4295
|
companyName,
|
|
1221
|
-
provider: "Bokio"
|
|
4296
|
+
provider: provider === "bokio" ? "Bokio" : "Fortnox"
|
|
1222
4297
|
});
|
|
1223
4298
|
console.log(" Created AGENTS.md and CLAUDE.md symlink");
|
|
1224
4299
|
const accountsSpinner = ora("Syncing chart of accounts...").start();
|
|
1225
|
-
|
|
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
|
+
}
|
|
1226
4310
|
accountsSpinner.succeed(`Synced ${accountsCount} accounts to accounts.yaml`);
|
|
1227
4311
|
let shouldSync;
|
|
1228
4312
|
if (options.sync !== undefined) {
|
|
@@ -1231,28 +4315,57 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1231
4315
|
shouldSync = true;
|
|
1232
4316
|
} else {
|
|
1233
4317
|
shouldSync = await confirm({
|
|
1234
|
-
message:
|
|
4318
|
+
message: `Sync existing journal entries from ${provider === "bokio" ? "Bokio" : "Fortnox"}?`,
|
|
1235
4319
|
default: true
|
|
1236
4320
|
});
|
|
1237
4321
|
}
|
|
1238
4322
|
let entriesCount = 0;
|
|
1239
4323
|
let fiscalYearsCount = 0;
|
|
1240
4324
|
if (shouldSync) {
|
|
1241
|
-
const shouldDownloadFiles = options.downloadFiles
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
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
|
+
}
|
|
1250
4362
|
}
|
|
1251
4363
|
console.log(" Sync complete!");
|
|
1252
4364
|
}
|
|
1253
4365
|
const commitSpinner = ora("Creating initial commit...").start();
|
|
1254
4366
|
await commitAll(targetDir, "Initial commit");
|
|
1255
4367
|
commitSpinner.succeed("Initial commit created");
|
|
4368
|
+
const providerName = provider === "bokio" ? "Bokio" : "Fortnox";
|
|
1256
4369
|
console.log(`
|
|
1257
4370
|
Success! Created ${folderName} at ${targetDir}
|
|
1258
4371
|
|
|
@@ -1268,24 +4381,25 @@ async function createBookkeepingRepo(name, options = {}) {
|
|
|
1268
4381
|
journal-entries/FY-2024/A-001-2024-01-15-invoice/entry.yaml
|
|
1269
4382
|
|
|
1270
4383
|
Commands:
|
|
1271
|
-
kvitton sync-journal Sync journal entries from
|
|
1272
|
-
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}
|
|
1273
4386
|
kvitton company-info Display company information
|
|
1274
4387
|
`);
|
|
1275
4388
|
}
|
|
1276
4389
|
|
|
1277
4390
|
// src/index.ts
|
|
1278
4391
|
var program = new Command;
|
|
1279
|
-
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", `
|
|
1280
4393
|
Environment Variables:
|
|
1281
|
-
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)
|
|
1282
4397
|
|
|
1283
4398
|
Examples:
|
|
1284
4399
|
$ create-kvitton
|
|
1285
4400
|
$ create-kvitton my-company-books
|
|
1286
|
-
$ create-kvitton
|
|
1287
|
-
$ create-kvitton my-repo -c abc-123
|
|
1288
|
-
$ 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
|
|
1289
4403
|
`).action((name, options) => {
|
|
1290
4404
|
createBookkeepingRepo(name, options);
|
|
1291
4405
|
});
|