@garyr/pt-cli 0.36.4 → 0.39.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/README.md +3 -1
- package/dist/commands/addCommand.js +98 -8
- package/dist/commands/configCommand.js +11 -4
- package/dist/commands/initCommand.js +65 -0
- package/dist/commands/learnCommand.js +78 -5
- package/dist/config.js +12 -2
- package/dist/index.js +6 -3
- package/dist/substitute.js +25 -4
- package/doc/configuration.md +70 -8
- package/doc/usage.md +71 -0
- package/doc/variable_substitution_example.md +64 -0
- package/package.json +1 -1
- package/src/commands/addCommand.ts +109 -8
- package/src/commands/configCommand.ts +11 -4
- package/src/commands/initCommand.ts +77 -0
- package/src/commands/learnCommand.ts +83 -8
- package/src/config.ts +14 -2
- package/src/index.ts +6 -3
- package/src/substitute.ts +29 -4
- package/tests/env-scanning.test.ts +330 -0
- package/tests/final-rst-verification.test.ts +173 -0
- package/tests/nested-variable-expansion.test.ts +242 -0
- package/tests/rst-env-example.test.ts +208 -0
- package/tests/substitute.test.ts +14 -6
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// Force a temporary home directory for testing before importing anything from the CLI
|
|
7
|
+
const testHome = path.join(process.cwd(), '.test-home-nested');
|
|
8
|
+
process.env.HOME = testHome;
|
|
9
|
+
|
|
10
|
+
// Import the init command to test the nested variable expansion functionality
|
|
11
|
+
import { init } from '../src/commands/initCommand.js';
|
|
12
|
+
import { loadConfig, saveConfig, PtConfig } from '../src/config.js';
|
|
13
|
+
|
|
14
|
+
// Helper to clean up test directories
|
|
15
|
+
function cleanup(...paths: string[]) {
|
|
16
|
+
for (const p of paths) {
|
|
17
|
+
if (fs.existsSync(p)) {
|
|
18
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Helper to set up a config with a template for testing
|
|
24
|
+
function setupTestConfig(templateName: string, template: any): PtConfig {
|
|
25
|
+
const config: PtConfig = {
|
|
26
|
+
version: '3.0',
|
|
27
|
+
templates: {
|
|
28
|
+
[templateName]: template
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
saveConfig(config);
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('nested variable expansion: prefix variable with nested {{ project }} reference', async () => {
|
|
36
|
+
const parentEnvDir = path.join(process.cwd(), 'test-parent-nested');
|
|
37
|
+
const projectDest = path.join(parentEnvDir, 'test-nested-project');
|
|
38
|
+
const templateRoot = path.join(process.cwd(), 'test-nested-tpl-root');
|
|
39
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
40
|
+
|
|
41
|
+
// Create template root with a file containing the nested variable example
|
|
42
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
path.join(templateRoot, 'README.md'),
|
|
45
|
+
'# {{ prefix }}\n\nThis is a test project.\n'
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Set up config with template that has the nested variable example
|
|
49
|
+
setupTestConfig('nested-tpl', {
|
|
50
|
+
description: 'Template with nested variable expansion',
|
|
51
|
+
templateRoot: templateRoot,
|
|
52
|
+
folders: [],
|
|
53
|
+
variables: [
|
|
54
|
+
{ name: 'prefix', prompt: 'Project prefix:', required: true },
|
|
55
|
+
{ name: 'project', prompt: 'Project name:', default: 'default' }
|
|
56
|
+
],
|
|
57
|
+
copy_files: [
|
|
58
|
+
{ src: 'README.md', dest: 'README.md', substitute_variables: true }
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Create parent directory with .env file containing the exact example from the user's request
|
|
63
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
path.join(parentEnvDir, '.env'),
|
|
66
|
+
`prefix='rst_{{ project }}'\nproject=MyProject\n`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Run init from within the parent directory
|
|
70
|
+
await init('nested-tpl', projectDest, {
|
|
71
|
+
yes: true,
|
|
72
|
+
skipPostConfig: true
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Verify nested variable expansion worked correctly
|
|
76
|
+
const readme = fs.readFileSync(path.join(projectDest, 'README.md'), 'utf-8');
|
|
77
|
+
|
|
78
|
+
// The prefix variable should be 'rst_MyProject' (after expanding {{ project }})
|
|
79
|
+
assert.ok(readme.includes('rst_MyProject'), 'README should contain expanded prefix value');
|
|
80
|
+
assert.ok(!readme.includes('{{ prefix }}'), 'README should NOT contain variable placeholder');
|
|
81
|
+
assert.ok(!readme.includes('{{ project }}'), 'README should NOT contain nested variable placeholder');
|
|
82
|
+
assert.ok(!readme.includes('rst_{{ project }}'), 'README should NOT contain unexpanded nested variable');
|
|
83
|
+
|
|
84
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('nested variable expansion: multiple levels of nesting', async () => {
|
|
88
|
+
const parentEnvDir = path.join(process.cwd(), 'test-parent-deep-nested');
|
|
89
|
+
const projectDest = path.join(parentEnvDir, 'test-deep-nested-project');
|
|
90
|
+
const templateRoot = path.join(process.cwd(), 'test-deep-nested-tpl-root');
|
|
91
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
92
|
+
|
|
93
|
+
// Create template root with a file containing deeply nested variables
|
|
94
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(templateRoot, 'config.txt'),
|
|
97
|
+
'name={{ prefix }}_{{ project }}_{{ version }}\n'
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Set up config with template that has deeply nested variables
|
|
101
|
+
setupTestConfig('deep-nested-tpl', {
|
|
102
|
+
description: 'Template with deeply nested variables',
|
|
103
|
+
templateRoot: templateRoot,
|
|
104
|
+
folders: [],
|
|
105
|
+
variables: [
|
|
106
|
+
{ name: 'prefix', prompt: 'Prefix:', required: true },
|
|
107
|
+
{ name: 'project', prompt: 'Project:', default: 'default' },
|
|
108
|
+
{ name: 'version', prompt: 'Version:', default: '1.0' }
|
|
109
|
+
],
|
|
110
|
+
copy_files: [
|
|
111
|
+
{ src: 'config.txt', dest: 'config.txt', substitute_variables: true }
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Create parent directory with .env file containing deeply nested variables
|
|
116
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
path.join(parentEnvDir, '.env'),
|
|
119
|
+
`prefix='app_{{ env }}'\nenv=prod\nproject=MyApp\nversion=2.0\n`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Run init from within the parent directory
|
|
123
|
+
await init('deep-nested-tpl', projectDest, {
|
|
124
|
+
yes: true,
|
|
125
|
+
skipPostConfig: true
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Verify deeply nested variable expansion worked correctly
|
|
129
|
+
const configContent = fs.readFileSync(path.join(projectDest, 'config.txt'), 'utf-8');
|
|
130
|
+
|
|
131
|
+
// The prefix variable should be 'app_prod' (after expanding {{ env }})
|
|
132
|
+
// The final result should be 'app_prod_MyApp_2.0'
|
|
133
|
+
assert.ok(configContent.includes('app_prod_MyApp_2.0'), 'config.txt should contain fully expanded variables');
|
|
134
|
+
assert.ok(!configContent.includes('{{ prefix }}'), 'config.txt should NOT contain variable placeholders');
|
|
135
|
+
assert.ok(!configContent.includes('{{ project }}'), 'config.txt should NOT contain variable placeholders');
|
|
136
|
+
assert.ok(!configContent.includes('{{ version }}'), 'config.txt should NOT contain variable placeholders');
|
|
137
|
+
assert.ok(!configContent.includes('{{ env }}'), 'config.txt should NOT contain nested variable placeholders');
|
|
138
|
+
assert.ok(!configContent.includes('app_{{ env }}'), 'config.txt should NOT contain unexpanded nested variable');
|
|
139
|
+
|
|
140
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('nested variable expansion: handles circular references gracefully', async () => {
|
|
144
|
+
const parentEnvDir = path.join(process.cwd(), 'test-parent-circular');
|
|
145
|
+
const projectDest = path.join(parentEnvDir, 'test-circular-project');
|
|
146
|
+
const templateRoot = path.join(process.cwd(), 'test-circular-tpl-root');
|
|
147
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
148
|
+
|
|
149
|
+
// Create template root with a file containing potentially circular variables
|
|
150
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
path.join(templateRoot, 'data.txt'),
|
|
153
|
+
'value={{ a }}\n'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Set up config with template that has variables that could cause circular references
|
|
157
|
+
setupTestConfig('circular-tpl', {
|
|
158
|
+
description: 'Template with potential circular references',
|
|
159
|
+
templateRoot: templateRoot,
|
|
160
|
+
folders: [],
|
|
161
|
+
variables: [
|
|
162
|
+
{ name: 'a', prompt: 'Value a:', required: true }
|
|
163
|
+
],
|
|
164
|
+
copy_files: [
|
|
165
|
+
{ src: 'data.txt', dest: 'data.txt', substitute_variables: true }
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Create parent directory with .env file that could cause circular references
|
|
170
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
171
|
+
fs.writeFileSync(
|
|
172
|
+
path.join(parentEnvDir, '.env'),
|
|
173
|
+
`a={{ a }}\n`
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Run init from within the parent directory - should not hang or crash
|
|
177
|
+
await init('circular-tpl', projectDest, {
|
|
178
|
+
yes: true,
|
|
179
|
+
skipPostConfig: true
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Verify the feature handles circular references without hanging
|
|
183
|
+
const dataContent = fs.readFileSync(path.join(projectDest, 'data.txt'), 'utf-8');
|
|
184
|
+
|
|
185
|
+
// The circular reference should be handled gracefully (either left as-is or replaced with empty)
|
|
186
|
+
assert.ok(dataContent.includes('{{ a }}') || dataContent.includes(''),
|
|
187
|
+
'circular reference should be handled without hanging');
|
|
188
|
+
assert.ok(dataContent.includes('value='), 'data.txt should still contain the original structure');
|
|
189
|
+
|
|
190
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('nested variable expansion: handles missing nested variables', async () => {
|
|
194
|
+
const parentEnvDir = path.join(process.cwd(), 'test-parent-missing-nested');
|
|
195
|
+
const projectDest = path.join(parentEnvDir, 'test-missing-nested-project');
|
|
196
|
+
const templateRoot = path.join(process.cwd(), 'test-missing-nested-tpl-root');
|
|
197
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
198
|
+
|
|
199
|
+
// Create template root with a file containing nested variables
|
|
200
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
path.join(templateRoot, 'README.md'),
|
|
203
|
+
'# {{ prefix }}\n\nThis is a test project.\n'
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Set up config with template that has nested variables
|
|
207
|
+
setupTestConfig('missing-nested-tpl', {
|
|
208
|
+
description: 'Template with missing nested variables',
|
|
209
|
+
templateRoot: templateRoot,
|
|
210
|
+
folders: [],
|
|
211
|
+
variables: [
|
|
212
|
+
{ name: 'prefix', prompt: 'Prefix:', required: true },
|
|
213
|
+
{ name: 'project', prompt: 'Project:', default: 'default' }
|
|
214
|
+
],
|
|
215
|
+
copy_files: [
|
|
216
|
+
{ src: 'README.md', dest: 'README.md', substitute_variables: true }
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Create parent directory with .env file that has nested variable but missing the referenced variable
|
|
221
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
222
|
+
fs.writeFileSync(
|
|
223
|
+
path.join(parentEnvDir, '.env'),
|
|
224
|
+
`prefix='rst_{{ missing_var }}'\n`
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Run init from within the parent directory
|
|
228
|
+
await init('missing-nested-tpl', projectDest, {
|
|
229
|
+
yes: true,
|
|
230
|
+
skipPostConfig: true
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Verify that missing nested variables are handled gracefully
|
|
234
|
+
const readme = fs.readFileSync(path.join(projectDest, 'README.md'), 'utf-8');
|
|
235
|
+
|
|
236
|
+
// The missing variable should remain as placeholder or be replaced with empty
|
|
237
|
+
assert.ok(readme.includes('# ') || readme.includes('rst_'), 'README should contain the prefix value');
|
|
238
|
+
assert.ok(!readme.includes('{{ prefix }}'), 'README should NOT contain the original prefix placeholder');
|
|
239
|
+
// The missing variable might remain as placeholder or be replaced with empty - both are acceptable
|
|
240
|
+
|
|
241
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
242
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// Force a temporary home directory for testing before importing anything from the CLI
|
|
7
|
+
const testHome = path.join(process.cwd(), '.test-home-rst-example');
|
|
8
|
+
process.env.HOME = testHome;
|
|
9
|
+
|
|
10
|
+
// Import the init command to test the nested variable expansion functionality
|
|
11
|
+
import { init } from '../src/commands/initCommand.js';
|
|
12
|
+
import { loadConfig, saveConfig, PtConfig } from '../src/config.js';
|
|
13
|
+
|
|
14
|
+
// Helper to clean up test directories
|
|
15
|
+
function cleanup(...paths: string[]) {
|
|
16
|
+
for (const p of paths) {
|
|
17
|
+
if (fs.existsSync(p)) {
|
|
18
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Helper to set up a config with a template for testing
|
|
24
|
+
function setupTestConfig(templateName: string, template: any): PtConfig {
|
|
25
|
+
const config: PtConfig = {
|
|
26
|
+
version: '3.0',
|
|
27
|
+
templates: {
|
|
28
|
+
[templateName]: template
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
saveConfig(config);
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('RST example: prefix variable with nested {{ project }} reference', async () => {
|
|
36
|
+
// Simulate the exact scenario from /mnt/production/CLIENT/RST/.env
|
|
37
|
+
const parentEnvDir = path.join(process.cwd(), 'test-rst-parent');
|
|
38
|
+
const projectDest = path.join(parentEnvDir, 'test-rst-project');
|
|
39
|
+
const templateRoot = path.join(process.cwd(), 'test-rst-tpl-root');
|
|
40
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
41
|
+
|
|
42
|
+
// Create template root with a file containing the exact example from the user's request
|
|
43
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
44
|
+
fs.writeFileSync(
|
|
45
|
+
path.join(templateRoot, 'README.md'),
|
|
46
|
+
'# {{ prefix }}\n\nThis is a test project.\n'
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Set up config with template that has the nested variable example
|
|
50
|
+
setupTestConfig('rst-tpl', {
|
|
51
|
+
description: 'RST Template with nested variable expansion',
|
|
52
|
+
templateRoot: templateRoot,
|
|
53
|
+
folders: [],
|
|
54
|
+
variables: [
|
|
55
|
+
{ name: 'prefix', prompt: 'Project prefix:', required: true },
|
|
56
|
+
{ name: 'project', prompt: 'Project name:', default: 'default' }
|
|
57
|
+
],
|
|
58
|
+
copy_files: [
|
|
59
|
+
{ src: 'README.md', dest: 'README.md', substitute_variables: true }
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Create parent directory with .env file containing the exact example from the user's request
|
|
64
|
+
// This simulates /mnt/production/CLIENT/RST/.env
|
|
65
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
path.join(parentEnvDir, '.env'),
|
|
68
|
+
`prefix='rst_{{ project }}'\nproject=MyRSTProject\n`
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Run init from within the parent directory
|
|
72
|
+
await init('rst-tpl', projectDest, {
|
|
73
|
+
yes: true,
|
|
74
|
+
skipPostConfig: true
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Verify nested variable expansion worked correctly
|
|
78
|
+
const readme = fs.readFileSync(path.join(projectDest, 'README.md'), 'utf-8');
|
|
79
|
+
|
|
80
|
+
// The prefix variable should be 'rst_MyRSTProject' (after expanding {{ project }})
|
|
81
|
+
assert.ok(readme.includes('rst_MyRSTProject'), 'README should contain expanded prefix value');
|
|
82
|
+
assert.ok(!readme.includes('{{ prefix }}'), 'README should NOT contain variable placeholder');
|
|
83
|
+
assert.ok(!readme.includes('{{ project }}'), 'README should NOT contain nested variable placeholder');
|
|
84
|
+
assert.ok(!readme.includes('rst_{{ project }}'), 'README should NOT contain unexpanded nested variable');
|
|
85
|
+
|
|
86
|
+
console.log('✓ RST example test passed: nested variable expansion works correctly');
|
|
87
|
+
console.log(` Input: prefix='rst_{{ project }}', project=MyRSTProject`);
|
|
88
|
+
console.log(` Output: prefix resolved to 'rst_MyRSTProject'`);
|
|
89
|
+
|
|
90
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('RST example: multiple nested variables in .env', async () => {
|
|
94
|
+
// Test multiple nested variables in a single .env file
|
|
95
|
+
const parentEnvDir = path.join(process.cwd(), 'test-rst-multi-parent');
|
|
96
|
+
const projectDest = path.join(parentEnvDir, 'test-rst-multi-project');
|
|
97
|
+
const templateRoot = path.join(process.cwd(), 'test-rst-multi-tpl-root');
|
|
98
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
99
|
+
|
|
100
|
+
// Create template root with a file containing multiple nested variables
|
|
101
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
path.join(templateRoot, 'config.txt'),
|
|
104
|
+
'name={{ prefix }}_{{ project }}_{{ version }}\n'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Set up config with template that has multiple nested variables
|
|
108
|
+
setupTestConfig('rst-multi-tpl', {
|
|
109
|
+
description: 'RST Template with multiple nested variables',
|
|
110
|
+
templateRoot: templateRoot,
|
|
111
|
+
folders: [],
|
|
112
|
+
variables: [
|
|
113
|
+
{ name: 'prefix', prompt: 'Prefix:', required: true },
|
|
114
|
+
{ name: 'project', prompt: 'Project:', default: 'default' },
|
|
115
|
+
{ name: 'version', prompt: 'Version:', default: '1.0' }
|
|
116
|
+
],
|
|
117
|
+
copy_files: [
|
|
118
|
+
{ src: 'config.txt', dest: 'config.txt', substitute_variables: true }
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Create parent directory with .env file containing multiple nested variables
|
|
123
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
124
|
+
fs.writeFileSync(
|
|
125
|
+
path.join(parentEnvDir, '.env'),
|
|
126
|
+
`prefix='app_{{ env }}'\nenv=prod\nproject=MyApp\nversion=2.0\n`
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Run init from within the parent directory
|
|
130
|
+
await init('rst-multi-tpl', projectDest, {
|
|
131
|
+
yes: true,
|
|
132
|
+
skipPostConfig: true
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Verify multiple nested variables were expanded correctly
|
|
136
|
+
const configContent = fs.readFileSync(path.join(projectDest, 'config.txt'), 'utf-8');
|
|
137
|
+
|
|
138
|
+
// The prefix variable should be 'app_prod' (after expanding {{ env }})
|
|
139
|
+
// The final result should be 'app_prod_MyApp_2.0'
|
|
140
|
+
assert.ok(configContent.includes('app_prod_MyApp_2.0'), 'config.txt should contain fully expanded variables');
|
|
141
|
+
assert.ok(!configContent.includes('{{ prefix }}'), 'config.txt should NOT contain variable placeholders');
|
|
142
|
+
assert.ok(!configContent.includes('{{ project }}'), 'config.txt should NOT contain variable placeholders');
|
|
143
|
+
assert.ok(!configContent.includes('{{ version }}'), 'config.txt should NOT contain variable placeholders');
|
|
144
|
+
assert.ok(!configContent.includes('{{ env }}'), 'config.txt should NOT contain nested variable placeholders');
|
|
145
|
+
assert.ok(!configContent.includes('app_{{ env }}'), 'config.txt should NOT contain unexpanded nested variable');
|
|
146
|
+
|
|
147
|
+
console.log('✓ RST multi-variable test passed: all nested variables expanded correctly');
|
|
148
|
+
console.log(` Input: prefix='app_{{ env }}', env=prod, project=MyApp, version=2.0`);
|
|
149
|
+
console.log(` Output: name resolved to 'app_prod_MyApp_2.0'`);
|
|
150
|
+
|
|
151
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('RST example: .env values are used as defaults in interactive mode', async () => {
|
|
155
|
+
// Test that .env values are used as defaults when user doesn't override
|
|
156
|
+
const parentEnvDir = path.join(process.cwd(), 'test-rst-defaults-parent');
|
|
157
|
+
const projectDest = path.join(parentEnvDir, 'test-rst-defaults-project');
|
|
158
|
+
const templateRoot = path.join(process.cwd(), 'test-rst-defaults-tpl-root');
|
|
159
|
+
cleanup(projectDest, templateRoot, testHome);
|
|
160
|
+
|
|
161
|
+
// Create template root with a file containing variables
|
|
162
|
+
fs.mkdirSync(templateRoot, { recursive: true });
|
|
163
|
+
fs.writeFileSync(
|
|
164
|
+
path.join(templateRoot, 'README.md'),
|
|
165
|
+
'# {{ prefix }}\n\nThis is a test project.\n'
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Set up config with template that has variables with defaults
|
|
169
|
+
setupTestConfig('rst-defaults-tpl', {
|
|
170
|
+
description: 'RST Template with defaults',
|
|
171
|
+
templateRoot: templateRoot,
|
|
172
|
+
folders: [],
|
|
173
|
+
variables: [
|
|
174
|
+
{ name: 'prefix', prompt: 'Project prefix:', required: true, default: 'default_prefix' },
|
|
175
|
+
{ name: 'project', prompt: 'Project name:', default: 'default_project' }
|
|
176
|
+
],
|
|
177
|
+
copy_files: [
|
|
178
|
+
{ src: 'README.md', dest: 'README.md', substitute_variables: true }
|
|
179
|
+
]
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Create parent directory with .env file containing values that should be used as defaults
|
|
183
|
+
fs.mkdirSync(parentEnvDir, { recursive: true });
|
|
184
|
+
fs.writeFileSync(
|
|
185
|
+
path.join(parentEnvDir, '.env'),
|
|
186
|
+
`prefix='rst_{{ project }}'\nproject=EnvProject\n`
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Run init from within the parent directory
|
|
190
|
+
await init('rst-defaults-tpl', projectDest, {
|
|
191
|
+
yes: true,
|
|
192
|
+
skipPostConfig: true
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Verify .env values were used as defaults
|
|
196
|
+
const readme = fs.readFileSync(path.join(projectDest, 'README.md'), 'utf-8');
|
|
197
|
+
|
|
198
|
+
// The prefix variable should be 'rst_EnvProject' (after expanding {{ project }})
|
|
199
|
+
assert.ok(readme.includes('rst_EnvProject'), 'README should contain expanded prefix value from .env');
|
|
200
|
+
assert.ok(!readme.includes('{{ prefix }}'), 'README should NOT contain variable placeholder');
|
|
201
|
+
assert.ok(!readme.includes('default_prefix'), 'README should NOT contain default value');
|
|
202
|
+
|
|
203
|
+
console.log('✓ RST defaults test passed: .env values used as defaults');
|
|
204
|
+
console.log(` Input: prefix='rst_{{ project }}', project=EnvProject`);
|
|
205
|
+
console.log(` Output: prefix resolved to 'rst_EnvProject'`);
|
|
206
|
+
|
|
207
|
+
cleanup(projectDest, templateRoot, parentEnvDir, testHome);
|
|
208
|
+
});
|
package/tests/substitute.test.ts
CHANGED
|
@@ -58,14 +58,15 @@ test('substituteVariables: variable with no whitespace in braces', () => {
|
|
|
58
58
|
|
|
59
59
|
test('substituteVariables: missing variable remains as normalized placeholder', () => {
|
|
60
60
|
const result = substituteVariables('Hello {{ unknown }}!', {});
|
|
61
|
-
// The regex
|
|
62
|
-
assert.strictEqual(result, 'Hello {{unknown}}!');
|
|
61
|
+
// The regex preserves original spacing when variable is not found
|
|
62
|
+
assert.strictEqual(result, 'Hello {{ unknown }}!');
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
test('substituteVariables: empty variables object leaves all placeholders', () => {
|
|
66
66
|
const content = '{{ foo }} and {{ bar }}';
|
|
67
67
|
const result = substituteVariables(content, {});
|
|
68
|
-
|
|
68
|
+
// The regex preserves original spacing when variables are not found
|
|
69
|
+
assert.strictEqual(result, '{{ foo }} and {{ bar }}');
|
|
69
70
|
});
|
|
70
71
|
|
|
71
72
|
test('substituteVariables: no variables in content returns content unchanged', () => {
|
|
@@ -79,7 +80,8 @@ test('substituteVariables: mixed - some found, some not', () => {
|
|
|
79
80
|
'{{ found }} and {{ missing }}',
|
|
80
81
|
{ found: 'YES' }
|
|
81
82
|
);
|
|
82
|
-
|
|
83
|
+
// The regex preserves original spacing when variable is not found
|
|
84
|
+
assert.strictEqual(result, 'YES and {{ missing }}');
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
test('substituteVariables: repeated variable is substituted in all occurrences', () => {
|
|
@@ -95,8 +97,14 @@ test('substituteVariables: empty string content', () => {
|
|
|
95
97
|
assert.strictEqual(result, '');
|
|
96
98
|
});
|
|
97
99
|
|
|
98
|
-
test('substituteVariables: value containing braces is
|
|
99
|
-
//
|
|
100
|
+
test('substituteVariables: value containing braces is recursively expanded', () => {
|
|
101
|
+
// With recursive expansion, {{ other }} in the value will be expanded if 'other' exists
|
|
102
|
+
const result = substituteVariables('{{ name }}', { name: '{{ other }}', other: 'expanded' });
|
|
103
|
+
assert.strictEqual(result, 'expanded');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('substituteVariables: value containing braces remains as-is if nested variable not found', () => {
|
|
107
|
+
// With recursive expansion, {{ other }} in the value will remain as-is if 'other' doesn't exist
|
|
100
108
|
const result = substituteVariables('{{ name }}', { name: '{{ other }}' });
|
|
101
109
|
assert.strictEqual(result, '{{ other }}');
|
|
102
110
|
});
|