@garyr/pt-cli 0.30.1 → 0.32.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/README.md +9 -0
- package/dist/commands/learnCommand.js +66 -51
- package/doc/testing.md +48 -0
- package/package.json +2 -1
- package/src/commands/learnCommand.ts +65 -53
- package/tests/config-utils.test.ts +473 -0
- package/tests/config.test.ts +36 -6
- package/tests/init.test.ts +428 -14
- package/tests/learn.test.ts +838 -0
- package/tests/substitute.test.ts +479 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
|
|
7
|
+
// Force a temporary home directory for testing before importing anything from the CLI
|
|
8
|
+
const testHome = path.join(process.cwd(), '.test-home-learn');
|
|
9
|
+
process.env.HOME = testHome;
|
|
10
|
+
|
|
11
|
+
import { learn } from '../src/commands/learnCommand.js';
|
|
12
|
+
import { loadConfig, saveConfig, PtConfig, CONFIG_PATH } from '../src/config.js';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Helpers
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** Create a temporary source directory with a given structure. Returns the root path. */
|
|
19
|
+
function createSourceDir(name: string): string {
|
|
20
|
+
const dir = path.join(process.cwd(), name);
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Remove a directory tree (and swallow ENOENT). */
|
|
26
|
+
function rmrf(p: string) {
|
|
27
|
+
if (fs.existsSync(p)) {
|
|
28
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Ensure a clean test config state (no leftover config files). */
|
|
33
|
+
function cleanConfig() {
|
|
34
|
+
if (fs.existsSync(CONFIG_PATH)) fs.unlinkSync(CONFIG_PATH);
|
|
35
|
+
const bak = CONFIG_PATH + '.bak';
|
|
36
|
+
if (fs.existsSync(bak)) fs.unlinkSync(bak);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Capture console.log output during an async callback. */
|
|
40
|
+
async function captureStdout(fn: () => Promise<void>): Promise<string> {
|
|
41
|
+
const original = console.log;
|
|
42
|
+
let captured = '';
|
|
43
|
+
console.log = (...args: unknown[]) => {
|
|
44
|
+
captured += args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '\n';
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
await fn();
|
|
48
|
+
} finally {
|
|
49
|
+
console.log = original;
|
|
50
|
+
}
|
|
51
|
+
return captured;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Tests
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
test('learn with --yes flag saves template to config', async () => {
|
|
59
|
+
const srcDir = createSourceDir('.test-learn-yes');
|
|
60
|
+
cleanConfig();
|
|
61
|
+
try {
|
|
62
|
+
// Create a minimal directory structure
|
|
63
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
64
|
+
fs.mkdirSync(path.join(srcDir, 'docs'), { recursive: true });
|
|
65
|
+
fs.writeFileSync(path.join(srcDir, 'README.md'), '# Hello');
|
|
66
|
+
|
|
67
|
+
await learn(srcDir, null, { yes: true });
|
|
68
|
+
|
|
69
|
+
const config = loadConfig();
|
|
70
|
+
const templateName = path.basename(srcDir);
|
|
71
|
+
assert.ok(config.templates[templateName], `Template "${templateName}" should be saved`);
|
|
72
|
+
assert.ok(config.templates[templateName].templateRoot, 'Should have a templateRoot');
|
|
73
|
+
assert.ok(Array.isArray(config.templates[templateName].folders), 'Should have folders array');
|
|
74
|
+
|
|
75
|
+
// The folder skeleton should include the two directories we created
|
|
76
|
+
const folderNames = config.templates[templateName].folders.map(f => f.name);
|
|
77
|
+
assert.ok(folderNames.includes('src'), 'Structure should contain src');
|
|
78
|
+
assert.ok(folderNames.includes('docs'), 'Structure should contain docs');
|
|
79
|
+
} finally {
|
|
80
|
+
rmrf(srcDir);
|
|
81
|
+
cleanConfig();
|
|
82
|
+
rmrf(testHome);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('learn with --json outputs JSON and does NOT save to config', async () => {
|
|
87
|
+
const srcDir = createSourceDir('.test-learn-json');
|
|
88
|
+
cleanConfig();
|
|
89
|
+
try {
|
|
90
|
+
fs.mkdirSync(path.join(srcDir, 'lib'), { recursive: true });
|
|
91
|
+
fs.writeFileSync(path.join(srcDir, 'README.md'), '# Project');
|
|
92
|
+
|
|
93
|
+
const output = await captureStdout(async () => {
|
|
94
|
+
await learn(srcDir, null, { json: true });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Should have produced valid JSON
|
|
98
|
+
const parsed = JSON.parse(output.trim());
|
|
99
|
+
assert.ok(parsed.name, 'JSON output should have a name');
|
|
100
|
+
assert.ok(Array.isArray(parsed.folders), 'JSON output should have folders array');
|
|
101
|
+
|
|
102
|
+
// Config should NOT have the template
|
|
103
|
+
const config = loadConfig();
|
|
104
|
+
assert.strictEqual(
|
|
105
|
+
Object.keys(config.templates).length,
|
|
106
|
+
0,
|
|
107
|
+
'No template should be saved when --json is used'
|
|
108
|
+
);
|
|
109
|
+
} finally {
|
|
110
|
+
rmrf(srcDir);
|
|
111
|
+
cleanConfig();
|
|
112
|
+
rmrf(testHome);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('learn with --name and --desc options', async () => {
|
|
117
|
+
const srcDir = createSourceDir('.test-learn-name-desc');
|
|
118
|
+
cleanConfig();
|
|
119
|
+
try {
|
|
120
|
+
fs.mkdirSync(path.join(srcDir, 'app'), { recursive: true });
|
|
121
|
+
|
|
122
|
+
await learn(srcDir, null, { yes: true, name: 'my-custom-name', desc: 'A custom description' });
|
|
123
|
+
|
|
124
|
+
const config = loadConfig();
|
|
125
|
+
assert.ok(config.templates['my-custom-name'], 'Template should be saved under the custom name');
|
|
126
|
+
assert.strictEqual(
|
|
127
|
+
config.templates['my-custom-name'].description,
|
|
128
|
+
'A custom description',
|
|
129
|
+
'Description should match --desc value'
|
|
130
|
+
);
|
|
131
|
+
} finally {
|
|
132
|
+
rmrf(srcDir);
|
|
133
|
+
cleanConfig();
|
|
134
|
+
rmrf(testHome);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('learn detects .pt-template.json for name, description, variables, and folders', async () => {
|
|
139
|
+
const srcDir = createSourceDir('.test-learn-pt-template-json');
|
|
140
|
+
cleanConfig();
|
|
141
|
+
try {
|
|
142
|
+
const ptConfig = {
|
|
143
|
+
name: 'json-detected-template',
|
|
144
|
+
description: 'Loaded from JSON config',
|
|
145
|
+
variables: [
|
|
146
|
+
{ name: 'project_name', prompt: 'Project name?', required: true },
|
|
147
|
+
{ name: 'author', prompt: 'Author?', default: 'anon' }
|
|
148
|
+
],
|
|
149
|
+
folders: [
|
|
150
|
+
{ name: 'src', info: 'source code', children: [] },
|
|
151
|
+
{ name: 'tests', info: 'test files' }
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig, null, 2));
|
|
155
|
+
|
|
156
|
+
await learn(srcDir, null, { yes: true });
|
|
157
|
+
|
|
158
|
+
const config = loadConfig();
|
|
159
|
+
const tpl = config.templates['json-detected-template'];
|
|
160
|
+
assert.ok(tpl, 'Template should be saved with the name from .pt-template.json');
|
|
161
|
+
assert.strictEqual(tpl.description, 'Loaded from JSON config', 'Description from JSON');
|
|
162
|
+
|
|
163
|
+
// Variables from JSON should be present
|
|
164
|
+
assert.ok(tpl.variables, 'Should have variables');
|
|
165
|
+
const varNames = tpl.variables!.map(v => v.name);
|
|
166
|
+
assert.ok(varNames.includes('project_name'), 'Should have project_name variable');
|
|
167
|
+
assert.ok(varNames.includes('author'), 'Should have author variable');
|
|
168
|
+
|
|
169
|
+
// Folders from JSON should be used verbatim
|
|
170
|
+
const folderNames = tpl.folders.map(f => f.name);
|
|
171
|
+
assert.ok(folderNames.includes('src'), 'Should have src folder from JSON');
|
|
172
|
+
assert.ok(folderNames.includes('tests'), 'Should have tests folder from JSON');
|
|
173
|
+
} finally {
|
|
174
|
+
rmrf(srcDir);
|
|
175
|
+
cleanConfig();
|
|
176
|
+
rmrf(testHome);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('learn detects .info.md for name and description', async () => {
|
|
181
|
+
const srcDir = createSourceDir('.test-learn-info-md');
|
|
182
|
+
cleanConfig();
|
|
183
|
+
try {
|
|
184
|
+
fs.mkdirSync(path.join(srcDir, 'stuff'), { recursive: true });
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
path.join(srcDir, '.info.md'),
|
|
187
|
+
'# My Info Template\nThis is a great template for stuff.\n\nMore details here.\n'
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await learn(srcDir, null, { yes: true });
|
|
191
|
+
|
|
192
|
+
const config = loadConfig();
|
|
193
|
+
const tpl = config.templates['My Info Template'];
|
|
194
|
+
assert.ok(tpl, 'Template should be saved with name from .info.md heading');
|
|
195
|
+
assert.strictEqual(
|
|
196
|
+
tpl.description,
|
|
197
|
+
'This is a great template for stuff.',
|
|
198
|
+
'Description should be the first non-empty, non-heading line from .info.md'
|
|
199
|
+
);
|
|
200
|
+
} finally {
|
|
201
|
+
rmrf(srcDir);
|
|
202
|
+
cleanConfig();
|
|
203
|
+
rmrf(testHome);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('learn detects {{ variable_name }} patterns in text files', async () => {
|
|
208
|
+
const srcDir = createSourceDir('.test-learn-variables');
|
|
209
|
+
cleanConfig();
|
|
210
|
+
try {
|
|
211
|
+
// Create files containing template variables
|
|
212
|
+
fs.writeFileSync(
|
|
213
|
+
path.join(srcDir, 'README.md'),
|
|
214
|
+
'# {{ project_name }}\nAuthor: {{ author_name }}\n'
|
|
215
|
+
);
|
|
216
|
+
fs.mkdirSync(path.join(srcDir, 'config'), { recursive: true });
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
path.join(srcDir, 'config', 'settings.yaml'),
|
|
219
|
+
'app_name: {{ app_title }}\nversion: {{ version }}\n'
|
|
220
|
+
);
|
|
221
|
+
// Also add a binary-ish extension file that should NOT be scanned
|
|
222
|
+
fs.writeFileSync(path.join(srcDir, 'image.png'), Buffer.from([0x89, 0x50, 0x4e, 0x47]));
|
|
223
|
+
|
|
224
|
+
await learn(srcDir, null, { yes: true, name: 'var-test' });
|
|
225
|
+
|
|
226
|
+
const config = loadConfig();
|
|
227
|
+
const tpl = config.templates['var-test'];
|
|
228
|
+
assert.ok(tpl, 'Template should exist');
|
|
229
|
+
assert.ok(tpl.variables, 'Should have detected variables');
|
|
230
|
+
|
|
231
|
+
const varNames = tpl.variables!.map(v => v.name);
|
|
232
|
+
assert.ok(varNames.includes('project_name'), 'Should detect project_name');
|
|
233
|
+
assert.ok(varNames.includes('author_name'), 'Should detect author_name');
|
|
234
|
+
assert.ok(varNames.includes('app_title'), 'Should detect app_title from subfolder');
|
|
235
|
+
assert.ok(varNames.includes('version'), 'Should detect version from subfolder');
|
|
236
|
+
|
|
237
|
+
// Each auto-detected variable should have sensible defaults
|
|
238
|
+
for (const v of tpl.variables!) {
|
|
239
|
+
assert.ok(v.prompt, `Variable "${v.name}" should have a prompt`);
|
|
240
|
+
assert.strictEqual(v.required, true, `Auto-detected variable "${v.name}" should be required`);
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
rmrf(srcDir);
|
|
244
|
+
cleanConfig();
|
|
245
|
+
rmrf(testHome);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('learn with --ignore patterns excludes matching directories', async () => {
|
|
250
|
+
const srcDir = createSourceDir('.test-learn-ignore');
|
|
251
|
+
cleanConfig();
|
|
252
|
+
try {
|
|
253
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
254
|
+
fs.mkdirSync(path.join(srcDir, 'vendor'), { recursive: true });
|
|
255
|
+
fs.mkdirSync(path.join(srcDir, 'tmp_stuff'), { recursive: true });
|
|
256
|
+
fs.writeFileSync(path.join(srcDir, 'README.md'), '# Test');
|
|
257
|
+
|
|
258
|
+
await learn(srcDir, null, { yes: true, name: 'ignore-test', ignore: 'vendor,tmp_stuff' });
|
|
259
|
+
|
|
260
|
+
const config = loadConfig();
|
|
261
|
+
const tpl = config.templates['ignore-test'];
|
|
262
|
+
assert.ok(tpl, 'Template should exist');
|
|
263
|
+
|
|
264
|
+
const folderNames = tpl.folders.map(f => f.name);
|
|
265
|
+
assert.ok(folderNames.includes('src'), 'src should be included');
|
|
266
|
+
assert.ok(!folderNames.includes('vendor'), 'vendor should be excluded by --ignore');
|
|
267
|
+
assert.ok(!folderNames.includes('tmp_stuff'), 'tmp_stuff should be excluded by --ignore');
|
|
268
|
+
} finally {
|
|
269
|
+
rmrf(srcDir);
|
|
270
|
+
cleanConfig();
|
|
271
|
+
rmrf(testHome);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('learn detects executable files for post_copy', async () => {
|
|
276
|
+
const srcDir = createSourceDir('.test-learn-executables');
|
|
277
|
+
cleanConfig();
|
|
278
|
+
try {
|
|
279
|
+
// Create executable files (detected by extension)
|
|
280
|
+
fs.writeFileSync(path.join(srcDir, 'setup.sh'), '#!/bin/bash\necho hello');
|
|
281
|
+
fs.writeFileSync(path.join(srcDir, 'deploy.py'), '#!/usr/bin/env python3\nprint("deploy")');
|
|
282
|
+
// Create a non-executable text file
|
|
283
|
+
fs.writeFileSync(path.join(srcDir, 'notes.txt'), 'just notes');
|
|
284
|
+
// Create a Makefile (always detected as executable)
|
|
285
|
+
fs.writeFileSync(path.join(srcDir, 'Makefile'), 'all:\n\techo build');
|
|
286
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
287
|
+
|
|
288
|
+
await learn(srcDir, null, { yes: true, name: 'exec-test' });
|
|
289
|
+
|
|
290
|
+
const config = loadConfig();
|
|
291
|
+
const tpl = config.templates['exec-test'];
|
|
292
|
+
assert.ok(tpl, 'Template should exist');
|
|
293
|
+
assert.ok(tpl.post_copy, 'Should have post_copy entries');
|
|
294
|
+
|
|
295
|
+
const postCopySrcs = tpl.post_copy!.map(pc => pc.src);
|
|
296
|
+
assert.ok(postCopySrcs.includes('setup.sh'), 'setup.sh should be in post_copy');
|
|
297
|
+
assert.ok(postCopySrcs.includes('deploy.py'), 'deploy.py should be in post_copy');
|
|
298
|
+
assert.ok(postCopySrcs.includes('Makefile'), 'Makefile should be in post_copy');
|
|
299
|
+
|
|
300
|
+
// Executables should NOT be in copy_files (they get moved to post_copy)
|
|
301
|
+
if (tpl.copy_files) {
|
|
302
|
+
const copySrcs = tpl.copy_files.map(cf => cf.src);
|
|
303
|
+
assert.ok(!copySrcs.includes('setup.sh'), 'setup.sh should not be in copy_files');
|
|
304
|
+
assert.ok(!copySrcs.includes('deploy.py'), 'deploy.py should not be in copy_files');
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
rmrf(srcDir);
|
|
308
|
+
cleanConfig();
|
|
309
|
+
rmrf(testHome);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('learn detects post_config tasks from post_config.sh', async () => {
|
|
314
|
+
const srcDir = createSourceDir('.test-learn-post-config');
|
|
315
|
+
cleanConfig();
|
|
316
|
+
try {
|
|
317
|
+
fs.mkdirSync(path.join(srcDir, 'app'), { recursive: true });
|
|
318
|
+
// Create a post_config.sh with the expected format
|
|
319
|
+
const scriptContent = [
|
|
320
|
+
'#!/bin/bash',
|
|
321
|
+
'echo "Running: Installing dependencies"',
|
|
322
|
+
'npm install',
|
|
323
|
+
'echo "Running: Building project"',
|
|
324
|
+
'npm run build',
|
|
325
|
+
'# This is a comment and should be skipped',
|
|
326
|
+
'echo "Running: Running tests"',
|
|
327
|
+
'npm test',
|
|
328
|
+
].join('\n');
|
|
329
|
+
fs.writeFileSync(path.join(srcDir, 'post_config.sh'), scriptContent);
|
|
330
|
+
|
|
331
|
+
await learn(srcDir, null, { yes: true, name: 'postconfig-test' });
|
|
332
|
+
|
|
333
|
+
const config = loadConfig();
|
|
334
|
+
const tpl = config.templates['postconfig-test'];
|
|
335
|
+
assert.ok(tpl, 'Template should exist');
|
|
336
|
+
assert.ok(tpl.post_config, 'Should have post_config tasks');
|
|
337
|
+
assert.strictEqual(tpl.post_config!.length, 3, 'Should have 3 post_config tasks');
|
|
338
|
+
|
|
339
|
+
// Verify task commands and descriptions
|
|
340
|
+
assert.strictEqual(tpl.post_config![0].command, 'npm install');
|
|
341
|
+
assert.strictEqual(tpl.post_config![0].description, 'Installing dependencies');
|
|
342
|
+
|
|
343
|
+
assert.strictEqual(tpl.post_config![1].command, 'npm run build');
|
|
344
|
+
assert.strictEqual(tpl.post_config![1].description, 'Building project');
|
|
345
|
+
|
|
346
|
+
assert.strictEqual(tpl.post_config![2].command, 'npm test');
|
|
347
|
+
assert.strictEqual(tpl.post_config![2].description, 'Running tests');
|
|
348
|
+
} finally {
|
|
349
|
+
rmrf(srcDir);
|
|
350
|
+
cleanConfig();
|
|
351
|
+
rmrf(testHome);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('learn updates an existing template with updateTemplate param', async () => {
|
|
356
|
+
const srcDir = createSourceDir('.test-learn-update');
|
|
357
|
+
cleanConfig();
|
|
358
|
+
try {
|
|
359
|
+
// Seed an initial config with an existing template
|
|
360
|
+
const initialConfig: PtConfig = {
|
|
361
|
+
version: '3.0',
|
|
362
|
+
templates: {
|
|
363
|
+
'existing-tpl': {
|
|
364
|
+
description: 'Old description',
|
|
365
|
+
templateRoot: '/old/path',
|
|
366
|
+
folders: [{ name: 'old-folder', info: 'old info' }],
|
|
367
|
+
variables: [
|
|
368
|
+
{ name: 'old_var', prompt: 'Old var:', required: true }
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
saveConfig(initialConfig);
|
|
374
|
+
|
|
375
|
+
// Create new source structure
|
|
376
|
+
fs.mkdirSync(path.join(srcDir, 'new-folder'), { recursive: true });
|
|
377
|
+
fs.mkdirSync(path.join(srcDir, 'another'), { recursive: true });
|
|
378
|
+
fs.writeFileSync(path.join(srcDir, 'README.md'), '# Updated {{ new_var }}');
|
|
379
|
+
|
|
380
|
+
// Update the existing template (--yes keeps existing description)
|
|
381
|
+
await learn(srcDir, 'existing-tpl', { yes: true });
|
|
382
|
+
|
|
383
|
+
const config = loadConfig();
|
|
384
|
+
const tpl = config.templates['existing-tpl'];
|
|
385
|
+
assert.ok(tpl, 'Template should still exist');
|
|
386
|
+
assert.strictEqual(tpl.description, 'Old description', 'Description preserved with --yes on update');
|
|
387
|
+
assert.strictEqual(tpl.templateRoot, path.resolve(srcDir), 'templateRoot should be updated');
|
|
388
|
+
|
|
389
|
+
// Folders should reflect the NEW source structure
|
|
390
|
+
const folderNames = tpl.folders.map(f => f.name);
|
|
391
|
+
assert.ok(folderNames.includes('new-folder'), 'Should include new-folder from updated source');
|
|
392
|
+
assert.ok(folderNames.includes('another'), 'Should include another from updated source');
|
|
393
|
+
|
|
394
|
+
// Old variable should be preserved, and new_var should be added from detection
|
|
395
|
+
assert.ok(tpl.variables, 'Should have variables');
|
|
396
|
+
const varNames = tpl.variables!.map(v => v.name);
|
|
397
|
+
assert.ok(varNames.includes('old_var'), 'Old variable should be preserved');
|
|
398
|
+
assert.ok(varNames.includes('new_var'), 'New detected variable should be added');
|
|
399
|
+
} finally {
|
|
400
|
+
rmrf(srcDir);
|
|
401
|
+
cleanConfig();
|
|
402
|
+
rmrf(testHome);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('learn --json output includes detected variables and folders', async () => {
|
|
407
|
+
const srcDir = createSourceDir('.test-learn-json-full');
|
|
408
|
+
cleanConfig();
|
|
409
|
+
try {
|
|
410
|
+
fs.mkdirSync(path.join(srcDir, 'components'), { recursive: true });
|
|
411
|
+
fs.mkdirSync(path.join(srcDir, 'utils'), { recursive: true });
|
|
412
|
+
fs.writeFileSync(
|
|
413
|
+
path.join(srcDir, 'config.yaml'),
|
|
414
|
+
'title: {{ site_title }}\nowner: {{ owner_name }}\n'
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const output = await captureStdout(async () => {
|
|
418
|
+
await learn(srcDir, null, { json: true, name: 'json-full-test' });
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const parsed = JSON.parse(output.trim());
|
|
422
|
+
assert.strictEqual(parsed.name, 'json-full-test', 'JSON name should match --name');
|
|
423
|
+
|
|
424
|
+
// Should contain detected variables
|
|
425
|
+
assert.ok(parsed.variables, 'JSON should include variables');
|
|
426
|
+
const varNames = parsed.variables.map((v: { name: string }) => v.name);
|
|
427
|
+
assert.ok(varNames.includes('site_title'), 'Should detect site_title');
|
|
428
|
+
assert.ok(varNames.includes('owner_name'), 'Should detect owner_name');
|
|
429
|
+
|
|
430
|
+
// Should contain folders
|
|
431
|
+
assert.ok(Array.isArray(parsed.folders), 'JSON should include folders');
|
|
432
|
+
const folderNames = parsed.folders.map((f: { name: string }) => f.name);
|
|
433
|
+
assert.ok(folderNames.includes('components'), 'Should have components folder');
|
|
434
|
+
assert.ok(folderNames.includes('utils'), 'Should have utils folder');
|
|
435
|
+
} finally {
|
|
436
|
+
rmrf(srcDir);
|
|
437
|
+
cleanConfig();
|
|
438
|
+
rmrf(testHome);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('learn with .pt-template.json overrides --yes name fallback', async () => {
|
|
443
|
+
const srcDir = createSourceDir('.test-learn-json-name-priority');
|
|
444
|
+
cleanConfig();
|
|
445
|
+
try {
|
|
446
|
+
// .pt-template.json has a name — it should win over the directory basename
|
|
447
|
+
const ptConfig = { name: 'from-json', description: 'JSON desc' };
|
|
448
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig));
|
|
449
|
+
fs.mkdirSync(path.join(srcDir, 'a'), { recursive: true });
|
|
450
|
+
|
|
451
|
+
await learn(srcDir, null, { yes: true });
|
|
452
|
+
|
|
453
|
+
const config = loadConfig();
|
|
454
|
+
assert.ok(config.templates['from-json'], 'Should use name from .pt-template.json, not dirname');
|
|
455
|
+
assert.ok(!config.templates[path.basename(srcDir)], 'Should NOT use dirname as name');
|
|
456
|
+
} finally {
|
|
457
|
+
rmrf(srcDir);
|
|
458
|
+
cleanConfig();
|
|
459
|
+
rmrf(testHome);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test('learn --name takes priority over .pt-template.json name', async () => {
|
|
464
|
+
const srcDir = createSourceDir('.test-learn-cli-name-priority');
|
|
465
|
+
cleanConfig();
|
|
466
|
+
try {
|
|
467
|
+
const ptConfig = { name: 'from-json', description: 'JSON desc' };
|
|
468
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig));
|
|
469
|
+
fs.mkdirSync(path.join(srcDir, 'x'), { recursive: true });
|
|
470
|
+
|
|
471
|
+
await learn(srcDir, null, { yes: true, name: 'cli-name' });
|
|
472
|
+
|
|
473
|
+
const config = loadConfig();
|
|
474
|
+
assert.ok(config.templates['cli-name'], 'CLI --name should take priority over JSON name');
|
|
475
|
+
assert.ok(!config.templates['from-json'], 'JSON name should NOT be used when --name is given');
|
|
476
|
+
} finally {
|
|
477
|
+
rmrf(srcDir);
|
|
478
|
+
cleanConfig();
|
|
479
|
+
rmrf(testHome);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test('learn --desc takes priority over .pt-template.json description', async () => {
|
|
484
|
+
const srcDir = createSourceDir('.test-learn-cli-desc-priority');
|
|
485
|
+
cleanConfig();
|
|
486
|
+
try {
|
|
487
|
+
const ptConfig = { name: 'desc-priority-test', description: 'JSON description' };
|
|
488
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig));
|
|
489
|
+
fs.mkdirSync(path.join(srcDir, 'z'), { recursive: true });
|
|
490
|
+
|
|
491
|
+
await learn(srcDir, null, { yes: true, desc: 'CLI description' });
|
|
492
|
+
|
|
493
|
+
const config = loadConfig();
|
|
494
|
+
const tpl = config.templates['desc-priority-test'];
|
|
495
|
+
assert.ok(tpl, 'Template should exist');
|
|
496
|
+
assert.strictEqual(tpl.description, 'CLI description', '--desc should override JSON description');
|
|
497
|
+
} finally {
|
|
498
|
+
rmrf(srcDir);
|
|
499
|
+
cleanConfig();
|
|
500
|
+
rmrf(testHome);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('learn update with --desc replaces existing description', async () => {
|
|
505
|
+
const srcDir = createSourceDir('.test-learn-update-desc');
|
|
506
|
+
cleanConfig();
|
|
507
|
+
try {
|
|
508
|
+
const initialConfig: PtConfig = {
|
|
509
|
+
version: '3.0',
|
|
510
|
+
templates: {
|
|
511
|
+
'update-desc-tpl': {
|
|
512
|
+
description: 'Original desc',
|
|
513
|
+
templateRoot: '/some/old/path',
|
|
514
|
+
folders: []
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
saveConfig(initialConfig);
|
|
519
|
+
|
|
520
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
521
|
+
|
|
522
|
+
await learn(srcDir, 'update-desc-tpl', { yes: true, desc: 'New description' });
|
|
523
|
+
|
|
524
|
+
const config = loadConfig();
|
|
525
|
+
assert.strictEqual(
|
|
526
|
+
config.templates['update-desc-tpl'].description,
|
|
527
|
+
'New description',
|
|
528
|
+
'Description should be replaced on update with --desc'
|
|
529
|
+
);
|
|
530
|
+
} finally {
|
|
531
|
+
rmrf(srcDir);
|
|
532
|
+
cleanConfig();
|
|
533
|
+
rmrf(testHome);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('learn excludes default directories (.git, node_modules, etc.)', async () => {
|
|
538
|
+
const srcDir = createSourceDir('.test-learn-default-excludes');
|
|
539
|
+
cleanConfig();
|
|
540
|
+
try {
|
|
541
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
542
|
+
fs.mkdirSync(path.join(srcDir, '.git', 'objects'), { recursive: true });
|
|
543
|
+
fs.mkdirSync(path.join(srcDir, 'node_modules', 'somepkg'), { recursive: true });
|
|
544
|
+
fs.mkdirSync(path.join(srcDir, '.vscode'), { recursive: true });
|
|
545
|
+
fs.mkdirSync(path.join(srcDir, 'dist'), { recursive: true });
|
|
546
|
+
|
|
547
|
+
await learn(srcDir, null, { yes: true, name: 'exclude-test' });
|
|
548
|
+
|
|
549
|
+
const config = loadConfig();
|
|
550
|
+
const tpl = config.templates['exclude-test'];
|
|
551
|
+
const folderNames = tpl.folders.map(f => f.name);
|
|
552
|
+
|
|
553
|
+
assert.ok(folderNames.includes('src'), 'src should be included');
|
|
554
|
+
assert.ok(!folderNames.includes('.git'), '.git should be excluded');
|
|
555
|
+
assert.ok(!folderNames.includes('node_modules'), 'node_modules should be excluded');
|
|
556
|
+
assert.ok(!folderNames.includes('.vscode'), '.vscode should be excluded');
|
|
557
|
+
assert.ok(!folderNames.includes('dist'), 'dist should be excluded');
|
|
558
|
+
} finally {
|
|
559
|
+
rmrf(srcDir);
|
|
560
|
+
cleanConfig();
|
|
561
|
+
rmrf(testHome);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('learn populates copy_files with auto-selected boilerplate files', async () => {
|
|
566
|
+
const srcDir = createSourceDir('.test-learn-copy-files');
|
|
567
|
+
cleanConfig();
|
|
568
|
+
try {
|
|
569
|
+
// Files that should be auto-selected by --yes
|
|
570
|
+
fs.writeFileSync(path.join(srcDir, 'README.md'), '# Project');
|
|
571
|
+
fs.writeFileSync(path.join(srcDir, '.gitignore'), 'node_modules/');
|
|
572
|
+
fs.writeFileSync(path.join(srcDir, 'package.json'), '{}');
|
|
573
|
+
// A file that should NOT be auto-selected (not in the defaults list)
|
|
574
|
+
fs.writeFileSync(path.join(srcDir, 'random.txt'), 'stuff');
|
|
575
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
576
|
+
|
|
577
|
+
await learn(srcDir, null, { yes: true, name: 'copyfiles-test' });
|
|
578
|
+
|
|
579
|
+
const config = loadConfig();
|
|
580
|
+
const tpl = config.templates['copyfiles-test'];
|
|
581
|
+
assert.ok(tpl.copy_files, 'Should have copy_files');
|
|
582
|
+
|
|
583
|
+
const copySrcs = tpl.copy_files!.map(cf => cf.src);
|
|
584
|
+
assert.ok(copySrcs.includes('README.md'), 'README.md should be auto-selected');
|
|
585
|
+
assert.ok(copySrcs.includes('.gitignore'), '.gitignore should be auto-selected');
|
|
586
|
+
assert.ok(!copySrcs.includes('random.txt'), 'random.txt should NOT be auto-selected');
|
|
587
|
+
} finally {
|
|
588
|
+
rmrf(srcDir);
|
|
589
|
+
cleanConfig();
|
|
590
|
+
rmrf(testHome);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test('learn with nested folder structure extracts children', async () => {
|
|
595
|
+
const srcDir = createSourceDir('.test-learn-nested');
|
|
596
|
+
cleanConfig();
|
|
597
|
+
try {
|
|
598
|
+
fs.mkdirSync(path.join(srcDir, 'src', 'components', 'ui'), { recursive: true });
|
|
599
|
+
fs.mkdirSync(path.join(srcDir, 'src', 'utils'), { recursive: true });
|
|
600
|
+
fs.mkdirSync(path.join(srcDir, 'docs'), { recursive: true });
|
|
601
|
+
|
|
602
|
+
await learn(srcDir, null, { yes: true, name: 'nested-test' });
|
|
603
|
+
|
|
604
|
+
const config = loadConfig();
|
|
605
|
+
const tpl = config.templates['nested-test'];
|
|
606
|
+
|
|
607
|
+
// Find the src folder node
|
|
608
|
+
const srcNode = tpl.folders.find(f => f.name === 'src');
|
|
609
|
+
assert.ok(srcNode, 'Should have src folder');
|
|
610
|
+
assert.ok(srcNode!.children, 'src should have children');
|
|
611
|
+
|
|
612
|
+
const srcChildNames = srcNode!.children!.map(c => c.name);
|
|
613
|
+
assert.ok(srcChildNames.includes('components'), 'src should have components child');
|
|
614
|
+
assert.ok(srcChildNames.includes('utils'), 'src should have utils child');
|
|
615
|
+
|
|
616
|
+
// Find the components node inside src
|
|
617
|
+
const componentsNode = srcNode!.children!.find(c => c.name === 'components');
|
|
618
|
+
assert.ok(componentsNode, 'Should have components node');
|
|
619
|
+
assert.ok(componentsNode!.children, 'components should have children');
|
|
620
|
+
const uiNode = componentsNode!.children!.find(c => c.name === 'ui');
|
|
621
|
+
assert.ok(uiNode, 'components should have ui child');
|
|
622
|
+
} finally {
|
|
623
|
+
rmrf(srcDir);
|
|
624
|
+
cleanConfig();
|
|
625
|
+
rmrf(testHome);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test('learn reads .info.md inside subdirectories for folder info', async () => {
|
|
630
|
+
const srcDir = createSourceDir('.test-learn-folder-info');
|
|
631
|
+
cleanConfig();
|
|
632
|
+
try {
|
|
633
|
+
fs.mkdirSync(path.join(srcDir, 'assets'), { recursive: true });
|
|
634
|
+
fs.writeFileSync(
|
|
635
|
+
path.join(srcDir, 'assets', '.info.md'),
|
|
636
|
+
'# Assets\nStatic assets like images and fonts.'
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
await learn(srcDir, null, { yes: true, name: 'folder-info-test' });
|
|
640
|
+
|
|
641
|
+
const config = loadConfig();
|
|
642
|
+
const tpl = config.templates['folder-info-test'];
|
|
643
|
+
const assetsNode = tpl.folders.find(f => f.name === 'assets');
|
|
644
|
+
assert.ok(assetsNode, 'Should have assets folder');
|
|
645
|
+
assert.ok(
|
|
646
|
+
assetsNode!.info.includes('Assets'),
|
|
647
|
+
'Folder info should contain content from .info.md'
|
|
648
|
+
);
|
|
649
|
+
} finally {
|
|
650
|
+
rmrf(srcDir);
|
|
651
|
+
cleanConfig();
|
|
652
|
+
rmrf(testHome);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test('learn with .pt-template.json copy_files uses JSON copy_files', async () => {
|
|
657
|
+
const srcDir = createSourceDir('.test-learn-json-copyfiles');
|
|
658
|
+
cleanConfig();
|
|
659
|
+
try {
|
|
660
|
+
const ptConfig = {
|
|
661
|
+
name: 'json-copyfiles',
|
|
662
|
+
description: 'Test JSON copy_files',
|
|
663
|
+
copy_files: [
|
|
664
|
+
{ src: 'custom.conf', dest: 'config/custom.conf', substitute_variables: true },
|
|
665
|
+
{ src: 'helper.txt', dest: 'bin/helper.txt', substitute_variables: false }
|
|
666
|
+
]
|
|
667
|
+
};
|
|
668
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig));
|
|
669
|
+
fs.writeFileSync(path.join(srcDir, 'custom.conf'), 'key=value');
|
|
670
|
+
fs.writeFileSync(path.join(srcDir, 'helper.txt'), '#!/bin/bash');
|
|
671
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
672
|
+
|
|
673
|
+
await learn(srcDir, null, { yes: true });
|
|
674
|
+
|
|
675
|
+
const config = loadConfig();
|
|
676
|
+
const tpl = config.templates['json-copyfiles'];
|
|
677
|
+
assert.ok(tpl.copy_files, 'Should have copy_files');
|
|
678
|
+
assert.strictEqual(tpl.copy_files!.length, 2, 'Should have 2 copy_files from JSON');
|
|
679
|
+
assert.strictEqual(tpl.copy_files![0].src, 'custom.conf');
|
|
680
|
+
assert.strictEqual(tpl.copy_files![0].dest, 'config/custom.conf');
|
|
681
|
+
assert.strictEqual(tpl.copy_files![1].src, 'helper.txt');
|
|
682
|
+
assert.strictEqual(tpl.copy_files![1].dest, 'bin/helper.txt');
|
|
683
|
+
} finally {
|
|
684
|
+
rmrf(srcDir);
|
|
685
|
+
cleanConfig();
|
|
686
|
+
rmrf(testHome);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test('learn with .pt-template.json post_config uses JSON post_config', async () => {
|
|
691
|
+
const srcDir = createSourceDir('.test-learn-json-postconfig');
|
|
692
|
+
cleanConfig();
|
|
693
|
+
try {
|
|
694
|
+
const ptConfig = {
|
|
695
|
+
name: 'json-postconfig',
|
|
696
|
+
description: 'Test JSON post_config',
|
|
697
|
+
post_config: [
|
|
698
|
+
{ command: 'yarn install', description: 'Install deps' },
|
|
699
|
+
{ command: 'yarn build', description: 'Build project' }
|
|
700
|
+
]
|
|
701
|
+
};
|
|
702
|
+
fs.writeFileSync(path.join(srcDir, '.pt-template.json'), JSON.stringify(ptConfig));
|
|
703
|
+
// Also have a post_config.sh — it should be IGNORED in favor of JSON
|
|
704
|
+
fs.writeFileSync(path.join(srcDir, 'post_config.sh'), '#!/bin/bash\nnpm install');
|
|
705
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
706
|
+
|
|
707
|
+
await learn(srcDir, null, { yes: true });
|
|
708
|
+
|
|
709
|
+
const config = loadConfig();
|
|
710
|
+
const tpl = config.templates['json-postconfig'];
|
|
711
|
+
assert.ok(tpl.post_config, 'Should have post_config');
|
|
712
|
+
assert.strictEqual(tpl.post_config!.length, 2, 'Should use JSON post_config, not script');
|
|
713
|
+
assert.strictEqual(tpl.post_config![0].command, 'yarn install');
|
|
714
|
+
assert.strictEqual(tpl.post_config![1].command, 'yarn build');
|
|
715
|
+
} finally {
|
|
716
|
+
rmrf(srcDir);
|
|
717
|
+
cleanConfig();
|
|
718
|
+
rmrf(testHome);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test('learn update merges new variables with existing ones', async () => {
|
|
723
|
+
const srcDir = createSourceDir('.test-learn-update-vars');
|
|
724
|
+
cleanConfig();
|
|
725
|
+
try {
|
|
726
|
+
const initialConfig: PtConfig = {
|
|
727
|
+
version: '3.0',
|
|
728
|
+
templates: {
|
|
729
|
+
'merge-vars-tpl': {
|
|
730
|
+
description: 'Merge test',
|
|
731
|
+
templateRoot: '/old',
|
|
732
|
+
folders: [],
|
|
733
|
+
variables: [
|
|
734
|
+
{ name: 'existing_var', prompt: 'Existing:', default: 'old-default', required: true },
|
|
735
|
+
{ name: 'shared_var', prompt: 'Shared:', required: false }
|
|
736
|
+
]
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
saveConfig(initialConfig);
|
|
741
|
+
|
|
742
|
+
// Source with a file containing {{ shared_var }} and {{ brand_new_var }}
|
|
743
|
+
fs.writeFileSync(
|
|
744
|
+
path.join(srcDir, 'template.md'),
|
|
745
|
+
'Hello {{ shared_var }} and {{ brand_new_var }}!'
|
|
746
|
+
);
|
|
747
|
+
fs.mkdirSync(path.join(srcDir, 'src'), { recursive: true });
|
|
748
|
+
|
|
749
|
+
await learn(srcDir, 'merge-vars-tpl', { yes: true });
|
|
750
|
+
|
|
751
|
+
const config = loadConfig();
|
|
752
|
+
const vars = config.templates['merge-vars-tpl'].variables!;
|
|
753
|
+
const varNames = vars.map(v => v.name);
|
|
754
|
+
|
|
755
|
+
assert.ok(varNames.includes('existing_var'), 'Existing variable should be preserved');
|
|
756
|
+
assert.ok(varNames.includes('shared_var'), 'Shared variable should still exist');
|
|
757
|
+
assert.ok(varNames.includes('brand_new_var'), 'New detected variable should be added');
|
|
758
|
+
|
|
759
|
+
// Verify the existing_var retains its original properties
|
|
760
|
+
const existingVar = vars.find(v => v.name === 'existing_var')!;
|
|
761
|
+
assert.strictEqual(existingVar.default, 'old-default', 'Existing var default should be preserved');
|
|
762
|
+
} finally {
|
|
763
|
+
rmrf(srcDir);
|
|
764
|
+
cleanConfig();
|
|
765
|
+
rmrf(testHome);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test('learn in interactive mode handles empty directory without empty checkbox errors', async () => {
|
|
770
|
+
const srcDir = createSourceDir('.test-learn-empty-interactive');
|
|
771
|
+
cleanConfig();
|
|
772
|
+
|
|
773
|
+
const originalPrompt = inquirer.prompt;
|
|
774
|
+
const promptCalls: any[] = [];
|
|
775
|
+
inquirer.prompt = async (questions: any) => {
|
|
776
|
+
promptCalls.push(questions);
|
|
777
|
+
const q = Array.isArray(questions) ? questions[0] : questions;
|
|
778
|
+
if (q.name === 'newName') return { newName: 'empty-interactive-tpl' };
|
|
779
|
+
if (q.name === 'newDesc') return { newDesc: 'Empty template description' };
|
|
780
|
+
if (q.name === 'hasMoreVariables') return { hasMoreVariables: false };
|
|
781
|
+
return {};
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
try {
|
|
785
|
+
await learn(srcDir, null, {});
|
|
786
|
+
|
|
787
|
+
const config = loadConfig();
|
|
788
|
+
assert.ok(config.templates['empty-interactive-tpl'], 'Template should be saved');
|
|
789
|
+
assert.deepStrictEqual(config.templates['empty-interactive-tpl'].folders, [], 'Folders list should be empty');
|
|
790
|
+
assert.deepStrictEqual(config.templates['empty-interactive-tpl'].copy_files, [], 'Copy files list should be empty');
|
|
791
|
+
|
|
792
|
+
const checkboxPrompts = promptCalls.filter(q => q.type === 'checkbox');
|
|
793
|
+
assert.strictEqual(checkboxPrompts.length, 0, 'No checkbox prompts should be called for empty directory');
|
|
794
|
+
} finally {
|
|
795
|
+
inquirer.prompt = originalPrompt;
|
|
796
|
+
rmrf(srcDir);
|
|
797
|
+
cleanConfig();
|
|
798
|
+
rmrf(testHome);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test('learn in interactive mode handles unchecking all folders without empty checkbox errors', async () => {
|
|
803
|
+
const srcDir = createSourceDir('.test-learn-uncheck-folders');
|
|
804
|
+
cleanConfig();
|
|
805
|
+
|
|
806
|
+
fs.mkdirSync(path.join(srcDir, 'folderA'), { recursive: true });
|
|
807
|
+
fs.mkdirSync(path.join(srcDir, 'folderB'), { recursive: true });
|
|
808
|
+
|
|
809
|
+
const originalPrompt = inquirer.prompt;
|
|
810
|
+
const promptCalls: any[] = [];
|
|
811
|
+
|
|
812
|
+
inquirer.prompt = async (questions: any) => {
|
|
813
|
+
promptCalls.push(questions);
|
|
814
|
+
const q = Array.isArray(questions) ? questions[0] : questions;
|
|
815
|
+
if (q.name === 'newName') return { newName: 'uncheck-folders-tpl' };
|
|
816
|
+
if (q.name === 'newDesc') return { newDesc: 'Uncheck folders description' };
|
|
817
|
+
if (q.name === 'hasMoreVariables') return { hasMoreVariables: false };
|
|
818
|
+
if (q.name === 'selectedFiles') return { selectedFiles: [] };
|
|
819
|
+
if (q.name === 'selectedStructure') return { selectedStructure: [] };
|
|
820
|
+
return {};
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
try {
|
|
824
|
+
await learn(srcDir, null, {});
|
|
825
|
+
|
|
826
|
+
const config = loadConfig();
|
|
827
|
+
assert.ok(config.templates['uncheck-folders-tpl'], 'Template should be saved');
|
|
828
|
+
assert.deepStrictEqual(config.templates['uncheck-folders-tpl'].folders, [], 'Folders list should be empty');
|
|
829
|
+
|
|
830
|
+
const selectedFoldersPrompt = promptCalls.find(q => q.name === 'selectedFolders');
|
|
831
|
+
assert.strictEqual(selectedFoldersPrompt, undefined, 'selectedFolders checkbox prompt should not be called');
|
|
832
|
+
} finally {
|
|
833
|
+
inquirer.prompt = originalPrompt;
|
|
834
|
+
rmrf(srcDir);
|
|
835
|
+
cleanConfig();
|
|
836
|
+
rmrf(testHome);
|
|
837
|
+
}
|
|
838
|
+
});
|