Compare commits

...
Author SHA1 Message Date
Yang JunandCursor b8b765b4cd docs: add AGENTS.md and llms.txt for AI agents
Co-authored-by: Cursor <[email protected]>
2026-06-22 02:21:05 +08:00
03a30e6dc4 docs: replace CookieHub with cookieconsent (#918)
Co-authored-by: Cursor <[email protected]>
2026-06-22 01:34:53 +08:00
4775227358 docs(security): route vulnerability reports to GitHub Advisories (#913)
Replace the private email contact with GitHub Security Advisories and
set the common-case fix expectation to within a month.

Co-authored-by: Cursor <[email protected]>
2026-06-20 00:00:05 +08:00
13 changed files with 204 additions and 54 deletions
-17
View File
@@ -1,17 +0,0 @@
---
description: Architecture overview for liquidjs internals
alwaysApply: true
---
## Async/sync duality via generators
All core logic is written once as a `Generator` function (`function *`). Use `yield` where you'd normally `await` a potentially async value.
- `toPromise(generator)` drives it **asynchronously** — awaits yielded promises.
- `toValueSync(generator)` drives it **synchronously** — passes yielded values through as-is.
Never duplicate logic into separate async and sync methods. A single generator serves both paths.
When wrapping an async+sync function pair (e.g. `contains`/`containsSync`, `exists`/`existsSync`, `readFile`/`readFileSync`), use `toLiquidAsync(asyncFn, syncFn?)` which returns a `LiquidAsync<F>` — one function that picks the sync or async implementation based on a leading `sync: boolean` arg. Then `yield` the result inside a generator to let the driver handle it in both modes.
See `src/util/async.ts`.
-6
View File
@@ -1,6 +0,0 @@
---
description: Project conventions for liquidjs
alwaysApply: true
---
- Keep edits minimal: change only what the task requires, match existing style.
-17
View File
@@ -1,17 +0,0 @@
---
description: Testing conventions — e2e uses built dist, integration uses src
globs: test/**/*.ts
alwaysApply: false
---
# Testing
## End-to-end tests (`test/e2e`)
- **Use the built package**, not TypeScript sources under `src/`.
- Import the public API from the package root (for example `import { Liquid } from '../..'`), which resolves through `package.json` to **`dist/`** (`main`, `module`, etc.).
- **Avoid** `import … from '../../src/liquid'` (or other `src/` paths) in `test/e2e/**` so e2e matches what consumers get from npm and you do not depend on an unbuilt tree.
## Integration and unit tests
- Tests under `test/integration/`, `src/**/*.spec.ts`, and similar may import from **`src/`** when the suite is meant to run against the current TypeScript sources (typical for this repos Jest setup).
+94
View File
@@ -0,0 +1,94 @@
# LiquidJS
Shopify / GitHub Pages compatible Liquid template engine. TypeScript in `src/`, bundles in `dist/`. Docs site in `docs/` (Hexo, `navy` theme).
## Layout
| Path | Contents |
| --- | --- |
| `src/parser`, `src/render`, `src/tags`, `src/filters` | Template parse and render |
| `src/context`, `src/template`, `src/tokens` | Scope, templates, token stream |
| `src/util/async.ts` | `toPromise`, `toValueSync`, `toLiquidAsync` |
| `test/` | Jest |
| `docs/source/` | Doc markdown; sidebar in `docs/source/_data/sidebar.yml` |
| `docs/themes/navy/` | Layout, CSS, JS |
| `.local/` | Scratch, repro, PoC (gitignored) |
## Commands
```
npm run build # after src/ changes, before npm test
npm test
npm run lint
npm run check # build + build:docs + test + lint + perf:diff (manual)
npm run build:docs
cd docs && npm start # http://localhost:4000
npm run perf:diff
```
PR CI (`pull_request`): build, lint, test, coverage, performance. Docs build runs on push to `master` only.
PR titles: conventional format (`feat:`, `fix:`, `docs:`, …) — checked by CI. Releases on `master` use semantic-release from merged commits.
Backward-compatible API changes expected unless doing an intentional major break.
## Architecture
All core logic is one `function *` per feature. Use `yield` where you'd normally `await` a potentially async value.
- `toPromise(generator)` — async driver; awaits yielded promises
- `toValueSync(generator)` — sync driver; passes yielded values through as-is
Never duplicate logic into separate async and sync methods. One generator serves both paths.
When wrapping an async+sync pair (e.g. `contains`/`containsSync`, `readFile`/`readFileSync`), use `toLiquidAsync(asyncFn, syncFn?)` — returns a `LiquidAsync<F>` that picks sync or async via a leading `sync: boolean` arg. `yield` the result inside a generator. See `src/util/async.ts`.
## Style
Make minimal changes only. Avoid sweeping edits. Always check after you made changes.
- Change only what the task requires. No drive-by refactors, test harnesses, or extra files unless asked.
- Match existing patterns in the file you edit.
- Repro, PoC, and scratch files go in `.local/` — not tracked `poc/` folders or one-off scripts under `docs/`.
### Comments
- Do not add narrative comments. Code should be clear from structure and naming; if it needs explanation, refactor instead.
- Comments follow existing repo usage only: non-obvious invariants, `@deprecated`, JSDoc on public API where TypeDoc needs it. Not for explaining changes to the author, migration history, or restating what the code already says.
- Comments document the code; they do not fix unclear code.
### Tests
- Assert observable behavior, not internal implementation details.
- Avoid duplicate coverage; keep test diffs minimal.
- **E2E** (`test/e2e/`): import from the package root (resolves to `dist/` via `package.json`). Do not import from `src/` — e2e must match what npm consumers get.
- **Integration/unit** (`test/integration/`, etc.): may import from `src/` against current TypeScript sources.
### Docs site
- Reuse existing asset paths under `docs/source/` and `docs/themes/navy/` — no new asset directories unless asked.
- Front matter `title:` is plain text (no backticks).
- `docs/source/llms.txt` — deployed to https://liquidjs.com/llms.txt for web agents (llms.txt spec).
- After theme/markdown changes: build or serve locally, check in a browser (light and dark), not only curl or editor preview.
### README
- Research original sources before reordering contributors, logos, or lists.
## Verify
- Do not commit, push, amend, or open a PR unless asked.
- After changes: verify yourself via CLI or UI (tests, `cd docs && npm start`, browser) before reporting done. Do not tell the user to check instead.
- Before push on sweeping changes: run `npm run check`.
- Confirm facts from `.github/workflows`, `package.json`, and library docs — not stale human docs or assumptions.
- When replacing or integrating a library: read its docs and understand what the previous setup did before changing behavior.
### Security fixes
- Reproduce on current `master` first. Smallest fix that addresses the reported issue.
- If Shopify/Ruby Liquid behaves the same, document unsafe usage in filter/docs instead of changing behavior.
## Docs
- Published: https://liquidjs.com
- Repo agent instructions: this file (`AGENTS.md`)
+5 -3
View File
@@ -6,8 +6,10 @@ Only the latest major version is supported with security updates. It can be chan
## Reporting a Vulnerability
Please contact harttleharttle@gmail.com to report a vulnerability or change request.
**Please do not report security vulnerabilities through public GitHub issues.**
- If the vulnerability in question affects common use cases, it will be treated as a bug and fixed very soon (typically within 1 week).
Report them via [GitHub Security Advisories — Report a vulnerability](https://github.com/harttle/liquidjs/security/advisories/new).
- If the vulnerability in question affects common use cases, it will be treated as a bug and fixed very soon (typically within a month).
- Otherwise, it'll be scheduled in the same priority of feature request (which is lower than bugs).
- If the request is declined, you'll receive a reply email anyway (most likely there will be a discussion).
- If the request is declined, you'll receive a reply anyway (most likely there will be a discussion).
+28
View File
@@ -0,0 +1,28 @@
# LiquidJS
> A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.
## Tutorials
- [Introduction to Liquid](https://liquidjs.com/tutorials/intro-to-liquid.html)
- [Setup](https://liquidjs.com/tutorials/setup.html)
- [Options](https://liquidjs.com/tutorials/options.html)
- [Render files](https://liquidjs.com/tutorials/render-file.html)
- [Partials and layouts](https://liquidjs.com/tutorials/partials-and-layouts.html)
- [Express.js](https://liquidjs.com/tutorials/use-in-expressjs.html)
- [Register filters and tags](https://liquidjs.com/tutorials/register-filters-tags.html)
- [Plugins](https://liquidjs.com/tutorials/plugins.html)
- [Sync and async](https://liquidjs.com/tutorials/sync-and-async.html)
- [Operators](https://liquidjs.com/tutorials/operators.html)
- [Truthy and falsy](https://liquidjs.com/tutorials/truthy-and-falsy.html)
- [Security model](https://liquidjs.com/tutorials/security-model.html)
- [Differences from Shopify Liquid](https://liquidjs.com/tutorials/differences.html)
- [Migrate to v9](https://liquidjs.com/tutorials/migrate-to-9.html)
- [Changelog](https://liquidjs.com/tutorials/changelog.html)
## Reference
- [Tags](https://liquidjs.com/tags/overview.html)
- [Filters](https://liquidjs.com/filters/overview.html)
- [API (TypeDoc)](https://liquidjs.com/api/)
- [Playground](https://liquidjs.com/playground.html)
+1 -3
View File
@@ -17,12 +17,10 @@ debug: false
});
</script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GM713991QQ"></script>
<script>
<script type="text/plain" data-category="analytics">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-GM713991QQ');
</script>
+1
View File
@@ -5,6 +5,7 @@
{{__('footer.license')}}
</div>
<div id="footer-links">
<button type="button" class="footer-link cookie-preferences" data-cc="show-consentModal" title="Manage cookie preferences"><i class="icon-shield"></i></button>
<a href="https://twitter.com/{{ config.twitter }}" class="footer-link" target="_blank"><i class="icon-twitter"></i></a>
<a href="https://opencollective.com/{{ config.oc }}" class="footer-link" target="_blank"><i class="icon-opencollective"></i></a>
<a href="https://github.com/{{ config.github }}" class="footer-link" target="_blank"><i class="icon-github"></i></a>
+3 -8
View File
@@ -32,12 +32,7 @@
<meta name="msapplication-TileImage" content="{{ url_for('icon/mstile-144x144.png') }}">
{{ css('css/navy') }}
{{ feed_tag('atom.xml') }}
<script src="https://cdn.cookiehub.eu/c2/e8e44c93.js"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return;
var cpm = {};
window.cookiehub.load(cpm);
});
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/[email protected]/dist/cookieconsent.css">
<script src="https://cdn.jsdelivr.net/gh/orestbida/[email protected]/dist/cookieconsent.umd.js"></script>
{{ js('js/cookieconsent-config') }}
</head>
+15
View File
@@ -0,0 +1,15 @@
#cc-main
--cc-btn-primary-bg: var(--color-link)
--cc-btn-primary-color: #fff
--cc-btn-primary-border-color: var(--color-link)
--cc-btn-primary-hover-bg: var(--color-link-hover)
--cc-btn-primary-hover-border-color: var(--color-link-hover)
--cc-btn-secondary-bg: #eaeff2
--cc-btn-secondary-color: var(--color-default)
--cc-btn-secondary-border-color: #eaeff2
--cc-toggle-on-bg: var(--color-link)
.cc--darkmode #cc-main
--cc-btn-secondary-bg: #3a4248
--cc-btn-secondary-color: var(--color-default)
--cc-btn-secondary-border-color: #3a4248
+7
View File
@@ -42,6 +42,13 @@
@media mq-normal
font-size: 30px
.cookie-preferences
background: none
border: 0
padding: 0
cursor: pointer
color: inherit
.icon-oc
height: 36px
width: 30px
+1
View File
@@ -9,6 +9,7 @@
@import "_partial/page"
@import "_partial/mobile_nav"
@import "_partial/footer"
@import "_partial/cookieconsent"
@import "_partial/highlight"
@import "_partial/icomoon.css"
@import "_partial/docsearch.min.css"
+49
View File
@@ -0,0 +1,49 @@
(function () {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('cc--darkmode');
}
var config = {
guiOptions: {
consentModal: {
equalWeightButtons: false
}
},
categories: {
necessary: {
enabled: true,
readOnly: true
},
analytics: {
enabled: false
}
},
language: {
default: 'en',
translations: {
en: {
consentModal: {
title: 'We use cookies',
description: 'This site uses cookies for analytics and to improve your experience.',
acceptAllBtn: 'Accept',
acceptNecessaryBtn: 'Reject'
}
}
}
}
};
if (/^localhost$|^127\.0\.0\.1$/i.test(location.hostname)) {
config.cookie = { secure: false };
}
function run() {
CookieConsent.run(config);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
}());