@garyr/pt-cli 0.30.1 → 0.31.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/dist/commands/learnCommand.js +66 -51
- package/doc/testing.md +48 -0
- package/package.json +1 -1
- package/src/commands/learnCommand.ts +65 -53
- package/tests/config-utils.test.ts +473 -0
- package/tests/config.test.ts +19 -4
- package/tests/init.test.ts +428 -14
- package/tests/learn.test.ts +838 -0
- package/tests/substitute.test.ts +479 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { test, after } 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 from the CLI
|
|
7
|
+
const testHome = path.join(process.cwd(), '.test-home-substitute');
|
|
8
|
+
process.env.HOME = testHome;
|
|
9
|
+
|
|
10
|
+
import { substituteVariables, processCopyFiles } from '../src/substitute.js';
|
|
11
|
+
import { TemplateConfig } from '../src/config.js';
|
|
12
|
+
|
|
13
|
+
// Helper: create a temp directory and return its path
|
|
14
|
+
function makeTempDir(name: string): string {
|
|
15
|
+
const dir = path.join(process.cwd(), `.test-tmp-substitute-${name}-${Date.now()}`);
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Helper: recursively remove a directory
|
|
21
|
+
function rmDir(dir: string) {
|
|
22
|
+
if (fs.existsSync(dir)) {
|
|
23
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clean up the test home after all tests
|
|
28
|
+
after(() => {
|
|
29
|
+
rmDir(testHome);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// substituteVariables tests
|
|
34
|
+
// ============================================================
|
|
35
|
+
|
|
36
|
+
test('substituteVariables: basic single variable substitution', () => {
|
|
37
|
+
const result = substituteVariables('Hello {{ name }}!', { name: 'World' });
|
|
38
|
+
assert.strictEqual(result, 'Hello World!');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('substituteVariables: multiple variables in one string', () => {
|
|
42
|
+
const result = substituteVariables(
|
|
43
|
+
'{{ greeting }}, {{ name }}! Welcome to {{ place }}.',
|
|
44
|
+
{ greeting: 'Hello', name: 'Alice', place: 'Wonderland' }
|
|
45
|
+
);
|
|
46
|
+
assert.strictEqual(result, 'Hello, Alice! Welcome to Wonderland.');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('substituteVariables: variable with extra whitespace in braces', () => {
|
|
50
|
+
const result = substituteVariables('Value is {{ name }}.', { name: 'OK' });
|
|
51
|
+
assert.strictEqual(result, 'Value is OK.');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('substituteVariables: variable with no whitespace in braces', () => {
|
|
55
|
+
const result = substituteVariables('Value is {{name}}.', { name: 'OK' });
|
|
56
|
+
assert.strictEqual(result, 'Value is OK.');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('substituteVariables: missing variable remains as normalized placeholder', () => {
|
|
60
|
+
const result = substituteVariables('Hello {{ unknown }}!', {});
|
|
61
|
+
// The regex replaces {{ unknown }} with {{unknown}} (no spaces) when not found
|
|
62
|
+
assert.strictEqual(result, 'Hello {{unknown}}!');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('substituteVariables: empty variables object leaves all placeholders', () => {
|
|
66
|
+
const content = '{{ foo }} and {{ bar }}';
|
|
67
|
+
const result = substituteVariables(content, {});
|
|
68
|
+
assert.strictEqual(result, '{{foo}} and {{bar}}');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('substituteVariables: no variables in content returns content unchanged', () => {
|
|
72
|
+
const content = 'Just a plain string with no mustaches.';
|
|
73
|
+
const result = substituteVariables(content, { name: 'ignored' });
|
|
74
|
+
assert.strictEqual(result, content);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('substituteVariables: mixed - some found, some not', () => {
|
|
78
|
+
const result = substituteVariables(
|
|
79
|
+
'{{ found }} and {{ missing }}',
|
|
80
|
+
{ found: 'YES' }
|
|
81
|
+
);
|
|
82
|
+
assert.strictEqual(result, 'YES and {{missing}}');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('substituteVariables: repeated variable is substituted in all occurrences', () => {
|
|
86
|
+
const result = substituteVariables(
|
|
87
|
+
'{{ x }} + {{ x }} = {{ result }}',
|
|
88
|
+
{ x: '2', result: '4' }
|
|
89
|
+
);
|
|
90
|
+
assert.strictEqual(result, '2 + 2 = 4');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('substituteVariables: empty string content', () => {
|
|
94
|
+
const result = substituteVariables('', { name: 'test' });
|
|
95
|
+
assert.strictEqual(result, '');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('substituteVariables: value containing braces is not re-processed', () => {
|
|
99
|
+
// Substituted values should be inserted literally, no recursive substitution
|
|
100
|
+
const result = substituteVariables('{{ name }}', { name: '{{ other }}' });
|
|
101
|
+
assert.strictEqual(result, '{{ other }}');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ============================================================
|
|
105
|
+
// processCopyFiles tests
|
|
106
|
+
// ============================================================
|
|
107
|
+
|
|
108
|
+
test('processCopyFiles: template with no copy_files returns immediately', async () => {
|
|
109
|
+
const template: TemplateConfig = {
|
|
110
|
+
description: 'No copy files',
|
|
111
|
+
folders: [],
|
|
112
|
+
// no copy_files key at all
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Should complete without error
|
|
116
|
+
await processCopyFiles('/nonexistent', '/nonexistent', template, {}, false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('processCopyFiles: copy single file without substitution', async () => {
|
|
120
|
+
const templateRoot = makeTempDir('copy-single-src');
|
|
121
|
+
const destDir = makeTempDir('copy-single-dest');
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Create source file
|
|
125
|
+
fs.writeFileSync(path.join(templateRoot, 'readme.txt'), 'Hello {{ name }}!');
|
|
126
|
+
|
|
127
|
+
const template: TemplateConfig = {
|
|
128
|
+
description: 'Single copy',
|
|
129
|
+
folders: [],
|
|
130
|
+
copy_files: [
|
|
131
|
+
{ src: 'readme.txt', dest: 'readme.txt' }
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
await processCopyFiles(templateRoot, destDir, template, { name: 'World' }, false);
|
|
136
|
+
|
|
137
|
+
const destFile = path.join(destDir, 'readme.txt');
|
|
138
|
+
assert.ok(fs.existsSync(destFile), 'Destination file should exist');
|
|
139
|
+
// Without substitute_variables, content should be unchanged
|
|
140
|
+
const content = fs.readFileSync(destFile, 'utf-8');
|
|
141
|
+
assert.strictEqual(content, 'Hello {{ name }}!');
|
|
142
|
+
} finally {
|
|
143
|
+
rmDir(templateRoot);
|
|
144
|
+
rmDir(destDir);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('processCopyFiles: copy single file with substitution enabled', async () => {
|
|
149
|
+
const templateRoot = makeTempDir('copy-sub-src');
|
|
150
|
+
const destDir = makeTempDir('copy-sub-dest');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// Create source file with variable placeholders
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
path.join(templateRoot, 'config.txt'),
|
|
156
|
+
'project={{ project_name }}\nauthor={{ author }}'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const template: TemplateConfig = {
|
|
160
|
+
description: 'Copy with substitution',
|
|
161
|
+
folders: [],
|
|
162
|
+
copy_files: [
|
|
163
|
+
{ src: 'config.txt', dest: 'config.txt', substitute_variables: true }
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await processCopyFiles(
|
|
168
|
+
templateRoot,
|
|
169
|
+
destDir,
|
|
170
|
+
template,
|
|
171
|
+
{ project_name: 'MyApp', author: 'Alice' },
|
|
172
|
+
false
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const destFile = path.join(destDir, 'config.txt');
|
|
176
|
+
assert.ok(fs.existsSync(destFile), 'Destination file should exist');
|
|
177
|
+
const content = fs.readFileSync(destFile, 'utf-8');
|
|
178
|
+
assert.strictEqual(content, 'project=MyApp\nauthor=Alice');
|
|
179
|
+
} finally {
|
|
180
|
+
rmDir(templateRoot);
|
|
181
|
+
rmDir(destDir);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('processCopyFiles: copy to nested destination path', async () => {
|
|
186
|
+
const templateRoot = makeTempDir('copy-nested-src');
|
|
187
|
+
const destDir = makeTempDir('copy-nested-dest');
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
fs.writeFileSync(path.join(templateRoot, 'file.txt'), 'content');
|
|
191
|
+
|
|
192
|
+
const template: TemplateConfig = {
|
|
193
|
+
description: 'Nested dest',
|
|
194
|
+
folders: [],
|
|
195
|
+
copy_files: [
|
|
196
|
+
{ src: 'file.txt', dest: 'sub/dir/file.txt' }
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
await processCopyFiles(templateRoot, destDir, template, {}, false);
|
|
201
|
+
|
|
202
|
+
const destFile = path.join(destDir, 'sub', 'dir', 'file.txt');
|
|
203
|
+
assert.ok(fs.existsSync(destFile), 'File should be created in nested directory');
|
|
204
|
+
assert.strictEqual(fs.readFileSync(destFile, 'utf-8'), 'content');
|
|
205
|
+
} finally {
|
|
206
|
+
rmDir(templateRoot);
|
|
207
|
+
rmDir(destDir);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('processCopyFiles: copy a directory recursively', async () => {
|
|
212
|
+
const templateRoot = makeTempDir('copy-dir-src');
|
|
213
|
+
const destDir = makeTempDir('copy-dir-dest');
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
// Create a source directory structure
|
|
217
|
+
const srcDir = path.join(templateRoot, 'scripts');
|
|
218
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
219
|
+
fs.writeFileSync(path.join(srcDir, 'run.sh'), '#!/bin/bash\necho "hello"');
|
|
220
|
+
fs.mkdirSync(path.join(srcDir, 'utils'), { recursive: true });
|
|
221
|
+
fs.writeFileSync(path.join(srcDir, 'utils', 'helper.sh'), '#!/bin/bash\necho "helper"');
|
|
222
|
+
|
|
223
|
+
const template: TemplateConfig = {
|
|
224
|
+
description: 'Directory copy',
|
|
225
|
+
folders: [],
|
|
226
|
+
copy_files: [
|
|
227
|
+
{ src: 'scripts', dest: 'scripts' }
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
await processCopyFiles(templateRoot, destDir, template, {}, false);
|
|
232
|
+
|
|
233
|
+
// Verify the directory structure was copied
|
|
234
|
+
assert.ok(fs.existsSync(path.join(destDir, 'scripts', 'run.sh')), 'run.sh should exist');
|
|
235
|
+
assert.ok(fs.existsSync(path.join(destDir, 'scripts', 'utils', 'helper.sh')), 'helper.sh should exist');
|
|
236
|
+
assert.strictEqual(
|
|
237
|
+
fs.readFileSync(path.join(destDir, 'scripts', 'run.sh'), 'utf-8'),
|
|
238
|
+
'#!/bin/bash\necho "hello"'
|
|
239
|
+
);
|
|
240
|
+
assert.strictEqual(
|
|
241
|
+
fs.readFileSync(path.join(destDir, 'scripts', 'utils', 'helper.sh'), 'utf-8'),
|
|
242
|
+
'#!/bin/bash\necho "helper"'
|
|
243
|
+
);
|
|
244
|
+
} finally {
|
|
245
|
+
rmDir(templateRoot);
|
|
246
|
+
rmDir(destDir);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('processCopyFiles: recursive directory copy with substitution', async () => {
|
|
251
|
+
const templateRoot = makeTempDir('copy-dir-sub-src');
|
|
252
|
+
const destDir = makeTempDir('copy-dir-sub-dest');
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const srcDir = path.join(templateRoot, 'templates');
|
|
256
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
257
|
+
fs.writeFileSync(path.join(srcDir, 'index.html'), '<title>{{ title }}</title>');
|
|
258
|
+
fs.mkdirSync(path.join(srcDir, 'css'), { recursive: true });
|
|
259
|
+
fs.writeFileSync(path.join(srcDir, 'css', 'theme.css'), '/* Theme: {{ theme }} */');
|
|
260
|
+
|
|
261
|
+
const template: TemplateConfig = {
|
|
262
|
+
description: 'Dir copy with substitution',
|
|
263
|
+
folders: [],
|
|
264
|
+
copy_files: [
|
|
265
|
+
{ src: 'templates', dest: 'output', substitute_variables: true }
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
await processCopyFiles(
|
|
270
|
+
templateRoot,
|
|
271
|
+
destDir,
|
|
272
|
+
template,
|
|
273
|
+
{ title: 'My Page', theme: 'dark' },
|
|
274
|
+
false
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
assert.strictEqual(
|
|
278
|
+
fs.readFileSync(path.join(destDir, 'output', 'index.html'), 'utf-8'),
|
|
279
|
+
'<title>My Page</title>'
|
|
280
|
+
);
|
|
281
|
+
assert.strictEqual(
|
|
282
|
+
fs.readFileSync(path.join(destDir, 'output', 'css', 'theme.css'), 'utf-8'),
|
|
283
|
+
'/* Theme: dark */'
|
|
284
|
+
);
|
|
285
|
+
} finally {
|
|
286
|
+
rmDir(templateRoot);
|
|
287
|
+
rmDir(destDir);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('processCopyFiles: dry run mode does not copy files', async () => {
|
|
292
|
+
const templateRoot = makeTempDir('dryrun-src');
|
|
293
|
+
const destDir = makeTempDir('dryrun-dest');
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
fs.writeFileSync(path.join(templateRoot, 'data.txt'), 'should not be copied');
|
|
297
|
+
|
|
298
|
+
const template: TemplateConfig = {
|
|
299
|
+
description: 'Dry run test',
|
|
300
|
+
folders: [],
|
|
301
|
+
copy_files: [
|
|
302
|
+
{ src: 'data.txt', dest: 'data.txt' }
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
await processCopyFiles(templateRoot, destDir, template, {}, true);
|
|
307
|
+
|
|
308
|
+
// In dry run, the file should NOT be copied
|
|
309
|
+
assert.ok(
|
|
310
|
+
!fs.existsSync(path.join(destDir, 'data.txt')),
|
|
311
|
+
'File should NOT exist in dry run mode'
|
|
312
|
+
);
|
|
313
|
+
} finally {
|
|
314
|
+
rmDir(templateRoot);
|
|
315
|
+
rmDir(destDir);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('processCopyFiles: dry run mode with substitution and chmod logs but does not act', async () => {
|
|
320
|
+
const templateRoot = makeTempDir('dryrun-full-src');
|
|
321
|
+
const destDir = makeTempDir('dryrun-full-dest');
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
fs.writeFileSync(path.join(templateRoot, 'script.sh'), '#!/bin/bash\necho {{ msg }}');
|
|
325
|
+
|
|
326
|
+
const template: TemplateConfig = {
|
|
327
|
+
description: 'Dry run full',
|
|
328
|
+
folders: [],
|
|
329
|
+
copy_files: [
|
|
330
|
+
{ src: 'script.sh', dest: 'script.sh', substitute_variables: true, chmod: '0755' }
|
|
331
|
+
],
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
await processCopyFiles(templateRoot, destDir, template, { msg: 'hello' }, true);
|
|
335
|
+
|
|
336
|
+
assert.ok(
|
|
337
|
+
!fs.existsSync(path.join(destDir, 'script.sh')),
|
|
338
|
+
'File should NOT exist in dry run mode'
|
|
339
|
+
);
|
|
340
|
+
} finally {
|
|
341
|
+
rmDir(templateRoot);
|
|
342
|
+
rmDir(destDir);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('processCopyFiles: missing source file warns but does not crash', async () => {
|
|
347
|
+
const templateRoot = makeTempDir('missing-src');
|
|
348
|
+
const destDir = makeTempDir('missing-dest');
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const template: TemplateConfig = {
|
|
352
|
+
description: 'Missing source',
|
|
353
|
+
folders: [],
|
|
354
|
+
copy_files: [
|
|
355
|
+
{ src: 'nonexistent.txt', dest: 'nonexistent.txt' }
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Should complete without throwing
|
|
360
|
+
await processCopyFiles(templateRoot, destDir, template, {}, false);
|
|
361
|
+
|
|
362
|
+
// Destination should not exist
|
|
363
|
+
assert.ok(
|
|
364
|
+
!fs.existsSync(path.join(destDir, 'nonexistent.txt')),
|
|
365
|
+
'Destination file should not exist for missing source'
|
|
366
|
+
);
|
|
367
|
+
} finally {
|
|
368
|
+
rmDir(templateRoot);
|
|
369
|
+
rmDir(destDir);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('processCopyFiles: chmod option sets file permissions', async () => {
|
|
374
|
+
// chmod only works properly on non-Windows
|
|
375
|
+
if (process.platform === 'win32') {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const templateRoot = makeTempDir('chmod-src');
|
|
380
|
+
const destDir = makeTempDir('chmod-dest');
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
fs.writeFileSync(path.join(templateRoot, 'run.sh'), '#!/bin/bash\necho hi');
|
|
384
|
+
|
|
385
|
+
const template: TemplateConfig = {
|
|
386
|
+
description: 'Chmod test',
|
|
387
|
+
folders: [],
|
|
388
|
+
copy_files: [
|
|
389
|
+
{ src: 'run.sh', dest: 'run.sh', chmod: '0755' }
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
await processCopyFiles(templateRoot, destDir, template, {}, false);
|
|
394
|
+
|
|
395
|
+
const destFile = path.join(destDir, 'run.sh');
|
|
396
|
+
assert.ok(fs.existsSync(destFile), 'File should exist');
|
|
397
|
+
|
|
398
|
+
const stat = fs.statSync(destFile);
|
|
399
|
+
// Check that the execute bit is set (0o755 = 493 decimal)
|
|
400
|
+
const mode = stat.mode & 0o777;
|
|
401
|
+
assert.ok(
|
|
402
|
+
(mode & 0o111) !== 0,
|
|
403
|
+
`File should have execute permission, got mode ${mode.toString(8)}`
|
|
404
|
+
);
|
|
405
|
+
} finally {
|
|
406
|
+
rmDir(templateRoot);
|
|
407
|
+
rmDir(destDir);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('processCopyFiles: multiple copy_files entries', async () => {
|
|
412
|
+
const templateRoot = makeTempDir('multi-src');
|
|
413
|
+
const destDir = makeTempDir('multi-dest');
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
fs.writeFileSync(path.join(templateRoot, 'a.txt'), 'file a');
|
|
417
|
+
fs.writeFileSync(path.join(templateRoot, 'b.txt'), 'Hello {{ who }}');
|
|
418
|
+
|
|
419
|
+
const template: TemplateConfig = {
|
|
420
|
+
description: 'Multiple copies',
|
|
421
|
+
folders: [],
|
|
422
|
+
copy_files: [
|
|
423
|
+
{ src: 'a.txt', dest: 'a.txt' },
|
|
424
|
+
{ src: 'b.txt', dest: 'b.txt', substitute_variables: true },
|
|
425
|
+
],
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
await processCopyFiles(templateRoot, destDir, template, { who: 'World' }, false);
|
|
429
|
+
|
|
430
|
+
assert.strictEqual(fs.readFileSync(path.join(destDir, 'a.txt'), 'utf-8'), 'file a');
|
|
431
|
+
assert.strictEqual(fs.readFileSync(path.join(destDir, 'b.txt'), 'utf-8'), 'Hello World');
|
|
432
|
+
} finally {
|
|
433
|
+
rmDir(templateRoot);
|
|
434
|
+
rmDir(destDir);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('processCopyFiles: copy_files as empty array does nothing', async () => {
|
|
439
|
+
const template: TemplateConfig = {
|
|
440
|
+
description: 'Empty copy_files',
|
|
441
|
+
folders: [],
|
|
442
|
+
copy_files: [],
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Should complete without error
|
|
446
|
+
await processCopyFiles('/nonexistent', '/nonexistent', template, {}, false);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('processCopyFiles: dry run on directory copy does not create files', async () => {
|
|
450
|
+
const templateRoot = makeTempDir('dryrun-dir-src');
|
|
451
|
+
const destDir = makeTempDir('dryrun-dir-dest');
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
const srcDir = path.join(templateRoot, 'mydir');
|
|
455
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
456
|
+
fs.writeFileSync(path.join(srcDir, 'file.txt'), 'content');
|
|
457
|
+
|
|
458
|
+
const template: TemplateConfig = {
|
|
459
|
+
description: 'Dry run dir',
|
|
460
|
+
folders: [],
|
|
461
|
+
copy_files: [
|
|
462
|
+
{ src: 'mydir', dest: 'mydir' }
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
await processCopyFiles(templateRoot, destDir, template, {}, true);
|
|
467
|
+
|
|
468
|
+
// In dry run, directory copy should not create files
|
|
469
|
+
// Note: the source code logs the green checkmark even in dry run for directories,
|
|
470
|
+
// but the actual copyDirRecursive is skipped, so the dest files won't exist.
|
|
471
|
+
assert.ok(
|
|
472
|
+
!fs.existsSync(path.join(destDir, 'mydir', 'file.txt')),
|
|
473
|
+
'File should NOT exist inside directory in dry run mode'
|
|
474
|
+
);
|
|
475
|
+
} finally {
|
|
476
|
+
rmDir(templateRoot);
|
|
477
|
+
rmDir(destDir);
|
|
478
|
+
}
|
|
479
|
+
});
|