@fredlackey/devutils 0.0.2 → 0.0.4

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": "@fredlackey/devutils",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A globally-installable Node.js CLI toolkit for bootstrapping and configuring development environments across any machine.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -7,13 +7,56 @@
7
7
  */
8
8
 
9
9
  const shell = require('../common/shell');
10
+ const fs = require('fs');
10
11
 
11
12
  /**
12
- * Checks if Chocolatey is installed
13
+ * Well-known path where Chocolatey is typically installed on Windows.
14
+ * This path is used as a fallback when choco is not found in PATH,
15
+ * which can happen when Chocolatey was just installed and the current
16
+ * process still has the old PATH environment variable.
17
+ */
18
+ const CHOCO_KNOWN_PATH = 'C:\\ProgramData\\chocolatey\\bin\\choco.exe';
19
+
20
+ /**
21
+ * Checks if Chocolatey is installed.
22
+ *
23
+ * First checks PATH, then falls back to checking the well-known
24
+ * installation path. This handles the case where Chocolatey was just
25
+ * installed and PATH hasn't been updated in the current process.
26
+ *
13
27
  * @returns {boolean}
14
28
  */
15
29
  function isInstalled() {
16
- return shell.commandExists('choco');
30
+ return getExecutablePath() !== null;
31
+ }
32
+
33
+ /**
34
+ * Gets the path to the Chocolatey executable.
35
+ *
36
+ * First checks if choco is in PATH, then falls back to the well-known
37
+ * installation path. This handles the case where Chocolatey was just
38
+ * installed and PATH hasn't been updated in the current process.
39
+ *
40
+ * @returns {string|null} The path to choco executable, or null if not found
41
+ */
42
+ function getExecutablePath() {
43
+ // First check if choco is in PATH
44
+ if (shell.commandExists('choco')) {
45
+ return 'choco';
46
+ }
47
+
48
+ // Fall back to checking the well-known installation path
49
+ // This handles cases where Chocolatey was just installed and PATH
50
+ // hasn't been updated in the current Node.js process
51
+ try {
52
+ if (fs.existsSync(CHOCO_KNOWN_PATH)) {
53
+ return CHOCO_KNOWN_PATH;
54
+ }
55
+ } catch {
56
+ // Ignore errors checking the path
57
+ }
58
+
59
+ return null;
17
60
  }
18
61
 
19
62
  /**
@@ -21,11 +64,12 @@ function isInstalled() {
21
64
  * @returns {Promise<string|null>}
22
65
  */
23
66
  async function getVersion() {
24
- if (!isInstalled()) {
67
+ const chocoPath = getExecutablePath();
68
+ if (!chocoPath) {
25
69
  return null;
26
70
  }
27
71
 
28
- const result = await shell.exec('choco --version');
72
+ const result = await shell.exec(`"${chocoPath}" --version`);
29
73
  if (result.code === 0) {
30
74
  return result.stdout.trim();
31
75
  }
@@ -41,14 +85,15 @@ async function getVersion() {
41
85
  * @returns {Promise<{ success: boolean, output: string }>}
42
86
  */
43
87
  async function install(packageName, options = {}) {
44
- if (!isInstalled()) {
88
+ const chocoPath = getExecutablePath();
89
+ if (!chocoPath) {
45
90
  return {
46
91
  success: false,
47
92
  output: 'Chocolatey is not installed'
48
93
  };
49
94
  }
50
95
 
51
- let command = `choco install ${packageName} -y`;
96
+ let command = `"${chocoPath}" install ${packageName} -y`;
52
97
 
53
98
  if (options.force) {
54
99
  command += ' --force';
@@ -71,14 +116,15 @@ async function install(packageName, options = {}) {
71
116
  * @returns {Promise<{ success: boolean, output: string }>}
72
117
  */
73
118
  async function uninstall(packageName) {
74
- if (!isInstalled()) {
119
+ const chocoPath = getExecutablePath();
120
+ if (!chocoPath) {
75
121
  return {
76
122
  success: false,
77
123
  output: 'Chocolatey is not installed'
78
124
  };
79
125
  }
80
126
 
81
- const result = await shell.exec(`choco uninstall ${packageName} -y`);
127
+ const result = await shell.exec(`"${chocoPath}" uninstall ${packageName} -y`);
82
128
  return {
83
129
  success: result.code === 0,
84
130
  output: result.stdout || result.stderr
@@ -91,11 +137,12 @@ async function uninstall(packageName) {
91
137
  * @returns {Promise<boolean>}
92
138
  */
93
139
  async function isPackageInstalled(packageName) {
94
- if (!isInstalled()) {
140
+ const chocoPath = getExecutablePath();
141
+ if (!chocoPath) {
95
142
  return false;
96
143
  }
97
144
 
98
- const result = await shell.exec(`choco list --local-only --exact ${packageName}`);
145
+ const result = await shell.exec(`"${chocoPath}" list --local-only --exact ${packageName}`);
99
146
  // Output contains the package name if installed
100
147
  return result.code === 0 && result.stdout.toLowerCase().includes(packageName.toLowerCase());
101
148
  }
@@ -106,11 +153,12 @@ async function isPackageInstalled(packageName) {
106
153
  * @returns {Promise<string|null>}
107
154
  */
108
155
  async function getPackageVersion(packageName) {
109
- if (!isInstalled()) {
156
+ const chocoPath = getExecutablePath();
157
+ if (!chocoPath) {
110
158
  return null;
111
159
  }
112
160
 
113
- const result = await shell.exec(`choco list --local-only --exact ${packageName}`);
161
+ const result = await shell.exec(`"${chocoPath}" list --local-only --exact ${packageName}`);
114
162
  if (result.code !== 0) {
115
163
  return null;
116
164
  }
@@ -133,7 +181,8 @@ async function getPackageVersion(packageName) {
133
181
  * @returns {Promise<{ success: boolean, output: string }>}
134
182
  */
135
183
  async function upgrade(packageName) {
136
- if (!isInstalled()) {
184
+ const chocoPath = getExecutablePath();
185
+ if (!chocoPath) {
137
186
  return {
138
187
  success: false,
139
188
  output: 'Chocolatey is not installed'
@@ -141,7 +190,7 @@ async function upgrade(packageName) {
141
190
  }
142
191
 
143
192
  const target = packageName || 'all';
144
- const result = await shell.exec(`choco upgrade ${target} -y`);
193
+ const result = await shell.exec(`"${chocoPath}" upgrade ${target} -y`);
145
194
  return {
146
195
  success: result.code === 0,
147
196
  output: result.stdout || result.stderr
@@ -154,11 +203,12 @@ async function upgrade(packageName) {
154
203
  * @returns {Promise<Array<{ name: string, version: string }>>}
155
204
  */
156
205
  async function search(query) {
157
- if (!isInstalled()) {
206
+ const chocoPath = getExecutablePath();
207
+ if (!chocoPath) {
158
208
  return [];
159
209
  }
160
210
 
161
- const result = await shell.exec(`choco search "${query}"`);
211
+ const result = await shell.exec(`"${chocoPath}" search "${query}"`);
162
212
  if (result.code !== 0) {
163
213
  return [];
164
214
  }
@@ -190,11 +240,12 @@ async function search(query) {
190
240
  * @returns {Promise<string|null>}
191
241
  */
192
242
  async function info(packageName) {
193
- if (!isInstalled()) {
243
+ const chocoPath = getExecutablePath();
244
+ if (!chocoPath) {
194
245
  return null;
195
246
  }
196
247
 
197
- const result = await shell.exec(`choco info ${packageName}`);
248
+ const result = await shell.exec(`"${chocoPath}" info ${packageName}`);
198
249
  if (result.code === 0) {
199
250
  return result.stdout;
200
251
  }
@@ -206,11 +257,12 @@ async function info(packageName) {
206
257
  * @returns {Promise<Array<{ name: string, version: string }>>}
207
258
  */
208
259
  async function listInstalled() {
209
- if (!isInstalled()) {
260
+ const chocoPath = getExecutablePath();
261
+ if (!chocoPath) {
210
262
  return [];
211
263
  }
212
264
 
213
- const result = await shell.exec('choco list --local-only');
265
+ const result = await shell.exec(`"${chocoPath}" list --local-only`);
214
266
  if (result.code !== 0) {
215
267
  return [];
216
268
  }
@@ -241,11 +293,12 @@ async function listInstalled() {
241
293
  * @returns {Promise<Array<{ name: string, currentVersion: string, availableVersion: string }>>}
242
294
  */
243
295
  async function listOutdated() {
244
- if (!isInstalled()) {
296
+ const chocoPath = getExecutablePath();
297
+ if (!chocoPath) {
245
298
  return [];
246
299
  }
247
300
 
248
- const result = await shell.exec('choco outdated');
301
+ const result = await shell.exec(`"${chocoPath}" outdated`);
249
302
  if (result.code !== 0) {
250
303
  return [];
251
304
  }
@@ -276,14 +329,15 @@ async function listOutdated() {
276
329
  * @returns {Promise<{ success: boolean, output: string }>}
277
330
  */
278
331
  async function pin(packageName) {
279
- if (!isInstalled()) {
332
+ const chocoPath = getExecutablePath();
333
+ if (!chocoPath) {
280
334
  return {
281
335
  success: false,
282
336
  output: 'Chocolatey is not installed'
283
337
  };
284
338
  }
285
339
 
286
- const result = await shell.exec(`choco pin add -n="${packageName}"`);
340
+ const result = await shell.exec(`"${chocoPath}" pin add -n="${packageName}"`);
287
341
  return {
288
342
  success: result.code === 0,
289
343
  output: result.stdout || result.stderr
@@ -296,14 +350,15 @@ async function pin(packageName) {
296
350
  * @returns {Promise<{ success: boolean, output: string }>}
297
351
  */
298
352
  async function unpin(packageName) {
299
- if (!isInstalled()) {
353
+ const chocoPath = getExecutablePath();
354
+ if (!chocoPath) {
300
355
  return {
301
356
  success: false,
302
357
  output: 'Chocolatey is not installed'
303
358
  };
304
359
  }
305
360
 
306
- const result = await shell.exec(`choco pin remove -n="${packageName}"`);
361
+ const result = await shell.exec(`"${chocoPath}" pin remove -n="${packageName}"`);
307
362
  return {
308
363
  success: result.code === 0,
309
364
  output: result.stdout || result.stderr
@@ -312,6 +367,7 @@ async function unpin(packageName) {
312
367
 
313
368
  module.exports = {
314
369
  isInstalled,
370
+ getExecutablePath,
315
371
  getVersion,
316
372
  install,
317
373
  uninstall,
@@ -7,13 +7,62 @@
7
7
  */
8
8
 
9
9
  const shell = require('../common/shell');
10
+ const fs = require('fs');
11
+ const path = require('path');
10
12
 
11
13
  /**
12
- * Checks if winget is available
14
+ * Well-known path where winget is typically installed on Windows.
15
+ * This path is used as a fallback when winget is not found in PATH,
16
+ * which can happen when winget was just installed and the current
17
+ * process still has the old PATH environment variable.
18
+ */
19
+ const WINGET_KNOWN_PATH = path.join(
20
+ process.env.LOCALAPPDATA || '',
21
+ 'Microsoft',
22
+ 'WindowsApps',
23
+ 'winget.exe'
24
+ );
25
+
26
+ /**
27
+ * Checks if winget is available.
28
+ *
29
+ * First checks PATH, then falls back to checking the well-known
30
+ * installation path. This handles the case where winget was just
31
+ * installed and PATH hasn't been updated in the current process.
32
+ *
13
33
  * @returns {boolean}
14
34
  */
15
35
  function isInstalled() {
16
- return shell.commandExists('winget');
36
+ return getExecutablePath() !== null;
37
+ }
38
+
39
+ /**
40
+ * Gets the path to the winget executable.
41
+ *
42
+ * First checks if winget is in PATH, then falls back to the well-known
43
+ * installation path. This handles the case where winget was just
44
+ * installed and PATH hasn't been updated in the current process.
45
+ *
46
+ * @returns {string|null} The path to winget executable, or null if not found
47
+ */
48
+ function getExecutablePath() {
49
+ // First check if winget is in PATH
50
+ if (shell.commandExists('winget')) {
51
+ return 'winget';
52
+ }
53
+
54
+ // Fall back to checking the well-known installation path
55
+ // This handles cases where winget was just installed and PATH
56
+ // hasn't been updated in the current Node.js process
57
+ try {
58
+ if (WINGET_KNOWN_PATH && fs.existsSync(WINGET_KNOWN_PATH)) {
59
+ return WINGET_KNOWN_PATH;
60
+ }
61
+ } catch {
62
+ // Ignore errors checking the path
63
+ }
64
+
65
+ return null;
17
66
  }
18
67
 
19
68
  /**
@@ -21,11 +70,12 @@ function isInstalled() {
21
70
  * @returns {Promise<string|null>}
22
71
  */
23
72
  async function getVersion() {
24
- if (!isInstalled()) {
73
+ const wingetPath = getExecutablePath();
74
+ if (!wingetPath) {
25
75
  return null;
26
76
  }
27
77
 
28
- const result = await shell.exec('winget --version');
78
+ const result = await shell.exec(`"${wingetPath}" --version`);
29
79
  if (result.code === 0) {
30
80
  // Output: "v1.6.2771"
31
81
  return result.stdout.trim().replace(/^v/, '');
@@ -43,14 +93,15 @@ async function getVersion() {
43
93
  * @returns {Promise<{ success: boolean, output: string }>}
44
94
  */
45
95
  async function install(packageName, options = {}) {
46
- if (!isInstalled()) {
96
+ const wingetPath = getExecutablePath();
97
+ if (!wingetPath) {
47
98
  return {
48
99
  success: false,
49
100
  output: 'winget is not available'
50
101
  };
51
102
  }
52
103
 
53
- let command = `winget install "${packageName}" --accept-package-agreements --accept-source-agreements`;
104
+ let command = `"${wingetPath}" install "${packageName}" --accept-package-agreements --accept-source-agreements`;
54
105
 
55
106
  if (options.silent !== false) {
56
107
  command += ' --silent';
@@ -79,14 +130,15 @@ async function install(packageName, options = {}) {
79
130
  * @returns {Promise<{ success: boolean, output: string }>}
80
131
  */
81
132
  async function uninstall(packageName, options = {}) {
82
- if (!isInstalled()) {
133
+ const wingetPath = getExecutablePath();
134
+ if (!wingetPath) {
83
135
  return {
84
136
  success: false,
85
137
  output: 'winget is not available'
86
138
  };
87
139
  }
88
140
 
89
- let command = `winget uninstall "${packageName}"`;
141
+ let command = `"${wingetPath}" uninstall "${packageName}"`;
90
142
 
91
143
  if (options.silent !== false) {
92
144
  command += ' --silent';
@@ -105,18 +157,19 @@ async function uninstall(packageName, options = {}) {
105
157
  * @returns {Promise<boolean>}
106
158
  */
107
159
  async function isPackageInstalled(packageName) {
108
- if (!isInstalled()) {
160
+ const wingetPath = getExecutablePath();
161
+ if (!wingetPath) {
109
162
  return false;
110
163
  }
111
164
 
112
- const result = await shell.exec(`winget list --exact --id "${packageName}"`);
165
+ const result = await shell.exec(`"${wingetPath}" list --exact --id "${packageName}"`);
113
166
  // Check if the output contains the package
114
167
  if (result.code === 0 && result.stdout.includes(packageName)) {
115
168
  return true;
116
169
  }
117
170
 
118
171
  // Try by name if ID didn't match
119
- const nameResult = await shell.exec(`winget list --exact --name "${packageName}"`);
172
+ const nameResult = await shell.exec(`"${wingetPath}" list --exact --name "${packageName}"`);
120
173
  return nameResult.code === 0 && nameResult.stdout.includes(packageName);
121
174
  }
122
175
 
@@ -126,11 +179,12 @@ async function isPackageInstalled(packageName) {
126
179
  * @returns {Promise<string|null>}
127
180
  */
128
181
  async function getPackageVersion(packageName) {
129
- if (!isInstalled()) {
182
+ const wingetPath = getExecutablePath();
183
+ if (!wingetPath) {
130
184
  return null;
131
185
  }
132
186
 
133
- const result = await shell.exec(`winget list --exact --id "${packageName}"`);
187
+ const result = await shell.exec(`"${wingetPath}" list --exact --id "${packageName}"`);
134
188
  if (result.code !== 0) {
135
189
  return null;
136
190
  }
@@ -159,14 +213,15 @@ async function getPackageVersion(packageName) {
159
213
  * @returns {Promise<{ success: boolean, output: string }>}
160
214
  */
161
215
  async function upgrade(packageName, options = {}) {
162
- if (!isInstalled()) {
216
+ const wingetPath = getExecutablePath();
217
+ if (!wingetPath) {
163
218
  return {
164
219
  success: false,
165
220
  output: 'winget is not available'
166
221
  };
167
222
  }
168
223
 
169
- let command = `winget upgrade "${packageName}" --accept-package-agreements --accept-source-agreements`;
224
+ let command = `"${wingetPath}" upgrade "${packageName}" --accept-package-agreements --accept-source-agreements`;
170
225
 
171
226
  if (options.silent !== false) {
172
227
  command += ' --silent';
@@ -184,14 +239,15 @@ async function upgrade(packageName, options = {}) {
184
239
  * @returns {Promise<{ success: boolean, output: string }>}
185
240
  */
186
241
  async function upgradeAll() {
187
- if (!isInstalled()) {
242
+ const wingetPath = getExecutablePath();
243
+ if (!wingetPath) {
188
244
  return {
189
245
  success: false,
190
246
  output: 'winget is not available'
191
247
  };
192
248
  }
193
249
 
194
- const result = await shell.exec('winget upgrade --all --accept-package-agreements --accept-source-agreements');
250
+ const result = await shell.exec(`"${wingetPath}" upgrade --all --accept-package-agreements --accept-source-agreements`);
195
251
  return {
196
252
  success: result.code === 0,
197
253
  output: result.stdout || result.stderr
@@ -204,11 +260,12 @@ async function upgradeAll() {
204
260
  * @returns {Promise<Array<{ name: string, id: string, version: string }>>}
205
261
  */
206
262
  async function search(query) {
207
- if (!isInstalled()) {
263
+ const wingetPath = getExecutablePath();
264
+ if (!wingetPath) {
208
265
  return [];
209
266
  }
210
267
 
211
- const result = await shell.exec(`winget search "${query}"`);
268
+ const result = await shell.exec(`"${wingetPath}" search "${query}"`);
212
269
  if (result.code !== 0) {
213
270
  return [];
214
271
  }
@@ -246,11 +303,12 @@ async function search(query) {
246
303
  * @returns {Promise<Array<{ name: string, id: string, version: string }>>}
247
304
  */
248
305
  async function list() {
249
- if (!isInstalled()) {
306
+ const wingetPath = getExecutablePath();
307
+ if (!wingetPath) {
250
308
  return [];
251
309
  }
252
310
 
253
- const result = await shell.exec('winget list');
311
+ const result = await shell.exec(`"${wingetPath}" list`);
254
312
  if (result.code !== 0) {
255
313
  return [];
256
314
  }
@@ -286,11 +344,12 @@ async function list() {
286
344
  * @returns {Promise<string|null>}
287
345
  */
288
346
  async function info(packageName) {
289
- if (!isInstalled()) {
347
+ const wingetPath = getExecutablePath();
348
+ if (!wingetPath) {
290
349
  return null;
291
350
  }
292
351
 
293
- const result = await shell.exec(`winget show "${packageName}"`);
352
+ const result = await shell.exec(`"${wingetPath}" show "${packageName}"`);
294
353
  if (result.code === 0) {
295
354
  return result.stdout;
296
355
  }
@@ -302,11 +361,12 @@ async function info(packageName) {
302
361
  * @returns {Promise<Array<{ name: string, id: string, currentVersion: string, availableVersion: string }>>}
303
362
  */
304
363
  async function listUpgradable() {
305
- if (!isInstalled()) {
364
+ const wingetPath = getExecutablePath();
365
+ if (!wingetPath) {
306
366
  return [];
307
367
  }
308
368
 
309
- const result = await shell.exec('winget upgrade');
369
+ const result = await shell.exec(`"${wingetPath}" upgrade`);
310
370
  if (result.code !== 0) {
311
371
  return [];
312
372
  }
@@ -347,14 +407,15 @@ async function listUpgradable() {
347
407
  * @returns {Promise<{ success: boolean, output: string }>}
348
408
  */
349
409
  async function updateSources() {
350
- if (!isInstalled()) {
410
+ const wingetPath = getExecutablePath();
411
+ if (!wingetPath) {
351
412
  return {
352
413
  success: false,
353
414
  output: 'winget is not available'
354
415
  };
355
416
  }
356
417
 
357
- const result = await shell.exec('winget source update');
418
+ const result = await shell.exec(`"${wingetPath}" source update`);
358
419
  return {
359
420
  success: result.code === 0,
360
421
  output: result.stdout || result.stderr
@@ -363,6 +424,7 @@ async function updateSources() {
363
424
 
364
425
  module.exports = {
365
426
  isInstalled,
427
+ getExecutablePath,
366
428
  getVersion,
367
429
  install,
368
430
  uninstall,