Skip to content

Commit e5afbb4

Browse files
feat!: replace mustache with nunjucks variable rendering (#41)
* Initial plan * Complete migration from Mustache to Nunjucks templating engine Co-authored-by: FidelusAleksander <[email protected]> * Migrate from Mustache to Nunjucks templating engine Co-authored-by: FidelusAleksander <[email protected]> * Fix CodeQL security issues by removing autoescape false and adding path validation Co-authored-by: FidelusAleksander <[email protected]> * Update tests to use new Nunjucks template and remove old template Co-authored-by: FidelusAleksander <[email protected]> * Add unused_value field and multiline paragraph testing as requested Co-authored-by: FidelusAleksander <[email protected]> * fix: update template file references and add new test template * Update tests * Exclude test markdown files from linting * Remove traversal limitation * Update dist * Update markdownlint * Update markdownlint * Update markdownlint * Update README * Linting * Split unit tests into multiple files * Remove unused variables * Rename tests, remove json cases from enhanced functionality --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]>
1 parent fe99663 commit e5afbb4

19 files changed

+7009
-1146
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# GitHub Action: Text Variables
22

3-
This is a JavaScript-based GitHub Action that replaces mustache-style variables
3+
This is a JavaScript-based GitHub Action that replaces nunjucks-style variables
44
(`{{ variable }}`) in text templates. The action is designed to enable dynamic
55
content creation for GitHub workflows, particularly for customizing Issue/PR
66
descriptions, comments, and other text content.

.github/linters/.markdown-lint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ MD004:
99
MD013:
1010
tables: false
1111

12+
# Allow bare URLs
13+
MD034: false
14+
1215
# Trailing punctuation in heading
1316
MD026:
1417
punctuation: '.,;:!'

.github/linters/eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const compat = new FlatCompat({
1717

1818
export default [
1919
{
20-
ignores: ['node_modules/', 'dist/', 'coverage/', '*.json']
20+
ignores: ['node_modules/', 'dist/', 'coverage/', '*.json', '__tests__/*.md']
2121
},
2222
...compat.extends('eslint:recommended', 'plugin:jest/recommended'),
2323
github.getFlatConfigs().recommended,

.github/workflows/ci.yml

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,31 @@ jobs:
7171
id: template-file
7272
uses: ./
7373
with:
74-
template-file: ./__tests__/sample-template.md
74+
template-file: ./__tests__/test-template.md
7575
template-vars: |
7676
{
7777
"name": "Spidertocat",
78-
"person": { "name": "Spider Person" },
79-
"multiline_paragraph": "This is a multiline\nparagraph example."
78+
"user": {
79+
"email": "[email protected]",
80+
"role": "admin"
81+
},
82+
"repositories": [
83+
{ "name": "web-crawler", "language": "Python" },
84+
{ "name": "spider-bot", "language": "JavaScript" }
85+
],
86+
"full_repo_name": "skills-test/example-repository"
8087
}
8188
8289
- name: Verify Text File JSON Output
8390
env:
8491
UPDATED_TEXT: ${{ steps.template-file.outputs.updated-text }}
8592
run: |
8693
echo "$UPDATED_TEXT" | grep -q "Spidertocat" || exit 1
87-
echo "$UPDATED_TEXT" | grep -q "Spider Person" || exit 1
88-
echo "$UPDATED_TEXT" | grep -q "This is a multiline" || exit 1
89-
echo "$UPDATED_TEXT" | grep -q "paragraph example." || exit 1
94+
echo "$UPDATED_TEXT" | grep -q "[email protected]" || exit 1
95+
echo "$UPDATED_TEXT" | grep -q "🔑 You have admin privileges!" || exit 1
96+
echo "$UPDATED_TEXT" | grep -q "web-crawler (Python)" || exit 1
97+
echo "$UPDATED_TEXT" | grep -q "spider-bot (JavaScript)" || exit 1
98+
echo "$UPDATED_TEXT" | grep -q "https://github.com/skills-test/example-repository" || exit 1
9099
91100
run-action-tests-yaml:
92101
name: Test Action with YAML vars
@@ -117,19 +126,31 @@ jobs:
117126
id: template-file
118127
uses: ./
119128
with:
120-
template-file: ./__tests__/sample-template.md
129+
template-file: ./__tests__/test-template.md
121130
template-vars: |
122131
name: Codercat
123-
person:
124-
name: Coder Person
132+
user:
133+
134+
role: user
135+
repositories:
136+
- name: code-analyzer
137+
language: Go
138+
- name: dev-tools
139+
language: Rust
125140
multiline_paragraph: |
126141
This is a yaml multiline
127142
paragraph example.
143+
full_repo_name: skills-test/example-repository
128144
129145
- name: Verify Template File Output
130146
env:
131147
UPDATED_TEXT: ${{ steps.template-file.outputs.updated-text }}
132148
run: |
133149
echo "$UPDATED_TEXT" | grep -q "Codercat" || exit 1
150+
echo "$UPDATED_TEXT" | grep -q "[email protected]" || exit 1
151+
echo "$UPDATED_TEXT" | grep -q "👤 Regular user access" || exit 1
152+
echo "$UPDATED_TEXT" | grep -q "code-analyzer (Go)" || exit 1
153+
echo "$UPDATED_TEXT" | grep -q "dev-tools (Rust)" || exit 1
134154
echo "$UPDATED_TEXT" | grep -q "This is a yaml multiline" || exit 1
135-
echo "$UPDATED_TEXT" | grep -q "paragraph example." || exit 1
155+
echo "$UPDATED_TEXT" | grep -q "paragraph example" || exit 1
156+
echo "$UPDATED_TEXT" | grep -q "https://github.com/skills-test/example-repository" || exit 1

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
dist/
22
node_modules/
33
coverage/
4-
examples/
4+
examples/
5+
__tests__/test-template.md

README.md

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
[![GitHub release](https://img.shields.io/github/release/skills/action-text-variables.svg)](https://github.com/skills/action-text-variables/releases)
55
[![Continuous Integration](https://github.com/skills/action-text-variables/actions/workflows/ci.yml/badge.svg)](https://github.com/skills/action-text-variables/actions/workflows/ci.yml)
66

7-
Replace mustache style variables (`{{ variable }}`) in text templates. Returns
8-
modified text as an output for use in other actions.
7+
Replace [Nunjucks](https://mozilla.github.io/nunjucks/) style variables
8+
(`{{ variable }}`) in text templates. Returns modified text as an output for use
9+
in other actions.
910

1011
## Use Cases 💡
1112

@@ -24,7 +25,7 @@ modified text as an output for use in other actions.
2425
steps:
2526
- name: Build comment using template
2627
id: build-comment
27-
uses: skills/action-text-variables@v1
28+
uses: skills/action-text-variables@v3
2829
with:
2930
template-text: 'Hello {{ login }}, nice to meet you!'
3031
template-vars: >
@@ -42,7 +43,7 @@ steps:
4243
steps:
4344
- name: Build comment using template
4445
id: build-comment
45-
uses: skills/action-text-variables@v1
46+
uses: skills/action-text-variables@v3
4647
with:
4748
template-text: 'Hello {{ login }}, nice to meet you!'
4849
template-vars: |
@@ -82,13 +83,11 @@ steps:
8283
8384
- name: Build comment using template
8485
id: build-comment
85-
uses: skills/action-text-variables@v1
86+
uses: skills/action-text-variables@v3
8687
with:
8788
template-file: my-files/my-template.md
88-
template-vars: >
89-
{
90-
"login": "${{ github.actor }}"
91-
}
89+
template-vars: |
90+
login: ${{ github.actor }}
9291
9392
- name: Do something with result
9493
run: echo "${{ steps.build-comment.outputs.updated-text }}"
@@ -110,14 +109,12 @@ steps:
110109
111110
- name: Build comment using template
112111
id: build-comment
113-
uses: skills/action-text-variables@v1
112+
uses: skills/action-text-variables@v3
114113
with:
115114
template-file: exercise-toolkit/markdown-templates/step-feedback/lesson-finished.md
116-
template-vars: >
117-
{
118-
"login": "${{ github.actor }}",
119-
"repo_full_name": "${{ github.repository }}"
120-
}
115+
template-vars: |
116+
login: ${{ github.actor }}
117+
repo_full_name: ${{ github.repository }}
121118
122119
- name: Do something with result
123120
run: echo "${{ steps.build-comment.outputs.updated-text }}"

__tests__/basic-functionality.test.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* Unit tests for basic functionality of the action's main functionality, src/main.js
3+
* Tests basic template replacement for both template-file and template-text with JSON and YAML
4+
*/
5+
const core = require('@actions/core')
6+
const main = require('../src/main')
7+
8+
// Mock the GitHub Actions core library
9+
const getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
10+
const setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation()
11+
12+
// Mock the action's main function
13+
const runMock = jest.spyOn(main, 'run')
14+
15+
describe('Basic Functionality', () => {
16+
beforeEach(() => {
17+
jest.clearAllMocks()
18+
})
19+
20+
// Basic Variables - JSON - Template File
21+
it('Basic Variables - JSON - Template File', async () => {
22+
// Arrange - Mock responses for the inputs
23+
getInputMock.mockImplementation(inputName => {
24+
switch (inputName) {
25+
case 'template-file':
26+
return '__tests__/test-template.md'
27+
case 'template-vars':
28+
return JSON.stringify({
29+
name: 'John1',
30+
user: {
31+
32+
role: 'admin'
33+
},
34+
repositories: [
35+
{ name: 'repo1', language: 'JavaScript' },
36+
{ name: 'repo2', language: 'Python' }
37+
],
38+
unused_value: 'unused',
39+
multiline_paragraph: `
40+
Line 1
41+
Line 2
42+
`,
43+
extra_var: 'extra value'
44+
})
45+
default:
46+
return ''
47+
}
48+
})
49+
50+
// Act - Load the template and replace the variables
51+
await main.run()
52+
expect(runMock).toHaveReturned()
53+
54+
// Assert - Check output name
55+
const call = setOutputMock.mock.calls[0]
56+
const outputName = call[0]
57+
expect(outputName).toBe('updated-text')
58+
59+
// Assert - Check inserted values
60+
const outputValue = call[1]
61+
expect(outputValue).toMatch(/Hello John1!/)
62+
expect(outputValue).toMatch(/Your email is: john1@example.com/)
63+
expect(outputValue).toMatch(/Line 1\s*Line 2/)
64+
expect(outputValue).not.toMatch(/unused/)
65+
expect(outputValue).not.toMatch(/extra value/)
66+
})
67+
68+
// Basic Variables - JSON - Template Text
69+
it('Basic Variables - JSON - Template Text', async () => {
70+
// Arrange - Mock responses for the inputs
71+
getInputMock.mockImplementation(inputName => {
72+
switch (inputName) {
73+
case 'template-text':
74+
return 'Hello {{ name }}'
75+
case 'template-vars':
76+
return JSON.stringify({
77+
name: 'John1',
78+
extra_var: 'extra value'
79+
})
80+
default:
81+
return ''
82+
}
83+
})
84+
85+
// Act - Load the template and replace the variables
86+
await main.run()
87+
expect(runMock).toHaveReturned()
88+
89+
// Assert - Check output name
90+
const call = setOutputMock.mock.calls[0]
91+
const outputName = call[0]
92+
expect(outputName).toBe('updated-text')
93+
94+
// Assert - Check inserted values
95+
const outputValue = call[1]
96+
expect(outputValue).toBe('Hello John1')
97+
expect(outputValue).not.toMatch(/extra value/)
98+
})
99+
100+
// Basic Variables - YAML - Template Text
101+
it('Basic Variables - YAML - Template Text', async () => {
102+
// Arrange - Mock responses for the inputs
103+
getInputMock.mockImplementation(inputName => {
104+
switch (inputName) {
105+
case 'template-text':
106+
return 'Hello {{ name }}'
107+
case 'template-vars':
108+
return `
109+
name: John1
110+
extra_var: extra value
111+
`
112+
default:
113+
return ''
114+
}
115+
})
116+
117+
// Act - Load the template and replace the variables
118+
await main.run()
119+
expect(runMock).toHaveReturned()
120+
121+
// Assert - Check output name
122+
const call = setOutputMock.mock.calls[0]
123+
const outputName = call[0]
124+
expect(outputName).toBe('updated-text')
125+
126+
// Assert - Check inserted values
127+
const outputValue = call[1]
128+
expect(outputValue).toBe('Hello John1')
129+
expect(outputValue).not.toMatch(/extra value/)
130+
})
131+
132+
// Basic Variables - YAML - Template File
133+
it('Basic Variables - YAML - Template File', async () => {
134+
// Arrange - Mock responses for the inputs
135+
getInputMock.mockImplementation(inputName => {
136+
switch (inputName) {
137+
case 'template-file':
138+
return '__tests__/test-template.md'
139+
case 'template-vars':
140+
return `
141+
name: John1
142+
user:
143+
144+
role: admin
145+
repositories:
146+
- name: repo1
147+
language: JavaScript
148+
- name: repo2
149+
language: Python
150+
unused_value: unused
151+
multiline_paragraph: |
152+
Line 1
153+
Line 2
154+
extra_var: extra value
155+
`
156+
default:
157+
return ''
158+
}
159+
})
160+
161+
// Act - Load the template and replace the variables
162+
await main.run()
163+
expect(runMock).toHaveReturned()
164+
165+
// Assert - Check output name
166+
const call = setOutputMock.mock.calls[0]
167+
const outputName = call[0]
168+
expect(outputName).toBe('updated-text')
169+
170+
// Assert - Check inserted values
171+
const outputValue = call[1]
172+
expect(outputValue).toMatch(/Hello John1!/)
173+
expect(outputValue).toMatch(/Your email is: john1@example.com/)
174+
expect(outputValue).toMatch(/Line 1\s*Line 2/)
175+
expect(outputValue).not.toMatch(/unused/)
176+
expect(outputValue).not.toMatch(/extra value/)
177+
})
178+
})

0 commit comments

Comments
 (0)