From ec5835ca777cf262619118647d50f93daa0b5917 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 21 Sep 2026 21:40:00 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E7=A8=8B=E9=AA=A8=E6=9E=B6=E3=80=81=E8=AE=B8=E5=8F=AF=E4=B8=8E?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 建立仓库目录结构、许可证与贡献约定,确认前后端技术选型。 --- .claude/skills/write-e2e-test/SKILL.md | 33 ++++ .gitignore | 81 ++++++++++ CONTRIBUTING.md | 88 +++++++++++ DCO | 37 +++++ LICENSE | 202 +++++++++++++++++++++++++ NOTICE | 8 + llms.txt | 49 ++++++ relicense_headers.py | 169 +++++++++++++++++++++ tsconfig.json | 5 + 9 files changed, 672 insertions(+) create mode 100644 .claude/skills/write-e2e-test/SKILL.md create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 DCO create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 llms.txt create mode 100644 relicense_headers.py create mode 100644 tsconfig.json diff --git a/.claude/skills/write-e2e-test/SKILL.md b/.claude/skills/write-e2e-test/SKILL.md new file mode 100644 index 0000000..8285c40 --- /dev/null +++ b/.claude/skills/write-e2e-test/SKILL.md @@ -0,0 +1,33 @@ +--- +name: write-e2e-test +description: Write or change a ReplyPier Playwright end-to-end test (e2e/). Use when adding e2e coverage for a feature, fixing a failing e2e spec, or adding a page object, fixture or fake-LLM script. +--- + +# Writing a ReplyPier e2e test + +The suite lives in `e2e/`. Read `e2e/AUTHORING.md` first — it is the source of truth for +layout and rules. The essentials: + +1. **Specs speak product language.** `specs//.spec.ts`, imports only from + `@e2e`, no selectors (`page.locator`) and no raw HTTP — lint fails otherwise. Title ends + with tags: `@`, plus `@smoke` only for the few journeys that must run on every PR. +2. **Selectors go in `src/pages/` (a screen) or `src/components/` (shared UI).** Prefer + `getByRole`/`getByLabel`; if the element has no accessible name, add an `aria-label` to + the Vue component rather than reaching for CSS. CSS needs a one-line "why" comment. +3. **Seed through `src/api/`, assert through the UI.** Need a new endpoint? Add a function to + the matching `src/api/*.ts` module. +4. **Script the AI with `llm.script(agent, s => …)`** — `reply`, `handOver`, `createTicket`, + `callTool(...).thenReply(...)`. Replies are structured ChatResponse JSON; handover and + tickets are boolean fields, not tool calls. +5. **Fixtures give each test its own `agent` + `widget`.** `test.use({ agentSettings: { transferToHuman: true } })` + for handover. `inbox` is Sam Agent's dashboard in a second browser. Never change org-wide + settings outside a `*.org.spec.ts`. +6. **Prove it is stable** before finishing: + `cd e2e && npx playwright test specs/ --repeat-each=10 && npm run lint && npm run typecheck` + (start the stack once with `./scripts/stack.sh up`; in a Claude worktree pass + `E2E_PYTHON` and `E2E_FRONTEND_NODE_MODULES` pointing at the main checkout, and run with + the sandbox disabled — Postgres, ports and the node_modules symlink need it). +7. **Update `e2e/FEATURES.md`** — the row for the journey, with the spec path and ✅. + +Enterprise specs live in the private `enterprise_frontend` repo under `e2e/`, import only +from `@e2e`, and must never be added to this public repo. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80904d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,81 @@ +static/ +.env +*.ipynb +*.key +logs/ +__pycache__/ +.DS_Store +# Python virtualenvs. Only .venv/ was listed, so backend/venv — 2GB of +# site-packages — was one `git add -A` away from being committed. The glob +# covers the "-old"/"-py313" copies that pile up around a version bump. +.venv/ +venv/ +venv-*/ +.idea/ +.vscode/ +.pytest_cache/ +node_modules/ +uploads/ +*.key +*.pem +*.crt +*.csr +*.csr.pem +*.csr.key +*.csr.crt +/shopify/ + +# WordPress plugin source lives in its own repo (replypier-wordpress-plugin); +# only the built, installable zip is tracked here for manual download. +/wordpress/* +!/wordpress/replypier-chat.zip + +# Firebase service account key (contains sensitive credentials) +replypier-13e0a-firebase-adminsdk-7dqo6-57f3de88cb.json + +test.db +*.db +*.db.zip +*.db.tar.gz +*.db.tar.bz2 +*.db.tar.xz +*.db.tar.lzma + +docs/ +.env.prod +.env.dev +backend/app/enterprise/ +frontend/src/modules/ + +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +.claude/settings.local.json + +# Hallmark design skill local logs +.hallmark/ + +# Local tooling and scratch files that have been accidentally staged before +backend/.claude/ +backend/*.log +backend/AWSCLIV2.pkg +backend/scripts/repro_*.py +build-and-push.sh +config/ + +# Env backups — these hold real credentials, never commit them +*.env.bak* +.env.*.bak* +.env.prod.bak* +frontend/.env.*.bak* + +# Stale loader copies — server.js serves the current build from frontend/public/webclient +replypier-test/replypier.min.js + +# Only the :dev webclient build emits a sourcemap, and the committed loader is +# the :prod one, which does not reference it. Keeps a stray dev artifact out. +frontend/public/webclient/*.map + +# Local deployment runtime files and generated test credentials +/deploy/local/.state/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d67ae5b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing to ReplyPier + +Thanks for your interest in contributing! This document explains how to get +your changes accepted. + +## License + +The ReplyPier open-source core is licensed under the +[Apache License 2.0](LICENSE). By contributing, you agree that your +contributions will be licensed under Apache-2.0. + +## Developer Certificate of Origin (DCO) + +We use the [Developer Certificate of Origin](DCO) (DCO 1.1) instead of a CLA. +It is a lightweight way for you to certify that you wrote, or otherwise have +the right to submit, the code you are contributing. + +**Every commit must be signed off.** Add a `Signed-off-by` trailer to each +commit message with: + +```bash +git commit -s -m "your message" +``` + +This appends a line like: + +``` +Signed-off-by: Your Name +``` + +The name and email must match your `git config user.name` and +`user.email`, and must be your real identity (no anonymous or pseudonymous +contributions). + +The [DCO app](https://github.com/apps/dco) runs on every pull request and +verifies that all commits are signed off. PRs with unsigned commits will fail +the check. + +### Fixing commits that are not signed off + +- **Last commit:** `git commit --amend -s --no-edit` +- **A whole branch:** `git rebase --signoff main` + +Then force-push your branch (`git push --force-with-lease`). + +### Tip: never forget `-s` + +You can make sign-off the default for this repo with a git hook: + +```bash +cat > .git/hooks/prepare-commit-msg <<'EOF' +#!/bin/sh +NAME=$(git config user.name) +EMAIL=$(git config user.email) +grep -qs "^Signed-off-by:" "$1" || printf "\nSigned-off-by: %s <%s>\n" "$NAME" "$EMAIL" >> "$1" +EOF +chmod +x .git/hooks/prepare-commit-msg +``` + +## Development workflow + +1. Fork the repository and create a feature branch: + `git checkout -b feature/amazing-feature` +2. Make your changes. Follow the existing code style + (PEP 8 for Python, type hints where practical; the project conventions for + Vue/TypeScript on the frontend). +3. Add or update tests for your change and make sure the suite passes. If it + changes something a user does, add or update the end-to-end spec too (see + [e2e/AUTHORING.md](e2e/AUTHORING.md)). +4. Commit with sign-off (`git commit -s`). +5. Push and open a Pull Request, filling out the PR template. + +## License headers + +New source files should carry the standard Apache-2.0 header. You can apply or +refresh headers across the codebase by running: + +```bash +python relicense_headers.py +``` + +This is idempotent — it only changes files whose header is missing or outdated. + +## Enterprise features + +Advanced/enterprise capabilities live in separate, proprietary modules and are +**not** covered by the Apache-2.0 license. Contributions in this repository +apply to the open-source core only. diff --git a/DCO b/DCO new file mode 100644 index 0000000..8201f99 --- /dev/null +++ b/DCO @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..3879455 --- /dev/null +++ b/NOTICE @@ -0,0 +1,8 @@ +ReplyPier +Copyright 2024-2026 ReplyPier + +This product includes software developed by ReplyPier +(https://replypier.site). + +Licensed under the Apache License, Version 2.0. See the LICENSE file for +the full license text. diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..dd5d479 --- /dev/null +++ b/llms.txt @@ -0,0 +1,49 @@ +# ReplyPier + +> ReplyPier is an open-source AI customer-support chatbot platform. You can sign up, create AI +> agents, add knowledge sources, and embed a chat widget — all from a browser, the terminal (CLI), +> or programmatically via an AI agent over MCP. + +## Start here (AI agents & automation) + +Use the **ReplyPier CLI** (the `replypier` command, installed with `pip install replypier-cli`) +to sign up and manage everything from the terminal. It is the account/config CLI — distinct from the +self-host Docker tool (`npm install -g replypier-deploy`). + +Minimal end-to-end flow: + +```bash +pip install replypier-cli +replypier signup --name "Acme Inc" --domain acme.com --admin-email you@acme.com --admin-name "You" +replypier agent create --name "Support" --type customer_support -i "Be concise" --json +# Add knowledge from a URL or a local PDF: +replypier knowledge add-url --website https://docs.acme.com --agent-id +replypier knowledge add-file ./guide.pdf --agent-id +# Create a widget to get the embed id: +replypier widget create --agent-id --name "Website widget" +# Embed on your site: +# +# +``` + +## Docs + +- [AI Agent & Automation Quickstart](https://docs.replypier.site/features/agents-quickstart): copy-paste path from signup to a live widget, built for AI agents and CI. +- [CLI reference](https://docs.replypier.site/features/cli): the account CLI — signup, login, tokens, agents, workflows, knowledge. +- [MCP Server](https://docs.replypier.site/features/mcp-server): let an AI agent configure ReplyPier over the Model Context Protocol (`replypier-mcp`). +- [Quickstart](https://docs.replypier.site/quickstart): hosted vs self-host, side by side. +- [Widget integration](https://docs.replypier.site/features/widget): embed the chat widget on any site. +- [Widget Apps & API keys](https://docs.replypier.site/features/widget-apps): authenticated widgets and programmatic tokens. + +## Key URLs + +- Sign up / dashboard: https://app.replypier.site +- Hosted API base: https://api.replypier.site (routes under `/api/v1`) +- Widget script: https://app.replypier.site/webclient/replypier.min.js +- Documentation: https://docs.replypier.site +- GitHub: https://github.com/replypier/replypier.site + +## Self-hosting (optional) + +- [Self-host with Docker](https://docs.replypier.site/quickstart#option-b-self-host-with-docker): run the full stack on your own infrastructure with the self-host CLI (`npm install -g replypier-deploy`, command `replypier-deploy init/start/stop`). +- [Docker deployment](https://docs.replypier.site/deployment/docker) diff --git a/relicense_headers.py b/relicense_headers.py new file mode 100644 index 0000000..01c2837 --- /dev/null +++ b/relicense_headers.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +""" +Relicense source-file headers to Apache-2.0. + +Replaces the old AGPL-3.0 per-file headers (and any previously-inserted +Apache header) with the Apache-2.0 short header, in the correct comment +syntax for each file type. Idempotent: running it twice produces no diff. + +Covers .py / .vue / .ts / .tsx under backend/ and frontend/src/, skipping +node_modules, virtualenvs, build output, and the proprietary enterprise +submodules (which are licensed separately). + +Usage: python relicense_headers.py +""" + +import os +import re +import sys +from typing import List, Tuple + +# --- Apache-2.0 short header ------------------------------------------------ + +COPYRIGHT = "Copyright 2024-2026 ReplyPier" + +BODY = f"""{COPYRIGHT} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.""" + +HEADERS = { + "py": '"""\n' + BODY + '\n"""\n\n', + "vue": "\n\n", + "ts": "/*\n" + BODY + "\n*/\n\n", +} + +# Any leading comment/docstring containing one of these markers is treated as +# an existing license block and replaced (covers relicensing + idempotency). +LICENSE_MARKERS = ( + "GNU Affero General Public License", + "Licensed under the Apache License", +) + +# --- File discovery --------------------------------------------------------- + +ROOTS = ["backend", "frontend/src"] + +EXCLUDE_DIRS = { + "node_modules", "venv", ".venv", "env", "__pycache__", "dist", "build", + "coverage_html", "htmlcov", ".git", "temp", "uploads", "logs", + ".mypy_cache", ".pytest_cache", +} + +# Proprietary enterprise submodules — licensed separately, never touched here. +EXCLUDE_PREFIXES = ( + os.path.join("backend", "app", "enterprise"), + os.path.join("frontend", "src", "modules", "enterprise"), +) + +EXT_STYLE = {".py": "py", ".vue": "vue", ".ts": "ts", ".tsx": "ts"} + + +def find_files() -> List[str]: + found = [] + for root in ROOTS: + if not os.path.isdir(root): + continue + for dirpath, dirnames, filenames in os.walk(root): + dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS] + rel = os.path.relpath(dirpath) + if rel.startswith(EXCLUDE_PREFIXES): + dirnames[:] = [] + continue + for name in filenames: + if os.path.splitext(name)[1] in EXT_STYLE: + found.append(os.path.join(dirpath, name)) + return found + + +# --- Header manipulation ---------------------------------------------------- + +def split_py_preamble(content: str) -> Tuple[str, str]: + """Peel off a shebang and/or PEP-263 coding declaration to preserve them.""" + prefix, rest = "", content + if rest.startswith("#!"): + nl = rest.find("\n") + if nl == -1: + return rest, "" + prefix, rest = rest[: nl + 1], rest[nl + 1:] + m = re.match(r"#.*coding[:=]\s*[-\w.]+.*\n", rest) + if m: + prefix, rest = prefix + rest[: m.end()], rest[m.end():] + return prefix, rest + + +def is_license_block(block: str) -> bool: + return any(marker in block for marker in LICENSE_MARKERS) + + +def strip_leading_license(rest: str, style: str) -> str: + """Remove a leading license comment/docstring if present; otherwise keep.""" + stripped = rest.lstrip("\n \t\r") + if style == "py": + for q in ('"""', "'''"): + if stripped.startswith(q): + end = stripped.find(q, len(q)) + if end != -1 and is_license_block(stripped[: end + len(q)]): + return stripped[end + len(q):].lstrip("\n") + return rest + elif style == "vue": + if stripped.startswith("") + if end != -1 and is_license_block(stripped[: end + 3]): + return stripped[end + 3:].lstrip("\n") + elif style == "ts": + if stripped.startswith("/*"): + end = stripped.find("*/") + if end != -1 and is_license_block(stripped[: end + 2]): + return stripped[end + 2:].lstrip("\n") + return rest + + +def relicense(path: str) -> bool: + style = EXT_STYLE[os.path.splitext(path)[1]] + with open(path, "r", encoding="utf-8") as f: + original = f.read() + + if style == "py": + prefix, rest = split_py_preamble(original) + else: + prefix, rest = "", original + + body = strip_leading_license(rest, style) + new_content = prefix + HEADERS[style] + body + + if new_content == original: + return False + with open(path, "w", encoding="utf-8") as f: + f.write(new_content) + return True + + +def main() -> None: + files = find_files() + changed = 0 + by_ext = {} + for path in files: + try: + if relicense(path): + changed += 1 + ext = os.path.splitext(path)[1] + by_ext[ext] = by_ext.get(ext, 0) + 1 + except Exception as e: # noqa: BLE001 + print(f"Error processing {path}: {e}", file=sys.stderr) + print(f"Scanned {len(files)} files, updated {changed}.") + for ext, n in sorted(by_ext.items()): + print(f" {ext}: {n}") + + +if __name__ == "__main__": + main() diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fe52093 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "composite": true + } +}