@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 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
  [![npm version](https://img.shields.io/npm/v/@nci-gis/js-tmpl.svg)](https://www.npmjs.com/package/@nci-gis/js-tmpl)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
8
 
8
9
  ## What is js-tmpl?
9
10
 
10
- js-tmpl is a **pure transformation layer** that generates files and directory structures from templates with predictable, explicit behavior.
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 template tools are either too simple (basic string replacement) or too complex (opinionated frameworks). js-tmpl fills the gap:
39
+ Most templating tools fail in one of two ways:
24
40
 
25
- - ✅ **Engine-First**: Programmatic API, CLI is secondary
26
- - ✅ **Deterministic**: Same input → same output, always
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
- See [docs/00-Motivation.md](docs/00-Motivation.md) for the full story.
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
- ## Links
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
- - [Documentation](docs/)
346
- - [Examples](examples/)
347
- - [Issue Tracker](https://github.com/nci-gis/js-tmpl/issues)
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.1",
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
+ }
@@ -1,7 +1,7 @@
1
1
  export const DEFAULTS = {
2
2
  templateDir: "templates",
3
3
  partialsDir: "templates.partials",
4
- valuesDir: "templates.values",
4
+ valuesDir: "",
5
5
  valuesFile: "",
6
6
  outDir: "dist",
7
7
  extname: ".hbs",
@@ -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$/.test(abs)) {
57
+ if (/\.ya?ml$/i.test(abs)) {
50
58
  return YAML.load(raw) || {};
51
59
  }
52
- if (/\.json$/.test(abs)) {
60
+
61
+ if (/\.json$/i.test(abs)) {
53
62
  return JSON.parse(raw);
54
63
  }
55
64
  }
@@ -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
- * Lookup default values file in valuesDir.
11
- * Searches for: default.yaml, default.yml
12
- *
13
- * @param {string} valuesDir - Absolute path to values directory
14
- * @returns {string|null} - Path to default values file or null if not found
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 lookupDefaultValuesFile(valuesDir) {
17
- const candidates = ["default.yaml", "default.yml"];
15
+ function resolveValuesFilePath(valuesFile, valuesDir, cwd) {
16
+ // If absolute, use as-is
17
+ if (path.isAbsolute(valuesFile)) {
18
+ return valuesFile;
19
+ }
18
20
 
19
- for (const filename of candidates) {
20
- const filepath = path.join(valuesDir, filename);
21
- if (fs.existsSync(filepath)) {
22
- return filepath;
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
- return null;
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
- // If valuesFile not specified, try to find default.yaml in valuesDir
49
- let valuesFile = mergedConfig.valuesFile;
50
- if (!valuesFile) {
51
- const valuesDir = abs(mergedConfig.valuesDir);
52
- valuesFile = lookupDefaultValuesFile(valuesDir);
53
-
54
- if (!valuesFile) {
55
- throw new Error(
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
- const values = loadYamlOrJson(abs(valuesFile));
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),