fscr 6.2.3 → 7.3.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 +48 -30
- package/dist/index.js +502 -185
- package/dist/lib/auth/auth-conf.js +49 -45
- package/dist/lib/cache/README.md +341 -0
- package/dist/lib/cache/cli.js +152 -0
- package/dist/lib/cache/file-watcher.js +193 -0
- package/dist/lib/cache/index.js +422 -0
- package/dist/lib/cache/monitor.js +224 -0
- package/dist/lib/commands/doctor.js +225 -0
- package/dist/lib/completions/completion.js +342 -0
- package/dist/lib/completions/generator.js +152 -0
- package/dist/lib/completions/scripts/bash.sh +108 -0
- package/dist/lib/completions/scripts/fish.sh +105 -0
- package/dist/lib/completions/scripts/powershell.ps1 +168 -0
- package/dist/lib/completions/scripts/zsh.sh +124 -0
- package/dist/lib/diagnostics/cache.js +121 -0
- package/dist/lib/diagnostics/fileSystem.js +236 -0
- package/dist/lib/diagnostics/gitCheck.js +41 -0
- package/dist/lib/diagnostics/nodeVersion.js +68 -0
- package/dist/lib/diagnostics/packageManager.js +64 -0
- package/dist/lib/diagnostics/performance.js +141 -0
- package/dist/lib/encryption/decryptConfig.js +3 -2
- package/dist/lib/encryption/encryption.js +153 -113
- package/dist/lib/generators/generateFScripts.js +16 -13
- package/dist/lib/generators/generateToc.js +23 -14
- package/dist/lib/generators/index.js +1 -1
- package/dist/lib/git/pub.js +27 -31
- package/dist/lib/git/taskRunner.js +79 -69
- package/dist/lib/git/validateNotDev.js +65 -54
- package/dist/lib/optionList.js +69 -57
- package/dist/lib/parsers/parseScriptsMd.cached.js +208 -0
- package/dist/lib/parsers/parseScriptsMd.js +88 -79
- package/dist/lib/parsers/parseScriptsPackage.js +4 -3
- package/dist/lib/performance/cache.js +199 -0
- package/dist/lib/performance/lazy-loader.js +189 -0
- package/dist/lib/performance/monitor.js +303 -0
- package/dist/lib/plugins/deployment/index.js +113 -0
- package/dist/lib/plugins/hooks.js +17 -0
- package/dist/lib/plugins/loader.js +91 -0
- package/dist/lib/plugins/task-notifier/index.js +72 -0
- package/dist/lib/release/bump.js +51 -43
- package/dist/lib/release/commitWithMessage.js +80 -52
- package/dist/lib/release/publish.js +19 -14
- package/dist/lib/release/pushToGit.js +40 -31
- package/dist/lib/release/releasenotes.js +116 -97
- package/dist/lib/release/seeChangedFiles.js +68 -60
- package/dist/lib/release/sort.js +200 -116
- package/dist/lib/release/tree.js +161 -147
- package/dist/lib/release/validateNotDev.js +52 -44
- package/dist/lib/running/index.js +1 -1
- package/dist/lib/running/runCLICommand.js +41 -31
- package/dist/lib/running/runParallel.js +61 -59
- package/dist/lib/running/runSequence.js +55 -53
- package/dist/lib/startScripts.js +129 -114
- package/dist/lib/taskList.js +99 -84
- package/dist/lib/test-files/.fscripts.md +113 -0
- package/dist/lib/test-files/.fscripts.test.md +103 -0
- package/dist/lib/test-files/.fscriptsb.md +107 -0
- package/dist/lib/test-files/.mdtest.md +40 -0
- package/dist/lib/test-files/consoleSample.js +17 -0
- package/dist/lib/test-files/inputSample.js +20 -0
- package/dist/lib/test-files/testConsole.js +1 -0
- package/dist/lib/test-files/testInput.js +2 -0
- package/dist/lib/upgradePackages.js +56 -46
- package/dist/lib/utils/clear.js +16 -13
- package/dist/lib/utils/console.js +27 -21
- package/dist/lib/utils/encryption.js +55 -13
- package/dist/lib/utils/hash.js +128 -0
- package/dist/lib/utils/helpers.js +153 -142
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/prompt.js +24 -29
- package/package.json +20 -32
- package/dist/lib/codemod/arrow.js +0 -13
- package/dist/lib/codemod/arrow2.js +0 -67
- package/dist/lib/codemod/funcs.js +0 -25
- package/dist/lib/codemod/removeConsole.js +0 -12
- package/dist/lib/codemod/test.js +0 -8
- package/dist/lib/components/App.js +0 -64
- package/dist/lib/components/Selector.js +0 -133
- package/dist/lib/components/TabChanger.js +0 -113
- package/dist/lib/components/Table.js +0 -177
- package/dist/lib/components/Tabs.js +0 -221
- package/dist/lib/generateFScripts.js +0 -25
- package/dist/lib/generateToc.js +0 -30
- package/dist/lib/helpers.js +0 -191
- package/dist/lib/parseScriptsMd.js +0 -85
- package/dist/lib/parseScriptsPackage.js +0 -9
- package/dist/lib/release/index.js +0 -4
- package/dist/lib/run/lib.js +0 -454
- package/dist/lib/run/main-p.js +0 -59
- package/dist/lib/run/main-s.js +0 -56
- package/dist/lib/run/parse-cli-args.js +0 -222
- package/dist/lib/run/run-p.js +0 -30
- package/dist/lib/run/run-s.js +0 -57
- package/dist/lib/runCLICommand.js +0 -30
- package/dist/lib/runParallel.js +0 -20
- package/dist/lib/runSequence.js +0 -38
- package/dist/lib/taskListAutoComplete.js +0 -15
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import inquirer from "inquirer";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect the user's current shell
|
|
14
|
+
*/
|
|
15
|
+
export function detectShell() {
|
|
16
|
+
// Check SHELL environment variable
|
|
17
|
+
const shellEnv = process.env.SHELL;
|
|
18
|
+
if (shellEnv) {
|
|
19
|
+
const shellName = path.basename(shellEnv);
|
|
20
|
+
if (["bash", "zsh", "fish"].includes(shellName)) {
|
|
21
|
+
return shellName;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check for PowerShell on Windows
|
|
26
|
+
if (process.platform === "win32") {
|
|
27
|
+
return "powershell";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Default to bash if unable to detect
|
|
31
|
+
return "bash";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get shell configuration file path
|
|
36
|
+
*/
|
|
37
|
+
export function getShellConfigPath(shell) {
|
|
38
|
+
const homeDir = os.homedir();
|
|
39
|
+
|
|
40
|
+
const configPaths = {
|
|
41
|
+
bash: path.join(homeDir, ".bashrc"),
|
|
42
|
+
zsh: path.join(homeDir, ".zshrc"),
|
|
43
|
+
fish: path.join(homeDir, ".config", "fish", "config.fish"),
|
|
44
|
+
powershell: path.join(
|
|
45
|
+
homeDir,
|
|
46
|
+
"Documents",
|
|
47
|
+
"WindowsPowerShell",
|
|
48
|
+
"Microsoft.PowerShell_profile.ps1"
|
|
49
|
+
)
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return configPaths[shell] || configPaths.bash;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the completion script path for a shell
|
|
57
|
+
*/
|
|
58
|
+
export function getCompletionScriptPath(shell) {
|
|
59
|
+
const scriptsDir = path.join(__dirname, "scripts");
|
|
60
|
+
const scriptFiles = {
|
|
61
|
+
bash: path.join(scriptsDir, "bash.sh"),
|
|
62
|
+
zsh: path.join(scriptsDir, "zsh.sh"),
|
|
63
|
+
fish: path.join(scriptsDir, "fish.sh"),
|
|
64
|
+
powershell: path.join(scriptsDir, "powershell.ps1")
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return scriptFiles[shell];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Install completion script for a specific shell
|
|
72
|
+
*/
|
|
73
|
+
export async function installCompletion(shell, options = {}) {
|
|
74
|
+
const { force = false, silent = false } = options;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const configPath = getShellConfigPath(shell);
|
|
78
|
+
const scriptPath = getCompletionScriptPath(shell);
|
|
79
|
+
|
|
80
|
+
// Check if script exists
|
|
81
|
+
if (!fs.existsSync(scriptPath)) {
|
|
82
|
+
throw new Error(`Completion script not found for ${shell}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Read the completion script
|
|
86
|
+
const completionScript = fs.readFileSync(scriptPath, "utf8");
|
|
87
|
+
|
|
88
|
+
// For fish, create completions directory
|
|
89
|
+
if (shell === "fish") {
|
|
90
|
+
const fishCompDir = path.join(os.homedir(), ".config", "fish", "completions");
|
|
91
|
+
await fs.ensureDir(fishCompDir);
|
|
92
|
+
|
|
93
|
+
const fishCompPath = path.join(fishCompDir, "fsr.fish");
|
|
94
|
+
await fs.writeFile(fishCompPath, completionScript);
|
|
95
|
+
|
|
96
|
+
if (!silent) {
|
|
97
|
+
console.log(chalk.green("✅ Installed Fish completions to"), fishCompPath);
|
|
98
|
+
}
|
|
99
|
+
return fishCompPath;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// For other shells, append to config file
|
|
103
|
+
await fs.ensureFile(configPath);
|
|
104
|
+
const currentConfig = await fs.readFile(configPath, "utf8");
|
|
105
|
+
|
|
106
|
+
// Check if already installed
|
|
107
|
+
const marker = "# FSCR completion";
|
|
108
|
+
if (currentConfig.includes(marker) && !force) {
|
|
109
|
+
if (!silent) {
|
|
110
|
+
console.log(chalk.yellow("⚠️ Completions already installed"));
|
|
111
|
+
console.log(chalk.dim(` Use --force to reinstall`));
|
|
112
|
+
}
|
|
113
|
+
return configPath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Prepare completion block
|
|
117
|
+
const completionBlock = `
|
|
118
|
+
${marker}
|
|
119
|
+
${completionScript}
|
|
120
|
+
# End FSCR completion
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
// Append or replace completion block
|
|
124
|
+
let newConfig;
|
|
125
|
+
if (currentConfig.includes(marker)) {
|
|
126
|
+
// Replace existing block
|
|
127
|
+
newConfig = currentConfig.replace(
|
|
128
|
+
/# FSCR completion[\s\S]*?# End FSCR completion\n?/,
|
|
129
|
+
completionBlock
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
// Append new block
|
|
133
|
+
newConfig = currentConfig + "\n" + completionBlock;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await fs.writeFile(configPath, newConfig);
|
|
137
|
+
|
|
138
|
+
if (!silent) {
|
|
139
|
+
console.log(chalk.green("✅ Installed completions to"), configPath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return configPath;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
throw new Error(`Failed to install ${shell} completions: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Uninstall completion script
|
|
150
|
+
*/
|
|
151
|
+
export async function uninstallCompletion(shell, options = {}) {
|
|
152
|
+
const { silent = false } = options;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const configPath = getShellConfigPath(shell);
|
|
156
|
+
|
|
157
|
+
if (shell === "fish") {
|
|
158
|
+
const fishCompPath = path.join(
|
|
159
|
+
os.homedir(),
|
|
160
|
+
".config",
|
|
161
|
+
"fish",
|
|
162
|
+
"completions",
|
|
163
|
+
"fsr.fish"
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (fs.existsSync(fishCompPath)) {
|
|
167
|
+
await fs.remove(fishCompPath);
|
|
168
|
+
if (!silent) {
|
|
169
|
+
console.log(chalk.green("✅ Removed Fish completions"));
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
if (!silent) {
|
|
173
|
+
console.log(chalk.yellow("⚠️ No Fish completions found"));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// For other shells, remove from config file
|
|
180
|
+
if (!fs.existsSync(configPath)) {
|
|
181
|
+
if (!silent) {
|
|
182
|
+
console.log(chalk.yellow(`⚠️ Config file not found: ${configPath}`));
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const currentConfig = await fs.readFile(configPath, "utf8");
|
|
188
|
+
const marker = "# FSCR completion";
|
|
189
|
+
|
|
190
|
+
if (!currentConfig.includes(marker)) {
|
|
191
|
+
if (!silent) {
|
|
192
|
+
console.log(chalk.yellow("⚠️ No completions found to uninstall"));
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Remove completion block
|
|
198
|
+
const newConfig = currentConfig.replace(
|
|
199
|
+
/# FSCR completion[\s\S]*?# End FSCR completion\n?/,
|
|
200
|
+
""
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await fs.writeFile(configPath, newConfig);
|
|
204
|
+
|
|
205
|
+
if (!silent) {
|
|
206
|
+
console.log(chalk.green("✅ Removed completions from"), configPath);
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
throw new Error(`Failed to uninstall ${shell} completions: ${error.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Show completion status for all shells
|
|
215
|
+
*/
|
|
216
|
+
export async function completionStatus() {
|
|
217
|
+
const shells = ["bash", "zsh", "fish", "powershell"];
|
|
218
|
+
const currentShell = detectShell();
|
|
219
|
+
|
|
220
|
+
console.log(chalk.bold("\n📋 Completion Status\n"));
|
|
221
|
+
|
|
222
|
+
for (const shell of shells) {
|
|
223
|
+
const isCurrent = shell === currentShell;
|
|
224
|
+
const marker = isCurrent ? chalk.green("●") : chalk.dim("○");
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const configPath = getShellConfigPath(shell);
|
|
228
|
+
let installed = false;
|
|
229
|
+
|
|
230
|
+
if (shell === "fish") {
|
|
231
|
+
const fishCompPath = path.join(
|
|
232
|
+
os.homedir(),
|
|
233
|
+
".config",
|
|
234
|
+
"fish",
|
|
235
|
+
"completions",
|
|
236
|
+
"fsr.fish"
|
|
237
|
+
);
|
|
238
|
+
installed = fs.existsSync(fishCompPath);
|
|
239
|
+
} else {
|
|
240
|
+
if (fs.existsSync(configPath)) {
|
|
241
|
+
const config = await fs.readFile(configPath, "utf8");
|
|
242
|
+
installed = config.includes("# FSCR completion");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const status = installed ? chalk.green("✅ Installed") : chalk.dim("Not installed");
|
|
247
|
+
const current = isCurrent ? chalk.yellow(" (current)") : "";
|
|
248
|
+
|
|
249
|
+
console.log(`${marker} ${chalk.bold(shell.padEnd(12))} ${status}${current}`);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log(`${marker} ${chalk.bold(shell.padEnd(12))} ${chalk.red("Error")}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Main completion command handler
|
|
260
|
+
*/
|
|
261
|
+
export default async function completion(argv) {
|
|
262
|
+
// Get the action from positional argument or from argv._[1]
|
|
263
|
+
const command = argv.action || argv._[1];
|
|
264
|
+
const shell = argv.shell || detectShell();
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
switch (command) {
|
|
268
|
+
case "install":
|
|
269
|
+
await installCompletion(shell, { force: argv.force });
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(chalk.cyan("🎉 Completions installed successfully!"));
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(chalk.dim("To activate completions, restart your shell or run:"));
|
|
274
|
+
console.log(
|
|
275
|
+
chalk.yellow(
|
|
276
|
+
shell === "fish"
|
|
277
|
+
? " exec fish"
|
|
278
|
+
: shell === "powershell"
|
|
279
|
+
? " . $PROFILE"
|
|
280
|
+
: ` source ${getShellConfigPath(shell)}`
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
console.log();
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
case "uninstall":
|
|
287
|
+
await uninstallCompletion(shell);
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(chalk.cyan("✅ Completions uninstalled successfully!"));
|
|
290
|
+
console.log();
|
|
291
|
+
break;
|
|
292
|
+
|
|
293
|
+
case "status":
|
|
294
|
+
await completionStatus();
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case "generate":
|
|
298
|
+
// Generate completion script without installing
|
|
299
|
+
const scriptPath = getCompletionScriptPath(shell);
|
|
300
|
+
if (!fs.existsSync(scriptPath)) {
|
|
301
|
+
throw new Error(`Completion script not found for ${shell}`);
|
|
302
|
+
}
|
|
303
|
+
const script = fs.readFileSync(scriptPath, "utf8");
|
|
304
|
+
console.log(script);
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
default:
|
|
308
|
+
// Interactive installation
|
|
309
|
+
const { confirmInstall } = await inquirer.prompt([
|
|
310
|
+
{
|
|
311
|
+
type: "confirm",
|
|
312
|
+
name: "confirmInstall",
|
|
313
|
+
message: `Install completions for ${chalk.cyan(shell)}?`,
|
|
314
|
+
default: true
|
|
315
|
+
}
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
if (confirmInstall) {
|
|
319
|
+
await installCompletion(shell, { force: argv.force });
|
|
320
|
+
console.log();
|
|
321
|
+
console.log(chalk.cyan("🎉 Completions installed successfully!"));
|
|
322
|
+
console.log();
|
|
323
|
+
console.log(chalk.dim("To activate completions, restart your shell or run:"));
|
|
324
|
+
console.log(
|
|
325
|
+
chalk.yellow(
|
|
326
|
+
shell === "fish"
|
|
327
|
+
? " exec fish"
|
|
328
|
+
: shell === "powershell"
|
|
329
|
+
? " . $PROFILE"
|
|
330
|
+
: ` source ${getShellConfigPath(shell)}`
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
console.log();
|
|
334
|
+
} else {
|
|
335
|
+
console.log(chalk.yellow("Installation cancelled"));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error(chalk.red("Error:"), error.message);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import parseScriptFile from "../parsers/parseScriptsMd.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache for parsed tasks to avoid re-parsing on every completion
|
|
7
|
+
*/
|
|
8
|
+
let taskCache = null;
|
|
9
|
+
let cacheTime = 0;
|
|
10
|
+
const CACHE_TTL = 5000; // 5 seconds
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get all available tasks from fscripts.md with caching
|
|
14
|
+
*/
|
|
15
|
+
export async function getAvailableTasks(forceRefresh = false) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
|
|
18
|
+
// Return cached tasks if still valid
|
|
19
|
+
if (!forceRefresh && taskCache && now - cacheTime < CACHE_TTL) {
|
|
20
|
+
return taskCache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const { allTasks } = await parseScriptFile();
|
|
25
|
+
taskCache = allTasks.map((task) => ({
|
|
26
|
+
name: task.name,
|
|
27
|
+
description: task.description || "",
|
|
28
|
+
lang: task.lang || "bash"
|
|
29
|
+
}));
|
|
30
|
+
cacheTime = now;
|
|
31
|
+
return taskCache;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// If parsing fails, return empty array
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get all available commands
|
|
40
|
+
*/
|
|
41
|
+
export function getAvailableCommands() {
|
|
42
|
+
return [
|
|
43
|
+
{ name: "start", description: "Choose category then task to run" },
|
|
44
|
+
{ name: "run", description: "Run a specific task" },
|
|
45
|
+
{ name: "list", description: "Select any task with text autocompletion" },
|
|
46
|
+
{ name: "scripts", description: "Choose a script from package.json" },
|
|
47
|
+
{ name: "run-s", description: "Run tasks in sequence" },
|
|
48
|
+
{ name: "run-p", description: "Run tasks in parallel" },
|
|
49
|
+
{ name: "bump", description: "Bump package.json version" },
|
|
50
|
+
{ name: "upgrade", description: "Upgrade packages" },
|
|
51
|
+
{ name: "branch", description: "Create new branch" },
|
|
52
|
+
{ name: "remote", description: "Get remote configuration" },
|
|
53
|
+
{ name: "encryption", description: "Encrypt/Decrypt files" },
|
|
54
|
+
{ name: "clear", description: "Clear recent task history" },
|
|
55
|
+
{ name: "generate", description: "Generate sample fscripts.md" },
|
|
56
|
+
{ name: "toc", description: "Generate table of contents" },
|
|
57
|
+
{ name: "completion", description: "Manage shell completions" }
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get completion subcommands
|
|
63
|
+
*/
|
|
64
|
+
export function getCompletionSubcommands() {
|
|
65
|
+
return [
|
|
66
|
+
{ name: "install", description: "Install completions" },
|
|
67
|
+
{ name: "uninstall", description: "Uninstall completions" },
|
|
68
|
+
{ name: "status", description: "Show completion status" },
|
|
69
|
+
{ name: "generate", description: "Generate completion script" }
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate task names for shell completion
|
|
75
|
+
*/
|
|
76
|
+
export async function generateTaskNames() {
|
|
77
|
+
const tasks = await getAvailableTasks();
|
|
78
|
+
return tasks.map((t) => t.name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate bash completion data
|
|
83
|
+
*/
|
|
84
|
+
export async function generateBashCompletionData() {
|
|
85
|
+
const tasks = await getAvailableTasks();
|
|
86
|
+
const commands = getAvailableCommands();
|
|
87
|
+
const completionCmds = getCompletionSubcommands();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
commands: commands.map((c) => c.name),
|
|
91
|
+
tasks: tasks.map((t) => t.name),
|
|
92
|
+
completionSubcommands: completionCmds.map((c) => c.name)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate zsh completion data
|
|
98
|
+
*/
|
|
99
|
+
export async function generateZshCompletionData() {
|
|
100
|
+
const tasks = await getAvailableTasks();
|
|
101
|
+
const commands = getAvailableCommands();
|
|
102
|
+
const completionCmds = getCompletionSubcommands();
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
commands: commands.map((c) => `${c.name}:${c.description}`),
|
|
106
|
+
tasks: tasks.map((t) => {
|
|
107
|
+
const desc = t.description.replace(/[:\n]/g, " ").trim();
|
|
108
|
+
return `${t.name}:${desc || "Run task"}`;
|
|
109
|
+
}),
|
|
110
|
+
completionSubcommands: completionCmds.map((c) => `${c.name}:${c.description}`)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate fish completion data
|
|
116
|
+
*/
|
|
117
|
+
export async function generateFishCompletionData() {
|
|
118
|
+
const tasks = await getAvailableTasks();
|
|
119
|
+
const commands = getAvailableCommands();
|
|
120
|
+
const completionCmds = getCompletionSubcommands();
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
commands: commands.map((c) => ({
|
|
124
|
+
name: c.name,
|
|
125
|
+
description: c.description
|
|
126
|
+
})),
|
|
127
|
+
tasks: tasks.map((t) => ({
|
|
128
|
+
name: t.name,
|
|
129
|
+
description: t.description || "Run task"
|
|
130
|
+
})),
|
|
131
|
+
completionSubcommands: completionCmds.map((c) => ({
|
|
132
|
+
name: c.name,
|
|
133
|
+
description: c.description
|
|
134
|
+
}))
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Export tasks for completion (used by shell scripts)
|
|
140
|
+
*/
|
|
141
|
+
export async function exportTasksForCompletion() {
|
|
142
|
+
const tasks = await getAvailableTasks();
|
|
143
|
+
return tasks.map((t) => t.name).join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear the task cache (useful when fscripts.md is updated)
|
|
148
|
+
*/
|
|
149
|
+
export function clearTaskCache() {
|
|
150
|
+
taskCache = null;
|
|
151
|
+
cacheTime = 0;
|
|
152
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# FSCR Bash completion script
|
|
3
|
+
|
|
4
|
+
_fscr_get_tasks() {
|
|
5
|
+
# Try to get tasks from fscripts.md
|
|
6
|
+
if command -v node >/dev/null 2>&1; then
|
|
7
|
+
local tasks_output
|
|
8
|
+
tasks_output=$(node -e "
|
|
9
|
+
import('$FSCR_COMPLETION_HELPER').then(async (m) => {
|
|
10
|
+
const tasks = await m.generateTaskNames();
|
|
11
|
+
console.log(tasks.join(' '));
|
|
12
|
+
}).catch(() => {});
|
|
13
|
+
" 2>/dev/null)
|
|
14
|
+
|
|
15
|
+
if [ -n "$tasks_output" ]; then
|
|
16
|
+
echo "$tasks_output"
|
|
17
|
+
return
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Fallback: parse fscripts.md directly
|
|
22
|
+
if [ -f "fscripts.md" ]; then
|
|
23
|
+
grep -E "^## " fscripts.md | sed 's/^## //' | tr '\n' ' '
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_fscr_completion() {
|
|
28
|
+
local cur prev words cword
|
|
29
|
+
_init_completion || return
|
|
30
|
+
|
|
31
|
+
# Main commands
|
|
32
|
+
local commands="start run list scripts run-s run-p bump upgrade branch remote encryption clear generate toc completion"
|
|
33
|
+
|
|
34
|
+
# Completion subcommands
|
|
35
|
+
local completion_cmds="install uninstall status generate"
|
|
36
|
+
|
|
37
|
+
# Get current word and previous word
|
|
38
|
+
local cur="${COMP_WORDS[COMP_CWORD]}"
|
|
39
|
+
local prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
40
|
+
|
|
41
|
+
# Get the main command (first argument after fsr)
|
|
42
|
+
local cmd=""
|
|
43
|
+
if [ ${COMP_CWORD} -gt 0 ]; then
|
|
44
|
+
cmd="${COMP_WORDS[1]}"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
case "${prev}" in
|
|
48
|
+
fsr|fsr)
|
|
49
|
+
# Complete main commands
|
|
50
|
+
COMPREPLY=($(compgen -W "${commands}" -- "${cur}"))
|
|
51
|
+
return 0
|
|
52
|
+
;;
|
|
53
|
+
run|run-s|run-p)
|
|
54
|
+
# Complete task names
|
|
55
|
+
local tasks=$(_fscr_get_tasks)
|
|
56
|
+
COMPREPLY=($(compgen -W "${tasks}" -- "${cur}"))
|
|
57
|
+
return 0
|
|
58
|
+
;;
|
|
59
|
+
completion)
|
|
60
|
+
# Complete completion subcommands
|
|
61
|
+
COMPREPLY=($(compgen -W "${completion_cmds}" -- "${cur}"))
|
|
62
|
+
return 0
|
|
63
|
+
;;
|
|
64
|
+
install|uninstall)
|
|
65
|
+
# Complete shell names
|
|
66
|
+
if [ "${cmd}" = "completion" ]; then
|
|
67
|
+
COMPREPLY=($(compgen -W "bash zsh fish powershell" -- "${cur}"))
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
;;
|
|
71
|
+
--shell)
|
|
72
|
+
# Complete shell names for --shell flag
|
|
73
|
+
COMPREPLY=($(compgen -W "bash zsh fish powershell" -- "${cur}"))
|
|
74
|
+
return 0
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# Handle flags
|
|
79
|
+
case "${cur}" in
|
|
80
|
+
-*)
|
|
81
|
+
case "${cmd}" in
|
|
82
|
+
completion)
|
|
83
|
+
COMPREPLY=($(compgen -W "--shell --force --help" -- "${cur}"))
|
|
84
|
+
;;
|
|
85
|
+
run)
|
|
86
|
+
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
return 0
|
|
93
|
+
;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
# Continue completing task names for run-s and run-p
|
|
97
|
+
if [ "${cmd}" = "run-s" ] || [ "${cmd}" = "run-p" ]; then
|
|
98
|
+
local tasks=$(_fscr_get_tasks)
|
|
99
|
+
COMPREPLY=($(compgen -W "${tasks}" -- "${cur}"))
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
return 0
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Register completion for both fsr and fsr
|
|
107
|
+
complete -F _fscr_completion fsr
|
|
108
|
+
complete -F _fscr_completion fsr
|