@rse/nunjucks-cli 2.0.1 → 2.2.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
  ChangeLog
3
3
  =========
4
4
 
5
+ 2.2.0 (2025-12-26)
6
+ ------------------
7
+
8
+ - IMPROVEMENT: finally fix SEA packaging
9
+ - IMPROVEMENT: provide Docker container publishing
10
+ - CLEANUP: cleanup distribution archive packaging
11
+
12
+ 2.1.0 (2025-12-26)
13
+ ------------------
14
+
15
+ - REFACTOR: change option -e/--extension to option -p/--plugin
16
+ - IMPROVEMENT: add new option -e for loading custom .env files
17
+ - IMPROVEMENT: add new option -E for loading standard .env files
18
+
5
19
  2.0.0 (2025-12-21)
6
20
  ------------------
7
21
 
package/Dockerfile CHANGED
@@ -3,15 +3,19 @@
3
3
  ##
4
4
 
5
5
  # build arguments (early)
6
- ARG IMAGE_PREFIX=docker.io/engelschall/
6
+ ARG IMAGE_PREFIX=ghcr.io/rse/
7
7
  ARG IMAGE_NAME=nunjucks-cli
8
- ARG IMAGE_VERSION=2.0.0
9
- ARG IMAGE_RELEASE=20251221
8
+ ARG IMAGE_VERSION=2.1.0
9
+ ARG IMAGE_RELEASE=20251226
10
10
  ARG IMAGE_ALIAS=latest
11
11
 
12
12
  # derive image from a certain base image
13
13
  FROM node:24-alpine3.23
14
14
 
15
+ # link to Github repository
16
+ LABEL org.opencontainers.image.source=https://github.com/rse/nunjucks-cli
17
+ LABEL org.opencontainers.image.description="Nunjucks Template Rendering Command-Line Interface"
18
+
15
19
  # add additional build tools
16
20
  RUN apk update && \
17
21
  apk upgrade
@@ -27,9 +31,7 @@ WORKDIR /app
27
31
  ENV HOME=/app
28
32
 
29
33
  # install tool
30
- RUN npm install -g \
31
- @rse/nunjucks-cli@1.5.2 \
32
- @rse/nunjucks-addons@1.0.8
34
+ RUN npm install -g @rse/nunjucks-cli
33
35
 
34
36
  # cleanup Alpine
35
37
  RUN rm -rf /var/cache/apk/*
package/README.md CHANGED
@@ -48,6 +48,14 @@ $ npx --yes --package @rse/nunjucks-cli --package @rse/nunjucks-addons -- \
48
48
  nunjucks -e @rse/nunjucks-addons [...]
49
49
  ```
50
50
 
51
+ Furthermore, instead of using NPM at all, you can also use Docker (at
52
+ least as long as you are working with stdin/stdout or perform the
53
+ corresponding bind-mounts):
54
+
55
+ ```sh
56
+ $ docker run -i --rm ghcr.io/rse/nunjucks-cli [...] -
57
+ ```
58
+
51
59
  Command-Line Interface (CLI)
52
60
  ----------------------------
53
61
 
@@ -57,11 +65,13 @@ Short excerpt of the CLI options and arguments from the companion [Unix manpage]
57
65
  $ nunjucks
58
66
  [-h|--help]
59
67
  [-V|--version]
68
+ [-e|--env <env-file>]
69
+ [-E|--envs]
60
70
  [-c|--config <config-file>]
61
71
  [-C|--option <key>=<value>]
62
72
  [-d|--defines <context-file>]
63
73
  [-D|--define <key>=<value>]
64
- [-e|--extension <module-name>]
74
+ [-p|--plugin <module-name>]
65
75
  [-o|--output <output-file>|-]
66
76
  <input-file>|-
67
77
  ```
@@ -70,6 +80,12 @@ $ nunjucks
70
80
  Show usage help.
71
81
  - `-V`|`--version`:<br/>
72
82
  Show program version information.
83
+ - `-e`|`--env` *env-file*:<br/>
84
+ Load environment file with key/value definitions.
85
+ These can later be accessed with the global `env` variable.
86
+ - [`-E`|`--envs`]:<br/>
87
+ Automatically load environment files with key/value definitions
88
+ from all `.env` files in current and all parent directories.
73
89
  - `-c`|`--config` `<config-file>`:<br/>
74
90
  Load Nunjucks configuration YAML file.
75
91
  - `-C`|`--option` `<key>=<value>`:<br/>
@@ -80,8 +96,8 @@ $ nunjucks
80
96
  - `-D`|`--define` `<key>=<value>`:<br/>
81
97
  Set context definition key/value.
82
98
  Can occur multiple times.
83
- - `-e`|`--extension` `<module-name>`:<br/>
84
- Load Nunjucks JavaScript extension module (installed via NPM).
99
+ - `-p`|`--plugin` `<module-name>`:<br/>
100
+ Load Nunjucks JavaScript plugin module (installed via NPM).
85
101
  - `-o`|`--output` `<output-file>`|`-`:<br/>
86
102
  Save output file (or stdout).
87
103
  - `<input-file>`|`-`:<br/>
package/nunjucks.md CHANGED
@@ -6,11 +6,13 @@
6
6
  `nunjucks`
7
7
  \[`-h`|`--help`\]
8
8
  \[`-V`|`--version`\]
9
+ \[`-e`|`--env` *env-file*\]
10
+ \[`-E`|`--envs`\]
9
11
  \[`-c`|`--config` *config-file*\]
10
12
  \[`-C`|`--option` *key*=*value*\]
11
13
  \[`-d`|`--defines` *context-file*\]
12
14
  \[`-D`|`--define` *key*=*value*\]
13
- \[`-e`|`--extension` *module-name*\]
15
+ \[`-p`|`--plugin` *module-name*\]
14
16
  \[`-o`|`--output` *output-file*|`-`\]
15
17
  \[*input-file*|`-`\]
16
18
 
@@ -27,33 +29,41 @@ It optionally can load Nunjucks addons like the ones from the companion
27
29
 
28
30
  The following top-level options and arguments exist:
29
31
 
30
- - \[`-h`|`--help`\]
32
+ - \[`-h`|`--help`\]:
31
33
  Show usage help.
32
34
 
33
- - \[`-V`|`--version`\]
35
+ - \[`-V`|`--version`\]:
34
36
  Show program version information.
35
37
 
36
- - \[`-c`|`--config` *config-file*\]
38
+ - \[`-e`|`--env` *env-file*\]:
39
+ Load environment file with key/value definitions.
40
+ These can later be accessed with the global `env` variable.
41
+
42
+ - \[`-E`|`--envs`\]:
43
+ Automatically load environment files with key/value definitions
44
+ from all `.env` files in current and all parent directories.
45
+
46
+ - \[`-c`|`--config` *config-file*\]:
37
47
  Load Nunjucks configuration YAML file.
38
48
 
39
- - \[`-C`|`--option` *key*=*value*\]
49
+ - \[`-C`|`--option` *key*=*value*\]:
40
50
  Set Nunjucks configuration option.
41
51
 
42
- - \[`-d`|`--defines` *context-file*\]
52
+ - \[`-d`|`--defines` *context-file*\]:
43
53
  Load context definition YAML file.
44
54
  Can occur multiple times.
45
55
 
46
- - \[`-D`|`--define` *key*=*value*\]
56
+ - \[`-D`|`--define` *key*=*value*\]:
47
57
  Set context definition key/value.
48
58
  Can occur multiple times.
49
59
 
50
- - \[`-e`|`--extension` *module-name*\]
51
- Load Nunjucks JavaScript extension module (installed via NPM).
60
+ - \[`-p`|`--plugin` *module-name*\]:
61
+ Load Nunjucks JavaScript plugin module (installed via NPM).
52
62
 
53
- - \[`-o`|`--output` *output-file*|`-`\]
63
+ - \[`-o`|`--output` *output-file*|`-`\]:
54
64
  Save output file (or stdout).
55
65
 
56
- - \[`<input-file>`|`-`\]
66
+ - \[`<input-file>`|`-`\]:
57
67
  Load input file (or stdin).
58
68
 
59
69
  ## EXAMPLE
package/nunjucks.ts CHANGED
@@ -16,6 +16,11 @@ import chalk from "chalk"
16
16
  import jsYAML from "js-yaml"
17
17
  import nunjucks from "nunjucks"
18
18
  import deepmerge from "deepmerge"
19
+ import dotenvx from "@dotenvx/dotenvx"
20
+ import * as findup from "find-up"
21
+
22
+ /* internal requirements */
23
+ import pkg from "./package.json" with { type: "json" }
19
24
 
20
25
  /* type definitions */
21
26
  type PackageInfo = {
@@ -38,196 +43,223 @@ type OptionsType = {
38
43
  type CLIOptions = {
39
44
  help: boolean
40
45
  version: boolean
46
+ env: string[]
47
+ envs: boolean
41
48
  config: string
42
49
  option: string[]
43
50
  defines: string[]
44
51
  define: string[]
45
- extension: string[]
52
+ plugin: string[]
46
53
  output: string
47
54
  _: string[]
48
55
  }
49
56
 
50
- /* load my own information */
51
- const my: PackageInfo = JSON.parse(await fs.promises.readFile(new URL("./package.json", import.meta.url), "utf-8"))
52
-
53
- /* parse command-line arguments */
54
- const program = new Command()
55
- const reduceArray = (v: string, l: string[]) => l.concat([ v ])
56
- program.name("nunjucks")
57
- .description("Nunjucks Template Rendering Command-Line Interface")
58
- .showHelpAfterError("hint: use option --help for usage information")
59
- .option("-h, --help", "show usage help", false)
60
- .option("-V, --version", "show program version information", false)
61
- .option("-c, --config <config-file>", "load Nunjucks configuration YAML file", "")
62
- .option("-C, --option <key>=<value>", "set Nunjucks configuration option", reduceArray, [])
63
- .option("-d, --defines <context-file>", "load context definition YAML file", reduceArray, [])
64
- .option("-D, --define <key>=<value>", "set context definition key/value", reduceArray, [])
65
- .option("-e, --extension <module-name>", "load Nunjucks JavaScript extension module", reduceArray, [])
66
- .option("-o, --output <output-file>", "save output file", "-")
67
- .argument("[<input-file>]", "input file")
68
- program.parse(process.argv)
69
- const argv: CLIOptions = {
70
- ...program.opts(),
71
- _: program.args
72
- } as CLIOptions
73
-
74
- /* handle special help request */
75
- if (argv.help) {
76
- console.log(program.helpInformation())
77
- console.log("Example:\n $ echo \"Hello, {{ who }}!\" | nunjucks -Dwho=World -\n")
78
- process.exit(0)
79
- }
57
+ /* establish asynchronous environment */
58
+ ;(async () => {
59
+ /* load my own information */
60
+ const my = pkg as PackageInfo
80
61
 
81
- /* handle special version request */
82
- if (argv.version) {
83
- console.log(`${my.name} ${my.version} (Node.js ${process.versions.node}, Nunjucks: ${my.dependencies.nunjucks})`)
84
- console.log(`${my.description}`)
85
- console.log(`Copyright (c) 2019-2025 ${my.author.name} <${my.author.url}>`)
86
- console.log(`Licensed under ${my.license} <http://spdx.org/licenses/${my.license}.html>`)
87
- process.exit(0)
88
- }
62
+ /* parse command-line arguments */
63
+ const program = new Command()
64
+ const reduceArray = (v: string, l: string[]) => l.concat([ v ])
65
+ program.name("nunjucks")
66
+ .description("Nunjucks Template Rendering Command-Line Interface")
67
+ .showHelpAfterError("hint: use option --help for usage information")
68
+ .option("-h, --help", "show usage help", false)
69
+ .option("-V, --version", "show program version information", false)
70
+ .option("-e, --env <env-file>", "load environment key/value file", reduceArray, [])
71
+ .option("-E, --envs", "load all environment key/value files", false)
72
+ .option("-c, --config <config-file>", "load Nunjucks configuration YAML file", "")
73
+ .option("-C, --option <key>=<value>", "set Nunjucks configuration option", reduceArray, [])
74
+ .option("-d, --defines <context-file>", "load context definition YAML file", reduceArray, [])
75
+ .option("-D, --define <key>=<value>", "set context definition key/value", reduceArray, [])
76
+ .option("-p, --plugin <module-name>", "load Nunjucks JavaScript plugin module", reduceArray, [])
77
+ .option("-o, --output <output-file>", "save output file", "-")
78
+ .argument("[<input-file>]", "input file")
79
+ program.parse(process.argv)
80
+ const argv: CLIOptions = {
81
+ ...program.opts(),
82
+ _: program.args
83
+ } as CLIOptions
89
84
 
90
- /* read input file */
91
- let input = ""
92
- if (argv._.length > 1) {
93
- console.error(chalk.red("nunjucks: ERROR: invalid number of arguments (zero or one input file expected)"))
94
- process.exit(1)
95
- }
96
- let inputFile: string = argv._[0] ?? "-"
97
- if (inputFile === "-") {
98
- inputFile = "<stdin>"
99
- process.stdin.setEncoding("utf-8")
100
- const BUFSIZE = 256
101
- const buf = Buffer.alloc(BUFSIZE)
102
- while (true) {
103
- let bytesRead = 0
85
+ /* handle special help request */
86
+ if (argv.help) {
87
+ console.log(program.helpInformation())
88
+ console.log("Example:\n $ echo \"Hello, {{ who }}!\" | nunjucks -Dwho=World -\n")
89
+ process.exit(0)
90
+ }
91
+
92
+ /* handle special version request */
93
+ if (argv.version) {
94
+ console.log(`${my.name} ${my.version} (Node.js ${process.versions.node}, Nunjucks: ${my.dependencies.nunjucks})`)
95
+ console.log(`${my.description}`)
96
+ console.log(`Copyright (c) 2019-2025 ${my.author.name} <${my.author.url}>`)
97
+ console.log(`Licensed under ${my.license} <http://spdx.org/licenses/${my.license}.html>`)
98
+ process.exit(0)
99
+ }
100
+
101
+ /* read input file */
102
+ let input = ""
103
+ if (argv._.length > 1) {
104
+ console.error(chalk.red("nunjucks: ERROR: invalid number of arguments (zero or one input file expected)"))
105
+ process.exit(1)
106
+ }
107
+ let inputFile: string = argv._[0] ?? "-"
108
+ if (inputFile === "-") {
109
+ inputFile = "<stdin>"
110
+ process.stdin.setEncoding("utf-8")
111
+ const BUFSIZE = 256
112
+ const buf = Buffer.alloc(BUFSIZE)
113
+ while (true) {
114
+ let bytesRead = 0
115
+ try {
116
+ bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, null)
117
+ }
118
+ catch (ex: any) {
119
+ if (ex.code === "EAGAIN") continue
120
+ else if (ex.code === "EOF") break
121
+ else throw ex
122
+ }
123
+ if (bytesRead === 0)
124
+ break
125
+ input += buf.toString("utf8", 0, bytesRead)
126
+ }
127
+ }
128
+ else {
129
+ if (!fs.existsSync(inputFile)) {
130
+ console.error(chalk.red(`nunjucks: ERROR: failed to find input file: "${inputFile}"`))
131
+ process.exit(1)
132
+ }
133
+ input = fs.readFileSync(inputFile, { encoding: "utf8" })
134
+ }
135
+
136
+ /* provide context variables for template */
137
+ let context: ContextType = {}
138
+ for (const define of argv.defines) {
104
139
  try {
105
- bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, null)
140
+ context = deepmerge(context, jsYAML.load(fs.readFileSync(define, { encoding: "utf8" })) as ContextType)
106
141
  }
107
142
  catch (ex: any) {
108
- if (ex.code === "EAGAIN") continue
109
- else if (ex.code === "EOF") break
110
- else throw ex
143
+ console.error(chalk.red(`nunjucks: ERROR: failed to load context YAML file: ${ex.toString()}`))
144
+ process.exit(1)
111
145
  }
112
- if (bytesRead === 0)
113
- break
114
- input += buf.toString("utf8", 0, bytesRead)
115
146
  }
116
- }
117
- else {
118
- if (!fs.existsSync(inputFile)) {
119
- console.error(chalk.red(`nunjucks: ERROR: failed to find input file: ${inputFile}`))
120
- process.exit(1)
121
- }
122
- input = fs.readFileSync(inputFile, { encoding: "utf8" })
123
- }
124
147
 
125
- /* provide context variables for template */
126
- let context: ContextType = {}
127
- for (const define of argv.defines) {
128
- try {
129
- context = deepmerge(context, jsYAML.load(fs.readFileSync(define, { encoding: "utf8" })) as ContextType)
148
+ /* load environment variables from all default files */
149
+ if (argv.envs) {
150
+ const files = findup.findUpMultipleSync(".env")
151
+ if (files.length > 0)
152
+ dotenvx.config({ path: files, quiet: true })
130
153
  }
131
- catch (ex: any) {
132
- console.error(chalk.red(`nunjucks: ERROR: failed to load context YAML file: ${ex.toString()}`))
133
- process.exit(1)
154
+
155
+ /* load environment variables from environment files */
156
+ if (argv.env.length > 0) {
157
+ for (const env of argv.env) {
158
+ if (!fs.existsSync(env)) {
159
+ console.error(chalk.red(`nunjucks: ERROR: environment file not found: "${env}"`))
160
+ process.exit(1)
161
+ }
162
+ }
163
+ dotenvx.config({ path: argv.env, quiet: true })
134
164
  }
135
- }
136
165
 
137
- /* expose environment variables to template */
138
- context.env = process.env
139
-
140
- /* add context defines */
141
- argv.define.forEach((define: string) => {
142
- const match = define.match(/^([^=]+)(?:=(.*))?$/)
143
- if (!match)
144
- return
145
- let [ , key, val ]: (string | undefined)[] = match
146
- if (!key)
147
- return
148
- if (val === undefined)
149
- val = "true"
150
- context[key] = val
151
- })
166
+ /* expose environment variables to template */
167
+ context.env = process.env
152
168
 
153
- /* determine Nunjucks options */
154
- let options: OptionsType = {}
155
- if (argv.config) {
156
- try {
157
- options = jsYAML.load(fs.readFileSync(argv.config, { encoding: "utf8" })) as OptionsType
169
+ /* add context defines */
170
+ argv.define.forEach((define: string) => {
171
+ const match = define.match(/^([^=]+)(?:=(.*))?$/)
172
+ if (!match)
173
+ return
174
+ let [ , key, val ]: (string | undefined)[] = match
175
+ if (!key)
176
+ return
177
+ if (val === undefined)
178
+ val = "true"
179
+ context[key] = val
180
+ })
181
+
182
+ /* determine Nunjucks options */
183
+ let options: OptionsType = {}
184
+ if (argv.config) {
185
+ try {
186
+ options = jsYAML.load(fs.readFileSync(argv.config, { encoding: "utf8" })) as OptionsType
187
+ }
188
+ catch (ex: any) {
189
+ console.error(chalk.red(`nunjucks: ERROR: failed to load options YAML file: ${ex.toString()}`))
190
+ process.exit(1)
191
+ }
158
192
  }
159
- catch (ex: any) {
160
- console.error(chalk.red(`nunjucks: ERROR: failed to load options YAML file: ${ex.toString()}`))
161
- process.exit(1)
193
+ if (argv.option.length > 0)
194
+ options = Object.assign(options, argv.option)
195
+ options = {
196
+ autoescape: false,
197
+ throwOnUndefined: false,
198
+ trimBlocks: true,
199
+ lstripBlocks: true,
200
+ watch: false,
201
+ noCache: true,
202
+ ...options
162
203
  }
163
- }
164
- if (argv.option.length > 0)
165
- options = Object.assign(options, argv.option)
166
- options = {
167
- autoescape: false,
168
- throwOnUndefined: false,
169
- trimBlocks: true,
170
- lstripBlocks: true,
171
- watch: false,
172
- noCache: true,
173
- ...options
174
- }
175
204
 
176
- /* configure environment */
177
- const env = nunjucks.configure(inputFile, options)
205
+ /* configure environment */
206
+ const env = nunjucks.configure(inputFile, options)
178
207
 
179
- /* load external extension files */
180
- for (const extension of argv.extension) {
181
- let modpath: string | null = path.resolve(extension)
182
- if (!fs.existsSync(modpath)) {
208
+ /* load external plugin modules */
209
+ for (const plugin of argv.plugin) {
210
+ let modpath: string | null = path.resolve(plugin)
211
+ if (!fs.existsSync(modpath)) {
212
+ try {
213
+ const require = createRequire(import.meta.url)
214
+ modpath = require.resolve(plugin)
215
+ }
216
+ catch (_ex) {
217
+ modpath = null
218
+ }
219
+ }
220
+ if (modpath === null) {
221
+ console.error(chalk.red(`nunjucks: ERROR: failed to find plugin module: ${plugin}`))
222
+ process.exit(1)
223
+ }
224
+
225
+ /* dynamically import the module */
226
+ let mod: any
183
227
  try {
184
- const require = createRequire(import.meta.url)
185
- modpath = require.resolve(extension)
228
+ mod = await import(modpath)
229
+
230
+ /* handle both default and named exports */
231
+ mod = mod.default ?? mod
186
232
  }
187
- catch (_ex) {
188
- modpath = null
233
+ catch (ex: any) {
234
+ console.error(chalk.red(`nunjucks: ERROR: failed to load plugin module: ${ex.toString()}`))
235
+ process.exit(1)
189
236
  }
190
- }
191
- if (modpath === null) {
192
- console.error(chalk.red(`nunjucks: ERROR: failed to find extension module: ${extension}`))
193
- process.exit(1)
237
+ if (!(mod !== null && typeof mod === "function")) {
238
+ console.error(chalk.red(`nunjucks: ERROR: failed to call plugin file: "${modpath}"`))
239
+ process.exit(1)
240
+ }
241
+ mod(env)
194
242
  }
195
243
 
196
- /* dynamically import the module */
197
- let mod: any
244
+ /* render Nunjucks template */
245
+ let output: string
198
246
  try {
199
- mod = await import(modpath)
200
-
201
- /* handle both default and named exports */
202
- mod = mod.default ?? mod
247
+ output = env.renderString(input, context)
203
248
  }
204
249
  catch (ex: any) {
205
- console.error(chalk.red(`nunjucks: ERROR: failed to load extension module: ${ex.toString()}`))
206
- process.exit(1)
207
- }
208
- if (!(mod !== null && typeof mod === "function")) {
209
- console.error(chalk.red(`nunjucks: ERROR: failed to call extension file: ${modpath}`))
250
+ console.error(chalk.red(`nunjucks: ERROR: failed to render template: ${ex.toString()}`))
210
251
  process.exit(1)
211
252
  }
212
- mod(env)
213
- }
214
-
215
- /* render Nunjucks template */
216
- let output: string
217
- try {
218
- output = env.renderString(input, context)
219
- }
220
- catch (ex: any) {
221
- console.error(chalk.red(`nunjucks: ERROR: failed to render template: ${ex.toString()}`))
222
- process.exit(1)
223
- }
224
253
 
225
- /* write output */
226
- if (argv.output === "-")
227
- process.stdout.write(output)
228
- else
229
- fs.writeFileSync(argv.output, output, { encoding: "utf8" })
254
+ /* write output */
255
+ if (argv.output === "-")
256
+ process.stdout.write(output)
257
+ else
258
+ fs.writeFileSync(argv.output, output, { encoding: "utf8" })
230
259
 
231
- /* die gracefully */
232
- process.exit(0)
260
+ /* die gracefully */
261
+ process.exit(0)
262
+ })().catch((err: any) => {
263
+ console.error(chalk.red(`nunjucks: ERROR: ${err.toString()}`))
264
+ })
233
265
 
package/package.json CHANGED
@@ -1,60 +1,66 @@
1
1
  {
2
- "name": "@rse/nunjucks-cli",
3
- "publishConfig": { "access": "public" },
4
- "version": "2.0.1",
5
- "stdver": "2.0.0-GA",
6
- "description": "Nunjucks Template Rendering Command-Line Interface",
2
+ "name": "@rse/nunjucks-cli",
3
+ "publishConfig": { "access": "public" },
4
+ "version": "2.2.0",
5
+ "stdver": "2.2.0-GA",
6
+ "description": "Nunjucks Template Rendering Command-Line Interface",
7
7
  "author": {
8
- "name": "Dr. Ralf S. Engelschall",
9
- "email": "rse@engelschall.com",
10
- "url": "http://engelschall.com"
8
+ "name": "Dr. Ralf S. Engelschall",
9
+ "email": "rse@engelschall.com",
10
+ "url": "http://engelschall.com"
11
11
  },
12
- "license": "MIT",
12
+ "license": "MIT",
13
13
  "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/rse/nunjucks-cli.git"
14
+ "type": "git",
15
+ "url": "git+https://github.com/rse/nunjucks-cli.git"
16
16
  },
17
17
  "bugs": {
18
- "url": "http://github.com/rse/nunjucks-cli/issues"
18
+ "url": "http://github.com/rse/nunjucks-cli/issues"
19
19
  },
20
20
  "bin": {
21
- "nunjucks": "nunjucks.js"
21
+ "nunjucks": "dst-stage1/nunjucks.js"
22
22
  },
23
- "man": "nunjucks.1",
24
- "type": "module",
23
+ "man": "dst-stage1/nunjucks.1",
24
+ "type": "module",
25
25
  "engines": {
26
- "node": ">=22.18.0"
26
+ "node": ">=22.18.0"
27
27
  },
28
28
  "dependencies": {
29
- "nunjucks": "3.2.4",
30
- "chalk": "5.6.2",
31
- "commander": "14.0.2",
32
- "js-yaml": "4.1.1",
33
- "deepmerge": "4.3.1"
29
+ "nunjucks": "3.2.4",
30
+ "chalk": "5.6.2",
31
+ "commander": "14.0.2",
32
+ "js-yaml": "4.1.1",
33
+ "deepmerge": "4.3.1",
34
+ "@dotenvx/dotenvx": "1.51.2",
35
+ "find-up": "8.0.0"
34
36
  },
35
37
  "devDependencies": {
36
- "eslint": "9.39.2",
37
- "@eslint/js": "9.39.2",
38
- "neostandard": "0.12.2",
39
- "eslint-plugin-promise": "7.2.1",
40
- "eslint-plugin-import": "2.32.0",
41
- "eslint-plugin-n": "17.23.1",
42
- "globals": "16.5.0",
43
- "remark-cli": "12.0.1",
44
- "remark": "15.0.1",
45
- "remark-man": "9.0.0",
46
- "typescript": "5.9.3",
47
- "typescript-eslint": "8.50.0",
48
- "@rse/stx": "1.1.3",
49
- "shx": "0.4.0",
50
- "@yao-pkg/pkg": "6.11.0",
38
+ "eslint": "9.39.2",
39
+ "@eslint/js": "9.39.2",
40
+ "neostandard": "0.12.2",
41
+ "eslint-plugin-promise": "7.2.1",
42
+ "eslint-plugin-import": "2.32.0",
43
+ "eslint-plugin-n": "17.23.1",
44
+ "globals": "16.5.0",
45
+ "remark-cli": "12.0.1",
46
+ "remark": "15.0.1",
47
+ "remark-man": "9.0.0",
48
+ "typescript": "5.9.3",
49
+ "typescript-eslint": "8.50.1",
50
+ "@rse/stx": "1.1.4",
51
+ "shx": "0.4.0",
52
+ "@yao-pkg/pkg": "6.11.0",
53
+ "vite": "7.3.0",
54
+ "@wroud/vite-plugin-tsc": "0.12.2",
55
+ "rollup-plugin-node-externals": "8.1.2",
56
+ "terser": "5.44.1",
51
57
 
52
- "@types/node": "25.0.3",
53
- "@types/js-yaml": "4.0.9",
54
- "@types/nunjucks": "3.2.6"
58
+ "@types/node": "25.0.3",
59
+ "@types/js-yaml": "4.0.9",
60
+ "@types/nunjucks": "3.2.6"
55
61
  },
56
62
  "scripts": {
57
- "start": "stx -v4 -c stx.conf",
58
- "test": "npm start test"
63
+ "start": "stx -v4 -c stx.conf",
64
+ "test": "npm start test"
59
65
  }
60
66
  }
package/stx.conf CHANGED
@@ -11,28 +11,47 @@ lint
11
11
 
12
12
  # build JavaScript from TypeScript
13
13
  build
14
- tsc --project tsconfig.json
15
-
16
- # build manual page
17
- man
18
- remark --quiet --use remark-man --output nunjucks.1 nunjucks.md
14
+ vite --config vite.mts build --mode production &&
15
+ remark --quiet --use remark-man --output dst-stage1/nunjucks.man nunjucks.md
19
16
 
20
17
  # execute simple smole test
21
18
  test : build
22
19
  echo 'Hello, {{who}}!' | node nunjucks.js -D who=world -
23
20
 
24
- # build all-in-one packages
25
- build:pkg [hostname=en4.*]
26
-
27
21
  # package distribution archives
28
- package : build build:pkg [hostname=en4.*]
29
- VERSION=`sed -n '/"version":/ s/.*: *"\(.*\)".*/\1/p' package.json` && \
22
+ package : package:docker package:sea [hostname=en4.*]
23
+
24
+ # package as Docker container image
25
+ package:docker [hostname=en4.*]
26
+ IMAGE_PREFIX=`egrep "ARG.*IMAGE_PREFIX" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
27
+ IMAGE_NAME=`egrep "ARG.*IMAGE_NAME" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
28
+ IMAGE_VERSION=`egrep "ARG.*IMAGE_VERSION" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
29
+ IMAGE_RELEASE=`egrep "ARG.*IMAGE_RELEASE" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
30
+ IMAGE_ALIAS=`egrep "ARG.*IMAGE_ALIAS" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
31
+ docker buildx build \
32
+ --progress plain \
33
+ --pull \
34
+ --no-cache \
35
+ --platform linux/amd64,linux/arm64 \
36
+ -t $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_ALIAS \
37
+ -t $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_VERSION \
38
+ -t $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_VERSION-$IMAGE_RELEASE \
39
+ -f Dockerfile . && \
40
+ docker image ls $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_VERSION-$IMAGE_RELEASE
41
+
42
+ # package as Single Excecutable Application (SEA)
43
+ package:sea [hostname=en4.*]
44
+ rm -rf dst-stage3 && \
45
+ mkdir dst-stage3 && \
46
+ cd dst-stage3 && \
47
+ VERSION=`sed -n '/"version":/ s/.*: *"\(.*\)".*/\1/p' ../package.json` && \
30
48
  targets="node24-linux-x64,node24-linux-arm64" && \
31
49
  targets="$targets,node24-win-x64,node24-win-arm64" && \
32
50
  targets="$targets,node24-macos-x64,node24-macos-arm64" && \
33
- sed -e 's;@rse/nunjucks-cli;nunjucks;' <package.json >nunjucks.json && \
34
- pkg --sea --public -c nunjucks.json -t "$targets" nunjucks.js && \
35
- rm -f nunjucks.json && \
51
+ sed -e 's;@rse/nunjucks-cli;nunjucks;' -e '/"type"/d' <../package.json >package.json && \
52
+ cp ../dst-stage2/nunjucks.cjs nunjucks.cjs && \
53
+ cp ../dst-stage1/nunjucks.man nunjucks.man && \
54
+ pkg --public --no-bytecode -c package.json -t "$targets" nunjucks.cjs && \
36
55
  shx mv nunjucks-linux-x64 nunjucks-lnx-x64 && \
37
56
  shx mv nunjucks-linux-arm64 nunjucks-lnx-a64 && \
38
57
  shx mv nunjucks-win-x64.exe nunjucks-win-x64.exe && \
@@ -46,20 +65,20 @@ package : build build:pkg [hostname=en4.*]
46
65
  mkdir -p nunjucks-$VERSION-mac-a64/ && \
47
66
  mkdir -p nunjucks-$VERSION-lnx-x64/ && \
48
67
  mkdir -p nunjucks-$VERSION-lnx-a64/ && \
49
- cp -p nunjucks.js nunjucks-$VERSION/nunjucks.js && \
68
+ cp -p nunjucks.cjs nunjucks-$VERSION/nunjucks.cjs && \
50
69
  cp -p nunjucks-win-x64.exe nunjucks-$VERSION-win-x64/nunjucks.exe && \
51
70
  cp -p nunjucks-win-a64.exe nunjucks-$VERSION-win-a64/nunjucks.exe && \
52
71
  cp -p nunjucks-mac-x64 nunjucks-$VERSION-mac-x64/nunjucks && \
53
72
  cp -p nunjucks-mac-a64 nunjucks-$VERSION-mac-a64/nunjucks && \
54
73
  cp -p nunjucks-lnx-x64 nunjucks-$VERSION-lnx-x64/nunjucks && \
55
74
  cp -p nunjucks-lnx-a64 nunjucks-$VERSION-lnx-a64/nunjucks && \
56
- cp -p nunjucks.1 nunjucks-$VERSION/nunjucks.man && \
57
- cp -p nunjucks.1 nunjucks-$VERSION-win-x64/nunjucks.man && \
58
- cp -p nunjucks.1 nunjucks-$VERSION-win-a64/nunjucks.man && \
59
- cp -p nunjucks.1 nunjucks-$VERSION-mac-x64/nunjucks.man && \
60
- cp -p nunjucks.1 nunjucks-$VERSION-mac-a64/nunjucks.man && \
61
- cp -p nunjucks.1 nunjucks-$VERSION-lnx-x64/nunjucks.man && \
62
- cp -p nunjucks.1 nunjucks-$VERSION-lnx-a64/nunjucks.man && \
75
+ cp -p nunjucks.man nunjucks-$VERSION/nunjucks.man && \
76
+ cp -p nunjucks.man nunjucks-$VERSION-win-x64/nunjucks.man && \
77
+ cp -p nunjucks.man nunjucks-$VERSION-win-a64/nunjucks.man && \
78
+ cp -p nunjucks.man nunjucks-$VERSION-mac-x64/nunjucks.man && \
79
+ cp -p nunjucks.man nunjucks-$VERSION-mac-a64/nunjucks.man && \
80
+ cp -p nunjucks.man nunjucks-$VERSION-lnx-x64/nunjucks.man && \
81
+ cp -p nunjucks.man nunjucks-$VERSION-lnx-a64/nunjucks.man && \
63
82
  zip -9 -r nunjucks-$VERSION.zip nunjucks-$VERSION/ && \
64
83
  zip -9 -r nunjucks-$VERSION-win-x64.zip nunjucks-$VERSION-win-x64/ && \
65
84
  zip -9 -r nunjucks-$VERSION-win-a64.zip nunjucks-$VERSION-win-a64/ && \
@@ -69,24 +88,37 @@ package : build build:pkg [hostname=en4.*]
69
88
  zip -9 -r nunjucks-$VERSION-lnx-a64.zip nunjucks-$VERSION-lnx-a64/
70
89
 
71
90
  # publish distribution archives
72
- publish : package [hostname=en4.*]
91
+ publish : publish:docker publish:sea [hostname=en4.*]
92
+
93
+ # publish Docker container image
94
+ publish:docker [hostname=en4.*]
95
+ IMAGE_PREFIX=`egrep "ARG.*IMAGE_PREFIX" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
96
+ IMAGE_NAME=`egrep "ARG.*IMAGE_NAME" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
97
+ IMAGE_VERSION=`egrep "ARG.*IMAGE_VERSION" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
98
+ IMAGE_RELEASE=`egrep "ARG.*IMAGE_RELEASE" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
99
+ IMAGE_ALIAS=`egrep "ARG.*IMAGE_ALIAS" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
100
+ docker push $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_VERSION-$IMAGE_RELEASE && \
101
+ docker push $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_VERSION && \
102
+ docker push $IMAGE_PREFIX$IMAGE_NAME:$IMAGE_ALIAS
103
+
104
+ # publish Single Excecutable Application (SEA)
105
+ publish:sea [hostname=en4.*]
73
106
  VERSION=`sed -n '/"version":/ s/.*: *"\(.*\)".*/\1/p' package.json` && \
74
107
  V=`echo "$VERSION" | sed -e 's;\.;\\.;g'` && \
75
108
  sed -n -e "/^${V} /, /^[0-9]\\./ { /^${V} /p; /^[0-9]\\./!p; }" <CHANGELOG.md >.notes.md && \
76
109
  gh release create --title "Nunjucks $VERSION" --notes-file .notes.md --verify-tag $VERSION \
77
- nunjucks-$VERSION.zip \
78
- nunjucks-$VERSION-win-x64.zip \
79
- nunjucks-$VERSION-win-a64.zip \
80
- nunjucks-$VERSION-mac-x64.zip \
81
- nunjucks-$VERSION-mac-a64.zip \
82
- nunjucks-$VERSION-lnx-x64.zip \
83
- nunjucks-$VERSION-lnx-a64.zip && \
110
+ dst-stage3/nunjucks-$VERSION.zip \
111
+ dst-stage3/nunjucks-$VERSION-win-x64.zip \
112
+ dst-stage3/nunjucks-$VERSION-win-a64.zip \
113
+ dst-stage3/nunjucks-$VERSION-mac-x64.zip \
114
+ dst-stage3/nunjucks-$VERSION-mac-a64.zip \
115
+ dst-stage3/nunjucks-$VERSION-lnx-x64.zip \
116
+ dst-stage3/nunjucks-$VERSION-lnx-a64.zip && \
84
117
  rm -f .notes.md
85
118
 
86
119
  # remove all generated artifacts
87
120
  clean
88
- shx rm -f nunjucks.js && \
89
- shx rm -rf nunjucks-*.zip
121
+ shx rm -rf dst-stage*
90
122
 
91
123
  # remove all generated artifacts
92
124
  distclean: clean
package/tsconfig.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2024",
4
- "module": "node20",
4
+ "module": "nodenext",
5
5
  "lib": [ "ES2024" ],
6
- "moduleResolution": "node16",
6
+ "moduleResolution": "nodenext",
7
7
  "resolveJsonModule": true,
8
8
  "declaration": false,
9
9
  "allowSyntheticDefaultImports": true,
@@ -11,7 +11,7 @@
11
11
  "forceConsistentCasingInFileNames": true,
12
12
  "strict": true,
13
13
  "skipLibCheck": true,
14
- "outDir": ".",
14
+ "outDir": "dst-stage1",
15
15
  "types": [ "node" ]
16
16
  },
17
17
  "include": [
package/vite.mts ADDED
@@ -0,0 +1,59 @@
1
+ /*
2
+ ** nunjucks -- Nunjucks Template Rendering Command-Line Interface
3
+ ** Copyright (c) 2019-2025 Dr. Ralf S. Engelschall <http://engelschall.com>
4
+ ** Licensed under MIT <http://spdx.org/licenses/MIT.html>
5
+ */
6
+
7
+ import * as Vite from "vite"
8
+ import { tscPlugin } from "@wroud/vite-plugin-tsc"
9
+ import nodeExternals from "rollup-plugin-node-externals"
10
+
11
+ export default Vite.defineConfig(({ command, mode }) => ({
12
+ logLevel: "info",
13
+ appType: "custom",
14
+ base: "",
15
+ root: "",
16
+ plugins: [
17
+ tscPlugin({
18
+ tscArgs: [ "--project", "tsconfig.json", ...(mode === "development" ? [ "--sourceMap" ] : []) ],
19
+ packageManager: "npx" as "npm",
20
+ prebuild: true
21
+ }),
22
+ nodeExternals({
23
+ builtins: true,
24
+ devDeps: false,
25
+ deps: false,
26
+ optDeps: false,
27
+ peerDeps: false
28
+ })
29
+ ],
30
+ resolve: {
31
+ mainFields: [ "module", "jsnext:main", "jsnext" ],
32
+ conditions: [ "node" ],
33
+ },
34
+ build: {
35
+ lib: {
36
+ entry: "dst-stage1/nunjucks.js",
37
+ formats: [ "cjs" ],
38
+ name: "Nunjucks",
39
+ fileName: () => "nunjucks.cjs"
40
+ },
41
+ target: "esnext",
42
+ outDir: "dst-stage2",
43
+ assetsDir: "",
44
+ emptyOutDir: (mode === "production"),
45
+ chunkSizeWarningLimit: 5000,
46
+ assetsInlineLimit: 0,
47
+ sourcemap: (mode === "development"),
48
+ minify: (mode === "production" ? "terser" : false),
49
+ reportCompressedSize: true,
50
+ rollupOptions: {
51
+ onwarn (warning, warn) {
52
+ if (warning.message.match(/Use of eval.*?is strongly discouraged/))
53
+ return
54
+ warn(warning)
55
+ }
56
+ }
57
+ }
58
+ }))
59
+
package/Dockerfile.mk DELETED
@@ -1,104 +0,0 @@
1
- ##
2
- ## Makefile: Docker Build Procedure
3
- ##
4
-
5
- # ==== DEFAULT ====
6
- # default build target
7
- TARGETS ?= build
8
- all: $(TARGETS)
9
-
10
- # ==== COMMON ====
11
- # configuration of container image
12
- DOCKER_IMAGE_CONFIG ?= __dummy=1
13
- IMAGE_CONFIG = \
14
- IMAGE_PREFIX=`egrep "ARG.*IMAGE_PREFIX" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
15
- IMAGE_NAME=`egrep "ARG.*IMAGE_NAME" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
16
- IMAGE_VERSION=`egrep "ARG.*IMAGE_VERSION" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
17
- IMAGE_RELEASE=`egrep "ARG.*IMAGE_RELEASE" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
18
- IMAGE_ALIAS=`egrep "ARG.*IMAGE_ALIAS" Dockerfile | sed -e 's;^.*=;;' | head -1` && \
19
- $(DOCKER_IMAGE_CONFIG)
20
-
21
- # ==== BUILD ====
22
- # (re)build a container image
23
- DOCKER_BUILD_FLAGS ?= --pull --no-cache
24
- build: Dockerfile
25
- @$(IMAGE_CONFIG) && \
26
- echo "++ building Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} ($${IMAGE_ALIAS})" && \
27
- docker build \
28
- $(DOCKER_BUILD_FLAGS) \
29
- --build-arg "IMAGE_PREFIX=$${IMAGE_PREFIX}" \
30
- --build-arg "IMAGE_NAME=$${IMAGE_NAME}" \
31
- --build-arg "IMAGE_VERSION=$${IMAGE_VERSION}" \
32
- --build-arg "IMAGE_RELEASE=$${IMAGE_RELEASE}" \
33
- --build-arg "IMAGE_ALIAS=$${IMAGE_ALIAS}" \
34
- -t $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_ALIAS} \
35
- -t $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION} \
36
- -t $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} \
37
- -f Dockerfile . && \
38
- docker image ls $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE}
39
-
40
- # ==== RUN ====
41
- # run a container image
42
- DOCKER_RUN_FLAGS ?= --rm -i -t -e TERM --init
43
- DOCKER_RUN_ARGS ?=
44
- run:
45
- @$(IMAGE_CONFIG) && \
46
- echo "++ running Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE}" && \
47
- docker run \
48
- --name "$${IMAGE_NAME}-temp" \
49
- $(DOCKER_RUN_FLAGS) \
50
- $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} \
51
- $(DOCKER_RUN_ARGS)
52
-
53
- # ==== INSPECT ====
54
- # inspect a container image
55
- DOCKER_INSPECT_FLAGS ?= --rm -i -t -e TERM -u root --entrypoint "/bin/bash"
56
- DOCKER_INSPECT_ARGS ?=
57
- inspect:
58
- @$(IMAGE_CONFIG) && \
59
- echo "++ inspecting Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE}" && \
60
- docker run \
61
- --name "$${IMAGE_NAME}-temp" \
62
- $(DOCKER_INSPECT_FLAGS) \
63
- $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} \
64
- $(DOCKER_INSPECT_ARGS)
65
-
66
- # ==== EXEC ====
67
- # enter a running container image
68
- DOCKER_EXEC_FLAGS ?= -i -t -e TERM
69
- DOCKER_EXEC_ARGS ?= /bin/bash
70
- exec:
71
- @$(IMAGE_CONFIG) && \
72
- echo "++ executing command in Docker container of Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE}" && \
73
- docker exec \
74
- $(DOCKER_EXEC_FLAGS) \
75
- `docker ps --filter "ancestor=$${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE}" --format "{{ .ID }}"` \
76
- $(DOCKER_EXEC_ARGS)
77
-
78
- # ==== PUSH ====
79
- # push container image to registry
80
- push:
81
- @$(IMAGE_CONFIG) && \
82
- echo "++ pushing Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} ($${IMAGE_ALIAS})" && \
83
- docker push $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} && \
84
- docker push $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION} && \
85
- docker push $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_ALIAS}
86
-
87
- # ==== CLEAN ====
88
- # remove container image
89
- clean:
90
- @$(IMAGE_CONFIG) && \
91
- echo "++ removing Docker image $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} ($${IMAGE_ALIAS})" && \
92
- docker image rm $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION}-$${IMAGE_RELEASE} >/dev/null 2>&1 || true && \
93
- docker image rm $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_VERSION} >/dev/null 2>&1 || true && \
94
- docker image rm $${IMAGE_PREFIX}$${IMAGE_NAME}:$${IMAGE_ALIAS} >/dev/null 2>&1 || true && \
95
-
96
- # ==== PRUNE ====
97
- # prune entire Docker environment
98
- prune:
99
- @echo "++ pruning Docker environment"
100
- docker container prune -f
101
- docker network prune -f
102
- docker volume prune -f
103
- docker image prune -f
104
-
package/nunjucks.1 DELETED
@@ -1,47 +0,0 @@
1
- .TH "NUNJUCKS" "1" "June 2025" "" ""
2
- .SH "NAME"
3
- \fBnunjucks\fR - Template Rendering Engine
4
- .SH "SYNOPSIS"
5
- .P
6
- \fBnunjucks\fR \[lB]\fB-h\fR|\fB--help\fR\[rB] \[lB]\fB-V\fR|\fB--version\fR\[rB] \[lB]\fB-c\fR|\fB--config\fR \fIconfig-file\fR\[rB] \[lB]\fB-C\fR|\fB--option\fR \fIkey\fR=\fIvalue\fR\[rB] \[lB]\fB-d\fR|\fB--defines\fR \fIcontext-file\fR\[rB] \[lB]\fB-D\fR|\fB--define\fR \fIkey\fR=\fIvalue\fR\[rB] \[lB]\fB-e\fR|\fB--extension\fR \fImodule-name\fR\[rB] \[lB]\fB-o\fR|\fB--output\fR \fIoutput-file\fR|\fB-\fR\[rB] \[lB]\fIinput-file\fR|\fB-\fR\[rB]
7
- .SH "DESCRIPTION"
8
- .P
9
- \fBnunjucks\fR(1) is a small command-line utility to render templates with the rich and powerful templating language \fBMozilla Nunjucks\fR \fI\(lahttps://mozilla.github.io/nunjucks/\(ra\fR. This allows you to define your configuration in a YAML file and then render an output file based on a template input file where your configuration can be expanded. It optionally can load Nunjucks addons like the ones from the companion \fBNunjucks Addons\fR \fI\(lahttps://github.com/rse/nunjucks-addons\(ra\fR package.
10
- .SH "OPTIONS"
11
- .P
12
- The following top-level options and arguments exist:
13
- .RS 0
14
- .IP \(bu 4
15
- \[lB]\fB-h\fR|\fB--help\fR\[rB] Show usage help.
16
- .IP \(bu 4
17
- \[lB]\fB-V\fR|\fB--version\fR\[rB] Show program version information.
18
- .IP \(bu 4
19
- \[lB]\fB-c\fR|\fB--config\fR \fIconfig-file\fR\[rB] Load Nunjucks configuration YAML file.
20
- .IP \(bu 4
21
- \[lB]\fB-C\fR|\fB--option\fR \fIkey\fR=\fIvalue\fR\[rB] Set Nunjucks configuration option.
22
- .IP \(bu 4
23
- \[lB]\fB-d\fR|\fB--defines\fR \fIcontext-file\fR\[rB] Load context definition YAML file. Can occur multiple times.
24
- .IP \(bu 4
25
- \[lB]\fB-D\fR|\fB--define\fR \fIkey\fR=\fIvalue\fR\[rB] Set context definition key/value. Can occur multiple times.
26
- .IP \(bu 4
27
- \[lB]\fB-e\fR|\fB--extension\fR \fImodule-name\fR\[rB] Load Nunjucks JavaScript extension module (installed via NPM).
28
- .IP \(bu 4
29
- \[lB]\fB-o\fR|\fB--output\fR \fIoutput-file\fR|\fB-\fR\[rB] Save output file (or stdout).
30
- .IP \(bu 4
31
- \[lB]\fB<input-file>\fR|\fB-\fR\[rB] Load input file (or stdin).
32
- .RE 0
33
-
34
- .SH "EXAMPLE"
35
- .P
36
- .RS 2
37
- .nf
38
- $ echo "Hello, {{who}}!" | nunjucks -D who=world -
39
- Hello, world!
40
- .fi
41
- .RE
42
- .SH "HISTORY"
43
- .P
44
- The \fBnunjucks\fR(1) utility was developed in August 2023 for being able to easily generate multiple configuration files for a complex \fIDocker-Compose\fR based setup.
45
- .SH "AUTHOR"
46
- .P
47
- Dr. Ralf S. Engelschall \fI\(larse@engelschall.com\(ra\fR
package/nunjucks.js DELETED
@@ -1,187 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- ** nunjucks -- Nunjucks Template Rendering Command-Line Interface
4
- ** Copyright (c) 2019-2025 Dr. Ralf S. Engelschall <http://engelschall.com>
5
- ** Licensed under MIT <http://spdx.org/licenses/MIT.html>
6
- */
7
- /* built-in requirements */
8
- import fs from "node:fs";
9
- import path from "node:path";
10
- import { createRequire } from "node:module";
11
- /* external requirements */
12
- import { Command } from "commander";
13
- import chalk from "chalk";
14
- import jsYAML from "js-yaml";
15
- import nunjucks from "nunjucks";
16
- import deepmerge from "deepmerge";
17
- /* load my own information */
18
- const my = JSON.parse(await fs.promises.readFile(new URL("./package.json", import.meta.url), "utf-8"));
19
- /* parse command-line arguments */
20
- const program = new Command();
21
- const reduceArray = (v, l) => l.concat([v]);
22
- program.name("nunjucks")
23
- .description("Nunjucks Template Rendering Command-Line Interface")
24
- .showHelpAfterError("hint: use option --help for usage information")
25
- .option("-h, --help", "show usage help", false)
26
- .option("-V, --version", "show program version information", false)
27
- .option("-c, --config <config-file>", "load Nunjucks configuration YAML file", "")
28
- .option("-C, --option <key>=<value>", "set Nunjucks configuration option", reduceArray, [])
29
- .option("-d, --defines <context-file>", "load context definition YAML file", reduceArray, [])
30
- .option("-D, --define <key>=<value>", "set context definition key/value", reduceArray, [])
31
- .option("-e, --extension <module-name>", "load Nunjucks JavaScript extension module", reduceArray, [])
32
- .option("-o, --output <output-file>", "save output file", "-")
33
- .argument("[<input-file>]", "input file");
34
- program.parse(process.argv);
35
- const argv = {
36
- ...program.opts(),
37
- _: program.args
38
- };
39
- /* handle special help request */
40
- if (argv.help) {
41
- console.log(program.helpInformation());
42
- console.log("Example:\n $ echo \"Hello, {{ who }}!\" | nunjucks -Dwho=World -\n");
43
- process.exit(0);
44
- }
45
- /* handle special version request */
46
- if (argv.version) {
47
- console.log(`${my.name} ${my.version} (Node.js ${process.versions.node}, Nunjucks: ${my.dependencies.nunjucks})`);
48
- console.log(`${my.description}`);
49
- console.log(`Copyright (c) 2019-2025 ${my.author.name} <${my.author.url}>`);
50
- console.log(`Licensed under ${my.license} <http://spdx.org/licenses/${my.license}.html>`);
51
- process.exit(0);
52
- }
53
- /* read input file */
54
- let input = "";
55
- if (argv._.length > 1) {
56
- console.error(chalk.red("nunjucks: ERROR: invalid number of arguments (zero or one input file expected)"));
57
- process.exit(1);
58
- }
59
- let inputFile = argv._[0] ?? "-";
60
- if (inputFile === "-") {
61
- inputFile = "<stdin>";
62
- process.stdin.setEncoding("utf-8");
63
- const BUFSIZE = 256;
64
- const buf = Buffer.alloc(BUFSIZE);
65
- while (true) {
66
- let bytesRead = 0;
67
- try {
68
- bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, null);
69
- }
70
- catch (ex) {
71
- if (ex.code === "EAGAIN")
72
- continue;
73
- else if (ex.code === "EOF")
74
- break;
75
- else
76
- throw ex;
77
- }
78
- if (bytesRead === 0)
79
- break;
80
- input += buf.toString("utf8", 0, bytesRead);
81
- }
82
- }
83
- else {
84
- if (!fs.existsSync(inputFile)) {
85
- console.error(chalk.red(`nunjucks: ERROR: failed to find input file: ${inputFile}`));
86
- process.exit(1);
87
- }
88
- input = fs.readFileSync(inputFile, { encoding: "utf8" });
89
- }
90
- /* provide context variables for template */
91
- let context = {};
92
- for (const define of argv.defines) {
93
- try {
94
- context = deepmerge(context, jsYAML.load(fs.readFileSync(define, { encoding: "utf8" })));
95
- }
96
- catch (ex) {
97
- console.error(chalk.red(`nunjucks: ERROR: failed to load context YAML file: ${ex.toString()}`));
98
- process.exit(1);
99
- }
100
- }
101
- /* expose environment variables to template */
102
- context.env = process.env;
103
- /* add context defines */
104
- argv.define.forEach((define) => {
105
- const match = define.match(/^([^=]+)(?:=(.*))?$/);
106
- if (!match)
107
- return;
108
- let [, key, val] = match;
109
- if (!key)
110
- return;
111
- if (val === undefined)
112
- val = "true";
113
- context[key] = val;
114
- });
115
- /* determine Nunjucks options */
116
- let options = {};
117
- if (argv.config) {
118
- try {
119
- options = jsYAML.load(fs.readFileSync(argv.config, { encoding: "utf8" }));
120
- }
121
- catch (ex) {
122
- console.error(chalk.red(`nunjucks: ERROR: failed to load options YAML file: ${ex.toString()}`));
123
- process.exit(1);
124
- }
125
- }
126
- if (argv.option.length > 0)
127
- options = Object.assign(options, argv.option);
128
- options = {
129
- autoescape: false,
130
- throwOnUndefined: false,
131
- trimBlocks: true,
132
- lstripBlocks: true,
133
- watch: false,
134
- noCache: true,
135
- ...options
136
- };
137
- /* configure environment */
138
- const env = nunjucks.configure(inputFile, options);
139
- /* load external extension files */
140
- for (const extension of argv.extension) {
141
- let modpath = path.resolve(extension);
142
- if (!fs.existsSync(modpath)) {
143
- try {
144
- const require = createRequire(import.meta.url);
145
- modpath = require.resolve(extension);
146
- }
147
- catch (_ex) {
148
- modpath = null;
149
- }
150
- }
151
- if (modpath === null) {
152
- console.error(chalk.red(`nunjucks: ERROR: failed to find extension module: ${extension}`));
153
- process.exit(1);
154
- }
155
- /* dynamically import the module */
156
- let mod;
157
- try {
158
- mod = await import(modpath);
159
- /* handle both default and named exports */
160
- mod = mod.default ?? mod;
161
- }
162
- catch (ex) {
163
- console.error(chalk.red(`nunjucks: ERROR: failed to load extension module: ${ex.toString()}`));
164
- process.exit(1);
165
- }
166
- if (!(mod !== null && typeof mod === "function")) {
167
- console.error(chalk.red(`nunjucks: ERROR: failed to call extension file: ${modpath}`));
168
- process.exit(1);
169
- }
170
- mod(env);
171
- }
172
- /* render Nunjucks template */
173
- let output;
174
- try {
175
- output = env.renderString(input, context);
176
- }
177
- catch (ex) {
178
- console.error(chalk.red(`nunjucks: ERROR: failed to render template: ${ex.toString()}`));
179
- process.exit(1);
180
- }
181
- /* write output */
182
- if (argv.output === "-")
183
- process.stdout.write(output);
184
- else
185
- fs.writeFileSync(argv.output, output, { encoding: "utf8" });
186
- /* die gracefully */
187
- process.exit(0);