figmanage 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/completion.d.ts +7 -0
- package/dist/cli/completion.js +160 -0
- package/dist/cli/format.js +147 -2
- package/dist/cli/index.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Create the `completion` command. Needs the parent program reference
|
|
4
|
+
* so it can introspect the full command tree at runtime.
|
|
5
|
+
*/
|
|
6
|
+
export declare function completionCommand(program: Command): Command;
|
|
7
|
+
//# sourceMappingURL=completion.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Introspect a Commander program and extract its command tree.
|
|
4
|
+
* Returns a map of command names to their subcommand names.
|
|
5
|
+
* Top-level commands without subcommands (like login, whoami) get an empty array.
|
|
6
|
+
*/
|
|
7
|
+
function extractCommandTree(program) {
|
|
8
|
+
const tree = new Map();
|
|
9
|
+
for (const cmd of program.commands) {
|
|
10
|
+
const subs = cmd.commands.map((sub) => sub.name());
|
|
11
|
+
tree.set(cmd.name(), subs);
|
|
12
|
+
}
|
|
13
|
+
return tree;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract all option flags from a command (including inherited ones).
|
|
17
|
+
* Returns long-form flags like --json, --help.
|
|
18
|
+
*/
|
|
19
|
+
function extractOptions(cmd) {
|
|
20
|
+
const flags = [];
|
|
21
|
+
for (const opt of cmd.options) {
|
|
22
|
+
if (opt.long)
|
|
23
|
+
flags.push(opt.long);
|
|
24
|
+
}
|
|
25
|
+
return flags;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Collect global options that appear across most commands.
|
|
29
|
+
* These get offered at every completion point.
|
|
30
|
+
*/
|
|
31
|
+
function extractGlobalOptions(program) {
|
|
32
|
+
const flags = new Set();
|
|
33
|
+
// Program-level options (--version, etc.)
|
|
34
|
+
for (const opt of program.options) {
|
|
35
|
+
if (opt.long)
|
|
36
|
+
flags.add(opt.long);
|
|
37
|
+
}
|
|
38
|
+
// Common flags present on subcommands
|
|
39
|
+
flags.add('--help');
|
|
40
|
+
flags.add('--json');
|
|
41
|
+
return [...flags];
|
|
42
|
+
}
|
|
43
|
+
function generateZshScript(program) {
|
|
44
|
+
const tree = extractCommandTree(program);
|
|
45
|
+
const globalOpts = extractGlobalOptions(program);
|
|
46
|
+
const topLevelCmds = [...tree.keys()];
|
|
47
|
+
// Build case arms for subcommand completion
|
|
48
|
+
const caseArms = [];
|
|
49
|
+
for (const [group, subs] of tree) {
|
|
50
|
+
if (subs.length > 0) {
|
|
51
|
+
caseArms.push(` ${group})\n local subcmds=(${subs.join(' ')})\n _describe 'subcommand' subcmds\n ;;`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return `#compdef figmanage
|
|
55
|
+
|
|
56
|
+
# Shell completion for figmanage
|
|
57
|
+
# Add to ~/.zshrc: eval "$(figmanage completion)"
|
|
58
|
+
|
|
59
|
+
_figmanage() {
|
|
60
|
+
local -a commands
|
|
61
|
+
commands=(${topLevelCmds.join(' ')})
|
|
62
|
+
|
|
63
|
+
local global_opts=(${globalOpts.join(' ')})
|
|
64
|
+
|
|
65
|
+
_arguments -C \\
|
|
66
|
+
'1:command:->cmd' \\
|
|
67
|
+
'2:subcommand:->sub' \\
|
|
68
|
+
'*::options:->opts'
|
|
69
|
+
|
|
70
|
+
case $state in
|
|
71
|
+
cmd)
|
|
72
|
+
_describe 'command' commands
|
|
73
|
+
;;
|
|
74
|
+
sub)
|
|
75
|
+
case $words[1] in
|
|
76
|
+
${caseArms.join('\n')}
|
|
77
|
+
*)
|
|
78
|
+
_describe 'option' global_opts
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
;;
|
|
82
|
+
opts)
|
|
83
|
+
_values 'options' $global_opts
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
compdef _figmanage figmanage
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
function generateBashScript(program) {
|
|
92
|
+
const tree = extractCommandTree(program);
|
|
93
|
+
const globalOpts = extractGlobalOptions(program);
|
|
94
|
+
const topLevelCmds = [...tree.keys()];
|
|
95
|
+
// Build case arms for subcommand completion
|
|
96
|
+
const caseArms = [];
|
|
97
|
+
for (const [group, subs] of tree) {
|
|
98
|
+
if (subs.length > 0) {
|
|
99
|
+
caseArms.push(` ${group})\n COMPREPLY=($(compgen -W "${subs.join(' ')}" -- "$cur"))\n ;;`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return `# Shell completion for figmanage
|
|
103
|
+
# Add to ~/.bashrc: eval "$(figmanage completion)"
|
|
104
|
+
|
|
105
|
+
_figmanage() {
|
|
106
|
+
local cur prev words cword
|
|
107
|
+
_init_completion || return
|
|
108
|
+
|
|
109
|
+
local commands="${topLevelCmds.join(' ')}"
|
|
110
|
+
local global_opts="${globalOpts.join(' ')}"
|
|
111
|
+
|
|
112
|
+
case $cword in
|
|
113
|
+
1)
|
|
114
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
115
|
+
;;
|
|
116
|
+
2)
|
|
117
|
+
case "\${words[1]}" in
|
|
118
|
+
${caseArms.join('\n')}
|
|
119
|
+
*)
|
|
120
|
+
COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
;;
|
|
124
|
+
*)
|
|
125
|
+
COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
complete -F _figmanage figmanage
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
function detectShell() {
|
|
134
|
+
const shell = process.env.SHELL ?? '';
|
|
135
|
+
if (shell.endsWith('/zsh'))
|
|
136
|
+
return 'zsh';
|
|
137
|
+
return 'bash';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create the `completion` command. Needs the parent program reference
|
|
141
|
+
* so it can introspect the full command tree at runtime.
|
|
142
|
+
*/
|
|
143
|
+
export function completionCommand(program) {
|
|
144
|
+
const cmd = new Command('completion')
|
|
145
|
+
.description('Output shell completion script')
|
|
146
|
+
.option('--shell <shell>', 'Shell type (bash or zsh)')
|
|
147
|
+
.action((options) => {
|
|
148
|
+
const shell = options.shell ?? detectShell();
|
|
149
|
+
if (shell !== 'bash' && shell !== 'zsh') {
|
|
150
|
+
console.error(`Unsupported shell: ${shell}. Use --shell bash or --shell zsh.`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const script = shell === 'zsh'
|
|
154
|
+
? generateZshScript(program)
|
|
155
|
+
: generateBashScript(program);
|
|
156
|
+
process.stdout.write(script);
|
|
157
|
+
});
|
|
158
|
+
return cmd;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=completion.js.map
|
package/dist/cli/format.js
CHANGED
|
@@ -2,13 +2,158 @@
|
|
|
2
2
|
export function isTTY() {
|
|
3
3
|
return process.stdout.isTTY === true;
|
|
4
4
|
}
|
|
5
|
+
/** Get terminal width, defaulting to 80 if unavailable */
|
|
6
|
+
function termWidth() {
|
|
7
|
+
return process.stdout.columns || 80;
|
|
8
|
+
}
|
|
9
|
+
/** Format a value for display in a table cell */
|
|
10
|
+
function formatCell(value) {
|
|
11
|
+
if (value === null || value === undefined)
|
|
12
|
+
return '';
|
|
13
|
+
if (typeof value === 'string')
|
|
14
|
+
return value;
|
|
15
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
16
|
+
return String(value);
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return `[${value.length} items]`;
|
|
19
|
+
if (typeof value === 'object')
|
|
20
|
+
return '{...}';
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
|
23
|
+
/** Truncate a string to maxLen, appending ... if truncated */
|
|
24
|
+
function truncate(str, maxLen) {
|
|
25
|
+
if (maxLen < 4)
|
|
26
|
+
return str.slice(0, maxLen);
|
|
27
|
+
if (str.length <= maxLen)
|
|
28
|
+
return str;
|
|
29
|
+
return str.slice(0, maxLen - 3) + '...';
|
|
30
|
+
}
|
|
31
|
+
/** Pad a string to the right with spaces */
|
|
32
|
+
function padRight(str, width) {
|
|
33
|
+
if (str.length >= width)
|
|
34
|
+
return str;
|
|
35
|
+
return str + ' '.repeat(width - str.length);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Render an array of objects as an aligned table.
|
|
39
|
+
* Columns are auto-sized to content, then shrunk proportionally if they
|
|
40
|
+
* exceed terminal width. A minimum gap of 2 spaces separates columns.
|
|
41
|
+
*/
|
|
42
|
+
function formatTable(rows) {
|
|
43
|
+
if (rows.length === 0)
|
|
44
|
+
return '';
|
|
45
|
+
// Collect all keys across all rows (preserving insertion order)
|
|
46
|
+
const keySet = new Set();
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
for (const key of Object.keys(row)) {
|
|
49
|
+
keySet.add(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const keys = Array.from(keySet);
|
|
53
|
+
// Build string grid: headers + data
|
|
54
|
+
const headers = keys.map((k) => k.toUpperCase());
|
|
55
|
+
const grid = rows.map((row) => keys.map((k) => formatCell(row[k])));
|
|
56
|
+
// Compute natural column widths (max of header and all data cells)
|
|
57
|
+
const colWidths = keys.map((_, i) => {
|
|
58
|
+
let max = headers[i].length;
|
|
59
|
+
for (const row of grid) {
|
|
60
|
+
if (row[i].length > max)
|
|
61
|
+
max = row[i].length;
|
|
62
|
+
}
|
|
63
|
+
return max;
|
|
64
|
+
});
|
|
65
|
+
const gap = 2;
|
|
66
|
+
const maxWidth = termWidth();
|
|
67
|
+
const totalGap = gap * (keys.length - 1);
|
|
68
|
+
const totalNatural = colWidths.reduce((a, b) => a + b, 0) + totalGap;
|
|
69
|
+
// If columns exceed terminal width, shrink the widest columns first
|
|
70
|
+
if (totalNatural > maxWidth && keys.length > 0) {
|
|
71
|
+
const budget = maxWidth - totalGap;
|
|
72
|
+
if (budget > 0) {
|
|
73
|
+
// Give each column at least 4 chars, distribute remaining proportionally
|
|
74
|
+
const minCol = 4;
|
|
75
|
+
const guaranteed = Math.min(minCol, Math.floor(budget / keys.length));
|
|
76
|
+
let remaining = budget;
|
|
77
|
+
// First pass: cap each column to its natural width or proportional share
|
|
78
|
+
const totalContent = colWidths.reduce((a, b) => a + b, 0);
|
|
79
|
+
for (let i = 0; i < colWidths.length; i++) {
|
|
80
|
+
const share = Math.max(guaranteed, Math.floor((colWidths[i] / totalContent) * budget));
|
|
81
|
+
colWidths[i] = Math.min(colWidths[i], share);
|
|
82
|
+
remaining -= colWidths[i];
|
|
83
|
+
}
|
|
84
|
+
// Distribute leftover to columns that were truncated
|
|
85
|
+
if (remaining > 0) {
|
|
86
|
+
for (let i = 0; i < colWidths.length && remaining > 0; i++) {
|
|
87
|
+
const natural = headers[i].length;
|
|
88
|
+
const canGrow = Math.max(0, natural - colWidths[i]);
|
|
89
|
+
const give = Math.min(canGrow, remaining);
|
|
90
|
+
colWidths[i] += give;
|
|
91
|
+
remaining -= give;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Render rows
|
|
97
|
+
const lines = [];
|
|
98
|
+
// Header row
|
|
99
|
+
const headerLine = keys
|
|
100
|
+
.map((_, i) => padRight(truncate(headers[i], colWidths[i]), colWidths[i]))
|
|
101
|
+
.join(' '.repeat(gap));
|
|
102
|
+
lines.push(headerLine.trimEnd());
|
|
103
|
+
// Data rows
|
|
104
|
+
for (const row of grid) {
|
|
105
|
+
const line = keys
|
|
106
|
+
.map((_, i) => padRight(truncate(row[i], colWidths[i]), colWidths[i]))
|
|
107
|
+
.join(' '.repeat(gap));
|
|
108
|
+
lines.push(line.trimEnd());
|
|
109
|
+
}
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Render a single object as key-value pairs.
|
|
114
|
+
* Falls back to JSON for deeply nested objects.
|
|
115
|
+
*/
|
|
116
|
+
function formatKeyValue(obj) {
|
|
117
|
+
// If every value is a nested object/array, this won't be readable as k/v.
|
|
118
|
+
// Check if the majority of values are complex -- if so, fall back to JSON.
|
|
119
|
+
const values = Object.values(obj);
|
|
120
|
+
const complexCount = values.filter((v) => v !== null && typeof v === 'object').length;
|
|
121
|
+
if (complexCount > values.length / 2) {
|
|
122
|
+
return JSON.stringify(obj, null, 2);
|
|
123
|
+
}
|
|
124
|
+
const entries = Object.entries(obj);
|
|
125
|
+
if (entries.length === 0)
|
|
126
|
+
return '{}';
|
|
127
|
+
return entries.map(([key, val]) => `${key}: ${formatCell(val)}`).join('\n');
|
|
128
|
+
}
|
|
5
129
|
/** Format output: JSON if piped or --json flag, human-readable if TTY */
|
|
6
130
|
export function formatOutput(data, options) {
|
|
7
131
|
if (options.json || !isTTY()) {
|
|
8
132
|
return JSON.stringify(data, null, 2);
|
|
9
133
|
}
|
|
10
|
-
//
|
|
11
|
-
|
|
134
|
+
// Primitives: render as-is
|
|
135
|
+
if (data === null || data === undefined)
|
|
136
|
+
return '';
|
|
137
|
+
if (typeof data === 'string')
|
|
138
|
+
return data;
|
|
139
|
+
if (typeof data === 'number' || typeof data === 'boolean')
|
|
140
|
+
return String(data);
|
|
141
|
+
// Array of objects: table
|
|
142
|
+
if (Array.isArray(data)) {
|
|
143
|
+
if (data.length === 0)
|
|
144
|
+
return '';
|
|
145
|
+
// If every element is a plain object, render as table
|
|
146
|
+
if (data.every((item) => item !== null && typeof item === 'object' && !Array.isArray(item))) {
|
|
147
|
+
return formatTable(data);
|
|
148
|
+
}
|
|
149
|
+
// Array of primitives or mixed: one per line
|
|
150
|
+
return data.map((item) => formatCell(item)).join('\n');
|
|
151
|
+
}
|
|
152
|
+
// Single object
|
|
153
|
+
if (typeof data === 'object') {
|
|
154
|
+
return formatKeyValue(data);
|
|
155
|
+
}
|
|
156
|
+
return String(data);
|
|
12
157
|
}
|
|
13
158
|
/** Print formatted output to stdout */
|
|
14
159
|
export function output(data, options = {}) {
|
package/dist/cli/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { analyticsCommand } from './analytics.js';
|
|
|
14
14
|
import { orgCommand } from './org.js';
|
|
15
15
|
import { librariesCommand } from './libraries.js';
|
|
16
16
|
import { teamsCommand } from './teams.js';
|
|
17
|
+
import { completionCommand } from './completion.js';
|
|
17
18
|
import { fileSummaryCommand, workspaceOverviewCommand, openCommentsCommand, cleanupStaleFilesCommand, organizeProjectCommand, setupProjectStructureCommand, seatOptimizationCommand, permissionAuditCommand, branchCleanupCommand, offboardUserCommand, onboardUserCommand, quarterlyReportCommand, } from './compound-commands.js';
|
|
18
19
|
export function registerCliCommands(program) {
|
|
19
20
|
// Auth commands (flat -- not resource-scoped)
|
|
@@ -87,5 +88,7 @@ export function registerCliCommands(program) {
|
|
|
87
88
|
program.addCommand(org);
|
|
88
89
|
program.addCommand(libraries);
|
|
89
90
|
program.addCommand(teams);
|
|
91
|
+
// Completion must be registered last so it can introspect all commands above
|
|
92
|
+
program.addCommand(completionCommand(program));
|
|
90
93
|
}
|
|
91
94
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED