codify-plugin-lib 1.0.182-beta4 → 1.0.182-beta40

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 (46) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/plugin/plugin.d.ts +3 -3
  4. package/dist/plugin/plugin.js +18 -4
  5. package/dist/pty/background-pty.d.ts +3 -2
  6. package/dist/pty/background-pty.js +6 -14
  7. package/dist/pty/index.d.ts +4 -2
  8. package/dist/pty/seqeuntial-pty.d.ts +3 -2
  9. package/dist/pty/seqeuntial-pty.js +44 -10
  10. package/dist/resource/parsed-resource-settings.d.ts +3 -1
  11. package/dist/resource/parsed-resource-settings.js +15 -2
  12. package/dist/resource/resource-controller.js +5 -5
  13. package/dist/resource/resource-settings.d.ts +8 -2
  14. package/dist/resource/resource-settings.js +2 -2
  15. package/dist/test.d.ts +1 -0
  16. package/dist/test.js +5 -0
  17. package/dist/utils/file-utils.d.ts +23 -0
  18. package/dist/utils/file-utils.js +186 -0
  19. package/dist/utils/functions.js +2 -2
  20. package/dist/utils/index.d.ts +4 -0
  21. package/dist/utils/index.js +30 -0
  22. package/dist/utils/load-resources.d.ts +1 -0
  23. package/dist/utils/load-resources.js +46 -0
  24. package/dist/utils/package-json-utils.d.ts +12 -0
  25. package/dist/utils/package-json-utils.js +34 -0
  26. package/package.json +4 -3
  27. package/src/index.ts +2 -1
  28. package/src/plugin/plugin.test.ts +31 -0
  29. package/src/plugin/plugin.ts +21 -4
  30. package/src/pty/background-pty.ts +9 -18
  31. package/src/pty/index.ts +6 -4
  32. package/src/pty/seqeuntial-pty.ts +59 -14
  33. package/src/pty/sequential-pty.test.ts +138 -5
  34. package/src/resource/parsed-resource-settings.test.ts +24 -0
  35. package/src/resource/parsed-resource-settings.ts +23 -7
  36. package/src/resource/resource-controller.test.ts +126 -0
  37. package/src/resource/resource-controller.ts +5 -6
  38. package/src/resource/resource-settings.test.ts +36 -0
  39. package/src/resource/resource-settings.ts +11 -4
  40. package/src/utils/file-utils.test.ts +7 -0
  41. package/src/utils/file-utils.ts +231 -0
  42. package/src/utils/functions.ts +3 -3
  43. package/src/utils/index.ts +37 -0
  44. package/src/utils/internal-utils.test.ts +1 -0
  45. package/src/utils/load-resources.ts +53 -0
  46. package/src/utils/package-json-utils.ts +40 -0
@@ -0,0 +1,186 @@
1
+ import * as fsSync from 'node:fs';
2
+ import * as fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { Readable } from 'node:stream';
5
+ import { finished } from 'node:stream/promises';
6
+ import { Utils } from './index.js';
7
+ const SPACE_REGEX = /^\s*$/;
8
+ export class FileUtils {
9
+ static async downloadFile(url, destination) {
10
+ console.log(`Downloading file from ${url} to ${destination}`);
11
+ const { body } = await fetch(url);
12
+ const dirname = path.dirname(destination);
13
+ if (!await fs.stat(dirname).then((s) => s.isDirectory()).catch(() => false)) {
14
+ await fs.mkdir(dirname, { recursive: true });
15
+ }
16
+ const ws = fsSync.createWriteStream(destination);
17
+ // Different type definitions here for readable stream (NodeJS vs DOM). Small hack to fix that
18
+ await finished(Readable.fromWeb(body).pipe(ws));
19
+ console.log(`Finished downloading to ${destination}`);
20
+ }
21
+ static async addToShellRc(line) {
22
+ const lineToInsert = addLeadingSpacer(addTrailingSpacer(line));
23
+ await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert);
24
+ function addLeadingSpacer(line) {
25
+ return line.startsWith('\n')
26
+ ? line
27
+ : '\n' + line;
28
+ }
29
+ function addTrailingSpacer(line) {
30
+ return line.endsWith('\n')
31
+ ? line
32
+ : line + '\n';
33
+ }
34
+ }
35
+ static async addAllToShellRc(lines) {
36
+ const formattedLines = '\n' + lines.join('\n') + '\n';
37
+ const shellRc = Utils.getPrimaryShellRc();
38
+ console.log(`Adding to ${path.basename(shellRc)}:
39
+ ${lines.join('\n')}`);
40
+ await fs.appendFile(shellRc, formattedLines);
41
+ }
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
+ }
52
+ const shellRc = Utils.getPrimaryShellRc();
53
+ console.log(`Saving path: ${value} to ${shellRc}`);
54
+ if (prepend) {
55
+ await fs.appendFile(shellRc, `\nexport PATH=$PATH:${value};`, { encoding: 'utf8' });
56
+ return;
57
+ }
58
+ await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
59
+ }
60
+ static async removeFromFile(filePath, search) {
61
+ const contents = await fs.readFile(filePath, 'utf8');
62
+ const newContents = contents.replaceAll(search, '');
63
+ await fs.writeFile(filePath, newContents, 'utf8');
64
+ }
65
+ static async removeLineFromFile(filePath, search) {
66
+ const file = await fs.readFile(filePath, 'utf8');
67
+ const lines = file.split('\n');
68
+ let searchRegex;
69
+ let searchString;
70
+ if (typeof search === 'object') {
71
+ const startRegex = /^([\t ]*)?/;
72
+ const endRegex = /([\t ]*)?/;
73
+ // Augment regex with spaces criteria to make sure this function is not deleting lines that are comments or has other content.
74
+ searchRegex = search
75
+ ? new RegExp(startRegex.source + search.source + endRegex.source, search.flags)
76
+ : search;
77
+ }
78
+ if (typeof search === 'string') {
79
+ searchString = search;
80
+ }
81
+ for (let counter = lines.length; counter >= 0; counter--) {
82
+ if (!lines[counter]) {
83
+ continue;
84
+ }
85
+ if (searchString && lines[counter].includes(searchString)) {
86
+ lines.splice(counter, 1);
87
+ continue;
88
+ }
89
+ if (searchRegex && lines[counter].search(searchRegex) !== -1) {
90
+ lines.splice(counter, 1);
91
+ }
92
+ }
93
+ await fs.writeFile(filePath, lines.join('\n'));
94
+ console.log(`Removed line: ${search} from ${filePath}`);
95
+ }
96
+ static async removeLineFromShellRc(search) {
97
+ return FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
98
+ }
99
+ static async removeAllLinesFromShellRc(searches) {
100
+ for (const search of searches) {
101
+ await FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
102
+ }
103
+ }
104
+ // Append the string to the end of a file ensuring at least 1 lines of space between.
105
+ // Ex result:
106
+ // something something;
107
+ //
108
+ // newline;
109
+ static appendToFileWithSpacing(file, textToInsert) {
110
+ const lines = file.trimEnd().split(/\n/);
111
+ if (lines.length === 0) {
112
+ return textToInsert;
113
+ }
114
+ const endingNewLines = FileUtils.calculateEndingNewLines(lines);
115
+ const numNewLines = endingNewLines === -1
116
+ ? 0
117
+ : Math.max(0, 2 - endingNewLines);
118
+ return lines.join('\n') + '\n'.repeat(numNewLines) + textToInsert;
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
+ }
167
+ // This is overly complicated but it can be used to insert into any
168
+ // position in the future
169
+ static calculateEndingNewLines(lines) {
170
+ let counter = 0;
171
+ while (true) {
172
+ const line = lines.at(-counter - 1);
173
+ if (!line) {
174
+ return -1;
175
+ }
176
+ if (!SPACE_REGEX.test(line)) {
177
+ return counter;
178
+ }
179
+ counter++;
180
+ // Short circuit here because we don't need to check over 2;
181
+ if (counter > 2) {
182
+ return counter;
183
+ }
184
+ }
185
+ }
186
+ }
@@ -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,11 @@ 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>;
26
30
  };
@@ -1,5 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
+ import { getPty, SpawnStatus } from '../pty/index.js';
3
4
  export function isDebug() {
4
5
  return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
5
6
  }
@@ -28,6 +29,29 @@ export const Utils = {
28
29
  isLinux() {
29
30
  return os.platform() === 'linux';
30
31
  },
32
+ async isArmArch() {
33
+ const $ = getPty();
34
+ if (!Utils.isMacOS()) {
35
+ // On Linux, check uname -m
36
+ const query = await $.spawn('uname -m');
37
+ return query.data.trim() === 'aarch64' || query.data.trim() === 'arm64';
38
+ }
39
+ const query = await $.spawn('sysctl -n machdep.cpu.brand_string');
40
+ return /M(\d)/.test(query.data);
41
+ },
42
+ async isHomebrewInstalled() {
43
+ const $ = getPty();
44
+ const query = await $.spawnSafe('which brew', { interactive: true });
45
+ return query.status === SpawnStatus.SUCCESS;
46
+ },
47
+ async isRosetta2Installed() {
48
+ if (!Utils.isMacOS()) {
49
+ return false;
50
+ }
51
+ const $ = getPty();
52
+ const query = await $.spawnSafe('arch -x86_64 /usr/bin/true 2> /dev/null', { interactive: true });
53
+ return query.status === SpawnStatus.SUCCESS;
54
+ },
31
55
  getShell() {
32
56
  const shell = process.env.SHELL || '';
33
57
  if (shell.endsWith('bash')) {
@@ -108,4 +132,10 @@ export const Utils = {
108
132
  path.join(homeDir, '.profile'),
109
133
  ];
110
134
  },
135
+ async isDirectoryOnPath(directory) {
136
+ const $ = getPty();
137
+ const { data: pathQuery } = await $.spawn('echo $PATH', { interactive: true });
138
+ const lines = pathQuery.split(':');
139
+ return lines.includes(directory);
140
+ },
111
141
  };
@@ -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-beta4",
3
+ "version": "1.0.182-beta40",
4
4
  "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -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-beta7",
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",
package/src/index.ts CHANGED
@@ -12,9 +12,10 @@ export * from './resource/parsed-resource-settings.js';
12
12
  export * from './resource/resource.js'
13
13
  export * from './resource/resource-settings.js'
14
14
  export * from './stateful-parameter/stateful-parameter.js'
15
+ export * from './utils/file-utils.js'
16
+ export * from './utils/functions.js'
15
17
  export * from './utils/index.js'
16
18
  export * from './utils/verbosity-level.js'
17
- export * from './utils/functions.js'
18
19
 
19
20
  export async function runPlugin(plugin: Plugin) {
20
21
  const messageHandler = new MessageHandler(plugin);
@@ -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',
@@ -22,10 +22,12 @@ import { Plan } from '../plan/plan.js';
22
22
  import { BackgroundPty } from '../pty/background-pty.js';
23
23
  import { getPty } from '../pty/index.js';
24
24
  import { SequentialPty } from '../pty/seqeuntial-pty.js';
25
- import { Resource } from '../resource/resource.js';
26
25
  import { ResourceController } from '../resource/resource-controller.js';
26
+ import { listAllResources } from '../utils/load-resources.js';
27
+ import { findNearestPackageJson, readNearestPackageJson } from '../utils/package-json-utils.js';
27
28
  import { ptyLocalStorage } from '../utils/pty-local-storage.js';
28
29
  import { VerbosityLevel } from '../utils/verbosity-level.js';
30
+ import path from 'node:path';
29
31
 
30
32
  export class Plugin {
31
33
  planStorage: Map<string, Plan<any>>;
@@ -33,12 +35,27 @@ export class Plugin {
33
35
 
34
36
  constructor(
35
37
  public name: string,
38
+ public version: string,
36
39
  public resourceControllers: Map<string, ResourceController<ResourceConfig>>
37
40
  ) {
38
41
  this.planStorage = new Map();
39
42
  }
40
43
 
41
- static create(name: string, resources: Resource<any>[]) {
44
+ static async create() {
45
+ const packageJson = readNearestPackageJson();
46
+ if (!packageJson) {
47
+ throw new Error('Failed to read nearest package.json');
48
+ }
49
+
50
+ const { name, version } = packageJson;
51
+
52
+ const resourceLocations = await listAllResources();
53
+ const resources = await Promise.all(resourceLocations.map((l) => {
54
+ return import(l).then((r) => r.default ?? r);
55
+ }))
56
+
57
+ console.log(resources);
58
+
42
59
  const controllers = resources
43
60
  .map((resource) => new ResourceController(resource))
44
61
 
@@ -46,7 +63,7 @@ export class Plugin {
46
63
  controllers.map((r) => [r.typeId, r] as const)
47
64
  );
48
65
 
49
- return new Plugin(name, controllersMap);
66
+ return new Plugin(name, version, controllersMap);
50
67
  }
51
68
 
52
69
  async initialize(data: InitializeRequestData): Promise<InitializeResponseData> {
@@ -89,7 +106,7 @@ export class Plugin {
89
106
 
90
107
  const resource = this.resourceControllers.get(data.type)!;
91
108
 
92
- const schema = resource.settings.schema as JSONSchemaType<any> | undefined;
109
+ const schema = resource.parsedSettings.schema as JSONSchemaType<any> | undefined;
93
110
  const requiredPropertyNames = (
94
111
  resource.settings.importAndDestroy?.requiredParameters
95
112
  ?? (typeof resource.settings.allowMultiple === 'object' ? resource.settings.allowMultiple.identifyingParameters : null)
@@ -20,8 +20,10 @@ EventEmitter.defaultMaxListeners = 1000;
20
20
  * without a tty (or even a stdin) attached so interactive commands will not work.
21
21
  */
22
22
  export class BackgroundPty implements IPty {
23
+ private historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
23
24
  private basePty = pty.spawn(this.getDefaultShell(), ['-i'], {
24
- env: process.env, name: nanoid(6),
25
+ env: { ...process.env, ...this.historyIgnore },
26
+ name: nanoid(6),
25
27
  handleFlowControl: true
26
28
  });
27
29
 
@@ -31,17 +33,19 @@ export class BackgroundPty implements IPty {
31
33
  this.initialize();
32
34
  }
33
35
 
34
- async spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
36
+ async spawn(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult> {
35
37
  const spawnResult = await this.spawnSafe(cmd, options);
36
38
 
37
39
  if (spawnResult.status !== 'success') {
38
- throw new SpawnError(cmd, spawnResult.exitCode, spawnResult.data);
40
+ throw new SpawnError(Array.isArray(cmd) ? cmd.join(' ') : cmd, spawnResult.exitCode, spawnResult.data);
39
41
  }
40
42
 
41
43
  return spawnResult;
42
44
  }
43
45
 
44
- async spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult> {
46
+ async spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult> {
47
+ cmd = Array.isArray(cmd) ? cmd.join('\\\n') : cmd;
48
+
45
49
  // cid is command id
46
50
  const cid = nanoid(10);
47
51
  debugLog(cid);
@@ -102,7 +106,7 @@ export class BackgroundPty implements IPty {
102
106
  }
103
107
  });
104
108
 
105
- console.log(`Running command ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`)
109
+ console.log(`Running command: ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`)
106
110
  this.basePty.write(`${command}\r`);
107
111
 
108
112
  }));
@@ -128,19 +132,6 @@ export class BackgroundPty implements IPty {
128
132
  let outputBuffer = '';
129
133
 
130
134
  return new Promise(resolve => {
131
- // zsh-specific commands
132
- switch (Utils.getShell()) {
133
- case Shell.ZSH: {
134
- this.basePty.write('setopt HIST_NO_STORE;\n');
135
- break;
136
- }
137
-
138
- default: {
139
- this.basePty.write('export HISTIGNORE=\'history*\';\n');
140
- break;
141
- }
142
- }
143
-
144
135
  this.basePty.write(' unset PS1;\n');
145
136
  this.basePty.write(' unset PS0;\n')
146
137
  this.basePty.write(' echo setup complete\\"\n')
package/src/pty/index.ts CHANGED
@@ -28,8 +28,10 @@ export enum SpawnStatus {
28
28
  */
29
29
  export interface SpawnOptions {
30
30
  cwd?: string;
31
- env?: Record<string, unknown>,
32
- interactive?: boolean,
31
+ env?: Record<string, unknown>;
32
+ interactive?: boolean;
33
+ requiresRoot?: boolean;
34
+ stdin?: boolean;
33
35
  }
34
36
 
35
37
  export class SpawnError extends Error {
@@ -48,9 +50,9 @@ export class SpawnError extends Error {
48
50
  }
49
51
 
50
52
  export interface IPty {
51
- spawn(cmd: string, options?: SpawnOptions): Promise<SpawnResult>
53
+ spawn(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>
52
54
 
53
- spawnSafe(cmd: string, options?: SpawnOptions): Promise<SpawnResult>
55
+ spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise<SpawnResult>
54
56
 
55
57
  kill(): Promise<{ exitCode: number, signal?: number | undefined }>
56
58
  }