@gilbertwong1996/ado 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gilbertwong1996/ado",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "AI-native Azure DevOps CLI — structured JSON output, embedded skills for LLM agents, single-file cross-platform binary.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/gilbertwong96/ado_cli",
@@ -23,8 +23,12 @@
23
23
  "bin": {
24
24
  "ado": "bin/ado"
25
25
  },
26
+ "scripts": {
27
+ "postinstall": "node scripts/postinstall.js"
28
+ },
26
29
  "files": [
27
30
  "bin/ado",
31
+ "scripts/postinstall.js",
28
32
  "README.md",
29
33
  "LICENSE"
30
34
  ],
@@ -32,10 +36,10 @@
32
36
  "node": ">=18"
33
37
  },
34
38
  "optionalDependencies": {
35
- "@gilbertwong1996/ado-darwin-arm64": "0.1.0",
36
- "@gilbertwong1996/ado-darwin-x64": "0.1.0",
37
- "@gilbertwong1996/ado-linux-arm64": "0.1.0",
38
- "@gilbertwong1996/ado-linux-x64": "0.1.0",
39
- "@gilbertwong1996/ado-win32-x64": "0.1.0"
39
+ "@gilbertwong1996/ado-darwin-arm64": "0.2.1",
40
+ "@gilbertwong1996/ado-darwin-x64": "0.2.1",
41
+ "@gilbertwong1996/ado-linux-arm64": "0.2.1",
42
+ "@gilbertwong1996/ado-linux-x64": "0.2.1",
43
+ "@gilbertwong1996/ado-win32-x64": "0.2.1"
40
44
  }
41
45
  }
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+ // ado npm postinstall — auto-installs shell completion.
3
+ //
4
+ // Runs after `npm install -g @gilbertwong1996/ado`. Detects the
5
+ // user's shell, generates the right completion script, and
6
+ // installs it to the standard auto-load location for that shell
7
+ // (e.g. ~/.config/fish/completions/ado.fish, ~/.zsh/completions/_ado,
8
+ // ~/.local/share/bash-completion/completions/ado). For shells that
9
+ // need an explicit fpath/source line in the user's shell config
10
+ // (zsh, powershell), appends the line to the config file.
11
+ //
12
+ // Honors the ADO_NO_COMPLETION env var to opt out:
13
+ // ADO_NO_COMPLETION=1 npm install -g @gilbertwong1996/ado
14
+ //
15
+ // Idempotent: re-running just refreshes the completion script.
16
+ // Skips re-appending the config line if it's already there.
17
+ //
18
+ // Note: stdout from the postinstall is shown by npm to the user
19
+ // (unless --silent). We use stdout for the success messages and
20
+ // stderr for warnings/errors, so the user always sees what was done.
21
+
22
+ 'use strict';
23
+
24
+ const { execFileSync } = require('child_process');
25
+ const fs = require('fs');
26
+ const os = require('os');
27
+ const path = require('path');
28
+
29
+ // ── Configuration ────────────────────────────────────────────────────
30
+
31
+ // Standard auto-load locations for completion scripts per shell.
32
+ // All paths are XDG-friendly and don't require sudo.
33
+ const HOME = os.homedir();
34
+ const PLATFORM = process.platform;
35
+
36
+ const SHELL_CONFIG = {
37
+ bash: {
38
+ installPath: path.join(
39
+ HOME,
40
+ '.local',
41
+ 'share',
42
+ 'bash-completion',
43
+ 'completions',
44
+ 'ado'
45
+ ),
46
+ needsConfigEdit: false,
47
+ // bash-completion's standard location. The bash-completion
48
+ // package (https://github.com/scop/bash-completion) auto-loads
49
+ // any file in this directory. Most package managers install
50
+ // bash-completion by default. If it's not installed, the file
51
+ // just sits there harmlessly.
52
+ autoLoadNote:
53
+ 'Works automatically with the bash-completion package. ' +
54
+ 'If <TAB> does nothing, install it via your package manager ' +
55
+ '(e.g. `brew install bash-completion`, `apt install bash-completion`).'
56
+ },
57
+
58
+ zsh: {
59
+ installPath: path.join(HOME, '.zsh', 'completions', '_ado'),
60
+ needsConfigEdit: true,
61
+ configPath: path.join(HOME, '.zshrc'),
62
+ configMarker: '# ado shell completion (added by npm postinstall)',
63
+ configLines: [
64
+ '# ado shell completion (added by npm postinstall)',
65
+ 'fpath=($HOME/.zsh/completions $fpath)',
66
+ 'autoload -U compinit && compinit'
67
+ ]
68
+ },
69
+
70
+ fish: {
71
+ installPath: path.join(
72
+ HOME,
73
+ '.config',
74
+ 'fish',
75
+ 'completions',
76
+ 'ado.fish'
77
+ ),
78
+ needsConfigEdit: false,
79
+ // Fish auto-loads every file in this directory. Zero config.
80
+ autoLoadNote: 'Works automatically. Restart your fish shell.'
81
+ },
82
+
83
+ powershell: {
84
+ installPath:
85
+ PLATFORM === 'win32'
86
+ ? path.join(HOME, 'Documents', 'PowerShell', 'ado-completion.ps1')
87
+ : path.join(HOME, '.config', 'powershell', 'ado-completion.ps1'),
88
+ needsConfigEdit: true,
89
+ configPath:
90
+ PLATFORM === 'win32'
91
+ ? path.join(
92
+ HOME,
93
+ 'Documents',
94
+ 'PowerShell',
95
+ 'Microsoft.PowerShell_profile.ps1'
96
+ )
97
+ : path.join(
98
+ HOME,
99
+ '.config',
100
+ 'powershell',
101
+ 'Microsoft.PowerShell_profile.ps1'
102
+ ),
103
+ configMarker: '# ado shell completion (added by npm postinstall)',
104
+ configLines: []
105
+ }
106
+ };
107
+
108
+ // ── Shell detection ───────────────────────────────────────────────────
109
+
110
+ function detectShell() {
111
+ // On Windows, default to PowerShell.
112
+ if (PLATFORM === 'win32') return 'powershell';
113
+
114
+ // On Unix, use $SHELL.
115
+ const sh = (process.env.SHELL || '').toLowerCase();
116
+ if (sh.endsWith('/bash') || sh.endsWith('/sh')) return 'bash';
117
+ if (sh.endsWith('/zsh')) return 'zsh';
118
+ if (sh.endsWith('/fish')) return 'fish';
119
+ if (sh.includes('pwsh') || sh.includes('powershell')) return 'powershell';
120
+
121
+ // Default to bash if undetectable.
122
+ return 'bash';
123
+ }
124
+
125
+ // ── Completion script generation ─────────────────────────────────────
126
+
127
+ function findAdoWrapper() {
128
+ // The Node.js wrapper that spawns the platform-specific binary.
129
+ // We try multiple paths because the postinstall runs in
130
+ // different cwd contexts:
131
+ // 1. In an installed npm package:
132
+ // node_modules/@gilbertwong1996/ado/scripts/postinstall.js
133
+ // -> bin/ado is at ../bin/ado
134
+ // 2. In the project source tree (for local testing):
135
+ // scripts/postinstall.js
136
+ // -> bin/ado is at npm/@gilbertwong1996-ado/bin/ado
137
+ //
138
+ // 3. For end-to-end testing, callers can set ADO_BIN env var
139
+ // to point at a real binary (e.g. the dev escript at ./ado)
140
+ // to bypass the wrapper entirely.
141
+
142
+ if (process.env.ADO_BIN && fs.existsSync(process.env.ADO_BIN)) {
143
+ return process.env.ADO_BIN;
144
+ }
145
+
146
+ // Try the npm-resolvable path first (works when installed)
147
+ try {
148
+ return require.resolve('@gilbertwong1996/ado/bin/ado');
149
+ } catch {
150
+ // Fall through to local source paths
151
+ }
152
+
153
+ // Try the local source path (for testing)
154
+ const localPath = path.join(
155
+ __dirname,
156
+ '..',
157
+ 'npm',
158
+ '@gilbertwong1996-ado',
159
+ 'bin',
160
+ 'ado'
161
+ );
162
+ if (fs.existsSync(localPath)) return localPath;
163
+
164
+ return null;
165
+ }
166
+
167
+ function generateCompletion(shell) {
168
+ const wrapper = findAdoWrapper();
169
+ if (!wrapper) {
170
+ return {
171
+ ok: false,
172
+ error:
173
+ 'Could not find the ado binary. This usually means a ' +
174
+ 'broken npm install. Try: npm install -g @gilbertwong1996/ado --force'
175
+ };
176
+ }
177
+
178
+ try {
179
+ const stdout = execFileSync(wrapper, ['completion', shell], {
180
+ stdio: ['ignore', 'pipe', 'pipe'],
181
+ encoding: 'utf8'
182
+ });
183
+ return { ok: true, script: stdout };
184
+ } catch (err) {
185
+ return {
186
+ ok: false,
187
+ error: err.stderr || err.message,
188
+ status: err.status
189
+ };
190
+ }
191
+ }
192
+
193
+ // ── Config file editing ──────────────────────────────────────────────
194
+
195
+ function ensureConfigLine(shell, script, cfg) {
196
+ if (!cfg.needsConfigEdit) {
197
+ return { skipped: true, reason: 'auto-load' };
198
+ }
199
+
200
+ const configPath = cfg.configPath;
201
+
202
+ if (shell === 'powershell') {
203
+ // For PowerShell, write a separate .ps1 file and source it
204
+ // from $PROFILE. This way updating ado (which rewrites the
205
+ // .ps1) doesn't touch the user's $PROFILE.
206
+ const ps1Path = cfg.installPath;
207
+ fs.mkdirSync(path.dirname(ps1Path), { recursive: true });
208
+ // The .ps1 content IS the completion script (Register-ArgumentCompleter
209
+ // call). The $PROFILE just . -sources it.
210
+ fs.writeFileSync(
211
+ ps1Path,
212
+ "# ado shell completion (auto-generated, do not edit)\n" +
213
+ "# Sourced by $PROFILE on shell startup.\n" +
214
+ "# Re-generated by `npm install -g @gilbertwong1996/ado`.\n" +
215
+ "\n" +
216
+ script
217
+ );
218
+ return appendLine(configPath, `. '${ps1Path.replace(/'/g, "''")}'`);
219
+ }
220
+
221
+ if (shell === 'zsh') {
222
+ // For zsh, append the fpath + compinit lines to .zshrc.
223
+ return appendLines(configPath, cfg.configLines);
224
+ }
225
+
226
+ return { skipped: true, reason: 'unknown shell' };
227
+ }
228
+
229
+ function appendLine(path, line) {
230
+ return appendLines(path, [line]);
231
+ }
232
+
233
+ function appendLines(filePath, lines) {
234
+ const marker = lines[0]; // First line is the marker
235
+ const content = fs.existsSync(filePath)
236
+ ? fs.readFileSync(filePath, 'utf8')
237
+ : '';
238
+
239
+ if (content.includes(marker)) {
240
+ return { skipped: true, reason: 'already configured' };
241
+ }
242
+
243
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
244
+ fs.appendFileSync(
245
+ filePath,
246
+ (content.endsWith('\n') || content === '' ? '' : '\n') +
247
+ '\n' +
248
+ lines.join('\n') +
249
+ '\n'
250
+ );
251
+ return { added: true, path: filePath };
252
+ }
253
+
254
+ // ── Main ─────────────────────────────────────────────────────────────
255
+
256
+ function main() {
257
+ // Opt-out
258
+ if (process.env.ADO_NO_COMPLETION === '1') {
259
+ console.log(
260
+ 'ado: ADO_NO_COMPLETION=1 set, skipping shell completion install.'
261
+ );
262
+ console.log(' To install later, run: ado completion <shell>');
263
+ return;
264
+ }
265
+
266
+ const shell = detectShell();
267
+ const cfg = SHELL_CONFIG[shell];
268
+
269
+ if (!cfg) {
270
+ console.error(`ado: unsupported shell '${shell}', skipping completion install.`);
271
+ return;
272
+ }
273
+
274
+ console.log(`ado: detected shell '${shell}'`);
275
+
276
+ // Generate the completion script
277
+ const result = generateCompletion(shell);
278
+ if (!result.ok) {
279
+ console.error(
280
+ `ado: failed to generate completion script: ${result.error}`
281
+ );
282
+ console.error(
283
+ ' You can install manually later with: ado completion ' + shell
284
+ );
285
+ return;
286
+ }
287
+
288
+ // Write the script to the install path
289
+ try {
290
+ fs.mkdirSync(path.dirname(cfg.installPath), { recursive: true });
291
+ fs.writeFileSync(cfg.installPath, result.script, 'utf8');
292
+ console.log(`ado: wrote completion script to ${cfg.installPath}`);
293
+ } catch (err) {
294
+ console.error(
295
+ `ado: failed to write completion script to ${cfg.installPath}: ${err.message}`
296
+ );
297
+ return;
298
+ }
299
+
300
+ // Edit the user's shell config if needed. PowerShell needs
301
+ // the generated script content too (to write it to a separate
302
+ // .ps1 file that gets sourced from $PROFILE), so we pass the
303
+ // script along.
304
+ if (cfg.needsConfigEdit) {
305
+ const configResult = ensureConfigLine(shell, result.script, cfg);
306
+ if (configResult.added) {
307
+ console.log(
308
+ `ado: added completion loader line to ${configResult.path}`
309
+ );
310
+ } else if (configResult.skipped) {
311
+ console.log(
312
+ `ado: shell config already has completion loader (${configResult.reason})`
313
+ );
314
+ }
315
+ }
316
+
317
+ // Final hint
318
+ console.log('');
319
+ console.log('ado: shell completion installed!');
320
+ if (cfg.autoLoadNote) {
321
+ console.log(' ' + cfg.autoLoadNote);
322
+ } else {
323
+ console.log(' Restart your shell, or: source ' + cfg.configPath);
324
+ }
325
+ console.log(' Then press <TAB> after typing `ado ` to see it in action.');
326
+ }
327
+
328
+ try {
329
+ main();
330
+ } catch (err) {
331
+ console.error(`ado: postinstall failed: ${err.message}`);
332
+ // Don't fail the install just because completion setup failed.
333
+ // The binary still works; users can run `ado completion <shell>`
334
+ // manually if they want.
335
+ process.exit(0);
336
+ }