From 06532364a438e48b3ca5c5696a72bc2cb6c31d85 Mon Sep 17 00:00:00 2001 From: Kento Ikeda Date: Thu, 5 Nov 2026 14:50:09 +0900 Subject: [PATCH] fix(setup): escape metadata in generated package files --- .github/workflows/ci-setup.yml | 30 +++++++++++++++++++++++++----- python/setup_template.py | 4 ++++ typescript/setup-template.ts | 8 +++++++- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-setup.yml b/.github/workflows/ci-setup.yml index 50159f3..9b34954 100644 --- a/.github/workflows/ci-setup.yml +++ b/.github/workflows/ci-setup.yml @@ -35,6 +35,9 @@ jobs: # subset: tool + plugin only, keep the monorepo layout. variant: [hoist-all, subset] fail-fast: false + env: + SETUP_AUTHOR: 'Example "Author" \ Contributor' + SETUP_DESCRIPTION: 'Tools for "search" in C:\work — 日本語 🧪' steps: - name: Checkout code @@ -48,14 +51,13 @@ jobs: python-version: '3.10' - name: Install hatch - run: pip install hatch pyyaml + run: pip install hatch pyyaml tomli - name: Run setup script (hoist-all) if: matrix.variant == 'hoist-all' working-directory: python - # Answers: package name, components, author, email, github user, - # description (defaults), confirm, hoist. - run: printf 'acme\n1,2,3,4,5,6,7\n\n\n\n\ny\ny\n' | python setup_template.py + # Exercise quotes, backslashes, and Unicode in free-form metadata. + run: printf '%s\n' acme '1,2,3,4,5,6,7' "$SETUP_AUTHOR" '' '' "$SETUP_DESCRIPTION" y y | python setup_template.py - name: Run setup script (subset) if: matrix.variant == 'subset' @@ -71,6 +73,14 @@ jobs: test -f .github/workflows/ci.yml test -f .github/workflows/publish.yml python -c "import yaml,glob; [yaml.safe_load(open(f)) for f in glob.glob('.github/workflows/*.yml') + ['.github/dependabot.yml']]" + python - <<'PY' + import os + import tomli + with open("pyproject.toml", "rb") as file: + project = tomli.load(file)["project"] + assert project["description"] == os.environ["SETUP_DESCRIPTION"] + assert project["authors"][0]["name"] == os.environ["SETUP_AUTHOR"] + PY hatch run prepare - name: Run checks on generated project (subset) @@ -89,6 +99,9 @@ jobs: matrix: variant: [hoist-all, subset] fail-fast: false + env: + SETUP_AUTHOR: 'Example "Author" \ Contributor' + SETUP_DESCRIPTION: 'Tools for "search" in C:\work — 日本語 🧪' steps: - name: Checkout code @@ -108,7 +121,7 @@ jobs: - name: Run setup script (hoist-all) if: matrix.variant == 'hoist-all' working-directory: typescript - run: printf 'acme\n1,2,3,4,5,6,7\n\n\n\n\ny\ny\n' | npx tsx setup-template.ts + run: printf '%s\n' acme '1,2,3,4,5,6,7' "$SETUP_AUTHOR" '' '' "$SETUP_DESCRIPTION" y y | npx tsx setup-template.ts - name: Run setup script (subset) if: matrix.variant == 'subset' @@ -125,6 +138,13 @@ jobs: test -f .github/workflows/publish.yml python3 -m pip install --quiet pyyaml python3 -c "import yaml,glob; [yaml.safe_load(open(f)) for f in glob.glob('.github/workflows/*.yml') + ['.github/dependabot.yml']]" + node --input-type=module <<'JS' + import assert from 'node:assert/strict' + import fs from 'node:fs' + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')) + assert.equal(pkg.description, process.env.SETUP_DESCRIPTION) + assert.equal(pkg.author, `${process.env.SETUP_AUTHOR} `) + JS npm install npm run check npm run build diff --git a/python/setup_template.py b/python/setup_template.py index 65dbadc..0532962 100644 --- a/python/setup_template.py +++ b/python/setup_template.py @@ -15,6 +15,7 @@ python setup_template.py """ +import json import os import re import shutil @@ -132,6 +133,9 @@ def replace_in_file(filepath: str, replacements: dict[str, str]) -> None: content = f.read() for old, new in replacements.items(): + if filepath == "pyproject.toml": + # TOML basic strings need JSON escapes and an explicit escape for DEL. + new = json.dumps(new, ensure_ascii=False)[1:-1].replace("\x7f", "\\u007f") content = content.replace(old, new) with open(filepath, "w", encoding="utf-8") as f: diff --git a/typescript/setup-template.ts b/typescript/setup-template.ts index 64bc7f2..7c79bf0 100644 --- a/typescript/setup-template.ts +++ b/typescript/setup-template.ts @@ -146,7 +146,13 @@ function applyReplacements(value: string, replacements: Record): function replaceInFile(filepath: string, replacements: Record): void { const content = fs.readFileSync(filepath, 'utf8') - fs.writeFileSync(filepath, applyReplacements(content, replacements), 'utf8') + const fileReplacements = + filepath === 'package.json' + ? Object.fromEntries( + Object.entries(replacements).map(([key, value]) => [key, JSON.stringify(value).slice(1, -1)]) + ) + : replacements + fs.writeFileSync(filepath, applyReplacements(content, fileReplacements), 'utf8') } // ---------------------------------------------------------------------------