miki-template 1.3.3 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/release-notes/v1.3.1.md +55 -55
- package/.github/release-notes/v1.3.3.md +77 -0
- package/.github/workflows/ci.yml +38 -54
- package/.github/workflows/release.yml +106 -0
- package/AGENT.md +71 -71
- package/API_REFERENCE.md +314 -314
- package/CHANGELOG.md +173 -169
- package/CODE_OF_CONDUCT.md +14 -14
- package/CONTRIBUTING.md +27 -27
- package/README.md +342 -321
- package/ROADMAP.md +40 -40
- package/benchmarks/report.json +16 -16
- package/benchmarks/run.js +49 -49
- package/benchmarks/stress.mjs +647 -647
- package/benchmarks/templates/large.dtpl +7 -7
- package/benchmarks/templates/medium.dtpl +3 -3
- package/benchmarks/templates/small.dtpl +7 -7
- package/context/component.md +109 -109
- package/context/prd.md +131 -131
- package/context/project-structure.md +33 -33
- package/dir/base.html +22 -22
- package/dir/cmpnt.html +10 -10
- package/dir/footer.html +2 -2
- package/dir/home.html +80 -80
- package/dir/index.html +80 -0
- package/dir/navbar.html +8 -8
- package/docs/README.md +18 -18
- package/docs/advanced_usage.md +71 -71
- package/docs/api.md +119 -119
- package/docs/filters.md +708 -708
- package/docs/installation.md +106 -106
- package/docs/overview.md +57 -57
- package/docs/partialdef.md +70 -70
- package/docs/security.md +27 -27
- package/docs/tags.md +673 -673
- package/docs/usage.md +646 -646
- package/eslint.config.mjs +42 -42
- package/ex.mjs +32 -32
- package/miki-template-extension/.github/workflows/ci.yml +116 -116
- package/miki-template-extension/.vscodeignore +7 -7
- package/miki-template-extension/CHANGELOG.md +99 -99
- package/miki-template-extension/LICENSE +21 -21
- package/miki-template-extension/README.md +273 -273
- package/miki-template-extension/extension.js +1013 -1013
- package/miki-template-extension/icon.svg +10 -10
- package/miki-template-extension/package.json +280 -280
- package/miki-template-extension/snippets/miki-template.json +717 -717
- package/miki-template-extension/syntaxes/language-configuration.json +114 -114
- package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +355 -355
- package/miki-template-extension/tests/grammar-tests.json +162 -162
- package/miki-template-extension/tests/run-grammar-tests.js +82 -82
- package/package.json +37 -34
- package/sample-app/package-lock.json +901 -0
- package/sample-app/package.json +9 -0
- package/sample-app/server.js +14 -0
- package/sample-app/views/index.html +1 -0
- package/scripts/build-vsix.js +129 -129
- package/scripts/build-vsix.ps1 +15 -15
- package/snippets/miki-template.json +177 -177
- package/src/asyncRender.js +20 -20
- package/src/cache.js +80 -80
- package/src/context.js +126 -126
- package/src/context_processors.js +48 -48
- package/src/esm.mjs +84 -84
- package/src/filters.js +975 -975
- package/src/i18n.js +171 -171
- package/src/index.js +974 -940
- package/src/lexer.js +114 -114
- package/src/libraries.js +371 -371
- package/src/parser.js +270 -270
- package/src/security.js +53 -53
- package/src/tags/control.js +719 -719
- package/src/tags/extra.js +154 -154
- package/src/tags/helpers.js +26 -26
- package/src/tags/i18n.js +256 -256
- package/src/tags/inheritance.js +335 -335
- package/src/tags/registry.js +18 -18
- package/src/tags/util.js +400 -400
- package/src/types.d.ts +107 -107
- package/syntaxes/language-configuration.json +26 -26
- package/syntaxes/miki-template.tmLanguage.json +146 -146
- package/tests/asyncRender.test.js +17 -17
- package/tests/base.html +6 -6
- package/tests/child.html +3 -3
- package/tests/context_processors.test.js +13 -13
- package/tests/esm.test.mjs +61 -61
- package/tests/filters.test.js +254 -254
- package/tests/include_security.test.js +9 -9
- package/tests/integration/README.md +32 -32
- package/tests/integration/features.test.cjs +1681 -1681
- package/tests/integration/features.test.mjs +1697 -1697
- package/tests/integration/templates/base.miki +6 -6
- package/tests/integration/templates/child.miki +6 -6
- package/tests/integration/templates/index.html +17 -17
- package/tests/lexer.test.js +45 -45
- package/tests/parser.test.js +57 -57
- package/tests/partial.html +1 -1
- package/tests/partialdef.test.js +79 -79
- package/tests/production_checks.js +57 -57
- package/tests/security.test.js +28 -28
- package/tests/tags.test.js +233 -233
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
# miki-template v1.3.1
|
|
2
|
-
|
|
3
|
-
## Highlights
|
|
4
|
-
|
|
5
|
-
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires the view engine, the views directory, and a `res.render` shim that makes HTMX-style partial responses Just Work.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
const express = require('express');
|
|
9
|
-
const miki = require('miki-template');
|
|
10
|
-
|
|
11
|
-
const app = express();
|
|
12
|
-
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
13
|
-
|
|
14
|
-
// Full page
|
|
15
|
-
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
16
|
-
|
|
17
|
-
// Partial — just append `#partialName` to the view name
|
|
18
|
-
app.get('/partials/:name', (req, res) =>
|
|
19
|
-
res.render(`home#${req.params.name}`, { user: req.user })
|
|
20
|
-
);
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
- **`miki.expressPartialRenderer()` middleware** for drop-in `res.renderPartial(view, locals)` without changing your existing `app.engine()` setup.
|
|
24
|
-
- **`miki.express()`** factory for `app.engine('html', miki.express())` — honors `view#partial` selectors.
|
|
25
|
-
- **`renderPartialFromSource(source, partialName, ctx, opts)`** and **`renderPartialFromFile(...)`** — render a single named `{% partialdef %}` from a string or file. Walks the AST (and the `extends` chain) to find partials nested inside blocks.
|
|
26
|
-
- **`{% include "file.html#partial" with ... %}`** — partial-selector syntax works with `include`, not just `res.render`.
|
|
27
|
-
- **Built-in libraries** (`humanize`, `cache`, `lorem`) are now auto-activated on module load. `{% lorem 5 p %}` works without `{% load lorem %}`.
|
|
28
|
-
|
|
29
|
-
## Tag fixes
|
|
30
|
-
|
|
31
|
-
- `{% with x=1, y=2 as pair %}` (multi-pair + alias) parses correctly.
|
|
32
|
-
- `{% with a=x b=y %}` (multi-pair, no alias) now binds each pair instead of being treated as one expression.
|
|
33
|
-
- `{% cycle 'a' 'b' as name %}` now emits nothing but stores the value (Django semantics).
|
|
34
|
-
- `{{ block.super }}` works inside partials that override blocks.
|
|
35
|
-
- `extends` resolves `views` from `options.settings.views` (Express path), `options.views`, or default.
|
|
36
|
-
- Path-traversal protection applies to `extends` *and* `include`, including `include "file#partial"`.
|
|
37
|
-
- `default` vs `default_if_none`: `default` falls back on `''`, `null`, and `undefined`; `default_if_none` only on `null`/`undefined` (Django parity).
|
|
38
|
-
- `removetags` accepts multiple tag names.
|
|
39
|
-
- `Context.get('a.b.c')` returns `undefined` for missing keys (was `''`), so `{% if x %}` and `default_if_none` work correctly.
|
|
40
|
-
|
|
41
|
-
## Test coverage
|
|
42
|
-
|
|
43
|
-
**382/382 tests pass.** Added a 190-test integration suite (`tests/integration/`) covering every tag, filter, and feature under both CommonJS and ESM, exercised against a real Express HTTP server on ephemeral ports. The test runner recursively picks up `.test.{cjs,mjs,js}` files.
|
|
44
|
-
|
|
45
|
-
## Docs
|
|
46
|
-
|
|
47
|
-
- `README.md` — promoted `miki.setupExpress` to the first listed feature; the Express section leads with the one-liner.
|
|
48
|
-
- `docs/usage.md` — full Express section rewritten with the new setup, options table, and middleware variant.
|
|
49
|
-
- `docs/api.md` — added rows for the new APIs; updated CJS/ESM import snippets.
|
|
50
|
-
- `docs/partialdef.md` — documented `include "file#partial"`, `renderPartialFromSource/File`, and the HTMX-over-HTTP flow.
|
|
51
|
-
- `CHANGELOG.md` — full entry for 1.3.1.
|
|
52
|
-
|
|
53
|
-
## Compatibility
|
|
54
|
-
|
|
55
|
-
No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
|
|
1
|
+
# miki-template v1.3.1
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires the view engine, the views directory, and a `res.render` shim that makes HTMX-style partial responses Just Work.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const miki = require('miki-template');
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
13
|
+
|
|
14
|
+
// Full page
|
|
15
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
16
|
+
|
|
17
|
+
// Partial — just append `#partialName` to the view name
|
|
18
|
+
app.get('/partials/:name', (req, res) =>
|
|
19
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
20
|
+
);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **`miki.expressPartialRenderer()` middleware** for drop-in `res.renderPartial(view, locals)` without changing your existing `app.engine()` setup.
|
|
24
|
+
- **`miki.express()`** factory for `app.engine('html', miki.express())` — honors `view#partial` selectors.
|
|
25
|
+
- **`renderPartialFromSource(source, partialName, ctx, opts)`** and **`renderPartialFromFile(...)`** — render a single named `{% partialdef %}` from a string or file. Walks the AST (and the `extends` chain) to find partials nested inside blocks.
|
|
26
|
+
- **`{% include "file.html#partial" with ... %}`** — partial-selector syntax works with `include`, not just `res.render`.
|
|
27
|
+
- **Built-in libraries** (`humanize`, `cache`, `lorem`) are now auto-activated on module load. `{% lorem 5 p %}` works without `{% load lorem %}`.
|
|
28
|
+
|
|
29
|
+
## Tag fixes
|
|
30
|
+
|
|
31
|
+
- `{% with x=1, y=2 as pair %}` (multi-pair + alias) parses correctly.
|
|
32
|
+
- `{% with a=x b=y %}` (multi-pair, no alias) now binds each pair instead of being treated as one expression.
|
|
33
|
+
- `{% cycle 'a' 'b' as name %}` now emits nothing but stores the value (Django semantics).
|
|
34
|
+
- `{{ block.super }}` works inside partials that override blocks.
|
|
35
|
+
- `extends` resolves `views` from `options.settings.views` (Express path), `options.views`, or default.
|
|
36
|
+
- Path-traversal protection applies to `extends` *and* `include`, including `include "file#partial"`.
|
|
37
|
+
- `default` vs `default_if_none`: `default` falls back on `''`, `null`, and `undefined`; `default_if_none` only on `null`/`undefined` (Django parity).
|
|
38
|
+
- `removetags` accepts multiple tag names.
|
|
39
|
+
- `Context.get('a.b.c')` returns `undefined` for missing keys (was `''`), so `{% if x %}` and `default_if_none` work correctly.
|
|
40
|
+
|
|
41
|
+
## Test coverage
|
|
42
|
+
|
|
43
|
+
**382/382 tests pass.** Added a 190-test integration suite (`tests/integration/`) covering every tag, filter, and feature under both CommonJS and ESM, exercised against a real Express HTTP server on ephemeral ports. The test runner recursively picks up `.test.{cjs,mjs,js}` files.
|
|
44
|
+
|
|
45
|
+
## Docs
|
|
46
|
+
|
|
47
|
+
- `README.md` — promoted `miki.setupExpress` to the first listed feature; the Express section leads with the one-liner.
|
|
48
|
+
- `docs/usage.md` — full Express section rewritten with the new setup, options table, and middleware variant.
|
|
49
|
+
- `docs/api.md` — added rows for the new APIs; updated CJS/ESM import snippets.
|
|
50
|
+
- `docs/partialdef.md` — documented `include "file#partial"`, `renderPartialFromSource/File`, and the HTMX-over-HTTP flow.
|
|
51
|
+
- `CHANGELOG.md` — full entry for 1.3.1.
|
|
52
|
+
|
|
53
|
+
## Compatibility
|
|
54
|
+
|
|
55
|
+
No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# miki-template v1.3.3
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- **Date/time filter fixed** — `{{ d|date:"yyyy-MM-dd" }}` now returns `"2024-06-15"` instead of `"24242424-JunJun-1515"`. Multi-character tokens (`yyyy`, `MM`, `dd`, `HH`, `mm`, `ii`, `ss`) are matched longest-first; backward-compatible single-character aliases (`m`/`d`/`H`/`i`/`s`) still work for Django-style unpadded values.
|
|
6
|
+
- **Inheritance 4× faster** — `{% extends %}` no longer re-reads the parent file from disk on every render. A 64-entry LRU cache in `src/cache.js` memoises the parent source. Inheritance rendering improved from ~481 rps to **~2,056 rps** in the strict benchmark.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install miki-template@1.3.3
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const miki = require('miki-template');
|
|
18
|
+
const express = require('express');
|
|
19
|
+
|
|
20
|
+
const app = express();
|
|
21
|
+
miki.setupExpress(app, { extension: 'html', views: './views' });
|
|
22
|
+
|
|
23
|
+
// Full page
|
|
24
|
+
app.get('/', (req, res) => res.render('home', { user: req.user }));
|
|
25
|
+
|
|
26
|
+
// HTMX partial — just append #partialName
|
|
27
|
+
app.get('/partials/:name', (req, res) =>
|
|
28
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
app.listen(3000);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What's changed
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- `date` and `time` filters — multi-character tokens render correctly.
|
|
38
|
+
|
|
39
|
+
### Performance
|
|
40
|
+
- Inheritance parent-source LRU cache in `src/cache.js` (`getParentSource` / `hasParentSource`). `clearCache()` also clears it.
|
|
41
|
+
|
|
42
|
+
### Tooling
|
|
43
|
+
- New `benchmarks/stress.mjs` — 37-check strict benchmark (correctness, compile speed, render speed, cache, scale, endurance, partial rendering, async, concurrency). Run with `node benchmarks/stress.mjs`. Exits with code 1 on any failure.
|
|
44
|
+
- `eslint.config.mjs` — added `URL`, `URLSearchParams`, `TextEncoder`, `TextDecoder`, `fetch`, `crypto`, `performance` to the Node 18+ globals list.
|
|
45
|
+
- Removed dead code: unused `elifBranches` variable in `parseIfChanged`, unused `extractPluralMappings` function in `i18n.js`.
|
|
46
|
+
- Renamed unused tag-parser `parser` parameters to `_parser` in `lorem` library and `parseLoad`.
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
|
|
50
|
+
| Check | Result |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `npx eslint src/**/*.js` | 0 errors, 0 warnings |
|
|
53
|
+
| `npm test` | 397/397 passing |
|
|
54
|
+
| `node benchmarks/stress.mjs` | 37/37 passing |
|
|
55
|
+
|
|
56
|
+
## Performance (from the stress benchmark)
|
|
57
|
+
|
|
58
|
+
| Template | Speed |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Small (76 B) | **175,439 rps** (0.006 ms) |
|
|
61
|
+
| Medium (595 B) | 13,947 rps (0.072 ms) |
|
|
62
|
+
| Large (1.6 KB, 6 sections × 12 items) | 2,277 rps (0.439 ms) |
|
|
63
|
+
| Inheritance child | 2,056 rps (0.486 ms) |
|
|
64
|
+
| Partials (50 entries) | 2,235 rps (0.447 ms) |
|
|
65
|
+
| Scale: 50,000 items | 107 ms median |
|
|
66
|
+
| Endurance: 100,000 renders | 7.5s total, RSS +0.9 MB |
|
|
67
|
+
| Async: 200 concurrent LARGE | 237 ms total |
|
|
68
|
+
|
|
69
|
+
## Compatibility
|
|
70
|
+
|
|
71
|
+
No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
|
|
72
|
+
|
|
73
|
+
## Links
|
|
74
|
+
|
|
75
|
+
- **Repository**: https://github.com/alainmiki/miki-template
|
|
76
|
+
- **Tag**: https://github.com/alainmiki/miki-template/releases/tag/v1.3.3
|
|
77
|
+
- **Previous**: [v1.3.1](https://github.com/alainmiki/miki-template/releases/tag/v1.3.1)
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -1,54 +1,38 @@
|
|
|
1
|
-
# CI
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
publish:
|
|
40
|
-
runs-on: windows-latest
|
|
41
|
-
needs: test
|
|
42
|
-
if: github.event_name == 'release' && github.event.action == 'created'
|
|
43
|
-
steps:
|
|
44
|
-
- uses: actions/checkout@v4
|
|
45
|
-
- name: Setup Node.js
|
|
46
|
-
uses: actions/setup-node@v4
|
|
47
|
-
with:
|
|
48
|
-
node-version: '20'
|
|
49
|
-
registry-url: 'https://registry.npmjs.org'
|
|
50
|
-
- run: npm ci
|
|
51
|
-
- name: Publish package to npm
|
|
52
|
-
run: npm publish --access public
|
|
53
|
-
env:
|
|
54
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
1
|
+
# CI Workflow — lint + test on every push and PR.
|
|
2
|
+
#
|
|
3
|
+
# Releases are handled by .github/workflows/release.yml, which triggers
|
|
4
|
+
# automatically when a `chore(release):` commit lands on main.
|
|
5
|
+
|
|
6
|
+
name: CI
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [ main ]
|
|
11
|
+
pull_request:
|
|
12
|
+
branches: [ main ]
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: windows-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '20'
|
|
23
|
+
- run: npm ci
|
|
24
|
+
- name: Lint code
|
|
25
|
+
run: npx eslint src/**/*.js
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
runs-on: windows-latest
|
|
29
|
+
needs: lint
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
- name: Setup Node.js
|
|
33
|
+
uses: actions/setup-node@v4
|
|
34
|
+
with:
|
|
35
|
+
node-version: '20'
|
|
36
|
+
- run: npm ci
|
|
37
|
+
- name: Run test suite
|
|
38
|
+
run: npm test
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Release workflow
|
|
2
|
+
#
|
|
3
|
+
# Fully automatic release pipeline. Triggered when a commit to main matches
|
|
4
|
+
# the `chore(release): v*` pattern produced by `npm run release:patch|minor|major`.
|
|
5
|
+
#
|
|
6
|
+
# Flow:
|
|
7
|
+
# 1. Detect the new version in package.json.
|
|
8
|
+
# 2. Lint + test + stress benchmark (must pass before continuing).
|
|
9
|
+
# 3. Create an annotated git tag v<version> and push it.
|
|
10
|
+
# 4. Create a GitHub Release with notes from .github/release-notes/v<version>.md
|
|
11
|
+
# (or a generic message if the file is missing).
|
|
12
|
+
# 5. Publish the package to npm using the NPM_TOKEN secret.
|
|
13
|
+
#
|
|
14
|
+
# Local usage:
|
|
15
|
+
# npm run release:patch # 1.3.3 → 1.3.4
|
|
16
|
+
# npm run release:minor # 1.3.3 → 1.4.0
|
|
17
|
+
# npm run release:major # 1.3.3 → 2.0.0
|
|
18
|
+
# That bumps package.json, creates a commit, pushes to main, and the rest
|
|
19
|
+
# happens here.
|
|
20
|
+
|
|
21
|
+
name: Release
|
|
22
|
+
|
|
23
|
+
on:
|
|
24
|
+
push:
|
|
25
|
+
branches: [main]
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
release:
|
|
32
|
+
# Only run for the explicit "chore(release):" commits. Any other
|
|
33
|
+
# push to main is ignored so contributors can edit package.json
|
|
34
|
+
# without accidentally publishing.
|
|
35
|
+
if: startsWith(github.event.head_commit.message, 'chore(release):')
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
with:
|
|
40
|
+
fetch-depth: 0
|
|
41
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
42
|
+
|
|
43
|
+
- uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: '20'
|
|
46
|
+
registry-url: 'https://registry.npmjs.org'
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: npm ci
|
|
50
|
+
|
|
51
|
+
- name: Lint
|
|
52
|
+
run: npx eslint src/**/*.js
|
|
53
|
+
|
|
54
|
+
- name: Test
|
|
55
|
+
run: npm test
|
|
56
|
+
|
|
57
|
+
- name: Stress benchmark
|
|
58
|
+
run: node benchmarks/stress.mjs
|
|
59
|
+
|
|
60
|
+
- name: Read package version
|
|
61
|
+
id: ver
|
|
62
|
+
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
|
|
63
|
+
|
|
64
|
+
- name: Check tag does not already exist
|
|
65
|
+
run: |
|
|
66
|
+
if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
|
|
67
|
+
echo "::error::Tag v${{ steps.ver.outputs.version }} already exists."
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
- name: Create and push git tag
|
|
72
|
+
run: |
|
|
73
|
+
git config user.name "github-actions[bot]"
|
|
74
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
75
|
+
git tag -a "v${{ steps.ver.outputs.version }}" -m "v${{ steps.ver.outputs.version }}"
|
|
76
|
+
git push origin "v${{ steps.ver.outputs.version }}"
|
|
77
|
+
|
|
78
|
+
- name: Build release body
|
|
79
|
+
id: notes
|
|
80
|
+
env:
|
|
81
|
+
NOTES_FILE: .github/release-notes/v${{ steps.ver.outputs.version }}.md
|
|
82
|
+
VERSION: ${{ steps.ver.outputs.version }}
|
|
83
|
+
run: |
|
|
84
|
+
{
|
|
85
|
+
echo "body<<EOF_RELEASE"
|
|
86
|
+
if [ -f "$NOTES_FILE" ]; then
|
|
87
|
+
cat "$NOTES_FILE"
|
|
88
|
+
else
|
|
89
|
+
echo "## v${VERSION}"
|
|
90
|
+
echo ""
|
|
91
|
+
echo "See [CHANGELOG.md](../../blob/main/CHANGELOG.md) for details."
|
|
92
|
+
fi
|
|
93
|
+
echo "EOF_RELEASE"
|
|
94
|
+
} >> "$GITHUB_OUTPUT"
|
|
95
|
+
|
|
96
|
+
- name: Create GitHub Release
|
|
97
|
+
uses: softprops/action-gh-release@v2
|
|
98
|
+
with:
|
|
99
|
+
tag_name: v${{ steps.ver.outputs.version }}
|
|
100
|
+
name: v${{ steps.ver.outputs.version }}
|
|
101
|
+
body: ${{ steps.notes.outputs.body }}
|
|
102
|
+
|
|
103
|
+
- name: Publish to npm
|
|
104
|
+
run: npm publish --access public
|
|
105
|
+
env:
|
|
106
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/AGENT.md
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
# AGENT.md - Agent Instructions, Guardrails, & Architecture Rules
|
|
2
|
-
|
|
3
|
-
This repository implements a Django-Style Template Engine for Node.js/Express. To ensure high code quality, consistent architecture, and full feature parity with Django without guesswork, all AI agents and developers must strictly follow these instructions and guardrails.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 🎯 Core Objectives
|
|
8
|
-
1. **Full Feature Parity**: Match Django's template engine rules for rendering variables, dotted lookups, built-in tags, filters, and template inheritance.
|
|
9
|
-
2. **Deterministic & Secure**: Escape output by default using a safe-string mechanism.
|
|
10
|
-
3. **No Spaghetti Code**: Maintain a strict separation of Lexer, Parser, Renderer, Context, and Tag/Filter registries.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## ⚠️ Strict Guardrails & Anti-Hallucination Rules
|
|
15
|
-
|
|
16
|
-
> [!IMPORTANT]
|
|
17
|
-
> **No Guesswork / Assumptions**
|
|
18
|
-
> - If a tag or filter's behavior is not clearly defined in [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) or Django documentation, **DO NOT guess**.
|
|
19
|
-
> - If you notice ambiguities (e.g., how to handle circular extends or nested cycles), raise a question to the user immediately or throw a descriptive compilation error.
|
|
20
|
-
|
|
21
|
-
> [!WARNING]
|
|
22
|
-
> **No Regex-Based Global Search & Replace**
|
|
23
|
-
> - You **must not** implement tags or filters using global regex replacements on the final string. All templates must go through:
|
|
24
|
-
> `Lexer (String -> Tokens) -> Parser (Tokens -> AST) -> Renderer (AST + Context -> HTML)`.
|
|
25
|
-
> - Direct regex replacement on HTML bypasses nesting, escaping, and blocks, which introduces severe bugs.
|
|
26
|
-
|
|
27
|
-
> [!CAUTION]
|
|
28
|
-
> **Security Guardrails**
|
|
29
|
-
> - All variable output must be HTML-escaped by default.
|
|
30
|
-
> - A variable is only safe from escaping if it is marked as a `SafeString` (e.g., using the `safe` filter or internally flagged).
|
|
31
|
-
> - Dynamic expression evaluation (such as in `if` tags) **must not** use direct JS `eval()`. Use a safe AST evaluator or sandboxed parser to prevent remote code execution.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 🧩 Architectural Guidelines
|
|
36
|
-
|
|
37
|
-
### 1. Lexing (`src/lexer.js`)
|
|
38
|
-
* Tokens must be generated for three primary types:
|
|
39
|
-
- `TEXT`: Plain HTML/text.
|
|
40
|
-
- `VAR`: Variables wrapped in `{{ ... }}`.
|
|
41
|
-
- `BLOCK`: Structural tags wrapped in `{% ... %}`.
|
|
42
|
-
* The Lexer must correctly handle verbatim blocks `{% verbatim %}` and comments `{% comment %}` by suppressing token output or passing raw text tokens.
|
|
43
|
-
|
|
44
|
-
### 2. Parsing (`src/parser.js`)
|
|
45
|
-
* The Parser takes a stream of tokens and constructs an AST of nodes.
|
|
46
|
-
* Every node must implement a `.render(context)` method (async or sync).
|
|
47
|
-
* For tags that have closing tags (e.g., `{% if %}`...`{% endif %}`), the parser must parse nested tokens recursively until the matching close tag is encountered.
|
|
48
|
-
|
|
49
|
-
### 3. Context & Scope Isolation (`src/context.js`)
|
|
50
|
-
* Context must support a stack-like structure: `.push()` to create a new scope, and `.pop()` to revert.
|
|
51
|
-
* **Variable Lookups**: Support dotted lookups:
|
|
52
|
-
- Example: `{{ user.profile.name }}` should search `user` in context, then resolve property `profile`, then property `name`.
|
|
53
|
-
- If a resolved value is a function, call it (without arguments, just like Django).
|
|
54
|
-
- If a lookup fails, return an empty string `""` by default, unless configured otherwise.
|
|
55
|
-
|
|
56
|
-
### 4. Template Inheritance (`src/tags/inheritance.js`)
|
|
57
|
-
* Inheritance works by having a child template load a parent template via `{% extends "parent.html" %}`.
|
|
58
|
-
* The child overrides blocks defined as `{% block block_name %}`.
|
|
59
|
-
* During render, the parent's AST is evaluated, but block nodes are replaced by the child's corresponding block nodes.
|
|
60
|
-
* Support block nesting and the `{{ block.super }}` variable to render parent block content.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 🛠️ Step-by-Step Task Execution Protocol
|
|
65
|
-
|
|
66
|
-
When executing tasks:
|
|
67
|
-
1. **Analyze Requirements**: Check [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) and [component.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/component.md).
|
|
68
|
-
2. **Check Existing Tests**: Run current test suite using `npm test` or `node --test` to ensure a green state.
|
|
69
|
-
3. **Write Tests First**: For any new filter, tag, or parser logic, write a corresponding unit test in `tests/` showing the expected template string and its expected output.
|
|
70
|
-
4. **Implement Modular Code**: Put tag handlers in `src/tags/`, filters in `src/filters.js`, and maintain registry isolation.
|
|
71
|
-
5. **Verify**: Ensure the test suite passes, check for escaping vulnerabilities, and check that no scope leaks occur.
|
|
1
|
+
# AGENT.md - Agent Instructions, Guardrails, & Architecture Rules
|
|
2
|
+
|
|
3
|
+
This repository implements a Django-Style Template Engine for Node.js/Express. To ensure high code quality, consistent architecture, and full feature parity with Django without guesswork, all AI agents and developers must strictly follow these instructions and guardrails.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Core Objectives
|
|
8
|
+
1. **Full Feature Parity**: Match Django's template engine rules for rendering variables, dotted lookups, built-in tags, filters, and template inheritance.
|
|
9
|
+
2. **Deterministic & Secure**: Escape output by default using a safe-string mechanism.
|
|
10
|
+
3. **No Spaghetti Code**: Maintain a strict separation of Lexer, Parser, Renderer, Context, and Tag/Filter registries.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ⚠️ Strict Guardrails & Anti-Hallucination Rules
|
|
15
|
+
|
|
16
|
+
> [!IMPORTANT]
|
|
17
|
+
> **No Guesswork / Assumptions**
|
|
18
|
+
> - If a tag or filter's behavior is not clearly defined in [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) or Django documentation, **DO NOT guess**.
|
|
19
|
+
> - If you notice ambiguities (e.g., how to handle circular extends or nested cycles), raise a question to the user immediately or throw a descriptive compilation error.
|
|
20
|
+
|
|
21
|
+
> [!WARNING]
|
|
22
|
+
> **No Regex-Based Global Search & Replace**
|
|
23
|
+
> - You **must not** implement tags or filters using global regex replacements on the final string. All templates must go through:
|
|
24
|
+
> `Lexer (String -> Tokens) -> Parser (Tokens -> AST) -> Renderer (AST + Context -> HTML)`.
|
|
25
|
+
> - Direct regex replacement on HTML bypasses nesting, escaping, and blocks, which introduces severe bugs.
|
|
26
|
+
|
|
27
|
+
> [!CAUTION]
|
|
28
|
+
> **Security Guardrails**
|
|
29
|
+
> - All variable output must be HTML-escaped by default.
|
|
30
|
+
> - A variable is only safe from escaping if it is marked as a `SafeString` (e.g., using the `safe` filter or internally flagged).
|
|
31
|
+
> - Dynamic expression evaluation (such as in `if` tags) **must not** use direct JS `eval()`. Use a safe AST evaluator or sandboxed parser to prevent remote code execution.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🧩 Architectural Guidelines
|
|
36
|
+
|
|
37
|
+
### 1. Lexing (`src/lexer.js`)
|
|
38
|
+
* Tokens must be generated for three primary types:
|
|
39
|
+
- `TEXT`: Plain HTML/text.
|
|
40
|
+
- `VAR`: Variables wrapped in `{{ ... }}`.
|
|
41
|
+
- `BLOCK`: Structural tags wrapped in `{% ... %}`.
|
|
42
|
+
* The Lexer must correctly handle verbatim blocks `{% verbatim %}` and comments `{% comment %}` by suppressing token output or passing raw text tokens.
|
|
43
|
+
|
|
44
|
+
### 2. Parsing (`src/parser.js`)
|
|
45
|
+
* The Parser takes a stream of tokens and constructs an AST of nodes.
|
|
46
|
+
* Every node must implement a `.render(context)` method (async or sync).
|
|
47
|
+
* For tags that have closing tags (e.g., `{% if %}`...`{% endif %}`), the parser must parse nested tokens recursively until the matching close tag is encountered.
|
|
48
|
+
|
|
49
|
+
### 3. Context & Scope Isolation (`src/context.js`)
|
|
50
|
+
* Context must support a stack-like structure: `.push()` to create a new scope, and `.pop()` to revert.
|
|
51
|
+
* **Variable Lookups**: Support dotted lookups:
|
|
52
|
+
- Example: `{{ user.profile.name }}` should search `user` in context, then resolve property `profile`, then property `name`.
|
|
53
|
+
- If a resolved value is a function, call it (without arguments, just like Django).
|
|
54
|
+
- If a lookup fails, return an empty string `""` by default, unless configured otherwise.
|
|
55
|
+
|
|
56
|
+
### 4. Template Inheritance (`src/tags/inheritance.js`)
|
|
57
|
+
* Inheritance works by having a child template load a parent template via `{% extends "parent.html" %}`.
|
|
58
|
+
* The child overrides blocks defined as `{% block block_name %}`.
|
|
59
|
+
* During render, the parent's AST is evaluated, but block nodes are replaced by the child's corresponding block nodes.
|
|
60
|
+
* Support block nesting and the `{{ block.super }}` variable to render parent block content.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🛠️ Step-by-Step Task Execution Protocol
|
|
65
|
+
|
|
66
|
+
When executing tasks:
|
|
67
|
+
1. **Analyze Requirements**: Check [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) and [component.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/component.md).
|
|
68
|
+
2. **Check Existing Tests**: Run current test suite using `npm test` or `node --test` to ensure a green state.
|
|
69
|
+
3. **Write Tests First**: For any new filter, tag, or parser logic, write a corresponding unit test in `tests/` showing the expected template string and its expected output.
|
|
70
|
+
4. **Implement Modular Code**: Put tag handlers in `src/tags/`, filters in `src/filters.js`, and maintain registry isolation.
|
|
71
|
+
5. **Verify**: Ensure the test suite passes, check for escaping vulnerabilities, and check that no scope leaks occur.
|