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.
Files changed (50) hide show
  1. package/bin/build.js +189 -0
  2. package/dist/bin/build.js +0 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.js +4 -0
  5. package/dist/messages/handlers.js +10 -2
  6. package/dist/plugin/plugin.d.ts +2 -1
  7. package/dist/plugin/plugin.js +4 -1
  8. package/dist/pty/background-pty.d.ts +3 -2
  9. package/dist/pty/background-pty.js +7 -14
  10. package/dist/pty/index.d.ts +8 -2
  11. package/dist/pty/seqeuntial-pty.d.ts +3 -2
  12. package/dist/pty/seqeuntial-pty.js +47 -12
  13. package/dist/resource/parsed-resource-settings.d.ts +3 -1
  14. package/dist/resource/parsed-resource-settings.js +15 -2
  15. package/dist/resource/resource-controller.js +5 -5
  16. package/dist/resource/resource-settings.d.ts +8 -2
  17. package/dist/resource/resource-settings.js +2 -2
  18. package/dist/test.d.ts +1 -0
  19. package/dist/test.js +5 -0
  20. package/dist/utils/file-utils.d.ts +14 -7
  21. package/dist/utils/file-utils.js +65 -51
  22. package/dist/utils/functions.js +2 -2
  23. package/dist/utils/index.d.ts +12 -0
  24. package/dist/utils/index.js +111 -0
  25. package/dist/utils/load-resources.d.ts +1 -0
  26. package/dist/utils/load-resources.js +46 -0
  27. package/dist/utils/package-json-utils.d.ts +12 -0
  28. package/dist/utils/package-json-utils.js +34 -0
  29. package/package.json +5 -4
  30. package/rollup.config.js +24 -0
  31. package/src/index.ts +4 -0
  32. package/src/messages/handlers.test.ts +23 -0
  33. package/src/messages/handlers.ts +11 -2
  34. package/src/plugin/plugin.test.ts +31 -0
  35. package/src/plugin/plugin.ts +6 -2
  36. package/src/pty/background-pty.ts +10 -18
  37. package/src/pty/index.ts +10 -4
  38. package/src/pty/seqeuntial-pty.ts +62 -16
  39. package/src/pty/sequential-pty.test.ts +137 -4
  40. package/src/resource/parsed-resource-settings.test.ts +24 -0
  41. package/src/resource/parsed-resource-settings.ts +23 -7
  42. package/src/resource/resource-controller.test.ts +126 -0
  43. package/src/resource/resource-controller.ts +5 -6
  44. package/src/resource/resource-settings.test.ts +36 -0
  45. package/src/resource/resource-settings.ts +11 -4
  46. package/src/utils/file-utils.test.ts +7 -0
  47. package/src/utils/file-utils.ts +70 -55
  48. package/src/utils/functions.ts +3 -3
  49. package/src/utils/index.ts +138 -0
  50. package/src/utils/internal-utils.test.ts +1 -0
package/dist/test.js ADDED
@@ -0,0 +1,5 @@
1
+ import { SequentialPty } from './pty/seqeuntial-pty.js';
2
+ import { VerbosityLevel } from './utils/verbosity-level.js';
3
+ VerbosityLevel.set(1);
4
+ const $ = new SequentialPty();
5
+ await $.spawn('sudo ls', { interactive: true });
@@ -1,16 +1,23 @@
1
1
  export declare class FileUtils {
2
2
  static downloadFile(url: string, destination: string): Promise<void>;
3
- static addToStartupFile(line: string): Promise<void>;
4
- static addAllToStartupFile(lines: string[]): Promise<void>;
5
- static addPathToPrimaryShellRc(value: string, prepend: boolean): Promise<void>;
3
+ static addToShellRc(line: string): Promise<void>;
4
+ static addAllToShellRc(lines: string[]): Promise<void>;
5
+ /**
6
+ * This method adds a directory path to the shell rc file if it doesn't already exist.
7
+ *
8
+ * @param value - The directory path to add.
9
+ * @param prepend - Whether to prepend the path to the existing PATH variable.
10
+ */
11
+ static addPathToShellRc(value: string, prepend: boolean): Promise<void>;
12
+ static removeFromFile(filePath: string, search: string): Promise<void>;
13
+ static removeLineFromFile(filePath: string, search: RegExp | string): Promise<void>;
14
+ static removeLineFromShellRc(search: RegExp | string): Promise<void>;
15
+ static removeAllLinesFromShellRc(searches: Array<RegExp | string>): Promise<void>;
16
+ static appendToFileWithSpacing(file: string, textToInsert: string): string;
6
17
  static dirExists(path: string): Promise<boolean>;
7
18
  static fileExists(path: string): Promise<boolean>;
8
19
  static exists(path: string): Promise<boolean>;
9
20
  static checkDirExistsOrThrowIfFile(path: string): Promise<boolean>;
10
21
  static createDirIfNotExists(path: string): Promise<void>;
11
- static removeFromFile(filePath: string, search: string): Promise<void>;
12
- static removeLineFromFile(filePath: string, search: RegExp | string): Promise<void>;
13
- static removeLineFromPrimaryShellRc(search: RegExp | string): Promise<void>;
14
- static appendToFileWithSpacing(file: string, textToInsert: string): string;
15
22
  private static calculateEndingNewLines;
16
23
  }
@@ -18,7 +18,7 @@ export class FileUtils {
18
18
  await finished(Readable.fromWeb(body).pipe(ws));
19
19
  console.log(`Finished downloading to ${destination}`);
20
20
  }
21
- static async addToStartupFile(line) {
21
+ static async addToShellRc(line) {
22
22
  const lineToInsert = addLeadingSpacer(addTrailingSpacer(line));
23
23
  await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert);
24
24
  function addLeadingSpacer(line) {
@@ -32,14 +32,23 @@ export class FileUtils {
32
32
  : line + '\n';
33
33
  }
34
34
  }
35
- static async addAllToStartupFile(lines) {
35
+ static async addAllToShellRc(lines) {
36
36
  const formattedLines = '\n' + lines.join('\n') + '\n';
37
37
  const shellRc = Utils.getPrimaryShellRc();
38
38
  console.log(`Adding to ${path.basename(shellRc)}:
39
39
  ${lines.join('\n')}`);
40
40
  await fs.appendFile(shellRc, formattedLines);
41
41
  }
42
- static async addPathToPrimaryShellRc(value, prepend) {
42
+ /**
43
+ * This method adds a directory path to the shell rc file if it doesn't already exist.
44
+ *
45
+ * @param value - The directory path to add.
46
+ * @param prepend - Whether to prepend the path to the existing PATH variable.
47
+ */
48
+ static async addPathToShellRc(value, prepend) {
49
+ if (await Utils.isDirectoryOnPath(value)) {
50
+ return;
51
+ }
43
52
  const shellRc = Utils.getPrimaryShellRc();
44
53
  console.log(`Saving path: ${value} to ${shellRc}`);
45
54
  if (prepend) {
@@ -48,53 +57,6 @@ ${lines.join('\n')}`);
48
57
  }
49
58
  await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
50
59
  }
51
- static async dirExists(path) {
52
- let stat;
53
- try {
54
- stat = await fs.stat(path);
55
- return stat.isDirectory();
56
- }
57
- catch {
58
- return false;
59
- }
60
- }
61
- static async fileExists(path) {
62
- let stat;
63
- try {
64
- stat = await fs.stat(path);
65
- return stat.isFile();
66
- }
67
- catch {
68
- return false;
69
- }
70
- }
71
- static async exists(path) {
72
- try {
73
- await fs.stat(path);
74
- return true;
75
- }
76
- catch {
77
- return false;
78
- }
79
- }
80
- static async checkDirExistsOrThrowIfFile(path) {
81
- let stat;
82
- try {
83
- stat = await fs.stat(path);
84
- }
85
- catch {
86
- return false;
87
- }
88
- if (stat.isDirectory()) {
89
- return true;
90
- }
91
- throw new Error(`Directory ${path} already exists and is a file`);
92
- }
93
- static async createDirIfNotExists(path) {
94
- if (!fsSync.existsSync(path)) {
95
- await fs.mkdir(path, { recursive: true });
96
- }
97
- }
98
60
  static async removeFromFile(filePath, search) {
99
61
  const contents = await fs.readFile(filePath, 'utf8');
100
62
  const newContents = contents.replaceAll(search, '');
@@ -131,9 +93,14 @@ ${lines.join('\n')}`);
131
93
  await fs.writeFile(filePath, lines.join('\n'));
132
94
  console.log(`Removed line: ${search} from ${filePath}`);
133
95
  }
134
- static async removeLineFromPrimaryShellRc(search) {
96
+ static async removeLineFromShellRc(search) {
135
97
  return FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
136
98
  }
99
+ static async removeAllLinesFromShellRc(searches) {
100
+ for (const search of searches) {
101
+ await FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
102
+ }
103
+ }
137
104
  // Append the string to the end of a file ensuring at least 1 lines of space between.
138
105
  // Ex result:
139
106
  // something something;
@@ -150,6 +117,53 @@ ${lines.join('\n')}`);
150
117
  : Math.max(0, 2 - endingNewLines);
151
118
  return lines.join('\n') + '\n'.repeat(numNewLines) + textToInsert;
152
119
  }
120
+ static async dirExists(path) {
121
+ let stat;
122
+ try {
123
+ stat = await fs.stat(path);
124
+ return stat.isDirectory();
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ static async fileExists(path) {
131
+ let stat;
132
+ try {
133
+ stat = await fs.stat(path);
134
+ return stat.isFile();
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ static async exists(path) {
141
+ try {
142
+ await fs.stat(path);
143
+ return true;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ static async checkDirExistsOrThrowIfFile(path) {
150
+ let stat;
151
+ try {
152
+ stat = await fs.stat(path);
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ if (stat.isDirectory()) {
158
+ return true;
159
+ }
160
+ throw new Error(`Directory ${path} already exists and is a file`);
161
+ }
162
+ static async createDirIfNotExists(path) {
163
+ if (!fsSync.existsSync(path)) {
164
+ await fs.mkdir(path, { recursive: true });
165
+ }
166
+ }
153
167
  // This is overly complicated but it can be used to insert into any
154
168
  // position in the future
155
169
  static calculateEndingNewLines(lines) {
@@ -5,9 +5,10 @@ export function splitUserConfig(config) {
5
5
  type: config.type,
6
6
  ...(config.name ? { name: config.name } : {}),
7
7
  ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
8
+ ...(config.os ? { os: config.os } : {}),
8
9
  };
9
10
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
- const { type, name, dependsOn, ...parameters } = config;
11
+ const { type, name, dependsOn, os, ...parameters } = config;
11
12
  return {
12
13
  parameters: parameters,
13
14
  coreParameters,
@@ -24,7 +25,6 @@ export function tildify(pathWithTilde) {
24
25
  return homeDirectory ? pathWithTilde.replace(homeDirectory, '~') : pathWithTilde;
25
26
  }
26
27
  export function resolvePathWithVariables(pathWithVariables) {
27
- // @ts-expect-error Ignore this for now
28
28
  return pathWithVariables.replace(/\$([A-Z_]+[A-Z0-9_]*)|\${([A-Z0-9_]*)}/ig, (_, a, b) => process.env[a || b]);
29
29
  }
30
30
  export function addVariablesToPath(pathWithoutVariables) {
@@ -20,7 +20,19 @@ export declare const Utils: {
20
20
  };
21
21
  isMacOS(): boolean;
22
22
  isLinux(): boolean;
23
+ isArmArch(): Promise<boolean>;
24
+ isHomebrewInstalled(): Promise<boolean>;
25
+ isRosetta2Installed(): Promise<boolean>;
23
26
  getShell(): Shell | undefined;
24
27
  getPrimaryShellRc(): string;
25
28
  getShellRcFiles(): string[];
29
+ isDirectoryOnPath(directory: string): Promise<boolean>;
30
+ assertBrewInstalled(): Promise<void>;
31
+ /**
32
+ * Installs a package via the system package manager. This will use Homebrew on macOS and apt on Ubuntu/Debian or dnf on Fedora.
33
+ * @param packageName
34
+ */
35
+ installViaPkgMgr(packageName: string): Promise<boolean>;
36
+ uninstallViaPkgMgr(packageName: string): Promise<boolean>;
37
+ linuxDistro(): Promise<"arch" | "centos" | "debian" | "fedora" | "rhel" | "ubuntu" | undefined>;
26
38
  };
@@ -1,5 +1,7 @@
1
+ import * as fs from 'node:fs/promises';
1
2
  import os from 'node:os';
2
3
  import path from 'node:path';
4
+ import { SpawnStatus, getPty } from '../pty/index.js';
3
5
  export function isDebug() {
4
6
  return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
5
7
  }
@@ -28,6 +30,29 @@ export const Utils = {
28
30
  isLinux() {
29
31
  return os.platform() === 'linux';
30
32
  },
33
+ async isArmArch() {
34
+ const $ = getPty();
35
+ if (!Utils.isMacOS()) {
36
+ // On Linux, check uname -m
37
+ const query = await $.spawn('uname -m');
38
+ return query.data.trim() === 'aarch64' || query.data.trim() === 'arm64';
39
+ }
40
+ const query = await $.spawn('sysctl -n machdep.cpu.brand_string');
41
+ return /M(\d)/.test(query.data);
42
+ },
43
+ async isHomebrewInstalled() {
44
+ const $ = getPty();
45
+ const query = await $.spawnSafe('which brew', { interactive: true });
46
+ return query.status === SpawnStatus.SUCCESS;
47
+ },
48
+ async isRosetta2Installed() {
49
+ if (!Utils.isMacOS()) {
50
+ return false;
51
+ }
52
+ const $ = getPty();
53
+ const query = await $.spawnSafe('arch -x86_64 /usr/bin/true 2> /dev/null', { interactive: true });
54
+ return query.status === SpawnStatus.SUCCESS;
55
+ },
31
56
  getShell() {
32
57
  const shell = process.env.SHELL || '';
33
58
  if (shell.endsWith('bash')) {
@@ -108,4 +133,90 @@ export const Utils = {
108
133
  path.join(homeDir, '.profile'),
109
134
  ];
110
135
  },
136
+ async isDirectoryOnPath(directory) {
137
+ const $ = getPty();
138
+ const { data: pathQuery } = await $.spawn('echo $PATH', { interactive: true });
139
+ const lines = pathQuery.split(':');
140
+ return lines.includes(directory);
141
+ },
142
+ async assertBrewInstalled() {
143
+ const $ = getPty();
144
+ const brewCheck = await $.spawnSafe('which brew', { interactive: true });
145
+ if (brewCheck.status === SpawnStatus.ERROR) {
146
+ throw new Error(`Homebrew is not installed. Cannot install git-lfs without Homebrew installed.
147
+
148
+ Brew can be installed using Codify:
149
+ {
150
+ "type": "homebrew",
151
+ }`);
152
+ }
153
+ },
154
+ /**
155
+ * Installs a package via the system package manager. This will use Homebrew on macOS and apt on Ubuntu/Debian or dnf on Fedora.
156
+ * @param packageName
157
+ */
158
+ async installViaPkgMgr(packageName) {
159
+ const $ = getPty();
160
+ if (Utils.isMacOS()) {
161
+ await this.assertBrewInstalled();
162
+ const { status } = await $.spawnSafe(`brew install ${packageName}`, { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } });
163
+ return status === SpawnStatus.SUCCESS;
164
+ }
165
+ if (Utils.isLinux()) {
166
+ const isAptInstalled = await $.spawnSafe('which apt');
167
+ if (isAptInstalled.status === SpawnStatus.SUCCESS) {
168
+ const { status } = await $.spawnSafe(`apt install ${packageName}`, { interactive: true, requiresRoot: true, env: { DEBIAN_FRONTEND: 'noninteractive' } });
169
+ return status === SpawnStatus.SUCCESS;
170
+ }
171
+ const isDnfInstalled = await $.spawnSafe('which dnf');
172
+ if (isDnfInstalled.status === SpawnStatus.SUCCESS) {
173
+ const { status } = await $.spawnSafe(`dnf install ${packageName} -y`, { interactive: true, requiresRoot: true });
174
+ return status === SpawnStatus.SUCCESS;
175
+ }
176
+ const isYumInstalled = await $.spawnSafe('which yum');
177
+ if (isYumInstalled.status === SpawnStatus.SUCCESS) {
178
+ const { status } = await $.spawnSafe(`yum install ${packageName} -y`, { interactive: true, requiresRoot: true });
179
+ return status === SpawnStatus.SUCCESS;
180
+ }
181
+ return false;
182
+ }
183
+ return false;
184
+ },
185
+ async uninstallViaPkgMgr(packageName) {
186
+ const $ = getPty();
187
+ if (Utils.isMacOS()) {
188
+ await this.assertBrewInstalled();
189
+ const { status } = await $.spawnSafe(`brew uninstall --zap ${packageName}`, { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } });
190
+ return status === SpawnStatus.SUCCESS;
191
+ }
192
+ if (Utils.isLinux()) {
193
+ const isAptInstalled = await $.spawnSafe('which apt');
194
+ if (isAptInstalled.status === SpawnStatus.SUCCESS) {
195
+ const { status } = await $.spawnSafe(`apt remove --purge ${packageName}`, { interactive: true, requiresRoot: true, env: { DEBIAN_FRONTEND: 'noninteractive' } });
196
+ return status === SpawnStatus.SUCCESS;
197
+ }
198
+ const isDnfInstalled = await $.spawnSafe('which dnf');
199
+ if (isDnfInstalled.status === SpawnStatus.SUCCESS) {
200
+ const { status } = await $.spawnSafe(`dnf autoremove ${packageName} -y`, { interactive: true, requiresRoot: true });
201
+ return status === SpawnStatus.SUCCESS;
202
+ }
203
+ const isYumInstalled = await $.spawnSafe('which yum');
204
+ if (isYumInstalled.status === SpawnStatus.SUCCESS) {
205
+ const { status } = await $.spawnSafe(`yum autoremove ${packageName} -y`, { interactive: true, requiresRoot: true });
206
+ return status === SpawnStatus.SUCCESS;
207
+ }
208
+ return false;
209
+ }
210
+ return false;
211
+ },
212
+ async linuxDistro() {
213
+ const osRelease = await fs.readFile('/etc/os-release', 'utf8');
214
+ const lines = osRelease.split('\n');
215
+ for (const line of lines) {
216
+ if (line.startsWith('ID=')) {
217
+ return line.slice(3).trim();
218
+ }
219
+ }
220
+ return undefined;
221
+ }
111
222
  };
@@ -0,0 +1 @@
1
+ export declare const listAllResources: (root?: string) => Promise<string[]>;
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import * as url from 'node:url';
4
+ export const listAllResources = async (root = path.join(path.dirname(url.fileURLToPath(import.meta.url)), '..', '..', '..', '..')) => {
5
+ console.log('Dirname', root);
6
+ const resourcesPath = path.join(root, 'src', 'resources');
7
+ const resourceDir = await fs.readdir(resourcesPath);
8
+ const dedupSet = new Set();
9
+ const result = new Set();
10
+ for (const folder of resourceDir) {
11
+ if (await fs.stat(path.join(resourcesPath, folder)).then(s => s.isDirectory()).catch(() => false)) {
12
+ for (const folderContents of await fs.readdir(path.join(resourcesPath, folder))) {
13
+ const isDirectory = await fs.stat(path.join(resourcesPath, folder, folderContents)).then(s => s.isDirectory());
14
+ // console.log(folderContents, isDirectory);
15
+ if (isDirectory) {
16
+ for (const innerContents of await fs.readdir(path.join(resourcesPath, folder, folderContents))) {
17
+ if (!dedupSet.has(path.join(resourcesPath, folder, folderContents))) {
18
+ dedupSet.add(path.join(resourcesPath, folder, folderContents));
19
+ addResourceFromDir(path.join(resourcesPath, folder, folderContents), result);
20
+ }
21
+ }
22
+ }
23
+ else {
24
+ if (!dedupSet.has(path.join(resourcesPath, folder))) {
25
+ dedupSet.add(path.join(resourcesPath, folder));
26
+ addResourceFromDir(path.join(resourcesPath, folder), result);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ else {
32
+ throw new Error('Only directories are allowed in resources folder');
33
+ }
34
+ }
35
+ return [...result];
36
+ };
37
+ function addResourceFromDir(dir, result) {
38
+ try {
39
+ const resourceFile = path.resolve(path.join(dir, 'resource.ts'));
40
+ if (!(fs.stat(resourceFile).then((s) => s.isFile())).catch(() => false)) {
41
+ return;
42
+ }
43
+ result.add(resourceFile);
44
+ }
45
+ catch { }
46
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Find the nearest package.json starting from a directory and walking upward.
3
+ * @param {string} startDir - Directory to start searching from
4
+ * @returns {string|null} Absolute path to package.json or null if not found
5
+ */
6
+ export declare function findNearestPackageJson(startDir?: string): string | null;
7
+ /**
8
+ * Read and parse the nearest package.json
9
+ * @param {string} startDir
10
+ * @returns {object|null}
11
+ */
12
+ export declare function readNearestPackageJson(startDir?: string): any;
@@ -0,0 +1,34 @@
1
+ import * as fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Find the nearest package.json starting from a directory and walking upward.
5
+ * @param {string} startDir - Directory to start searching from
6
+ * @returns {string|null} Absolute path to package.json or null if not found
7
+ */
8
+ export function findNearestPackageJson(startDir = process.cwd()) {
9
+ let currentDir = path.resolve(startDir);
10
+ while (true) {
11
+ const pkgPath = path.join(currentDir, "package.json");
12
+ if (fs.existsSync(pkgPath)) {
13
+ return pkgPath;
14
+ }
15
+ const parentDir = path.dirname(currentDir);
16
+ if (parentDir === currentDir) {
17
+ // Reached filesystem root
18
+ return null;
19
+ }
20
+ currentDir = parentDir;
21
+ }
22
+ }
23
+ /**
24
+ * Read and parse the nearest package.json
25
+ * @param {string} startDir
26
+ * @returns {object|null}
27
+ */
28
+ export function readNearestPackageJson(startDir = process.cwd()) {
29
+ const pkgPath = findNearestPackageJson(startDir);
30
+ if (!pkgPath)
31
+ return null;
32
+ const contents = fs.readFileSync(pkgPath, 'utf8');
33
+ return JSON.parse(contents);
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.182-beta6",
3
+ "version": "1.0.182-beta60",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "prepublishOnly": "tsc"
12
12
  },
13
13
  "bin": {
14
- "codify-deploy": "./dist/bin/deploy-plugin.js"
14
+ "codify-build": "./bin/build.js"
15
15
  },
16
16
  "keywords": [],
17
17
  "author": "",
@@ -22,11 +22,12 @@
22
22
  "ajv": "^8.12.0",
23
23
  "ajv-formats": "^2.1.1",
24
24
  "clean-deep": "^3.4.0",
25
- "codify-schemas": "1.0.86",
25
+ "codify-schemas": "1.0.86-beta10",
26
26
  "lodash.isequal": "^4.5.0",
27
27
  "nanoid": "^5.0.9",
28
28
  "strip-ansi": "^7.1.0",
29
- "uuid": "^10.0.0"
29
+ "uuid": "^10.0.0",
30
+ "zod": "4.1.13"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@apidevtools/json-schema-ref-parser": "^11.7.2",
@@ -0,0 +1,24 @@
1
+ import commonjs from '@rollup/plugin-commonjs';
2
+ import json from '@rollup/plugin-json';
3
+ import nodeResolve from '@rollup/plugin-node-resolve';
4
+ import terser from '@rollup/plugin-terser';
5
+ import typescript from '@rollup/plugin-typescript';
6
+
7
+ export default {
8
+ input: 'src/index.ts',
9
+ output: {
10
+ dir: 'dist',
11
+ format: 'cjs',
12
+ inlineDynamicImports: true,
13
+ },
14
+ external: ['@homebridge/node-pty-prebuilt-multiarch'],
15
+ plugins: [
16
+ json(),
17
+ nodeResolve({ exportConditions: ['node'] }),
18
+ typescript({
19
+ exclude: ['**/*.test.ts', '**/*.d.ts', 'test']
20
+ }),
21
+ commonjs(),
22
+ terser()
23
+ ]
24
+ }
package/src/index.ts CHANGED
@@ -7,14 +7,18 @@ export * from './plan/change-set.js'
7
7
  export * from './plan/plan.js'
8
8
  export * from './plan/plan-types.js'
9
9
  export * from './plugin/plugin.js'
10
+ export * from './pty/background-pty.js';
10
11
  export * from './pty/index.js'
12
+ export * from './pty/seqeuntial-pty.js';
11
13
  export * from './resource/parsed-resource-settings.js';
12
14
  export * from './resource/resource.js'
13
15
  export * from './resource/resource-settings.js'
14
16
  export * from './stateful-parameter/stateful-parameter.js'
17
+ export * from './utils/file-utils.js'
15
18
  export * from './utils/functions.js'
16
19
  export * from './utils/index.js'
17
20
  export * from './utils/verbosity-level.js'
21
+ export * from 'zod/v4';
18
22
 
19
23
  export async function runPlugin(plugin: Plugin) {
20
24
  const messageHandler = new MessageHandler(plugin);
@@ -235,6 +235,29 @@ describe('Message handler tests', () => {
235
235
  process.send = undefined;
236
236
  })
237
237
 
238
+ it('handles changing the verbosity level', async () => {
239
+ const resource = new TestResource()
240
+ const plugin = testPlugin(resource);
241
+ const handler = new MessageHandler(plugin);
242
+
243
+ process.send = (message) => {
244
+ expect(message).toMatchObject({
245
+ cmd: 'setVerbosityLevel_Response',
246
+ status: MessageStatus.SUCCESS,
247
+ })
248
+ return true;
249
+ }
250
+
251
+ expect(async () => await handler.onMessage({
252
+ cmd: 'setVerbosityLevel',
253
+ data: {
254
+ verbosityLevel: 2,
255
+ }
256
+ })).rejects.to.not.throw;
257
+
258
+ process.send = undefined;
259
+ })
260
+
238
261
  it('Supports ipc message v2 (success)', async () => {
239
262
  const resource = new TestResource()
240
263
  const plugin = testPlugin(resource);
@@ -2,7 +2,7 @@ import { Ajv, SchemaObject, ValidateFunction } from 'ajv';
2
2
  import addFormats from 'ajv-formats';
3
3
  import {
4
4
  ApplyRequestDataSchema,
5
- ApplyResponseDataSchema,
5
+ EmptyResponseDataSchema,
6
6
  GetResourceInfoRequestDataSchema,
7
7
  GetResourceInfoResponseDataSchema,
8
8
  ImportRequestDataSchema,
@@ -19,6 +19,7 @@ import {
19
19
  PlanRequestDataSchema,
20
20
  PlanResponseDataSchema,
21
21
  ResourceSchema,
22
+ SetVerbosityRequestDataSchema,
22
23
  ValidateRequestDataSchema,
23
24
  ValidateResponseDataSchema
24
25
  } from 'codify-schemas';
@@ -42,6 +43,14 @@ const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) =
42
43
  requestValidator: GetResourceInfoRequestDataSchema,
43
44
  responseValidator: GetResourceInfoResponseDataSchema
44
45
  },
46
+ 'setVerbosityLevel': {
47
+ async handler(plugin: Plugin, data: any) {
48
+ await plugin.setVerbosityLevel(data)
49
+ return null;
50
+ },
51
+ requestValidator: SetVerbosityRequestDataSchema,
52
+ responseValidator: EmptyResponseDataSchema,
53
+ },
45
54
  'match': {
46
55
  handler: async (plugin: Plugin, data: any) => plugin.match(data),
47
56
  requestValidator: MatchRequestDataSchema,
@@ -63,7 +72,7 @@ const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) =
63
72
  return null;
64
73
  },
65
74
  requestValidator: ApplyRequestDataSchema,
66
- responseValidator: ApplyResponseDataSchema
75
+ responseValidator: EmptyResponseDataSchema
67
76
  },
68
77
  }
69
78
 
@@ -7,6 +7,7 @@ import { spy } from 'sinon';
7
7
  import { ResourceSettings } from '../resource/resource-settings.js';
8
8
  import { TestConfig, TestStatefulParameter } from '../utils/test-utils.test.js';
9
9
  import { getPty } from '../pty/index.js';
10
+ import { z } from 'zod';
10
11
 
11
12
  interface TestConfig extends StringIndexedObject {
12
13
  propA: string;
@@ -170,6 +171,36 @@ describe('Plugin tests', () => {
170
171
  })
171
172
  })
172
173
 
174
+ it('Can get resource info (zod schema)', async () => {
175
+ const schema = z
176
+ .object({
177
+ plugins: z
178
+ .array(z.string())
179
+ .describe(
180
+ 'Asdf plugins to install. See: https://github.com/asdf-community for a full list'
181
+ )
182
+ })
183
+ .strict()
184
+
185
+ const resource = new class extends TestResource {
186
+ getSettings(): ResourceSettings<TestConfig> {
187
+ return {
188
+ id: 'typeId',
189
+ operatingSystems: [OS.Darwin],
190
+ schema,
191
+ }
192
+ }
193
+ }
194
+ const testPlugin = Plugin.create('testPlugin', [resource as any])
195
+
196
+ const resourceInfo = await testPlugin.getResourceInfo({ type: 'typeId' })
197
+ expect(resourceInfo.import).toMatchObject({
198
+ requiredParameters: [
199
+ 'plugins'
200
+ ]
201
+ })
202
+ })
203
+
173
204
  it('Get resource info to default import to the one specified in the resource settings', async () => {
174
205
  const schema = {
175
206
  '$schema': 'http://json-schema.org/draft-07/schema',