diff --git a/build/utils.mts b/build/utils.mts index 4cbe1ccc..77d05512 100644 --- a/build/utils.mts +++ b/build/utils.mts @@ -38,7 +38,17 @@ export const exec = catchWrap(execute, 99); export const quote = (paths: string | string[]) => [paths] .flat() - .map((path) => `'${path.replaceAll("'", "'\\''")}'`) + .map((path) => { + // Quoting settles what the shell does with a name and nothing about + // what the tool then makes of it: `'--write.md'` arrives at prettier as + // `--write.md`, which it reads as an option. It answered that one by + // printing an error and exiting 0 -- a check that passed having checked + // nothing. A leading `./` says the argument is a path and costs a + // relative name two characters. + const safe = path.startsWith('-') ? `./${path}` : path; + + return `'${safe.replaceAll("'", "'\\''")}'`; + }) .join(' '); /** diff --git a/build/utils.test.mts b/build/utils.test.mts index 8c674191..7752cee2 100644 --- a/build/utils.test.mts +++ b/build/utils.test.mts @@ -140,6 +140,17 @@ describe('quote', () => { deepStrictEqual(quote("it's.md"), "'it'\\''s.md'"); }); + test('keeps a name that looks like an option from being read as one', () => { + // Quoting alone leaves `--write.md` arriving at the tool as `--write.md`. + deepStrictEqual(quote('--write.md'), "'./--write.md'"); + deepStrictEqual(quote('-'), "'./-'"); + }); + + test('leaves an ordinary path alone', () => { + deepStrictEqual(quote('doc/adr/0001-a.md'), "'doc/adr/0001-a.md'"); + deepStrictEqual(quote('/abs/path.jar'), "'/abs/path.jar'"); + }); + test('survives a round trip through the shell it is written for', async () => { // The escaping is only worth anything if the shell `exec` uses agrees // with it, so this asks that shell rather than a model of it.