sota-adif-updater 1.0.0

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.
Files changed (42) hide show
  1. package/LICENSE +121 -0
  2. package/README.md +28 -0
  3. package/dist/src/adif/parser.d.ts +2 -0
  4. package/dist/src/adif/parser.js +57 -0
  5. package/dist/src/adif/parser.js.map +1 -0
  6. package/dist/src/adif/types.d.ts +11 -0
  7. package/dist/src/adif/types.js +2 -0
  8. package/dist/src/adif/types.js.map +1 -0
  9. package/dist/src/adif/writer.d.ts +3 -0
  10. package/dist/src/adif/writer.js +31 -0
  11. package/dist/src/adif/writer.js.map +1 -0
  12. package/dist/src/index.d.ts +1 -0
  13. package/dist/src/index.js +6 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/prompts/flow.d.ts +1 -0
  16. package/dist/src/prompts/flow.js +148 -0
  17. package/dist/src/prompts/flow.js.map +1 -0
  18. package/dist/src/validation/pota.d.ts +2 -0
  19. package/dist/src/validation/pota.js +9 -0
  20. package/dist/src/validation/pota.js.map +1 -0
  21. package/dist/src/validation/sota.d.ts +2 -0
  22. package/dist/src/validation/sota.js +9 -0
  23. package/dist/src/validation/sota.js.map +1 -0
  24. package/dist/tests/adif/parser.test.d.ts +1 -0
  25. package/dist/tests/adif/parser.test.js +64 -0
  26. package/dist/tests/adif/parser.test.js.map +1 -0
  27. package/dist/tests/adif/writer.test.d.ts +1 -0
  28. package/dist/tests/adif/writer.test.js +112 -0
  29. package/dist/tests/adif/writer.test.js.map +1 -0
  30. package/dist/tests/prompts/flow.test.d.ts +1 -0
  31. package/dist/tests/prompts/flow.test.js +196 -0
  32. package/dist/tests/prompts/flow.test.js.map +1 -0
  33. package/dist/tests/validation/pota.test.d.ts +1 -0
  34. package/dist/tests/validation/pota.test.js +42 -0
  35. package/dist/tests/validation/pota.test.js.map +1 -0
  36. package/dist/tests/validation/sota.test.d.ts +1 -0
  37. package/dist/tests/validation/sota.test.js +38 -0
  38. package/dist/tests/validation/sota.test.js.map +1 -0
  39. package/dist/vitest.config.d.ts +2 -0
  40. package/dist/vitest.config.js +7 -0
  41. package/dist/vitest.config.js.map +1 -0
  42. package/package.json +37 -0
package/LICENSE ADDED
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # sota_adif_updater
2
+ This is a small command line utility to quickly add SOTA and POTA references to an existing ADIF file.
3
+
4
+ ## Releasing a new version
5
+
6
+ 1. Ensure all changes are merged to `main` and CI is green.
7
+ 2. On `main`, bump the version and create a git tag:
8
+ ```bash
9
+ npm version patch # or minor / major
10
+ ```
11
+ 3. Push the commit and tag:
12
+ ```bash
13
+ git push origin main --tags
14
+ ```
15
+ 4. Go to **GitHub → Releases → New Release**, select the tag, add release notes, and click **Publish release**.
16
+
17
+ The release workflow will automatically build the project and publish the new version to npm.
18
+
19
+ ## One-time npm Trusted Publisher setup
20
+
21
+ This project uses [npm Trusted Publishers](https://docs.npmjs.com/trusted-publishers) so no stored secrets are needed. To configure it on npmjs.com:
22
+
23
+ 1. Go to the package page → **Settings** → **Trusted Publishers** → **Add a trusted publisher**.
24
+ 2. Fill in:
25
+ - **Repository owner**: `spowers42`
26
+ - **Repository name**: `sota_adif_updater`
27
+ - **Workflow filename**: `release.yml`
28
+ 3. Save. GitHub Actions will now authenticate via OIDC automatically on each release.
@@ -0,0 +1,2 @@
1
+ import type { AdifFile } from './types.js';
2
+ export declare function parseAdif(content: string): AdifFile;
@@ -0,0 +1,57 @@
1
+ function parseFields(text) {
2
+ const fields = [];
3
+ const tagRegex = /<([^>]+)>/gi;
4
+ let match;
5
+ while ((match = tagRegex.exec(text)) !== null) {
6
+ const tagContent = match[1];
7
+ const colonIdx = tagContent.indexOf(':');
8
+ if (colonIdx === -1)
9
+ continue; // no length specifier — skip markers like EOR/EOH
10
+ const name = tagContent.slice(0, colonIdx).toUpperCase();
11
+ const rest = tagContent.slice(colonIdx + 1);
12
+ const length = parseInt(rest.split(':')[0], 10);
13
+ if (isNaN(length))
14
+ continue;
15
+ const valueStart = match.index + match[0].length;
16
+ const value = text.slice(valueStart, valueStart + length);
17
+ fields.push({ name, value });
18
+ tagRegex.lastIndex = valueStart + length;
19
+ }
20
+ return fields;
21
+ }
22
+ function parseRecords(body) {
23
+ const records = [];
24
+ const eorRegex = /<EOR>/gi;
25
+ let lastIndex = 0;
26
+ let match;
27
+ while ((match = eorRegex.exec(body)) !== null) {
28
+ const recordText = body.slice(lastIndex, match.index);
29
+ const fields = parseFields(recordText);
30
+ if (fields.length > 0) {
31
+ records.push({ fields });
32
+ }
33
+ lastIndex = match.index + match[0].length;
34
+ }
35
+ return records;
36
+ }
37
+ export function parseAdif(content) {
38
+ const eohMatch = /<EOH>/i.exec(content);
39
+ if (!eohMatch) {
40
+ const hasRecords = /<EOR>/i.test(content);
41
+ if (!hasRecords) {
42
+ throw new Error('Not a valid ADIF file: no <EOR> or <EOH> markers found');
43
+ }
44
+ return {
45
+ headerText: '',
46
+ records: parseRecords(content),
47
+ };
48
+ }
49
+ const eohIndex = eohMatch.index;
50
+ const headerText = content.slice(0, eohIndex);
51
+ const body = content.slice(eohIndex + eohMatch[0].length);
52
+ return {
53
+ headerText,
54
+ records: parseRecords(body),
55
+ };
56
+ }
57
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/adif/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface AdifField {
2
+ name: string;
3
+ value: string;
4
+ }
5
+ export interface AdifRecord {
6
+ fields: AdifField[];
7
+ }
8
+ export interface AdifFile {
9
+ headerText: string;
10
+ records: AdifRecord[];
11
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/adif/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { AdifFile } from './types.js';
2
+ export declare function serializeAdif(file: AdifFile): string;
3
+ export declare function updateRecords(file: AdifFile, sotaRef?: string, potaRefs?: string[]): AdifFile;
@@ -0,0 +1,31 @@
1
+ function serializeField(field) {
2
+ return `<${field.name.toUpperCase()}:${field.value.length}>${field.value}`;
3
+ }
4
+ export function serializeAdif(file) {
5
+ let output = '';
6
+ if (file.headerText !== '') {
7
+ output += file.headerText + '<EOH>\n\n';
8
+ }
9
+ for (const record of file.records) {
10
+ const fieldStrs = record.fields.map(serializeField);
11
+ output += fieldStrs.join(' ') + ' <EOR>\n';
12
+ }
13
+ return output;
14
+ }
15
+ export function updateRecords(file, sotaRef, potaRefs = []) {
16
+ const updatedRecords = file.records.map((record) => {
17
+ const fields = record.fields.filter((f) => f.name !== 'SOTA_REF' && f.name !== 'POTA_REF' && f.name !== 'POTA_REF_LIST');
18
+ if (sotaRef) {
19
+ fields.push({ name: 'SOTA_REF', value: sotaRef });
20
+ }
21
+ if (potaRefs.length === 1) {
22
+ fields.push({ name: 'POTA_REF', value: potaRefs[0] });
23
+ }
24
+ else if (potaRefs.length > 1) {
25
+ fields.push({ name: 'POTA_REF_LIST', value: potaRefs.join(',') });
26
+ }
27
+ return { fields };
28
+ });
29
+ return { ...file, records: updatedRecords };
30
+ }
31
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/adif/writer.ts"],"names":[],"mappings":"AAEA,SAAS,cAAc,CAAC,KAAgB;IACtC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE,EAAE,CAAC;QAC3B,MAAM,IAAI,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC;IAC1C,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;IAC7C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,IAAc,EACd,OAAgB,EAChB,WAAqB,EAAE;IAEvB,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe,CACpF,CAAC;QAEF,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { runFlow } from './prompts/flow.js';
2
+ runFlow().catch((err) => {
3
+ console.error(err instanceof Error ? err.message : err);
4
+ process.exit(1);
5
+ });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC/B,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runFlow(): Promise<void>;
@@ -0,0 +1,148 @@
1
+ import { input, confirm } from '@inquirer/prompts';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { parseAdif } from '../adif/parser.js';
6
+ import { serializeAdif, updateRecords } from '../adif/writer.js';
7
+ import { formatSotaRef, validateSotaRef } from '../validation/sota.js';
8
+ import { formatPotaRef, validatePotaRef } from '../validation/pota.js';
9
+ function existingValues(records, ...fieldNames) {
10
+ const names = new Set(fieldNames.map((n) => n.toUpperCase()));
11
+ return [
12
+ ...new Set(records.flatMap((r) => r.fields.filter((f) => names.has(f.name)).map((f) => f.value))),
13
+ ];
14
+ }
15
+ function expandPath(filePath) {
16
+ if (filePath.startsWith('~/')) {
17
+ return join(homedir(), filePath.slice(2));
18
+ }
19
+ return filePath;
20
+ }
21
+ async function askForFile() {
22
+ while (true) {
23
+ const raw = await input({ message: 'Path to ADIF file:' });
24
+ const filePath = expandPath(raw.trim());
25
+ try {
26
+ const content = await readFile(filePath, 'utf-8');
27
+ const parsed = parseAdif(content);
28
+ const count = parsed.records.length;
29
+ console.log(`\nValid ADIF file — ${count} QSO record${count === 1 ? '' : 's'} found.\n`);
30
+ if (count === 0) {
31
+ console.log('Warning: file contains no QSO records. Changes will have no effect.\n');
32
+ }
33
+ return { filePath, parsed };
34
+ }
35
+ catch (err) {
36
+ console.error(`\nError: ${err instanceof Error ? err.message : String(err)}\n`);
37
+ }
38
+ }
39
+ }
40
+ async function askForSotaRef(records) {
41
+ const existing = existingValues(records, 'SOTA_REF');
42
+ let proceed;
43
+ if (existing.length > 0) {
44
+ console.log(`\nExisting SOTA_REF found: ${existing.join(', ')}`);
45
+ proceed = await confirm({
46
+ message: 'Replace existing SOTA references in all records?',
47
+ default: false,
48
+ });
49
+ }
50
+ else {
51
+ proceed = await confirm({ message: 'Add a SOTA reference to all records?', default: false });
52
+ }
53
+ if (!proceed)
54
+ return undefined;
55
+ const ref = await input({
56
+ message: 'SOTA reference (e.g. W2/WE-003):',
57
+ validate: (value) => {
58
+ if (!value.trim())
59
+ return 'SOTA reference is required';
60
+ if (!validateSotaRef(value))
61
+ return 'Invalid format — expected e.g. W2/WE-003 or G/LD-003';
62
+ return true;
63
+ },
64
+ });
65
+ return formatSotaRef(ref);
66
+ }
67
+ async function askForPotaRefs(records) {
68
+ const existing = existingValues(records, 'POTA_REF', 'POTA_REF_LIST');
69
+ let proceed;
70
+ if (existing.length > 0) {
71
+ console.log(`\nExisting POTA reference(s) found: ${existing.join(', ')}`);
72
+ proceed = await confirm({
73
+ message: 'Replace existing POTA references in all records?',
74
+ default: false,
75
+ });
76
+ }
77
+ else {
78
+ proceed = await confirm({ message: 'Add a POTA reference to all records?', default: false });
79
+ }
80
+ if (!proceed)
81
+ return [];
82
+ const refs = [];
83
+ const first = await input({
84
+ message: 'POTA reference (e.g. US-1234):',
85
+ validate: (value) => {
86
+ if (!value.trim())
87
+ return 'POTA reference is required';
88
+ if (!validatePotaRef(value))
89
+ return 'Invalid format — expected e.g. US-1234 or VE-5082@CA-AB';
90
+ return true;
91
+ },
92
+ });
93
+ refs.push(formatPotaRef(first));
94
+ while (true) {
95
+ const more = await confirm({ message: 'Add another POTA reference?', default: false });
96
+ if (!more)
97
+ break;
98
+ const next = await input({
99
+ message: 'POTA reference (e.g. US-1234):',
100
+ validate: (value) => {
101
+ if (!value.trim())
102
+ return 'POTA reference is required';
103
+ if (!validatePotaRef(value))
104
+ return 'Invalid format — expected e.g. US-1234 or VE-5082@CA-AB';
105
+ return true;
106
+ },
107
+ });
108
+ refs.push(formatPotaRef(next));
109
+ }
110
+ return refs;
111
+ }
112
+ export async function runFlow() {
113
+ try {
114
+ console.log('SOTA/POTA ADIF Updater\n');
115
+ const { filePath, parsed } = await askForFile();
116
+ const sotaRef = await askForSotaRef(parsed.records);
117
+ const potaRefs = await askForPotaRefs(parsed.records);
118
+ if (!sotaRef && potaRefs.length === 0) {
119
+ console.log('\nNo changes selected. Exiting.');
120
+ return;
121
+ }
122
+ console.log('\nChanges to apply to all records:');
123
+ if (sotaRef)
124
+ console.log(` SOTA_REF: ${sotaRef}`);
125
+ if (potaRefs.length === 1)
126
+ console.log(` POTA_REF: ${potaRefs[0]}`);
127
+ if (potaRefs.length > 1)
128
+ console.log(` POTA_REF_LIST: ${potaRefs.join(',')}`);
129
+ console.log(` Records affected: ${parsed.records.length}\n`);
130
+ const apply = await confirm({ message: 'Apply these changes?', default: true });
131
+ if (!apply) {
132
+ console.log('\nCancelled. No changes made.');
133
+ return;
134
+ }
135
+ const updated = updateRecords(parsed, sotaRef, potaRefs);
136
+ const serialized = serializeAdif(updated);
137
+ await writeFile(filePath, serialized, 'utf-8');
138
+ console.log(`\nDone. Updated ${parsed.records.length} record${parsed.records.length === 1 ? '' : 's'} in ${filePath}`);
139
+ }
140
+ catch (err) {
141
+ if (err instanceof Error && err.name === 'ExitPromptError') {
142
+ console.log('\nCancelled.');
143
+ return;
144
+ }
145
+ throw err;
146
+ }
147
+ }
148
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../../../src/prompts/flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function validatePotaRef(ref: string): boolean;
2
+ export declare function formatPotaRef(ref: string): string;
@@ -0,0 +1,9 @@
1
+ // POTA references: <program>-<park-number>[@<ISO-3166-2-location>] e.g. K-5033, VE-5082@CA-AB
2
+ const POTA_REF_REGEX = /^[A-Z]{1,4}-\d{4,5}(@[A-Z]{2}-[A-Z0-9]+)?$/i;
3
+ export function validatePotaRef(ref) {
4
+ return POTA_REF_REGEX.test(ref.trim());
5
+ }
6
+ export function formatPotaRef(ref) {
7
+ return ref.trim().toUpperCase();
8
+ }
9
+ //# sourceMappingURL=pota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pota.js","sourceRoot":"","sources":["../../../src/validation/pota.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,MAAM,cAAc,GAAG,6CAA6C,CAAC;AAErE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function validateSotaRef(ref: string): boolean;
2
+ export declare function formatSotaRef(ref: string): string;
@@ -0,0 +1,9 @@
1
+ // SOTA references: <ITU-prefix>/<area-code>-<3-digit-number> e.g. W2/WE-003, G/LD-003, VK2/HU-071
2
+ const SOTA_REF_REGEX = /^[A-Z0-9]{1,8}\/[A-Z]{1,4}-\d{3}$/i;
3
+ export function validateSotaRef(ref) {
4
+ return SOTA_REF_REGEX.test(ref.trim());
5
+ }
6
+ export function formatSotaRef(ref) {
7
+ return ref.trim().toUpperCase();
8
+ }
9
+ //# sourceMappingURL=sota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sota.js","sourceRoot":"","sources":["../../../src/validation/sota.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAE5D,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { parseAdif } from '../../src/adif/parser.js';
5
+ const fixturesDir = join(import.meta.dirname, '../../fixtures');
6
+ function fixture(name) {
7
+ return readFileSync(join(fixturesDir, name), 'utf-8');
8
+ }
9
+ describe('parseAdif', () => {
10
+ it('parses a valid ADIF file with header and two records', () => {
11
+ const result = parseAdif(fixture('valid.adi'));
12
+ expect(result.records).toHaveLength(2);
13
+ expect(result.headerText).toContain('ADIF_VER');
14
+ });
15
+ it('extracts field names in uppercase', () => {
16
+ const result = parseAdif(fixture('valid.adi'));
17
+ const names = result.records[0].fields.map((f) => f.name);
18
+ expect(names).toContain('CALL');
19
+ expect(names).toContain('QSO_DATE');
20
+ expect(names).toContain('MODE');
21
+ });
22
+ it('extracts field values correctly', () => {
23
+ const result = parseAdif(fixture('valid.adi'));
24
+ const call = result.records[0].fields.find((f) => f.name === 'CALL');
25
+ expect(call?.value).toBe('W1AW');
26
+ });
27
+ it('parses a file without a header', () => {
28
+ const result = parseAdif(fixture('no_header.adi'));
29
+ expect(result.headerText).toBe('');
30
+ expect(result.records).toHaveLength(1);
31
+ });
32
+ it('parses fields with type indicators (e.g. <FIELD:5:S>value)', () => {
33
+ const content = '<ADIF_VER:5>3.1.4 <EOH>\n<CALL:4:S>W1AW <EOR>\n';
34
+ const result = parseAdif(content);
35
+ const call = result.records[0].fields.find((f) => f.name === 'CALL');
36
+ expect(call?.value).toBe('W1AW');
37
+ });
38
+ it('handles case-insensitive EOH and EOR markers', () => {
39
+ const content = '<adif_ver:5>3.1.4 <eoh>\n<call:4>W1AW <eor>\n';
40
+ const result = parseAdif(content);
41
+ expect(result.records).toHaveLength(1);
42
+ });
43
+ it('parses a file with existing SOTA and POTA refs', () => {
44
+ const result = parseAdif(fixture('with_refs.adi'));
45
+ const sota = result.records[0].fields.find((f) => f.name === 'SOTA_REF');
46
+ const pota = result.records[0].fields.find((f) => f.name === 'POTA_REF');
47
+ expect(sota?.value).toBe('W2/WE-003');
48
+ expect(pota?.value).toBe('K-1234');
49
+ });
50
+ it('throws for a file with no EOR or EOH markers', () => {
51
+ expect(() => parseAdif('this is not adif')).toThrow('Not a valid ADIF file');
52
+ });
53
+ it('returns empty records array for a header-only file', () => {
54
+ const content = '<ADIF_VER:5>3.1.4 <EOH>\n';
55
+ const result = parseAdif(content);
56
+ expect(result.records).toHaveLength(0);
57
+ });
58
+ it('skips records with no fields', () => {
59
+ const content = '<EOH>\n<EOR>\n<CALL:4>W1AW <EOR>\n';
60
+ const result = parseAdif(content);
61
+ expect(result.records).toHaveLength(1);
62
+ });
63
+ });
64
+ //# sourceMappingURL=parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.js","sourceRoot":"","sources":["../../../tests/adif/parser.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { serializeAdif, updateRecords } from '../../src/adif/writer.js';
3
+ import { parseAdif } from '../../src/adif/parser.js';
4
+ const SIMPLE_FILE = {
5
+ headerText: 'Test header\n<ADIF_VER:5>3.1.4 ',
6
+ records: [
7
+ { fields: [{ name: 'CALL', value: 'W1AW' }, { name: 'BAND', value: '40m' }] },
8
+ { fields: [{ name: 'CALL', value: 'K1TTT' }, { name: 'BAND', value: '20m' }] },
9
+ ],
10
+ };
11
+ describe('updateRecords', () => {
12
+ it('adds SOTA_REF to all records when absent', () => {
13
+ const result = updateRecords(SIMPLE_FILE, 'W2/WE-003');
14
+ for (const rec of result.records) {
15
+ const sota = rec.fields.find((f) => f.name === 'SOTA_REF');
16
+ expect(sota?.value).toBe('W2/WE-003');
17
+ }
18
+ });
19
+ it('replaces existing SOTA_REF in all records', () => {
20
+ const file = {
21
+ headerText: '',
22
+ records: [
23
+ { fields: [{ name: 'CALL', value: 'W1AW' }, { name: 'SOTA_REF', value: 'G/LD-003' }] },
24
+ ],
25
+ };
26
+ const result = updateRecords(file, 'W2/WE-003');
27
+ const sota = result.records[0].fields.find((f) => f.name === 'SOTA_REF');
28
+ expect(sota?.value).toBe('W2/WE-003');
29
+ expect(result.records[0].fields.filter((f) => f.name === 'SOTA_REF')).toHaveLength(1);
30
+ });
31
+ it('adds a single POTA_REF to all records', () => {
32
+ const result = updateRecords(SIMPLE_FILE, undefined, ['K-5033']);
33
+ for (const rec of result.records) {
34
+ const pota = rec.fields.find((f) => f.name === 'POTA_REF');
35
+ expect(pota?.value).toBe('K-5033');
36
+ }
37
+ });
38
+ it('uses POTA_REF_LIST for multiple parks, comma-delimited', () => {
39
+ const result = updateRecords(SIMPLE_FILE, undefined, ['K-5033', 'K-5034']);
40
+ const potaList = result.records[0].fields.find((f) => f.name === 'POTA_REF_LIST');
41
+ expect(potaList?.value).toBe('K-5033,K-5034');
42
+ expect(result.records[0].fields.find((f) => f.name === 'POTA_REF')).toBeUndefined();
43
+ });
44
+ it('replaces existing POTA_REF with a new single ref', () => {
45
+ const file = {
46
+ headerText: '',
47
+ records: [
48
+ { fields: [{ name: 'CALL', value: 'W1AW' }, { name: 'POTA_REF', value: 'K-9999' }] },
49
+ ],
50
+ };
51
+ const result = updateRecords(file, undefined, ['K-5033']);
52
+ const pota = result.records[0].fields.find((f) => f.name === 'POTA_REF');
53
+ expect(pota?.value).toBe('K-5033');
54
+ expect(result.records[0].fields.filter((f) => f.name === 'POTA_REF')).toHaveLength(1);
55
+ });
56
+ it('removes existing POTA_REF_LIST when replacing with a single ref', () => {
57
+ const file = {
58
+ headerText: '',
59
+ records: [
60
+ { fields: [{ name: 'CALL', value: 'W1AW' }, { name: 'POTA_REF_LIST', value: 'K-9998,K-9999' }] },
61
+ ],
62
+ };
63
+ const result = updateRecords(file, undefined, ['K-5033']);
64
+ expect(result.records[0].fields.find((f) => f.name === 'POTA_REF')?.value).toBe('K-5033');
65
+ expect(result.records[0].fields.find((f) => f.name === 'POTA_REF_LIST')).toBeUndefined();
66
+ });
67
+ it('does not add any fields when called with no refs', () => {
68
+ const result = updateRecords(SIMPLE_FILE);
69
+ expect(result.records[0].fields).toHaveLength(SIMPLE_FILE.records[0].fields.length);
70
+ expect(result.records[0].fields.find((f) => f.name === 'SOTA_REF')).toBeUndefined();
71
+ expect(result.records[0].fields.find((f) => f.name === 'POTA_REF')).toBeUndefined();
72
+ expect(result.records[0].fields.find((f) => f.name === 'POTA_REF_LIST')).toBeUndefined();
73
+ });
74
+ it('does not mutate the original file', () => {
75
+ updateRecords(SIMPLE_FILE, 'W2/WE-003', ['K-5033']);
76
+ expect(SIMPLE_FILE.records[0].fields.find((f) => f.name === 'SOTA_REF')).toBeUndefined();
77
+ });
78
+ });
79
+ describe('serializeAdif', () => {
80
+ it('includes the header text and EOH marker', () => {
81
+ const out = serializeAdif(SIMPLE_FILE);
82
+ expect(out).toContain('ADIF_VER');
83
+ expect(out).toContain('<EOH>');
84
+ });
85
+ it('includes EOR markers for each record', () => {
86
+ const out = serializeAdif(SIMPLE_FILE);
87
+ const eorCount = (out.match(/<EOR>/gi) ?? []).length;
88
+ expect(eorCount).toBe(2);
89
+ });
90
+ it('serializes field values with correct length specifiers', () => {
91
+ const out = serializeAdif(SIMPLE_FILE);
92
+ expect(out).toContain('<CALL:4>W1AW');
93
+ expect(out).toContain('<CALL:5>K1TTT');
94
+ });
95
+ it('omits header section when headerText is empty', () => {
96
+ const file = { headerText: '', records: SIMPLE_FILE.records };
97
+ const out = serializeAdif(file);
98
+ expect(out).not.toContain('<EOH>');
99
+ expect(out).toContain('<EOR>');
100
+ });
101
+ it('round-trips a parsed file correctly', () => {
102
+ const original = '<ADIF_VER:5>3.1.4 <EOH>\n\n<CALL:4>W1AW <BAND:3>40m <EOR>\n';
103
+ const parsed = parseAdif(original);
104
+ const serialized = serializeAdif(parsed);
105
+ const reparsed = parseAdif(serialized);
106
+ expect(reparsed.records).toHaveLength(parsed.records.length);
107
+ const origCall = parsed.records[0].fields.find((f) => f.name === 'CALL');
108
+ const newCall = reparsed.records[0].fields.find((f) => f.name === 'CALL');
109
+ expect(newCall?.value).toBe(origCall?.value);
110
+ });
111
+ });
112
+ //# sourceMappingURL=writer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.test.js","sourceRoot":"","sources":["../../../tests/adif/writer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGrD,MAAM,WAAW,GAAa;IAC5B,UAAU,EAAE,iCAAiC;IAC7C,OAAO,EAAE;QACP,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;QAC7E,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;KAC/E;CACF,CAAC;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QACvD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;YAC3D,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GAAa;YACrB,UAAU,EAAE,EAAE;YACd,OAAO,EAAE;gBACP,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE;aACvF;SACF,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;YAC3D,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC;QAClF,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACtF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,IAAI,GAAa;YACrB,UAAU,EAAE,EAAE;YACd,OAAO,EAAE;gBACP,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE;aACrF;SACF,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,IAAI,GAAa;YACrB,UAAU,EAAE,EAAE;YACd,OAAO,EAAE;gBACP,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE;aACjG;SACF,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1F,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACrD,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,IAAI,GAAa,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;QACxE,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,QAAQ,GAAG,6DAA6D,CAAC;QAC/E,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;QAEvC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAC1E,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ vi.mock('@inquirer/prompts', () => ({
3
+ input: vi.fn(),
4
+ confirm: vi.fn(),
5
+ }));
6
+ vi.mock('node:fs/promises', () => ({
7
+ readFile: vi.fn(),
8
+ writeFile: vi.fn(),
9
+ }));
10
+ import { input, confirm } from '@inquirer/prompts';
11
+ import { readFile, writeFile } from 'node:fs/promises';
12
+ import { runFlow } from '../../src/prompts/flow.js';
13
+ const mockedInput = vi.mocked(input);
14
+ const mockedConfirm = vi.mocked(confirm);
15
+ const mockedReadFile = vi.mocked(readFile);
16
+ const mockedWriteFile = vi.mocked(writeFile);
17
+ const VALID_ADIF = '<ADIF_VER:5>3.1.4 <EOH>\n\n<CALL:4>W1AW <BAND:3>40m <EOR>\n';
18
+ const ADIF_WITH_SOTA = '<ADIF_VER:5>3.1.4 <EOH>\n\n<CALL:4>W1AW <SOTA_REF:8>G/LD-003 <EOR>\n';
19
+ const ADIF_WITH_POTA = '<ADIF_VER:5>3.1.4 <EOH>\n\n<CALL:4>W1AW <POTA_REF:6>K-9999 <EOR>\n';
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ mockedReadFile.mockResolvedValue(VALID_ADIF);
23
+ mockedWriteFile.mockResolvedValue(undefined);
24
+ });
25
+ describe('runFlow', () => {
26
+ it('exits without writing when the user skips both SOTA and POTA', async () => {
27
+ mockedInput.mockResolvedValueOnce('/fake/file.adi');
28
+ mockedConfirm
29
+ .mockResolvedValueOnce(false) // skip SOTA
30
+ .mockResolvedValueOnce(false); // skip POTA
31
+ await runFlow();
32
+ expect(mockedWriteFile).not.toHaveBeenCalled();
33
+ });
34
+ it('writes the file with only a SOTA ref when POTA is skipped', async () => {
35
+ mockedInput
36
+ .mockResolvedValueOnce('/fake/file.adi')
37
+ .mockResolvedValueOnce('W2/WE-003');
38
+ mockedConfirm
39
+ .mockResolvedValueOnce(true) // add SOTA
40
+ .mockResolvedValueOnce(false) // skip POTA
41
+ .mockResolvedValueOnce(true); // confirm apply
42
+ await runFlow();
43
+ expect(mockedWriteFile).toHaveBeenCalledOnce();
44
+ const written = mockedWriteFile.mock.calls[0][1];
45
+ expect(written).toContain('<SOTA_REF:9>W2/WE-003');
46
+ expect(written).not.toContain('POTA_REF');
47
+ });
48
+ it('writes the file with only a POTA ref when SOTA is skipped', async () => {
49
+ mockedInput
50
+ .mockResolvedValueOnce('/fake/file.adi')
51
+ .mockResolvedValueOnce('K-5033');
52
+ mockedConfirm
53
+ .mockResolvedValueOnce(false) // skip SOTA
54
+ .mockResolvedValueOnce(true) // add POTA
55
+ .mockResolvedValueOnce(false) // no more POTA
56
+ .mockResolvedValueOnce(true); // confirm apply
57
+ await runFlow();
58
+ expect(mockedWriteFile).toHaveBeenCalledOnce();
59
+ const written = mockedWriteFile.mock.calls[0][1];
60
+ expect(written).toContain('<POTA_REF:6>K-5033');
61
+ expect(written).not.toContain('SOTA_REF');
62
+ });
63
+ it('collects multiple POTA refs and writes them comma-delimited', async () => {
64
+ mockedInput
65
+ .mockResolvedValueOnce('/fake/file.adi')
66
+ .mockResolvedValueOnce('K-5033')
67
+ .mockResolvedValueOnce('K-5034');
68
+ mockedConfirm
69
+ .mockResolvedValueOnce(false) // skip SOTA
70
+ .mockResolvedValueOnce(true) // add POTA
71
+ .mockResolvedValueOnce(true) // add another POTA
72
+ .mockResolvedValueOnce(false) // no more POTA
73
+ .mockResolvedValueOnce(true); // confirm apply
74
+ await runFlow();
75
+ const written = mockedWriteFile.mock.calls[0][1];
76
+ expect(written).toContain('<POTA_REF_LIST:13>K-5033,K-5034');
77
+ expect(written).not.toContain('<POTA_REF:');
78
+ });
79
+ it('writes both SOTA and multiple POTA refs using POTA_REF_LIST', async () => {
80
+ mockedInput
81
+ .mockResolvedValueOnce('/fake/file.adi')
82
+ .mockResolvedValueOnce('W2/WE-003')
83
+ .mockResolvedValueOnce('K-5033')
84
+ .mockResolvedValueOnce('K-5034');
85
+ mockedConfirm
86
+ .mockResolvedValueOnce(true) // add SOTA
87
+ .mockResolvedValueOnce(true) // add POTA
88
+ .mockResolvedValueOnce(true) // add another POTA
89
+ .mockResolvedValueOnce(false) // no more POTA
90
+ .mockResolvedValueOnce(true); // confirm apply
91
+ await runFlow();
92
+ const written = mockedWriteFile.mock.calls[0][1];
93
+ expect(written).toContain('<SOTA_REF:9>W2/WE-003');
94
+ expect(written).toContain('<POTA_REF_LIST:13>K-5033,K-5034');
95
+ });
96
+ it('does not write when the user declines confirmation', async () => {
97
+ mockedInput
98
+ .mockResolvedValueOnce('/fake/file.adi')
99
+ .mockResolvedValueOnce('W2/WE-003');
100
+ mockedConfirm
101
+ .mockResolvedValueOnce(true) // add SOTA
102
+ .mockResolvedValueOnce(false) // skip POTA
103
+ .mockResolvedValueOnce(false); // decline apply
104
+ await runFlow();
105
+ expect(mockedWriteFile).not.toHaveBeenCalled();
106
+ });
107
+ it('re-prompts for the file path when reading fails', async () => {
108
+ mockedInput
109
+ .mockResolvedValueOnce('/bad/path.adi')
110
+ .mockResolvedValueOnce('/fake/file.adi');
111
+ mockedReadFile
112
+ .mockRejectedValueOnce(new Error('ENOENT: no such file'))
113
+ .mockResolvedValueOnce(VALID_ADIF);
114
+ mockedConfirm
115
+ .mockResolvedValueOnce(false) // skip SOTA
116
+ .mockResolvedValueOnce(false); // skip POTA
117
+ await runFlow();
118
+ expect(mockedInput).toHaveBeenCalledTimes(2);
119
+ });
120
+ it('re-prompts for the file path when content is not valid ADIF', async () => {
121
+ mockedInput
122
+ .mockResolvedValueOnce('/not-adif.txt')
123
+ .mockResolvedValueOnce('/fake/file.adi');
124
+ mockedReadFile
125
+ .mockResolvedValueOnce('not adif content at all')
126
+ .mockResolvedValueOnce(VALID_ADIF);
127
+ mockedConfirm
128
+ .mockResolvedValueOnce(false) // skip SOTA
129
+ .mockResolvedValueOnce(false); // skip POTA
130
+ await runFlow();
131
+ expect(mockedInput).toHaveBeenCalledTimes(2);
132
+ });
133
+ it('writes the file to the correct path', async () => {
134
+ mockedInput
135
+ .mockResolvedValueOnce('/specific/path/log.adi')
136
+ .mockResolvedValueOnce('W2/WE-003');
137
+ mockedConfirm
138
+ .mockResolvedValueOnce(true) // add SOTA
139
+ .mockResolvedValueOnce(false) // skip POTA
140
+ .mockResolvedValueOnce(true); // confirm apply
141
+ await runFlow();
142
+ expect(mockedWriteFile).toHaveBeenCalledWith('/specific/path/log.adi', expect.any(String), 'utf-8');
143
+ });
144
+ describe('existing reference warnings', () => {
145
+ it('asks to replace existing SOTA_REF and writes new value when confirmed', async () => {
146
+ mockedReadFile.mockResolvedValue(ADIF_WITH_SOTA);
147
+ mockedInput
148
+ .mockResolvedValueOnce('/fake/file.adi')
149
+ .mockResolvedValueOnce('W2/WE-003');
150
+ mockedConfirm
151
+ .mockResolvedValueOnce(true) // replace existing SOTA
152
+ .mockResolvedValueOnce(false) // skip POTA
153
+ .mockResolvedValueOnce(true); // confirm apply
154
+ await runFlow();
155
+ expect(mockedWriteFile).toHaveBeenCalledOnce();
156
+ const written = mockedWriteFile.mock.calls[0][1];
157
+ expect(written).toContain('<SOTA_REF:9>W2/WE-003');
158
+ expect(written).not.toContain('G/LD-003');
159
+ });
160
+ it('skips SOTA update and does not write when user declines to replace existing SOTA_REF', async () => {
161
+ mockedReadFile.mockResolvedValue(ADIF_WITH_SOTA);
162
+ mockedInput.mockResolvedValueOnce('/fake/file.adi');
163
+ mockedConfirm
164
+ .mockResolvedValueOnce(false) // decline replacing existing SOTA
165
+ .mockResolvedValueOnce(false); // skip POTA
166
+ await runFlow();
167
+ expect(mockedWriteFile).not.toHaveBeenCalled();
168
+ });
169
+ it('asks to replace existing POTA_REF and writes new value when confirmed', async () => {
170
+ mockedReadFile.mockResolvedValue(ADIF_WITH_POTA);
171
+ mockedInput
172
+ .mockResolvedValueOnce('/fake/file.adi')
173
+ .mockResolvedValueOnce('K-5033');
174
+ mockedConfirm
175
+ .mockResolvedValueOnce(false) // skip SOTA
176
+ .mockResolvedValueOnce(true) // replace existing POTA
177
+ .mockResolvedValueOnce(false) // no more POTA
178
+ .mockResolvedValueOnce(true); // confirm apply
179
+ await runFlow();
180
+ expect(mockedWriteFile).toHaveBeenCalledOnce();
181
+ const written = mockedWriteFile.mock.calls[0][1];
182
+ expect(written).toContain('<POTA_REF:6>K-5033');
183
+ expect(written).not.toContain('K-9999');
184
+ });
185
+ it('skips POTA update and does not write when user declines to replace existing POTA_REF', async () => {
186
+ mockedReadFile.mockResolvedValue(ADIF_WITH_POTA);
187
+ mockedInput.mockResolvedValueOnce('/fake/file.adi');
188
+ mockedConfirm
189
+ .mockResolvedValueOnce(false) // skip SOTA
190
+ .mockResolvedValueOnce(false); // decline replacing existing POTA
191
+ await runFlow();
192
+ expect(mockedWriteFile).not.toHaveBeenCalled();
193
+ });
194
+ });
195
+ });
196
+ //# sourceMappingURL=flow.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.test.js","sourceRoot":"","sources":["../../../tests/prompts/flow.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;IACd,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;CACjB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;IACjB,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;CACnB,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD,MAAM,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACrC,MAAM,aAAa,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACzC,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC3C,MAAM,eAAe,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAE7C,MAAM,UAAU,GAAG,6DAA6D,CAAC;AACjF,MAAM,cAAc,GAAG,sEAAsE,CAAC;AAC9F,MAAM,cAAc,GAAG,oEAAoE,CAAC;AAE5F,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,cAAc,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC7C,eAAe,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,WAAW,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QACpD,aAAa;aACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY;QAE7C,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,WAAW;aACR,qBAAqB,CAAC,gBAAgB,CAAC;aACvC,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACtC,aAAa;aACV,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAEhD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,WAAW;aACR,qBAAqB,CAAC,gBAAgB,CAAC;aACvC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACnC,aAAa;aACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,KAAK,CAAC,CAAC,eAAe;aAC5C,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAEhD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,WAAW;aACR,qBAAqB,CAAC,gBAAgB,CAAC;aACvC,qBAAqB,CAAC,QAAQ,CAAC;aAC/B,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACnC,aAAa;aACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,IAAI,CAAC,CAAE,mBAAmB;aAChD,qBAAqB,CAAC,KAAK,CAAC,CAAC,eAAe;aAC5C,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAEhD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;QAC7D,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,WAAW;aACR,qBAAqB,CAAC,gBAAgB,CAAC;aACvC,qBAAqB,CAAC,WAAW,CAAC;aAClC,qBAAqB,CAAC,QAAQ,CAAC;aAC/B,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACnC,aAAa;aACV,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,IAAI,CAAC,CAAE,mBAAmB;aAChD,qBAAqB,CAAC,KAAK,CAAC,CAAC,eAAe;aAC5C,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAEhD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,WAAW;aACR,qBAAqB,CAAC,gBAAgB,CAAC;aACvC,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACtC,aAAa;aACV,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB;QAEjD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,WAAW;aACR,qBAAqB,CAAC,eAAe,CAAC;aACtC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QAC3C,cAAc;aACX,qBAAqB,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;aACxD,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACrC,aAAa;aACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY;QAE7C,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,WAAW,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,WAAW;aACR,qBAAqB,CAAC,eAAe,CAAC;aACtC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QAC3C,cAAc;aACX,qBAAqB,CAAC,yBAAyB,CAAC;aAChD,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACrC,aAAa;aACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY;QAE7C,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,WAAW,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,WAAW;aACR,qBAAqB,CAAC,wBAAwB,CAAC;aAC/C,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACtC,aAAa;aACV,qBAAqB,CAAC,IAAI,CAAC,CAAE,WAAW;aACxC,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;aACzC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QAEhD,MAAM,OAAO,EAAE,CAAC;QAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,wBAAwB,EACxB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAClB,OAAO,CACR,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;QAC3C,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;YACrF,cAAc,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACjD,WAAW;iBACR,qBAAqB,CAAC,gBAAgB,CAAC;iBACvC,qBAAqB,CAAC,WAAW,CAAC,CAAC;YACtC,aAAa;iBACV,qBAAqB,CAAC,IAAI,CAAC,CAAE,wBAAwB;iBACrD,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;iBACzC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;YAEhD,MAAM,OAAO,EAAE,CAAC;YAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;YAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;YACnD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;YACpG,cAAc,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACjD,WAAW,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;YACpD,aAAa;iBACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,kCAAkC;iBAC/D,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY;YAE7C,MAAM,OAAO,EAAE,CAAC;YAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;YACrF,cAAc,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACjD,WAAW;iBACR,qBAAqB,CAAC,gBAAgB,CAAC;iBACvC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;YACnC,aAAa;iBACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;iBACzC,qBAAqB,CAAC,IAAI,CAAC,CAAE,wBAAwB;iBACrD,qBAAqB,CAAC,KAAK,CAAC,CAAC,eAAe;iBAC5C,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;YAEhD,MAAM,OAAO,EAAE,CAAC;YAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;YAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;YAChD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;YACpG,cAAc,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACjD,WAAW,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;YACpD,aAAa;iBACV,qBAAqB,CAAC,KAAK,CAAC,CAAC,YAAY;iBACzC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,kCAAkC;YAEnE,MAAM,OAAO,EAAE,CAAC;YAEhB,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validatePotaRef, formatPotaRef } from '../../src/validation/pota.js';
3
+ describe('validatePotaRef', () => {
4
+ it.each([
5
+ 'K-5033',
6
+ 'K-50331', // 5-digit park number
7
+ 'VE-5082',
8
+ 'G-0100',
9
+ 'DL-0001',
10
+ 'VE-5082@CA-AB', // with ISO 3166-2 location
11
+ 'K-1234@US-CO',
12
+ ])('accepts valid ref: %s', (ref) => {
13
+ expect(validatePotaRef(ref)).toBe(true);
14
+ });
15
+ it.each([
16
+ 'K5033', // missing hyphen
17
+ 'K-503', // too few digits
18
+ 'K-503333', // too many digits
19
+ 'TOOLONG-1234', // program code too long
20
+ '-1234', // empty program
21
+ '', // empty string
22
+ 'K-1234@', // trailing @ with no location
23
+ 'K-1234@X', // incomplete location code
24
+ ])('rejects invalid ref: %s', (ref) => {
25
+ expect(validatePotaRef(ref)).toBe(false);
26
+ });
27
+ it('trims whitespace before validating', () => {
28
+ expect(validatePotaRef(' K-5033 ')).toBe(true);
29
+ });
30
+ });
31
+ describe('formatPotaRef', () => {
32
+ it('uppercases the reference', () => {
33
+ expect(formatPotaRef('k-5033')).toBe('K-5033');
34
+ });
35
+ it('uppercases a reference with location', () => {
36
+ expect(formatPotaRef('ve-5082@ca-ab')).toBe('VE-5082@CA-AB');
37
+ });
38
+ it('trims surrounding whitespace', () => {
39
+ expect(formatPotaRef(' K-5033 ')).toBe('K-5033');
40
+ });
41
+ });
42
+ //# sourceMappingURL=pota.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pota.test.js","sourceRoot":"","sources":["../../../tests/validation/pota.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE9E,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,IAAI,CAAC;QACN,QAAQ;QACR,SAAS,EAAU,sBAAsB;QACzC,SAAS;QACT,QAAQ;QACR,SAAS;QACT,eAAe,EAAG,2BAA2B;QAC7C,cAAc;KACf,CAAC,CAAC,uBAAuB,EAAE,CAAC,GAAG,EAAE,EAAE;QAClC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,OAAO,EAAY,iBAAiB;QACpC,OAAO,EAAY,iBAAiB;QACpC,UAAU,EAAS,kBAAkB;QACrC,cAAc,EAAK,wBAAwB;QAC3C,OAAO,EAAY,gBAAgB;QACnC,EAAE,EAAiB,eAAe;QAClC,SAAS,EAAU,8BAA8B;QACjD,UAAU,EAAS,2BAA2B;KAC/C,CAAC,CAAC,yBAAyB,EAAE,CAAC,GAAG,EAAE,EAAE;QACpC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateSotaRef, formatSotaRef } from '../../src/validation/sota.js';
3
+ describe('validateSotaRef', () => {
4
+ it.each([
5
+ 'W2/WE-003',
6
+ 'G/LD-003',
7
+ 'VK2/HU-071',
8
+ 'HS0/CB-001',
9
+ 'W7/AZ-123',
10
+ 'ZL/WL-042',
11
+ ])('accepts valid ref: %s', (ref) => {
12
+ expect(validateSotaRef(ref)).toBe(true);
13
+ });
14
+ it.each([
15
+ 'W2WE-003', // missing slash
16
+ 'W2/WE003', // missing hyphen
17
+ 'W2/WE-03', // too few digits
18
+ 'W2/WE-1234', // too many digits
19
+ '/WE-003', // empty prefix
20
+ 'W2/TOOLONG-003', // area code too long
21
+ '', // empty string
22
+ 'W2/WE-', // missing number
23
+ ])('rejects invalid ref: %s', (ref) => {
24
+ expect(validateSotaRef(ref)).toBe(false);
25
+ });
26
+ it('trims whitespace before validating', () => {
27
+ expect(validateSotaRef(' W2/WE-003 ')).toBe(true);
28
+ });
29
+ });
30
+ describe('formatSotaRef', () => {
31
+ it('uppercases the reference', () => {
32
+ expect(formatSotaRef('w2/we-003')).toBe('W2/WE-003');
33
+ });
34
+ it('trims surrounding whitespace', () => {
35
+ expect(formatSotaRef(' W2/WE-003 ')).toBe('W2/WE-003');
36
+ });
37
+ });
38
+ //# sourceMappingURL=sota.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sota.test.js","sourceRoot":"","sources":["../../../tests/validation/sota.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE9E,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,IAAI,CAAC;QACN,WAAW;QACX,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,WAAW;KACZ,CAAC,CAAC,uBAAuB,EAAE,CAAC,GAAG,EAAE,EAAE;QAClC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,UAAU,EAAQ,gBAAgB;QAClC,UAAU,EAAQ,iBAAiB;QACnC,UAAU,EAAQ,iBAAiB;QACnC,YAAY,EAAM,kBAAkB;QACpC,SAAS,EAAS,eAAe;QACjC,gBAAgB,EAAE,qBAAqB;QACvC,EAAE,EAAgB,eAAe;QACjC,QAAQ,EAAU,iBAAiB;KACpC,CAAC,CAAC,yBAAyB,EAAE,CAAC,GAAG,EAAE,EAAE;QACpC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({
3
+ test: {
4
+ include: ['tests/**/*.test.ts'],
5
+ },
6
+ });
7
+ //# sourceMappingURL=vitest.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.js","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,oBAAoB,CAAC;KAChC;CACF,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "sota-adif-updater",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "A CLI utility to add SOTA and POTA references to an existing ADIF file",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "sota-adif-updater": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=22"
17
+ },
18
+ "scripts": {
19
+ "dev": "tsx src/index.ts",
20
+ "build": "tsc",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "lint": "eslint src tests",
24
+ "lint:fix": "eslint src tests --fix"
25
+ },
26
+ "dependencies": {
27
+ "@inquirer/prompts": "^7.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.0.0",
31
+ "eslint": "^9.0.0",
32
+ "tsx": "^4.0.0",
33
+ "typescript": "^5.0.0",
34
+ "typescript-eslint": "^8.0.0",
35
+ "vitest": "^2.0.0"
36
+ }
37
+ }