@garyr/pt-cli 0.28.0 → 0.30.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/dist/commands/initCommand.js +2 -1
- package/dist/commands/learnCommand.js +27 -6
- package/dist/remote.js +4 -2
- package/package.json +2 -2
- package/src/commands/initCommand.ts +2 -1
- package/src/commands/learnCommand.ts +26 -6
- package/src/remote.ts +5 -2
- package/test-direct-template.json +24 -0
- package/tests/config.test.ts +650 -0
|
@@ -155,12 +155,32 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
155
155
|
console.log(chalk.cyan(`Auto-detected ${detectedVars.length} variable(s): ${detectedVars.join(', ')}`));
|
|
156
156
|
}
|
|
157
157
|
let variables = [];
|
|
158
|
-
//
|
|
159
|
-
if (isUpdate
|
|
160
|
-
|
|
158
|
+
// During updates, merge existing template variables with JSON file variables
|
|
159
|
+
if (isUpdate) {
|
|
160
|
+
// Start with existing template variables
|
|
161
|
+
if (config.templates[updateTemplate].variables) {
|
|
162
|
+
variables = [...config.templates[updateTemplate].variables];
|
|
163
|
+
}
|
|
164
|
+
// Then add JSON variables (overwrite/update existing ones with same name)
|
|
165
|
+
if (fileTemplateConfig.variables && Array.isArray(fileTemplateConfig.variables)) {
|
|
166
|
+
for (const v of fileTemplateConfig.variables) {
|
|
167
|
+
const existingIndex = variables.findIndex(existing => existing.name === v.name);
|
|
168
|
+
if (existingIndex !== -1) {
|
|
169
|
+
// Update existing variable with JSON values (but preserve other fields)
|
|
170
|
+
variables[existingIndex] = { ...variables[existingIndex], ...v };
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Add new variable
|
|
174
|
+
variables.push({ ...v });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
161
178
|
}
|
|
162
|
-
else
|
|
163
|
-
variables
|
|
179
|
+
else {
|
|
180
|
+
// For new templates, use JSON variables if available
|
|
181
|
+
if (fileTemplateConfig.variables && Array.isArray(fileTemplateConfig.variables)) {
|
|
182
|
+
variables = [...fileTemplateConfig.variables];
|
|
183
|
+
}
|
|
164
184
|
}
|
|
165
185
|
// Add detected variables if not already present
|
|
166
186
|
for (const varName of detectedVars) {
|
|
@@ -369,7 +389,8 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
369
389
|
loop: false,
|
|
370
390
|
theme: {
|
|
371
391
|
icon: {
|
|
372
|
-
|
|
392
|
+
checked: chalk.green('[x] '),
|
|
393
|
+
unchecked: '[ ] ',
|
|
373
394
|
}
|
|
374
395
|
},
|
|
375
396
|
choices
|
package/dist/remote.js
CHANGED
|
@@ -8,12 +8,14 @@ import { extract } from 'tar'; // You'll need: npm install tar
|
|
|
8
8
|
export async function downloadAndExtract(url) {
|
|
9
9
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pt-template-'));
|
|
10
10
|
let downloadUrl = url;
|
|
11
|
+
// Strip trailing slash and .git suffix before converting to archive URL
|
|
12
|
+
let cleanUrl = url.replace(/\/$/, '').replace(/\.git$/, '');
|
|
11
13
|
// Convert GitHub/Gitea URLs to Zip/Tarball endpoints
|
|
12
14
|
if (url.includes('github.com')) {
|
|
13
|
-
downloadUrl =
|
|
15
|
+
downloadUrl = cleanUrl + '/archive/refs/heads/main.tar.gz';
|
|
14
16
|
}
|
|
15
17
|
else if (url.includes('gitea')) {
|
|
16
|
-
downloadUrl =
|
|
18
|
+
downloadUrl = cleanUrl + '/archive/main.tar.gz';
|
|
17
19
|
}
|
|
18
20
|
const response = await fetch(downloadUrl);
|
|
19
21
|
if (!response.ok)
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garyr/pt-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Project Template CLI - Learn structures and initialize projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"pt": "
|
|
7
|
+
"pt": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
@@ -158,11 +158,30 @@ export async function learn(sourcePath: string, updateTemplate: string | null =
|
|
|
158
158
|
|
|
159
159
|
let variables: TemplateVariable[] = [];
|
|
160
160
|
|
|
161
|
-
//
|
|
162
|
-
if (isUpdate
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
// During updates, merge existing template variables with JSON file variables
|
|
162
|
+
if (isUpdate) {
|
|
163
|
+
// Start with existing template variables
|
|
164
|
+
if (config.templates[updateTemplate].variables) {
|
|
165
|
+
variables = [...config.templates[updateTemplate].variables];
|
|
166
|
+
}
|
|
167
|
+
// Then add JSON variables (overwrite/update existing ones with same name)
|
|
168
|
+
if (fileTemplateConfig.variables && Array.isArray(fileTemplateConfig.variables)) {
|
|
169
|
+
for (const v of fileTemplateConfig.variables) {
|
|
170
|
+
const existingIndex = variables.findIndex(existing => existing.name === v.name);
|
|
171
|
+
if (existingIndex !== -1) {
|
|
172
|
+
// Update existing variable with JSON values (but preserve other fields)
|
|
173
|
+
variables[existingIndex] = { ...variables[existingIndex], ...v };
|
|
174
|
+
} else {
|
|
175
|
+
// Add new variable
|
|
176
|
+
variables.push({ ...v });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
// For new templates, use JSON variables if available
|
|
182
|
+
if (fileTemplateConfig.variables && Array.isArray(fileTemplateConfig.variables)) {
|
|
183
|
+
variables = [...fileTemplateConfig.variables];
|
|
184
|
+
}
|
|
166
185
|
}
|
|
167
186
|
|
|
168
187
|
// Add detected variables if not already present
|
|
@@ -380,7 +399,8 @@ export async function learn(sourcePath: string, updateTemplate: string | null =
|
|
|
380
399
|
loop: false,
|
|
381
400
|
theme: {
|
|
382
401
|
icon: {
|
|
383
|
-
|
|
402
|
+
checked: chalk.green('[x] '),
|
|
403
|
+
unchecked: '[ ] ',
|
|
384
404
|
}
|
|
385
405
|
},
|
|
386
406
|
choices
|
package/src/remote.ts
CHANGED
|
@@ -10,11 +10,14 @@ export async function downloadAndExtract(url: string): Promise<string> {
|
|
|
10
10
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pt-template-'));
|
|
11
11
|
let downloadUrl = url;
|
|
12
12
|
|
|
13
|
+
// Strip trailing slash and .git suffix before converting to archive URL
|
|
14
|
+
let cleanUrl = url.replace(/\/$/, '').replace(/\.git$/, '');
|
|
15
|
+
|
|
13
16
|
// Convert GitHub/Gitea URLs to Zip/Tarball endpoints
|
|
14
17
|
if (url.includes('github.com')) {
|
|
15
|
-
downloadUrl =
|
|
18
|
+
downloadUrl = cleanUrl + '/archive/refs/heads/main.tar.gz';
|
|
16
19
|
} else if (url.includes('gitea')) {
|
|
17
|
-
downloadUrl =
|
|
20
|
+
downloadUrl = cleanUrl + '/archive/main.tar.gz';
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const response = await fetch(downloadUrl);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "direct-json-test",
|
|
3
|
+
"description": "A mock template for direct JSON scaffolding test",
|
|
4
|
+
"folders": [
|
|
5
|
+
{
|
|
6
|
+
"name": "src",
|
|
7
|
+
"info": "contains sources",
|
|
8
|
+
"children": [
|
|
9
|
+
{
|
|
10
|
+
"name": "components",
|
|
11
|
+
"info": "reusable components"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "utils",
|
|
15
|
+
"info": "utility functions"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "docs",
|
|
21
|
+
"info": "documentation folder"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
package/tests/config.test.ts
CHANGED
|
@@ -26,6 +26,12 @@ test('normalizeVariable key ordering', () => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('saveConfig and loadConfig roundtrip', () => {
|
|
29
|
+
// Save the current config to restore later
|
|
30
|
+
let savedConfig: PtConfig | null = null;
|
|
31
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
32
|
+
savedConfig = loadConfig();
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
// Clean up any existing test files
|
|
30
36
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
31
37
|
fs.unlinkSync(CONFIG_PATH);
|
|
@@ -92,7 +98,651 @@ test('saveConfig and loadConfig roundtrip', () => {
|
|
|
92
98
|
if (fs.existsSync(backupPath)) {
|
|
93
99
|
fs.unlinkSync(backupPath);
|
|
94
100
|
}
|
|
101
|
+
|
|
102
|
+
// Restore the original config if it existed
|
|
103
|
+
if (savedConfig) {
|
|
104
|
+
saveConfig(savedConfig);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (fs.existsSync(testHome)) {
|
|
108
|
+
fs.rmdirSync(testHome);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('normalizeVariable with minimal input', () => {
|
|
113
|
+
const variableInput = {
|
|
114
|
+
name: 'minimalVar'
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const normalized = normalizeVariable(variableInput);
|
|
118
|
+
|
|
119
|
+
// Verify that only name is present
|
|
120
|
+
const keys = Object.keys(normalized);
|
|
121
|
+
assert.deepStrictEqual(keys, ['name']);
|
|
122
|
+
assert.strictEqual(normalized.name, 'minimalVar');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('normalizeVariable with all fields', () => {
|
|
126
|
+
const variableInput = {
|
|
127
|
+
required: false,
|
|
128
|
+
default: 'default-value',
|
|
129
|
+
prompt: 'Custom prompt:',
|
|
130
|
+
name: 'allFields'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const normalized = normalizeVariable(variableInput);
|
|
134
|
+
|
|
135
|
+
// Verify all fields are present and in correct order
|
|
136
|
+
const keys = Object.keys(normalized);
|
|
137
|
+
assert.deepStrictEqual(keys, ['name', 'prompt', 'default', 'required']);
|
|
138
|
+
assert.strictEqual(normalized.name, 'allFields');
|
|
139
|
+
assert.strictEqual(normalized.prompt, 'Custom prompt:');
|
|
140
|
+
assert.strictEqual(normalized.default, 'default-value');
|
|
141
|
+
assert.strictEqual(normalized.required, false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('saveConfig creates backup on overwrite', () => {
|
|
145
|
+
// Save the current config to restore later
|
|
146
|
+
let savedConfig: PtConfig | null = null;
|
|
147
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
148
|
+
savedConfig = loadConfig();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Clean up any existing test files
|
|
152
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
153
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
154
|
+
}
|
|
155
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
156
|
+
if (fs.existsSync(backupPath)) {
|
|
157
|
+
fs.unlinkSync(backupPath);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const config1: PtConfig = {
|
|
161
|
+
version: '1.0.0',
|
|
162
|
+
templates: {}
|
|
163
|
+
};
|
|
164
|
+
const config2: PtConfig = {
|
|
165
|
+
version: '2.0.0',
|
|
166
|
+
templates: {}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Save first config
|
|
170
|
+
saveConfig(config1);
|
|
171
|
+
assert.ok(fs.existsSync(CONFIG_PATH), 'Config file should be created');
|
|
172
|
+
|
|
173
|
+
// Save second config (should create backup)
|
|
174
|
+
saveConfig(config2);
|
|
175
|
+
assert.ok(fs.existsSync(backupPath), 'Backup file should exist');
|
|
176
|
+
|
|
177
|
+
// Verify backup contains first config
|
|
178
|
+
const backupContent = fs.readFileSync(backupPath, 'utf-8');
|
|
179
|
+
assert.ok(backupContent.includes("version: 1.0.0") || backupContent.includes("version: '1.0.0'"), 'Backup should contain previous version');
|
|
180
|
+
|
|
181
|
+
// Clean up
|
|
182
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
183
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
184
|
+
}
|
|
185
|
+
if (fs.existsSync(backupPath)) {
|
|
186
|
+
fs.unlinkSync(backupPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Restore the original config if it existed
|
|
190
|
+
if (savedConfig) {
|
|
191
|
+
saveConfig(savedConfig);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (fs.existsSync(testHome)) {
|
|
195
|
+
fs.rmdirSync(testHome);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('saveConfig atomic write behavior', () => {
|
|
200
|
+
// Save the current config to restore later
|
|
201
|
+
let savedConfig: PtConfig | null = null;
|
|
202
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
203
|
+
savedConfig = loadConfig();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Clean up any existing test files
|
|
207
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
208
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
209
|
+
}
|
|
210
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
211
|
+
if (fs.existsSync(backupPath)) {
|
|
212
|
+
fs.unlinkSync(backupPath);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const config: PtConfig = {
|
|
216
|
+
version: '1.0.0',
|
|
217
|
+
templates: {}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Save config - should use atomic write
|
|
221
|
+
saveConfig(config);
|
|
222
|
+
|
|
223
|
+
// Verify config exists
|
|
224
|
+
assert.ok(fs.existsSync(CONFIG_PATH), 'Config file should be created');
|
|
225
|
+
|
|
226
|
+
// Verify no temp file remains
|
|
227
|
+
const tempPath = CONFIG_PATH + '.tmp';
|
|
228
|
+
assert.ok(!fs.existsSync(tempPath), 'Temp file should not remain');
|
|
229
|
+
|
|
230
|
+
// Clean up
|
|
231
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
232
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
233
|
+
}
|
|
234
|
+
if (fs.existsSync(backupPath)) {
|
|
235
|
+
fs.unlinkSync(backupPath);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Restore the original config if it existed
|
|
239
|
+
if (savedConfig) {
|
|
240
|
+
saveConfig(savedConfig);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (fs.existsSync(testHome)) {
|
|
244
|
+
fs.rmdirSync(testHome);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('loadConfig returns default config when file missing', () => {
|
|
249
|
+
// Save the current config to restore later
|
|
250
|
+
let savedConfig: PtConfig | null = null;
|
|
251
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
252
|
+
savedConfig = loadConfig();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Clean up any existing test files
|
|
256
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
257
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
258
|
+
}
|
|
259
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
260
|
+
if (fs.existsSync(backupPath)) {
|
|
261
|
+
fs.unlinkSync(backupPath);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const config = loadConfig();
|
|
265
|
+
|
|
266
|
+
// Verify default config structure
|
|
267
|
+
assert.strictEqual(config.version, '3.0');
|
|
268
|
+
assert.deepStrictEqual(config.templates, {});
|
|
269
|
+
assert.deepStrictEqual(config.default_post_config, []);
|
|
270
|
+
assert.deepStrictEqual(config.variables, []);
|
|
271
|
+
|
|
272
|
+
// Clean up
|
|
273
|
+
if (fs.existsSync(testHome)) {
|
|
274
|
+
fs.rmdirSync(testHome);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Restore the original config if it existed
|
|
278
|
+
if (savedConfig) {
|
|
279
|
+
saveConfig(savedConfig);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('saveConfig with variables normalizes key order', () => {
|
|
284
|
+
// Save the current config to restore later
|
|
285
|
+
let savedConfig: PtConfig | null = null;
|
|
286
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
287
|
+
savedConfig = loadConfig();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Clean up any existing test files
|
|
291
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
292
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
293
|
+
}
|
|
294
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
295
|
+
if (fs.existsSync(backupPath)) {
|
|
296
|
+
fs.unlinkSync(backupPath);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const config: PtConfig = {
|
|
300
|
+
version: '1.0.0',
|
|
301
|
+
templates: {
|
|
302
|
+
'test-template': {
|
|
303
|
+
description: 'Test',
|
|
304
|
+
folders: [],
|
|
305
|
+
variables: [
|
|
306
|
+
{
|
|
307
|
+
required: true,
|
|
308
|
+
default: 'value1',
|
|
309
|
+
prompt: 'Prompt1:',
|
|
310
|
+
name: 'var1'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
required: false,
|
|
314
|
+
default: 'value2',
|
|
315
|
+
prompt: 'Prompt2:',
|
|
316
|
+
name: 'var2'
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Save config
|
|
324
|
+
saveConfig(config);
|
|
325
|
+
|
|
326
|
+
// Load and verify
|
|
327
|
+
const loaded = loadConfig();
|
|
328
|
+
const vars = loaded.templates['test-template'].variables;
|
|
329
|
+
|
|
330
|
+
assert.strictEqual(vars.length, 2, 'Should have 2 variables');
|
|
331
|
+
assert.strictEqual(vars[0].name, 'var1', 'First variable name should be var1');
|
|
332
|
+
assert.strictEqual(vars[1].name, 'var2', 'Second variable name should be var2');
|
|
333
|
+
|
|
334
|
+
// Clean up
|
|
335
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
336
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
337
|
+
}
|
|
338
|
+
if (fs.existsSync(backupPath)) {
|
|
339
|
+
fs.unlinkSync(backupPath);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Restore the original config if it existed
|
|
343
|
+
if (savedConfig) {
|
|
344
|
+
saveConfig(savedConfig);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (fs.existsSync(testHome)) {
|
|
348
|
+
fs.rmdirSync(testHome);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('saveConfig with empty config file', () => {
|
|
353
|
+
// Save the current config to restore later
|
|
354
|
+
let savedConfig: PtConfig | null = null;
|
|
355
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
356
|
+
savedConfig = loadConfig();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Clean up any existing test files
|
|
360
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
361
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
362
|
+
}
|
|
363
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
364
|
+
if (fs.existsSync(backupPath)) {
|
|
365
|
+
fs.unlinkSync(backupPath);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Create empty config file
|
|
369
|
+
fs.writeFileSync(CONFIG_PATH, '');
|
|
370
|
+
|
|
371
|
+
// Try to load - should throw error
|
|
372
|
+
assert.throws(() => {
|
|
373
|
+
loadConfig();
|
|
374
|
+
}, /Config file is empty/, 'Should throw error for empty config file');
|
|
375
|
+
|
|
376
|
+
// Clean up
|
|
377
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
378
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
379
|
+
}
|
|
380
|
+
if (fs.existsSync(backupPath)) {
|
|
381
|
+
fs.unlinkSync(backupPath);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Restore the original config if it existed
|
|
385
|
+
if (savedConfig) {
|
|
386
|
+
saveConfig(savedConfig);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (fs.existsSync(testHome)) {
|
|
390
|
+
fs.rmdirSync(testHome);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test('normalizeVariable preserves field order', () => {
|
|
395
|
+
const variableInput = {
|
|
396
|
+
required: true,
|
|
397
|
+
default: 'my-default',
|
|
398
|
+
prompt: 'Enter variable:',
|
|
399
|
+
name: 'varName'
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const normalized = normalizeVariable(variableInput);
|
|
403
|
+
|
|
404
|
+
// Verify field order is preserved
|
|
405
|
+
const keys = Object.keys(normalized);
|
|
406
|
+
assert.deepStrictEqual(keys, ['name', 'prompt', 'default', 'required']);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('saveConfig handles multiple templates', () => {
|
|
410
|
+
// Save the current config to restore later
|
|
411
|
+
let savedConfig: PtConfig | null = null;
|
|
412
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
413
|
+
savedConfig = loadConfig();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Clean up any existing test files
|
|
417
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
418
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
419
|
+
}
|
|
420
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
421
|
+
if (fs.existsSync(backupPath)) {
|
|
422
|
+
fs.unlinkSync(backupPath);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const config: PtConfig = {
|
|
426
|
+
version: '1.0.0',
|
|
427
|
+
templates: {
|
|
428
|
+
'template1': {
|
|
429
|
+
description: 'Template 1',
|
|
430
|
+
folders: []
|
|
431
|
+
},
|
|
432
|
+
'template2': {
|
|
433
|
+
description: 'Template 2',
|
|
434
|
+
folders: []
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// Save config
|
|
440
|
+
saveConfig(config);
|
|
441
|
+
|
|
442
|
+
// Load and verify
|
|
443
|
+
const loaded = loadConfig();
|
|
444
|
+
assert.strictEqual(Object.keys(loaded.templates).length, 2, 'Should have 2 templates');
|
|
445
|
+
assert.strictEqual(loaded.templates['template1'].description, 'Template 1');
|
|
446
|
+
assert.strictEqual(loaded.templates['template2'].description, 'Template 2');
|
|
447
|
+
|
|
448
|
+
// Clean up
|
|
449
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
450
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
451
|
+
}
|
|
452
|
+
if (fs.existsSync(backupPath)) {
|
|
453
|
+
fs.unlinkSync(backupPath);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Restore the original config if it existed
|
|
457
|
+
if (savedConfig) {
|
|
458
|
+
saveConfig(savedConfig);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (fs.existsSync(testHome)) {
|
|
462
|
+
fs.rmdirSync(testHome);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('saveConfig handles templates with variables', () => {
|
|
467
|
+
// Save the current config to restore later
|
|
468
|
+
let savedConfig: PtConfig | null = null;
|
|
469
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
470
|
+
savedConfig = loadConfig();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Clean up any existing test files
|
|
474
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
475
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
476
|
+
}
|
|
477
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
478
|
+
if (fs.existsSync(backupPath)) {
|
|
479
|
+
fs.unlinkSync(backupPath);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const config: PtConfig = {
|
|
483
|
+
version: '1.0.0',
|
|
484
|
+
templates: {
|
|
485
|
+
'template-with-vars': {
|
|
486
|
+
description: 'Template with variables',
|
|
487
|
+
folders: [],
|
|
488
|
+
variables: [
|
|
489
|
+
{
|
|
490
|
+
required: true,
|
|
491
|
+
default: 'default-value',
|
|
492
|
+
prompt: 'Enter value:',
|
|
493
|
+
name: 'myVar'
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// Save config
|
|
501
|
+
saveConfig(config);
|
|
502
|
+
|
|
503
|
+
// Load and verify
|
|
504
|
+
const loaded = loadConfig();
|
|
505
|
+
const template = loaded.templates['template-with-vars'];
|
|
506
|
+
|
|
507
|
+
assert.ok(template, 'Template should exist');
|
|
508
|
+
assert.strictEqual(template.variables?.length, 1, 'Should have 1 variable');
|
|
509
|
+
assert.strictEqual(template.variables?.[0].name, 'myVar', 'Variable name should be myVar');
|
|
510
|
+
assert.strictEqual(template.variables?.[0].prompt, 'Enter value:', 'Variable prompt should match');
|
|
511
|
+
assert.strictEqual(template.variables?.[0].default, 'default-value', 'Variable default should match');
|
|
512
|
+
assert.strictEqual(template.variables?.[0].required, true, 'Variable should be required');
|
|
513
|
+
|
|
514
|
+
// Clean up
|
|
515
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
516
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
517
|
+
}
|
|
518
|
+
if (fs.existsSync(backupPath)) {
|
|
519
|
+
fs.unlinkSync(backupPath);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Restore the original config if it existed
|
|
523
|
+
if (savedConfig) {
|
|
524
|
+
saveConfig(savedConfig);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (fs.existsSync(testHome)) {
|
|
528
|
+
fs.rmdirSync(testHome);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('loadConfig handles migration from v2.0', () => {
|
|
533
|
+
// Save the current config to restore later
|
|
534
|
+
let savedConfig: PtConfig | null = null;
|
|
535
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
536
|
+
savedConfig = loadConfig();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Clean up any existing test files
|
|
540
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
541
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
542
|
+
}
|
|
543
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
544
|
+
if (fs.existsSync(backupPath)) {
|
|
545
|
+
fs.unlinkSync(backupPath);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Create a v2.0 config
|
|
549
|
+
const v2Config = {
|
|
550
|
+
version: '2.0',
|
|
551
|
+
templates: {
|
|
552
|
+
'test-template': {
|
|
553
|
+
name: 'Test Template',
|
|
554
|
+
description: 'Test Description',
|
|
555
|
+
type: 'web',
|
|
556
|
+
folders: []
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
|
|
562
|
+
|
|
563
|
+
// Load config - should migrate to v3.0
|
|
564
|
+
const config = loadConfig();
|
|
565
|
+
|
|
566
|
+
// Verify migration happened
|
|
567
|
+
assert.strictEqual(config.version, '3.0', 'Should be migrated to v3.0');
|
|
568
|
+
assert.strictEqual(config.templates['test-template'].description, 'Test Template', 'Name should be migrated to description');
|
|
569
|
+
assert.ok(!config.templates['test-template'].type, 'Type should be removed');
|
|
570
|
+
|
|
571
|
+
// Clean up
|
|
572
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
573
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
574
|
+
}
|
|
575
|
+
if (fs.existsSync(backupPath)) {
|
|
576
|
+
fs.unlinkSync(backupPath);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Restore the original config if it existed
|
|
580
|
+
if (savedConfig) {
|
|
581
|
+
saveConfig(savedConfig);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (fs.existsSync(testHome)) {
|
|
585
|
+
fs.rmdirSync(testHome);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('loadConfig handles migration from v2.0 with global_post_config', () => {
|
|
590
|
+
// Save the current config to restore later
|
|
591
|
+
let savedConfig: PtConfig | null = null;
|
|
592
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
593
|
+
savedConfig = loadConfig();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Clean up any existing test files
|
|
597
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
598
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
599
|
+
}
|
|
600
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
601
|
+
if (fs.existsSync(backupPath)) {
|
|
602
|
+
fs.unlinkSync(backupPath);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Create a v2.0 config with global_post_config
|
|
606
|
+
const v2Config = {
|
|
607
|
+
version: '2.0',
|
|
608
|
+
templates: {},
|
|
609
|
+
global_post_config: [
|
|
610
|
+
{
|
|
611
|
+
command: 'npm install',
|
|
612
|
+
description: 'Install dependencies'
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
|
|
618
|
+
|
|
619
|
+
// Load config - should migrate to v3.0
|
|
620
|
+
const config = loadConfig();
|
|
621
|
+
|
|
622
|
+
// Verify migration happened
|
|
623
|
+
assert.strictEqual(config.version, '3.0', 'Should be migrated to v3.0');
|
|
624
|
+
assert.deepStrictEqual(config.default_post_config, [
|
|
625
|
+
{
|
|
626
|
+
command: 'npm install',
|
|
627
|
+
description: 'Install dependencies'
|
|
628
|
+
}
|
|
629
|
+
], 'global_post_config should be migrated to default_post_config');
|
|
630
|
+
assert.ok(!config.global_post_config, 'global_post_config should be removed');
|
|
631
|
+
|
|
632
|
+
// Clean up
|
|
633
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
634
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
635
|
+
}
|
|
636
|
+
if (fs.existsSync(backupPath)) {
|
|
637
|
+
fs.unlinkSync(backupPath);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Restore the original config if it existed
|
|
641
|
+
if (savedConfig) {
|
|
642
|
+
saveConfig(savedConfig);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (fs.existsSync(testHome)) {
|
|
646
|
+
fs.rmdirSync(testHome);
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test('loadConfig handles migration from v2.0 with variables', () => {
|
|
651
|
+
// Save the current config to restore later
|
|
652
|
+
let savedConfig: PtConfig | null = null;
|
|
653
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
654
|
+
savedConfig = loadConfig();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Clean up any existing test files
|
|
658
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
659
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
660
|
+
}
|
|
661
|
+
const backupPath = CONFIG_PATH + '.bak';
|
|
662
|
+
if (fs.existsSync(backupPath)) {
|
|
663
|
+
fs.unlinkSync(backupPath);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Create a v2.0 config with variables as Record<string, string>
|
|
667
|
+
const v2Config = {
|
|
668
|
+
version: '2.0',
|
|
669
|
+
templates: {},
|
|
670
|
+
variables: {
|
|
671
|
+
'client_name': 'My Client',
|
|
672
|
+
'project_type': 'web'
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(v2Config));
|
|
677
|
+
|
|
678
|
+
// Load config - should migrate to v3.0
|
|
679
|
+
const config = loadConfig();
|
|
680
|
+
|
|
681
|
+
// Verify migration happened
|
|
682
|
+
assert.strictEqual(config.version, '3.0', 'Should be migrated to v3.0');
|
|
683
|
+
assert.strictEqual(config.variables.length, 2, 'Should have 2 variables');
|
|
684
|
+
assert.strictEqual(config.variables[0].name, 'client_name', 'First variable name should be client_name');
|
|
685
|
+
assert.strictEqual(config.variables[0].default, 'My Client', 'First variable default should be My Client');
|
|
686
|
+
assert.strictEqual(config.variables[1].name, 'project_type', 'Second variable name should be project_type');
|
|
687
|
+
assert.strictEqual(config.variables[1].default, 'web', 'Second variable default should be web');
|
|
688
|
+
|
|
689
|
+
// Clean up
|
|
690
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
691
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
692
|
+
}
|
|
693
|
+
if (fs.existsSync(backupPath)) {
|
|
694
|
+
fs.unlinkSync(backupPath);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Restore the original config if it existed
|
|
698
|
+
if (savedConfig) {
|
|
699
|
+
saveConfig(savedConfig);
|
|
700
|
+
}
|
|
701
|
+
|
|
95
702
|
if (fs.existsSync(testHome)) {
|
|
96
703
|
fs.rmdirSync(testHome);
|
|
97
704
|
}
|
|
98
705
|
});
|
|
706
|
+
|
|
707
|
+
test('loadConfig handles existing config file', () => {
|
|
708
|
+
// Don't delete the existing config file - let it be restored by the user
|
|
709
|
+
// This test verifies that loadConfig works with an existing config
|
|
710
|
+
const config = loadConfig();
|
|
711
|
+
|
|
712
|
+
// Verify config is loaded correctly
|
|
713
|
+
assert.ok(config, 'Config should be loaded');
|
|
714
|
+
assert.ok(config.templates, 'Templates should exist');
|
|
715
|
+
assert.ok(config.version, 'Version should exist');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test('saveConfig preserves existing config when not deleting', () => {
|
|
719
|
+
// Save the current config to restore later
|
|
720
|
+
let savedConfig: PtConfig | null = null;
|
|
721
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
722
|
+
savedConfig = loadConfig();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Don't delete the config file - test saving without deletion
|
|
726
|
+
const config: PtConfig = {
|
|
727
|
+
version: '1.0.0',
|
|
728
|
+
templates: {
|
|
729
|
+
'test-template': {
|
|
730
|
+
description: 'Test',
|
|
731
|
+
folders: []
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// Save config
|
|
737
|
+
saveConfig(config);
|
|
738
|
+
|
|
739
|
+
// Load and verify
|
|
740
|
+
const loaded = loadConfig();
|
|
741
|
+
assert.strictEqual(loaded.version, '1.0.0');
|
|
742
|
+
assert.ok(loaded.templates['test-template']);
|
|
743
|
+
|
|
744
|
+
// Restore the original config if it existed
|
|
745
|
+
if (savedConfig) {
|
|
746
|
+
saveConfig(savedConfig);
|
|
747
|
+
}
|
|
748
|
+
});
|