@tak-ps/node-cot
Version:
Lightweight JavaScript library for parsing and manipulating TAK messages
175 lines • 5.64 kB
JavaScript
import fs from 'node:fs/promises';
import path from 'node:path';
import { CoTParser, DataPackage } from './index.js';
function usage() {
return [
'Usage:',
' ./cli.ts package validate <file>.zip',
' ./cli.ts feature convert <input.geojson|input.cot>'
].join('\n');
}
function getErrorMessage(err) {
if (err instanceof Error)
return err.message;
return String(err);
}
async function withoutConsoleWarn(fn) {
const warn = console.warn;
console.warn = () => undefined;
try {
return await fn();
}
finally {
console.warn = warn;
}
}
function extractUID(raw) {
const match = raw.match(/<event\b[^>]*\buid=(['"])([^'"]+)\1/i);
return match?.[2];
}
function isJSONInput(inputPath, raw) {
const ext = path.extname(inputPath).toLowerCase();
if (ext === '.geojson' || ext === '.json')
return true;
return raw.trimStart().startsWith('{');
}
function normalizeGeoJSON(input) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new Error('GeoJSON input must be an object');
}
const geojson = input;
if (geojson.type === 'Feature') {
return geojson;
}
if (geojson.type === 'FeatureCollection') {
const features = geojson.features;
if (!Array.isArray(features) || features.length !== 1) {
throw new Error('FeatureCollection input must contain exactly one feature');
}
const [feature] = features;
if (!feature || typeof feature !== 'object' || Array.isArray(feature)) {
throw new Error('FeatureCollection contains an invalid feature');
}
return feature;
}
throw new Error('GeoJSON input must be a Feature or a single-feature FeatureCollection');
}
async function validatePackage(inputPath) {
const resolved = path.resolve(inputPath);
let pkg;
try {
pkg = await withoutConsoleWarn(async () => {
return await DataPackage.parse(resolved, {
cleanup: false,
strict: true
});
});
const issues = [];
for (const content of pkg.contents) {
const entry = content._attributes.zipEntry;
const buffer = await pkg.getFileBuffer(entry);
if (path.extname(entry).toLowerCase() !== '.cot')
continue;
const raw = buffer.toString('utf8');
try {
await withoutConsoleWarn(() => CoTParser.from_xml(raw));
}
catch (err) {
issues.push({
entry,
uid: extractUID(raw),
error: getErrorMessage(err)
});
}
}
if (issues.length) {
console.error(JSON.stringify({
valid: false,
package: resolved,
errors: issues
}, null, 4));
process.exitCode = 1;
return;
}
const cots = await pkg.cots({
respectIgnore: false,
parseAttachments: true
});
const attachments = await pkg.attachments({
respectIgnore: false
});
const files = await pkg.files({
respectIgnore: false
});
const attachmentCount = Array.from(attachments.values())
.reduce((sum, entries) => sum + entries.length, 0);
console.log(JSON.stringify({
valid: true,
package: resolved,
manifest: pkg.settings,
counts: {
contents: pkg.contents.length,
cots: cots.length,
attachments: attachmentCount,
files: files.size
}
}, null, 4));
}
finally {
if (pkg) {
await pkg.destroy();
}
}
}
async function convertFeature(inputPath) {
const resolved = path.resolve(inputPath);
const raw = await fs.readFile(resolved, 'utf8');
if (isJSONInput(resolved, raw)) {
const feature = normalizeGeoJSON(JSON.parse(raw));
const cot = await withoutConsoleWarn(async () => {
return await CoTParser.from_geojson(feature);
});
const xml = CoTParser.to_xml(cot);
await withoutConsoleWarn(() => CoTParser.from_xml(xml));
process.stdout.write(`${xml}\n`);
return;
}
const cot = await withoutConsoleWarn(() => CoTParser.from_xml(raw));
const feature = await CoTParser.to_geojson(cot);
process.stdout.write(`${JSON.stringify(feature, null, 4)}\n`);
}
const commands = {
'package validate': async (args) => {
if (args.length !== 1) {
throw new Error('package validate requires a single .zip file path');
}
await validatePackage(args[0]);
},
'feature convert': async (args) => {
if (args.length !== 1) {
throw new Error('feature convert requires a single input path');
}
await convertFeature(args[0]);
}
};
try {
const args = process.argv.slice(2);
if (!args.length || args.includes('--help') || args.includes('-h')) {
console.log(usage());
process.exit(0);
}
const commandKey = args.slice(0, 2).join(' ');
const handler = commands[commandKey];
if (!handler) {
throw new Error(`Unknown command: ${args.join(' ')}`);
}
await handler(args.slice(2));
}
catch (err) {
console.error(getErrorMessage(err));
console.error('');
console.error(usage());
process.exit(1);
}
//# sourceMappingURL=cli.js.map