diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..1d159d6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Every path is owned by the maintainer, so each pull request — including the ones +# Dependabot opens — automatically requests a review rather than sitting unnoticed. +# +# This is the GitHub account handle, which is unchanged. The publisher's DISPLAY +# name is WinterNova5; every id, URL, and handle still reads ElecTreeFrying, and +# that is correct — do not "fix" it here. +* @ElecTreeFrying diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8a8686f --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,130 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +**electreefrying.git@gmail.com**. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla]. + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + +[homepage]: https://www.contributor-covenant.org +[mozilla]: https://github.com/mozilla/diversity diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..5b16c2f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing + +Thanks for wanting to help. Bug reports and feature requests are very welcome — but +please read the next section before you open a pull request, because this repository +works differently from most snippet extensions. + +## ⚠️ The snippets are generated, and the generator is not in this repository + +Every file under `snippets/`, all of `reference.md`, and the README's `## Snippets` +section are **build artifacts**. They are produced from the `@nativescript/core` +TypeScript types by a generator under `tools/` — and `tools/` is deliberately not +committed, so a fresh clone does not contain it. + +Two consequences, and they are the whole reason this section is first: + +1. **Hand-editing a snippet file is futile.** The next generator run overwrites it + wholesale. A PR that edits `snippets/**/*.json`, `reference.md`, or the README's + `## Snippets` block cannot be merged as-is, no matter how correct the change is. +2. **You cannot regenerate them yourself.** `npm run generate`, `npm run validate`, + and `npm run check` all live in `tools/`, so they will not run from a clone. + +**So: to change a snippet, open an issue instead of a PR.** Describe the element, the +flavor(s), and what the expansion should be. That is genuinely the fastest path — the +change gets made in the generator's curated data and regenerated, which is the only +way it will survive. + +This is not a brush-off. Snippet issues are the most useful reports this project gets. + +## What you *can* edit directly + +These are hand-maintained and normal PRs are welcome: + +- `README.md` — **everything except** the `## Snippets` section +- [`SPEC.md`](../SPEC.md) — the user-facing contract +- [`SUPPORT.md`](../SUPPORT.md) +- `CHANGELOG.md`, `LICENSE.md` +- `package.json` — metadata, categories, keywords +- Anything under `.github/` + +## What CI checks + +There is no build and no test framework — the published artifact is `package.json` +plus declarative JSON, and no code executes at runtime. CI verifies the one thing it +can from a clone: that every file `contributes.snippets` points at exists and parses +as valid JSON. The deeper structural and fidelity gates live in `tools/` and run +locally before a release. + +## Testing a snippet by hand + +Press F5 to open an Extension Development Host, open a file of the matching +language, and type the prefix. + +| Flavor | Language ID(s) | +|---|---| +| Angular | `html` | +| Core | `xml` | +| Vue | `vue` | +| React | `typescriptreact`, `javascriptreact` | +| Svelte | `svelte` | + +All prefixes start with `ns-`. Per element there is `ns-` (bare tag), +`ns--prop` (properties as tab stops), and `ns--comp` (properties, events, +and a doc comment). + +If a snippet does not appear, the language ID is almost always the reason — check the +one in the status bar against the table above. + +## Code style + +- **LF line endings**, enforced by `.gitattributes`. Snippet JSON is LF with 2-space + indent; a CRLF re-save produces an enormous phantom diff. +- Do not bump `version` in `package.json` — releases own that. + +[`CLAUDE.md`](../CLAUDE.md) documents the generation pipeline in full, if you want the +complete picture. + +## Reporting bugs + +Use the issue templates. [`SUPPORT.md`](../SUPPORT.md) covers the known limitations — +including why gesture snippets behave differently in Vue than in Svelte. + +For **security** problems, do not open an issue — see [`SECURITY.md`](SECURITY.md). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ac22d6a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,40 @@ + + +## What this changes + + + +## Why + + + +## How to verify + + + +--- + +## Checklist + +- [ ] **`snippets/**/*.json` and `reference.md` were not hand-edited.** Both are + build artifacts, regenerated wholesale — a hand-edit is silently overwritten. + Change the generator or its curated data instead, then regenerate. +- [ ] The README's `## Snippets` section was not hand-edited either; it is rewritten + in place between that heading and the next `## ` heading. +- [ ] If `package.json` display strings changed — `package.nls.json` **and all eight + locale files** were updated together. A key missing from one locale silently + falls back to English there. +- [ ] Snippet prefixes, bodies, and descriptions stay English. They are typed + identifiers, not display text. +- [ ] Links added to `README.md` or `CHANGELOG.md` are **absolute URLs**. Both ship + inside the VSIX, where relative paths do not resolve. +- [ ] No AI attribution anywhere in the commits or this description — no + `Co-Authored-By: Claude`, no "Generated with", no 🤖. +- [ ] Line endings are LF (enforced by `.gitattributes`). +- [ ] `version` in `package.json` was **not** hand-bumped — `vsce publish` owns that. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..31d88d4 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,66 @@ +# Security Policy + +## Supported versions + +Only the **latest published version** of NativeScript Snippets receives security +fixes. Older versions are never patched in place — a fix ships as a new release to +both registries: + +- [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=ElecTreeFrying.nativescript-angular-html-snippets) +- [Open VSX](https://open-vsx.org/extension/ElecTreeFrying/nativescript-angular-html-snippets) + +If you are reporting against an older version, please confirm the problem still +reproduces on the latest one first. + +## Reporting a vulnerability + +**Please do not open a public issue for a security problem.** A public report tells +everyone about the weakness before there is a fix available. + +Email **electreefrying.git@gmail.com** with: + +- what the problem is, and roughly how severe you think it is +- the extension version, your VS Code (or Cursor / VSCodium / Windsurf) version, and your OS +- steps to reproduce, or a proof of concept +- whether you would like to be credited in the release notes, and under what name + +You can expect an acknowledgement as soon as I can manage it — usually within a week. +If the report is confirmed, I will let you know the fix timeline and tell you when the +patched version is live on both registries. If I conclude it is not a vulnerability, +I will explain why rather than going quiet. + +This is a solo-maintained project, so please be patient with response times. There is +no bug bounty. + +## Scope + +The attack surface here is unusually small, and it is worth being explicit about why. +This extension **ships no executable code at all** — no `main`, no `activationEvents`, +no bundle. The published artifact is `package.json` plus declarative snippet JSON +under `snippets/`. Nothing runs; VS Code reads the snippet definitions and expands +them on request. There are no runtime dependencies, no network requests, and no +telemetry. + +**In scope:** + +- Snippet content that VS Code's snippet engine would treat as something other than + literal text — a variable, a command, or a shell transform smuggled through a + `${...}` construct +- A malformed snippet file that crashes or hangs the editor +- Anything in the published `package.json` that grants the extension more capability + than a snippets extension should have + +**Out of scope:** + +- Vulnerabilities in VS Code itself — report those to + [Microsoft](https://github.com/microsoft/vscode/security/policy) +- Snippets that expand to *wrong* or outdated NativeScript markup — ordinary bugs, + please open a normal issue +- The local generator under `tools/`. It is developer tooling, is not committed, and + is not part of any published release + +## Disclosure + +Please give me a reasonable window to ship a fix before disclosing publicly. Once the +patched version is live on both registries, you are welcome to write about it — and +I will credit you in the release notes if you would like. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0a78243 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +version: 2 + +updates: + # This extension ships no runtime dependency — the published artifact is + # package.json + snippets/. Everything in package.json is dev tooling for the + # local generator, @nativescript/core chief among it: a bump there is the + # upstream signal that the shipped snippets may be stale and want a + # regenerate + re-verify pass locally. + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + commit-message: + prefix: chore + include: scope + # Wait a week before proposing a freshly published version. Malicious or + # broken npm releases are typically caught and yanked inside that window. + cooldown: + default-days: 7 + + # The actions pinned in .github/workflows/ + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + commit-message: + prefix: chore + cooldown: + default-days: 7 diff --git a/.github/scripts/check-nls.js b/.github/scripts/check-nls.js new file mode 100644 index 0000000..d627054 --- /dev/null +++ b/.github/scripts/check-nls.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node +/** + * Manifest-localization gate. + * + * VS Code substitutes a manifest string ONLY when the whole value matches + * /^%key%$/. A key missing from the bundle leaves the literal "%key%" rendered in + * the UI, and a placeholder embedded in a longer string is never substituted at + * all. Neither is caught by tsc, eslint, or `vsce package` — but both are fully + * decidable from the files on disk, which is what this script does. + * + * Zero dependencies, so it runs on a bare checkout with no `npm ci`. + * + * Exit 0 = clean. Exit 1 = at least one error. + */ +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..', '..'); +const LOCALES = [ 'zh-cn', 'es', 'fr', 'pt-br', 'ru', 'de', 'ja', 'tr' ]; +const EXACT = /^%([\w\d.\-]+)%$/; +const ANY = /%[\w\d.\-]+%/; + +/** Positions whose value VS Code matches at runtime — never display text. */ +const FORBIDDEN = [ + [ /\.enum\[\]$/, 'enum VALUE is matched against settings at runtime' ], + [ /\.default$/, 'default VALUE is matched at runtime' ], + [ /^contributes\.commands\[\]\.command$/, 'command ID is resolved at runtime' ], + [ /\.when$/, 'when-clause is a context expression' ], + [ /^contributes\.menus\./, 'menu entries are runtime-resolved' ], + [ /^contributes\.keybindings\[\]\./, 'keybindings are runtime-resolved' ], + [ /^(name|publisher|version|main|browser|icon|license)$/, 'manifest identity field' ], + [ /^categories\[\]$/, 'category is matched against a fixed VS Code vocabulary' ], +]; + +const errors = []; +const warnings = []; + +const readJson = (p) => { + const raw = fs.readFileSync(p, 'utf8'); + if (raw.charCodeAt(0) === 0xfeff) errors.push(`${path.basename(p)}: has a UTF-8 BOM`); + return { raw, json: JSON.parse(raw.replace(/^/, '')) }; +}; + +/** Flat one-level bundles: a line scan finds keys JSON.parse would silently collapse. */ +function duplicates(raw) { + const seen = new Set(), dup = []; + for (const line of raw.split('\n')) { + const m = line.match(/^\s*"((?:[^"\\]|\\.)*)"\s*:/); + if (m) { if (seen.has(m[1])) dup.push(m[1]); seen.add(m[1]); } + } + return dup; +} + +function walk(node, p, out) { + if (typeof node === 'string') return void out.push([ p, node ]); + if (Array.isArray(node)) return void node.forEach((v) => walk(v, p + '[]', out)); + if (node && typeof node === 'object') for (const k of Object.keys(node)) walk(node[k], p ? `${p}.${k}` : k, out); +} + +const pkg = readJson(path.join(ROOT, 'package.json')).json; +const baseFile = path.join(ROOT, 'package.nls.json'); +if (!fs.existsSync(baseFile)) { console.error('FAIL: package.nls.json is missing'); process.exit(1); } +const { raw: baseRaw, json: base } = readJson(baseFile); + +// 1. every placeholder in the manifest resolves, and sits somewhere localizable +const strings = []; +walk(pkg, '', strings); +const used = new Set(); +for (const [ p, v ] of strings) { + const m = v.match(EXACT); + if (m) { + used.add(m[1]); + if (!(m[1] in base)) errors.push(`unresolved %${m[1]}% at ${p} — VS Code renders the literal placeholder`); + const bad = FORBIDDEN.find(([ re ]) => re.test(p)); + if (bad) errors.push(`%${m[1]}% sits at ${p} — ${bad[1]}`); + } else if (ANY.test(v)) { + errors.push(`embedded placeholder at ${p}: ${JSON.stringify(v)} — only a whole-value %key% is substituted`); + } +} + +// 2. base bundle integrity +for (const d of duplicates(baseRaw)) errors.push(`package.nls.json: duplicate key "${d}" — the earlier value is silently dead`); +for (const [ k, v ] of Object.entries(base)) { + if (typeof v !== 'string') errors.push(`package.nls.json: "${k}" is ${typeof v}, expected string`); + else if (!v.trim()) errors.push(`package.nls.json: "${k}" is empty — renders as a blank label`); +} +for (const k of Object.keys(base)) if (!used.has(k)) warnings.push(`orphan key "${k}" — defined but referenced nowhere in package.json`); + +// 3. every locale carries exactly the base key set +const baseKeys = Object.keys(base); +const args = (s) => (String(s).match(/\{\d+\}/g) || []).sort().join(','); +for (const loc of LOCALES) { + const f = path.join(ROOT, `package.nls.${loc}.json`); + if (!fs.existsSync(f)) { errors.push(`package.nls.${loc}.json is missing`); continue; } + let r; + try { r = readJson(f); } catch (e) { errors.push(`package.nls.${loc}.json: invalid JSON — ${e.message}`); continue; } + for (const d of duplicates(r.raw)) errors.push(`package.nls.${loc}.json: duplicate key "${d}"`); + const missing = baseKeys.filter((k) => !(k in r.json)); + const extra = Object.keys(r.json).filter((k) => !baseKeys.includes(k)); + if (missing.length) errors.push(`package.nls.${loc}.json: missing ${missing.length} key(s) — ${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ' …' : ''} (falls back to English, which reads as a broken translation)`); + if (extra.length) errors.push(`package.nls.${loc}.json: ${extra.length} stale key(s) — ${extra.slice(0, 5).join(', ')}${extra.length > 5 ? ' …' : ''}`); + for (const [ k, v ] of Object.entries(r.json)) { + if (typeof v !== 'string') errors.push(`package.nls.${loc}.json: "${k}" is ${typeof v}, expected string`); + else if (!v.trim()) errors.push(`package.nls.${loc}.json: "${k}" is empty`); + else if (k in base && args(base[k]) !== args(v)) errors.push(`package.nls.${loc}.json: "${k}" placeholder args differ from base (${args(base[k]) || 'none'} vs ${args(v) || 'none'})`); + } +} + +// 4. the bundles must actually ship. A .vscodeignore glob that matches them is +// fatal: the extension would install with every string unresolved. The `*` has +// to be inside the character class — "package.nls*.json" is the natural way to +// write the mistake, and omitting it made this check silently unable to fire. +const ignore = path.join(ROOT, '.vscodeignore'); +if (fs.existsSync(ignore)) { + for (const line of fs.readFileSync(ignore, 'utf8').split('\n')) { + const t = line.trim(); + if (!t || t.startsWith('#') || t.startsWith('!')) continue; + if (/(^|\/)(\*\*?|package\.nls[\w.*\-]*)\.json$/.test(t)) { + errors.push(`.vscodeignore excludes the nls bundles via "${t}" — they must ship inside the VSIX or every string renders unresolved`); + } + } +} + +for (const w of warnings) console.warn(`warning: ${w}`); +if (errors.length) { + console.error(`\nFAILED — ${errors.length} localization error(s):`); + for (const e of errors) console.error(` ✗ ${e}`); + process.exit(1); +} +console.log(`OK — ${baseKeys.length} key(s) × ${LOCALES.length} locale(s), all placeholders resolve, no forbidden positions.`); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..59c34ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,164 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +# Least privilege by default. No job here writes to the repo; a job that ever needs +# to should raise its own `permissions:` block rather than widening this one. +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + snippets: + name: Validate shipped snippets + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + # NOTE: `npm run generate` / `validate` / `check` cannot run here. + # All three live in tools/, which is gitignored and therefore absent from + # a fresh clone — CI only ever sees the shipped artifacts. So this job + # checks what a clone actually has: that every snippet file the manifest + # points at exists and parses. It deliberately does NOT re-implement + # tools/validate.js (content fidelity) — that stays the local gate. + # + # No `npm ci`: this check needs no dependencies, which is the point. + - name: Every contributed snippet file exists and is valid JSON + run: | + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + const entries = pkg.contributes.snippets; + const seen = new Set(); + let failed = 0; + + for (const s of entries) { + if (!fs.existsSync(s.path)) { + console.error('MISSING ' + s.path + ' (language: ' + s.language + ')'); + failed++; + continue; + } + if (seen.has(s.path)) { continue; } + seen.add(s.path); + try { + JSON.parse(fs.readFileSync(s.path, 'utf8')); + } catch (e) { + console.error('BAD JSON ' + s.path + ' — ' + e.message); + failed++; + } + } + + console.log('contributes.snippets entries : ' + entries.length); + console.log('distinct files parsed OK : ' + seen.size); + if (failed) { + console.error('FAILED: ' + failed + ' problem(s)'); + process.exit(1); + } + console.log('All shipped snippet files resolve and parse.'); + " + + localization: + name: Localization bundles + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + # No `npm ci` — the gate is dependency-free on purpose, so a broken lockfile + # can never mask a broken translation. It decides everything from the files + # on disk: unresolved %key% placeholders, keys missing from a locale, keys + # localized in runtime-matched positions. + # + # This repo localizes only displayName + description; snippet prefixes and + # bodies stay English by design (they are typed identifiers, not display + # text), so there is deliberately nothing else for this gate to cover. + - name: Verify package.nls bundles + run: node .github/scripts/check-nls.js + + package: + name: VSIX packages + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [ snippets, localization ] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + # Needed only for the pinned vsce binary below — this repo has no + # `vscode:prepublish`, so packaging itself still runs no npm script. + - name: Install dependencies + run: npm ci + + # The release gate CI was missing. The manifest can be invalid while every + # other job here passes, and only this step would catch it. + # + # Invoked by explicit path, NOT `npx vsce` or `npm exec -- vsce`. Both of those + # fall back to the registry when the binary is absent, and the unscoped `vsce` + # package there is DEPRECATED and frozen at 2.15.0 — an error that reads like a + # manifest bug and is not one. This path resolves only to the `@vscode/vsce` + # version in package-lock.json, so CI and a local publish run use the same tool. + - name: Build the VSIX + run: ./node_modules/.bin/vsce package --out extension.vsix + + # Authoritative shipping check: inspect the archive itself rather than + # reasoning about .vscodeignore globs. + - name: Assert the VSIX contents are correct + run: | + unzip -l extension.vsix > listing.txt + fail=0 + + for loc in "" .zh-cn .es .fr .pt-br .ru .de .ja .tr; do + if ! grep -q "package.nls${loc}.json" listing.txt; then + echo "MISSING from VSIX: package.nls${loc}.json" + fail=1 + fi + done + + # The snippets ARE the product here — an empty VSIX would still package. + if ! grep -q "extension/snippets/" listing.txt; then + echo "MISSING from VSIX: snippets/ — the extension would install with nothing to contribute" + fail=1 + fi + + # Maintainer-only trees must never ship. tools/ is the generator and is + # gitignored; qa/ is tracked but VSIX-excluded. + for leak in "extension/tools/" "extension/qa/" "CLAUDE.md" "extension/.github/" "SPEC.md"; do + if grep -q "$leak" listing.txt; then + echo "LEAKED into VSIX: $leak" + fail=1 + fi + done + + echo "--- VSIX contents ---" + cat listing.txt + exit $fail + + - uses: actions/upload-artifact@v4 + with: + name: vsix + path: extension.vsix + retention-days: 14 diff --git a/.gitignore b/.gitignore index 607027c..091799c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,7 @@ Thumbs.db # Local-only working trees — kept on disk, never committed. # NOTE: qa/ is deliberately TRACKED in this repo, unlike the other three — never add it here. tools/ -# Agent session state. Self-ignored by .remember/.gitignore too; named here so the tree stays -# ignored even if that file is ever lost. -.remember/ +# Agent session state. .claude/ # Local media — masters and cards that never enter the tree diff --git a/.vscodeignore b/.vscodeignore index 64276b8..95f0bb6 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -3,8 +3,8 @@ .vscode-test/** .github/** -# Dependencies & build output — this extension has no runtime bundle; -# the .vsix ships package.json + snippets/ + README/CHANGELOG/LICENSE + images/icon.png +# Dependencies & build output — this extension has no runtime bundle; the .vsix ships +# package.json + package.nls*.json + snippets/ + README/CHANGELOG/LICENSE + images/icon.png node_modules/** # Source & build tooling — snippets/ are generated artifacts, tools/ is the generator @@ -31,7 +31,6 @@ snippets/**/README.md # Self-ignored from git, so they never appear in `git status` — but vsce packages # from disk, not from git, and would otherwise ship them. Must stay named here. .claude/** -.remember/** # Demo media — the README loads these from GitHub, so the package stays lean. # .mov are the convert-to-gif masters. images/icon.png is the Marketplace icon diff --git a/CHANGELOG.md b/CHANGELOG.md index 6826bc3..986641b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## v1.0.2 (2026-08-08) + +### Added + +- **The extension's name and description now follow VS Code's display language.** Both render in Simplified Chinese, Spanish, French, Brazilian Portuguese, Russian, German, Japanese, and Turkish, with English as the fallback for every other language. Snippet prefixes and bodies are deliberately not translated — `ns-button` is a typed identifier, not display text. + +### Changes + +- **Open VSX is now linked directly from the README** — a version badge, an **Open VSX listing** line under Installation, and a link to the publisher's other extensions on the registry. Compatibility notes that the listing is published by **WinterNova5**, a verified Open VSX publisher, and now records which display languages the extension ships in. +- **The publisher reads as WinterNova5** throughout the README. Install commands, URLs, and the extension id are unchanged. +- **GitHub Sponsors** now sits alongside the existing donation options in the README's Support section and in the support guide. +- No snippet changes — the shipped snippets are byte-identical to v1.0.1. + ## v1.0.1 (2026-07-25) ### Added diff --git a/CLAUDE.md b/CLAUDE.md index b1acef5..3eb92e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ Do NOT append a `Co-Authored-By: Claude …` trailer (or any other AI attributio ## What this is -A **generated, multi-flavor VS Code snippets extension** for NativeScript. It ships **no runtime/activation code** (no `extension.ts`, no `main`, no `activationEvents`) — the published artifact is `package.json` + the snippet JSON under `snippets/`. But it is **not** hand-authored: every snippet is derived from `@nativescript/core` v9 TypeScript types by a build-time generator under `tools/`. There is a build step (the generator) even though there is no runtime. The `tools/` generator is **local-only (gitignored)** — not committed or distributed, so a fresh clone has the shipped `snippets/` + docs but not the build tooling. +A **generated, multi-flavor VS Code snippets extension** for NativeScript. It ships **no runtime/activation code** (no `extension.ts`, no `main`, no `activationEvents`) — the published artifact is `package.json` + the `package.nls*.json` display-string files + the snippet JSON under `snippets/`. But it is **not** hand-authored: every snippet is derived from `@nativescript/core` v9 TypeScript types by a build-time generator under `tools/`. There is a build step (the generator) even though there is no runtime. The `tools/` generator is **local-only (gitignored)** — not committed or distributed, so a fresh clone has the shipped `snippets/` + docs but not the build tooling. The same `ns-*` prefixes are contributed to five flavors, each scoped to its language(s) via `contributes.snippets`: @@ -67,6 +67,8 @@ These are silently overwritten on the next `npm run generate`: The rest of the README, plus `SPEC.md`, `SUPPORT.md`, `CLAUDE.md`, `CHANGELOG.md`, and `LICENSE.md`, are hand-maintained. +**So are the `package.nls*.json` files** — the manifest's `%displayName%`/`%description%` placeholders resolve from `package.nls.json` (English base + per-key fallback) and the locale files `package.nls.{zh-cn,es,fr,pt-br,ru,de,ja,tr}.json`. They are the one shipped surface `npm run generate` does **not** own: edit them directly (all nine ship in the VSIX; the Marketplace listing shows the English base). Snippet prefixes, bodies, and `description` fields stay English — they are generated, and prefixes are typed identifiers, not display text. + ## Prefix & variant grammar All prefixes start with `ns-`. Per element: diff --git a/README.md b/README.md index cc1ded9..1b3aee9 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,20 @@ [![installs][installs-badge]][package] [![downloads][downloads-badge]][package] [![rating][rating-badge]][package] -[![license][license-badge]][repo] +[![open vsx][ovsx-badge]][open-vsx] [![vscode][vscode-badge]][package] +[![license][license-badge]][repo] + +[![sponsor][sponsor-badge]][sponsors] [version-badge]: https://vsmarketplacebadges.dev/version-short/ElecTreeFrying.nativescript-angular-html-snippets.png [installs-badge]: https://vsmarketplacebadges.dev/installs-short/ElecTreeFrying.nativescript-angular-html-snippets.png [downloads-badge]: https://vsmarketplacebadges.dev/downloads-short/ElecTreeFrying.nativescript-angular-html-snippets.png [rating-badge]: https://vsmarketplacebadges.dev/rating-short/ElecTreeFrying.nativescript-angular-html-snippets.png -[license-badge]: https://img.shields.io/github/license/ElecTreeFrying/nativescript-snippets +[ovsx-badge]: https://img.shields.io/open-vsx/v/ElecTreeFrying/nativescript-angular-html-snippets [vscode-badge]: https://img.shields.io/badge/vscode-%3E%3D1.30.0-blue +[license-badge]: https://img.shields.io/github/license/ElecTreeFrying/nativescript-snippets +[sponsor-badge]: https://img.shields.io/badge/GitHub_Sponsors-ea4aaa?logo=githubsponsors&logoColor=white [package]: https://marketplace.visualstudio.com/items?itemName=ElecTreeFrying.nativescript-angular-html-snippets [repo]: https://github.com/ElecTreeFrying/nativescript-snippets @@ -202,6 +207,7 @@ Most prefixes work in every flavor; a few are flavor-specific: `ActionBarExtensi - **Marketplace:** Extensions view (Cmd/Ctrl+Shift+X) → search **NativeScript Snippets** by *WinterNova5* → **Install**. - **CLI:** `code --install-extension ElecTreeFrying.nativescript-angular-html-snippets` - **Direct:** [VS Code Marketplace listing][package] +- **Direct (Open VSX):** [Open VSX listing][open-vsx] - **Cursor / Windsurf / VSCodium / Gitpod:** Search **NativeScript Snippets** in the Extensions panel — served via [Open VSX][open-vsx]. [open-vsx]: https://open-vsx.org/extension/ElecTreeFrying/nativescript-angular-html-snippets @@ -211,9 +217,10 @@ Most prefixes work in every flavor; a few are flavor-specific: `ActionBarExtensi ## Compatibility - **VS Code:** 1.30.0 or later. -- **Registries:** Available on both the [VS Code Marketplace][package] and [Open VSX][open-vsx]. +- **Registries:** Available on both the [VS Code Marketplace][package] and [Open VSX][open-vsx] — published by **WinterNova5**, a verified Open VSX publisher. - **Compatible hosts:** Cursor, Windsurf, VSCodium, Gitpod, Code Server, and other forks that implement the VS Code API at the same engine version — installable directly from their Extensions panel via Open VSX. - **Web:** Runs on [vscode.dev](https://vscode.dev) and [github.dev](https://github.dev) — it's a pure declarative extension, so there's no native code to hold it back. +- **Display language:** The extension's name and description follow VS Code's display language — Simplified Chinese, Spanish, French, Brazilian Portuguese, Russian, German, Japanese, and Turkish, with English as the fallback. Snippet prefixes and bodies stay English: `ns-button` is a typed identifier, not display text. - **Languages:** Angular (`html`), Core (`xml`), and React (`typescriptreact`/`javascriptreact`) work out of the box; Vue and Svelte require the **Vue (Volar)** / **Svelte** language extensions. - **Footprint:** Pure declarative snippet JSON — zero runtime dependencies. - **Telemetry:** None. Everything runs locally. @@ -274,9 +281,10 @@ See [SPEC.md][SPEC] for the snippet grammar and generation pipeline — behavior ## Related -- **[All extensions by WinterNova5][all]** on the VS Code Marketplace. +- **[All extensions by WinterNova5][all]** on the VS Code Marketplace — and on [Open VSX][all-ovsx]. [all]: https://marketplace.visualstudio.com/publishers/ElecTreeFrying +[all-ovsx]: https://open-vsx.org/namespace/ElecTreeFrying --- diff --git a/SPEC.md b/SPEC.md index d638356..c7c832c 100644 --- a/SPEC.md +++ b/SPEC.md @@ -22,7 +22,7 @@ The formal specification of the snippet system: the prefix grammar, the variant ## 1. Overview -This extension contributes VS Code snippets for NativeScript UI to five framework flavors. It has **no runtime code** — the shipped artifact is `package.json` plus the snippet JSON under `snippets/`. All snippet content and documentation is **generated** at build time from `@nativescript/core` TypeScript types by the pipeline under `tools/`; the JSON files are build artifacts, not source. +This extension contributes VS Code snippets for NativeScript UI to five framework flavors. It has **no runtime code** — the shipped artifact is `package.json`, the `package.nls*.json` display-string files, and the snippet JSON under `snippets/`. All snippet content and documentation is **generated** at build time from `@nativescript/core` TypeScript types by the pipeline under `tools/`; the JSON files are build artifacts, not source. The system's two invariants: @@ -131,7 +131,7 @@ The following are **generated** and must never be hand-edited — the next `npm - **`reference.md`** — rewritten wholesale by `gen-docs.js`. - **README `## Snippets` section** — `gen-docs.js` replaces everything between the `## Snippets` heading and the next `## ` heading. Editors must keep that heading and a following `## ` heading; the block between is generator-owned. -Everything else (the rest of `README.md`, this `SPEC.md`, `SUPPORT.md`, `CLAUDE.md`, `CHANGELOG.md`, `LICENSE.md`) is hand-maintained. +Everything else (the rest of `README.md`, this `SPEC.md`, `SUPPORT.md`, `CLAUDE.md`, `CHANGELOG.md`, `LICENSE.md`, and the `package.nls*.json` localization files) is hand-maintained. ## 10. Validation rules @@ -165,6 +165,8 @@ Only Angular and Core ship `settings-icon.json` (iOS icons are an `ActionItem` c Svelte's `gestures.json` is mapped to **two** languages — `svelte` (the markup between elements) **and** `svelte-start-tag` (the region *inside* an element's opening tag, which the Svelte grammar scopes as its own language). This is what lets gesture fragments like `on:tap={…}` expand inside a tag, not only between elements. No other flavor needs it: Vue's `