codify-plugin-lib 1.0.182-beta6 → 1.0.182-beta60
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/bin/build.js +189 -0
- package/dist/bin/build.js +0 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/messages/handlers.js +10 -2
- package/dist/plugin/plugin.d.ts +2 -1
- package/dist/plugin/plugin.js +4 -1
- package/dist/pty/background-pty.d.ts +3 -2
- package/dist/pty/background-pty.js +7 -14
- package/dist/pty/index.d.ts +8 -2
- package/dist/pty/seqeuntial-pty.d.ts +3 -2
- package/dist/pty/seqeuntial-pty.js +47 -12
- package/dist/resource/parsed-resource-settings.d.ts +3 -1
- package/dist/resource/parsed-resource-settings.js +15 -2
- package/dist/resource/resource-controller.js +5 -5
- package/dist/resource/resource-settings.d.ts +8 -2
- package/dist/resource/resource-settings.js +2 -2
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/dist/utils/file-utils.d.ts +14 -7
- package/dist/utils/file-utils.js +65 -51
- package/dist/utils/functions.js +2 -2
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.js +111 -0
- package/dist/utils/load-resources.d.ts +1 -0
- package/dist/utils/load-resources.js +46 -0
- package/dist/utils/package-json-utils.d.ts +12 -0
- package/dist/utils/package-json-utils.js +34 -0
- package/package.json +5 -4
- package/rollup.config.js +24 -0
- package/src/index.ts +4 -0
- package/src/messages/handlers.test.ts +23 -0
- package/src/messages/handlers.ts +11 -2
- package/src/plugin/plugin.test.ts +31 -0
- package/src/plugin/plugin.ts +6 -2
- package/src/pty/background-pty.ts +10 -18
- package/src/pty/index.ts +10 -4
- package/src/pty/seqeuntial-pty.ts +62 -16
- package/src/pty/sequential-pty.test.ts +137 -4
- package/src/resource/parsed-resource-settings.test.ts +24 -0
- package/src/resource/parsed-resource-settings.ts +23 -7
- package/src/resource/resource-controller.test.ts +126 -0
- package/src/resource/resource-controller.ts +5 -6
- package/src/resource/resource-settings.test.ts +36 -0
- package/src/resource/resource-settings.ts +11 -4
- package/src/utils/file-utils.test.ts +7 -0
- package/src/utils/file-utils.ts +70 -55
- package/src/utils/functions.ts +3 -3
- package/src/utils/index.ts +138 -0
- package/src/utils/internal-utils.test.ts +1 -0
package/bin/build.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Ajv } from 'ajv';
|
|
3
|
+
import { IpcMessageSchema, MessageStatus, ResourceSchema } from 'codify-schemas';
|
|
4
|
+
import mergeJsonSchemas from 'merge-json-schemas';
|
|
5
|
+
import { fork } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
import { SequentialPty, VerbosityLevel } from '../dist/index.js';
|
|
10
|
+
|
|
11
|
+
const ajv = new Ajv({
|
|
12
|
+
strict: true
|
|
13
|
+
});
|
|
14
|
+
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
|
|
15
|
+
|
|
16
|
+
function sendMessageAndAwaitResponse(process, message) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
process.on('message', (response) => {
|
|
19
|
+
if (!ipcMessageValidator(response)) {
|
|
20
|
+
throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Wait for the message response. Other messages such as sudoRequest may be sent before the response returns
|
|
24
|
+
if (response.cmd === message.cmd + '_Response') {
|
|
25
|
+
if (response.status === MessageStatus.SUCCESS) {
|
|
26
|
+
resolve(response.data)
|
|
27
|
+
} else {
|
|
28
|
+
reject(new Error(String(response.data)))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Send message last to ensure listeners are all registered
|
|
34
|
+
process.send(message);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fetchDocumentationMaps() {
|
|
39
|
+
console.log('Building documentation...');
|
|
40
|
+
|
|
41
|
+
const results = new Map();
|
|
42
|
+
const resourcesPath = path.resolve(process.cwd(), 'src', 'resources');
|
|
43
|
+
const resourcesDir = fs.readdirSync(resourcesPath);
|
|
44
|
+
|
|
45
|
+
for (const resource of resourcesDir) {
|
|
46
|
+
const resourcePath = path.join(resourcesPath, resource);
|
|
47
|
+
if (!isDirectory(resourcePath)) continue;
|
|
48
|
+
|
|
49
|
+
const contents = fs.readdirSync(resourcePath);
|
|
50
|
+
const isGroup = contents.some((content) => isDirectory(path.join(resourcePath, content)));
|
|
51
|
+
const isAllDir = contents.every((content) => isDirectory(path.join(resourcePath, content)));
|
|
52
|
+
|
|
53
|
+
if (isGroup && !isAllDir) {
|
|
54
|
+
throw new Error(`Documentation groups must only contain directories. ${resourcePath} does not`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isGroup) {
|
|
58
|
+
if (contents.includes('README.md')) {
|
|
59
|
+
results.set(resource, resource);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
for (const innerDir of contents) {
|
|
63
|
+
const innerDirReadme = path.join(resourcePath, innerDir, 'README.md');
|
|
64
|
+
if (isFile(innerDirReadme)) {
|
|
65
|
+
results.set(innerDir, path.relative('./src/resources', path.join(resourcePath, innerDir)));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isDirectory(path) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.statSync(path).isDirectory();
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isFile(path) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.statSync(path).isFile();
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
VerbosityLevel.set(3);
|
|
91
|
+
const $ = new SequentialPty();
|
|
92
|
+
|
|
93
|
+
await $.spawn('rm -rf ./dist')
|
|
94
|
+
await $.spawn('npm run rollup -- -f es', { interactive: true });
|
|
95
|
+
|
|
96
|
+
const plugin = fork(
|
|
97
|
+
'./dist/index.js',
|
|
98
|
+
[],
|
|
99
|
+
{
|
|
100
|
+
// Use default true to test plugins in secure mode (un-able to request sudo directly)
|
|
101
|
+
detached: true,
|
|
102
|
+
env: { ...process.env },
|
|
103
|
+
execArgv: ['--import', 'tsx/esm'],
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const initializeResult = await sendMessageAndAwaitResponse(plugin, {
|
|
108
|
+
cmd: 'initialize',
|
|
109
|
+
data: {}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const { resourceDefinitions } = initializeResult;
|
|
113
|
+
const resourceTypes = resourceDefinitions.map((i) => i.type);
|
|
114
|
+
const resourceInfoMap = new Map();
|
|
115
|
+
|
|
116
|
+
const schemasMap = new Map()
|
|
117
|
+
for (const type of resourceTypes) {
|
|
118
|
+
const resourceInfo = await sendMessageAndAwaitResponse(plugin, {
|
|
119
|
+
cmd: 'getResourceInfo',
|
|
120
|
+
data: { type }
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
schemasMap.set(type, resourceInfo.schema);
|
|
124
|
+
resourceInfoMap.set(type, resourceInfo);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(resourceInfoMap);
|
|
128
|
+
|
|
129
|
+
const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => {
|
|
130
|
+
// const resolvedSchema = await $RefParser.dereference(schema)
|
|
131
|
+
const resourceSchema = JSON.parse(JSON.stringify(ResourceSchema));
|
|
132
|
+
|
|
133
|
+
delete resourceSchema.$id;
|
|
134
|
+
delete resourceSchema.$schema;
|
|
135
|
+
delete resourceSchema.title;
|
|
136
|
+
delete resourceSchema.oneOf;
|
|
137
|
+
delete resourceSchema.properties.type;
|
|
138
|
+
|
|
139
|
+
if (schema) {
|
|
140
|
+
delete schema.$id;
|
|
141
|
+
delete schema.$schema;
|
|
142
|
+
delete schema.title;
|
|
143
|
+
delete schema.oneOf;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return mergeJsonSchemas([schema ?? {}, resourceSchema, { properties: { type: { const: type, type: 'string' } } }]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
await $.spawn('rm -rf ./dist')
|
|
151
|
+
await $.spawn('npm run rollup', { interactive: true }); // re-run rollup without building for es
|
|
152
|
+
|
|
153
|
+
console.log('Generated JSON Schemas for all resources')
|
|
154
|
+
|
|
155
|
+
const distFolder = path.resolve(process.cwd(), 'dist');
|
|
156
|
+
const schemaOutputPath = path.resolve(distFolder, 'schemas.json');
|
|
157
|
+
fs.writeFileSync(schemaOutputPath, JSON.stringify(mergedSchemas, null, 2));
|
|
158
|
+
|
|
159
|
+
console.log('Successfully wrote schema to ./dist/schemas.json');
|
|
160
|
+
|
|
161
|
+
const documentationMap = fetchDocumentationMaps();
|
|
162
|
+
|
|
163
|
+
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync('./dist/manifest.json', JSON.stringify({
|
|
166
|
+
name: packageJson.name,
|
|
167
|
+
version: packageJson.version,
|
|
168
|
+
description: packageJson.description,
|
|
169
|
+
resources: [...resourceInfoMap.values()].map((info) => ({
|
|
170
|
+
type: info.type,
|
|
171
|
+
description: info.description ?? info.schema?.description,
|
|
172
|
+
sensitiveParameters: info.sensitiveParameters,
|
|
173
|
+
schema: info.schema,
|
|
174
|
+
operatingSystems: info.operatingSystems,
|
|
175
|
+
documentationKey: documentationMap.get(info.type),
|
|
176
|
+
})),
|
|
177
|
+
}, null, 2), 'utf8');
|
|
178
|
+
|
|
179
|
+
for (const key of documentationMap.values()) {
|
|
180
|
+
fs.mkdirSync(path.join('dist', 'documentation', key), { recursive: true })
|
|
181
|
+
|
|
182
|
+
fs.copyFileSync(
|
|
183
|
+
path.resolve(path.join('src', 'resources', key, 'README.md')),
|
|
184
|
+
path.resolve(path.join('dist', 'documentation', key, 'README.md')),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
plugin.kill(9);
|
|
189
|
+
process.exit(0);
|
package/dist/bin/build.js
CHANGED
|
File without changes
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,16 @@ export * from './plan/change-set.js';
|
|
|
5
5
|
export * from './plan/plan.js';
|
|
6
6
|
export * from './plan/plan-types.js';
|
|
7
7
|
export * from './plugin/plugin.js';
|
|
8
|
+
export * from './pty/background-pty.js';
|
|
8
9
|
export * from './pty/index.js';
|
|
10
|
+
export * from './pty/seqeuntial-pty.js';
|
|
9
11
|
export * from './resource/parsed-resource-settings.js';
|
|
10
12
|
export * from './resource/resource.js';
|
|
11
13
|
export * from './resource/resource-settings.js';
|
|
12
14
|
export * from './stateful-parameter/stateful-parameter.js';
|
|
15
|
+
export * from './utils/file-utils.js';
|
|
13
16
|
export * from './utils/functions.js';
|
|
14
17
|
export * from './utils/index.js';
|
|
15
18
|
export * from './utils/verbosity-level.js';
|
|
19
|
+
export * from 'zod/v4';
|
|
16
20
|
export declare function runPlugin(plugin: Plugin): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -5,14 +5,18 @@ export * from './plan/change-set.js';
|
|
|
5
5
|
export * from './plan/plan.js';
|
|
6
6
|
export * from './plan/plan-types.js';
|
|
7
7
|
export * from './plugin/plugin.js';
|
|
8
|
+
export * from './pty/background-pty.js';
|
|
8
9
|
export * from './pty/index.js';
|
|
10
|
+
export * from './pty/seqeuntial-pty.js';
|
|
9
11
|
export * from './resource/parsed-resource-settings.js';
|
|
10
12
|
export * from './resource/resource.js';
|
|
11
13
|
export * from './resource/resource-settings.js';
|
|
12
14
|
export * from './stateful-parameter/stateful-parameter.js';
|
|
15
|
+
export * from './utils/file-utils.js';
|
|
13
16
|
export * from './utils/functions.js';
|
|
14
17
|
export * from './utils/index.js';
|
|
15
18
|
export * from './utils/verbosity-level.js';
|
|
19
|
+
export * from 'zod/v4';
|
|
16
20
|
export async function runPlugin(plugin) {
|
|
17
21
|
const messageHandler = new MessageHandler(plugin);
|
|
18
22
|
process.on('message', (message) => messageHandler.onMessage(message));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ajv } from 'ajv';
|
|
2
2
|
import addFormats from 'ajv-formats';
|
|
3
|
-
import { ApplyRequestDataSchema,
|
|
3
|
+
import { ApplyRequestDataSchema, EmptyResponseDataSchema, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, ImportResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, IpcMessageV2Schema, MatchRequestDataSchema, MatchResponseDataSchema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, SetVerbosityRequestDataSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
|
|
4
4
|
import { SudoError } from '../errors.js';
|
|
5
5
|
const SupportedRequests = {
|
|
6
6
|
'initialize': {
|
|
@@ -18,6 +18,14 @@ const SupportedRequests = {
|
|
|
18
18
|
requestValidator: GetResourceInfoRequestDataSchema,
|
|
19
19
|
responseValidator: GetResourceInfoResponseDataSchema
|
|
20
20
|
},
|
|
21
|
+
'setVerbosityLevel': {
|
|
22
|
+
async handler(plugin, data) {
|
|
23
|
+
await plugin.setVerbosityLevel(data);
|
|
24
|
+
return null;
|
|
25
|
+
},
|
|
26
|
+
requestValidator: SetVerbosityRequestDataSchema,
|
|
27
|
+
responseValidator: EmptyResponseDataSchema,
|
|
28
|
+
},
|
|
21
29
|
'match': {
|
|
22
30
|
handler: async (plugin, data) => plugin.match(data),
|
|
23
31
|
requestValidator: MatchRequestDataSchema,
|
|
@@ -39,7 +47,7 @@ const SupportedRequests = {
|
|
|
39
47
|
return null;
|
|
40
48
|
},
|
|
41
49
|
requestValidator: ApplyRequestDataSchema,
|
|
42
|
-
responseValidator:
|
|
50
|
+
responseValidator: EmptyResponseDataSchema
|
|
43
51
|
},
|
|
44
52
|
};
|
|
45
53
|
export class MessageHandler {
|
package/dist/plugin/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApplyRequestData, GetResourceInfoRequestData, GetResourceInfoResponseData, ImportRequestData, ImportResponseData, InitializeRequestData, InitializeResponseData, MatchRequestData, MatchResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ResourceJson, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
|
|
1
|
+
import { ApplyRequestData, GetResourceInfoRequestData, GetResourceInfoResponseData, ImportRequestData, ImportResponseData, InitializeRequestData, InitializeResponseData, MatchRequestData, MatchResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ResourceJson, SetVerbosityRequestData, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
|
|
2
2
|
import { Plan } from '../plan/plan.js';
|
|
3
3
|
import { BackgroundPty } from '../pty/background-pty.js';
|
|
4
4
|
import { Resource } from '../resource/resource.js';
|
|
@@ -17,6 +17,7 @@ export declare class Plugin {
|
|
|
17
17
|
validate(data: ValidateRequestData): Promise<ValidateResponseData>;
|
|
18
18
|
plan(data: PlanRequestData): Promise<PlanResponseData>;
|
|
19
19
|
apply(data: ApplyRequestData): Promise<void>;
|
|
20
|
+
setVerbosityLevel(data: SetVerbosityRequestData): Promise<void>;
|
|
20
21
|
kill(): Promise<void>;
|
|
21
22
|
private resolvePlan;
|
|
22
23
|
protected crossValidateResources(resources: ResourceJson[]): Promise<void>;
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -55,7 +55,7 @@ export class Plugin {
|
|
|
55
55
|
throw new Error(`Cannot get info for resource ${data.type}, resource doesn't exist`);
|
|
56
56
|
}
|
|
57
57
|
const resource = this.resourceControllers.get(data.type);
|
|
58
|
-
const schema = resource.
|
|
58
|
+
const schema = resource.parsedSettings.schema;
|
|
59
59
|
const requiredPropertyNames = (resource.settings.importAndDestroy?.requiredParameters
|
|
60
60
|
?? (typeof resource.settings.allowMultiple === 'object' ? resource.settings.allowMultiple.identifyingParameters : null)
|
|
61
61
|
?? schema?.required
|
|
@@ -173,6 +173,9 @@ export class Plugin {
|
|
|
173
173
|
throw new ApplyValidationError(plan);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
+
async setVerbosityLevel(data) {
|
|
177
|
+
VerbosityLevel.set(data.verbosityLevel);
|
|
178
|
+
}
|
|
176
179
|
async kill() {
|
|
177
180
|
await this.planPty.kill();
|
|
178
181
|
}
|
|
@@ -6,11 +6,12 @@ import { IPty, SpawnOptions, SpawnResult } from './index.js';
|
|
|
6
6
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
7
7
|
*/
|
|
8
8
|
export declare class BackgroundPty implements IPty {
|
|
9
|
+
private historyIgnore;
|
|
9
10
|
private basePty;
|
|
10
11
|
private promiseQueue;
|
|
11
12
|
constructor();
|
|
12
|
-
spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
13
|
-
spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
13
|
+
spawn(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
14
|
+
spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
14
15
|
kill(): Promise<{
|
|
15
16
|
exitCode: number;
|
|
16
17
|
signal?: number | undefined;
|
|
@@ -17,8 +17,11 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
17
17
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
18
18
|
*/
|
|
19
19
|
export class BackgroundPty {
|
|
20
|
+
historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
20
21
|
basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
|
|
21
|
-
env: process.env,
|
|
22
|
+
env: { ...process.env, ...this.historyIgnore },
|
|
23
|
+
cols: 10_000, // Set to a really large value to prevent wrapping
|
|
24
|
+
name: nanoid(6),
|
|
22
25
|
handleFlowControl: true
|
|
23
26
|
});
|
|
24
27
|
promiseQueue = new PromiseQueue();
|
|
@@ -28,11 +31,12 @@ export class BackgroundPty {
|
|
|
28
31
|
async spawn(cmd, options) {
|
|
29
32
|
const spawnResult = await this.spawnSafe(cmd, options);
|
|
30
33
|
if (spawnResult.status !== 'success') {
|
|
31
|
-
throw new SpawnError(cmd, spawnResult.exitCode, spawnResult.data);
|
|
34
|
+
throw new SpawnError(Array.isArray(cmd) ? cmd.join(' ') : cmd, spawnResult.exitCode, spawnResult.data);
|
|
32
35
|
}
|
|
33
36
|
return spawnResult;
|
|
34
37
|
}
|
|
35
38
|
async spawnSafe(cmd, options) {
|
|
39
|
+
cmd = Array.isArray(cmd) ? cmd.join('\\\n') : cmd;
|
|
36
40
|
// cid is command id
|
|
37
41
|
const cid = nanoid(10);
|
|
38
42
|
debugLog(cid);
|
|
@@ -84,7 +88,7 @@ export class BackgroundPty {
|
|
|
84
88
|
resolve(null);
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
|
-
console.log(`Running command ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`);
|
|
91
|
+
console.log(`Running command: ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`);
|
|
88
92
|
this.basePty.write(`${command}\r`);
|
|
89
93
|
}));
|
|
90
94
|
}).finally(async () => {
|
|
@@ -104,17 +108,6 @@ export class BackgroundPty {
|
|
|
104
108
|
await this.promiseQueue.run(async () => {
|
|
105
109
|
let outputBuffer = '';
|
|
106
110
|
return new Promise(resolve => {
|
|
107
|
-
// zsh-specific commands
|
|
108
|
-
switch (Utils.getShell()) {
|
|
109
|
-
case Shell.ZSH: {
|
|
110
|
-
this.basePty.write('setopt HIST_NO_STORE;\n');
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
default: {
|
|
114
|
-
this.basePty.write('export HISTIGNORE=\'history*\';\n');
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
111
|
this.basePty.write(' unset PS1;\n');
|
|
119
112
|
this.basePty.write(' unset PS0;\n');
|
|
120
113
|
this.basePty.write(' echo setup complete\\"\n');
|
package/dist/pty/index.d.ts
CHANGED
|
@@ -21,11 +21,17 @@ export declare enum SpawnStatus {
|
|
|
21
21
|
*
|
|
22
22
|
* @property {boolean} [interactive] - Indicates whether the spawned process needs
|
|
23
23
|
* to be interactive. Only works within apply (not plan). Defaults to true.
|
|
24
|
+
*
|
|
25
|
+
* @property {boolean} [disableWrapping] - Forces the terminal width to 10_000 to disable wrapping.
|
|
26
|
+
* In applys, this is off by default while it is on during plans.
|
|
24
27
|
*/
|
|
25
28
|
export interface SpawnOptions {
|
|
26
29
|
cwd?: string;
|
|
27
30
|
env?: Record<string, unknown>;
|
|
28
31
|
interactive?: boolean;
|
|
32
|
+
requiresRoot?: boolean;
|
|
33
|
+
stdin?: boolean;
|
|
34
|
+
disableWrapping?: boolean;
|
|
29
35
|
}
|
|
30
36
|
export declare class SpawnError extends Error {
|
|
31
37
|
data: string;
|
|
@@ -34,8 +40,8 @@ export declare class SpawnError extends Error {
|
|
|
34
40
|
constructor(cmd: string, exitCode: number, data: string);
|
|
35
41
|
}
|
|
36
42
|
export interface IPty {
|
|
37
|
-
spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
38
|
-
spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
43
|
+
spawn(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
44
|
+
spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
39
45
|
kill(): Promise<{
|
|
40
46
|
exitCode: number;
|
|
41
47
|
signal?: number | undefined;
|
|
@@ -6,11 +6,12 @@ import { IPty, SpawnOptions, SpawnResult } from './index.js';
|
|
|
6
6
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
7
7
|
*/
|
|
8
8
|
export declare class SequentialPty implements IPty {
|
|
9
|
-
spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
10
|
-
spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult>;
|
|
9
|
+
spawn(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
10
|
+
spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>;
|
|
11
11
|
kill(): Promise<{
|
|
12
12
|
exitCode: number;
|
|
13
13
|
signal?: number | undefined;
|
|
14
14
|
}>;
|
|
15
|
+
externalSpawn(cmd: string, opts: SpawnOptions): Promise<SpawnResult>;
|
|
15
16
|
private getDefaultShell;
|
|
16
17
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import pty from '@homebridge/node-pty-prebuilt-multiarch';
|
|
2
|
+
import { Ajv } from 'ajv';
|
|
3
|
+
import { CommandRequestResponseDataSchema, MessageCmd } from 'codify-schemas';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
2
5
|
import { EventEmitter } from 'node:events';
|
|
3
6
|
import stripAnsi from 'strip-ansi';
|
|
4
7
|
import { Shell, Utils } from '../utils/index.js';
|
|
5
8
|
import { VerbosityLevel } from '../utils/verbosity-level.js';
|
|
6
9
|
import { SpawnError, SpawnStatus } from './index.js';
|
|
7
10
|
EventEmitter.defaultMaxListeners = 1000;
|
|
11
|
+
const ajv = new Ajv({
|
|
12
|
+
strict: true,
|
|
13
|
+
});
|
|
14
|
+
const validateSudoRequestResponse = ajv.compile(CommandRequestResponseDataSchema);
|
|
8
15
|
/**
|
|
9
16
|
* The background pty is a specialized pty designed for speed. It can launch multiple tasks
|
|
10
17
|
* in parallel by moving them to the background. It attaches unix FIFO pipes to each process
|
|
@@ -15,12 +22,20 @@ export class SequentialPty {
|
|
|
15
22
|
async spawn(cmd, options) {
|
|
16
23
|
const spawnResult = await this.spawnSafe(cmd, options);
|
|
17
24
|
if (spawnResult.status !== 'success') {
|
|
18
|
-
throw new SpawnError(cmd, spawnResult.exitCode, spawnResult.data);
|
|
25
|
+
throw new SpawnError(Array.isArray(cmd) ? cmd.join('\n') : cmd, spawnResult.exitCode, spawnResult.data);
|
|
19
26
|
}
|
|
20
27
|
return spawnResult;
|
|
21
28
|
}
|
|
22
29
|
async spawnSafe(cmd, options) {
|
|
23
|
-
|
|
30
|
+
cmd = Array.isArray(cmd) ? cmd.join(' ') : cmd;
|
|
31
|
+
if (cmd.includes('sudo')) {
|
|
32
|
+
throw new Error('Do not directly use sudo. Use the option { requiresRoot: true } instead');
|
|
33
|
+
}
|
|
34
|
+
// If sudo is required, we must delegate to the main codify process.
|
|
35
|
+
if (options?.stdin || options?.requiresRoot) {
|
|
36
|
+
return this.externalSpawn(cmd, options);
|
|
37
|
+
}
|
|
38
|
+
console.log(`Running command: ${Array.isArray(cmd) ? cmd.join('\\\n') : cmd}` + (options?.cwd ? `(${options?.cwd})` : ''));
|
|
24
39
|
return new Promise((resolve) => {
|
|
25
40
|
const output = [];
|
|
26
41
|
const historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
|
|
@@ -30,12 +45,14 @@ export class SequentialPty {
|
|
|
30
45
|
...process.env, ...options?.env,
|
|
31
46
|
TERM_PROGRAM: 'codify',
|
|
32
47
|
COMMAND_MODE: 'unix2003',
|
|
33
|
-
COLORTERM: 'truecolor',
|
|
48
|
+
COLORTERM: 'truecolor',
|
|
49
|
+
...historyIgnore
|
|
34
50
|
};
|
|
35
51
|
// Initial terminal dimensions
|
|
36
|
-
|
|
52
|
+
// Set to a really large value to prevent wrapping
|
|
53
|
+
const initialCols = options?.disableWrapping ? 10_000 : process.stdout.columns ?? 80;
|
|
37
54
|
const initialRows = process.stdout.rows ?? 24;
|
|
38
|
-
const args =
|
|
55
|
+
const args = options?.interactive ? ['-i', '-c', cmd] : ['-c', cmd];
|
|
39
56
|
// Run the command in a pty for interactivity
|
|
40
57
|
const mPty = pty.spawn(this.getDefaultShell(), args, {
|
|
41
58
|
...options,
|
|
@@ -49,20 +66,14 @@ export class SequentialPty {
|
|
|
49
66
|
}
|
|
50
67
|
output.push(data.toString());
|
|
51
68
|
});
|
|
52
|
-
const stdinListener = (data) => {
|
|
53
|
-
mPty.write(data.toString());
|
|
54
|
-
};
|
|
55
69
|
const resizeListener = () => {
|
|
56
70
|
const { columns, rows } = process.stdout;
|
|
57
|
-
mPty.resize(columns, rows);
|
|
71
|
+
mPty.resize(columns, options?.disableWrapping ? 10_000 : rows);
|
|
58
72
|
};
|
|
59
73
|
// Listen to resize events for the terminal window;
|
|
60
74
|
process.stdout.on('resize', resizeListener);
|
|
61
|
-
// Listen for user input
|
|
62
|
-
process.stdin.on('data', stdinListener);
|
|
63
75
|
mPty.onExit((result) => {
|
|
64
76
|
process.stdout.off('resize', resizeListener);
|
|
65
|
-
process.stdin.off('data', stdinListener);
|
|
66
77
|
resolve({
|
|
67
78
|
status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
68
79
|
exitCode: result.exitCode,
|
|
@@ -78,6 +89,30 @@ export class SequentialPty {
|
|
|
78
89
|
signal: 0,
|
|
79
90
|
};
|
|
80
91
|
}
|
|
92
|
+
// For safety reasons, requests that require sudo or are interactive must be run via the main client
|
|
93
|
+
async externalSpawn(cmd, opts) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
const requestId = nanoid(8);
|
|
96
|
+
const listener = (data) => {
|
|
97
|
+
if (data.requestId === requestId) {
|
|
98
|
+
process.removeListener('message', listener);
|
|
99
|
+
if (!validateSudoRequestResponse(data.data)) {
|
|
100
|
+
throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
|
|
101
|
+
}
|
|
102
|
+
resolve(data.data);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
process.on('message', listener);
|
|
106
|
+
process.send({
|
|
107
|
+
cmd: MessageCmd.COMMAND_REQUEST,
|
|
108
|
+
data: {
|
|
109
|
+
command: cmd,
|
|
110
|
+
options: opts ?? {},
|
|
111
|
+
},
|
|
112
|
+
requestId
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
81
116
|
getDefaultShell() {
|
|
82
117
|
return process.env.SHELL;
|
|
83
118
|
}
|
|
@@ -18,10 +18,12 @@ export type ParsedParameterSetting = {
|
|
|
18
18
|
export declare class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
|
|
19
19
|
private cache;
|
|
20
20
|
id: string;
|
|
21
|
+
description?: string;
|
|
21
22
|
schema?: Partial<JSONSchemaType<T | any>>;
|
|
22
23
|
allowMultiple?: {
|
|
24
|
+
identifyingParameters?: string[];
|
|
23
25
|
matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
|
|
24
|
-
|
|
26
|
+
findAllParameters?: () => Promise<Array<Partial<T>>>;
|
|
25
27
|
} | boolean;
|
|
26
28
|
removeStatefulParametersBeforeDestroy?: boolean | undefined;
|
|
27
29
|
dependencies?: string[] | undefined;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { ZodObject, z } from 'zod';
|
|
1
2
|
import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
|
|
2
3
|
import { resolveElementEqualsFn, resolveEqualsFn, resolveMatcher, resolveParameterTransformFn } from './resource-settings.js';
|
|
3
4
|
export class ParsedResourceSettings {
|
|
4
5
|
cache = new Map();
|
|
5
6
|
id;
|
|
7
|
+
description;
|
|
6
8
|
schema;
|
|
7
9
|
allowMultiple;
|
|
8
10
|
removeStatefulParametersBeforeDestroy;
|
|
@@ -13,8 +15,19 @@ export class ParsedResourceSettings {
|
|
|
13
15
|
settings;
|
|
14
16
|
constructor(settings) {
|
|
15
17
|
this.settings = settings;
|
|
16
|
-
const { parameterSettings, ...rest } = settings;
|
|
18
|
+
const { parameterSettings, schema, ...rest } = settings;
|
|
17
19
|
Object.assign(this, rest);
|
|
20
|
+
if (schema) {
|
|
21
|
+
this.schema = schema instanceof ZodObject
|
|
22
|
+
? z.toJSONSchema(schema.strict(), {
|
|
23
|
+
target: 'draft-7',
|
|
24
|
+
override(ctx) {
|
|
25
|
+
ctx.jsonSchema.title = settings.id;
|
|
26
|
+
ctx.jsonSchema.description = settings.description ?? `${settings.id} resource. Can be used to manage ${settings.id}`;
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
: schema;
|
|
30
|
+
}
|
|
18
31
|
this.validateSettings();
|
|
19
32
|
}
|
|
20
33
|
get typeId() {
|
|
@@ -118,7 +131,7 @@ export class ParsedResourceSettings {
|
|
|
118
131
|
&& typeof this.settings.allowMultiple === 'object' && this.settings.allowMultiple?.identifyingParameters?.includes(k))) {
|
|
119
132
|
throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed to be identifying parameters for allowMultiple.`);
|
|
120
133
|
}
|
|
121
|
-
const schema = this.
|
|
134
|
+
const schema = this.schema;
|
|
122
135
|
if (!this.settings.importAndDestroy && (schema?.oneOf
|
|
123
136
|
&& Array.isArray(schema.oneOf)
|
|
124
137
|
&& schema.oneOf.some((s) => s.required))
|
|
@@ -17,16 +17,16 @@ export class ResourceController {
|
|
|
17
17
|
this.settings = resource.getSettings();
|
|
18
18
|
this.typeId = this.settings.id;
|
|
19
19
|
this.dependencies = this.settings.dependencies ?? [];
|
|
20
|
-
|
|
20
|
+
this.parsedSettings = new ParsedResourceSettings(this.settings);
|
|
21
|
+
if (this.parsedSettings.schema) {
|
|
21
22
|
this.ajv = new Ajv({
|
|
22
23
|
allErrors: true,
|
|
23
24
|
strict: true,
|
|
24
25
|
strictRequired: false,
|
|
25
26
|
allowUnionTypes: true
|
|
26
27
|
});
|
|
27
|
-
this.schemaValidator = this.ajv.compile(this.
|
|
28
|
+
this.schemaValidator = this.ajv.compile(this.parsedSettings.schema);
|
|
28
29
|
}
|
|
29
|
-
this.parsedSettings = new ParsedResourceSettings(this.settings);
|
|
30
30
|
}
|
|
31
31
|
async initialize() {
|
|
32
32
|
return this.resource.initialize();
|
|
@@ -374,8 +374,8 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
374
374
|
.sort((a, b) => this.parsedSettings.statefulParameterOrder.get(a.name) - this.parsedSettings.statefulParameterOrder.get(b.name));
|
|
375
375
|
}
|
|
376
376
|
getAllParameterKeys() {
|
|
377
|
-
return this.
|
|
378
|
-
? Object.keys(this.
|
|
377
|
+
return this.parsedSettings.schema
|
|
378
|
+
? Object.keys(this.parsedSettings.schema?.properties)
|
|
379
379
|
: Object.keys(this.parsedSettings.parameterSettings);
|
|
380
380
|
}
|
|
381
381
|
getParametersToRefreshForImport(parameters, context) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { JSONSchemaType } from 'ajv';
|
|
2
2
|
import { OS, StringIndexedObject } from 'codify-schemas';
|
|
3
|
+
import { ZodObject } from 'zod';
|
|
3
4
|
import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
|
|
5
|
+
import { ParsedResourceSettings } from './parsed-resource-settings.js';
|
|
4
6
|
import { RefreshContext } from './resource.js';
|
|
5
7
|
export interface InputTransformation {
|
|
6
8
|
to: (input: any) => Promise<any> | any;
|
|
@@ -21,12 +23,16 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
21
23
|
/**
|
|
22
24
|
* Schema to validate user configs with. Must be in the format JSON Schema draft07
|
|
23
25
|
*/
|
|
24
|
-
schema?: Partial<JSONSchemaType<T | any
|
|
26
|
+
schema?: Partial<JSONSchemaType<T | any>> | ZodObject;
|
|
25
27
|
/**
|
|
26
28
|
* Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import.
|
|
27
29
|
* This differs from the parameter level sensitivity which also prevents the parameter value from being displayed in the plan.
|
|
28
30
|
*/
|
|
29
31
|
isSensitive?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* An optional description of the resource. This does not affect the behavior of the resource.
|
|
34
|
+
*/
|
|
35
|
+
description?: string;
|
|
30
36
|
/**
|
|
31
37
|
* Allow multiple of the same resource to unique. Set truthy if
|
|
32
38
|
* multiples are allowed, for example for applications, there can be multiple copy of the same application installed
|
|
@@ -290,4 +296,4 @@ export declare function resolveEqualsFn(parameter: ParameterSetting): (desired:
|
|
|
290
296
|
export declare function resolveElementEqualsFn(parameter: ArrayParameterSetting): (desired: unknown, current: unknown) => boolean;
|
|
291
297
|
export declare function resolveFnFromEqualsFnOrString(fnOrString: ((a: unknown, b: unknown) => boolean) | ParameterSettingType | undefined): ((a: unknown, b: unknown) => boolean) | undefined;
|
|
292
298
|
export declare function resolveParameterTransformFn(parameter: ParameterSetting): InputTransformation | undefined;
|
|
293
|
-
export declare function resolveMatcher<T extends StringIndexedObject>(settings:
|
|
299
|
+
export declare function resolveMatcher<T extends StringIndexedObject>(settings: ParsedResourceSettings<T>): (desired: Partial<T>, current: Partial<T>) => boolean;
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { addVariablesToPath, areArraysEqual, resolvePathWithVariables, tildify, untildify } from '../utils/functions.js';
|
|
4
4
|
const ParameterEqualsDefaults = {
|
|
5
5
|
'boolean': (a, b) => Boolean(a) === Boolean(b),
|
|
6
|
-
'directory'
|
|
6
|
+
'directory'(a, b) {
|
|
7
7
|
let transformedA = resolvePathWithVariables(untildify(String(a)));
|
|
8
8
|
let transformedB = resolvePathWithVariables(untildify(String(b)));
|
|
9
9
|
if (transformedA.startsWith('.')) { // Only relative paths start with '.'
|
|
@@ -65,7 +65,7 @@ export function resolveFnFromEqualsFnOrString(fnOrString) {
|
|
|
65
65
|
const ParameterTransformationDefaults = {
|
|
66
66
|
'directory': {
|
|
67
67
|
to: (a) => resolvePathWithVariables((untildify(String(a)))),
|
|
68
|
-
from
|
|
68
|
+
from(a, original) {
|
|
69
69
|
if (ParameterEqualsDefaults.directory(a, original)) {
|
|
70
70
|
return original;
|
|
71
71
|
}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|