@rhinostone/swig 2.6.0 → 2.7.1

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/HISTORY.md CHANGED
@@ -1,3 +1,15 @@
1
+ [2.7.1](https://github.com/gina-io/swig/tree/v2.7.1) / 2026-06-08
2
+ -----------------------------------------------------------------
3
+
4
+ * **Security** The filesystem loader now confines `include` / `extends` / `import` path resolution to the configured loader root. When a `basepath` is set, template paths that resolve outside that root — including paths supplied by an untrusted runtime variable such as `{% include userVar %}` — are rejected, closing a directory-traversal arbitrary-file-read vector (CVE-2023-25345). A new `allowOutsideRoot` loader option opts out for templates that intentionally read files from outside the root.
5
+
6
+ [2.7.0](https://github.com/gina-io/swig/tree/v2.7.0) / 2026-06-07
7
+ -----------------------------------------------------------------
8
+
9
+ * **Added** `@rhinostone/swig-django` is a new Django Template Language frontend for the `@rhinostone/swig-core` engine — the fourth flavor in the multi-flavor family alongside the native, Twig, and Jinja2 frontends. It renders real Django templates: the full built-in tag set (`for`/`empty` with `forloop`, `if`/`elif`/`else`, `block`/`extends`/`include`, `with`, `autoescape`, `spaceless`, `comment`, `verbatim`, `cycle`, `firstof`), ~42 built-in filters with colon-argument syntax such as `{{ value|date:"Y-m-d" }}`, a Django-faithful variable resolver (auto-calls callable attributes while honoring `alters_data`/`do_not_call_in_templates`, numeric indexing like `{{ list.0 }}`, and `.keys`/`.values`/`.items` dict iteration), and async loader support via `renderFile(path, locals, cb)`. Installs as `npm install @rhinostone/swig-django`.
10
+
11
+ * **Fixed** De-linked the dead `paularmstrong/swig` issue and pull-request references in `HISTORY.md` and the matching `.changes/v*.md` fragments (2026-06-01 link-health scan, gina-io/gina#33). The upstream repository remains but its issue tracker is disabled, so the inline `[gh-N](...)` links are now plain `gh-N` text — every reference is preserved for historical traceability, and the fragments are edited in lockstep with `HISTORY.md` to keep `changie merge` idempotent. Documentation / changelog only; no runtime or API change.
12
+
1
13
  [2.6.0](https://github.com/gina-io/swig/tree/v2.6.0) / 2026-06-03
2
14
  -----------------------------------------------------------------
3
15
 
@@ -327,29 +339,29 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
327
339
  [1.0.0-pre3](https://github.com/paularmstrong/swig/tree/v1.0.0-pre3) / 2013-08-20
328
340
  ---------------------------------------------------------------------------------
329
341
 
330
- * **Changed** Allow tags at block-level if specified. [gh-289](https://github.com/paularmstrong/swig/issues/289)
331
- * **Fixed** `swig.compileFile` runs callback template is found in cache. [gh-291](https://github.com/paularmstrong/swig/issues/291)
332
- * **Fixed** Accidental modification of Swig Options Object. [gh-287](https://github.com/paularmstrong/swig/issues/287)
333
- * **Fixed** Preserve forward-slashes in text chunks. [gh-285](https://github.com/paularmstrong/swig/issues/285)
342
+ * **Changed** Allow tags at block-level if specified. gh-289
343
+ * **Fixed** `swig.compileFile` runs callback template is found in cache. gh-291
344
+ * **Fixed** Accidental modification of Swig Options Object. gh-287
345
+ * **Fixed** Preserve forward-slashes in text chunks. gh-285
334
346
 
335
347
  [1.0.0-pre2](https://github.com/paularmstrong/swig/tree/v1.0.0-pre2) / 2013-08-18
336
348
  ---------------------------------------------------------------------------------
337
349
 
338
350
  * **Changed** Binary: Allow --method-name to be a shortcut for --wrap-start var setting.
339
351
  * **Changed** Make reverse filter an alias for `sort(true)`.
340
- * **Added** Allow asyncronous `compileFile` and `renderFile` operations. [gh-283](https://github.com/paularmstrong/swig/issues/283)
352
+ * **Added** Allow asyncronous `compileFile` and `renderFile` operations. gh-283
341
353
  * **Added** Filter: `sort`.
342
- * **Added** Allow {% end[tag] tokens... %}. [gh-278](https://github.com/paularmstrong/swig/issues/278)
354
+ * **Added** Allow {% end[tag] tokens... %}. gh-278
343
355
  * **Added** Built source map for minified browser source.
344
- * **Added** Contextual support for object method calls. [gh-275](https://github.com/paularmstrong/swig/issues/275)
345
- * **Added** `parser.on('start'|'end'...` options. [gh-274](https://github.com/paularmstrong/swig/issues/274)
346
- * **Added** Allow object prototypal inheritance. [gh-273](https://github.com/paularmstrong/swig/issues/273)
347
- * **Fixed** Prevent circular extends. [gh-282](https://github.com/paularmstrong/swig/issues/282)
348
- * **Fixed** Throw an error if reserved word is used as var. [gh-276](https://github.com/paularmstrong/swig/issues/276)
349
- * **Fixed** Add filename to errors if possible. [gh-280](https://github.com/paularmstrong/swig/issues/280)
350
- * **Fixed** Filters work over arrays/objects if possible. [gh-259](https://github.com/paularmstrong/swig/issues/259)
351
- * **Fixed** Allow {% parent %} to work in middle parent templates. [gh-277](https://github.com/paularmstrong/swig/issues/277)
352
- * **Fixed** Allow newlines in tags/vars/comments. [gh-272](https://github.com/paularmstrong/swig/issues/272)
356
+ * **Added** Contextual support for object method calls. gh-275
357
+ * **Added** `parser.on('start'|'end'...` options. gh-274
358
+ * **Added** Allow object prototypal inheritance. gh-273
359
+ * **Fixed** Prevent circular extends. gh-282
360
+ * **Fixed** Throw an error if reserved word is used as var. gh-276
361
+ * **Fixed** Add filename to errors if possible. gh-280
362
+ * **Fixed** Filters work over arrays/objects if possible. gh-259
363
+ * **Fixed** Allow {% parent %} to work in middle parent templates. gh-277
364
+ * **Fixed** Allow newlines in tags/vars/comments. gh-272
353
365
 
354
366
  [1.0.0-pre1](https://github.com/paularmstrong/swig/tree/v1.0.0-pre1) / 2013-08-14
355
367
  ---------------------------------------------------------------------------------
@@ -370,57 +382,57 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
370
382
  * **Changed** allow `_`, `$` to start var names in templates.
371
383
  * **Changed** Documentation is auto-generated from jsdoc comments in-files.
372
384
  * **Added** Ability to set custom var/tag/comment controls (`{{`, `}}`, etc, can be customized).
373
- * **Added** Variable/string concatenation [gh-135](https://github.com/paularmstrong/swig/issues/135).
385
+ * **Added** Variable/string concatenation gh-135.
374
386
  * **Added** Binary application for `compile`, `run`, and `render` (Lets you pre-compile templates into JS functions for client-side delivery).
375
387
  * **Fixed** Lots.
376
388
 
377
389
  [0.14.0](https://github.com/paularmstrong/swig/tree/v0.14.0) / 2013-06-08
378
390
  -------------------------------------------------------------------------
379
391
 
380
- * **Added** Allow executing functions from within templates [gh-182](https://github.com/paularmstrong/swig/pull/182)
381
- * **Added** New `spaceless` tag [gh-193](https://github.com/paularmstrong/swig/pull/193)
382
- * **Fixed** bug when attempting to loop over nested vars with `for`. [gh-232](https://github.com/paularmstrong/swig/pull/232)
392
+ * **Added** Allow executing functions from within templates gh-182
393
+ * **Added** New `spaceless` tag gh-193
394
+ * **Fixed** bug when attempting to loop over nested vars with `for`. gh-232
383
395
 
384
396
  [0.13.5](https://github.com/paularmstrong/swig/tree/v0.13.5) / 2013-01-29
385
397
  -------------------------------------------------------------------------
386
398
 
387
- * **Fixed** date filter output for 'O' when time-zone offset is negative [gh-185](https://github.com/paularmstrong/swig/pull/185)
399
+ * **Fixed** date filter output for 'O' when time-zone offset is negative gh-185
388
400
 
389
401
  [0.13.4](https://github.com/paularmstrong/swig/tree/v0.13.4) / 2012-12-19
390
402
  -------------------------------------------------------------------------
391
403
 
392
- * **Fixed** Runaway loop on missing template [gh-162](https://github.com/paularmstrong/swig/pull/162) [gh-165](https://github.com/paularmstrong/swig/pull/165)
393
- * **Fixed** Allow variables in if tag conditionals to have filters with arguments [gh-167](https://github.com/paularmstrong/swig/pull/167)
404
+ * **Fixed** Runaway loop on missing template gh-162 gh-165
405
+ * **Fixed** Allow variables in if tag conditionals to have filters with arguments gh-167
394
406
 
395
407
  [0.13.3](https://github.com/paularmstrong/swig/tree/v0.13.3) / 2012-12-07
396
408
  -------------------------------------------------------------------------
397
409
 
398
- * **Added** Support % (modulus) in if tags [gh-155](https://github.com/paularmstrong/swig/pull/155)
399
- * **Added** Support multi-root via array [gh-143](https://github.com/paularmstrong/swig/pull/143)
410
+ * **Added** Support % (modulus) in if tags gh-155
411
+ * **Added** Support multi-root via array gh-143
400
412
 
401
413
  [0.13.2](https://github.com/paularmstrong/swig/tree/v0.13.2) / 2012-10-28
402
414
  -------------------------------------------------------------------------
403
415
 
404
- * **Changed** Allow variables, filters, arguments to span lines [gh-122](https://github.com/paularmstrong/swig/issues/122)
405
- * **Changed** Throw Errors when using undefined filters [gh-115](https://github.com/paularmstrong/swig/issues/115)
406
- * **Fixed** compiling files from absolute paths [gh-103](https://github.com/paularmstrong/swig/issues/103)
407
- * **Fixed** Prevent global variables from being used before context variables [gh-117](https://github.com/paularmstrong/swig/issues/117)
416
+ * **Changed** Allow variables, filters, arguments to span lines gh-122
417
+ * **Changed** Throw Errors when using undefined filters gh-115
418
+ * **Fixed** compiling files from absolute paths gh-103
419
+ * **Fixed** Prevent global variables from being used before context variables gh-117
408
420
 
409
421
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.13.2/docs)
410
422
 
411
423
  [0.13.1](https://github.com/paularmstrong/swig/tree/v0.13.1) / 2012-10-28
412
424
  -------------------------------------------------------------------------
413
425
 
414
- * **Fixed** Macros should be preserved when using inheritence [gh-132](https://github.com/paularmstrong/swig/issues/132) ([nsaun](https://github.com/nsaun))
415
- * **Fixed** bug in parent tag logic [gh-130](https://github.com/paularmstrong/swig/issues/130)
416
- * **Fixed** Error messaging when parent block failed compilation [gh-129](https://github.com/paularmstrong/swig/issues/129) ([nsaun](https://github.com/nsaun))
426
+ * **Fixed** Macros should be preserved when using inheritence gh-132 ([nsaun](https://github.com/nsaun))
427
+ * **Fixed** bug in parent tag logic gh-130
428
+ * **Fixed** Error messaging when parent block failed compilation gh-129 ([nsaun](https://github.com/nsaun))
417
429
 
418
430
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.13.1/docs)
419
431
 
420
432
  [0.13.0](https://github.com/paularmstrong/swig/tree/v0.13.0) / 2012-10-20
421
433
  -------------------------------------------------------------------------
422
434
 
423
- * **Added** Support for nested blocks! [gh-64](https://github.com/paularmstrong/swig/issues/64) [gh-129](https://github.com/paularmstrong/swig/issues/129) ([nsaun](https://github.com/nsaun))
435
+ * **Added** Support for nested blocks! gh-64 gh-129 ([nsaun](https://github.com/nsaun))
424
436
  * **Changed** Removed the `parentBlock` argument from tags.
425
437
  * **Fixed** Object keys may now contain dots
426
438
 
@@ -449,28 +461,28 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
449
461
  [0.11.2](https://github.com/paularmstrong/swig/tree/v0.11.2) / 2012-04-10
450
462
  -------------------------------------------------------------------------
451
463
 
452
- * **Fixed** Update support for underscore@1.3.3 [gh-70](https://github.com/paularmstrong/swig/issues/70) [gh-71](https://github.com/paularmstrong/swig/issues/71)
464
+ * **Fixed** Update support for underscore@1.3.3 gh-70 gh-71
453
465
 
454
466
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.11.2/docs)
455
467
 
456
468
  [0.11.1](https://github.com/paularmstrong/swig/tree/v0.11.1) / 2012-04-01
457
469
  -------------------------------------------------------------------------
458
470
 
459
- * **Fixed** Duplicate (string) tokens were being removed when extending a base template. [gh-67](https://github.com/paularmstrong/swig/issues/67)
471
+ * **Fixed** Duplicate (string) tokens were being removed when extending a base template. gh-67
460
472
 
461
473
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.11.1/docs)
462
474
 
463
475
  [0.11.0](https://github.com/paularmstrong/swig/tree/v0.11.0) / 2012-02-27
464
476
  -------------------------------------------------------------------------
465
477
 
466
- * **Added** Support for Windows style paths [gh-57](https://github.com/paularmstrong/swig/issues/57)
478
+ * **Added** Support for Windows style paths gh-57
467
479
  * **Added** `ignore missing` tokens to include tag
468
480
  * **Changed** include tag `with context` to only work if `context` is an object
469
481
  * **Changed** `autoescape` tag controls no longer 'yes' or 'no'. Use `true` and `false`
470
482
  * **Changed** parser is now passed into tags as an argument
471
483
  * **Changed** don't require passing context object when rendering template
472
- * **Fixed** dateformats `N` and `w` [gh-59](https://github.com/paularmstrong/swig/issues/59)
473
- * **Fixed** number changing to string after add filter or set from variable [gh-53](https://github.com/paularmstrong/swig/issues/53) [gh-58](https://github.com/paularmstrong/swig/issues/58)
484
+ * **Fixed** dateformats `N` and `w` gh-59
485
+ * **Fixed** number changing to string after add filter or set from variable gh-53 gh-58
474
486
  * **Fixed** speed decrease caused by loop.cycle fixed
475
487
  * **Fixed** Ensure set tag bubbles through extends and blocks
476
488
 
@@ -479,22 +491,22 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
479
491
  [0.10.0](https://github.com/paularmstrong/swig/tree/v0.10.0) / 2012-02-13
480
492
  -------------------------------------------------------------------------
481
493
 
482
- * **Added** loop.index0, loop.revindex, loop.revindex0, and loop.cycle [gh-48](https://github.com/paularmstrong/swig/issues/48)
483
- * **Added** init config `extensions` for 3rd party extension access in custom tags [gh-44](https://github.com/paularmstrong/swig/issues/44)
484
- * **Added** Whitespace Control [gh-46](https://github.com/paularmstrong/swig/issues/46)
485
- * **Changed** The `empty` tag in `for` loops is now `else` [gh-49](https://github.com/paularmstrong/swig/issues/49)
486
- * **Changed** `forloop` vars to `loop` closes [gh-47](https://github.com/paularmstrong/swig/issues/47)
487
- * **Fixed** `include` tag's `with` and `only` args documentation [gh-50](https://github.com/paularmstrong/swig/issues/50)
494
+ * **Added** loop.index0, loop.revindex, loop.revindex0, and loop.cycle gh-48
495
+ * **Added** init config `extensions` for 3rd party extension access in custom tags gh-44
496
+ * **Added** Whitespace Control gh-46
497
+ * **Changed** The `empty` tag in `for` loops is now `else` gh-49
498
+ * **Changed** `forloop` vars to `loop` closes gh-47
499
+ * **Fixed** `include` tag's `with` and `only` args documentation gh-50
488
500
 
489
501
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.10.0/docs)
490
502
 
491
503
  [0.9.4](https://github.com/paularmstrong/swig/tree/v0.9.4) / 2012-02-07
492
504
  -----------------------------------------------------------------------
493
505
 
494
- * **Fixed** `parent` tag would not render when called within tags [gh-41](https://github.com/paularmstrong/swig/issues/41)
495
- * **Fixed** Documentation for forloop.index & forloop.key [gh-42](https://github.com/paularmstrong/swig/issues/42)
496
- * **Fixed** Errors when using `include` inside base template `block` tags [gh-43](https://github.com/paularmstrong/swig/issues/43)
497
- * **Fixed** Allow `set` tag to set values to numbers [gh-45](https://github.com/paularmstrong/swig/issues/45)
506
+ * **Fixed** `parent` tag would not render when called within tags gh-41
507
+ * **Fixed** Documentation for forloop.index & forloop.key gh-42
508
+ * **Fixed** Errors when using `include` inside base template `block` tags gh-43
509
+ * **Fixed** Allow `set` tag to set values to numbers gh-45
498
510
  * **Fixed** `set` tag for booleans using too many checks
499
511
 
500
512
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.9.4/docs)
@@ -502,21 +514,21 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
502
514
  [0.9.3](https://github.com/paularmstrong/swig/tree/v0.9.3) / 2012-01-28
503
515
  -----------------------------------------------------------------------
504
516
 
505
- * **Fixed** Allow object and array values to be accessed via context variables [gh-40](https://github.com/paularmstrong/swig/issues/40)
517
+ * **Fixed** Allow object and array values to be accessed via context variables gh-40
506
518
 
507
519
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.9.3/docs)
508
520
 
509
521
  [0.9.2](https://github.com/paularmstrong/swig/tree/v0.9.2) / 2012-01-23
510
522
  -----------------------------------------------------------------------
511
523
 
512
- * **Fixed** Correctly reset autoescape after closing an autoescape tag. [gh-39](https://github.com/paularmstrong/swig/issues/39)
524
+ * **Fixed** Correctly reset autoescape after closing an autoescape tag. gh-39
513
525
 
514
526
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.9.2/docs)
515
527
 
516
528
  [0.9.1](https://github.com/paularmstrong/swig/tree/v0.9.1) / 2012-01-18
517
529
  -----------------------------------------------------------------------
518
530
 
519
- * **Fixed** Allow multi-line tags and comments. [gh-30](https://github.com/paularmstrong/swig/issues/30)
531
+ * **Fixed** Allow multi-line tags and comments. gh-30
520
532
 
521
533
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.9.1/docs)
522
534
 
@@ -524,7 +536,7 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
524
536
  -----------------------------------------------------------------------
525
537
 
526
538
  * **Added** DateZ license to browser header, use link to underscore license.
527
- * **Added** Timezone support in `date` filter [gh-27](https://github.com/paularmstrong/swig/issues/27).
539
+ * **Added** Timezone support in `date` filter gh-27.
528
540
  * **Added** New `raw` tag.
529
541
  * **Changed** Swig is no longer node 0.4 compatible.
530
542
  * **Fixed** Filter `date('f')` for 10am times.
@@ -546,18 +558,18 @@ Migrating from v0.x.x? The upstream wiki has since been deleted; see the individ
546
558
  * **Changed** `swig.init()` will clear template cache.
547
559
  * **Changed** `swig.init()` is now optional for browser mode with no custom settings.
548
560
  * **Changed** Development dependencies are be more lenient.
549
- * **Fixed** Parser will properly preserver '\' escaping. [gh-24](https://github.com/paularmstrong/swig/issues/24)
561
+ * **Fixed** Parser will properly preserver '\' escaping. gh-24
550
562
  * **Fixed** Rewrote tag argument parsing for proper space handling.
551
- * **Fixed** Rewrote filter argument parsing. [gh-23](https://github.com/paularmstrong/swig/issues/23)
552
- * **Fixed** Allow pipe `|` characters in filter arguments. [gh-22](https://github.com/paularmstrong/swig/issues/22)
563
+ * **Fixed** Rewrote filter argument parsing. gh-23
564
+ * **Fixed** Allow pipe `|` characters in filter arguments. gh-22
553
565
 
554
566
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.8.0/docs)
555
567
 
556
568
  [0.7.0](https://github.com/paularmstrong/swig/tree/v0.7.0) / 2011-10-05
557
569
  -----------------------------------------------------------------------
558
570
 
559
- * **Added** `make browser` will build Swig for use in major browsers. [gh-3](https://github.com/paularmstrong/swig/issues/3)
560
- * **Changed** Allow overriding `escape` filters. [gh-19](https://github.com/paularmstrong/swig/issues/19)
571
+ * **Added** `make browser` will build Swig for use in major browsers. gh-3
572
+ * **Changed** Allow overriding `escape` filters. gh-19
561
573
 
562
574
  [Documentation](https://github.com/paularmstrong/swig/tree/v0.7.0/docs)
563
575
 
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  Swig
2
2
  ====
3
3
 
4
- [![CI](https://github.com/gina-io/swig/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/gina-io/swig/actions/workflows/ci.yml) [![NPM version](http://img.shields.io/npm/v/@rhinostone/swig.svg?style=flat)](https://www.npmjs.com/package/@rhinostone/swig) [![NPM Downloads](http://img.shields.io/npm/dm/@rhinostone/swig.svg?style=flat)](https://www.npmjs.com/package/@rhinostone/swig) [![Socket Badge](https://socket.dev/api/badge/npm/package/@rhinostone/swig)](https://socket.dev/npm/package/@rhinostone/swig)
4
+ [![CI](https://github.com/gina-io/swig/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/gina-io/swig/actions/workflows/ci.yml) [![NPM version](https://img.shields.io/npm/v/@rhinostone/swig.svg?style=flat)](https://www.npmjs.com/package/@rhinostone/swig) [![NPM Downloads](https://img.shields.io/npm/dm/@rhinostone/swig.svg?style=flat)](https://www.npmjs.com/package/@rhinostone/swig) [![Socket Badge](https://socket.dev/api/badge/npm/package/@rhinostone/swig)](https://socket.dev/npm/package/@rhinostone/swig)
5
5
 
6
- > **Multi-flavor template engine** for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired), Twig syntax, and Python Jinja2 syntax via dedicated frontends sharing one IR backend. [gina-io/swig](https://github.com/gina-io/swig) started as a maintained continuation of the abandoned [paularmstrong/swig](https://github.com/paularmstrong/swig) (last released 2014) and is now a standalone project. Security and bug fixes ship here.
6
+ > **Multi-flavor template engine** for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired), Twig syntax, Python Jinja2 syntax, and Django Template Language syntax via dedicated frontends sharing one IR backend. [gina-io/swig](https://github.com/gina-io/swig) started as a maintained continuation of the abandoned [paularmstrong/swig](https://github.com/paularmstrong/swig) (last released 2014) and is now a standalone project. Security and bug fixes ship here.
7
7
 
8
8
  > **Part of the [Gina](https://github.com/gina-io/gina) ecosystem.** This is the built-in template engine for [Gina](https://gina.io) ([npm](https://www.npmjs.com/package/gina)), a Node.js MVC framework with HTTP/2, multi-bundle architecture, and scope-based data isolation.
9
9
 
@@ -13,6 +13,8 @@ Swig is a **Jinja2/Django-inspired** template engine for node.js and browsers. T
13
13
 
14
14
  > **Coming from Python Jinja2?** Install [@rhinostone/swig-jinja2](https://www.npmjs.com/package/@rhinostone/swig-jinja2) — a dedicated Jinja2-syntax frontend (near-subset) with closer parity than porting to native Swig syntax here.
15
15
 
16
+ > **Coming from Django?** Install [@rhinostone/swig-django](https://www.npmjs.com/package/@rhinostone/swig-django) — a dedicated Django Template Language frontend that renders real Django templates, with far closer parity than native Swig syntax here.
17
+
16
18
  Workspace packages
17
19
  ------------------
18
20
 
@@ -21,6 +23,7 @@ Workspace packages
21
23
  | [`@rhinostone/swig`](https://www.npmjs.com/package/@rhinostone/swig) | Native Swig syntax (Jinja2/Django-inspired). Drop-in for `@rhinostone/swig@1.x` consumers. | Upgrading from `@rhinostone/swig@1.x`, or starting fresh with Swig syntax. |
22
24
  | [`@rhinostone/swig-twig`](https://www.npmjs.com/package/@rhinostone/swig-twig) | Twig-syntax frontend with closer Twig parity. | Migrating from PHP Twig, or writing new templates in Twig syntax. |
23
25
  | [`@rhinostone/swig-jinja2`](https://www.npmjs.com/package/@rhinostone/swig-jinja2) | Python Jinja2-syntax frontend (near-subset). | Migrating from Python Jinja2, or writing new templates in Jinja2 syntax. |
26
+ | [`@rhinostone/swig-django`](https://www.npmjs.com/package/@rhinostone/swig-django) | Django Template Language frontend (real DTL). | Migrating from Django, or writing new templates in Django syntax. |
24
27
  | [`@rhinostone/swig-core`](https://www.npmjs.com/package/@rhinostone/swig-core) | Shared IR, backend, and runtime primitives. | Building a custom flavor frontend. Otherwise pulled in transitively. |
25
28
 
26
29
  Each frontend pins the matching `@rhinostone/swig-core` version exactly (no caret, no tilde) — frontends and the core release in lockstep on every cut.
@@ -68,6 +71,10 @@ For Python Jinja2 syntax:
68
71
 
69
72
  npm install @rhinostone/swig-jinja2
70
73
 
74
+ For Django syntax:
75
+
76
+ npm install @rhinostone/swig-django
77
+
71
78
  Documentation
72
79
  -------------
73
80
 
@@ -116,7 +123,7 @@ Migrating from `@rhinostone/swig@1.x`
116
123
 
117
124
  `@rhinostone/swig@2.x` is **drop-in for `1.x` consumers** — `swig.compileFile`, `swig.renderFile`, `swig.setFilter`, `swig.setTag`, and the rest of the public API are unchanged. The internal carve into [@rhinostone/swig-core](https://www.npmjs.com/package/@rhinostone/swig-core) is transparent (test gate during the alpha cycle: byte-identical compiled output against the `1.x` test suite).
118
125
 
119
- `2.0.0` also ships [@rhinostone/swig-twig](https://www.npmjs.com/package/@rhinostone/swig-twig), a sibling Twig-syntax frontend, and `2.5.0` adds [@rhinostone/swig-jinja2](https://www.npmjs.com/package/@rhinostone/swig-jinja2) for Python Jinja2 syntax. Switching is opt-in — your existing `@rhinostone/swig` install keeps working.
126
+ `2.0.0` also ships [@rhinostone/swig-twig](https://www.npmjs.com/package/@rhinostone/swig-twig), a sibling Twig-syntax frontend; `2.5.0` adds [@rhinostone/swig-jinja2](https://www.npmjs.com/package/@rhinostone/swig-jinja2) for Python Jinja2 syntax; and `2.7.0` adds [@rhinostone/swig-django](https://www.npmjs.com/package/@rhinostone/swig-django) for Django Template Language syntax. Switching is opt-in — your existing `@rhinostone/swig` install keeps working.
120
127
 
121
128
  Migrating from Jinja2 or Django
122
129
  -------------------------------
@@ -139,7 +146,7 @@ How it works
139
146
 
140
147
  Swig reads template files and translates them into cached JavaScript functions. The pipeline is: parse → emit IR → lower IR to JS source → `new Function(...)`. At render time, the compiled function runs against a context object to produce the output string.
141
148
 
142
- In `2.x`, frontend parsers (native Swig syntax in [@rhinostone/swig](https://www.npmjs.com/package/@rhinostone/swig), Twig syntax in [@rhinostone/swig-twig](https://www.npmjs.com/package/@rhinostone/swig-twig), Python Jinja2 syntax in [@rhinostone/swig-jinja2](https://www.npmjs.com/package/@rhinostone/swig-jinja2)) emit a shared intermediate representation. The backend in [@rhinostone/swig-core](https://www.npmjs.com/package/@rhinostone/swig-core) lowers IR to JS. New flavors plug in at the frontend without touching the runtime.
149
+ In `2.x`, frontend parsers (native Swig syntax in [@rhinostone/swig](https://www.npmjs.com/package/@rhinostone/swig), Twig syntax in [@rhinostone/swig-twig](https://www.npmjs.com/package/@rhinostone/swig-twig), Python Jinja2 syntax in [@rhinostone/swig-jinja2](https://www.npmjs.com/package/@rhinostone/swig-jinja2), Django Template Language syntax in [@rhinostone/swig-django](https://www.npmjs.com/package/@rhinostone/swig-django)) emit a shared intermediate representation. The backend in [@rhinostone/swig-core](https://www.npmjs.com/package/@rhinostone/swig-core) lowers IR to JS. New flavors plug in at the frontend without touching the runtime.
143
150
 
144
151
  License
145
152
  -------
package/ROADMAP.md CHANGED
@@ -15,13 +15,23 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
15
15
  | Status | Item |
16
16
  | --- | --- |
17
17
  | Planned | Async parse path for the remaining dynamic targets — runtime-resolved `{% import %}` / `{% from %}` paths on the async-codegen branch. Static-target async dispatch shipped in 2.2.0; dynamic `{% extends %}` shipped in 2.5.2 (native + Twig + Jinja2) and dynamic `{% include %}` paths already resolve (the include path has always been an expression). Dynamic `import` / `from` are on hold pending consumer demand. |
18
- | Planned | Ship a Django frontend as an additional `@rhinostone/swig-*` package. On demand — when there's concrete user demand. (The Jinja2 frontend shipped in `2.5.0`.) |
19
18
  | Planned | Modern browser-test harness. The legacy phantomjs runner was removed; a replacement (e.g. Playwright or JSDOM) has not yet landed, so browser parity is verified in the interim via the production build plus a symbol grep. (The `mocha` → `node:test` runner migration, the `expect.js` → in-repo assertion-shim swap, and the `blanket` → `node:test` built-in coverage migration have all shipped.) |
20
19
 
21
20
  ---
22
21
 
23
22
  ## Completed
24
23
 
24
+ ### v2.7.1 (June 2026)
25
+
26
+ - Hardened the filesystem loader against directory traversal / arbitrary local file read (CVE-2023-25345). When a `basepath` is configured, `include` / `extends` / `import` paths that resolve outside that root are now rejected — including paths supplied by an untrusted runtime variable such as `{% include userVar %}`, which could otherwise be steered to read files anywhere on disk (`{% include "../../../etc/passwd" %}`). A new third argument, `swig.loaders.fs(basepath, encoding, allowOutsideRoot)`, opts out for templates that intentionally read files from outside the root. In-root paths — including relative paths that stay within the configured root — are unaffected; only paths that escape the root are rejected, and the no-`basepath` default is unchanged. The fix lives in `@rhinostone/swig-core`, so the native, Twig, Jinja2, and Django frontends all inherit it.
27
+ - All five packages (`@rhinostone/swig`, `@rhinostone/swig-core`, `@rhinostone/swig-twig`, `@rhinostone/swig-jinja2`, `@rhinostone/swig-django`) released in lockstep at `2.7.1`. The functional change is confined to the `@rhinostone/swig-core` filesystem loader.
28
+
29
+ ### v2.7.0 (June 2026)
30
+
31
+ - Added `@rhinostone/swig-django`, a Django Template Language frontend on the shared `@rhinostone/swig-core` engine — the fourth dialect in the multi-flavor family alongside native swig, `@rhinostone/swig-twig`, and `@rhinostone/swig-jinja2`. It renders real Django templates: 15 tags (`if` / `elif` / `else`, `for` / `empty` with `forloop`, `block`, `extends`, `include`, `with`, `autoescape`, `spaceless`, `comment`, `verbatim`, `cycle`, `firstof`), 42 built-in filters with Django's colon-argument syntax (`{{ value|date:"Y-m-d" }}`), and a Django-faithful variable resolver — callable attributes are auto-called (honoring `alters_data` / `do_not_call_in_templates`), numeric indexing works (`{{ list.0 }}`), and dicts iterate via `.keys` / `.values` / `.items`. Async loader support via `renderFileAsync` / `compileFileAsync` and `renderFile(path, locals, cb)` against an async loader. Autoescape and the CVE-2023-25345 guards are inherited from `@rhinostone/swig-core`. Every tag and filter was cross-checked against Django 5.2; the behavioural differences and explicit non-goals (no `{% load %}` / custom tag libraries, no `{% url %}` / `{% static %}` / `{% csrf_token %}` / `{% trans %}` / `{% blocktrans %}` framework infrastructure, no whitespace-control, no `is` tests) are documented in the Django templating guide.
32
+ - All five packages (`@rhinostone/swig`, `@rhinostone/swig-core`, `@rhinostone/swig-twig`, `@rhinostone/swig-jinja2`, `@rhinostone/swig-django`) released in lockstep at `2.7.0`. `@rhinostone/swig-core` gained an additive opt-in loop-context flag and a variable-resolver primitive (`_utils.resolve`) consumed by the Django frontend; native swig, Twig, and Jinja2 are functionally unchanged (proven byte-identical compiled output).
33
+ - Documentation housekeeping: de-linked the dead `paularmstrong/swig` issue and pull-request references in `HISTORY.md` (the upstream issue tracker is disabled), preserving every reference as plain text.
34
+
25
35
  ### v2.6.0 (June 2026)
26
36
 
27
37
  - The native `json` / `json_encode` filters now HTML-escape their output (`<`, `>`, `&`, and `'` are emitted as `\u00XX` escapes) and are marked safe, so `{{ data|json }}` renders valid JSON that can be embedded directly inside a `<script>` block instead of `&quot;`-escaped text. `url_encode` is now marked safe as well — its output never contains HTML-significant characters. `url_decode` is deliberately unchanged: its output stays autoescaped, since decoded content can contain HTML. Only the native `@rhinostone/swig` flavor changes here — `@rhinostone/swig-twig` keeps `json_encode` / `url_encode` unescaped (Twig-faithful), and `@rhinostone/swig-jinja2` keeps its `tojson` (safe) / `urlencode` (not safe) split (Jinja2-faithful).
package/dist/swig.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! Swig v2.6.0 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
1
+ /*! Swig v2.7.1 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
2
2
  /*! DateZ (c) 2011 Tomo Universalis | @license https://github.com/ocrybit/DateZ/blob/master/LISENCE */
3
3
  (() => {
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -6,9 +6,17 @@
6
6
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7
7
  };
8
8
 
9
+ // packages/swig-core/lib/security.js
10
+ var require_security = __commonJS({
11
+ "packages/swig-core/lib/security.js"(exports) {
12
+ exports.dangerousProps = ["__proto__", "constructor", "prototype"];
13
+ }
14
+ });
15
+
9
16
  // packages/swig-core/lib/utils.js
10
17
  var require_utils = __commonJS({
11
18
  "packages/swig-core/lib/utils.js"(exports) {
19
+ var security = require_security();
12
20
  var isArray;
13
21
  exports.strip = function(input) {
14
22
  return input.replace(/^\s+|\s+$/g, "");
@@ -201,6 +209,40 @@
201
209
  exports.coerceOutput = function(v) {
202
210
  return v === null || v === void 0 ? "" : v;
203
211
  };
212
+ function resolveSeg(obj, seg) {
213
+ var val;
214
+ if (obj === null || obj === void 0) {
215
+ return void 0;
216
+ }
217
+ if (security.dangerousProps.indexOf(seg) !== -1) {
218
+ return void 0;
219
+ }
220
+ val = obj[seg];
221
+ if (typeof val === "function") {
222
+ if (val.alters_data === true) {
223
+ return void 0;
224
+ }
225
+ if (val.do_not_call_in_templates === true) {
226
+ return val;
227
+ }
228
+ try {
229
+ return val.call(obj);
230
+ } catch (e) {
231
+ return void 0;
232
+ }
233
+ }
234
+ return val;
235
+ }
236
+ exports.resolve = function(obj, path) {
237
+ var cur = obj, i;
238
+ for (i = 0; i < path.length; i += 1) {
239
+ cur = resolveSeg(cur, path[i]);
240
+ if (cur === null || cur === void 0) {
241
+ return cur;
242
+ }
243
+ }
244
+ return cur;
245
+ };
204
246
  }
205
247
  });
206
248
 
@@ -1229,13 +1271,6 @@
1229
1271
  }
1230
1272
  });
1231
1273
 
1232
- // packages/swig-core/lib/security.js
1233
- var require_security = __commonJS({
1234
- "packages/swig-core/lib/security.js"(exports) {
1235
- exports.dangerousProps = ["__proto__", "constructor", "prototype"];
1236
- }
1237
- });
1238
-
1239
1274
  // lib/tags/for.js
1240
1275
  var require_for = __commonJS({
1241
1276
  "lib/tags/for.js"(exports) {
@@ -1436,7 +1471,7 @@
1436
1471
  return;
1437
1472
  }
1438
1473
  if (node.type === "For") {
1439
- var forVal = node.value, forKey = node.key, forIterable, forBodyJS = "", ctxloopcache = ("_ctx.__loopcache" + Math.random()).replace(/\./g, ""), ctx = "_ctx.", ctxloop = "_ctx.loop";
1474
+ var forVal = node.value, forKey = node.key, forIterable, forBodyJS = "", ctxloopcache = ("_ctx.__loopcache" + Math.random()).replace(/\./g, ""), ctx = "_ctx.", loopName = node.loopName || "loop", ctxloop = "_ctx." + loopName, loopFields = node.loopFields || {}, fIndex = loopFields.index || "index", fIndex0 = loopFields.index0 || "index0", fRevindex = loopFields.revindex || "revindex", fRevindex0 = loopFields.revindex0 || "revindex0", parentloopJS = node.loopParent ? ", parentloop: " + ctxloopcache + "." + loopName : "";
1440
1475
  if (node.iterable && typeof node.iterable === "object" && typeof node.iterable.type === "string") {
1441
1476
  forIterable = exports.emitExpr(node.iterable);
1442
1477
  } else {
@@ -1467,7 +1502,7 @@
1467
1502
  });
1468
1503
  forEmptyCheck = " if (!__l || !__len) {\n" + forEmptyJS + " return;\n }\n";
1469
1504
  }
1470
- out += "(function () {\n var __l = " + forIterable + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n' + forEmptyCheck + " var " + ctxloopcache + " = { loop: " + ctxloop + ", " + forVal + ": " + ctx + forVal + ", " + forKey + ": " + ctx + forKey + " };\n " + ctxloop + " = { first: false, index: 1, index0: 0, revindex: __len, revindex0: __len - 1, length: __len, last: false };\n _utils.each(__l, function (" + forVal + ", " + forKey + ") {\n " + ctx + forVal + " = " + forVal + ";\n " + ctx + forKey + " = " + forKey + ";\n " + ctxloop + ".key = " + forKey + ";\n " + ctxloop + ".first = (" + ctxloop + ".index0 === 0);\n " + ctxloop + ".last = (" + ctxloop + ".revindex0 === 0);\n " + forBodyJS + " " + ctxloop + ".index += 1; " + ctxloop + ".index0 += 1; " + ctxloop + ".revindex -= 1; " + ctxloop + ".revindex0 -= 1;\n });\n " + ctxloop + " = " + ctxloopcache + ".loop;\n " + ctx + forVal + " = " + ctxloopcache + "." + forVal + ";\n " + ctx + forKey + " = " + ctxloopcache + "." + forKey + ";\n " + ctxloopcache + " = undefined;\n})();\n";
1505
+ out += "(function () {\n var __l = " + forIterable + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n' + forEmptyCheck + " var " + ctxloopcache + " = { " + loopName + ": " + ctxloop + ", " + forVal + ": " + ctx + forVal + ", " + forKey + ": " + ctx + forKey + " };\n " + ctxloop + " = { first: false, " + fIndex + ": 1, " + fIndex0 + ": 0, " + fRevindex + ": __len, " + fRevindex0 + ": __len - 1, length: __len, last: false" + parentloopJS + " };\n _utils.each(__l, function (" + forVal + ", " + forKey + ") {\n " + ctx + forVal + " = " + forVal + ";\n " + ctx + forKey + " = " + forKey + ";\n " + ctxloop + ".key = " + forKey + ";\n " + ctxloop + ".first = (" + ctxloop + "." + fIndex0 + " === 0);\n " + ctxloop + ".last = (" + ctxloop + "." + fRevindex0 + " === 0);\n " + forBodyJS + " " + ctxloop + "." + fIndex + " += 1; " + ctxloop + "." + fIndex0 + " += 1; " + ctxloop + "." + fRevindex + " -= 1; " + ctxloop + "." + fRevindex0 + " -= 1;\n });\n " + ctxloop + " = " + ctxloopcache + "." + loopName + ";\n " + ctx + forVal + " = " + ctxloopcache + "." + forVal + ";\n " + ctx + forKey + " = " + ctxloopcache + "." + forKey + ";\n " + ctxloopcache + " = undefined;\n})();\n";
1471
1506
  return;
1472
1507
  }
1473
1508
  if (node.type === "Macro") {
@@ -1835,6 +1870,9 @@
1835
1870
  utils.each(node.path, function(segment) {
1836
1871
  checkDangerousSegment(segment, d, node);
1837
1872
  });
1873
+ if (node.resolve) {
1874
+ return "_utils.resolve(_ctx, " + JSON.stringify(node.path) + ")";
1875
+ }
1838
1876
  return checkMatchExpr(node.path);
1839
1877
  }
1840
1878
  function emitVarRefExists(node, d) {
@@ -4091,17 +4129,25 @@
4091
4129
  "packages/swig-core/lib/loaders/filesystem.js"(exports, module) {
4092
4130
  var fs = require_fs();
4093
4131
  var path = require_path_browserify();
4094
- module.exports = function(basepath, encoding) {
4132
+ module.exports = function(basepath, encoding, allowOutsideRoot) {
4095
4133
  var ret = {};
4096
4134
  encoding = encoding || "utf8";
4097
4135
  basepath = basepath ? path.normalize(basepath) : null;
4098
4136
  ret.resolve = function(to, from) {
4137
+ var resolved, rootWithSep;
4099
4138
  if (basepath) {
4100
4139
  from = basepath;
4101
4140
  } else {
4102
4141
  from = from ? path.dirname(from) : process.cwd();
4103
4142
  }
4104
- return path.resolve(from, to);
4143
+ resolved = path.resolve(from, to);
4144
+ if (basepath && !allowOutsideRoot) {
4145
+ rootWithSep = path.normalize(basepath + path.sep);
4146
+ if (resolved !== basepath && resolved.indexOf(rootWithSep) !== 0) {
4147
+ throw new Error('Template "' + to + '" resolves outside the loader root "' + basepath + '" (CVE-2023-25345).');
4148
+ }
4149
+ }
4150
+ return resolved;
4105
4151
  };
4106
4152
  ret.load = function(identifier, cb) {
4107
4153
  if (!fs || cb && !fs.readFile || !fs.readFileSync) {
@@ -4711,7 +4757,7 @@
4711
4757
  var loaders = require_loaders2();
4712
4758
  var preWalker = require_pre_walker();
4713
4759
  var engine = require_engine();
4714
- exports.version = "2.6.0";
4760
+ exports.version = "2.7.1";
4715
4761
  var defaultOptions = {
4716
4762
  autoescape: true,
4717
4763
  varControls: ["{{", "}}"],
@@ -4931,6 +4977,20 @@
4931
4977
  window.swig = swig;
4932
4978
  }
4933
4979
  })();
4980
+ /*!
4981
+ * Resolve a single path segment against a value, Django-style. A plain
4982
+ * property access (`obj[seg]`) covers dictionary, attribute, and array /
4983
+ * string index lookup in one step (JS string-keys an array / string by a
4984
+ * numeric segment, so `["a"][0]` and `["a"]["0"]` both yield the element).
4985
+ * A callable leaf is auto-called with no arguments, bound to its receiver,
4986
+ * honoring Django's opt-outs: `fn.alters_data === true` is not called and
4987
+ * yields `undefined` (Django renders nothing for data-altering callables);
4988
+ * `fn.do_not_call_in_templates === true` is returned uncalled. A call that
4989
+ * throws (e.g. a method that needs arguments) yields `undefined`, mirroring
4990
+ * Django's `string_if_invalid` fallback on a failed auto-call. The
4991
+ * `_dangerousProps` segments are rejected at runtime as defense-in-depth.
4992
+ * @private
4993
+ */
4934
4994
  /*!
4935
4995
  * Attach `loc` to the node if provided, skipping the assignment otherwise
4936
4996
  * so consumers can tell "no source location available" from