@jaskrowo/xtb2sure 0.1.0 → 0.2.2
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/README.md +4 -4
- package/bin/xtb2sure.js +3 -0
- package/package.json +15 -7
- package/src/cli.ts +1 -1
- package/.editorconfig +0 -30
- package/.github/actions/setup/action.yml +0 -15
- package/.github/actions/validate-pr-title/action.yml +0 -18
- package/.github/pull_request_template.md +0 -3
- package/.github/workflows/main.yml +0 -51
- package/.github/workflows/npm-audit.yml +0 -23
- package/.github/workflows/pr.yml +0 -20
- package/.github/workflows/validate.yml +0 -60
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.release-it.cjs +0 -41
- package/CODE_OF_CONDUCT.md +0 -23
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -11
- package/biome.json +0 -20
- package/commitlint.config.cjs +0 -12
- package/config/default.mjs +0 -11
- package/docs/PRD.md +0 -141
- package/knip.config.js +0 -5
- package/renovate.json +0 -10
- package/test/cli.test.ts +0 -148
- package/test/date.test.ts +0 -8
- package/test/fixtures/IKE/input.xlsx +0 -0
- package/test/fixtures/IKE/output_investments.csv +0 -3
- package/test/fixtures/IKE/output_transactions.csv +0 -3
- package/test/fixtures/PLN/input.xlsx +0 -0
- package/test/fixtures/PLN/output_investments.csv +0 -5
- package/test/fixtures/PLN/output_transactions.csv +0 -6
- package/test/integration.test.ts +0 -171
- package/test/operations.test.ts +0 -23
- package/tsconfig.json +0 -24
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Convert XTB `.xlsx` exports into Sure-compatible CSV files.
|
|
|
11
11
|
|
|
12
12
|
## Requirements
|
|
13
13
|
|
|
14
|
-
- Node.js 24
|
|
14
|
+
- Node.js 24+
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ npm install
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
node
|
|
25
|
+
node ./src/cli.ts convert <input.xlsx> --currency <CODE> --account "<ACCOUNT_NAME>"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Options:
|
|
@@ -37,11 +37,11 @@ Options:
|
|
|
37
37
|
Examples:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
node
|
|
40
|
+
node ./src/cli.ts convert sample_data/IKE/input.xlsx --currency USD --account "XTB IKE"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
node
|
|
44
|
+
node ./src/cli.ts convert sample_data/PLN/input.xlsx --currency PLN --account "XTB PLN" --out-dir ./out
|
|
45
45
|
|
|
46
46
|
Or via npm script:
|
|
47
47
|
|
package/bin/xtb2sure.js
ADDED
package/package.json
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaskrowo/xtb2sure",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Convert XTB .xlsx exports into Sure-compatible CSV files",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": "24"
|
|
7
|
+
"node": ">=24"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/domeq/xtb2sure.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"bin"
|
|
21
|
+
],
|
|
14
22
|
"bin": {
|
|
15
|
-
"xtb2sure": "./
|
|
23
|
+
"xtb2sure": "./bin/xtb2sure.js"
|
|
16
24
|
},
|
|
17
25
|
"scripts": {
|
|
18
|
-
"dev": "node --
|
|
19
|
-
"convert": "node
|
|
26
|
+
"dev": "node --watch src/index.ts",
|
|
27
|
+
"convert": "node ./src/cli.ts convert",
|
|
20
28
|
"format": "biome format --write .",
|
|
21
29
|
"knip": "knip",
|
|
22
30
|
"lint-fix": "biome check --write .",
|
|
23
31
|
"lint": "biome check .",
|
|
24
32
|
"prepare": "husky",
|
|
25
33
|
"release": "release-it",
|
|
26
|
-
"start": "node
|
|
27
|
-
"test": "node --
|
|
34
|
+
"start": "node src/index.ts",
|
|
35
|
+
"test": "node --test",
|
|
28
36
|
"typecheck": "tsc --noEmit"
|
|
29
37
|
},
|
|
30
38
|
"lint-staged": {
|
package/src/cli.ts
CHANGED
package/.editorconfig
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs.
|
|
2
|
-
# Requires EditorConfig JetBrains Plugin - http://github.com/editorconfig/editorconfig-jetbrains
|
|
3
|
-
|
|
4
|
-
# Set this file as the topmost .editorconfig
|
|
5
|
-
# (multiple files can be used, and are applied starting from current document location)
|
|
6
|
-
root = true
|
|
7
|
-
|
|
8
|
-
# Use bracketed regexp to target specific file types or file locations
|
|
9
|
-
[*.{ts,js,tsx,json,yml,yaml}]
|
|
10
|
-
|
|
11
|
-
# Use hard or soft tabs ["tab", "space"]
|
|
12
|
-
indent_style = space
|
|
13
|
-
|
|
14
|
-
# Size of a single indent [an integer, "tab"]
|
|
15
|
-
indent_size = tab
|
|
16
|
-
|
|
17
|
-
# Number of columns representing a tab character [an integer]
|
|
18
|
-
tab_width = 4
|
|
19
|
-
|
|
20
|
-
# Line breaks representation ["lf", "cr", "crlf"]
|
|
21
|
-
end_of_line = lf
|
|
22
|
-
|
|
23
|
-
# ["latin1", "utf-8", "utf-16be", "utf-16le"]
|
|
24
|
-
charset = utf-8
|
|
25
|
-
|
|
26
|
-
# Remove any whitespace characters preceding newline characters ["true", "false"]
|
|
27
|
-
trim_trailing_whitespace = false
|
|
28
|
-
|
|
29
|
-
# Ensure file ends with a newline when saving ["true", "false"]
|
|
30
|
-
insert_final_newline = true
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
name: Runtime & application setup
|
|
2
|
-
description: Sets right node version, installs dependencies
|
|
3
|
-
|
|
4
|
-
runs:
|
|
5
|
-
using: 'composite'
|
|
6
|
-
steps:
|
|
7
|
-
- name: Set correct node version
|
|
8
|
-
uses: actions/setup-node@v7
|
|
9
|
-
with:
|
|
10
|
-
node-version-file: package.json
|
|
11
|
-
cache: 'npm'
|
|
12
|
-
registry-url: 'https://registry.npmjs.org'
|
|
13
|
-
- name: Install dependencies
|
|
14
|
-
run: npm ci --quiet --no-audit --no-fund --ignore-scripts
|
|
15
|
-
shell: bash
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
name: Validate PR title
|
|
2
|
-
description: Runs commitlint on the PR title
|
|
3
|
-
|
|
4
|
-
runs:
|
|
5
|
-
using: composite
|
|
6
|
-
steps:
|
|
7
|
-
- name: Set correct node version
|
|
8
|
-
uses: actions/setup-node@v7
|
|
9
|
-
with:
|
|
10
|
-
node-version: 24.x
|
|
11
|
-
- name: Install commitlint
|
|
12
|
-
run: npm install -g @commitlint/cli @commitlint/config-conventional
|
|
13
|
-
shell: bash
|
|
14
|
-
- name: Validate PR title
|
|
15
|
-
env:
|
|
16
|
-
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
17
|
-
run: echo "$PR_TITLE" | npx commitlint
|
|
18
|
-
shell: bash
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
name: Main branch
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
push:
|
|
6
|
-
branches:
|
|
7
|
-
- main
|
|
8
|
-
merge_group:
|
|
9
|
-
|
|
10
|
-
permissions:
|
|
11
|
-
id-token: write
|
|
12
|
-
contents: read
|
|
13
|
-
|
|
14
|
-
env:
|
|
15
|
-
TZ: ${{ vars.TZ }}
|
|
16
|
-
FORCE_COLOR: 1
|
|
17
|
-
|
|
18
|
-
jobs:
|
|
19
|
-
validate:
|
|
20
|
-
name: Validate
|
|
21
|
-
uses: ./.github/workflows/validate.yml
|
|
22
|
-
secrets: inherit
|
|
23
|
-
|
|
24
|
-
npm:
|
|
25
|
-
name: NPM audit
|
|
26
|
-
uses: ./.github/workflows/npm-audit.yml
|
|
27
|
-
secrets: inherit
|
|
28
|
-
|
|
29
|
-
release:
|
|
30
|
-
name: Release
|
|
31
|
-
needs: [validate]
|
|
32
|
-
runs-on: ubuntu-latest
|
|
33
|
-
concurrency: ${{ github.workflow }}-release
|
|
34
|
-
permissions:
|
|
35
|
-
contents: write
|
|
36
|
-
id-token: write
|
|
37
|
-
steps:
|
|
38
|
-
- uses: actions/checkout@v7
|
|
39
|
-
with:
|
|
40
|
-
fetch-depth: 0
|
|
41
|
-
token: ${{ secrets.RELEASE_TOKEN }}
|
|
42
|
-
- name: Select correct node version, install deps
|
|
43
|
-
uses: ./.github/actions/setup
|
|
44
|
-
- name: Configure git
|
|
45
|
-
run: |
|
|
46
|
-
git config user.name "domeq"
|
|
47
|
-
git config user.email "iskra.wojtek@gmail.com"
|
|
48
|
-
- name: Create a release
|
|
49
|
-
env:
|
|
50
|
-
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
51
|
-
run: npm run release -- --ci
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
name: NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_call:
|
|
5
|
-
|
|
6
|
-
env:
|
|
7
|
-
TZ: Europe/Oslo
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
audit:
|
|
11
|
-
name: Audit
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
timeout-minutes: 15
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v7
|
|
16
|
-
- name: Set correct node version
|
|
17
|
-
uses: actions/setup-node@v7
|
|
18
|
-
with:
|
|
19
|
-
node-version-file: package.json
|
|
20
|
-
cache: npm
|
|
21
|
-
registry-url: 'https://registry.npmjs.org'
|
|
22
|
-
- name: NPM Audit
|
|
23
|
-
run: npm audit --omit=dev --audit-level=high
|
package/.github/workflows/pr.yml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: PR
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
permissions:
|
|
8
|
-
id-token: write
|
|
9
|
-
contents: read
|
|
10
|
-
pull-requests: write
|
|
11
|
-
|
|
12
|
-
env:
|
|
13
|
-
TZ: ${{ vars.TZ }}
|
|
14
|
-
FORCE_COLOR: 1
|
|
15
|
-
|
|
16
|
-
jobs:
|
|
17
|
-
validate:
|
|
18
|
-
name: Validate
|
|
19
|
-
uses: ./.github/workflows/validate.yml
|
|
20
|
-
secrets: inherit
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
name: Validate
|
|
2
|
-
|
|
3
|
-
permissions:
|
|
4
|
-
id-token: write
|
|
5
|
-
contents: read
|
|
6
|
-
|
|
7
|
-
on:
|
|
8
|
-
workflow_call:
|
|
9
|
-
|
|
10
|
-
env:
|
|
11
|
-
TZ: ${{ vars.TZ }}
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
validate_pr_title:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
name: Validate PR title
|
|
17
|
-
if: github.event.pull_request != null
|
|
18
|
-
timeout-minutes: 5
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v7
|
|
21
|
-
- name: Validate PR title
|
|
22
|
-
uses: ./.github/actions/validate-pr-title
|
|
23
|
-
linters:
|
|
24
|
-
name: Linters
|
|
25
|
-
runs-on: ubuntu-latest
|
|
26
|
-
timeout-minutes: 15
|
|
27
|
-
steps:
|
|
28
|
-
- uses: actions/checkout@v7
|
|
29
|
-
- uses: ./.github/actions/setup
|
|
30
|
-
- name: Lint
|
|
31
|
-
run: npm run lint
|
|
32
|
-
typecheck:
|
|
33
|
-
name: Typecheck
|
|
34
|
-
runs-on: ubuntu-latest
|
|
35
|
-
timeout-minutes: 15
|
|
36
|
-
steps:
|
|
37
|
-
- uses: actions/checkout@v7
|
|
38
|
-
- uses: ./.github/actions/setup
|
|
39
|
-
- name: Typecheck
|
|
40
|
-
run: npm run typecheck
|
|
41
|
-
knip:
|
|
42
|
-
name: Knip
|
|
43
|
-
runs-on: ubuntu-latest
|
|
44
|
-
timeout-minutes: 15
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v7
|
|
47
|
-
- uses: ./.github/actions/setup
|
|
48
|
-
- name: Knip
|
|
49
|
-
run: npm run knip
|
|
50
|
-
- name: Knip (production mode)
|
|
51
|
-
run: npm run knip -- --production
|
|
52
|
-
tests:
|
|
53
|
-
name: Tests
|
|
54
|
-
runs-on: ubuntu-latest
|
|
55
|
-
timeout-minutes: 15
|
|
56
|
-
steps:
|
|
57
|
-
- uses: actions/checkout@v7
|
|
58
|
-
- uses: ./.github/actions/setup
|
|
59
|
-
- name: Test
|
|
60
|
-
run: npm test
|
package/.husky/commit-msg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
npx --no -- commitlint --edit ${1}
|
package/.husky/pre-commit
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
npx lint-staged
|
package/.release-it.cjs
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
git: {
|
|
3
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholders
|
|
4
|
-
commitMessage: "chore: release ${version} \n\n${changelog}",
|
|
5
|
-
requireBranch: "main",
|
|
6
|
-
requireCleanWorkingDir: true,
|
|
7
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholders
|
|
8
|
-
tagName: "v${version}",
|
|
9
|
-
},
|
|
10
|
-
github: {
|
|
11
|
-
release: true,
|
|
12
|
-
tokenRef: "GITHUB_TOKEN",
|
|
13
|
-
},
|
|
14
|
-
npm: {
|
|
15
|
-
publish: true,
|
|
16
|
-
skipChecks: true,
|
|
17
|
-
},
|
|
18
|
-
plugins: {
|
|
19
|
-
"@release-it/conventional-changelog": {
|
|
20
|
-
infile: "CHANGELOG.md",
|
|
21
|
-
preset: {
|
|
22
|
-
name: "conventionalcommits",
|
|
23
|
-
types: [
|
|
24
|
-
{ section: "Features", type: "feat" },
|
|
25
|
-
{ section: "Features", type: "feature" },
|
|
26
|
-
{ section: "Bug Fixes", type: "fix" },
|
|
27
|
-
{ section: "Performance Improvements", type: "perf" },
|
|
28
|
-
{ section: "Reverts", type: "revert" },
|
|
29
|
-
{ section: "Documentation", type: "docs" },
|
|
30
|
-
{ section: "Styles", type: "style" },
|
|
31
|
-
{ section: "Miscellaneous Chores", type: "chore" },
|
|
32
|
-
{ section: "Code Refactoring", type: "refactor" },
|
|
33
|
-
{ section: "Tests", type: "test" },
|
|
34
|
-
{ section: "Build System", type: "build" },
|
|
35
|
-
{ section: "Continuous Integration", type: "ci" },
|
|
36
|
-
{ section: "Configuration changes", type: "config" },
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
package/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Code of Conduct
|
|
2
|
-
|
|
3
|
-
## Our Pledge
|
|
4
|
-
|
|
5
|
-
We are committed to providing a friendly, safe, and welcoming environment for everyone.
|
|
6
|
-
|
|
7
|
-
## Our Standards
|
|
8
|
-
|
|
9
|
-
Examples of positive behavior:
|
|
10
|
-
|
|
11
|
-
- Being respectful and constructive
|
|
12
|
-
- Accepting and giving feedback gracefully
|
|
13
|
-
- Prioritizing collaboration and empathy
|
|
14
|
-
|
|
15
|
-
Examples of unacceptable behavior:
|
|
16
|
-
|
|
17
|
-
- Harassment, discrimination, or abusive language
|
|
18
|
-
- Personal attacks or trolling
|
|
19
|
-
- Publishing private information without consent
|
|
20
|
-
|
|
21
|
-
## Enforcement
|
|
22
|
-
|
|
23
|
-
Project maintainers are responsible for clarifying and enforcing this code of conduct.
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Contributing
|
|
2
|
-
|
|
3
|
-
Thanks for your interest in contributing to `xtb2sure`.
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
1. Install Node.js 22+.
|
|
8
|
-
2. Install dependencies:
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Validate changes
|
|
15
|
-
|
|
16
|
-
Run the test suite before opening a pull request:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm test
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Guidelines
|
|
23
|
-
|
|
24
|
-
- Keep behavior deterministic and fixture-driven.
|
|
25
|
-
- Add tests for every functional change.
|
|
26
|
-
- Prefer small, focused pull requests.
|
package/SECURITY.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
If you discover a security issue, please report it privately to the maintainers.
|
|
4
|
-
|
|
5
|
-
Please include:
|
|
6
|
-
|
|
7
|
-
- A clear description of the issue
|
|
8
|
-
- Steps to reproduce
|
|
9
|
-
- Potential impact
|
|
10
|
-
|
|
11
|
-
Do not open public issues for security vulnerabilities.
|
package/biome.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
|
|
3
|
-
"vcs": {
|
|
4
|
-
"enabled": true,
|
|
5
|
-
"clientKind": "git",
|
|
6
|
-
"useIgnoreFile": true
|
|
7
|
-
},
|
|
8
|
-
"formatter": {
|
|
9
|
-
"enabled": true,
|
|
10
|
-
"indentStyle": "space",
|
|
11
|
-
"indentWidth": 4,
|
|
12
|
-
"lineWidth": 120
|
|
13
|
-
},
|
|
14
|
-
"linter": {
|
|
15
|
-
"enabled": true,
|
|
16
|
-
"rules": {
|
|
17
|
-
"recommended": true
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
package/commitlint.config.cjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
extends: ["@commitlint/config-conventional"],
|
|
3
|
-
rules: {
|
|
4
|
-
"body-max-line-length": [2, "always", Infinity],
|
|
5
|
-
"footer-max-line-length": [2, "always", Infinity],
|
|
6
|
-
"type-enum": [
|
|
7
|
-
2,
|
|
8
|
-
"always",
|
|
9
|
-
["build", "chore", "ci", "config", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"],
|
|
10
|
-
],
|
|
11
|
-
},
|
|
12
|
-
};
|
package/config/default.mjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const NAME = process.env.DD_SERVICE || "xtb2sure";
|
|
2
|
-
const ENVIRONMENT = process.env.NODE_ENV || "development";
|
|
3
|
-
const VERSION = process.env.npm_package_version || "dev";
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
application: {
|
|
7
|
-
env: ENVIRONMENT,
|
|
8
|
-
name: NAME,
|
|
9
|
-
version: VERSION,
|
|
10
|
-
},
|
|
11
|
-
};
|
package/docs/PRD.md
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# xtb2sure PRD
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Build an open-source TypeScript CLI that converts XTB `.xlsx` exports into two Sure-compatible CSV files:
|
|
6
|
-
|
|
7
|
-
1. transactions CSV (for Sure Transactions import)
|
|
8
|
-
2. investments CSV (for Sure Trades import)
|
|
9
|
-
|
|
10
|
-
Target runtime is current Node LTS, executed directly via Node strip-types.
|
|
11
|
-
|
|
12
|
-
## Confirmed Decisions
|
|
13
|
-
|
|
14
|
-
- Input format: `.xlsx`
|
|
15
|
-
- Source data: only `Cash Operations` sheet
|
|
16
|
-
- Output: two CSVs per input file
|
|
17
|
-
- Date output format: `YYYY-MM-DD`
|
|
18
|
-
- Currency: required CLI parameter
|
|
19
|
-
- Account: account-level label provided by CLI (e.g. `XTB PLN`, `XTB IKE`)
|
|
20
|
-
- Include tax rows (`Free funds interest tax`, `Withholding tax`)
|
|
21
|
-
- Ignore summary `Total` row
|
|
22
|
-
- Trade ticker uses XTB input as-is (e.g. `EIMI.UK`, `IUSQ.DE`)
|
|
23
|
-
|
|
24
|
-
## Input Schema (XTB Cash Operations)
|
|
25
|
-
|
|
26
|
-
Relevant columns:
|
|
27
|
-
- `Type`
|
|
28
|
-
- `Instrument`
|
|
29
|
-
- `Ticker`
|
|
30
|
-
- `Time`
|
|
31
|
-
- `Amount`
|
|
32
|
-
- `ID`
|
|
33
|
-
- `Comment`
|
|
34
|
-
- `Product`
|
|
35
|
-
- `Position ID`
|
|
36
|
-
|
|
37
|
-
## Output A: Transactions CSV
|
|
38
|
-
|
|
39
|
-
Header:
|
|
40
|
-
- `date,amount,name,account,notes`
|
|
41
|
-
|
|
42
|
-
Includes operation types:
|
|
43
|
-
- `Deposit`
|
|
44
|
-
- `IKE deposit`
|
|
45
|
-
- `IKZE deposit`
|
|
46
|
-
- `Free funds interest`
|
|
47
|
-
- `Free funds interest tax`
|
|
48
|
-
- `Withholding tax`
|
|
49
|
-
|
|
50
|
-
Mapping:
|
|
51
|
-
- `date` from `Time` (Excel serial UTC) -> `YYYY-MM-DD`
|
|
52
|
-
- `amount` from `Amount` (signed as-is)
|
|
53
|
-
- `name` from `Type` (or normalized label if fixture requires)
|
|
54
|
-
- `account` from CLI account label
|
|
55
|
-
- `notes` as traceable text including `XTB <ID>` and source comment
|
|
56
|
-
|
|
57
|
-
## Output B: Investments CSV
|
|
58
|
-
|
|
59
|
-
Header:
|
|
60
|
-
- `date,ticker,currency,qty,price,account,name`
|
|
61
|
-
|
|
62
|
-
Includes operation types:
|
|
63
|
-
- `Stock purchase`
|
|
64
|
-
- `Stock sell`
|
|
65
|
-
|
|
66
|
-
Mapping:
|
|
67
|
-
- `date` from `Time` -> `YYYY-MM-DD`
|
|
68
|
-
- `ticker` from XTB `Ticker` as-is (e.g. `IPOL.UK`, `SXRV.DE`)
|
|
69
|
-
- `currency` from CLI currency argument
|
|
70
|
-
- `qty` parsed from `Comment` (sell rows negative where reflected in fixture)
|
|
71
|
-
- `price` parsed from `Comment` (`@ <price>`)
|
|
72
|
-
- `account` from CLI account label
|
|
73
|
-
- `name` fixture-compatible human-readable label including `XTB <ID>`
|
|
74
|
-
|
|
75
|
-
## CLI (v1)
|
|
76
|
-
|
|
77
|
-
Command:
|
|
78
|
-
- `xtb2sure convert <input.xlsx> --currency <CODE> --account "<ACCOUNT_NAME>"`
|
|
79
|
-
|
|
80
|
-
Options:
|
|
81
|
-
- `--out-dir <dir>`
|
|
82
|
-
- `--transactions-out <file>`
|
|
83
|
-
- `--investments-out <file>`
|
|
84
|
-
- `--strict`
|
|
85
|
-
- `--dry-run`
|
|
86
|
-
- `--report <file>`
|
|
87
|
-
|
|
88
|
-
Exit codes:
|
|
89
|
-
- `0` success
|
|
90
|
-
- `1` conversion/validation failure
|
|
91
|
-
- `2` usage/config error
|
|
92
|
-
|
|
93
|
-
## TDD Plan
|
|
94
|
-
|
|
95
|
-
### Unit tests
|
|
96
|
-
- Excel serial date conversion
|
|
97
|
-
- operation classification (transaction/investment/ignored)
|
|
98
|
-
- qty/price parser from comment text
|
|
99
|
-
- per-row mapping by type
|
|
100
|
-
|
|
101
|
-
### Integration tests (fixture exact)
|
|
102
|
-
- `sample_data/IKE/input.xlsx` -> exact match:
|
|
103
|
-
- `sample_data/IKE/output_transactions.csv`
|
|
104
|
-
- `sample_data/IKE/output_investments.csv`
|
|
105
|
-
- `sample_data/PLN/input.xlsx` -> exact match:
|
|
106
|
-
- `sample_data/PLN/output_transactions.csv`
|
|
107
|
-
- `sample_data/PLN/output_investments.csv`
|
|
108
|
-
- investments schema regression checks:
|
|
109
|
-
- `ticker` equals source `Cash Operations.Ticker` value for the matching XTB operation ID
|
|
110
|
-
|
|
111
|
-
### CLI tests
|
|
112
|
-
- required args validation (`--currency`, `--account`)
|
|
113
|
-
- output file generation
|
|
114
|
-
- `--dry-run` behavior
|
|
115
|
-
- strict-mode failure behavior
|
|
116
|
-
- summary counters and exit codes
|
|
117
|
-
|
|
118
|
-
## Open Source Baseline
|
|
119
|
-
|
|
120
|
-
Include in repo from the beginning:
|
|
121
|
-
- `README.md`
|
|
122
|
-
- `LICENSE` (permissive and inclusive; MIT recommended unless you choose otherwise)
|
|
123
|
-
- `CONTRIBUTING.md`
|
|
124
|
-
- `CODE_OF_CONDUCT.md`
|
|
125
|
-
- optional but recommended: `SECURITY.md`, CI workflow, issue templates
|
|
126
|
-
|
|
127
|
-
## Non-goals (v1)
|
|
128
|
-
|
|
129
|
-
- `Closed Positions` / `Open Positions` parsing
|
|
130
|
-
- Sure API upload automation
|
|
131
|
-
- FX conversion logic
|
|
132
|
-
- Data enrichment beyond deterministic mapping
|
|
133
|
-
|
|
134
|
-
## Fixture Status
|
|
135
|
-
|
|
136
|
-
Current fixtures are aligned and usable for TDD:
|
|
137
|
-
- `sample_data/IKE`
|
|
138
|
-
- `sample_data/PLN`
|
|
139
|
-
|
|
140
|
-
Investments outputs now use:
|
|
141
|
-
- ticker from input as-is
|
package/knip.config.js
DELETED
package/renovate.json
DELETED
package/test/cli.test.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { access, mkdtemp, readFile } from "node:fs/promises";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import xlsx from "xlsx";
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
const rootDir = path.resolve(__dirname, "..");
|
|
13
|
-
const cliPath = path.join(rootDir, "src", "cli.ts");
|
|
14
|
-
|
|
15
|
-
test("CLI validates required --currency and --account args", async () => {
|
|
16
|
-
const result = await runCli([
|
|
17
|
-
"convert",
|
|
18
|
-
path.join(rootDir, "test", "fixtures", "IKE", "input.xlsx"),
|
|
19
|
-
"--currency",
|
|
20
|
-
"USD",
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
assert.equal(result.code, 2);
|
|
24
|
-
assert.match(result.stderr, /required option/);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("CLI generates output files", async () => {
|
|
28
|
-
const tmpDir = await mkdtemp(path.join(os.tmpdir(), "xtb2sure-cli-"));
|
|
29
|
-
const txPath = path.join(tmpDir, "tx.csv");
|
|
30
|
-
const invPath = path.join(tmpDir, "inv.csv");
|
|
31
|
-
|
|
32
|
-
const result = await runCli([
|
|
33
|
-
"convert",
|
|
34
|
-
path.join(rootDir, "test", "fixtures", "IKE", "input.xlsx"),
|
|
35
|
-
"--currency",
|
|
36
|
-
"USD",
|
|
37
|
-
"--account",
|
|
38
|
-
"XTB IKE",
|
|
39
|
-
"--transactions-out",
|
|
40
|
-
txPath,
|
|
41
|
-
"--investments-out",
|
|
42
|
-
invPath,
|
|
43
|
-
]);
|
|
44
|
-
|
|
45
|
-
assert.equal(result.code, 0);
|
|
46
|
-
await access(txPath);
|
|
47
|
-
await access(invPath);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("CLI dry-run does not create output files", async () => {
|
|
51
|
-
const tmpDir = await mkdtemp(path.join(os.tmpdir(), "xtb2sure-dry-"));
|
|
52
|
-
const txPath = path.join(tmpDir, "tx.csv");
|
|
53
|
-
const invPath = path.join(tmpDir, "inv.csv");
|
|
54
|
-
|
|
55
|
-
const result = await runCli([
|
|
56
|
-
"convert",
|
|
57
|
-
path.join(rootDir, "test", "fixtures", "IKE", "input.xlsx"),
|
|
58
|
-
"--currency",
|
|
59
|
-
"USD",
|
|
60
|
-
"--account",
|
|
61
|
-
"XTB IKE",
|
|
62
|
-
"--transactions-out",
|
|
63
|
-
txPath,
|
|
64
|
-
"--investments-out",
|
|
65
|
-
invPath,
|
|
66
|
-
"--dry-run",
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
assert.equal(result.code, 0);
|
|
70
|
-
await assert.rejects(() => access(txPath));
|
|
71
|
-
await assert.rejects(() => access(invPath));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("CLI strict mode fails when mapping issue is encountered", async () => {
|
|
75
|
-
const tmpDir = await mkdtemp(path.join(os.tmpdir(), "xtb2sure-strict-"));
|
|
76
|
-
const inputPath = path.join(tmpDir, "bad.xlsx");
|
|
77
|
-
const reportPath = path.join(tmpDir, "report.json");
|
|
78
|
-
await writeInvalidTradeWorkbook(inputPath);
|
|
79
|
-
|
|
80
|
-
const strictResult = await runCli([
|
|
81
|
-
"convert",
|
|
82
|
-
inputPath,
|
|
83
|
-
"--currency",
|
|
84
|
-
"USD",
|
|
85
|
-
"--account",
|
|
86
|
-
"XTB TEST",
|
|
87
|
-
"--strict",
|
|
88
|
-
"--report",
|
|
89
|
-
reportPath,
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
assert.equal(strictResult.code, 1);
|
|
93
|
-
|
|
94
|
-
const relaxedResult = await runCli([
|
|
95
|
-
"convert",
|
|
96
|
-
inputPath,
|
|
97
|
-
"--currency",
|
|
98
|
-
"USD",
|
|
99
|
-
"--account",
|
|
100
|
-
"XTB TEST",
|
|
101
|
-
"--report",
|
|
102
|
-
reportPath,
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
assert.equal(relaxedResult.code, 0);
|
|
106
|
-
const report = JSON.parse(await readFile(reportPath, "utf8"));
|
|
107
|
-
assert.equal(report.totals.issues, 1);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
function runCli(args: string[]): Promise<{ code: number | null; stdout: string; stderr: string }> {
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
const child = spawn(process.execPath, ["--experimental-transform-types", cliPath, ...args], {
|
|
113
|
-
cwd: rootDir,
|
|
114
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
let stdout = "";
|
|
118
|
-
let stderr = "";
|
|
119
|
-
|
|
120
|
-
child.stdout.on("data", (chunk) => {
|
|
121
|
-
stdout += String(chunk);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
child.stderr.on("data", (chunk) => {
|
|
125
|
-
stderr += String(chunk);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
child.on("error", reject);
|
|
129
|
-
child.on("close", (code) => {
|
|
130
|
-
resolve({ code, stdout, stderr });
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function writeInvalidTradeWorkbook(outPath: string): Promise<void> {
|
|
136
|
-
const worksheet = xlsx.utils.aoa_to_sheet([
|
|
137
|
-
["Account number", "123"],
|
|
138
|
-
["Cash Operations"],
|
|
139
|
-
["Date from (UTC)", 46000],
|
|
140
|
-
["Date to (UTC)", 46001],
|
|
141
|
-
["Type", "Instrument", "Ticker", "Category", "Time", "Amount", "ID", "Comment", "Product", "Position ID"],
|
|
142
|
-
["Stock purchase", "Test instrument", "TEST.UK", "", 46000.5, 123.45, "1", "BROKEN COMMENT", "My Trades", ""],
|
|
143
|
-
]);
|
|
144
|
-
|
|
145
|
-
const workbook = xlsx.utils.book_new();
|
|
146
|
-
xlsx.utils.book_append_sheet(workbook, worksheet, "Cash Operations");
|
|
147
|
-
xlsx.writeFile(workbook, outPath);
|
|
148
|
-
}
|
package/test/date.test.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { excelSerialToYYYYMMDD } from "../src/date.ts";
|
|
4
|
-
|
|
5
|
-
test("excelSerialToYYYYMMDD converts known fixture serials", () => {
|
|
6
|
-
assert.equal(excelSerialToYYYYMMDD(46266.43755445602), "2026-09-01");
|
|
7
|
-
assert.equal(excelSerialToYYYYMMDD(46079.041666666664), "2026-02-26");
|
|
8
|
-
});
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
date,ticker,currency,qty,price,account,name
|
|
2
|
-
2026-01-05,IUSQ.DE,PLN,1.0,93.88,XTB PLN,MSCI ACWI - Stock purchase (XTB 910000005)
|
|
3
|
-
2026-01-06,IPOL.UK,PLN,3.0,34.04666667,XTB PLN,MSCI Poland - Stock purchase (XTB 910000007)
|
|
4
|
-
2026-01-09,SXR8.DE,PLN,-0.1019,637.68400393,XTB PLN,Core S&P 500 - Stock sell (XTB 910000006)
|
|
5
|
-
2026-01-21,EIMI.UK,PLN,114.0,47.33,XTB PLN,Core MSCI EM IMI - Stock purchase (XTB 910000008)
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
date,amount,name,account,notes
|
|
2
|
-
2026-09-04,5000.00,Deposit,XTB PLN,XTB 910000001: Mock bank deposit txn=MOCK-TXN-001 [subaccount: Investment Plans]
|
|
3
|
-
2026-03-05,0.01,Free funds interest,XTB PLN,XTB 910000002: Mock free-funds interest 2026-02 [subaccount: My Trades]
|
|
4
|
-
2026-03-05,-0.01,Free funds interest tax,XTB PLN,XTB 910000003: Mock free-funds interest tax 2026-02 [subaccount: My Trades]
|
|
5
|
-
2026-01-02,0.93,Withholding tax,XTB PLN,XTB 910000004: mock WHT correction [subaccount: Investment Plans]
|
|
6
|
-
2026-02-26,-11304.00,IKZE deposit,XTB PLN,XTB 910000009: Mock transfer out to IKZE 200002 [subaccount: My Trades]
|
package/test/integration.test.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { readCashOperationsSheet } from "../src/excel.ts";
|
|
7
|
-
import { convertFile } from "../src/index.ts";
|
|
8
|
-
import { classifyOperation } from "../src/operations.ts";
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
const rootDir = path.resolve(__dirname, "..");
|
|
13
|
-
|
|
14
|
-
test("IKE fixture conversion matches expected outputs", async () => {
|
|
15
|
-
const result = await convertFile({
|
|
16
|
-
account: "XTB IKE",
|
|
17
|
-
currency: "USD",
|
|
18
|
-
dryRun: true,
|
|
19
|
-
inputPath: path.join(rootDir, "test", "fixtures", "IKE", "input.xlsx"),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const expectedTransactions = await readExpected("test/fixtures/IKE/output_transactions.csv");
|
|
23
|
-
const expectedInvestments = await readExpected("test/fixtures/IKE/output_investments.csv");
|
|
24
|
-
|
|
25
|
-
assert.equal(normalizeEol(result.transactionsCsv), expectedTransactions);
|
|
26
|
-
assert.equal(normalizeEol(result.investmentsCsv), expectedInvestments);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("PLN fixture conversion matches expected outputs", async () => {
|
|
30
|
-
const result = await convertFile({
|
|
31
|
-
account: "XTB PLN",
|
|
32
|
-
currency: "PLN",
|
|
33
|
-
dryRun: true,
|
|
34
|
-
inputPath: path.join(rootDir, "test", "fixtures", "PLN", "input.xlsx"),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const expectedTransactions = await readExpected("test/fixtures/PLN/output_transactions.csv");
|
|
38
|
-
const expectedInvestments = await readExpected("test/fixtures/PLN/output_investments.csv");
|
|
39
|
-
|
|
40
|
-
assert.equal(normalizeEol(result.transactionsCsv), expectedTransactions);
|
|
41
|
-
assert.equal(normalizeEol(result.investmentsCsv), expectedInvestments);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("investment ticker regression: output ticker equals source ticker by XTB operation ID", async () => {
|
|
45
|
-
const inputPath = path.join(rootDir, "test", "fixtures", "PLN", "input.xlsx");
|
|
46
|
-
const result = await convertFile({
|
|
47
|
-
account: "XTB PLN",
|
|
48
|
-
currency: "PLN",
|
|
49
|
-
dryRun: true,
|
|
50
|
-
inputPath,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const rows = readCashOperationsSheet(inputPath);
|
|
54
|
-
const byId = new Map<string, string>();
|
|
55
|
-
|
|
56
|
-
for (const row of rows) {
|
|
57
|
-
if (classifyOperation(row.Type) === "investment") {
|
|
58
|
-
byId.set(row.ID, row.Ticker);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const line of normalizeEol(result.investmentsCsv).split("\n").slice(1)) {
|
|
63
|
-
if (!line.trim()) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const cells = parseCsvLine(line);
|
|
68
|
-
const ticker = cells[1];
|
|
69
|
-
const name = cells[6];
|
|
70
|
-
assert.ok(typeof ticker === "string" && ticker.length > 0);
|
|
71
|
-
assert.ok(typeof name === "string" && name.length > 0);
|
|
72
|
-
const idMatch = name.match(/XTB\s+(\d+)/);
|
|
73
|
-
assert.ok(idMatch, `Missing XTB ID in row: ${line}`);
|
|
74
|
-
const id = idMatch[1];
|
|
75
|
-
assert.ok(id);
|
|
76
|
-
assert.equal(ticker, byId.get(id));
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("investment currency is always taken from CLI argument", async () => {
|
|
81
|
-
const result = await convertFile({
|
|
82
|
-
account: "XTB PLN",
|
|
83
|
-
currency: "PLN",
|
|
84
|
-
dryRun: true,
|
|
85
|
-
inputPath: path.join(rootDir, "test", "fixtures", "PLN", "input.xlsx"),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
for (const line of normalizeEol(result.investmentsCsv).split("\n").slice(1)) {
|
|
89
|
-
if (!line.trim()) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const cells = parseCsvLine(line);
|
|
94
|
-
assert.equal(cells[2], "PLN");
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("investment price is derived from Amount and qty", async () => {
|
|
99
|
-
const inputPath = path.join(rootDir, "test", "fixtures", "PLN", "input.xlsx");
|
|
100
|
-
const result = await convertFile({
|
|
101
|
-
account: "XTB PLN",
|
|
102
|
-
currency: "PLN",
|
|
103
|
-
dryRun: true,
|
|
104
|
-
inputPath,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const rows = readCashOperationsSheet(inputPath);
|
|
108
|
-
const amountById = new Map<string, number>();
|
|
109
|
-
|
|
110
|
-
for (const row of rows) {
|
|
111
|
-
if (classifyOperation(row.Type) === "investment") {
|
|
112
|
-
amountById.set(row.ID, Math.abs(row.Amount));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (const line of normalizeEol(result.investmentsCsv).split("\n").slice(1)) {
|
|
117
|
-
if (!line.trim()) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const cells = parseCsvLine(line);
|
|
122
|
-
const qty = Math.abs(Number(cells[3]));
|
|
123
|
-
const price = Number(cells[4]);
|
|
124
|
-
const name = cells[6] ?? "";
|
|
125
|
-
const idMatch = name.match(/XTB\s+(\d+)/);
|
|
126
|
-
assert.ok(idMatch, `Missing XTB ID in row: ${line}`);
|
|
127
|
-
const id = idMatch[1];
|
|
128
|
-
assert.ok(id);
|
|
129
|
-
const expectedAmount = amountById.get(id);
|
|
130
|
-
assert.ok(typeof expectedAmount === "number", `No amount found for ID ${id}`);
|
|
131
|
-
|
|
132
|
-
const actualAmount = qty * price;
|
|
133
|
-
assert.ok(Math.abs(actualAmount - expectedAmount) < 0.02, `Amount mismatch for ${id}`);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
async function readExpected(relativePath: string): Promise<string> {
|
|
138
|
-
const value = await readFile(path.join(rootDir, relativePath), "utf8");
|
|
139
|
-
return normalizeEol(value);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function normalizeEol(value: string): string {
|
|
143
|
-
return value.replace(/\r\n/g, "\n");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function parseCsvLine(line: string): string[] {
|
|
147
|
-
const cells: string[] = [];
|
|
148
|
-
let current = "";
|
|
149
|
-
let inQuotes = false;
|
|
150
|
-
|
|
151
|
-
for (let i = 0; i < line.length; i += 1) {
|
|
152
|
-
const char = line[i];
|
|
153
|
-
if (char === '"') {
|
|
154
|
-
if (inQuotes && line[i + 1] === '"') {
|
|
155
|
-
current += '"';
|
|
156
|
-
i += 1;
|
|
157
|
-
} else {
|
|
158
|
-
inQuotes = !inQuotes;
|
|
159
|
-
}
|
|
160
|
-
} else if (char === "," && !inQuotes) {
|
|
161
|
-
cells.push(current);
|
|
162
|
-
current = "";
|
|
163
|
-
} else {
|
|
164
|
-
current += char;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
cells.push(current);
|
|
169
|
-
|
|
170
|
-
return cells;
|
|
171
|
-
}
|
package/test/operations.test.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { classifyOperation, parseTradeValues } from "../src/operations.ts";
|
|
4
|
-
|
|
5
|
-
test("classifyOperation maps supported operation groups", () => {
|
|
6
|
-
assert.equal(classifyOperation("Deposit"), "transaction");
|
|
7
|
-
assert.equal(classifyOperation("Stock purchase"), "investment");
|
|
8
|
-
assert.equal(classifyOperation("Total"), "ignored");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("parseTradeValues parses BUY format with split quantity", () => {
|
|
12
|
-
const parsed = parseTradeValues("OPEN BUY 7/7.7575 @ 42.750");
|
|
13
|
-
assert.equal(parsed.qty, "7");
|
|
14
|
-
assert.equal(parsed.price, "42.75");
|
|
15
|
-
assert.equal(parsed.qtyHadExplicitDecimal, false);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("parseTradeValues parses SELL format with decimal-preserving integer price", () => {
|
|
19
|
-
const parsed = parseTradeValues("CLOSE SELL 0.9179 @ 1233.0");
|
|
20
|
-
assert.equal(parsed.qty, "0.9179");
|
|
21
|
-
assert.equal(parsed.price, "1233.0");
|
|
22
|
-
assert.equal(parsed.qtyHadExplicitDecimal, true);
|
|
23
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"skipLibCheck": true,
|
|
4
|
-
"target": "esnext",
|
|
5
|
-
"resolveJsonModule": true,
|
|
6
|
-
"verbatimModuleSyntax": true,
|
|
7
|
-
"rootDir": ".",
|
|
8
|
-
"allowImportingTsExtensions": true,
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"module": "NodeNext",
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"strict": true,
|
|
13
|
-
"noUncheckedIndexedAccess": true,
|
|
14
|
-
"noImplicitOverride": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"allowJs": false,
|
|
17
|
-
"baseUrl": "./",
|
|
18
|
-
"paths": {
|
|
19
|
-
"#*": ["./src/*"]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"include": ["**/*"],
|
|
23
|
-
"exclude": ["node_modules", "dist"]
|
|
24
|
-
}
|