@fuzzle/opencode-accountant 0.4.0-next.1 → 0.4.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/accountant.md +61 -1
- package/dist/index.js +344 -54
- package/package.json +1 -1
package/agent/accountant.md
CHANGED
|
@@ -97,11 +97,71 @@ The `import-pipeline` tool provides an **atomic, safe workflow** using git workt
|
|
|
97
97
|
- Deletes processed CSV files from main repo's import/incoming
|
|
98
98
|
- Cleans up the worktree
|
|
99
99
|
4. **Handle Failures**: If any step fails (e.g., unknown postings found):
|
|
100
|
-
- Worktree is
|
|
100
|
+
- Worktree is preserved by default at `/tmp/import-worktree-<uuid>` for debugging
|
|
101
|
+
- Main branch remains untouched
|
|
101
102
|
- Review error output for unknown postings with full CSV row data
|
|
102
103
|
- Update rules file with `if` directives to match the transaction
|
|
103
104
|
- Re-run `import-pipeline`
|
|
104
105
|
|
|
106
|
+
### Error Recovery and Worktree Preservation
|
|
107
|
+
|
|
108
|
+
**Default Behavior:**
|
|
109
|
+
|
|
110
|
+
- On success: Worktrees are automatically cleaned up
|
|
111
|
+
- On error: Worktrees are preserved in `/tmp/import-worktree-<uuid>` for debugging
|
|
112
|
+
- Worktrees in `/tmp` are automatically cleaned up on system reboot
|
|
113
|
+
|
|
114
|
+
**Manual Recovery from Failed Import:**
|
|
115
|
+
|
|
116
|
+
If an import fails and the worktree is preserved, you can:
|
|
117
|
+
|
|
118
|
+
1. **Inspect the worktree:**
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cd /tmp/import-worktree-<uuid>
|
|
122
|
+
hledger check # Validate journal
|
|
123
|
+
hledger balance # Check balances
|
|
124
|
+
cat ledger/2026.journal # View imported transactions
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
2. **Continue the import manually:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd /tmp/import-worktree-<uuid>
|
|
131
|
+
# Fix any issues (edit rules, fix transactions, etc.)
|
|
132
|
+
git add .
|
|
133
|
+
git commit -m "Fix import issues"
|
|
134
|
+
git checkout main
|
|
135
|
+
git merge --no-ff import-<uuid>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
3. **Clean up when done:**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git worktree remove /tmp/import-worktree-<uuid>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
4. **Or use the cleanup tool:**
|
|
145
|
+
```bash
|
|
146
|
+
cleanup-worktrees # Removes worktrees >24h old
|
|
147
|
+
cleanup-worktrees --all true # Removes all import worktrees
|
|
148
|
+
cleanup-worktrees --dryRun true # Preview without removing
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Logs:**
|
|
152
|
+
|
|
153
|
+
- Every import run generates a detailed log: `.memory/import-<timestamp>.md`
|
|
154
|
+
- Log includes all commands, output, timing, and errors
|
|
155
|
+
- Log path is included in import-pipeline output
|
|
156
|
+
- Review the log to understand what failed and why
|
|
157
|
+
|
|
158
|
+
**Force Cleanup on Error:**
|
|
159
|
+
If you prefer the old behavior (always cleanup, even on error):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
import-pipeline --keepWorktreeOnError false
|
|
163
|
+
```
|
|
164
|
+
|
|
105
165
|
### Rules Files
|
|
106
166
|
|
|
107
167
|
- The location of the rules files is configured in `config/import/providers.yaml`
|
package/dist/index.js
CHANGED
|
@@ -17509,7 +17509,8 @@ function validatePaths(paths) {
|
|
|
17509
17509
|
pending: pathsObj.pending,
|
|
17510
17510
|
done: pathsObj.done,
|
|
17511
17511
|
unrecognized: pathsObj.unrecognized,
|
|
17512
|
-
rules: pathsObj.rules
|
|
17512
|
+
rules: pathsObj.rules,
|
|
17513
|
+
logs: pathsObj.logs
|
|
17513
17514
|
};
|
|
17514
17515
|
}
|
|
17515
17516
|
function validateDetectionRule(providerName, index, rule) {
|
|
@@ -17639,6 +17640,9 @@ function loadImportConfig(directory) {
|
|
|
17639
17640
|
for (const [name, config2] of Object.entries(providersObj)) {
|
|
17640
17641
|
providers[name] = validateProviderConfig(name, config2);
|
|
17641
17642
|
}
|
|
17643
|
+
if (!paths.logs) {
|
|
17644
|
+
paths.logs = ".memory";
|
|
17645
|
+
}
|
|
17642
17646
|
return { paths, providers };
|
|
17643
17647
|
}
|
|
17644
17648
|
|
|
@@ -17907,15 +17911,35 @@ function isInWorktree(directory) {
|
|
|
17907
17911
|
return false;
|
|
17908
17912
|
}
|
|
17909
17913
|
}
|
|
17910
|
-
async function withWorktree(directory, operation) {
|
|
17914
|
+
async function withWorktree(directory, operation, options) {
|
|
17911
17915
|
let createdWorktree = null;
|
|
17916
|
+
let operationSucceeded = false;
|
|
17917
|
+
const logger = options?.logger;
|
|
17918
|
+
const keepOnError = options?.keepOnError ?? true;
|
|
17912
17919
|
try {
|
|
17920
|
+
logger?.logStep("Create Worktree", "start");
|
|
17913
17921
|
createdWorktree = createImportWorktree(directory);
|
|
17922
|
+
logger?.logStep("Create Worktree", "success", `Path: ${createdWorktree.path}`);
|
|
17923
|
+
logger?.info(`Branch: ${createdWorktree.branch}`);
|
|
17924
|
+
logger?.info(`UUID: ${createdWorktree.uuid}`);
|
|
17914
17925
|
const result = await operation(createdWorktree);
|
|
17926
|
+
operationSucceeded = true;
|
|
17915
17927
|
return result;
|
|
17916
17928
|
} finally {
|
|
17917
17929
|
if (createdWorktree) {
|
|
17918
|
-
|
|
17930
|
+
if (operationSucceeded) {
|
|
17931
|
+
logger?.logStep("Cleanup Worktree", "start");
|
|
17932
|
+
removeWorktree(createdWorktree, true);
|
|
17933
|
+
logger?.logStep("Cleanup Worktree", "success", "Worktree removed");
|
|
17934
|
+
} else if (!keepOnError) {
|
|
17935
|
+
logger?.warn("Operation failed, but keepOnError=false, removing worktree");
|
|
17936
|
+
removeWorktree(createdWorktree, true);
|
|
17937
|
+
} else {
|
|
17938
|
+
logger?.warn("Operation failed, worktree preserved for debugging");
|
|
17939
|
+
logger?.info(`Worktree path: ${createdWorktree.path}`);
|
|
17940
|
+
logger?.info(`To clean up manually: git worktree remove ${createdWorktree.path}`);
|
|
17941
|
+
logger?.info(`To list all worktrees: git worktree list`);
|
|
17942
|
+
}
|
|
17919
17943
|
}
|
|
17920
17944
|
}
|
|
17921
17945
|
}
|
|
@@ -24028,7 +24052,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
|
|
|
24028
24052
|
transactionYear
|
|
24029
24053
|
};
|
|
24030
24054
|
}
|
|
24031
|
-
async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree) {
|
|
24055
|
+
async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree, _logger) {
|
|
24032
24056
|
const restrictionError = checkAccountantAgent(agent, "import statements");
|
|
24033
24057
|
if (restrictionError) {
|
|
24034
24058
|
return restrictionError;
|
|
@@ -24529,8 +24553,8 @@ It must be run inside an import worktree (use import-pipeline for the full workf
|
|
|
24529
24553
|
}
|
|
24530
24554
|
});
|
|
24531
24555
|
// src/tools/import-pipeline.ts
|
|
24532
|
-
import * as
|
|
24533
|
-
import * as
|
|
24556
|
+
import * as fs14 from "fs";
|
|
24557
|
+
import * as path13 from "path";
|
|
24534
24558
|
|
|
24535
24559
|
// src/utils/accountDeclarations.ts
|
|
24536
24560
|
import * as fs12 from "fs";
|
|
@@ -24647,6 +24671,158 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
|
24647
24671
|
};
|
|
24648
24672
|
}
|
|
24649
24673
|
|
|
24674
|
+
// src/utils/logger.ts
|
|
24675
|
+
import fs13 from "fs/promises";
|
|
24676
|
+
import path12 from "path";
|
|
24677
|
+
|
|
24678
|
+
class MarkdownLogger {
|
|
24679
|
+
buffer = [];
|
|
24680
|
+
logPath;
|
|
24681
|
+
context = {};
|
|
24682
|
+
autoFlush;
|
|
24683
|
+
sectionDepth = 0;
|
|
24684
|
+
constructor(config2) {
|
|
24685
|
+
this.autoFlush = config2.autoFlush ?? true;
|
|
24686
|
+
this.context = config2.context || {};
|
|
24687
|
+
const filename = config2.filename || `import-${this.getTimestamp()}.md`;
|
|
24688
|
+
this.logPath = path12.join(config2.logDir, filename);
|
|
24689
|
+
this.buffer.push(`# Import Pipeline Log`);
|
|
24690
|
+
this.buffer.push(`**Started**: ${new Date().toLocaleString()}`);
|
|
24691
|
+
this.buffer.push("");
|
|
24692
|
+
}
|
|
24693
|
+
startSection(title, level = 2) {
|
|
24694
|
+
this.buffer.push("");
|
|
24695
|
+
this.buffer.push(`${"#".repeat(level + 1)} ${title}`);
|
|
24696
|
+
this.buffer.push(`**Started**: ${this.getTime()}`);
|
|
24697
|
+
this.buffer.push("");
|
|
24698
|
+
this.sectionDepth++;
|
|
24699
|
+
}
|
|
24700
|
+
endSection() {
|
|
24701
|
+
if (this.sectionDepth > 0) {
|
|
24702
|
+
this.buffer.push("");
|
|
24703
|
+
this.buffer.push("---");
|
|
24704
|
+
this.buffer.push("");
|
|
24705
|
+
this.sectionDepth--;
|
|
24706
|
+
}
|
|
24707
|
+
}
|
|
24708
|
+
info(message) {
|
|
24709
|
+
this.buffer.push(message);
|
|
24710
|
+
if (this.autoFlush)
|
|
24711
|
+
this.flushAsync();
|
|
24712
|
+
}
|
|
24713
|
+
warn(message) {
|
|
24714
|
+
this.buffer.push(`\u26A0\uFE0F **WARNING**: ${message}`);
|
|
24715
|
+
if (this.autoFlush)
|
|
24716
|
+
this.flushAsync();
|
|
24717
|
+
}
|
|
24718
|
+
error(message, error45) {
|
|
24719
|
+
this.buffer.push(`\u274C **ERROR**: ${message}`);
|
|
24720
|
+
if (error45) {
|
|
24721
|
+
const errorStr = error45 instanceof Error ? error45.message : String(error45);
|
|
24722
|
+
this.buffer.push("");
|
|
24723
|
+
this.buffer.push("```");
|
|
24724
|
+
this.buffer.push(errorStr);
|
|
24725
|
+
if (error45 instanceof Error && error45.stack) {
|
|
24726
|
+
this.buffer.push("");
|
|
24727
|
+
this.buffer.push(error45.stack);
|
|
24728
|
+
}
|
|
24729
|
+
this.buffer.push("```");
|
|
24730
|
+
this.buffer.push("");
|
|
24731
|
+
}
|
|
24732
|
+
if (this.autoFlush)
|
|
24733
|
+
this.flushAsync();
|
|
24734
|
+
}
|
|
24735
|
+
debug(message) {
|
|
24736
|
+
this.buffer.push(`\uD83D\uDD0D ${message}`);
|
|
24737
|
+
if (this.autoFlush)
|
|
24738
|
+
this.flushAsync();
|
|
24739
|
+
}
|
|
24740
|
+
logStep(stepName, status, details) {
|
|
24741
|
+
const icon = status === "success" ? "\u2705" : status === "error" ? "\u274C" : "\u25B6\uFE0F";
|
|
24742
|
+
const statusText = status.charAt(0).toUpperCase() + status.slice(1);
|
|
24743
|
+
this.buffer.push(`**${stepName}**: ${icon} ${statusText}`);
|
|
24744
|
+
if (details) {
|
|
24745
|
+
this.buffer.push(` ${details}`);
|
|
24746
|
+
}
|
|
24747
|
+
this.buffer.push("");
|
|
24748
|
+
if (this.autoFlush)
|
|
24749
|
+
this.flushAsync();
|
|
24750
|
+
}
|
|
24751
|
+
logCommand(command, output) {
|
|
24752
|
+
this.buffer.push("```bash");
|
|
24753
|
+
this.buffer.push(`$ ${command}`);
|
|
24754
|
+
if (output) {
|
|
24755
|
+
this.buffer.push("");
|
|
24756
|
+
const lines = output.trim().split(`
|
|
24757
|
+
`);
|
|
24758
|
+
if (lines.length > 50) {
|
|
24759
|
+
this.buffer.push(...lines.slice(0, 50));
|
|
24760
|
+
this.buffer.push(`... (${lines.length - 50} more lines omitted)`);
|
|
24761
|
+
} else {
|
|
24762
|
+
this.buffer.push(output.trim());
|
|
24763
|
+
}
|
|
24764
|
+
}
|
|
24765
|
+
this.buffer.push("```");
|
|
24766
|
+
this.buffer.push("");
|
|
24767
|
+
if (this.autoFlush)
|
|
24768
|
+
this.flushAsync();
|
|
24769
|
+
}
|
|
24770
|
+
logResult(data) {
|
|
24771
|
+
this.buffer.push("```json");
|
|
24772
|
+
this.buffer.push(JSON.stringify(data, null, 2));
|
|
24773
|
+
this.buffer.push("```");
|
|
24774
|
+
this.buffer.push("");
|
|
24775
|
+
if (this.autoFlush)
|
|
24776
|
+
this.flushAsync();
|
|
24777
|
+
}
|
|
24778
|
+
setContext(key, value) {
|
|
24779
|
+
this.context[key] = value;
|
|
24780
|
+
}
|
|
24781
|
+
async flush() {
|
|
24782
|
+
if (this.buffer.length === 0)
|
|
24783
|
+
return;
|
|
24784
|
+
try {
|
|
24785
|
+
await fs13.mkdir(path12.dirname(this.logPath), { recursive: true });
|
|
24786
|
+
await fs13.writeFile(this.logPath, this.buffer.join(`
|
|
24787
|
+
`), "utf-8");
|
|
24788
|
+
} catch {}
|
|
24789
|
+
}
|
|
24790
|
+
getLogPath() {
|
|
24791
|
+
return this.logPath;
|
|
24792
|
+
}
|
|
24793
|
+
flushAsync() {
|
|
24794
|
+
this.flush().catch(() => {});
|
|
24795
|
+
}
|
|
24796
|
+
getTimestamp() {
|
|
24797
|
+
return new Date().toISOString().replace(/:/g, "-").split(".")[0];
|
|
24798
|
+
}
|
|
24799
|
+
getTime() {
|
|
24800
|
+
return new Date().toLocaleTimeString();
|
|
24801
|
+
}
|
|
24802
|
+
}
|
|
24803
|
+
function createLogger(config2) {
|
|
24804
|
+
return new MarkdownLogger(config2);
|
|
24805
|
+
}
|
|
24806
|
+
function createImportLogger(directory, worktreeId, provider) {
|
|
24807
|
+
const context = {};
|
|
24808
|
+
if (worktreeId)
|
|
24809
|
+
context.worktreeId = worktreeId;
|
|
24810
|
+
if (provider)
|
|
24811
|
+
context.provider = provider;
|
|
24812
|
+
const logger = createLogger({
|
|
24813
|
+
logDir: path12.join(directory, ".memory"),
|
|
24814
|
+
autoFlush: true,
|
|
24815
|
+
context
|
|
24816
|
+
});
|
|
24817
|
+
if (worktreeId)
|
|
24818
|
+
logger.info(`**Worktree ID**: ${worktreeId}`);
|
|
24819
|
+
if (provider)
|
|
24820
|
+
logger.info(`**Provider**: ${provider}`);
|
|
24821
|
+
logger.info(`**Repository**: ${directory}`);
|
|
24822
|
+
logger.info("");
|
|
24823
|
+
return logger;
|
|
24824
|
+
}
|
|
24825
|
+
|
|
24650
24826
|
// src/tools/import-pipeline.ts
|
|
24651
24827
|
class NoTransactionsError extends Error {
|
|
24652
24828
|
constructor() {
|
|
@@ -24686,8 +24862,8 @@ function buildCommitMessage(provider, currency, fromDate, untilDate, transaction
|
|
|
24686
24862
|
return `${parts.join(" ")}${dateRange}${txStr}`;
|
|
24687
24863
|
}
|
|
24688
24864
|
function cleanupIncomingFiles(worktree, context) {
|
|
24689
|
-
const incomingDir =
|
|
24690
|
-
if (!
|
|
24865
|
+
const incomingDir = path13.join(worktree.mainRepoPath, "import/incoming");
|
|
24866
|
+
if (!fs14.existsSync(incomingDir)) {
|
|
24691
24867
|
return;
|
|
24692
24868
|
}
|
|
24693
24869
|
const importStep = context.result.steps.import;
|
|
@@ -24702,11 +24878,11 @@ function cleanupIncomingFiles(worktree, context) {
|
|
|
24702
24878
|
for (const fileResult of importResult.files) {
|
|
24703
24879
|
if (!fileResult.csv)
|
|
24704
24880
|
continue;
|
|
24705
|
-
const filename =
|
|
24706
|
-
const filePath =
|
|
24707
|
-
if (
|
|
24881
|
+
const filename = path13.basename(fileResult.csv);
|
|
24882
|
+
const filePath = path13.join(incomingDir, filename);
|
|
24883
|
+
if (fs14.existsSync(filePath)) {
|
|
24708
24884
|
try {
|
|
24709
|
-
|
|
24885
|
+
fs14.unlinkSync(filePath);
|
|
24710
24886
|
deletedCount++;
|
|
24711
24887
|
} catch (error45) {
|
|
24712
24888
|
console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
@@ -24717,9 +24893,13 @@ function cleanupIncomingFiles(worktree, context) {
|
|
|
24717
24893
|
console.log(`[INFO] Cleaned up ${deletedCount} file(s) from import/incoming/`);
|
|
24718
24894
|
}
|
|
24719
24895
|
}
|
|
24720
|
-
async function executeClassifyStep(context, worktree) {
|
|
24896
|
+
async function executeClassifyStep(context, worktree, logger) {
|
|
24897
|
+
logger?.startSection("Step 2: Classify Transactions");
|
|
24898
|
+
logger?.logStep("Classify", "start");
|
|
24721
24899
|
if (context.options.skipClassify) {
|
|
24900
|
+
logger?.info("Classification skipped (skipClassify: true)");
|
|
24722
24901
|
context.result.steps.classify = buildStepResult(true, "Classification skipped (skipClassify: true)");
|
|
24902
|
+
logger?.endSection();
|
|
24723
24903
|
return;
|
|
24724
24904
|
}
|
|
24725
24905
|
const inWorktree = () => true;
|
|
@@ -24729,18 +24909,23 @@ async function executeClassifyStep(context, worktree) {
|
|
|
24729
24909
|
let message = success2 ? "Classification complete" : "Classification had issues";
|
|
24730
24910
|
if (classifyParsed.unrecognized?.length > 0) {
|
|
24731
24911
|
message = `Classification complete with ${classifyParsed.unrecognized.length} unrecognized file(s)`;
|
|
24912
|
+
logger?.warn(`${classifyParsed.unrecognized.length} unrecognized file(s)`);
|
|
24732
24913
|
}
|
|
24914
|
+
logger?.logStep("Classify", success2 ? "success" : "error", message);
|
|
24733
24915
|
const details = {
|
|
24734
24916
|
success: success2,
|
|
24735
24917
|
unrecognized: classifyParsed.unrecognized,
|
|
24736
24918
|
classified: classifyParsed
|
|
24737
24919
|
};
|
|
24738
24920
|
context.result.steps.classify = buildStepResult(success2, message, details);
|
|
24921
|
+
logger?.endSection();
|
|
24739
24922
|
}
|
|
24740
|
-
async function executeAccountDeclarationsStep(context, worktree) {
|
|
24923
|
+
async function executeAccountDeclarationsStep(context, worktree, logger) {
|
|
24924
|
+
logger?.startSection("Step 3: Check Account Declarations");
|
|
24925
|
+
logger?.logStep("Check Accounts", "start");
|
|
24741
24926
|
const config2 = context.configLoader(worktree.path);
|
|
24742
|
-
const pendingDir =
|
|
24743
|
-
const rulesDir =
|
|
24927
|
+
const pendingDir = path13.join(worktree.path, config2.paths.pending);
|
|
24928
|
+
const rulesDir = path13.join(worktree.path, config2.paths.rules);
|
|
24744
24929
|
const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
|
|
24745
24930
|
if (csvFiles.length === 0) {
|
|
24746
24931
|
context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
|
|
@@ -24771,7 +24956,7 @@ async function executeAccountDeclarationsStep(context, worktree) {
|
|
|
24771
24956
|
context.result.steps.accountDeclarations = buildStepResult(true, "No accounts found in rules files", {
|
|
24772
24957
|
accountsAdded: [],
|
|
24773
24958
|
journalUpdated: "",
|
|
24774
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24959
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
|
|
24775
24960
|
});
|
|
24776
24961
|
return;
|
|
24777
24962
|
}
|
|
@@ -24794,7 +24979,7 @@ async function executeAccountDeclarationsStep(context, worktree) {
|
|
|
24794
24979
|
context.result.steps.accountDeclarations = buildStepResult(false, "Could not determine transaction year from CSV files", {
|
|
24795
24980
|
accountsAdded: [],
|
|
24796
24981
|
journalUpdated: "",
|
|
24797
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24982
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
|
|
24798
24983
|
});
|
|
24799
24984
|
return;
|
|
24800
24985
|
}
|
|
@@ -24805,19 +24990,28 @@ async function executeAccountDeclarationsStep(context, worktree) {
|
|
|
24805
24990
|
context.result.steps.accountDeclarations = buildStepResult(false, `Failed to create year journal: ${error45 instanceof Error ? error45.message : String(error45)}`, {
|
|
24806
24991
|
accountsAdded: [],
|
|
24807
24992
|
journalUpdated: "",
|
|
24808
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
24993
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
|
|
24809
24994
|
});
|
|
24810
24995
|
return;
|
|
24811
24996
|
}
|
|
24812
24997
|
const result = ensureAccountDeclarations(yearJournalPath, allAccounts);
|
|
24813
|
-
const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${
|
|
24998
|
+
const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path13.relative(worktree.path, yearJournalPath)}` : "All required accounts already declared";
|
|
24999
|
+
logger?.logStep("Check Accounts", "success", message);
|
|
25000
|
+
if (result.added.length > 0) {
|
|
25001
|
+
for (const account of result.added) {
|
|
25002
|
+
logger?.info(` - ${account}`);
|
|
25003
|
+
}
|
|
25004
|
+
}
|
|
24814
25005
|
context.result.steps.accountDeclarations = buildStepResult(true, message, {
|
|
24815
25006
|
accountsAdded: result.added,
|
|
24816
|
-
journalUpdated:
|
|
24817
|
-
rulesScanned: Array.from(matchedRulesFiles).map((f) =>
|
|
25007
|
+
journalUpdated: path13.relative(worktree.path, yearJournalPath),
|
|
25008
|
+
rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
|
|
24818
25009
|
});
|
|
25010
|
+
logger?.endSection();
|
|
24819
25011
|
}
|
|
24820
|
-
async function executeDryRunStep(context, worktree) {
|
|
25012
|
+
async function executeDryRunStep(context, worktree, logger) {
|
|
25013
|
+
logger?.startSection("Step 4: Dry Run Import");
|
|
25014
|
+
logger?.logStep("Dry Run", "start");
|
|
24821
25015
|
const inWorktree = () => true;
|
|
24822
25016
|
const dryRunResult = await importStatements(worktree.path, context.agent, {
|
|
24823
25017
|
provider: context.options.provider,
|
|
@@ -24826,20 +25020,30 @@ async function executeDryRunStep(context, worktree) {
|
|
|
24826
25020
|
}, context.configLoader, context.hledgerExecutor, inWorktree);
|
|
24827
25021
|
const dryRunParsed = JSON.parse(dryRunResult);
|
|
24828
25022
|
const message = dryRunParsed.success ? `Dry run passed: ${dryRunParsed.summary?.totalTransactions || 0} transactions ready` : `Dry run failed: ${dryRunParsed.summary?.unknown || 0} unknown account(s)`;
|
|
25023
|
+
logger?.logStep("Dry Run", dryRunParsed.success ? "success" : "error", message);
|
|
25024
|
+
if (dryRunParsed.summary?.totalTransactions) {
|
|
25025
|
+
logger?.info(`Found ${dryRunParsed.summary.totalTransactions} transactions`);
|
|
25026
|
+
}
|
|
24829
25027
|
context.result.steps.dryRun = buildStepResult(dryRunParsed.success, message, {
|
|
24830
25028
|
success: dryRunParsed.success,
|
|
24831
25029
|
summary: dryRunParsed.summary
|
|
24832
25030
|
});
|
|
24833
25031
|
if (!dryRunParsed.success) {
|
|
25032
|
+
logger?.error("Dry run found unknown accounts or errors");
|
|
25033
|
+
logger?.endSection();
|
|
24834
25034
|
context.result.error = "Dry run found unknown accounts or errors";
|
|
24835
25035
|
context.result.hint = "Add rules to categorize unknown transactions, then retry";
|
|
24836
25036
|
throw new Error("Dry run failed");
|
|
24837
25037
|
}
|
|
24838
25038
|
if (dryRunParsed.summary?.totalTransactions === 0) {
|
|
25039
|
+
logger?.endSection();
|
|
24839
25040
|
throw new NoTransactionsError;
|
|
24840
25041
|
}
|
|
25042
|
+
logger?.endSection();
|
|
24841
25043
|
}
|
|
24842
|
-
async function executeImportStep(context, worktree) {
|
|
25044
|
+
async function executeImportStep(context, worktree, logger) {
|
|
25045
|
+
logger?.startSection("Step 5: Import Transactions");
|
|
25046
|
+
logger?.logStep("Import", "start");
|
|
24843
25047
|
const inWorktree = () => true;
|
|
24844
25048
|
const importResult = await importStatements(worktree.path, context.agent, {
|
|
24845
25049
|
provider: context.options.provider,
|
|
@@ -24848,17 +25052,23 @@ async function executeImportStep(context, worktree) {
|
|
|
24848
25052
|
}, context.configLoader, context.hledgerExecutor, inWorktree);
|
|
24849
25053
|
const importParsed = JSON.parse(importResult);
|
|
24850
25054
|
const message = importParsed.success ? `Imported ${importParsed.summary?.totalTransactions || 0} transactions` : `Import failed: ${importParsed.error || "Unknown error"}`;
|
|
25055
|
+
logger?.logStep("Import", importParsed.success ? "success" : "error", message);
|
|
24851
25056
|
context.result.steps.import = buildStepResult(importParsed.success, message, {
|
|
24852
25057
|
success: importParsed.success,
|
|
24853
25058
|
summary: importParsed.summary,
|
|
24854
25059
|
error: importParsed.error
|
|
24855
25060
|
});
|
|
24856
25061
|
if (!importParsed.success) {
|
|
25062
|
+
logger?.error("Import failed", new Error(importParsed.error || "Unknown error"));
|
|
25063
|
+
logger?.endSection();
|
|
24857
25064
|
context.result.error = `Import failed: ${importParsed.error || "Unknown error"}`;
|
|
24858
25065
|
throw new Error("Import failed");
|
|
24859
25066
|
}
|
|
25067
|
+
logger?.endSection();
|
|
24860
25068
|
}
|
|
24861
|
-
async function executeReconcileStep(context, worktree) {
|
|
25069
|
+
async function executeReconcileStep(context, worktree, logger) {
|
|
25070
|
+
logger?.startSection("Step 6: Reconcile Balance");
|
|
25071
|
+
logger?.logStep("Reconcile", "start");
|
|
24862
25072
|
const inWorktree = () => true;
|
|
24863
25073
|
const reconcileResult = await reconcileStatement(worktree.path, context.agent, {
|
|
24864
25074
|
provider: context.options.provider,
|
|
@@ -24868,6 +25078,11 @@ async function executeReconcileStep(context, worktree) {
|
|
|
24868
25078
|
}, context.configLoader, context.hledgerExecutor, inWorktree);
|
|
24869
25079
|
const reconcileParsed = JSON.parse(reconcileResult);
|
|
24870
25080
|
const message = reconcileParsed.success ? `Balance reconciled: ${reconcileParsed.actualBalance}` : `Balance mismatch: expected ${reconcileParsed.expectedBalance}, got ${reconcileParsed.actualBalance}`;
|
|
25081
|
+
logger?.logStep("Reconcile", reconcileParsed.success ? "success" : "error", message);
|
|
25082
|
+
if (reconcileParsed.success) {
|
|
25083
|
+
logger?.info(`Actual: ${reconcileParsed.actualBalance}`);
|
|
25084
|
+
logger?.info(`Expected: ${reconcileParsed.expectedBalance}`);
|
|
25085
|
+
}
|
|
24871
25086
|
context.result.steps.reconcile = buildStepResult(reconcileParsed.success, message, {
|
|
24872
25087
|
success: reconcileParsed.success,
|
|
24873
25088
|
actualBalance: reconcileParsed.actualBalance,
|
|
@@ -24876,12 +25091,17 @@ async function executeReconcileStep(context, worktree) {
|
|
|
24876
25091
|
error: reconcileParsed.error
|
|
24877
25092
|
});
|
|
24878
25093
|
if (!reconcileParsed.success) {
|
|
25094
|
+
logger?.error("Reconciliation failed", new Error(reconcileParsed.error || "Balance mismatch"));
|
|
25095
|
+
logger?.endSection();
|
|
24879
25096
|
context.result.error = `Reconciliation failed: ${reconcileParsed.error || "Balance mismatch"}`;
|
|
24880
25097
|
context.result.hint = "Check for missing transactions or incorrect rules";
|
|
24881
25098
|
throw new Error("Reconciliation failed");
|
|
24882
25099
|
}
|
|
25100
|
+
logger?.endSection();
|
|
24883
25101
|
}
|
|
24884
|
-
async function executeMergeStep(context, worktree) {
|
|
25102
|
+
async function executeMergeStep(context, worktree, logger) {
|
|
25103
|
+
logger?.startSection("Step 7: Merge to Main");
|
|
25104
|
+
logger?.logStep("Merge", "start");
|
|
24885
25105
|
const importDetails = context.result.steps.import?.details;
|
|
24886
25106
|
const reconcileDetails = context.result.steps.reconcile?.details;
|
|
24887
25107
|
if (!importDetails || !reconcileDetails) {
|
|
@@ -24894,11 +25114,17 @@ async function executeMergeStep(context, worktree) {
|
|
|
24894
25114
|
const transactionCount = importDetails.summary?.totalTransactions || 0;
|
|
24895
25115
|
const commitMessage = buildCommitMessage(context.options.provider, context.options.currency, commitInfo.fromDate, commitInfo.untilDate, transactionCount);
|
|
24896
25116
|
try {
|
|
25117
|
+
logger?.info(`Commit message: "${commitMessage}"`);
|
|
24897
25118
|
mergeWorktree(worktree, commitMessage);
|
|
25119
|
+
logger?.logStep("Merge", "success", "Merged to main branch");
|
|
24898
25120
|
const mergeDetails = { commitMessage };
|
|
24899
25121
|
context.result.steps.merge = buildStepResult(true, `Merged to main: "${commitMessage}"`, mergeDetails);
|
|
24900
25122
|
cleanupIncomingFiles(worktree, context);
|
|
25123
|
+
logger?.endSection();
|
|
24901
25124
|
} catch (error45) {
|
|
25125
|
+
logger?.logStep("Merge", "error");
|
|
25126
|
+
logger?.error("Merge to main branch failed", error45);
|
|
25127
|
+
logger?.endSection();
|
|
24902
25128
|
const message = `Merge failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
24903
25129
|
context.result.steps.merge = buildStepResult(false, message);
|
|
24904
25130
|
context.result.error = "Merge to main branch failed";
|
|
@@ -24916,6 +25142,13 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24916
25142
|
if (restrictionError) {
|
|
24917
25143
|
return restrictionError;
|
|
24918
25144
|
}
|
|
25145
|
+
const logger = createImportLogger(directory, undefined, options.provider);
|
|
25146
|
+
logger.startSection("Import Pipeline", 1);
|
|
25147
|
+
logger.info(`Provider filter: ${options.provider || "all"}`);
|
|
25148
|
+
logger.info(`Currency filter: ${options.currency || "all"}`);
|
|
25149
|
+
logger.info(`Skip classify: ${options.skipClassify || false}`);
|
|
25150
|
+
logger.info(`Keep worktree on error: ${options.keepWorktreeOnError ?? true}`);
|
|
25151
|
+
logger.info("");
|
|
24919
25152
|
const result = {
|
|
24920
25153
|
success: false,
|
|
24921
25154
|
steps: {}
|
|
@@ -24930,33 +25163,47 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24930
25163
|
};
|
|
24931
25164
|
try {
|
|
24932
25165
|
return await withWorktree(directory, async (worktree) => {
|
|
25166
|
+
logger.setContext("worktreeId", worktree.uuid);
|
|
25167
|
+
logger.setContext("worktreePath", worktree.path);
|
|
24933
25168
|
result.worktreeId = worktree.uuid;
|
|
24934
25169
|
result.steps.worktree = buildStepResult(true, `Created worktree at ${worktree.path}`, {
|
|
24935
25170
|
path: worktree.path,
|
|
24936
25171
|
branch: worktree.branch
|
|
24937
25172
|
});
|
|
25173
|
+
logger.startSection("Step 1: Sync Files");
|
|
25174
|
+
logger.logStep("Sync Files", "start");
|
|
24938
25175
|
try {
|
|
24939
25176
|
const config2 = configLoader(directory);
|
|
24940
25177
|
const syncResult = syncCSVFilesToWorktree(directory, worktree.path, config2.paths.import);
|
|
24941
25178
|
if (syncResult.synced.length === 0 && syncResult.errors.length === 0) {
|
|
25179
|
+
logger.logStep("Sync Files", "success", "No CSV files to sync");
|
|
24942
25180
|
result.steps.sync = buildStepResult(true, "No CSV files to sync", {
|
|
24943
25181
|
synced: []
|
|
24944
25182
|
});
|
|
24945
25183
|
} else if (syncResult.errors.length > 0) {
|
|
25184
|
+
logger.warn(`Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`);
|
|
24946
25185
|
result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`, { synced: syncResult.synced, errors: syncResult.errors });
|
|
24947
25186
|
} else {
|
|
25187
|
+
logger.logStep("Sync Files", "success", `Synced ${syncResult.synced.length} CSV file(s)`);
|
|
25188
|
+
for (const file2 of syncResult.synced) {
|
|
25189
|
+
logger.info(` - ${file2}`);
|
|
25190
|
+
}
|
|
24948
25191
|
result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} CSV file(s) to worktree`, { synced: syncResult.synced });
|
|
24949
25192
|
}
|
|
25193
|
+
logger.endSection();
|
|
24950
25194
|
} catch (error45) {
|
|
25195
|
+
logger.logStep("Sync Files", "error");
|
|
25196
|
+
logger.error("Failed to sync CSV files", error45);
|
|
25197
|
+
logger.endSection();
|
|
24951
25198
|
const errorMsg = error45 instanceof Error ? error45.message : String(error45);
|
|
24952
25199
|
result.steps.sync = buildStepResult(false, `Failed to sync CSV files: ${errorMsg}`, { synced: [], errors: [{ file: "unknown", error: errorMsg }] });
|
|
24953
25200
|
}
|
|
24954
25201
|
try {
|
|
24955
|
-
await executeClassifyStep(context, worktree);
|
|
24956
|
-
await executeAccountDeclarationsStep(context, worktree);
|
|
24957
|
-
await executeDryRunStep(context, worktree);
|
|
24958
|
-
await executeImportStep(context, worktree);
|
|
24959
|
-
await executeReconcileStep(context, worktree);
|
|
25202
|
+
await executeClassifyStep(context, worktree, logger);
|
|
25203
|
+
await executeAccountDeclarationsStep(context, worktree, logger);
|
|
25204
|
+
await executeDryRunStep(context, worktree, logger);
|
|
25205
|
+
await executeImportStep(context, worktree, logger);
|
|
25206
|
+
await executeReconcileStep(context, worktree, logger);
|
|
24960
25207
|
try {
|
|
24961
25208
|
const config2 = configLoader(directory);
|
|
24962
25209
|
const cleanupResult = cleanupProcessedCSVFiles(directory, config2.paths.import);
|
|
@@ -24981,7 +25228,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24981
25228
|
}
|
|
24982
25229
|
});
|
|
24983
25230
|
}
|
|
24984
|
-
await executeMergeStep(context, worktree);
|
|
25231
|
+
await executeMergeStep(context, worktree, logger);
|
|
24985
25232
|
const existingCleanup = result.steps.cleanup;
|
|
24986
25233
|
if (existingCleanup) {
|
|
24987
25234
|
existingCleanup.message += ", worktree cleaned up";
|
|
@@ -24991,10 +25238,30 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
24991
25238
|
};
|
|
24992
25239
|
}
|
|
24993
25240
|
const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
|
|
25241
|
+
logger.startSection("Summary");
|
|
25242
|
+
logger.info(`\u2705 Import completed successfully`);
|
|
25243
|
+
logger.info(`Total transactions imported: ${transactionCount}`);
|
|
25244
|
+
if (context.result.steps.reconcile?.details?.actualBalance) {
|
|
25245
|
+
logger.info(`Balance reconciliation: \u2705 Matched (${context.result.steps.reconcile.details.actualBalance})`);
|
|
25246
|
+
}
|
|
25247
|
+
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
25248
|
+
logger.endSection();
|
|
24994
25249
|
return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
|
|
24995
25250
|
} catch (error45) {
|
|
24996
|
-
result.steps.
|
|
24997
|
-
|
|
25251
|
+
const worktreePath = context.result.steps.worktree?.details?.path;
|
|
25252
|
+
const keepWorktree = options.keepWorktreeOnError ?? true;
|
|
25253
|
+
logger.error("Pipeline step failed", error45);
|
|
25254
|
+
if (keepWorktree && worktreePath) {
|
|
25255
|
+
logger.warn(`Worktree preserved at: ${worktreePath}`);
|
|
25256
|
+
logger.info(`To continue manually: cd ${worktreePath}`);
|
|
25257
|
+
logger.info(`To clean up: git worktree remove ${worktreePath}`);
|
|
25258
|
+
}
|
|
25259
|
+
logger.info(`Log file: ${logger.getLogPath()}`);
|
|
25260
|
+
result.steps.cleanup = buildStepResult(true, keepWorktree ? `Worktree preserved for debugging (CSV files preserved for retry)` : "Worktree cleaned up after failure (CSV files preserved for retry)", {
|
|
25261
|
+
cleanedAfterFailure: !keepWorktree,
|
|
25262
|
+
worktreePreserved: keepWorktree,
|
|
25263
|
+
worktreePath,
|
|
25264
|
+
preserveReason: keepWorktree ? "error occurred" : undefined,
|
|
24998
25265
|
csvCleanup: { deleted: [] }
|
|
24999
25266
|
});
|
|
25000
25267
|
if (error45 instanceof NoTransactionsError) {
|
|
@@ -25005,11 +25272,18 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
|
|
|
25005
25272
|
}
|
|
25006
25273
|
return buildErrorResult5(result, result.error, result.hint);
|
|
25007
25274
|
}
|
|
25275
|
+
}, {
|
|
25276
|
+
keepOnError: options.keepWorktreeOnError ?? true,
|
|
25277
|
+
logger
|
|
25008
25278
|
});
|
|
25009
25279
|
} catch (error45) {
|
|
25280
|
+
logger.error("Pipeline failed", error45);
|
|
25010
25281
|
result.steps.worktree = buildStepResult(false, `Failed to create worktree: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
25011
25282
|
result.error = "Failed to create worktree";
|
|
25012
25283
|
return buildErrorResult5(result, result.error);
|
|
25284
|
+
} finally {
|
|
25285
|
+
logger.endSection();
|
|
25286
|
+
await logger.flush();
|
|
25013
25287
|
}
|
|
25014
25288
|
}
|
|
25015
25289
|
var import_pipeline_default = tool({
|
|
@@ -25024,25 +25298,40 @@ This tool orchestrates the full import workflow in an isolated git worktree:
|
|
|
25024
25298
|
4. **Import**: Imports transactions to the journal
|
|
25025
25299
|
5. **Reconcile**: Validates closing balance matches CSV metadata
|
|
25026
25300
|
6. **Merge**: Merges worktree to main with --no-ff
|
|
25027
|
-
7. **Cleanup**: Removes worktree
|
|
25301
|
+
7. **Cleanup**: Removes worktree (or preserves on error)
|
|
25028
25302
|
|
|
25029
25303
|
**Safety Features:**
|
|
25030
25304
|
- All changes happen in isolated worktree
|
|
25031
|
-
- If any step fails, worktree is
|
|
25305
|
+
- If any step fails, worktree is preserved by default for debugging
|
|
25032
25306
|
- Balance reconciliation ensures data integrity
|
|
25033
25307
|
- Atomic commit with merge --no-ff preserves history
|
|
25034
25308
|
|
|
25309
|
+
**Worktree Cleanup:**
|
|
25310
|
+
- On success: Worktree is always cleaned up
|
|
25311
|
+
- On error (default): Worktree is kept at /tmp/import-worktree-<uuid> for debugging
|
|
25312
|
+
- On error (--keepWorktreeOnError false): Worktree is removed (old behavior)
|
|
25313
|
+
- Manual cleanup: git worktree remove /tmp/import-worktree-<uuid>
|
|
25314
|
+
- Auto cleanup: System reboot (worktrees are in /tmp)
|
|
25315
|
+
|
|
25316
|
+
**Logging:**
|
|
25317
|
+
- All operations are logged to .memory/import-<timestamp>.md
|
|
25318
|
+
- Log includes full command output, timing, and error details
|
|
25319
|
+
- Log path is included in tool output for easy access
|
|
25320
|
+
- NO console output (avoids polluting OpenCode TUI)
|
|
25321
|
+
|
|
25035
25322
|
**Usage:**
|
|
25036
25323
|
- Basic: import-pipeline (processes all pending CSVs)
|
|
25037
25324
|
- Filtered: import-pipeline --provider ubs --currency chf
|
|
25038
25325
|
- With manual balance: import-pipeline --closingBalance "CHF 1234.56"
|
|
25039
|
-
- Skip classify: import-pipeline --skipClassify true
|
|
25326
|
+
- Skip classify: import-pipeline --skipClassify true
|
|
25327
|
+
- Always cleanup: import-pipeline --keepWorktreeOnError false`,
|
|
25040
25328
|
args: {
|
|
25041
25329
|
provider: tool.schema.string().optional().describe('Filter by provider (e.g., "ubs", "revolut")'),
|
|
25042
25330
|
currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur")'),
|
|
25043
25331
|
closingBalance: tool.schema.string().optional().describe("Manual closing balance override (if not in CSV metadata)"),
|
|
25044
25332
|
account: tool.schema.string().optional().describe("Manual account override (auto-detected from rules file if not provided)"),
|
|
25045
|
-
skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)")
|
|
25333
|
+
skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)"),
|
|
25334
|
+
keepWorktreeOnError: tool.schema.boolean().optional().describe("Keep worktree on error for debugging (default: true)")
|
|
25046
25335
|
},
|
|
25047
25336
|
async execute(params, context) {
|
|
25048
25337
|
const { directory, agent } = context;
|
|
@@ -25051,21 +25340,22 @@ This tool orchestrates the full import workflow in an isolated git worktree:
|
|
|
25051
25340
|
currency: params.currency,
|
|
25052
25341
|
closingBalance: params.closingBalance,
|
|
25053
25342
|
account: params.account,
|
|
25054
|
-
skipClassify: params.skipClassify
|
|
25343
|
+
skipClassify: params.skipClassify,
|
|
25344
|
+
keepWorktreeOnError: params.keepWorktreeOnError
|
|
25055
25345
|
});
|
|
25056
25346
|
}
|
|
25057
25347
|
});
|
|
25058
25348
|
// src/tools/init-directories.ts
|
|
25059
|
-
import * as
|
|
25060
|
-
import * as
|
|
25349
|
+
import * as fs15 from "fs";
|
|
25350
|
+
import * as path14 from "path";
|
|
25061
25351
|
async function initDirectories(directory) {
|
|
25062
25352
|
try {
|
|
25063
25353
|
const config2 = loadImportConfig(directory);
|
|
25064
25354
|
const directoriesCreated = [];
|
|
25065
25355
|
const gitkeepFiles = [];
|
|
25066
|
-
const importBase =
|
|
25067
|
-
if (!
|
|
25068
|
-
|
|
25356
|
+
const importBase = path14.join(directory, "import");
|
|
25357
|
+
if (!fs15.existsSync(importBase)) {
|
|
25358
|
+
fs15.mkdirSync(importBase, { recursive: true });
|
|
25069
25359
|
directoriesCreated.push("import");
|
|
25070
25360
|
}
|
|
25071
25361
|
const pathsToCreate = [
|
|
@@ -25075,20 +25365,20 @@ async function initDirectories(directory) {
|
|
|
25075
25365
|
{ key: "unrecognized", path: config2.paths.unrecognized }
|
|
25076
25366
|
];
|
|
25077
25367
|
for (const { path: dirPath } of pathsToCreate) {
|
|
25078
|
-
const fullPath =
|
|
25079
|
-
if (!
|
|
25080
|
-
|
|
25368
|
+
const fullPath = path14.join(directory, dirPath);
|
|
25369
|
+
if (!fs15.existsSync(fullPath)) {
|
|
25370
|
+
fs15.mkdirSync(fullPath, { recursive: true });
|
|
25081
25371
|
directoriesCreated.push(dirPath);
|
|
25082
25372
|
}
|
|
25083
|
-
const gitkeepPath =
|
|
25084
|
-
if (!
|
|
25085
|
-
|
|
25086
|
-
gitkeepFiles.push(
|
|
25373
|
+
const gitkeepPath = path14.join(fullPath, ".gitkeep");
|
|
25374
|
+
if (!fs15.existsSync(gitkeepPath)) {
|
|
25375
|
+
fs15.writeFileSync(gitkeepPath, "");
|
|
25376
|
+
gitkeepFiles.push(path14.join(dirPath, ".gitkeep"));
|
|
25087
25377
|
}
|
|
25088
25378
|
}
|
|
25089
|
-
const gitignorePath =
|
|
25379
|
+
const gitignorePath = path14.join(importBase, ".gitignore");
|
|
25090
25380
|
let gitignoreCreated = false;
|
|
25091
|
-
if (!
|
|
25381
|
+
if (!fs15.existsSync(gitignorePath)) {
|
|
25092
25382
|
const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
|
|
25093
25383
|
/incoming/*.csv
|
|
25094
25384
|
/incoming/*.pdf
|
|
@@ -25106,7 +25396,7 @@ async function initDirectories(directory) {
|
|
|
25106
25396
|
.DS_Store
|
|
25107
25397
|
Thumbs.db
|
|
25108
25398
|
`;
|
|
25109
|
-
|
|
25399
|
+
fs15.writeFileSync(gitignorePath, gitignoreContent);
|
|
25110
25400
|
gitignoreCreated = true;
|
|
25111
25401
|
}
|
|
25112
25402
|
const parts = [];
|
package/package.json
CHANGED