diff --git a/src/commands/pack.ts b/src/commands/pack.ts index 66fb92a..10961dc 100644 --- a/src/commands/pack.ts +++ b/src/commands/pack.ts @@ -2,7 +2,7 @@ import { Manifest } from "@elgato/schemas/streamdeck/plugins"; import { ZipWriter } from "@zip.js/zip.js"; import chalk from "chalk"; import { createReadStream, createWriteStream, existsSync, writeFileSync } from "node:fs"; -import { readFile, rm } from "node:fs/promises"; +import { rm } from "node:fs/promises"; import { basename, dirname, join, resolve } from "node:path"; import { Readable, Writable } from "node:stream"; import type { ReadableStream } from "node:stream/web"; @@ -142,10 +142,11 @@ async function getPackageContents( fileFn?: (file: FileInfo, stream?: ReadableStream) => Promise | void, ): Promise { // Get the manifest, and generate the base contents. - const manifest = await readJsonFile(join(path, "manifest.json")); + const manifestPath = join(path, "manifest.json"); + const manifest = await readJsonFile(manifestPath); const contents: PackageInfo = { files: [], - manifest, + manifest: manifest.value, size: 0, sizePad: 0, }; @@ -158,8 +159,8 @@ async function getPackageContents( if (fileFn) { // When the entry is the manifest, remove the `Nodejs.Debug` flag. if (file.path.relative === "manifest.json") { - delete manifest.Nodejs?.Debug; - const sanitizedManifest = JSON.stringify(manifest, undefined, "".repeat(4)); + delete manifest.value.Nodejs?.Debug; + const sanitizedManifest = manifest.stringify(); const stream = new Readable(); stream.push(sanitizedManifest, "utf-8"); @@ -189,13 +190,13 @@ async function version(path: string, version: string | null): Promise; + const manifest = await readJsonFile(manifestPath); // Ensure the version in the manifest has the correct number of segments, [{major}.{minor}.{patch}.{build}] - version ??= manifest.Version?.toString() || ""; - manifest.Version = `${version}${".0".repeat(Math.max(0, 4 - version.split(".").length))}`; - write(JSON.stringify(manifest, undefined, "\t")); + version ??= manifest.value.Version?.toString() || ""; + manifest.value.Version = `${version}${".0".repeat(Math.max(0, 4 - version.split(".").length))}`; + + write(manifest.stringify()); } return { diff --git a/src/system/fs.ts b/src/system/fs.ts index c6a6162..c631f24 100644 --- a/src/system/fs.ts +++ b/src/system/fs.ts @@ -165,19 +165,55 @@ export async function mkdirIfNotExists(path: string): Promise { * @param path Path to the JSON file. * @returns Contents parsed as JSON. */ -export async function readJsonFile(path: string): Promise { +export async function readJsonFile(path: string): Promise> { if (!existsSync(path)) { throw new Error(`JSON file not found, ${path}`); } try { const contents = await readFile(path, { encoding: "utf-8" }); - return JSON.parse(contents); + const value = JSON.parse(contents) as T; + + return { + value, + stringify(): string { + // Detect the original line ending style (CRLF or LF) + const lineEnding = contents.includes("\r\n") ? "\r\n" : "\n"; + + // Detect the original indentation style (tabs or spaces) + const indentMatch = contents.match(/^[\t ]+/m); + const indent = indentMatch?.[0] ?? "\t"; + + let stringified = JSON.stringify(value, undefined, indent); + + // Preserve original line endings + if (lineEnding === "\r\n") { + stringified = stringified.replace(/(? = { + /** + * The parsed value of the JSON file. + */ + readonly value: T; + /** + * Stringifies the JSON value while preserving original formatting. + * @returns The stringified JSON. + */ + stringify(): string; +}; + /** * Defines how a path will be relocated. */