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.
- package/LICENSE +121 -0
- package/README.md +28 -0
- package/dist/src/adif/parser.d.ts +2 -0
- package/dist/src/adif/parser.js +57 -0
- package/dist/src/adif/parser.js.map +1 -0
- package/dist/src/adif/types.d.ts +11 -0
- package/dist/src/adif/types.js +2 -0
- package/dist/src/adif/types.js.map +1 -0
- package/dist/src/adif/writer.d.ts +3 -0
- package/dist/src/adif/writer.js +31 -0
- package/dist/src/adif/writer.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/prompts/flow.d.ts +1 -0
- package/dist/src/prompts/flow.js +148 -0
- package/dist/src/prompts/flow.js.map +1 -0
- package/dist/src/validation/pota.d.ts +2 -0
- package/dist/src/validation/pota.js +9 -0
- package/dist/src/validation/pota.js.map +1 -0
- package/dist/src/validation/sota.d.ts +2 -0
- package/dist/src/validation/sota.js +9 -0
- package/dist/src/validation/sota.js.map +1 -0
- package/dist/tests/adif/parser.test.d.ts +1 -0
- package/dist/tests/adif/parser.test.js +64 -0
- package/dist/tests/adif/parser.test.js.map +1 -0
- package/dist/tests/adif/writer.test.d.ts +1 -0
- package/dist/tests/adif/writer.test.js +112 -0
- package/dist/tests/adif/writer.test.js.map +1 -0
- package/dist/tests/prompts/flow.test.d.ts +1 -0
- package/dist/tests/prompts/flow.test.js +196 -0
- package/dist/tests/prompts/flow.test.js.map +1 -0
- package/dist/tests/validation/pota.test.d.ts +1 -0
- package/dist/tests/validation/pota.test.js +42 -0
- package/dist/tests/validation/pota.test.js.map +1 -0
- package/dist/tests/validation/sota.test.d.ts +1 -0
- package/dist/tests/validation/sota.test.js +38 -0
- package/dist/tests/validation/sota.test.js.map +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +7 -0
- package/dist/vitest.config.js.map +1 -0
- 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,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":"AAEA,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,aAAa,CAAC;IAC/B,IAAI,KAA6B,CAAC;IAElC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,SAAS,CAAC,kDAAkD;QAEjF,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEhD,IAAI,KAAK,CAAC,MAAM,CAAC;YAAE,SAAS;QAE5B,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7B,QAAQ,CAAC,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,SAAS,CAAC;IAC3B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAA6B,CAAC;IAElC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5C,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAExC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO;YACL,UAAU,EAAE,EAAE;YACd,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC;SAC/B,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC;IAChC,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAE1D,OAAO;QACL,UAAU;QACV,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/adif/types.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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,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,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":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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 @@
|
|
|
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
|
+
}
|