codify-plugin-lib 1.0.179 → 1.0.181
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/bin/build.d.ts +1 -0
- package/dist/bin/build.js +80 -0
- package/dist/bin/deploy-plugin.d.ts +2 -0
- package/dist/bin/deploy-plugin.js +8 -0
- package/dist/plugin/plugin.js +21 -6
- package/dist/pty/background-pty.d.ts +1 -0
- package/dist/pty/background-pty.js +8 -2
- package/dist/resource/resource-settings.d.ts +5 -0
- package/dist/resource/resource-settings.js +3 -2
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +2 -0
- package/dist/utils/codify-spawn.d.ts +29 -0
- package/dist/utils/codify-spawn.js +136 -0
- package/dist/utils/pty-local-storage.d.ts +0 -1
- package/package.json +16 -11
- package/src/plugin/plugin.ts +24 -6
- package/src/pty/background-pty.ts +9 -2
- package/src/resource/resource-settings.ts +9 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function build(): Promise<void>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { IpcMessageSchema, MessageStatus, ResourceSchema } from 'codify-schemas';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import mergeJsonSchemas from 'merge-json-schemas';
|
|
5
|
+
import { fork } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import * as url from 'node:url';
|
|
9
|
+
import { codifySpawn } from '../utils/codify-spawn.js';
|
|
10
|
+
const ajv = new Ajv({
|
|
11
|
+
strict: true
|
|
12
|
+
});
|
|
13
|
+
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
|
|
14
|
+
function sendMessageAndAwaitResponse(process, message) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
process.on('message', (response) => {
|
|
17
|
+
if (!ipcMessageValidator(response)) {
|
|
18
|
+
throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
|
|
19
|
+
}
|
|
20
|
+
// Wait for the message response. Other messages such as sudoRequest may be sent before the response returns
|
|
21
|
+
if (response.cmd === message.cmd + '_Response') {
|
|
22
|
+
if (response.status === MessageStatus.SUCCESS) {
|
|
23
|
+
resolve(response.data);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
reject(new Error(String(response.data)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// Send message last to ensure listeners are all registered
|
|
31
|
+
process.send(message);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function build() {
|
|
35
|
+
await fs.rm('./dist', { force: true, recursive: true });
|
|
36
|
+
await codifySpawn('npm run rollup -- -f es');
|
|
37
|
+
const plugin = fork('./dist/index.js', [], {
|
|
38
|
+
// Use default true to test plugins in secure mode (un-able to request sudo directly)
|
|
39
|
+
detached: true,
|
|
40
|
+
env: { ...process.env },
|
|
41
|
+
execArgv: ['--import', 'tsx/esm'],
|
|
42
|
+
});
|
|
43
|
+
const initializeResult = await sendMessageAndAwaitResponse(plugin, {
|
|
44
|
+
cmd: 'initialize',
|
|
45
|
+
data: {}
|
|
46
|
+
});
|
|
47
|
+
const { resourceDefinitions } = initializeResult;
|
|
48
|
+
const resourceTypes = resourceDefinitions.map((i) => i.type);
|
|
49
|
+
const schemasMap = new Map();
|
|
50
|
+
for (const type of resourceTypes) {
|
|
51
|
+
const resourceInfo = await sendMessageAndAwaitResponse(plugin, {
|
|
52
|
+
cmd: 'getResourceInfo',
|
|
53
|
+
data: { type }
|
|
54
|
+
});
|
|
55
|
+
schemasMap.set(type, resourceInfo.schema);
|
|
56
|
+
}
|
|
57
|
+
const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => {
|
|
58
|
+
// const resolvedSchema = await $RefParser.dereference(schema)
|
|
59
|
+
const resourceSchema = JSON.parse(JSON.stringify(ResourceSchema));
|
|
60
|
+
delete resourceSchema.$id;
|
|
61
|
+
delete resourceSchema.$schema;
|
|
62
|
+
delete resourceSchema.title;
|
|
63
|
+
delete resourceSchema.oneOf;
|
|
64
|
+
delete resourceSchema.properties.type;
|
|
65
|
+
if (schema) {
|
|
66
|
+
delete schema.$id;
|
|
67
|
+
delete schema.$schema;
|
|
68
|
+
delete schema.title;
|
|
69
|
+
delete schema.oneOf;
|
|
70
|
+
}
|
|
71
|
+
return mergeJsonSchemas([schema ?? {}, resourceSchema, { properties: { type: { const: type, type: 'string' } } }]);
|
|
72
|
+
});
|
|
73
|
+
await fs.rm('./dist', { force: true, recursive: true });
|
|
74
|
+
await codifySpawn('npm run rollup'); // re-run rollup without building for es
|
|
75
|
+
console.log('Generated JSON Schemas for all resources');
|
|
76
|
+
const distFolder = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', 'dist');
|
|
77
|
+
const schemaOutputPath = path.resolve(distFolder, 'schemas.json');
|
|
78
|
+
await fs.writeFile(schemaOutputPath, JSON.stringify(mergedSchemas, null, 2));
|
|
79
|
+
console.log('Successfully wrote schema to ./dist/schemas.json');
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import { build } from './build.js';
|
|
5
|
+
const packageJson = fs.readFileSync(path.join(process.env['npm_config_local_prefix'], 'package.json'), 'utf8');
|
|
6
|
+
const { name: libraryName, version: libraryVersion } = JSON.parse(packageJson);
|
|
7
|
+
console.log(libraryName, libraryVersion);
|
|
8
|
+
await build();
|
package/dist/plugin/plugin.js
CHANGED
|
@@ -30,13 +30,22 @@ export class Plugin {
|
|
|
30
30
|
}
|
|
31
31
|
return {
|
|
32
32
|
resourceDefinitions: [...this.resourceControllers.values()]
|
|
33
|
-
.map((r) =>
|
|
34
|
-
|
|
35
|
-
type: r.typeId,
|
|
36
|
-
sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {})
|
|
33
|
+
.map((r) => {
|
|
34
|
+
const sensitiveParameters = Object.entries(r.settings.parameterSettings ?? {})
|
|
37
35
|
.filter(([, v]) => v?.isSensitive)
|
|
38
|
-
.map(([k]) => k)
|
|
39
|
-
|
|
36
|
+
.map(([k]) => k);
|
|
37
|
+
// Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import
|
|
38
|
+
// sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks
|
|
39
|
+
// on a specific sensitive parameter.
|
|
40
|
+
if (r.settings.isSensitive && sensitiveParameters.length === 0) {
|
|
41
|
+
sensitiveParameters.push('*');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
dependencies: r.dependencies,
|
|
45
|
+
type: r.typeId,
|
|
46
|
+
sensitiveParameters,
|
|
47
|
+
};
|
|
48
|
+
})
|
|
40
49
|
};
|
|
41
50
|
}
|
|
42
51
|
getResourceInfo(data) {
|
|
@@ -51,9 +60,15 @@ export class Plugin {
|
|
|
51
60
|
?? undefined);
|
|
52
61
|
const allowMultiple = resource.settings.allowMultiple !== undefined
|
|
53
62
|
&& resource.settings.allowMultiple !== false;
|
|
63
|
+
// Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import
|
|
64
|
+
// sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks
|
|
65
|
+
// on a specific sensitive parameter.
|
|
54
66
|
const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {})
|
|
55
67
|
.filter(([, v]) => v?.isSensitive)
|
|
56
68
|
.map(([k]) => k);
|
|
69
|
+
if (resource.settings.isSensitive && sensitiveParameters.length === 0) {
|
|
70
|
+
sensitiveParameters.push('*');
|
|
71
|
+
}
|
|
57
72
|
return {
|
|
58
73
|
plugin: this.name,
|
|
59
74
|
type: data.type,
|
|
@@ -16,7 +16,7 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
16
16
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
17
17
|
*/
|
|
18
18
|
export class BackgroundPty {
|
|
19
|
-
basePty = pty.spawn(
|
|
19
|
+
basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
|
|
20
20
|
env: process.env, name: nanoid(6),
|
|
21
21
|
handleFlowControl: true
|
|
22
22
|
});
|
|
@@ -103,7 +103,10 @@ export class BackgroundPty {
|
|
|
103
103
|
await this.promiseQueue.run(async () => {
|
|
104
104
|
let outputBuffer = '';
|
|
105
105
|
return new Promise(resolve => {
|
|
106
|
-
|
|
106
|
+
// zsh-specific commands
|
|
107
|
+
if (this.getDefaultShell() === 'zsh') {
|
|
108
|
+
this.basePty.write('setopt hist_ignore_space;\n');
|
|
109
|
+
}
|
|
107
110
|
this.basePty.write(' unset PS1;\n');
|
|
108
111
|
this.basePty.write(' unset PS0;\n');
|
|
109
112
|
this.basePty.write(' echo setup complete\\"\n');
|
|
@@ -117,4 +120,7 @@ export class BackgroundPty {
|
|
|
117
120
|
});
|
|
118
121
|
});
|
|
119
122
|
}
|
|
123
|
+
getDefaultShell() {
|
|
124
|
+
return process.platform === 'darwin' ? 'zsh' : 'bash';
|
|
125
|
+
}
|
|
120
126
|
}
|
|
@@ -18,6 +18,11 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
18
18
|
* Schema to validate user configs with. Must be in the format JSON Schema draft07
|
|
19
19
|
*/
|
|
20
20
|
schema?: Partial<JSONSchemaType<T | any>>;
|
|
21
|
+
/**
|
|
22
|
+
* Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import.
|
|
23
|
+
* This differs from the parameter level sensitivity which also prevents the parameter value from being displayed in the plan.
|
|
24
|
+
*/
|
|
25
|
+
isSensitive?: boolean;
|
|
21
26
|
/**
|
|
22
27
|
* Allow multiple of the same resource to unique. Set truthy if
|
|
23
28
|
* multiples are allowed, for example for applications, there can be multiple copy of the same application installed
|
|
@@ -12,8 +12,9 @@ const ParameterEqualsDefaults = {
|
|
|
12
12
|
if (transformedB.startsWith('.')) { // Only relative paths start with '.'
|
|
13
13
|
transformedB = path.resolve(transformedB);
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// macOS has case-insensitive filesystem by default, Linux is case-sensitive
|
|
16
|
+
const isCaseSensitive = process.platform === 'linux';
|
|
17
|
+
if (!isCaseSensitive) {
|
|
17
18
|
transformedA = transformedA.toLowerCase();
|
|
18
19
|
transformedB = transformedB.toLowerCase();
|
|
19
20
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SpawnOptions } from 'node:child_process';
|
|
2
|
+
export declare enum SpawnStatus {
|
|
3
|
+
SUCCESS = "success",
|
|
4
|
+
ERROR = "error"
|
|
5
|
+
}
|
|
6
|
+
export interface SpawnResult {
|
|
7
|
+
status: SpawnStatus;
|
|
8
|
+
data: string;
|
|
9
|
+
}
|
|
10
|
+
type CodifySpawnOptions = {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
throws?: boolean;
|
|
13
|
+
requiresRoot?: boolean;
|
|
14
|
+
requestsTTY?: boolean;
|
|
15
|
+
} & Omit<SpawnOptions, 'detached' | 'shell' | 'stdio'>;
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param cmd Command to run. Ex: `rm -rf`
|
|
19
|
+
* @param opts Standard options for node spawn. Additional argument:
|
|
20
|
+
* throws determines if a shell will throw a JS error. Defaults to true
|
|
21
|
+
*
|
|
22
|
+
* @see promiseSpawn
|
|
23
|
+
* @see spawn
|
|
24
|
+
*
|
|
25
|
+
* @returns SpawnResult { status: SUCCESS | ERROR; data: string }
|
|
26
|
+
*/
|
|
27
|
+
export declare function codifySpawn(cmd: string, opts?: CodifySpawnOptions): Promise<SpawnResult>;
|
|
28
|
+
export declare function isDebug(): boolean;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { MessageCmd, SudoRequestResponseDataSchema } from 'codify-schemas';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import stripAnsi from 'strip-ansi';
|
|
6
|
+
import { VerbosityLevel } from './utils.js';
|
|
7
|
+
import { SudoError } from '../errors.js';
|
|
8
|
+
const ajv = new Ajv({
|
|
9
|
+
strict: true,
|
|
10
|
+
});
|
|
11
|
+
const validateSudoRequestResponse = ajv.compile(SudoRequestResponseDataSchema);
|
|
12
|
+
export var SpawnStatus;
|
|
13
|
+
(function (SpawnStatus) {
|
|
14
|
+
SpawnStatus["SUCCESS"] = "success";
|
|
15
|
+
SpawnStatus["ERROR"] = "error";
|
|
16
|
+
})(SpawnStatus || (SpawnStatus = {}));
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param cmd Command to run. Ex: `rm -rf`
|
|
20
|
+
* @param opts Standard options for node spawn. Additional argument:
|
|
21
|
+
* throws determines if a shell will throw a JS error. Defaults to true
|
|
22
|
+
*
|
|
23
|
+
* @see promiseSpawn
|
|
24
|
+
* @see spawn
|
|
25
|
+
*
|
|
26
|
+
* @returns SpawnResult { status: SUCCESS | ERROR; data: string }
|
|
27
|
+
*/
|
|
28
|
+
export async function codifySpawn(cmd, opts) {
|
|
29
|
+
const throws = opts?.throws ?? true;
|
|
30
|
+
console.log(`Running command: ${cmd}` + (opts?.cwd ? `(${opts?.cwd})` : ''));
|
|
31
|
+
try {
|
|
32
|
+
// TODO: Need to benchmark the effects of using sh vs zsh for shell.
|
|
33
|
+
// Seems like zsh shells run slower
|
|
34
|
+
const result = await (opts?.requiresRoot
|
|
35
|
+
? externalSpawnWithSudo(cmd, opts)
|
|
36
|
+
: internalSpawn(cmd, opts ?? {}));
|
|
37
|
+
if (result.status !== SpawnStatus.SUCCESS) {
|
|
38
|
+
throw new Error(result.data);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (isDebug()) {
|
|
44
|
+
console.error(`CodifySpawn error for command ${cmd}`, error);
|
|
45
|
+
}
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
if (error.message?.startsWith('sudo:')) {
|
|
48
|
+
throw new SudoError(cmd);
|
|
49
|
+
}
|
|
50
|
+
if (throws) {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
if (error instanceof Error) {
|
|
54
|
+
return {
|
|
55
|
+
status: SpawnStatus.ERROR,
|
|
56
|
+
data: error.message,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
status: SpawnStatus.ERROR,
|
|
61
|
+
data: String(error),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function internalSpawn(cmd, opts) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
const output = [];
|
|
68
|
+
// If TERM_PROGRAM=Apple_Terminal is set then ANSI escape characters may be included
|
|
69
|
+
// in the response.
|
|
70
|
+
const env = { ...process.env, ...opts.env, TERM_PROGRAM: 'codify', COMMAND_MODE: 'unix2003', COLORTERM: 'truecolor' };
|
|
71
|
+
const shell = getDefaultShell();
|
|
72
|
+
const rcFile = shell === 'zsh' ? '~/.zshrc' : '~/.bashrc';
|
|
73
|
+
// Source start up shells to emulate a users environment vs. a non-interactive non-login shell script
|
|
74
|
+
// Ignore all stdin
|
|
75
|
+
// If tty is requested then we'll need to sleep 1 to avoid race conditions. This is because if the terminal updates async after the tty message is
|
|
76
|
+
// displayed then it'll disappear. By adding sleep 1 it'll allow ink.js to finish all the updates before the tty message is shown
|
|
77
|
+
const _process = spawn(`source ${rcFile}; ${opts.requestsTTY ? 'sleep 1;' : ''}${cmd}`, [], {
|
|
78
|
+
...opts,
|
|
79
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
|
+
shell,
|
|
81
|
+
env
|
|
82
|
+
});
|
|
83
|
+
const { stdout, stderr, stdin } = _process;
|
|
84
|
+
stdout.setEncoding('utf8');
|
|
85
|
+
stderr.setEncoding('utf8');
|
|
86
|
+
stdout.on('data', (data) => {
|
|
87
|
+
output.push(data.toString());
|
|
88
|
+
});
|
|
89
|
+
stderr.on('data', (data) => {
|
|
90
|
+
output.push(data.toString());
|
|
91
|
+
});
|
|
92
|
+
_process.on('error', (data) => { });
|
|
93
|
+
// please node that this is not a full replacement for 'inherit'
|
|
94
|
+
// the child process can and will detect if stdout is a pty and change output based on it
|
|
95
|
+
// the terminal context is lost & ansi information (coloring) etc will be lost
|
|
96
|
+
if (stdout && stderr && VerbosityLevel.get() > 0) {
|
|
97
|
+
stdout.pipe(process.stdout);
|
|
98
|
+
stderr.pipe(process.stderr);
|
|
99
|
+
}
|
|
100
|
+
_process.on('close', (code) => {
|
|
101
|
+
resolve({
|
|
102
|
+
status: code === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
103
|
+
data: stripAnsi(output.join('\n')),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function externalSpawnWithSudo(cmd, opts) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const requestId = nanoid(8);
|
|
111
|
+
const listener = (data) => {
|
|
112
|
+
if (data.requestId === requestId) {
|
|
113
|
+
process.removeListener('message', listener);
|
|
114
|
+
if (!validateSudoRequestResponse(data.data)) {
|
|
115
|
+
throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
|
|
116
|
+
}
|
|
117
|
+
resolve(data.data);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
process.on('message', listener);
|
|
121
|
+
process.send({
|
|
122
|
+
cmd: MessageCmd.SUDO_REQUEST,
|
|
123
|
+
data: {
|
|
124
|
+
command: cmd,
|
|
125
|
+
options: opts ?? {},
|
|
126
|
+
},
|
|
127
|
+
requestId
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export function isDebug() {
|
|
132
|
+
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
133
|
+
}
|
|
134
|
+
function getDefaultShell() {
|
|
135
|
+
return process.platform === 'darwin' ? 'zsh' : 'bash';
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codify-plugin-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.181",
|
|
4
4
|
"description": "Library plugin library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -10,42 +10,47 @@
|
|
|
10
10
|
"posttest": "tsc",
|
|
11
11
|
"prepublishOnly": "tsc"
|
|
12
12
|
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"codify-deploy": "./dist/bin/deploy-plugin.js"
|
|
15
|
+
},
|
|
13
16
|
"keywords": [],
|
|
14
17
|
"author": "",
|
|
15
18
|
"license": "ISC",
|
|
16
19
|
"dependencies": {
|
|
20
|
+
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
21
|
+
"@npmcli/promise-spawn": "^7.0.1",
|
|
17
22
|
"ajv": "^8.12.0",
|
|
18
23
|
"ajv-formats": "^2.1.1",
|
|
24
|
+
"clean-deep": "^3.4.0",
|
|
19
25
|
"codify-schemas": "1.0.83",
|
|
20
|
-
"@npmcli/promise-spawn": "^7.0.1",
|
|
21
|
-
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
|
|
22
|
-
"uuid": "^10.0.0",
|
|
23
26
|
"lodash.isequal": "^4.5.0",
|
|
24
27
|
"nanoid": "^5.0.9",
|
|
25
28
|
"strip-ansi": "^7.1.0",
|
|
26
|
-
"
|
|
29
|
+
"uuid": "^10.0.0"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
32
|
+
"@apidevtools/json-schema-ref-parser": "^11.7.2",
|
|
29
33
|
"@oclif/prettier-config": "^0.2.1",
|
|
30
34
|
"@oclif/test": "^3",
|
|
31
|
-
"@types/
|
|
35
|
+
"@types/lodash.isequal": "^4.5.8",
|
|
32
36
|
"@types/node": "^20",
|
|
37
|
+
"@types/npmcli__promise-spawn": "^6.0.3",
|
|
33
38
|
"@types/semver": "^7.5.4",
|
|
34
39
|
"@types/sinon": "^17.0.3",
|
|
35
40
|
"@types/uuid": "^10.0.0",
|
|
36
|
-
"@types/lodash.isequal": "^4.5.8",
|
|
37
41
|
"chai-as-promised": "^7.1.1",
|
|
38
|
-
"vitest": "^3.0.5",
|
|
39
|
-
"vitest-mock-extended": "^1.3.1",
|
|
40
|
-
"sinon": "^17.0.1",
|
|
41
42
|
"eslint": "^8.51.0",
|
|
42
43
|
"eslint-config-oclif": "^5",
|
|
43
44
|
"eslint-config-oclif-typescript": "^3",
|
|
45
|
+
"eslint-config-prettier": "^9.0.0",
|
|
46
|
+
"merge-json-schemas": "^1.0.0",
|
|
44
47
|
"shx": "^0.3.3",
|
|
48
|
+
"sinon": "^17.0.1",
|
|
45
49
|
"ts-node": "^10.9.1",
|
|
46
50
|
"tsc-watch": "^6.0.4",
|
|
47
51
|
"typescript": "^5",
|
|
48
|
-
"
|
|
52
|
+
"vitest": "^3.0.5",
|
|
53
|
+
"vitest-mock-extended": "^1.3.1"
|
|
49
54
|
},
|
|
50
55
|
"engines": {
|
|
51
56
|
"node": ">=18.0.0"
|
package/src/plugin/plugin.ts
CHANGED
|
@@ -59,13 +59,24 @@ export class Plugin {
|
|
|
59
59
|
|
|
60
60
|
return {
|
|
61
61
|
resourceDefinitions: [...this.resourceControllers.values()]
|
|
62
|
-
.map((r) =>
|
|
63
|
-
|
|
64
|
-
type: r.typeId,
|
|
65
|
-
sensitiveParameters: Object.entries(r.settings.parameterSettings ?? {})
|
|
62
|
+
.map((r) => {
|
|
63
|
+
const sensitiveParameters = Object.entries(r.settings.parameterSettings ?? {})
|
|
66
64
|
.filter(([, v]) => v?.isSensitive)
|
|
67
|
-
.map(([k]) => k)
|
|
68
|
-
|
|
65
|
+
.map(([k]) => k);
|
|
66
|
+
|
|
67
|
+
// Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import
|
|
68
|
+
// sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks
|
|
69
|
+
// on a specific sensitive parameter.
|
|
70
|
+
if (r.settings.isSensitive && sensitiveParameters.length === 0) {
|
|
71
|
+
sensitiveParameters.push('*');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
dependencies: r.dependencies,
|
|
76
|
+
type: r.typeId,
|
|
77
|
+
sensitiveParameters,
|
|
78
|
+
}
|
|
79
|
+
})
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
|
|
@@ -87,10 +98,17 @@ export class Plugin {
|
|
|
87
98
|
const allowMultiple = resource.settings.allowMultiple !== undefined
|
|
88
99
|
&& resource.settings.allowMultiple !== false;
|
|
89
100
|
|
|
101
|
+
// Here we add '*' if the resource is sensitive but no sensitive parameters are found. This works because the import
|
|
102
|
+
// sensitivity check only checks for the existance of a sensitive parameter whereas the parameter blocking one blocks
|
|
103
|
+
// on a specific sensitive parameter.
|
|
90
104
|
const sensitiveParameters = Object.entries(resource.settings.parameterSettings ?? {})
|
|
91
105
|
.filter(([, v]) => v?.isSensitive)
|
|
92
106
|
.map(([k]) => k);
|
|
93
107
|
|
|
108
|
+
if (resource.settings.isSensitive && sensitiveParameters.length === 0) {
|
|
109
|
+
sensitiveParameters.push('*');
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
return {
|
|
95
113
|
plugin: this.name,
|
|
96
114
|
type: data.type,
|
|
@@ -19,7 +19,7 @@ EventEmitter.defaultMaxListeners = 1000;
|
|
|
19
19
|
* without a tty (or even a stdin) attached so interactive commands will not work.
|
|
20
20
|
*/
|
|
21
21
|
export class BackgroundPty implements IPty {
|
|
22
|
-
private basePty = pty.spawn(
|
|
22
|
+
private basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
|
|
23
23
|
env: process.env, name: nanoid(6),
|
|
24
24
|
handleFlowControl: true
|
|
25
25
|
});
|
|
@@ -127,7 +127,10 @@ export class BackgroundPty implements IPty {
|
|
|
127
127
|
let outputBuffer = '';
|
|
128
128
|
|
|
129
129
|
return new Promise(resolve => {
|
|
130
|
-
|
|
130
|
+
// zsh-specific commands
|
|
131
|
+
if (this.getDefaultShell() === 'zsh') {
|
|
132
|
+
this.basePty.write('setopt hist_ignore_space;\n');
|
|
133
|
+
}
|
|
131
134
|
this.basePty.write(' unset PS1;\n');
|
|
132
135
|
this.basePty.write(' unset PS0;\n')
|
|
133
136
|
this.basePty.write(' echo setup complete\\"\n')
|
|
@@ -142,4 +145,8 @@ export class BackgroundPty implements IPty {
|
|
|
142
145
|
})
|
|
143
146
|
})
|
|
144
147
|
}
|
|
148
|
+
|
|
149
|
+
private getDefaultShell(): string {
|
|
150
|
+
return process.platform === 'darwin' ? 'zsh' : 'bash';
|
|
151
|
+
}
|
|
145
152
|
}
|
|
@@ -27,6 +27,12 @@ export interface ResourceSettings<T extends StringIndexedObject> {
|
|
|
27
27
|
*/
|
|
28
28
|
schema?: Partial<JSONSchemaType<T | any>>;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Mark the resource as sensitive. Defaults to false. This prevents the resource from automatically being imported by init and import.
|
|
32
|
+
* This differs from the parameter level sensitivity which also prevents the parameter value from being displayed in the plan.
|
|
33
|
+
*/
|
|
34
|
+
isSensitive?: boolean;
|
|
35
|
+
|
|
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
|
|
@@ -344,8 +350,9 @@ const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown,
|
|
|
344
350
|
transformedB = path.resolve(transformedB)
|
|
345
351
|
}
|
|
346
352
|
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
// macOS has case-insensitive filesystem by default, Linux is case-sensitive
|
|
354
|
+
const isCaseSensitive = process.platform === 'linux';
|
|
355
|
+
if (!isCaseSensitive) {
|
|
349
356
|
transformedA = transformedA.toLowerCase();
|
|
350
357
|
transformedB = transformedB.toLowerCase();
|
|
351
358
|
}
|