@codifycli/plugin-core 1.2.3-beta.1 → 1.2.3
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/messages/sender.d.ts +1 -1
- package/dist/messages/sender.js +4 -3
- package/dist/plan/change-set.d.ts +1 -0
- package/dist/plan/change-set.js +12 -3
- package/dist/pty/background-pty.js +2 -0
- package/dist/pty/seqeuntial-pty.js +2 -0
- package/dist/utils/file-utils.js +3 -3
- package/dist/utils/functions.js +1 -1
- package/package.json +1 -1
- package/src/messages/sender.ts +4 -3
- package/src/plan/change-set.test.ts +27 -0
- package/src/plan/change-set.ts +10 -3
- package/src/pty/background-pty.ts +2 -0
- package/src/pty/seqeuntial-pty.ts +2 -0
- package/src/utils/file-utils.ts +3 -3
- package/src/utils/functions.ts +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
declare class CodifyCliSenderImpl {
|
|
5
5
|
private readonly validateIpcMessageV2;
|
|
6
6
|
requestPressKeyToContinuePrompt(message?: string): Promise<void>;
|
|
7
|
-
sendApplyNote(message: string, resourceType?: string):
|
|
7
|
+
sendApplyNote(message: string, resourceType?: string): void;
|
|
8
8
|
getCodifyCliCredentials(): Promise<string>;
|
|
9
9
|
private sendAndWaitForResponse;
|
|
10
10
|
}
|
package/dist/messages/sender.js
CHANGED
|
@@ -17,16 +17,17 @@ class CodifyCliSenderImpl {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
sendApplyNote(message, resourceType) {
|
|
21
21
|
if (!process.send || !process.connected) {
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
process.send({
|
|
25
25
|
cmd: MessageCmd.APPLY_NOTE_REQUEST,
|
|
26
26
|
data: {
|
|
27
27
|
message,
|
|
28
28
|
...(resourceType && { resourceType }),
|
|
29
|
-
}
|
|
29
|
+
},
|
|
30
|
+
requestId: nanoid(8),
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
async getCodifyCliCredentials() {
|
package/dist/plan/change-set.js
CHANGED
|
@@ -92,9 +92,9 @@ export class ChangeSet {
|
|
|
92
92
|
*/
|
|
93
93
|
static calculateParameterChanges(desiredParameters, currentParameters, parameterOptions) {
|
|
94
94
|
const parameterChangeSet = new Array();
|
|
95
|
-
// Filter out null
|
|
96
|
-
const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => v
|
|
97
|
-
const current = Object.fromEntries(Object.entries(currentParameters).filter(([, v]) => v
|
|
95
|
+
// Filter out null, undefined, [], and {} — all treated as "no value"
|
|
96
|
+
const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => !ChangeSet.isAbsent(v)));
|
|
97
|
+
const current = Object.fromEntries(Object.entries(currentParameters).filter(([, v]) => !ChangeSet.isAbsent(v)));
|
|
98
98
|
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
99
99
|
if (ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
100
100
|
parameterChangeSet.push({
|
|
@@ -148,6 +148,15 @@ export class ChangeSet {
|
|
|
148
148
|
const indexNext = orderOfOperations.indexOf(next);
|
|
149
149
|
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
150
150
|
}
|
|
151
|
+
static isAbsent(v) {
|
|
152
|
+
if (v === null || v === undefined)
|
|
153
|
+
return true;
|
|
154
|
+
if (Array.isArray(v))
|
|
155
|
+
return v.length === 0;
|
|
156
|
+
if (typeof v === 'object')
|
|
157
|
+
return Object.keys(v).length === 0;
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
151
160
|
static isSame(desired, current, setting) {
|
|
152
161
|
return (setting?.isEqual ?? ((a, b) => a === b))(desired, current);
|
|
153
162
|
}
|
|
@@ -110,6 +110,8 @@ export class BackgroundPty {
|
|
|
110
110
|
return new Promise(resolve => {
|
|
111
111
|
this.basePty.write(' unset PS1;\n');
|
|
112
112
|
this.basePty.write(' unset PS0;\n');
|
|
113
|
+
// zsh autocorrect prompts (e.g. "zsh: correct 'config' to '.config' [nyae]?") will hang
|
|
114
|
+
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
113
115
|
this.basePty.write(' unsetopt CORRECT CORRECT_ALL 2>/dev/null; true;\n');
|
|
114
116
|
this.basePty.write(' echo setup complete\\"\n');
|
|
115
117
|
const listener = this.basePty.onData((data) => {
|
|
@@ -66,6 +66,8 @@ export class SequentialPty {
|
|
|
66
66
|
// Set to a really large value to prevent wrapping
|
|
67
67
|
const initialCols = options?.disableWrapping ? 10_000 : process.stdout.columns ?? 80;
|
|
68
68
|
const initialRows = process.stdout.rows ?? 24;
|
|
69
|
+
// zsh autocorrect prompts (e.g. "zsh: correct 'config' to '.config' [nyae]?") will hang
|
|
70
|
+
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
69
71
|
const disableAutocorrect = Utils.getShell() === Shell.ZSH ? 'unsetopt CORRECT CORRECT_ALL 2>/dev/null; ' : '';
|
|
70
72
|
const wrappedCmd = `${disableAutocorrect}${cmd}`;
|
|
71
73
|
const args = options?.interactive ? ['-i', '-c', wrappedCmd] : ['-c', wrappedCmd];
|
package/dist/utils/file-utils.js
CHANGED
|
@@ -24,7 +24,7 @@ export class FileUtils {
|
|
|
24
24
|
await FileUtils.createShellRcIfNotExists();
|
|
25
25
|
const lineToInsert = addLeadingSpacer(addTrailingSpacer(line));
|
|
26
26
|
await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert);
|
|
27
|
-
|
|
27
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
28
28
|
function addLeadingSpacer(line) {
|
|
29
29
|
return line.startsWith('\n')
|
|
30
30
|
? line
|
|
@@ -43,7 +43,7 @@ export class FileUtils {
|
|
|
43
43
|
console.log(`Adding to ${path.basename(shellRc)}:
|
|
44
44
|
${lines.join('\n')}`);
|
|
45
45
|
await fs.appendFile(shellRc, formattedLines);
|
|
46
|
-
|
|
46
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* This method adds a directory path to the shell rc file if it doesn't already exist.
|
|
@@ -64,7 +64,7 @@ ${lines.join('\n')}`);
|
|
|
64
64
|
else {
|
|
65
65
|
await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
68
68
|
}
|
|
69
69
|
static async removeFromFile(filePath, search) {
|
|
70
70
|
const contents = await fs.readFile(filePath, 'utf8');
|
package/dist/utils/functions.js
CHANGED
|
@@ -31,7 +31,7 @@ export function resolvePathWithVariables(pathWithVariables) {
|
|
|
31
31
|
export function addVariablesToPath(pathWithoutVariables) {
|
|
32
32
|
let result = pathWithoutVariables;
|
|
33
33
|
for (const [key, value] of Object.entries(process.env)) {
|
|
34
|
-
if (!value || !path.isAbsolute(value) || value === '/' || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
|
|
34
|
+
if (!value || !path.isAbsolute(value) || value === '/' || value === homeDirectory || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
37
|
result = result.replaceAll(value, `$${key}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codifycli/plugin-core",
|
|
3
|
-
"version": "1.2.3
|
|
3
|
+
"version": "1.2.3",
|
|
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",
|
package/src/messages/sender.ts
CHANGED
|
@@ -27,17 +27,18 @@ class CodifyCliSenderImpl {
|
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
sendApplyNote(message: string, resourceType?: string): void {
|
|
31
31
|
if (!process.send || !process.connected) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
process.send(<IpcMessageV2>{
|
|
36
36
|
cmd: MessageCmd.APPLY_NOTE_REQUEST,
|
|
37
37
|
data: <ApplyNoteRequestData>{
|
|
38
38
|
message,
|
|
39
39
|
...(resourceType && { resourceType }),
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
requestId: nanoid(8),
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -304,6 +304,33 @@ describe('Change set tests', () => {
|
|
|
304
304
|
expect(cs.operation).to.eq(ResourceOperation.DESTROY);
|
|
305
305
|
})
|
|
306
306
|
|
|
307
|
+
it('treats empty objects {} as no-op', () => {
|
|
308
|
+
const desired = {
|
|
309
|
+
keyboard: {},
|
|
310
|
+
dock: {},
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const current = {
|
|
314
|
+
keyboard: {},
|
|
315
|
+
dock: {},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const cs = ChangeSet.calculateModification(desired, current);
|
|
319
|
+
expect(cs.parameterChanges.length).to.eq(0);
|
|
320
|
+
expect(cs.operation).to.eq(ResourceOperation.NOOP);
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('treats empty object {} as absent when current has no value', () => {
|
|
324
|
+
const desired = {
|
|
325
|
+
keyboard: {},
|
|
326
|
+
dock: {},
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const cs = ChangeSet.calculateModification(desired, {});
|
|
330
|
+
expect(cs.parameterChanges.length).to.eq(0);
|
|
331
|
+
expect(cs.operation).to.eq(ResourceOperation.NOOP);
|
|
332
|
+
})
|
|
333
|
+
|
|
307
334
|
it('correctly determines array equality 5', () => {
|
|
308
335
|
const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }];
|
|
309
336
|
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
package/src/plan/change-set.ts
CHANGED
|
@@ -154,13 +154,13 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
154
154
|
): ParameterChange<T>[] {
|
|
155
155
|
const parameterChangeSet = new Array<ParameterChange<T>>();
|
|
156
156
|
|
|
157
|
-
// Filter out null
|
|
157
|
+
// Filter out null, undefined, [], and {} — all treated as "no value"
|
|
158
158
|
const desired = Object.fromEntries(
|
|
159
|
-
Object.entries(desiredParameters).filter(([, v]) => v
|
|
159
|
+
Object.entries(desiredParameters).filter(([, v]) => !ChangeSet.isAbsent(v))
|
|
160
160
|
) as Partial<T>
|
|
161
161
|
|
|
162
162
|
const current = Object.fromEntries(
|
|
163
|
-
Object.entries(currentParameters).filter(([, v]) => v
|
|
163
|
+
Object.entries(currentParameters).filter(([, v]) => !ChangeSet.isAbsent(v))
|
|
164
164
|
) as Partial<T>
|
|
165
165
|
|
|
166
166
|
for (const k of new Set([...Object.keys(current), ...Object.keys(desired)])) {
|
|
@@ -227,6 +227,13 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
227
227
|
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
private static isAbsent(v: unknown): boolean {
|
|
231
|
+
if (v === null || v === undefined) return true;
|
|
232
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
233
|
+
if (typeof v === 'object') return Object.keys(v as object).length === 0;
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
230
237
|
private static isSame(
|
|
231
238
|
desired: unknown,
|
|
232
239
|
current: unknown,
|
|
@@ -135,6 +135,8 @@ export class BackgroundPty implements IPty {
|
|
|
135
135
|
return new Promise(resolve => {
|
|
136
136
|
this.basePty.write(' unset PS1;\n');
|
|
137
137
|
this.basePty.write(' unset PS0;\n')
|
|
138
|
+
// zsh autocorrect prompts (e.g. "zsh: correct 'config' to '.config' [nyae]?") will hang
|
|
139
|
+
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
138
140
|
this.basePty.write(' unsetopt CORRECT CORRECT_ALL 2>/dev/null; true;\n')
|
|
139
141
|
this.basePty.write(' echo setup complete\\"\n')
|
|
140
142
|
|
|
@@ -88,6 +88,8 @@ export class SequentialPty implements IPty {
|
|
|
88
88
|
const initialCols = options?.disableWrapping ? 10_000 : process.stdout.columns ?? 80
|
|
89
89
|
const initialRows = process.stdout.rows ?? 24;
|
|
90
90
|
|
|
91
|
+
// zsh autocorrect prompts (e.g. "zsh: correct 'config' to '.config' [nyae]?") will hang
|
|
92
|
+
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
|
|
91
93
|
const disableAutocorrect = Utils.getShell() === Shell.ZSH ? 'unsetopt CORRECT CORRECT_ALL 2>/dev/null; ' : '';
|
|
92
94
|
const wrappedCmd = `${disableAutocorrect}${cmd}`;
|
|
93
95
|
const args = options?.interactive ? ['-i', '-c', wrappedCmd] : ['-c', wrappedCmd]
|
package/src/utils/file-utils.ts
CHANGED
|
@@ -35,7 +35,7 @@ export class FileUtils {
|
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert)
|
|
38
|
-
|
|
38
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
39
39
|
|
|
40
40
|
function addLeadingSpacer(line: string): string {
|
|
41
41
|
return line.startsWith('\n')
|
|
@@ -60,7 +60,7 @@ export class FileUtils {
|
|
|
60
60
|
${lines.join('\n')}`)
|
|
61
61
|
|
|
62
62
|
await fs.appendFile(shellRc, formattedLines)
|
|
63
|
-
|
|
63
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
@@ -85,7 +85,7 @@ ${lines.join('\n')}`)
|
|
|
85
85
|
await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc());
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
static async removeFromFile(filePath: string, search: string): Promise<void> {
|
package/src/utils/functions.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function resolvePathWithVariables(pathWithVariables: string) {
|
|
|
43
43
|
export function addVariablesToPath(pathWithoutVariables: string) {
|
|
44
44
|
let result = pathWithoutVariables;
|
|
45
45
|
for (const [key, value] of Object.entries(process.env)) {
|
|
46
|
-
if (!value || !path.isAbsolute(value) || value === '/' || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
|
|
46
|
+
if (!value || !path.isAbsolute(value) || value === '/' || value === homeDirectory || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
|