@nci-gis/js-tmpl 0.0.1-beta.1 → 0.0.1-beta.2
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/README.md +99 -14
- package/package.json +15 -14
- package/src/config/defaults.js +1 -1
- package/src/config/loader.js +11 -2
- package/src/config/resolver.js +34 -26
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# js-tmpl
|
|
2
2
|
|
|
3
|
-
> A lightweight, deterministic file templating engine built on Handlebars
|
|
3
|
+
> A lightweight, deterministic file templating engine built on Handlebars.
|
|
4
|
+
> An explicit file templating engine for developers who care about **control, predictability, and composability**.
|
|
4
5
|
|
|
5
6
|
[](https://www.npmjs.com/package/@nci-gis/js-tmpl)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
8
|
|
|
8
9
|
## What is js-tmpl?
|
|
9
10
|
|
|
10
|
-
js-tmpl is a **pure transformation layer** that
|
|
11
|
+
js-tmpl is a **pure transformation layer** that turns **templates + data → files**, nothing more, nothing less.
|
|
11
12
|
|
|
12
13
|
It's designed for:
|
|
13
14
|
|
|
@@ -18,17 +19,32 @@ It's designed for:
|
|
|
18
19
|
|
|
19
20
|
**Not a framework. Not a workflow tool. Just a focused rendering engine.**
|
|
20
21
|
|
|
22
|
+
## Is js-tmpl for you?
|
|
23
|
+
|
|
24
|
+
js-tmpl is a good fit if you:
|
|
25
|
+
|
|
26
|
+
- embed templating inside other tools or pipelines
|
|
27
|
+
- want **the same input to always produce the same output**
|
|
28
|
+
- prefer explicit configuration over conventions
|
|
29
|
+
- need programmatic control, not just a CLI
|
|
30
|
+
|
|
31
|
+
It may **not** be a good fit if you want:
|
|
32
|
+
|
|
33
|
+
- opinionated project generators
|
|
34
|
+
- convention-based magic
|
|
35
|
+
- interactive scaffolding workflows
|
|
36
|
+
|
|
21
37
|
## Why js-tmpl?
|
|
22
38
|
|
|
23
|
-
Most
|
|
39
|
+
Most templating tools fail in one of two ways:
|
|
24
40
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- ✅ **Explicit**: No magic defaults or hidden conventions
|
|
28
|
-
- ✅ **Composable**: Small, focused layers
|
|
29
|
-
- ✅ **Embeddable**: Designed to integrate into larger tools
|
|
41
|
+
- they are too simple to scale beyond string replacement
|
|
42
|
+
- or too opinionated to embed safely in larger systems
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
js-tmpl sits intentionally in between.
|
|
45
|
+
|
|
46
|
+
See [Motivation](docs/Motivation.md) - The full story.
|
|
47
|
+
See [Design Principles](docs/PRINCIPLES.md) - Core philosophy guiding all decisions.
|
|
32
48
|
|
|
33
49
|
## Features
|
|
34
50
|
|
|
@@ -40,6 +56,52 @@ See [docs/00-Motivation.md](docs/00-Motivation.md) for the full story.
|
|
|
40
56
|
- 🔒 **No Global State** - Isolated render passes, no pollution
|
|
41
57
|
- 📝 **YAML/JSON Support** - Load values from either format
|
|
42
58
|
|
|
59
|
+
## Fixed Rules for Minimal Auto-Discovery
|
|
60
|
+
|
|
61
|
+
js-tmpl follows the principle **"Explicit Over Implicit"** - most configuration must be provided explicitly. However, for developer convenience, exactly **ONE** type of auto-discovery is allowed:
|
|
62
|
+
|
|
63
|
+
### Project Configuration File (Optional)
|
|
64
|
+
|
|
65
|
+
js-tmpl will search for a project config file in **exactly these locations**, in this order, relative to the current working directory:
|
|
66
|
+
|
|
67
|
+
1. `js-tmpl.config.yaml` (highest priority)
|
|
68
|
+
2. `js-tmpl.config.yml`
|
|
69
|
+
3. `js-tmpl.config.json`
|
|
70
|
+
4. `config/js-tmpl.yaml`
|
|
71
|
+
5. `config/js-tmpl.json` (lowest priority)
|
|
72
|
+
|
|
73
|
+
**First match wins.** If no config file is found, internal defaults are used.
|
|
74
|
+
|
|
75
|
+
### What is NOT Auto-Discovered
|
|
76
|
+
|
|
77
|
+
Everything else must be **explicitly specified**:
|
|
78
|
+
|
|
79
|
+
- ✅ **Values file** - Required via `--values` flag or `valuesFile` config
|
|
80
|
+
- ✅ **Template directory** - Must be in config or defaults to `templates/`
|
|
81
|
+
- ✅ **Output directory** - Must be in config or defaults to `dist/`
|
|
82
|
+
- ✅ **Partials directory** - Must be in config or defaults to `templates.partials/`
|
|
83
|
+
|
|
84
|
+
### Override Auto-Discovery
|
|
85
|
+
|
|
86
|
+
You can bypass auto-discovery entirely:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Explicit config file (no auto-discovery)
|
|
90
|
+
js-tmpl render --values data.yaml --config-file /path/to/custom-config.yaml
|
|
91
|
+
|
|
92
|
+
# No config file (use defaults only)
|
|
93
|
+
js-tmpl render --values data.yaml --template-dir ./templates --out ./dist
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Why These Rules?
|
|
97
|
+
|
|
98
|
+
1. **Predictable** - Fixed search order, no magic
|
|
99
|
+
2. **Minimal** - Only config file location is auto-discovered
|
|
100
|
+
3. **Overridable** - Always use `--config-file` for explicit control
|
|
101
|
+
4. **Documented** - You're reading the complete list right now
|
|
102
|
+
|
|
103
|
+
**These are the ONLY auto-discovery rules. Nothing else is implicit.**
|
|
104
|
+
|
|
43
105
|
## Installation
|
|
44
106
|
|
|
45
107
|
```bash
|
|
@@ -173,6 +235,20 @@ templates.partials/
|
|
|
173
235
|
└── metadata.hbs → {{> common.metadata}}
|
|
174
236
|
```
|
|
175
237
|
|
|
238
|
+
## Mental Model
|
|
239
|
+
|
|
240
|
+
> ⚠️ Design note
|
|
241
|
+
> js-tmpl prefers failing loudly over guessing silently.
|
|
242
|
+
|
|
243
|
+
Think of js-tmpl as a function:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
(input templates, data, config) → output files
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
There is no hidden state, no lifecycle, and no side effects.
|
|
250
|
+
If you need orchestration, state, or interactivity, build it **around** js-tmpl — not inside it.
|
|
251
|
+
|
|
176
252
|
## CLI Reference
|
|
177
253
|
|
|
178
254
|
```bash
|
|
@@ -340,9 +416,18 @@ For security concerns, see [SECURITY.md](SECURITY.md).
|
|
|
340
416
|
|
|
341
417
|
MIT © pasxd245
|
|
342
418
|
|
|
343
|
-
##
|
|
419
|
+
## Learn More
|
|
420
|
+
|
|
421
|
+
### 📚 Documentation
|
|
422
|
+
|
|
423
|
+
- **[📖 Documentation Hub](docs/ToC.md)** - Complete documentation index with learning paths
|
|
424
|
+
- [Design Principles](docs/PRINCIPLES.md) - Core philosophy guiding all decisions
|
|
425
|
+
- [Workflow Overview](docs/WORKFLOW.md) - Visual diagrams of the rendering pipeline
|
|
426
|
+
- [API Reference](docs/API.md) - Complete programmatic API documentation
|
|
427
|
+
- [Motivation](docs/Motivation.md) - Why js-tmpl exists and our vision
|
|
428
|
+
|
|
429
|
+
### 🔗 Others
|
|
344
430
|
|
|
345
|
-
- [
|
|
346
|
-
- [
|
|
347
|
-
- [
|
|
348
|
-
- [NPM Package](https://www.npmjs.com/package/@nci-gis/js-tmpl)
|
|
431
|
+
- [Examples](examples/) - Working examples and templates
|
|
432
|
+
- [Issue Tracker](https://github.com/nci-gis/js-tmpl/issues) - Report bugs or request features
|
|
433
|
+
- [NPM Package](https://www.npmjs.com/package/@nci-gis/js-tmpl) - Package registry
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nci-gis/js-tmpl",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.2",
|
|
4
4
|
"description": "The pure JavaScript templating engine that uses handlebars.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
"import": "./src/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test $(find tests -name '*.test.js')",
|
|
16
|
+
"test:watch": "node --test --watch $(find tests -name '*.test.js')",
|
|
17
|
+
"test:coverage": "node --experimental-test-coverage --test $(find tests -name '*.test.js')",
|
|
18
|
+
"lint": "eslint src/ tests/",
|
|
19
|
+
"lint:fix": "eslint src/ tests/ --fix",
|
|
20
|
+
"build": "echo 'No build step required for pure JS library'",
|
|
21
|
+
"start": "node src/cli/main.js",
|
|
22
|
+
"help": "node src/cli/main.js --help",
|
|
23
|
+
"dev": "node src/cli/main.js",
|
|
24
|
+
"tool": "node src/cli/main.js"
|
|
25
|
+
},
|
|
14
26
|
"keywords": [
|
|
15
27
|
"js",
|
|
16
28
|
"templating",
|
|
@@ -37,6 +49,7 @@
|
|
|
37
49
|
"node": ">=20.0.0",
|
|
38
50
|
"pnpm": ">=10.0.0"
|
|
39
51
|
},
|
|
52
|
+
"packageManager": "pnpm@10.22.0",
|
|
40
53
|
"dependencies": {
|
|
41
54
|
"config": "^4.1.1",
|
|
42
55
|
"handlebars": "^4.7.8",
|
|
@@ -46,17 +59,5 @@
|
|
|
46
59
|
"@eslint/js": "^9.39.2",
|
|
47
60
|
"eslint": "^9.39.2",
|
|
48
61
|
"eslint-plugin-simple-import-sort": "^12.1.1"
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"test": "node --test $(find tests -name '*.test.js')",
|
|
52
|
-
"test:watch": "node --test --watch $(find tests -name '*.test.js')",
|
|
53
|
-
"test:coverage": "node --experimental-test-coverage --test $(find tests -name '*.test.js')",
|
|
54
|
-
"lint": "eslint src/ tests/",
|
|
55
|
-
"lint:fix": "eslint src/ tests/ --fix",
|
|
56
|
-
"build": "echo 'No build step required for pure JS library'",
|
|
57
|
-
"start": "node src/cli/main.js",
|
|
58
|
-
"help": "node src/cli/main.js --help",
|
|
59
|
-
"dev": "node src/cli/main.js",
|
|
60
|
-
"tool": "node src/cli/main.js"
|
|
61
62
|
}
|
|
62
|
-
}
|
|
63
|
+
}
|
package/src/config/defaults.js
CHANGED
package/src/config/loader.js
CHANGED
|
@@ -10,6 +10,14 @@ import YAML from "js-yaml";
|
|
|
10
10
|
* @returns {object} Parsed values.
|
|
11
11
|
*/
|
|
12
12
|
export function loadYamlOrJson(filePath) {
|
|
13
|
+
// Check if file exists before attempting to read
|
|
14
|
+
if (!fs.existsSync(filePath)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Values file not found: ${filePath}\n` +
|
|
17
|
+
"Check that the file exists and the path is correct."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
14
22
|
|
|
15
23
|
if (/\.ya?ml$/i.test(filePath)) {
|
|
@@ -46,10 +54,11 @@ export function loadProjectConfig(cwd, explicitFile) {
|
|
|
46
54
|
|
|
47
55
|
const raw = fs.readFileSync(abs, "utf8");
|
|
48
56
|
|
|
49
|
-
if (/\.ya?ml
|
|
57
|
+
if (/\.ya?ml$/i.test(abs)) {
|
|
50
58
|
return YAML.load(raw) || {};
|
|
51
59
|
}
|
|
52
|
-
|
|
60
|
+
|
|
61
|
+
if (/\.json$/i.test(abs)) {
|
|
53
62
|
return JSON.parse(raw);
|
|
54
63
|
}
|
|
55
64
|
}
|
package/src/config/resolver.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
|
|
@@ -7,23 +6,28 @@ import { loadProjectConfig,loadYamlOrJson } from "./loader.js";
|
|
|
7
6
|
import { buildView } from "./view.js";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @param {string}
|
|
14
|
-
* @returns {string
|
|
9
|
+
* Resolve valuesFile path based on valuesDir
|
|
10
|
+
* @param {string} valuesFile - The values file name or path
|
|
11
|
+
* @param {string} valuesDir - Values directory (may be empty string)
|
|
12
|
+
* @param {string} cwd - Current working directory
|
|
13
|
+
* @returns {string} Absolute path to values file
|
|
15
14
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
15
|
+
function resolveValuesFilePath(valuesFile, valuesDir, cwd) {
|
|
16
|
+
// If absolute, use as-is
|
|
17
|
+
if (path.isAbsolute(valuesFile)) {
|
|
18
|
+
return valuesFile;
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// If valuesDir is set (truthy), use it as base
|
|
22
|
+
if (valuesDir) {
|
|
23
|
+
const absoluteValuesDir = path.isAbsolute(valuesDir)
|
|
24
|
+
? valuesDir
|
|
25
|
+
: path.join(cwd, valuesDir);
|
|
26
|
+
return path.join(absoluteValuesDir, valuesFile);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
// Otherwise, resolve from cwd
|
|
30
|
+
return path.join(cwd, valuesFile);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
/**
|
|
@@ -45,20 +49,24 @@ export function resolveConfig(cli, cwd = process.cwd()) {
|
|
|
45
49
|
|
|
46
50
|
const abs = (p) => (path.isAbsolute(p) ? p : path.join(cwd, p));
|
|
47
51
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`Missing valuesFile. Use --values or create ${mergedConfig.valuesDir}/default.yaml`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
52
|
+
// Validate valuesFile is provided
|
|
53
|
+
if (!mergedConfig.valuesFile) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'Missing required configuration: valuesFile\n' +
|
|
56
|
+
'Provide via:\n' +
|
|
57
|
+
' - CLI: --values path/to/values.yaml\n' +
|
|
58
|
+
' - Config: valuesFile: "path/to/values.yaml" in js-tmpl.config.yaml'
|
|
59
|
+
);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
// Resolve path - simple logic based on valuesDir presence
|
|
63
|
+
const valuesFilePath = resolveValuesFilePath(
|
|
64
|
+
mergedConfig.valuesFile,
|
|
65
|
+
mergedConfig.valuesDir,
|
|
66
|
+
cwd
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const values = loadYamlOrJson(valuesFilePath);
|
|
62
70
|
|
|
63
71
|
return {
|
|
64
72
|
templateDir: abs(mergedConfig.templateDir),
|