mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
fix(plugins): tolerate a UTF-8 BOM in trek-plugin.json
Windows editors love to prepend a BOM, and a bare JSON.parse then dies with an "Unexpected token" pointing at an invisible character - in the SDK CLIs (dev/validate/entry/submit) and, worse, server-side: a BOM in an author repo travels through pack into the artifact and fails discovery and registry install. Strip it at every manifest/JSON read (readJsonFile in the SDK, parseJsonText in the installer).
This commit is contained in:
@@ -14,6 +14,7 @@ import path from 'node:path';
|
||||
import http from 'node:http';
|
||||
import { createRequire } from 'node:module';
|
||||
import * as sdk from '../index.js';
|
||||
import { readJsonFile } from './json.js';
|
||||
|
||||
interface Fixtures {
|
||||
config?: Record<string, unknown>;
|
||||
@@ -123,7 +124,7 @@ export function installSdkInjection(): void {
|
||||
|
||||
function loadFixtures(dir: string): Fixtures {
|
||||
const p = path.join(dir, 'dev-fixtures.json');
|
||||
if (fs.existsSync(p)) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { console.warn('warning: dev-fixtures.json is not valid JSON — ignoring'); } }
|
||||
if (fs.existsSync(p)) { try { return readJsonFile<Fixtures>(p); } catch { console.warn('warning: dev-fixtures.json is not valid JSON — ignoring'); } }
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ export async function runDev(dir: string, opts: { port?: number } = {}): Promise
|
||||
const abs = path.resolve(dir);
|
||||
const manifestPath = path.join(abs, 'trek-plugin.json');
|
||||
if (!fs.existsSync(manifestPath)) throw new Error(`no trek-plugin.json in ${abs}`);
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;
|
||||
const manifest = readJsonFile<Record<string, unknown>>(manifestPath);
|
||||
const id = String(manifest.id);
|
||||
const grants = new Set(Array.isArray(manifest.permissions) ? (manifest.permissions as string[]) : []);
|
||||
const fx = loadFixtures(abs);
|
||||
|
||||
@@ -14,6 +14,7 @@ import path from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { loadPrivateKey, signArtifact, publicKeyBase64 } from './sign.js';
|
||||
import { readJsonFile } from './json.js';
|
||||
|
||||
interface Version {
|
||||
version: string; gitTag: string; commitSha: string; downloadUrl: string;
|
||||
@@ -47,7 +48,7 @@ export function buildEntry(opts: {
|
||||
/** Optional Ed25519 private-key file — signs the artifact and pins the author key. */
|
||||
signKeyPath?: string;
|
||||
}): Entry {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(opts.dir, 'trek-plugin.json'), 'utf8')) as Record<string, unknown>;
|
||||
const manifest = readJsonFile<Record<string, unknown>>(path.join(opts.dir, 'trek-plugin.json'));
|
||||
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(opts.repo)) throw new Error(`--repo must be "owner/name", got "${opts.repo}"`);
|
||||
const minTrek = minTrekFrom(manifest.trek);
|
||||
if (!minTrek) throw new Error('manifest has no "trek" version range to derive minTrekVersion from (e.g. "trek": ">=3.2.0 <4.0.0")');
|
||||
@@ -77,7 +78,7 @@ export function buildEntry(opts: {
|
||||
}
|
||||
|
||||
if (opts.mergePath) {
|
||||
const existing = JSON.parse(fs.readFileSync(opts.mergePath, 'utf8')) as Entry;
|
||||
const existing = readJsonFile<Entry>(opts.mergePath);
|
||||
if (authorPublicKey && existing.authorPublicKey && existing.authorPublicKey !== authorPublicKey) {
|
||||
throw new Error('this signing key differs from the one already published for this plugin — TREK would reject the update. Use the original key.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
/**
|
||||
* Read + parse a JSON file, tolerating a UTF-8 BOM (0xFEFF) — Windows editors
|
||||
* love to add one, and a bare JSON.parse then fails with a cryptic
|
||||
* "Unexpected token" that points at an invisible character.
|
||||
*/
|
||||
export function readJsonFile<T = unknown>(p: string): T {
|
||||
const text = fs.readFileSync(p, 'utf8');
|
||||
return JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text) as T;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readJsonFile } from './json.js';
|
||||
|
||||
const DEFAULT_REGISTRY = 'mauriceboe/TREK-Plugins';
|
||||
|
||||
@@ -69,7 +70,7 @@ export function submitEntry(entry: EntryLike, opts: { registry?: string; branch?
|
||||
let toWrite = entry;
|
||||
let action = 'add';
|
||||
if (fs.existsSync(abs)) {
|
||||
const existing = JSON.parse(fs.readFileSync(abs, 'utf8')) as EntryLike;
|
||||
const existing = readJsonFile<EntryLike>(abs);
|
||||
toWrite = mergeOnto(existing, entry);
|
||||
action = 'update';
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { preflight } from './preflight.js';
|
||||
import { submitEntry } from './submit.js';
|
||||
import { publishPlugin } from './publish.js';
|
||||
import { generateKeypair, loadPrivateKey, signArtifact, publicKeyBase64, defaultKeyPath } from './sign.js';
|
||||
import { readJsonFile } from './json.js';
|
||||
|
||||
const [cmd, ...args] = process.argv.slice(2);
|
||||
|
||||
@@ -124,7 +125,7 @@ async function main(): Promise<void> {
|
||||
}
|
||||
} else if (cmd === 'preflight') {
|
||||
const entry = flags.entry
|
||||
? JSON.parse(fs.readFileSync(flags.entry, 'utf8'))
|
||||
? readJsonFile<ReturnType<typeof buildEntry>>(flags.entry)
|
||||
: (flags.repo && flags.tag
|
||||
? buildEntry({ dir: pos[0] || '.', repo: flags.repo, tag: flags.tag, zipPath: flags.zip || 'plugin.zip', commit: flags.commit, signKeyPath: signKey(), now: new Date().toISOString() })
|
||||
: fail('preflight needs --repo <owner/name> --tag <vX>, or --entry <file.json>'));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { validateManifest } from '../manifest.js';
|
||||
import { readJsonFile } from './json.js';
|
||||
|
||||
export interface ValidateReport {
|
||||
ok: boolean;
|
||||
@@ -24,7 +25,7 @@ export function validatePluginDir(dir: string): ValidateReport {
|
||||
|
||||
let manifestId = '';
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
const raw = readJsonFile<Record<string, unknown>>(manifestPath);
|
||||
const res = validateManifest(raw);
|
||||
errors.push(...res.errors);
|
||||
manifestId = res.manifest?.id ?? String(raw.id ?? '');
|
||||
|
||||
Reference in New Issue
Block a user