@garyr/pt-cli 1.0.1 ā 1.1.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 +41 -14
- package/dist/commands/completionCommand.js +396 -0
- package/dist/commands/learnCommand.js +28 -7
- package/dist/commands/template-utils.js +17 -7
- package/dist/commands/updateCommand.js +56 -25
- package/dist/config.js +3 -0
- package/dist/index.js +9 -1
- package/doc/usage.md +38 -0
- package/package.json +4 -1
- package/src/commands/completionCommand.ts +404 -0
- package/src/commands/learnCommand.ts +29 -7
- package/src/commands/template-utils.ts +17 -8
- package/src/commands/updateCommand.ts +125 -91
- package/src/config.ts +3 -0
- package/src/index.ts +10 -1
- package/tests/completion.test.ts +210 -0
- package/tests/config-utils.test.ts +1 -1
- package/tests/learn.test.ts +5 -4
- package/tests/update.test.ts +63 -9
|
@@ -4,7 +4,7 @@ import inquirer from 'inquirer';
|
|
|
4
4
|
import { loadConfig, saveConfig, getTemplateNames, shouldExclude, shouldIgnore, shouldExcludeFile, getDefaultPostConfig } from '../config.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { downloadAndExtract } from '../remote.js';
|
|
7
|
-
import { extractStructure, findVariablesInFiles, isExecutable, parseInfoFile, loadJsonTemplateConfig, parsePostConfigScript, mergePostConfigTasks, mergePostCopyFiles,
|
|
7
|
+
import { extractStructure, findVariablesInFiles, isExecutable, parseInfoFile, loadJsonTemplateConfig, parsePostConfigScript, mergePostConfigTasks, mergePostCopyFiles, promptNewVariables, promptGlobalVariables, printNoNewVariables, promptNewFolders, printNoNewFolders, printNewFiles, promptNewFiles, promptRootFiles, promptStructureFolders, promptCopyFolders, promptPostConfigTasks } from './template-utils.js';
|
|
8
8
|
export async function update(sourcePath, templateName, options = {}) {
|
|
9
9
|
const isFullMode = options.noDiff;
|
|
10
10
|
let resolvedPath;
|
|
@@ -125,11 +125,13 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
125
125
|
if (!isFullMode) {
|
|
126
126
|
// Additive mode: only present new variables for selection
|
|
127
127
|
const existingVarNames = new Set(config.templates[templateName].variables?.map(v => v.name) || []);
|
|
128
|
+
// newVars are NOT added to variables yet - only existing variables + JSON variables are in variables
|
|
128
129
|
const newVars = variables.filter(v => !existingVarNames.has(v.name));
|
|
129
130
|
if (newVars.length > 0) {
|
|
130
131
|
console.log(chalk.cyan(`\nš New Variables:`));
|
|
131
132
|
console.log(chalk.green(` + ${newVars.length} new variable(s): ${newVars.map(v => v.name).join(', ')}`));
|
|
132
133
|
const selectedNewVars = await promptNewVariables(newVars, options);
|
|
134
|
+
// Only add the selected new variables (not all newVars)
|
|
133
135
|
variables.push(...selectedNewVars);
|
|
134
136
|
}
|
|
135
137
|
else {
|
|
@@ -150,24 +152,34 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
else {
|
|
153
|
-
// Full mode:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
// Full mode: replace all variables with detected ones (no additive)
|
|
156
|
+
variables = [];
|
|
157
|
+
// Add detected variables
|
|
158
|
+
for (const varName of detectedVars) {
|
|
159
|
+
variables.push({
|
|
160
|
+
name: varName,
|
|
161
|
+
prompt: `Enter ${varName}:`,
|
|
162
|
+
required: true
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Also include JSON variables
|
|
166
|
+
if (fileTemplateConfig.variables && Array.isArray(fileTemplateConfig.variables)) {
|
|
167
|
+
for (const v of fileTemplateConfig.variables) {
|
|
168
|
+
const existingIndex = variables.findIndex(existing => existing.name === v.name);
|
|
169
|
+
if (existingIndex !== -1) {
|
|
170
|
+
variables[existingIndex] = { ...variables[existingIndex], ...v };
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
variables.push({ ...v });
|
|
159
174
|
}
|
|
160
175
|
}
|
|
161
176
|
}
|
|
162
|
-
if (globalVarsToPrompt.length > 0) {
|
|
163
|
-
const selectedGlobals = await promptGlobalVariables(globalVarsToPrompt, options);
|
|
164
|
-
variables.push(...selectedGlobals);
|
|
165
|
-
}
|
|
166
|
-
const additionalVars = await promptAdditionalVariables(variables, options);
|
|
167
|
-
variables.push(...additionalVars);
|
|
168
177
|
}
|
|
169
178
|
// 1. Structure (skeleton) - Additive mode
|
|
170
179
|
let folders = [];
|
|
180
|
+
let selectedStructure = [];
|
|
181
|
+
let selectedFiles = [];
|
|
182
|
+
let selectedFolders = [];
|
|
171
183
|
if (!isFullMode) {
|
|
172
184
|
// Additive mode: only add new folders
|
|
173
185
|
const detectedFolders = fileTemplateConfig.folders && Array.isArray(fileTemplateConfig.folders)
|
|
@@ -185,6 +197,14 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
185
197
|
printNoNewFolders();
|
|
186
198
|
folders = existingFolders;
|
|
187
199
|
}
|
|
200
|
+
// selectedStructure should include ALL folders (existing + added) in additive mode
|
|
201
|
+
// so that the final filter doesn't drop user-selected new folders
|
|
202
|
+
selectedStructure = [
|
|
203
|
+
...new Set([
|
|
204
|
+
...existingFolders.map(f => f.name),
|
|
205
|
+
...folders.filter(f => !existingFolders.some(ef => ef.name === f.name)).map(f => f.name),
|
|
206
|
+
]),
|
|
207
|
+
];
|
|
188
208
|
}
|
|
189
209
|
else {
|
|
190
210
|
// Full mode: original behavior
|
|
@@ -196,11 +216,10 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
196
216
|
const rootEntries = fs.readdirSync(resolvedPath, { withFileTypes: true })
|
|
197
217
|
.filter(e => !shouldExclude(resolvedPath, path.join(resolvedPath, e.name), ignorePatterns))
|
|
198
218
|
.filter(e => !shouldIgnore(e.name, e.name, ignorePatterns));
|
|
199
|
-
const rootFiles = rootEntries.filter(e => e.isFile())
|
|
219
|
+
const rootFiles = rootEntries.filter(e => e.isFile())
|
|
220
|
+
.map(e => e.name)
|
|
221
|
+
.filter(fileName => !shouldExcludeFile(fileName));
|
|
200
222
|
const rootDirs = rootEntries.filter(e => e.isDirectory()).map(e => e.name);
|
|
201
|
-
let selectedFiles = [];
|
|
202
|
-
let selectedFolders = [];
|
|
203
|
-
let selectedStructure = [];
|
|
204
223
|
if (!isFullMode) {
|
|
205
224
|
// Additive mode for files and folders
|
|
206
225
|
const existingCopyFiles = config.templates[templateName].copy_files || [];
|
|
@@ -209,8 +228,6 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
209
228
|
printNewFiles(newFiles.length, newFiles);
|
|
210
229
|
const addedFiles = await promptNewFiles(newFiles, options);
|
|
211
230
|
selectedFiles = [...existingCopyFiles.filter(cf => !rootDirs.includes(cf.src)).map(cf => cf.src), ...addedFiles];
|
|
212
|
-
// Structure
|
|
213
|
-
selectedStructure = config.templates[templateName].folders?.map(f => f.name) || [];
|
|
214
231
|
// Seed selectedFolders from existing copy_files directory entries
|
|
215
232
|
selectedFolders = existingCopyFiles
|
|
216
233
|
.filter(f => rootDirs.includes(f.src))
|
|
@@ -228,9 +245,9 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
228
245
|
}
|
|
229
246
|
}
|
|
230
247
|
else {
|
|
231
|
-
// Full mode: original behavior
|
|
248
|
+
// Full mode: original behavior - pick up all files like a fresh learn
|
|
232
249
|
if (options.yes || options.json) {
|
|
233
|
-
selectedFiles = rootFiles
|
|
250
|
+
selectedFiles = rootFiles;
|
|
234
251
|
selectedStructure = rootDirs;
|
|
235
252
|
selectedFolders = rootDirs.filter(d => ['APP', 'scripts', 'bin'].some(p => d === p));
|
|
236
253
|
}
|
|
@@ -308,8 +325,24 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
308
325
|
const detectedExecutables = rootFiles
|
|
309
326
|
.filter(file => isExecutable(path.join(resolvedPath, file), file))
|
|
310
327
|
.filter(file => !shouldExcludeFile(file));
|
|
328
|
+
// In additive mode, only add executables that were explicitly selected by the user
|
|
329
|
+
let selectedExecutables = [];
|
|
330
|
+
if (!isFullMode) {
|
|
331
|
+
// In additive mode, only include executables that are in selectedFiles (user explicitly chose them)
|
|
332
|
+
// BUT exclude files that already exist in copy_files with custom settings (chmod, substitute_variables: false)
|
|
333
|
+
const existingCopyFiles = config.templates[templateName].copy_files || [];
|
|
334
|
+
const existingCopySrcs = new Set(existingCopyFiles.map(cf => cf.src));
|
|
335
|
+
const existingCustomSettings = new Set(existingCopyFiles
|
|
336
|
+
.filter(cf => cf.chmod || cf.substitute_variables === false)
|
|
337
|
+
.map(cf => cf.src));
|
|
338
|
+
selectedExecutables = detectedExecutables.filter(exec => selectedFiles.includes(exec) && !existingCustomSettings.has(exec));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// In full mode, include all detected executables (original behavior)
|
|
342
|
+
selectedExecutables = detectedExecutables;
|
|
343
|
+
}
|
|
311
344
|
const existingPostCopy = config.templates[templateName].post_copy || [];
|
|
312
|
-
const post_copy = mergePostCopyFiles(existingPostCopy, fileTemplateConfig.post_copy,
|
|
345
|
+
const post_copy = mergePostCopyFiles(existingPostCopy, fileTemplateConfig.post_copy, selectedExecutables);
|
|
313
346
|
if (post_copy.length > 0) {
|
|
314
347
|
templateConfig.post_copy = post_copy;
|
|
315
348
|
const postCopySrcs = post_copy.map(f => f.src);
|
|
@@ -320,9 +353,7 @@ export async function update(sourcePath, templateName, options = {}) {
|
|
|
320
353
|
name: templateName,
|
|
321
354
|
...templateConfig
|
|
322
355
|
};
|
|
323
|
-
process.stdout.write(JSON.stringify(output, null, 2) + '\n'
|
|
324
|
-
process.exit(0);
|
|
325
|
-
});
|
|
356
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
326
357
|
return;
|
|
327
358
|
}
|
|
328
359
|
config.templates[templateName] = templateConfig;
|
package/dist/config.js
CHANGED
|
@@ -223,6 +223,7 @@ export const DEFAULT_EXCLUDES = [
|
|
|
223
223
|
'dist',
|
|
224
224
|
'build',
|
|
225
225
|
'bin',
|
|
226
|
+
'.vscode',
|
|
226
227
|
'.DS_Store',
|
|
227
228
|
'Thumbs.db',
|
|
228
229
|
];
|
|
@@ -327,6 +328,8 @@ export function shouldExcludeFile(fileName) {
|
|
|
327
328
|
'yarn.lock',
|
|
328
329
|
'pnpm-lock.yaml',
|
|
329
330
|
'composer.lock',
|
|
331
|
+
'post_config.sh',
|
|
332
|
+
'post_config.bat',
|
|
330
333
|
];
|
|
331
334
|
for (const pattern of excludePatterns) {
|
|
332
335
|
if (pattern.startsWith('*')) {
|
package/dist/index.js
CHANGED
|
@@ -12,12 +12,13 @@ import { addCommand } from './commands/addCommand.js';
|
|
|
12
12
|
import { removeCommand } from './commands/removeCommand.js';
|
|
13
13
|
import { defaultPostConfigCommand } from './commands/defaultPostConfigCommand.js';
|
|
14
14
|
import { securityResponseCommand } from './commands/securityResponseCommand.js';
|
|
15
|
+
import { completionCommand } from './commands/completionCommand.js';
|
|
15
16
|
import pkg from '../package.json' with { type: 'json' };
|
|
16
17
|
const program = new Command();
|
|
17
18
|
program
|
|
18
19
|
.name('pt')
|
|
19
20
|
.description('Project Template CLI - Learn project structures and initialize new ones')
|
|
20
|
-
.version(pkg.version, '-v', 'output the version number');
|
|
21
|
+
.version(pkg.version, '-v, --version', 'output the version number');
|
|
21
22
|
program
|
|
22
23
|
.command('learn [path]')
|
|
23
24
|
.description('Learn a project structure from an existing directory')
|
|
@@ -114,4 +115,11 @@ program
|
|
|
114
115
|
.action(async (response) => {
|
|
115
116
|
await securityResponseCommand(response);
|
|
116
117
|
});
|
|
118
|
+
program
|
|
119
|
+
.command('completion [shell]')
|
|
120
|
+
.description('Generate shell completion script')
|
|
121
|
+
.option('--templates', 'Internal helper to list template names for completion')
|
|
122
|
+
.action(async (shellArg, options) => {
|
|
123
|
+
await completionCommand(shellArg, options);
|
|
124
|
+
});
|
|
117
125
|
program.parse(process.argv);
|
package/doc/usage.md
CHANGED
|
@@ -305,3 +305,41 @@ To see your entire configuration (including all templates) in JSON format:
|
|
|
305
305
|
```bash
|
|
306
306
|
pt config --json
|
|
307
307
|
```
|
|
308
|
+
|
|
309
|
+
## Shell Completions
|
|
310
|
+
|
|
311
|
+
`pt` can generate tab-completion scripts for Bash, Zsh, and Fish shells.
|
|
312
|
+
|
|
313
|
+
### Installation
|
|
314
|
+
|
|
315
|
+
#### Bash
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
pt completion bash > /etc/bash_completion.d/pt
|
|
319
|
+
# Or user-local:
|
|
320
|
+
pt completion bash > ~/.local/share/bash-completion/completions/pt
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Zsh
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
pt completion zsh > ~/.zsh/completions/_pt
|
|
327
|
+
# Add to ~/.zshrc:
|
|
328
|
+
# fpath=(~/.zsh/completions $fpath)
|
|
329
|
+
# autoload -U compinit && compinit
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### Fish
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
pt completion fish > ~/.config/fish/completions/pt.fish
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Dynamic Completion
|
|
339
|
+
|
|
340
|
+
Template names auto-complete dynamically from your local `~/.pt/config.yaml` for:
|
|
341
|
+
- `pt init <TAB>`
|
|
342
|
+
- `pt update <TAB>`
|
|
343
|
+
- `pt config <TAB>`
|
|
344
|
+
- `pt remove <TAB>` (and `pt rm <TAB>`)
|
|
345
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garyr/pt-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Project Template CLI - Learn structures and initialize projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"dev": "tsx src/index.ts",
|
|
14
14
|
"test": "node --import tsx --test tests/**/*.test.ts",
|
|
15
15
|
"test:sequential": "node --import tsx --test tests/config.test.ts && node --import tsx --test tests/init.test.ts && node --import tsx --test tests/learn.test.ts && node --import tsx --test tests/substitute.test.ts && node --import tsx --test tests/config-utils.test.ts",
|
|
16
|
+
"completion:bash": "tsx src/index.ts completion bash",
|
|
17
|
+
"completion:zsh": "tsx src/index.ts completion zsh",
|
|
18
|
+
"completion:fish": "tsx src/index.ts completion fish",
|
|
16
19
|
"prepublishOnly": "npm run build",
|
|
17
20
|
"build:linux": "bun build ./src/index.ts --compile --minify --target=bun-linux-x64 --outfile ../BUILD/pt-cli/pt-linux",
|
|
18
21
|
"build:macos": "bun build ./src/index.ts --compile --minify --target=bun-darwin-x64 --outfile ../BUILD/pt-cli/pt-macos",
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import YAML from 'yaml';
|
|
3
|
+
import { getConfigPath } from '../config.js';
|
|
4
|
+
|
|
5
|
+
export function getTemplatesForCompletion(): string[] {
|
|
6
|
+
try {
|
|
7
|
+
const configPath = getConfigPath();
|
|
8
|
+
if (!fs.existsSync(configPath)) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
12
|
+
if (!content.trim()) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const parsed = YAML.parse(content);
|
|
16
|
+
if (!parsed || !parsed.templates || typeof parsed.templates !== 'object') {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return Object.keys(parsed.templates);
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function generateBashCompletion(): string {
|
|
26
|
+
return `# Bash completion for pt
|
|
27
|
+
_pt_completions() {
|
|
28
|
+
local cur prev words cword
|
|
29
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
30
|
+
_init_completion || return
|
|
31
|
+
else
|
|
32
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
33
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
34
|
+
words=("\${COMP_WORDS[@]}")
|
|
35
|
+
cword=$COMP_CWORD
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
local commands="learn update init config ignore variables default-post-config add remove rm security-response completion"
|
|
39
|
+
|
|
40
|
+
# Complete top-level command or global flags
|
|
41
|
+
if [[ $cword -eq 1 ]]; then
|
|
42
|
+
if [[ "$cur" == -* ]]; then
|
|
43
|
+
COMPREPLY=( $(compgen -W "-v --version -h --help" -- "$cur") )
|
|
44
|
+
else
|
|
45
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
46
|
+
fi
|
|
47
|
+
return 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
local cmd="\${words[1]}"
|
|
51
|
+
|
|
52
|
+
case "$cmd" in
|
|
53
|
+
learn)
|
|
54
|
+
if [[ "$cur" == -* ]]; then
|
|
55
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --name --desc --json --allow-untrusted -h --help" -- "$cur") )
|
|
56
|
+
fi
|
|
57
|
+
;;
|
|
58
|
+
update)
|
|
59
|
+
if [[ "$cur" == -* ]]; then
|
|
60
|
+
COMPREPLY=( $(compgen -W "--ignore -y --yes --desc --no-diff -h --help" -- "$cur") )
|
|
61
|
+
elif [[ $cword -eq 2 ]]; then
|
|
62
|
+
local templates
|
|
63
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
64
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
65
|
+
fi
|
|
66
|
+
;;
|
|
67
|
+
init)
|
|
68
|
+
if [[ "$cur" == -* ]]; then
|
|
69
|
+
COMPREPLY=( $(compgen -W "-f --file --skip-post-config --dry-run -y --yes --vars -h --help" -- "$cur") )
|
|
70
|
+
elif [[ $cword -eq 2 ]]; then
|
|
71
|
+
local templates
|
|
72
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
73
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
config)
|
|
77
|
+
if [[ "$cur" == -* ]]; then
|
|
78
|
+
COMPREPLY=( $(compgen -W "--json -h --help" -- "$cur") )
|
|
79
|
+
elif [[ $cword -eq 2 ]]; then
|
|
80
|
+
local templates
|
|
81
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
82
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
83
|
+
fi
|
|
84
|
+
;;
|
|
85
|
+
ignore)
|
|
86
|
+
if [[ "$cur" == -* ]]; then
|
|
87
|
+
COMPREPLY=( $(compgen -W "--set -h --help" -- "$cur") )
|
|
88
|
+
fi
|
|
89
|
+
;;
|
|
90
|
+
variables)
|
|
91
|
+
if [[ "$cur" == -* ]]; then
|
|
92
|
+
COMPREPLY=( $(compgen -W "--set --json --delete -h --help" -- "$cur") )
|
|
93
|
+
fi
|
|
94
|
+
;;
|
|
95
|
+
default-post-config)
|
|
96
|
+
if [[ "$cur" == -* ]]; then
|
|
97
|
+
COMPREPLY=( $(compgen -W "--set --json -h --help" -- "$cur") )
|
|
98
|
+
fi
|
|
99
|
+
;;
|
|
100
|
+
add)
|
|
101
|
+
if [[ "$cur" == -* ]]; then
|
|
102
|
+
COMPREPLY=( $(compgen -W "-f --file -h --help" -- "$cur") )
|
|
103
|
+
fi
|
|
104
|
+
;;
|
|
105
|
+
remove|rm)
|
|
106
|
+
if [[ "$cur" == -* ]]; then
|
|
107
|
+
COMPREPLY=( $(compgen -W "-y --yes -h --help" -- "$cur") )
|
|
108
|
+
elif [[ $cword -eq 2 ]]; then
|
|
109
|
+
local templates
|
|
110
|
+
templates=$(pt completion --templates 2>/dev/null)
|
|
111
|
+
COMPREPLY=( $(compgen -W "$templates" -- "$cur") )
|
|
112
|
+
fi
|
|
113
|
+
;;
|
|
114
|
+
completion)
|
|
115
|
+
if [[ "$cur" == -* ]]; then
|
|
116
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
117
|
+
elif [[ $cword -eq 2 ]]; then
|
|
118
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
119
|
+
fi
|
|
120
|
+
;;
|
|
121
|
+
security-response)
|
|
122
|
+
if [[ "$cur" == -* ]]; then
|
|
123
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
|
|
124
|
+
fi
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
complete -F _pt_completions pt
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function generateZshCompletion(): string {
|
|
134
|
+
return `#compdef pt
|
|
135
|
+
|
|
136
|
+
_pt_templates() {
|
|
137
|
+
local -a templates
|
|
138
|
+
templates=(\${(f)"$(pt completion --templates 2>/dev/null)"})
|
|
139
|
+
if [[ \${#templates[@]} -gt 0 ]]; then
|
|
140
|
+
_describe -t templates 'template' templates
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_pt() {
|
|
145
|
+
local context state state_policy
|
|
146
|
+
typeset -A opt_args
|
|
147
|
+
|
|
148
|
+
_arguments -C \\
|
|
149
|
+
'(-v --version)'{-v,--version}'[output the version number]' \\
|
|
150
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
151
|
+
'1: :->command' \\
|
|
152
|
+
'*:: :->args'
|
|
153
|
+
|
|
154
|
+
case $state in
|
|
155
|
+
command)
|
|
156
|
+
local -a commands
|
|
157
|
+
commands=(
|
|
158
|
+
'learn:Learn a project structure from an existing directory'
|
|
159
|
+
'update:Update an existing template with new structure/files'
|
|
160
|
+
'init:Initialize a new project from a learned template'
|
|
161
|
+
'config:Show current config location and list templates, or export a specific template'
|
|
162
|
+
'ignore:View or set global ignore patterns (comma-separated)'
|
|
163
|
+
'variables:View or set global variables (comma-separated key=value)'
|
|
164
|
+
'default-post-config:View or set default post-config tasks'
|
|
165
|
+
'add:Import/add a template from a JSON string or file'
|
|
166
|
+
'remove:Remove a learned template from the config'
|
|
167
|
+
'rm:Remove a learned template from the config'
|
|
168
|
+
'security-response:Handle security response from GUI'
|
|
169
|
+
'completion:Generate shell completion script'
|
|
170
|
+
)
|
|
171
|
+
_describe -t commands 'pt command' commands
|
|
172
|
+
;;
|
|
173
|
+
args)
|
|
174
|
+
case $words[1] in
|
|
175
|
+
learn)
|
|
176
|
+
_arguments \\
|
|
177
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
178
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
179
|
+
'--name=[Template name]:name:' \\
|
|
180
|
+
'--desc=[Template description]:description:' \\
|
|
181
|
+
'--json[Output template structure as JSON for sharing instead of saving]' \\
|
|
182
|
+
'--allow-untrusted[Bypass the trusted-source check for remote URLs]' \\
|
|
183
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
184
|
+
'1:path:_files -/'
|
|
185
|
+
;;
|
|
186
|
+
update)
|
|
187
|
+
_arguments \\
|
|
188
|
+
'--ignore=[Folder patterns to ignore]:patterns:' \\
|
|
189
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm prompts]' \\
|
|
190
|
+
'--desc=[Template description]:description:' \\
|
|
191
|
+
'--no-diff[Disable additive mode, show full list]' \\
|
|
192
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
193
|
+
'1:template:_pt_templates' \\
|
|
194
|
+
'2:sourcePath:_files -/'
|
|
195
|
+
;;
|
|
196
|
+
init)
|
|
197
|
+
_arguments \\
|
|
198
|
+
'(-f --file)'{-f,--file}'[Initialize directly from a JSON template file]:file:_files' \\
|
|
199
|
+
'--skip-post-config[Skip running post-config tasks]' \\
|
|
200
|
+
'--dry-run[Show what would be created without making changes]' \\
|
|
201
|
+
'(-y --yes)'{-y,--yes}'[Automatically answer yes to prompts]' \\
|
|
202
|
+
'--vars=[Comma-separated key=value variables]:variables:' \\
|
|
203
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
204
|
+
'1:template:_pt_templates' \\
|
|
205
|
+
'2:destPath:_files -/'
|
|
206
|
+
;;
|
|
207
|
+
config)
|
|
208
|
+
_arguments \\
|
|
209
|
+
'--json[Output config or specific template as JSON]' \\
|
|
210
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
211
|
+
'1:template:_pt_templates'
|
|
212
|
+
;;
|
|
213
|
+
ignore)
|
|
214
|
+
_arguments \\
|
|
215
|
+
'--set[Set the ignore patterns to the provided value]' \\
|
|
216
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
217
|
+
'1:patterns:'
|
|
218
|
+
;;
|
|
219
|
+
variables)
|
|
220
|
+
_arguments \\
|
|
221
|
+
'--set[Set the variables to the provided pairs]' \\
|
|
222
|
+
'--json=[Set variables via JSON string or file]:data:' \\
|
|
223
|
+
'--delete=[Delete a specific global variable]:key:' \\
|
|
224
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
225
|
+
'1:pairs:'
|
|
226
|
+
;;
|
|
227
|
+
default-post-config)
|
|
228
|
+
_arguments \\
|
|
229
|
+
'--set[Set the default post-config tasks via JSON]' \\
|
|
230
|
+
'--json=[JSON string or file containing tasks array]:data:' \\
|
|
231
|
+
'(-h --help)'{-h,--help}'[display help for command]'
|
|
232
|
+
;;
|
|
233
|
+
add)
|
|
234
|
+
_arguments \\
|
|
235
|
+
'(-f --file)'{-f,--file}'[Path to JSON file containing template data]:file:_files' \\
|
|
236
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
237
|
+
'1:name:' \\
|
|
238
|
+
'2:json:'
|
|
239
|
+
;;
|
|
240
|
+
remove|rm)
|
|
241
|
+
_arguments \\
|
|
242
|
+
'(-y --yes)'{-y,--yes}'[Automatically confirm removal]' \\
|
|
243
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
244
|
+
'1:template:_pt_templates'
|
|
245
|
+
;;
|
|
246
|
+
security-response)
|
|
247
|
+
_arguments \\
|
|
248
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
249
|
+
'1:response:'
|
|
250
|
+
;;
|
|
251
|
+
completion)
|
|
252
|
+
_arguments \\
|
|
253
|
+
'(-h --help)'{-h,--help}'[display help for command]' \\
|
|
254
|
+
'1:shell:(bash zsh fish)'
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
;;
|
|
258
|
+
esac
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if [[ "$(basename -- "$0")" != "_pt" ]]; then
|
|
262
|
+
compdef _pt pt 2>/dev/null || true
|
|
263
|
+
fi
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function generateFishCompletion(): string {
|
|
268
|
+
return `# Fish completion for pt
|
|
269
|
+
|
|
270
|
+
function __fish_pt_needs_command
|
|
271
|
+
set -l cmd (commandline -opc)
|
|
272
|
+
if [ (count $cmd) -eq 1 ]
|
|
273
|
+
return 0
|
|
274
|
+
end
|
|
275
|
+
return 1
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
function __fish_pt_using_command
|
|
279
|
+
set -l cmd (commandline -opc)
|
|
280
|
+
if [ (count $cmd) -gt 1 ]
|
|
281
|
+
if [ "$argv[1]" = "$cmd[2]" ]
|
|
282
|
+
return 0
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
return 1
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
function __fish_pt_templates
|
|
289
|
+
pt completion --templates 2>/dev/null
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Global options
|
|
293
|
+
complete -c pt -n '__fish_pt_needs_command' -s v -l version -d 'output the version number'
|
|
294
|
+
complete -c pt -n '__fish_pt_needs_command' -s h -l help -d 'display help for command'
|
|
295
|
+
|
|
296
|
+
# Commands
|
|
297
|
+
complete -c pt -n '__fish_pt_needs_command' -a learn -d 'Learn a project structure from an existing directory'
|
|
298
|
+
complete -c pt -n '__fish_pt_needs_command' -a update -d 'Update an existing template with new structure/files'
|
|
299
|
+
complete -c pt -n '__fish_pt_needs_command' -a init -d 'Initialize a new project from a learned template'
|
|
300
|
+
complete -c pt -n '__fish_pt_needs_command' -a config -d 'Show current config location and list templates, or export a specific template'
|
|
301
|
+
complete -c pt -n '__fish_pt_needs_command' -a ignore -d 'View or set global ignore patterns (comma-separated)'
|
|
302
|
+
complete -c pt -n '__fish_pt_needs_command' -a variables -d 'View or set global variables (comma-separated key=value)'
|
|
303
|
+
complete -c pt -n '__fish_pt_needs_command' -a default-post-config -d 'View or set default post-config tasks'
|
|
304
|
+
complete -c pt -n '__fish_pt_needs_command' -a add -d 'Import/add a template from a JSON string or file'
|
|
305
|
+
complete -c pt -n '__fish_pt_needs_command' -a remove -d 'Remove a learned template from the config'
|
|
306
|
+
complete -c pt -n '__fish_pt_needs_command' -a rm -d 'Remove a learned template from the config'
|
|
307
|
+
complete -c pt -n '__fish_pt_needs_command' -a security-response -d 'Handle security response from GUI'
|
|
308
|
+
complete -c pt -n '__fish_pt_needs_command' -a completion -d 'Generate shell completion script'
|
|
309
|
+
|
|
310
|
+
# learn
|
|
311
|
+
complete -c pt -n '__fish_pt_using_command learn' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
312
|
+
complete -c pt -n '__fish_pt_using_command learn' -s y -l yes -d 'Automatically confirm prompts'
|
|
313
|
+
complete -c pt -n '__fish_pt_using_command learn' -l name -d 'Template name (skip prompt)'
|
|
314
|
+
complete -c pt -n '__fish_pt_using_command learn' -l desc -d 'Template description (skip prompt)'
|
|
315
|
+
complete -c pt -n '__fish_pt_using_command learn' -l json -d 'Output template structure as JSON for sharing instead of saving'
|
|
316
|
+
complete -c pt -n '__fish_pt_using_command learn' -l allow-untrusted -d 'Bypass the trusted-source check for remote URLs'
|
|
317
|
+
|
|
318
|
+
# update
|
|
319
|
+
complete -c pt -n '__fish_pt_using_command update' -a '(__fish_pt_templates)' -d 'Template name'
|
|
320
|
+
complete -c pt -n '__fish_pt_using_command update' -l ignore -d 'Folder patterns to ignore (comma-separated)'
|
|
321
|
+
complete -c pt -n '__fish_pt_using_command update' -s y -l yes -d 'Automatically confirm prompts'
|
|
322
|
+
complete -c pt -n '__fish_pt_using_command update' -l desc -d 'Template description (skip prompt)'
|
|
323
|
+
complete -c pt -n '__fish_pt_using_command update' -l no-diff -d 'Disable additive mode, show full list'
|
|
324
|
+
|
|
325
|
+
# init
|
|
326
|
+
complete -c pt -n '__fish_pt_using_command init' -a '(__fish_pt_templates)' -d 'Template name'
|
|
327
|
+
complete -c pt -n '__fish_pt_using_command init' -s f -l file -d 'Initialize directly from a JSON template file without adding it to local config'
|
|
328
|
+
complete -c pt -n '__fish_pt_using_command init' -l skip-post-config -d 'Skip running post-config tasks'
|
|
329
|
+
complete -c pt -n '__fish_pt_using_command init' -l dry-run -d 'Show what would be created without making changes'
|
|
330
|
+
complete -c pt -n '__fish_pt_using_command init' -s y -l yes -d 'Automatically answer yes to prompts'
|
|
331
|
+
complete -c pt -n '__fish_pt_using_command init' -l vars -d 'Comma-separated key=value variables'
|
|
332
|
+
|
|
333
|
+
# config
|
|
334
|
+
complete -c pt -n '__fish_pt_using_command config' -a '(__fish_pt_templates)' -d 'Template name'
|
|
335
|
+
complete -c pt -n '__fish_pt_using_command config' -l json -d 'Output config or specific template as JSON'
|
|
336
|
+
|
|
337
|
+
# ignore
|
|
338
|
+
complete -c pt -n '__fish_pt_using_command ignore' -l set -d 'Set the ignore patterns to the provided value'
|
|
339
|
+
|
|
340
|
+
# variables
|
|
341
|
+
complete -c pt -n '__fish_pt_using_command variables' -l set -d 'Set the variables to the provided pairs'
|
|
342
|
+
complete -c pt -n '__fish_pt_using_command variables' -l json -d 'Set variables via JSON string or file'
|
|
343
|
+
complete -c pt -n '__fish_pt_using_command variables' -l delete -d 'Delete a specific global variable'
|
|
344
|
+
|
|
345
|
+
# default-post-config
|
|
346
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l set -d 'Set the default post-config tasks via JSON'
|
|
347
|
+
complete -c pt -n '__fish_pt_using_command default-post-config' -l json -d 'JSON string or file containing tasks array'
|
|
348
|
+
|
|
349
|
+
# add
|
|
350
|
+
complete -c pt -n '__fish_pt_using_command add' -s f -l file -d 'Path to JSON file containing template data'
|
|
351
|
+
|
|
352
|
+
# remove / rm
|
|
353
|
+
complete -c pt -n '__fish_pt_using_command remove' -a '(__fish_pt_templates)' -d 'Template name'
|
|
354
|
+
complete -c pt -n '__fish_pt_using_command remove' -s y -l yes -d 'Automatically confirm removal'
|
|
355
|
+
complete -c pt -n '__fish_pt_using_command rm' -a '(__fish_pt_templates)' -d 'Template name'
|
|
356
|
+
complete -c pt -n '__fish_pt_using_command rm' -s y -l yes -d 'Automatically confirm removal'
|
|
357
|
+
|
|
358
|
+
# completion
|
|
359
|
+
complete -c pt -n '__fish_pt_using_command completion' -a 'bash zsh fish' -d 'Shell'
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function generateShellScript(shell: string): string {
|
|
364
|
+
switch (shell.toLowerCase()) {
|
|
365
|
+
case 'bash':
|
|
366
|
+
return generateBashCompletion();
|
|
367
|
+
case 'zsh':
|
|
368
|
+
return generateZshCompletion();
|
|
369
|
+
case 'fish':
|
|
370
|
+
return generateFishCompletion();
|
|
371
|
+
default: {
|
|
372
|
+
const supported = ['bash', 'zsh', 'fish'];
|
|
373
|
+
throw new Error(`Unsupported shell: ${shell}. Supported: ${supported.join(', ')}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function generateCompletion(shell: string): string {
|
|
379
|
+
return generateShellScript(shell);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export async function completionCommand(shellArg?: string, options?: { templates?: boolean }): Promise<void> {
|
|
383
|
+
if (options?.templates || shellArg === '--templates' || shellArg === '_templates') {
|
|
384
|
+
const templates = getTemplatesForCompletion();
|
|
385
|
+
if (templates.length > 0) {
|
|
386
|
+
console.log(templates.join('\n'));
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!shellArg) {
|
|
392
|
+
console.error('Error: Please specify a shell (bash, zsh, fish).');
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
const script = generateCompletion(shellArg);
|
|
398
|
+
console.log(script);
|
|
399
|
+
} catch (err: any) {
|
|
400
|
+
console.error(err.message || String(err));
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|