Skip to content

Commit a039024

Browse files
committed
[feat] Introduce post_processing encryption step option
Squashed commit of the following: commit 7368dea Merge: 33f9d29 b644cf3 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Jan 6 09:17:16 2026 -0600 Merge branch 'feat/postprocess-encrypt-file' of https://github.com/wfp/common-identifier-algorithm-shared into feat/postprocess-encrypt-file commit 33f9d29 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:32:22 2026 -0600 fix docs and examples commit d7eba6f Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Fri Jan 2 11:52:30 2026 -0600 working gpg implementation (needs more testing) check binary exists + more logging integrate gpg with postprocessing wrapper, standardise api bubble up error messages commit eca5e2a Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Fri Jan 2 12:54:44 2026 -0600 [feat] introduce prefixes for saved documents in config commit bc7855d Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:47:41 2026 -0600 jest -> vitest commit b644cf3 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Jan 6 08:52:09 2026 -0600 add junit test report commit 7499e41 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Jan 6 08:35:20 2026 -0600 coverage only commit 29dac49 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Jan 6 08:10:14 2026 -0600 cobertura coverage format commit 32d4b42 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Jan 6 08:07:57 2026 -0600 explicit gpgbinpath in tests commit 6591d5a Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 17:04:28 2026 -0600 back to linux commit ab4bc63 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 17:03:01 2026 -0600 mock isExecutable commit 4ae15bc Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:53:52 2026 -0600 [tmp] try windows runner commit 286f9a2 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:47:41 2026 -0600 jest -> vitest commit 453d36f Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:32:22 2026 -0600 fix docs and examples commit 85eae86 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:32:10 2026 -0600 bubble up error messages commit 26da6bd Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Mon Jan 5 16:16:42 2026 -0600 integrate gpg with postprocessing wrapper, standardise api commit 425e661 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Fri Jan 2 12:54:44 2026 -0600 [feat] introduce prefixes for saved documents in config commit 083e076 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Fri Jan 2 12:00:55 2026 -0600 check binary exists + more logging commit fc81a33 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Fri Jan 2 11:52:30 2026 -0600 working gpg implementation (needs more testing) commit 3e85c16 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Wed Dec 31 12:06:48 2025 -0600 [feat] migrate to vitest and improve logging Squashed commit of the following: commit f794720b72e703f55e509010557987352b3b777d Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Wed Dec 31 10:12:31 2025 -0600 FIX: filter undefined's from algorithm cols commit d7d8236a2d3b1982038c2e9ea15bac17038224a3 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Wed Dec 31 10:12:01 2025 -0600 more tests for data validation commit b84a216dccfdde5ee7972e90dedd7a6912718beb Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Wed Dec 31 09:30:35 2025 -0600 port tests to vitest (unfinished) commit fb32ae6e4a3879b6318d8ec4b3acdc24262f7d43 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Wed Dec 31 08:08:20 2025 -0600 bring in vitest commit fa51a42 Merge: 1357833 8a32a1f Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Dec 30 09:43:13 2025 -0600 Merge branch 'main' into feat/postprocess-encrypt-file commit 1357833 Author: Dean Ford <66484815+dem1fo@users.noreply.github.com> Date: Tue Dec 30 06:53:54 2025 -0600 [temp] postprocess first pass implementation
1 parent 8a32a1f commit a039024

56 files changed

Lines changed: 5390 additions & 2014 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yaml

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,16 @@ jobs:
3434
run: npm install
3535

3636
- name: Run tests
37-
run: npm run test:ci
38-
env:
39-
NODE_OPTIONS: '--experimental-vm-modules'
40-
41-
- name: Publish Test Results
42-
uses: EnricoMi/publish-unit-test-result-action@v2.18.0
43-
if: always()
44-
with:
45-
files: ./coverage/test-report/test-report.xml
46-
47-
- name: Publish Code Coverage
48-
uses: irongut/CodeCoverageSummary@v1.3.0
49-
with:
50-
filename: coverage/cobertura-coverage.xml
51-
badge: true
52-
format: markdown
53-
output: both
54-
55-
- name: Add Coverage PR Comment
56-
uses: marocchino/sticky-pull-request-comment@v2
57-
if: github.event_name == 'pull_request'
37+
run: npm run test:coverage
38+
39+
- name: Report Coverage
40+
if: (!cancelled())
41+
uses: davelosert/vitest-coverage-report-action@v2
42+
43+
- name: Publish JUnit Test Report
44+
if: (!cancelled()) && (success() || failure())
45+
uses: mikepenz/action-junit-report@v5
5846
with:
59-
recreate: true
60-
path: code-coverage-results.md
47+
report_paths: reports/junit.xml
48+
check_name: 'Vitest JUnit'
6149

62-
- name: Write to job Summary
63-
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ tmp
33
dist
44
.vscode
55
coverage
6+
reports
67

78
/lib
89
output/

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This repository is designed to be used with file-based data programmatically; fo
1010
📦common-identifier-algorithm-shared
1111
┣ 📂src
1212
┃ ┣ 📂config # functions related to the handling of configuration files
13+
┃ ┣ 📂crypto # functions related to encryption & signing of files
1314
┃ ┣ 📂decoding # reading and decoding files - CSV or XLSX
1415
┃ ┣ 📂encoding # encoding and writing files - CSV or XLSX
1516
┃ ┣ 📂hashing # base hashing logic and supporting utilities
@@ -46,4 +47,8 @@ This repository is designed to be used with file-based data programmatically; fo
4647
- The `src/processing` processing function identifies if the target is a mapping document based on the current configuration and the data in the file. Using the active configuration it collects data into `static` `to_translate` and `reference` buckets per-row and passes it to the active algorithm for processing
4748
- The active algorithm takes the `{ static:[...], to_translate:[...], reference: [...] }` per-row data and returns a map with the columns it wants to add -- ex: `{ USCADI: "....", DOCUMENT_HASH: "..." }`
4849
- The data returned by the algorithm is merged into the source rows so the encoders can package multiple different outputs
49-
- The `src/encoding` Encoders (CSV and XLSX) write the output based on the relevant `[destination]` / `[destination_map]` section of the active configuration.
50+
- The `src/encoding` Encoders (CSV and XLSX) write the output based on the relevant `[destination]` / `[destination_map]` section of the active configuration.
51+
52+
### Post-processing
53+
54+
- Any post-processing steps (e.g. encryption, signing, etc.) are handled after the encoding step, using the relevant classes in `src/crypto` (e.g. `GpgWrapper` for GPG encryption/signing)

docs/configuration-files.md

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,24 @@ Look in any of the existing algorithm implementation repositories for examples o
1010

1111
The meta section of the file contains information related to application versioning.
1212

13-
```
13+
```toml
1414
[meta]
15-
region="ABC" # application installations are region dependent, the value specified here MUST match the built-in region.
15+
id="ABC" # application installations are typically region dependent, the value specified here MUST match the built-in region.
1616
version="1.0.0" # the version information is shown in the top-right of the desktop UI for user visibility.
1717
signature = "aaabbb"
1818
```
19-
The signature is the calculated `md5` hash value of the configuration file itself, computed using the `src/config/generateConfigHash.ts` utility. This feature exists to reduce the likelihood of accidental changes to the config file causing issues in the deterministic processing of input data.
19+
The signature is the calculated `md5` hash value of the configuration file itself, computed using the [`generateConfigHash`](../src/config/utils.ts) utility. This feature exists to reduce the likelihood of accidental changes to the config file causing issues in the deterministic processing of input data.
2020

2121
When a change is made to the configuration file, a new signature value must be created to reflect its new content.
2222

23-
> TODO: git pre-commit hook to validate the config file on commit and throw an error if values don't match.
24-
2523
### Messages
2624

2725
> [!IMPORTANT]
2826
> If you are intending on using the UI component of this application, the messages section in the configuration file is NOT optional. The values for `terms_and_conditions`, `error_in_config`, and `error_in_salt` MUST be defined.
2927
3028
Messages are an optional field used to set the default error and terms & conditions messages within the UI application. Each of these fields supports `HTML` tag syntax.
3129

32-
```
30+
```toml
3331
[messages]
3432
# terms and conditions are shown to the user on first start and upon configuration file changes.
3533
terms_and_conditions="""
@@ -46,9 +44,7 @@ error_in_salt=""
4644

4745
The `source` sections defines the expected input columns in the source dataset. The `name` key is the human readable name in the header of the CSV file, `alias` is the more machine-friendly name used by the application internally, and `default` is the default value to use for empty cells where necessary.
4846

49-
> TODO: make `alias` an optional parameter - it is not relevant for programmatic usage.
50-
51-
```
47+
```toml
5248
[source]
5349
# an array of column names, their aliases, and default values where necessary.
5450
columns = [
@@ -62,7 +58,7 @@ columns = [
6258

6359
This file section details which validation rules to apply to which columns in the input file.
6460

65-
```
61+
```toml
6662
[validations]
6763
# per column name to apply validation rules to, define an array of validation rules
6864
column_name = [
@@ -74,7 +70,7 @@ column_name = [
7470
]
7571
```
7672

77-
```
73+
```toml
7874
# the structure of a validation rule is as follows:
7975
{
8076
# the name of the validation rule, from the supported list.
@@ -107,7 +103,7 @@ This is the list of currently supported validation rules, these are further desc
107103

108104
### Algorithm
109105

110-
```
106+
```toml
111107
[algorithm]
112108
# the aliased columns to use as part of the algo implementation.
113109
[algorithm.columns]
@@ -119,7 +115,7 @@ static = [ "col_b" ]
119115
reference = [ "col_c" ]
120116
```
121117

122-
```
118+
```toml
123119
[algorithm.hash]
124120
# the hashing algorithm to use, currently only SHA256 is supported
125121
strategy = "SHA256"
@@ -146,7 +142,7 @@ darwin = "$APPDATA/<path_to_file>/file.asc"
146142

147143
Define the columns to include in the output file, including the human-readable names to convert to where necessary.
148144

149-
```
145+
```toml
150146
[destination]
151147
# array of column names and aliases to include in the output file
152148
columns = [
@@ -163,7 +159,7 @@ postfix = "_OUTPUT"
163159

164160
Define the columns to include in the output mapping file, including the human-readable names to convert to where necessary.
165161

166-
```
162+
```toml
167163
[destination_map]
168164
# array of column names and aliases to include in the output mapping file
169165
columns = [
@@ -183,7 +179,7 @@ Define the columns to include in the error report, including the human-readable
183179
> [!IMPORTANT]
184180
> Make sure to include the `Errors | errors` column in the output configuration, otherwise they will not be included in the output file.
185181
186-
```
182+
```toml
187183
[destination_errors]
188184
# array of column names and aliases to include in the errors file
189185
columns = [
@@ -195,4 +191,15 @@ columns = [
195191
# suffix that is appended to the errors filename -> <input_file_name><postfix>.csv
196192
# for XLSX, this is also the name of the sheet containing the final data
197193
postfix = "_ERRORS"
194+
```
195+
196+
### Post-processing
197+
198+
#### Encryption / Signing
199+
200+
```toml
201+
[post_processing]
202+
[post_processing.encryption]
203+
recipient = "QWERTY" # the fingerprint or identifier of the recipient to encrypt the file for
204+
gpgBinaryPath = "" # optional path to gpg binary, overrides system path lookup
198205
```

examples/example_algorithm/config.toml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[meta]
22
id="ANY"
33
version="1.0.0"
4-
signature="a36d008f81060928709a6704da61bbd6"
4+
signature="f2789d9afdb774d88be5998aa37bdb78"
55

66
[source]
77
columns = [
@@ -35,18 +35,25 @@ strategy = "SHA256"
3535
columns = [
3636
{ name = "Common Identifier", alias = "hashed_id" },
3737
]
38-
postfix = "-OUTPUT-{{yyyy-MM-dd--HH-mm-ss}}"
38+
prefix = "OUTPUT-"
39+
postfix = "-{{yyyyMMddHHmm}}"
3940

4041
[destination_map]
4142
columns = [
4243
{ name = "ID", alias = "id" },
4344
{ name = "Common Identifier", alias = "hashed_id" }
4445
]
45-
postfix = "-MAPPING-{{yyyy-MM-dd--HH-mm-ss}}"
46+
prefix = "MAPPING-"
47+
postfix = "-{{yyyyMMddHHmm}}"
4648

4749
[destination_errors]
4850
columns = [
4951
{ name = "Errors", alias = "errors" },
5052
{ name = "ID", alias = "id" },
5153
]
52-
postfix = "-ERRORS-{{yyyy-MM-dd--HH-mm-ss}}"
54+
prefix = "ERRORS-"
55+
postfix = "-{{yyyyMMddHHmm}}"
56+
57+
# [post_processing]
58+
# [post_processing.encryption]
59+
# recipient = "SOME_PUBLIC_KEY_FINGERPRINT"

examples/file_based_usage.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// REPLACE ALL REFERENCES TO "_generic_hasher" WITH THE DESIRED ALGORITHM IN THE ALGORITHMS DIRECTORY.
22

3-
import { loadConfig, preprocessFile, processFile } from '../src/index';
3+
import { existsSync, rmSync } from 'node:fs';
4+
import { loadConfig, postprocessFile, preprocessFile, processFile } from '../src/index';
45
import { makeHasher, ALGORITHM_ID } from './example_algorithm/_generic_hasher';
5-
import { dirname, join } from 'node:path';
6+
import { dirname, join, parse, resolve } from 'node:path';
67
import { fileURLToPath } from 'node:url';
78

89
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -12,31 +13,52 @@ const INPUT_PATH = join(__dirname, 'example_algorithm', 'input_10.csv');
1213
const OUTPUT_PATH = join(__dirname, 'output', 'output_10.csv');
1314
const VALIDATION_ERRORS_PATH = join(__dirname, 'output', 'validation_errors.csv');
1415

15-
// load configuration from file
16+
if (existsSync(resolve(__dirname, "output"))) rmSync(join(__dirname, "output"), { recursive: true, force: true});
17+
18+
// 1. load configuration from file
1619
const configLoadResult = loadConfig({
1720
configPath: CONFIG_PATH,
1821
algorithmId: ALGORITHM_ID,
1922
validateConfig: true,
2023
embeddedSalt: { source: "STRING", value: "SOME_SALT_VALUE" }
2124
});
22-
if (!configLoadResult.success) throw new Error(`ERROR: Unable to load configuration file >> ${configLoadResult.error}`);
25+
if (!configLoadResult.success)
26+
throw new Error(`ERROR: Unable to load configuration file >> ${configLoadResult.error}`);
2327

24-
// validate the input file against all configured validation rules.
28+
// 2. validate the input file against all configured validation rules.
2529
const preprocessResult = await preprocessFile({
2630
config: configLoadResult.config,
2731
inputFilePath: INPUT_PATH,
2832
errorFileOutputPath: VALIDATION_ERRORS_PATH,
2933
});
3034

3135
if (!preprocessResult.isValid)
32-
throw new Error('Validation errors found in input file, review error file output.');
36+
throw new Error('ERROR: Validation errors found in input file, review error file output.');
3337

34-
// validate the input file against all configured validation rules.
38+
// 3. process the input file according to the configuration.
3539
const processFileResult = await processFile({
3640
config: configLoadResult.config,
3741
inputFilePath: INPUT_PATH,
3842
outputPath: OUTPUT_PATH,
3943
hasherFactory: makeHasher,
4044
});
41-
// print the result, save the result, etc.
42-
console.dir(processFileResult, { depth: 3 });
45+
if (!processFileResult.outputFilePath)
46+
throw new Error(`ERROR: Unable to process input file`);
47+
48+
// 4. print the result, save the result, etc.
49+
console.dir(processFileResult, { depth: 3 });
50+
51+
52+
// 5. postprocess the output file according to the configuration.
53+
const postprocessResult = await postprocessFile({
54+
config: configLoadResult.config,
55+
inputPath: processFileResult.outputFilePath,
56+
outputPath: join(parse(processFileResult.outputFilePath).dir, "ENCRYPTED.gpg"),
57+
options: {
58+
signer: "" // OPTIONAL: Signing key
59+
}
60+
});
61+
if (!postprocessResult.success) {
62+
const errors = postprocessResult.steps.filter(step => !step.success);
63+
throw new Error(`ERROR: Postprocessing failed, one or more steps failed >> ${JSON.stringify(errors)}`);
64+
}

examples/programmatic_usage.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// REPLACE ALL REFERENCES TO "_generic_hasher" WITH THE DESIRED ALGORITHM IN THE ALGORITHMS DIRECTORY.
2-
import { generateHashesForDocument, validateConfigCore, validateDocument, type CidDocument, type Config } from '../src/index';
3-
import { SUPPORTED_VALIDATORS } from '../src/validation/Validation';
2+
import { generateHashesForDocument, validateConfigCore, validateDocument, type CidDocument, type Config } from '@/index';
3+
import { SUPPORTED_VALIDATORS } from '@/validation/Validation';
44
import { makeHasher } from './example_algorithm/_generic_hasher';
55

66
/*
@@ -54,7 +54,7 @@ const doc: CidDocument = {
5454
]
5555
}
5656

57-
function main() {
57+
async function main() {
5858
const configValidationResult = validateConfigCore(config, "UNKNOWN");
5959
if (!!configValidationResult) {
6060
console.log(`ERROR: ${configValidationResult}`);
@@ -75,6 +75,9 @@ function main() {
7575

7676
// print the results, save the results, up to you.
7777
console.dir(result, { depth: 5 });
78+
79+
// NOTE: do any postprocessing steps as required, e.g. encryption, signing, etc.
80+
// see the GpgWrapper class in src/crypto/gpg.ts for example usage.
7881
}
7982

8083
main();

jest.ci.config.js

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)