@codifycli/plugin-core 1.2.6 → 1.2.7-beta.2
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.
|
@@ -18,10 +18,12 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
18
18
|
*/
|
|
19
19
|
export class BackgroundPty {
|
|
20
20
|
historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
21
|
+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
|
|
22
|
+
// detected via the absence of process.env.SHELL, so rc files sourced via
|
|
23
|
+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
|
|
24
|
+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
|
|
25
|
+
// hiding PATH exports written by FileUtils.addToShellRc().
|
|
26
|
+
basePty = pty.spawn(this.getDefaultShell(), Utils.needsLoginShell() ? ['-l', '-i'] : ['-i'], {
|
|
25
27
|
env: { ...process.env, ...this.historyIgnore },
|
|
26
28
|
cols: 10_000, // Set to a really large value to prevent wrapping
|
|
27
29
|
name: nanoid(6),
|
|
@@ -70,11 +70,13 @@ export class SequentialPty {
|
|
|
70
70
|
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
71
71
|
const disableAutocorrect = Utils.getShell() === Shell.ZSH ? 'unsetopt CORRECT CORRECT_ALL 2>/dev/null; ' : '';
|
|
72
72
|
const wrappedCmd = `${disableAutocorrect}${cmd}`;
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
73
|
+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
|
|
74
|
+
// detected via the absence of process.env.SHELL, so rc files sourced via
|
|
75
|
+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
|
|
76
|
+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
|
|
77
|
+
// hiding PATH exports written by FileUtils.addToShellRc().
|
|
78
|
+
const loginFlag = Utils.needsLoginShell() ? ['-l'] : [];
|
|
79
|
+
const args = options?.interactive ? [...loginFlag, '-i', '-c', wrappedCmd] : ['-c', wrappedCmd];
|
|
78
80
|
// Run the command in a pty for interactivity
|
|
79
81
|
const mPty = pty.spawn(this.getDefaultShell(), args, {
|
|
80
82
|
...options,
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -64,6 +64,20 @@ export declare const Utils: {
|
|
|
64
64
|
* shell regardless of how Codify was started.
|
|
65
65
|
*/
|
|
66
66
|
getDefaultShell(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Whether interactive spawns should add `-l` (login shell) on top of `-i`.
|
|
69
|
+
* Only needed when `process.env.SHELL` is unset — the signal that Codify was
|
|
70
|
+
* launched outside a terminal (e.g. the desktop app via launchd on macOS),
|
|
71
|
+
* where GUI-launched processes don't inherit the user's rc-file env (e.g.
|
|
72
|
+
* TART_HOME, PATH additions).
|
|
73
|
+
*
|
|
74
|
+
* When `SHELL` *is* set (normal terminal/CI launches), a login shell must be
|
|
75
|
+
* avoided: on Linux, `-l` sources `~/.profile`/`~/.bash_profile` instead of
|
|
76
|
+
* `~/.bashrc` (often not sourcing `.bashrc` at all, since CI images
|
|
77
|
+
* typically ship no `~/.bash_profile`), so PATH exports written by
|
|
78
|
+
* `FileUtils.addToShellRc()` become invisible to later interactive spawns.
|
|
79
|
+
*/
|
|
80
|
+
needsLoginShell(): boolean;
|
|
67
81
|
getPrimaryShellRc(): string;
|
|
68
82
|
getShellRcFiles(): string[];
|
|
69
83
|
isDirectoryOnPath(directory: string): Promise<boolean>;
|
package/dist/utils/index.js
CHANGED
|
@@ -98,6 +98,22 @@ export const Utils = {
|
|
|
98
98
|
getDefaultShell() {
|
|
99
99
|
return process.env.SHELL || os.userInfo().shell || '/bin/zsh';
|
|
100
100
|
},
|
|
101
|
+
/**
|
|
102
|
+
* Whether interactive spawns should add `-l` (login shell) on top of `-i`.
|
|
103
|
+
* Only needed when `process.env.SHELL` is unset — the signal that Codify was
|
|
104
|
+
* launched outside a terminal (e.g. the desktop app via launchd on macOS),
|
|
105
|
+
* where GUI-launched processes don't inherit the user's rc-file env (e.g.
|
|
106
|
+
* TART_HOME, PATH additions).
|
|
107
|
+
*
|
|
108
|
+
* When `SHELL` *is* set (normal terminal/CI launches), a login shell must be
|
|
109
|
+
* avoided: on Linux, `-l` sources `~/.profile`/`~/.bash_profile` instead of
|
|
110
|
+
* `~/.bashrc` (often not sourcing `.bashrc` at all, since CI images
|
|
111
|
+
* typically ship no `~/.bash_profile`), so PATH exports written by
|
|
112
|
+
* `FileUtils.addToShellRc()` become invisible to later interactive spawns.
|
|
113
|
+
*/
|
|
114
|
+
needsLoginShell() {
|
|
115
|
+
return !process.env.SHELL;
|
|
116
|
+
},
|
|
101
117
|
getPrimaryShellRc() {
|
|
102
118
|
return this.getShellRcFiles()[0];
|
|
103
119
|
},
|
|
@@ -207,10 +223,23 @@ Brew can be installed using Codify:
|
|
|
207
223
|
if (isAptInstalled.status === SpawnStatus.SUCCESS) {
|
|
208
224
|
await $.spawn('apt-get update', { requiresRoot: true });
|
|
209
225
|
const flagStr = extraFlags.length > 0 ? `${extraFlags.join(' ')} ` : '';
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
let status = SpawnStatus.ERROR;
|
|
227
|
+
let data = '';
|
|
228
|
+
const maxLockRetries = 5;
|
|
229
|
+
for (let attempt = 0; attempt <= maxLockRetries; attempt++) {
|
|
230
|
+
({ status, data } = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${flagStr}${packageName}`, {
|
|
231
|
+
requiresRoot: true,
|
|
232
|
+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
|
|
233
|
+
}));
|
|
234
|
+
// dpkg/apt lock is held by another process (e.g. unattended-upgrades on a fresh VM).
|
|
235
|
+
// This isn't a broken-dependency condition, so back off and retry rather than falling
|
|
236
|
+
// through to `apt-get install -f`, which will just hit the same lock and fail too.
|
|
237
|
+
const isLockContention = status === SpawnStatus.ERROR
|
|
238
|
+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
|
|
239
|
+
if (!isLockContention || attempt === maxLockRetries)
|
|
240
|
+
break;
|
|
241
|
+
await new Promise((resolve) => setTimeout(resolve, 5000 * (attempt + 1)));
|
|
242
|
+
}
|
|
214
243
|
if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) {
|
|
215
244
|
await $.spawn('dpkg --configure -a', { requiresRoot: true });
|
|
216
245
|
await $.spawn(`apt-get -y install ${flagStr}${packageName}`, {
|
|
@@ -219,6 +248,11 @@ Brew can be installed using Codify:
|
|
|
219
248
|
});
|
|
220
249
|
return;
|
|
221
250
|
}
|
|
251
|
+
const isLockContention = status === SpawnStatus.ERROR
|
|
252
|
+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
|
|
253
|
+
if (isLockContention) {
|
|
254
|
+
throw new Error(`Failed to install package ${packageName} via apt: dpkg/apt lock held by another process after ${maxLockRetries} retries: ${data}`);
|
|
255
|
+
}
|
|
222
256
|
if (status === SpawnStatus.ERROR) {
|
|
223
257
|
// Attempt to fix broken dependencies then retry
|
|
224
258
|
const fixResult = await $.spawnSafe('apt-get install -f -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codifycli/plugin-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7-beta.2",
|
|
4
4
|
"description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -21,10 +21,12 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
21
21
|
*/
|
|
22
22
|
export class BackgroundPty implements IPty {
|
|
23
23
|
private historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
24
|
+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
|
|
25
|
+
// detected via the absence of process.env.SHELL, so rc files sourced via
|
|
26
|
+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
|
|
27
|
+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
|
|
28
|
+
// hiding PATH exports written by FileUtils.addToShellRc().
|
|
29
|
+
private basePty = pty.spawn(this.getDefaultShell(), Utils.needsLoginShell() ? ['-l', '-i'] : ['-i'], {
|
|
28
30
|
env: { ...process.env, ...this.historyIgnore },
|
|
29
31
|
cols: 10_000, // Set to a really large value to prevent wrapping
|
|
30
32
|
name: nanoid(6),
|
|
@@ -92,11 +92,13 @@ export class SequentialPty implements IPty {
|
|
|
92
92
|
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
93
93
|
const disableAutocorrect = Utils.getShell() === Shell.ZSH ? 'unsetopt CORRECT CORRECT_ALL 2>/dev/null; ' : '';
|
|
94
94
|
const wrappedCmd = `${disableAutocorrect}${cmd}`;
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
95
|
+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
|
|
96
|
+
// detected via the absence of process.env.SHELL, so rc files sourced via
|
|
97
|
+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
|
|
98
|
+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
|
|
99
|
+
// hiding PATH exports written by FileUtils.addToShellRc().
|
|
100
|
+
const loginFlag = Utils.needsLoginShell() ? ['-l'] : [];
|
|
101
|
+
const args = options?.interactive ? [...loginFlag, '-i', '-c', wrappedCmd] : ['-c', wrappedCmd]
|
|
100
102
|
|
|
101
103
|
// Run the command in a pty for interactivity
|
|
102
104
|
const mPty = pty.spawn(this.getDefaultShell(), args, {
|
package/src/utils/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
import { getPty, SpawnStatus } from '../pty/index.js';
|
|
7
|
+
import { getPty, SpawnResult, SpawnStatus } from '../pty/index.js';
|
|
8
8
|
|
|
9
9
|
export function isDebug(): boolean {
|
|
10
10
|
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
@@ -146,6 +146,22 @@ export const Utils = {
|
|
|
146
146
|
return process.env.SHELL || os.userInfo().shell || '/bin/zsh';
|
|
147
147
|
},
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Whether interactive spawns should add `-l` (login shell) on top of `-i`.
|
|
151
|
+
* Only needed when `process.env.SHELL` is unset — the signal that Codify was
|
|
152
|
+
* launched outside a terminal (e.g. the desktop app via launchd on macOS),
|
|
153
|
+
* where GUI-launched processes don't inherit the user's rc-file env (e.g.
|
|
154
|
+
* TART_HOME, PATH additions).
|
|
155
|
+
*
|
|
156
|
+
* When `SHELL` *is* set (normal terminal/CI launches), a login shell must be
|
|
157
|
+
* avoided: on Linux, `-l` sources `~/.profile`/`~/.bash_profile` instead of
|
|
158
|
+
* `~/.bashrc` (often not sourcing `.bashrc` at all, since CI images
|
|
159
|
+
* typically ship no `~/.bash_profile`), so PATH exports written by
|
|
160
|
+
* `FileUtils.addToShellRc()` become invisible to later interactive spawns.
|
|
161
|
+
*/
|
|
162
|
+
needsLoginShell(): boolean {
|
|
163
|
+
return !process.env.SHELL;
|
|
164
|
+
},
|
|
149
165
|
|
|
150
166
|
getPrimaryShellRc(): string {
|
|
151
167
|
return this.getShellRcFiles()[0];
|
|
@@ -274,10 +290,25 @@ Brew can be installed using Codify:
|
|
|
274
290
|
if (isAptInstalled.status === SpawnStatus.SUCCESS) {
|
|
275
291
|
await $.spawn('apt-get update', { requiresRoot: true });
|
|
276
292
|
const flagStr = extraFlags.length > 0 ? `${extraFlags.join(' ')} ` : '';
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
293
|
+
|
|
294
|
+
let status: SpawnResult['status'] = SpawnStatus.ERROR;
|
|
295
|
+
let data = '';
|
|
296
|
+
const maxLockRetries = 5;
|
|
297
|
+
for (let attempt = 0; attempt <= maxLockRetries; attempt++) {
|
|
298
|
+
({ status, data } = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${flagStr}${packageName}`, {
|
|
299
|
+
requiresRoot: true,
|
|
300
|
+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
|
|
301
|
+
}));
|
|
302
|
+
|
|
303
|
+
// dpkg/apt lock is held by another process (e.g. unattended-upgrades on a fresh VM).
|
|
304
|
+
// This isn't a broken-dependency condition, so back off and retry rather than falling
|
|
305
|
+
// through to `apt-get install -f`, which will just hit the same lock and fail too.
|
|
306
|
+
const isLockContention = status === SpawnStatus.ERROR
|
|
307
|
+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
|
|
308
|
+
if (!isLockContention || attempt === maxLockRetries) break;
|
|
309
|
+
|
|
310
|
+
await new Promise((resolve) => setTimeout(resolve, 5000 * (attempt + 1)));
|
|
311
|
+
}
|
|
281
312
|
|
|
282
313
|
if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) {
|
|
283
314
|
await $.spawn('dpkg --configure -a', { requiresRoot: true });
|
|
@@ -288,6 +319,12 @@ Brew can be installed using Codify:
|
|
|
288
319
|
return;
|
|
289
320
|
}
|
|
290
321
|
|
|
322
|
+
const isLockContention = status === SpawnStatus.ERROR
|
|
323
|
+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
|
|
324
|
+
if (isLockContention) {
|
|
325
|
+
throw new Error(`Failed to install package ${packageName} via apt: dpkg/apt lock held by another process after ${maxLockRetries} retries: ${data}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
291
328
|
if (status === SpawnStatus.ERROR) {
|
|
292
329
|
// Attempt to fix broken dependencies then retry
|
|
293
330
|
const fixResult = await $.spawnSafe('apt-get install -f -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0', {
|