@codifycli/plugin-core 1.0.0-beta1
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/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.github/workflows/release.yaml +19 -0
- package/.github/workflows/unit-test-ci.yaml +18 -0
- package/.prettierrc.json +1 -0
- package/bin/build.js +189 -0
- 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/common/errors.d.ts +8 -0
- package/dist/common/errors.js +24 -0
- package/dist/entities/change-set.d.ts +24 -0
- package/dist/entities/change-set.js +152 -0
- package/dist/entities/errors.d.ts +4 -0
- package/dist/entities/errors.js +7 -0
- package/dist/entities/plan-types.d.ts +25 -0
- package/dist/entities/plan-types.js +1 -0
- package/dist/entities/plan.d.ts +15 -0
- package/dist/entities/plan.js +127 -0
- package/dist/entities/plugin.d.ts +16 -0
- package/dist/entities/plugin.js +80 -0
- package/dist/entities/resource-options.d.ts +31 -0
- package/dist/entities/resource-options.js +76 -0
- package/dist/entities/resource-types.d.ts +11 -0
- package/dist/entities/resource-types.js +1 -0
- package/dist/entities/resource.d.ts +42 -0
- package/dist/entities/resource.js +303 -0
- package/dist/entities/stateful-parameter.d.ts +29 -0
- package/dist/entities/stateful-parameter.js +46 -0
- package/dist/entities/transform-parameter.d.ts +4 -0
- package/dist/entities/transform-parameter.js +2 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/messages/handlers.d.ts +14 -0
- package/dist/messages/handlers.js +134 -0
- package/dist/messages/sender.d.ts +11 -0
- package/dist/messages/sender.js +57 -0
- package/dist/plan/change-set.d.ts +53 -0
- package/dist/plan/change-set.js +153 -0
- package/dist/plan/plan-types.d.ts +23 -0
- package/dist/plan/plan-types.js +1 -0
- package/dist/plan/plan.d.ts +66 -0
- package/dist/plan/plan.js +328 -0
- package/dist/plugin/plugin.d.ts +24 -0
- package/dist/plugin/plugin.js +200 -0
- package/dist/pty/background-pty.d.ts +21 -0
- package/dist/pty/background-pty.js +127 -0
- package/dist/pty/index.d.ts +50 -0
- package/dist/pty/index.js +20 -0
- package/dist/pty/promise-queue.d.ts +5 -0
- package/dist/pty/promise-queue.js +26 -0
- package/dist/pty/seqeuntial-pty.d.ts +17 -0
- package/dist/pty/seqeuntial-pty.js +119 -0
- package/dist/pty/vitest.config.d.ts +2 -0
- package/dist/pty/vitest.config.js +11 -0
- package/dist/resource/config-parser.d.ts +11 -0
- package/dist/resource/config-parser.js +21 -0
- package/dist/resource/parsed-resource-settings.d.ts +47 -0
- package/dist/resource/parsed-resource-settings.js +196 -0
- package/dist/resource/resource-controller.d.ts +36 -0
- package/dist/resource/resource-controller.js +402 -0
- package/dist/resource/resource-settings.d.ts +303 -0
- package/dist/resource/resource-settings.js +147 -0
- package/dist/resource/resource.d.ts +144 -0
- package/dist/resource/resource.js +44 -0
- package/dist/resource/stateful-parameter.d.ts +165 -0
- package/dist/resource/stateful-parameter.js +94 -0
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +2 -0
- package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
- package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
- package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
- package/dist/stateful-parameter/stateful-parameter.js +43 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +5 -0
- package/dist/utils/codify-spawn.d.ts +29 -0
- package/dist/utils/codify-spawn.js +136 -0
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.js +10 -0
- package/dist/utils/file-utils.d.ts +23 -0
- package/dist/utils/file-utils.js +186 -0
- package/dist/utils/functions.d.ts +12 -0
- package/dist/utils/functions.js +74 -0
- package/dist/utils/index.d.ts +46 -0
- package/dist/utils/index.js +271 -0
- package/dist/utils/internal-utils.d.ts +12 -0
- package/dist/utils/internal-utils.js +74 -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/dist/utils/pty-local-storage.d.ts +2 -0
- package/dist/utils/pty-local-storage.js +2 -0
- package/dist/utils/spawn-2.d.ts +5 -0
- package/dist/utils/spawn-2.js +7 -0
- package/dist/utils/spawn.d.ts +29 -0
- package/dist/utils/spawn.js +124 -0
- package/dist/utils/utils.d.ts +18 -0
- package/dist/utils/utils.js +86 -0
- package/dist/utils/verbosity-level.d.ts +5 -0
- package/dist/utils/verbosity-level.js +9 -0
- package/package.json +59 -0
- package/rollup.config.js +24 -0
- package/src/common/errors.test.ts +43 -0
- package/src/common/errors.ts +31 -0
- package/src/errors.ts +8 -0
- package/src/index.test.ts +6 -0
- package/src/index.ts +30 -0
- package/src/messages/handlers.test.ts +329 -0
- package/src/messages/handlers.ts +181 -0
- package/src/messages/sender.ts +69 -0
- package/src/plan/change-set.test.ts +280 -0
- package/src/plan/change-set.ts +236 -0
- package/src/plan/plan-types.ts +27 -0
- package/src/plan/plan.test.ts +413 -0
- package/src/plan/plan.ts +499 -0
- package/src/plugin/plugin.test.ts +533 -0
- package/src/plugin/plugin.ts +291 -0
- package/src/pty/background-pty.test.ts +69 -0
- package/src/pty/background-pty.ts +154 -0
- package/src/pty/index.test.ts +129 -0
- package/src/pty/index.ts +66 -0
- package/src/pty/promise-queue.ts +33 -0
- package/src/pty/seqeuntial-pty.ts +151 -0
- package/src/pty/sequential-pty.test.ts +194 -0
- package/src/resource/config-parser.ts +42 -0
- package/src/resource/parsed-resource-settings.test.ts +186 -0
- package/src/resource/parsed-resource-settings.ts +307 -0
- package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
- package/src/resource/resource-controller.test.ts +1081 -0
- package/src/resource/resource-controller.ts +563 -0
- package/src/resource/resource-settings.test.ts +1213 -0
- package/src/resource/resource-settings.ts +545 -0
- package/src/resource/resource.ts +157 -0
- package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
- package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
- package/src/stateful-parameter/stateful-parameter.ts +160 -0
- package/src/utils/debug.ts +11 -0
- package/src/utils/file-utils.test.ts +7 -0
- package/src/utils/file-utils.ts +231 -0
- package/src/utils/functions.ts +103 -0
- package/src/utils/index.ts +340 -0
- package/src/utils/internal-utils.test.ts +52 -0
- package/src/utils/pty-local-storage.ts +3 -0
- package/src/utils/test-utils.test.ts +96 -0
- package/src/utils/verbosity-level.ts +11 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { LinuxDistro } from 'codify-schemas';
|
|
2
|
+
import * as fsSync from 'node:fs';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { SpawnStatus, getPty } from '../pty/index.js';
|
|
7
|
+
export function isDebug() {
|
|
8
|
+
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
9
|
+
}
|
|
10
|
+
export var Shell;
|
|
11
|
+
(function (Shell) {
|
|
12
|
+
Shell["ZSH"] = "zsh";
|
|
13
|
+
Shell["BASH"] = "bash";
|
|
14
|
+
Shell["SH"] = "sh";
|
|
15
|
+
Shell["KSH"] = "ksh";
|
|
16
|
+
Shell["CSH"] = "csh";
|
|
17
|
+
Shell["FISH"] = "fish";
|
|
18
|
+
})(Shell || (Shell = {}));
|
|
19
|
+
export const Utils = {
|
|
20
|
+
getUser() {
|
|
21
|
+
return os.userInfo().username;
|
|
22
|
+
},
|
|
23
|
+
getSystemInfo() {
|
|
24
|
+
return {
|
|
25
|
+
os: os.type(),
|
|
26
|
+
shell: this.getShell(),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
isMacOS() {
|
|
30
|
+
return os.platform() === 'darwin';
|
|
31
|
+
},
|
|
32
|
+
isLinux() {
|
|
33
|
+
return os.platform() === 'linux';
|
|
34
|
+
},
|
|
35
|
+
async isArmArch() {
|
|
36
|
+
const $ = getPty();
|
|
37
|
+
if (!Utils.isMacOS()) {
|
|
38
|
+
// On Linux, check uname -m
|
|
39
|
+
const query = await $.spawn('uname -m');
|
|
40
|
+
return query.data.trim() === 'aarch64' || query.data.trim() === 'arm64';
|
|
41
|
+
}
|
|
42
|
+
const query = await $.spawn('sysctl -n machdep.cpu.brand_string');
|
|
43
|
+
return /M(\d)/.test(query.data);
|
|
44
|
+
},
|
|
45
|
+
async isHomebrewInstalled() {
|
|
46
|
+
const $ = getPty();
|
|
47
|
+
const query = await $.spawnSafe('which brew', { interactive: true });
|
|
48
|
+
return query.status === SpawnStatus.SUCCESS;
|
|
49
|
+
},
|
|
50
|
+
async isRosetta2Installed() {
|
|
51
|
+
if (!Utils.isMacOS()) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const $ = getPty();
|
|
55
|
+
const query = await $.spawnSafe('arch -x86_64 /usr/bin/true 2> /dev/null', { interactive: true });
|
|
56
|
+
return query.status === SpawnStatus.SUCCESS;
|
|
57
|
+
},
|
|
58
|
+
getShell() {
|
|
59
|
+
const shell = process.env.SHELL || '';
|
|
60
|
+
if (shell.endsWith('bash')) {
|
|
61
|
+
return Shell.BASH;
|
|
62
|
+
}
|
|
63
|
+
if (shell.endsWith('zsh')) {
|
|
64
|
+
return Shell.ZSH;
|
|
65
|
+
}
|
|
66
|
+
if (shell.endsWith('sh')) {
|
|
67
|
+
return Shell.SH;
|
|
68
|
+
}
|
|
69
|
+
if (shell.endsWith('csh')) {
|
|
70
|
+
return Shell.CSH;
|
|
71
|
+
}
|
|
72
|
+
if (shell.endsWith('ksh')) {
|
|
73
|
+
return Shell.KSH;
|
|
74
|
+
}
|
|
75
|
+
if (shell.endsWith('fish')) {
|
|
76
|
+
return Shell.FISH;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
},
|
|
80
|
+
getPrimaryShellRc() {
|
|
81
|
+
return this.getShellRcFiles()[0];
|
|
82
|
+
},
|
|
83
|
+
getShellRcFiles() {
|
|
84
|
+
const shell = process.env.SHELL || '';
|
|
85
|
+
const homeDir = os.homedir();
|
|
86
|
+
if (shell.endsWith('bash')) {
|
|
87
|
+
// Linux typically uses .bashrc, macOS uses .bash_profile
|
|
88
|
+
if (Utils.isLinux()) {
|
|
89
|
+
return [
|
|
90
|
+
path.join(homeDir, '.bashrc'),
|
|
91
|
+
path.join(homeDir, '.bash_profile'),
|
|
92
|
+
path.join(homeDir, '.profile'),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
path.join(homeDir, '.bash_profile'),
|
|
97
|
+
path.join(homeDir, '.bashrc'),
|
|
98
|
+
path.join(homeDir, '.profile'),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
if (shell.endsWith('zsh')) {
|
|
102
|
+
return [
|
|
103
|
+
path.join(homeDir, '.zshrc'),
|
|
104
|
+
path.join(homeDir, '.zprofile'),
|
|
105
|
+
path.join(homeDir, '.zshenv'),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
if (shell.endsWith('sh')) {
|
|
109
|
+
return [
|
|
110
|
+
path.join(homeDir, '.profile'),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
if (shell.endsWith('ksh')) {
|
|
114
|
+
return [
|
|
115
|
+
path.join(homeDir, '.profile'),
|
|
116
|
+
path.join(homeDir, '.kshrc'),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
if (shell.endsWith('csh')) {
|
|
120
|
+
return [
|
|
121
|
+
path.join(homeDir, '.cshrc'),
|
|
122
|
+
path.join(homeDir, '.login'),
|
|
123
|
+
path.join(homeDir, '.logout'),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
if (shell.endsWith('fish')) {
|
|
127
|
+
return [
|
|
128
|
+
path.join(homeDir, '.config/fish/config.fish'),
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
// Default to bash-style files
|
|
132
|
+
return [
|
|
133
|
+
path.join(homeDir, '.bashrc'),
|
|
134
|
+
path.join(homeDir, '.bash_profile'),
|
|
135
|
+
path.join(homeDir, '.profile'),
|
|
136
|
+
];
|
|
137
|
+
},
|
|
138
|
+
async isDirectoryOnPath(directory) {
|
|
139
|
+
const $ = getPty();
|
|
140
|
+
const { data: pathQuery } = await $.spawn('echo $PATH', { interactive: true });
|
|
141
|
+
const lines = pathQuery.split(':');
|
|
142
|
+
return lines.includes(directory);
|
|
143
|
+
},
|
|
144
|
+
async assertBrewInstalled() {
|
|
145
|
+
const $ = getPty();
|
|
146
|
+
const brewCheck = await $.spawnSafe('which brew', { interactive: true });
|
|
147
|
+
if (brewCheck.status === SpawnStatus.ERROR) {
|
|
148
|
+
throw new Error(`Homebrew is not installed. Cannot install git-lfs without Homebrew installed.
|
|
149
|
+
|
|
150
|
+
Brew can be installed using Codify:
|
|
151
|
+
{
|
|
152
|
+
"type": "homebrew",
|
|
153
|
+
}`);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Installs a package via the system package manager. This will use Homebrew on macOS and apt on Ubuntu/Debian or dnf on Fedora.
|
|
158
|
+
* @param packageName
|
|
159
|
+
*/
|
|
160
|
+
async installViaPkgMgr(packageName) {
|
|
161
|
+
const $ = getPty();
|
|
162
|
+
if (Utils.isMacOS()) {
|
|
163
|
+
await this.assertBrewInstalled();
|
|
164
|
+
await $.spawn(`brew install ${packageName}`, { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } });
|
|
165
|
+
}
|
|
166
|
+
if (Utils.isLinux()) {
|
|
167
|
+
const isAptInstalled = await $.spawnSafe('which apt');
|
|
168
|
+
if (isAptInstalled.status === SpawnStatus.SUCCESS) {
|
|
169
|
+
await $.spawn('apt-get update', { requiresRoot: true });
|
|
170
|
+
const { status, data } = await $.spawnSafe(`apt-get -y install ${packageName}`, {
|
|
171
|
+
requiresRoot: true,
|
|
172
|
+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
|
|
173
|
+
});
|
|
174
|
+
if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) {
|
|
175
|
+
await $.spawn('dpkg --configure -a', { requiresRoot: true });
|
|
176
|
+
await $.spawn(`apt-get -y install ${packageName}`, {
|
|
177
|
+
requiresRoot: true,
|
|
178
|
+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (status === SpawnStatus.ERROR) {
|
|
183
|
+
throw new Error(`Failed to install package ${packageName} via apt: ${data}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const isDnfInstalled = await $.spawnSafe('which dnf');
|
|
187
|
+
if (isDnfInstalled.status === SpawnStatus.SUCCESS) {
|
|
188
|
+
await $.spawn('dnf update', { requiresRoot: true });
|
|
189
|
+
await $.spawn(`dnf install ${packageName} -y`, { requiresRoot: true });
|
|
190
|
+
}
|
|
191
|
+
const isYumInstalled = await $.spawnSafe('which yum');
|
|
192
|
+
if (isYumInstalled.status === SpawnStatus.SUCCESS) {
|
|
193
|
+
await $.spawn('yum update', { requiresRoot: true });
|
|
194
|
+
await $.spawn(`yum install ${packageName} -y`, { requiresRoot: true });
|
|
195
|
+
}
|
|
196
|
+
const isPacmanInstalled = await $.spawnSafe('which pacman');
|
|
197
|
+
if (isPacmanInstalled.status === SpawnStatus.SUCCESS) {
|
|
198
|
+
await $.spawn('pacman -Syu', { requiresRoot: true });
|
|
199
|
+
await $.spawn(`pacman -S ${packageName} --noconfirm`, { requiresRoot: true });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
async uninstallViaPkgMgr(packageName) {
|
|
204
|
+
const $ = getPty();
|
|
205
|
+
if (Utils.isMacOS()) {
|
|
206
|
+
await this.assertBrewInstalled();
|
|
207
|
+
const { status } = await $.spawnSafe(`brew uninstall --zap ${packageName}`, {
|
|
208
|
+
interactive: true,
|
|
209
|
+
env: { HOMEBREW_NO_AUTO_UPDATE: 1 }
|
|
210
|
+
});
|
|
211
|
+
return status === SpawnStatus.SUCCESS;
|
|
212
|
+
}
|
|
213
|
+
if (Utils.isLinux()) {
|
|
214
|
+
const isAptInstalled = await $.spawnSafe('which apt');
|
|
215
|
+
if (isAptInstalled.status === SpawnStatus.SUCCESS) {
|
|
216
|
+
const { status } = await $.spawnSafe(`apt-get autoremove -y --purge ${packageName}`, {
|
|
217
|
+
requiresRoot: true,
|
|
218
|
+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
|
|
219
|
+
});
|
|
220
|
+
return status === SpawnStatus.SUCCESS;
|
|
221
|
+
}
|
|
222
|
+
const isDnfInstalled = await $.spawnSafe('which dnf');
|
|
223
|
+
if (isDnfInstalled.status === SpawnStatus.SUCCESS) {
|
|
224
|
+
const { status } = await $.spawnSafe(`dnf autoremove ${packageName} -y`, { requiresRoot: true });
|
|
225
|
+
return status === SpawnStatus.SUCCESS;
|
|
226
|
+
}
|
|
227
|
+
const isYumInstalled = await $.spawnSafe('which yum');
|
|
228
|
+
if (isYumInstalled.status === SpawnStatus.SUCCESS) {
|
|
229
|
+
const { status } = await $.spawnSafe(`yum autoremove ${packageName} -y`, { requiresRoot: true });
|
|
230
|
+
return status === SpawnStatus.SUCCESS;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
},
|
|
236
|
+
async getLinuxDistro() {
|
|
237
|
+
const osRelease = await fs.readFile('/etc/os-release', 'utf8');
|
|
238
|
+
const lines = osRelease.split('\n');
|
|
239
|
+
for (const line of lines) {
|
|
240
|
+
if (line.startsWith('ID=')) {
|
|
241
|
+
const distroId = line.slice(3).trim().replaceAll('"', '');
|
|
242
|
+
return Object.values(LinuxDistro).includes(distroId) ? distroId : undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
},
|
|
247
|
+
async isUbuntu() {
|
|
248
|
+
return (await this.getLinuxDistro()) === LinuxDistro.UBUNTU;
|
|
249
|
+
},
|
|
250
|
+
async isDebian() {
|
|
251
|
+
return (await this.getLinuxDistro()) === LinuxDistro.DEBIAN;
|
|
252
|
+
},
|
|
253
|
+
async isArch() {
|
|
254
|
+
return (await this.getLinuxDistro()) === LinuxDistro.ARCH;
|
|
255
|
+
},
|
|
256
|
+
async isCentOS() {
|
|
257
|
+
return (await this.getLinuxDistro()) === LinuxDistro.CENTOS;
|
|
258
|
+
},
|
|
259
|
+
async isFedora() {
|
|
260
|
+
return (await this.getLinuxDistro()) === LinuxDistro.FEDORA;
|
|
261
|
+
},
|
|
262
|
+
async isRHEL() {
|
|
263
|
+
return (await this.getLinuxDistro()) === LinuxDistro.RHEL;
|
|
264
|
+
},
|
|
265
|
+
isDebianBased() {
|
|
266
|
+
return fsSync.existsSync('/etc/debian_version');
|
|
267
|
+
},
|
|
268
|
+
isRedhatBased() {
|
|
269
|
+
return fsSync.existsSync('/etc/redhat-release');
|
|
270
|
+
}
|
|
271
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
export declare function splitUserConfig<T extends StringIndexedObject>(config: ResourceConfig & T): {
|
|
3
|
+
parameters: T;
|
|
4
|
+
coreParameters: ResourceConfig;
|
|
5
|
+
};
|
|
6
|
+
export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
|
|
7
|
+
export declare function untildify(pathWithTilde: string): string;
|
|
8
|
+
export declare function tildify(pathWithTilde: string): string;
|
|
9
|
+
export declare function resolvePathWithVariables(pathWithVariables: string): string;
|
|
10
|
+
export declare function addVariablesToPath(pathWithoutVariables: string): string;
|
|
11
|
+
export declare function unhome(pathWithHome: string): string;
|
|
12
|
+
export declare function areArraysEqual(isElementEqual: ((desired: unknown, current: unknown) => boolean) | undefined, desired: unknown, current: unknown): boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function splitUserConfig(config) {
|
|
4
|
+
const coreParameters = {
|
|
5
|
+
type: config.type,
|
|
6
|
+
...(config.name ? { name: config.name } : {}),
|
|
7
|
+
...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
|
|
8
|
+
};
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
const { type, name, dependsOn, ...parameters } = config;
|
|
11
|
+
return {
|
|
12
|
+
parameters: parameters,
|
|
13
|
+
coreParameters,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function setsEqual(set1, set2) {
|
|
17
|
+
return set1.size === set2.size && [...set1].every((v) => set2.has(v));
|
|
18
|
+
}
|
|
19
|
+
const homeDirectory = os.homedir();
|
|
20
|
+
export function untildify(pathWithTilde) {
|
|
21
|
+
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
22
|
+
}
|
|
23
|
+
export function tildify(pathWithTilde) {
|
|
24
|
+
return homeDirectory ? pathWithTilde.replace(homeDirectory, '~') : pathWithTilde;
|
|
25
|
+
}
|
|
26
|
+
export function resolvePathWithVariables(pathWithVariables) {
|
|
27
|
+
// @ts-expect-error Ignore this for now
|
|
28
|
+
return pathWithVariables.replace(/\$([A-Z_]+[A-Z0-9_]*)|\${([A-Z0-9_]*)}/ig, (_, a, b) => process.env[a || b]);
|
|
29
|
+
}
|
|
30
|
+
export function addVariablesToPath(pathWithoutVariables) {
|
|
31
|
+
let result = pathWithoutVariables;
|
|
32
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
33
|
+
if (!value || !path.isAbsolute(value) || value === '/' || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
result = result.replaceAll(value, `$${key}`);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
export function unhome(pathWithHome) {
|
|
41
|
+
return pathWithHome.includes('$HOME') ? pathWithHome.replaceAll('$HOME', os.homedir()) : pathWithHome;
|
|
42
|
+
}
|
|
43
|
+
export function areArraysEqual(isElementEqual, desired, current) {
|
|
44
|
+
if (!desired || !current) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
48
|
+
throw new Error(`A non-array value:
|
|
49
|
+
|
|
50
|
+
Desired: ${JSON.stringify(desired, null, 2)}
|
|
51
|
+
|
|
52
|
+
Current: ${JSON.stringify(desired, null, 2)}
|
|
53
|
+
|
|
54
|
+
Was provided even though type array was specified.
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
if (desired.length !== current.length) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const desiredCopy = [...desired];
|
|
61
|
+
const currentCopy = [...current];
|
|
62
|
+
// Algorithm for to check equality between two un-ordered; un-hashable arrays using
|
|
63
|
+
// an isElementEqual method. Time: O(n^2)
|
|
64
|
+
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
65
|
+
const idx = currentCopy.findIndex((e2) => (isElementEqual
|
|
66
|
+
?? ((a, b) => a === b))(desiredCopy[counter], e2));
|
|
67
|
+
if (idx === -1) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
desiredCopy.splice(counter, 1);
|
|
71
|
+
currentCopy.splice(idx, 1);
|
|
72
|
+
}
|
|
73
|
+
return currentCopy.length === 0;
|
|
74
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { SpawnOptions } from 'node:child_process';
|
|
3
|
+
export declare enum SpawnStatus {
|
|
4
|
+
SUCCESS = "success",
|
|
5
|
+
ERROR = "error"
|
|
6
|
+
}
|
|
7
|
+
export interface SpawnResult {
|
|
8
|
+
status: SpawnStatus;
|
|
9
|
+
data: string;
|
|
10
|
+
}
|
|
11
|
+
type CodifySpawnOptions = {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
throws?: boolean;
|
|
14
|
+
requiresRoot?: 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 $(cmd: string, opts?: CodifySpawnOptions): Promise<SpawnResult>;
|
|
28
|
+
export declare function isDebug(): boolean;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { MessageCmd, SudoRequestResponseDataSchema } from 'codify-schemas';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { SudoError } from '../errors.js';
|
|
5
|
+
const ajv = new Ajv({
|
|
6
|
+
strict: true,
|
|
7
|
+
});
|
|
8
|
+
const validateSudoRequestResponse = ajv.compile(SudoRequestResponseDataSchema);
|
|
9
|
+
export var SpawnStatus;
|
|
10
|
+
(function (SpawnStatus) {
|
|
11
|
+
SpawnStatus["SUCCESS"] = "success";
|
|
12
|
+
SpawnStatus["ERROR"] = "error";
|
|
13
|
+
})(SpawnStatus || (SpawnStatus = {}));
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param cmd Command to run. Ex: `rm -rf`
|
|
17
|
+
* @param opts Standard options for node spawn. Additional argument:
|
|
18
|
+
* throws determines if a shell will throw a JS error. Defaults to true
|
|
19
|
+
*
|
|
20
|
+
* @see promiseSpawn
|
|
21
|
+
* @see spawn
|
|
22
|
+
*
|
|
23
|
+
* @returns SpawnResult { status: SUCCESS | ERROR; data: string }
|
|
24
|
+
*/
|
|
25
|
+
export async function $(cmd, opts) {
|
|
26
|
+
const throws = opts?.throws ?? true;
|
|
27
|
+
console.log(`Running command: ${cmd}`);
|
|
28
|
+
try {
|
|
29
|
+
// TODO: Need to benchmark the effects of using sh vs zsh for shell.
|
|
30
|
+
// Seems like zsh shells run slower
|
|
31
|
+
let result;
|
|
32
|
+
if (!opts?.requiresRoot) {
|
|
33
|
+
result = await internalSpawn(cmd, opts ?? {});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
result = await externalSpawnWithSudo(cmd, opts);
|
|
37
|
+
}
|
|
38
|
+
if (result.status !== SpawnStatus.SUCCESS) {
|
|
39
|
+
throw new Error(result.data);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (isDebug()) {
|
|
45
|
+
console.error(`CodifySpawn error for command ${cmd}`, error);
|
|
46
|
+
}
|
|
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: error + '',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function internalSpawn(cmd, opts) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const output = [];
|
|
68
|
+
// Source start up shells to emulate a users environment vs. a non-interactive non-login shell script
|
|
69
|
+
// Ignore all stdin
|
|
70
|
+
const _process = spawn(`source ~/.zshrc; ${cmd}`, [], {
|
|
71
|
+
...opts,
|
|
72
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
|
+
shell: 'zsh',
|
|
74
|
+
});
|
|
75
|
+
const { stdout, stderr, stdin } = _process;
|
|
76
|
+
stdout.setEncoding('utf8');
|
|
77
|
+
stderr.setEncoding('utf8');
|
|
78
|
+
stdout.on('data', (data) => {
|
|
79
|
+
output.push(data.toString());
|
|
80
|
+
});
|
|
81
|
+
stderr.on('data', (data) => {
|
|
82
|
+
output.push(data.toString());
|
|
83
|
+
});
|
|
84
|
+
_process.on('error', (data) => {
|
|
85
|
+
});
|
|
86
|
+
// please node that this is not a full replacement for 'inherit'
|
|
87
|
+
// the child process can and will detect if stdout is a pty and change output based on it
|
|
88
|
+
// the terminal context is lost & ansi information (coloring) etc will be lost
|
|
89
|
+
if (stdout && stderr) {
|
|
90
|
+
stdout.pipe(process.stdout);
|
|
91
|
+
stderr.pipe(process.stderr);
|
|
92
|
+
}
|
|
93
|
+
_process.on('close', (code) => {
|
|
94
|
+
resolve({
|
|
95
|
+
status: code === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
|
|
96
|
+
data: output.join('\n'),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function externalSpawnWithSudo(cmd, opts) {
|
|
102
|
+
return await new Promise((resolve) => {
|
|
103
|
+
const listener = (data) => {
|
|
104
|
+
if (data.cmd === MessageCmd.SUDO_REQUEST + '_Response') {
|
|
105
|
+
process.removeListener('message', listener);
|
|
106
|
+
if (!validateSudoRequestResponse(data.data)) {
|
|
107
|
+
throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
|
|
108
|
+
}
|
|
109
|
+
resolve(data.data);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
process.on('message', listener);
|
|
113
|
+
process.send({
|
|
114
|
+
cmd: MessageCmd.SUDO_REQUEST,
|
|
115
|
+
data: {
|
|
116
|
+
command: cmd,
|
|
117
|
+
options: opts ?? {},
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export function isDebug() {
|
|
123
|
+
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
124
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
export declare const VerbosityLevel: {
|
|
3
|
+
level: number;
|
|
4
|
+
get(): number;
|
|
5
|
+
set(level: number): void;
|
|
6
|
+
};
|
|
7
|
+
export declare function isDebug(): boolean;
|
|
8
|
+
export declare function splitUserConfig<T extends StringIndexedObject>(config: ResourceConfig & T): {
|
|
9
|
+
parameters: T;
|
|
10
|
+
coreParameters: ResourceConfig;
|
|
11
|
+
};
|
|
12
|
+
export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
|
|
13
|
+
export declare function untildify(pathWithTilde: string): string;
|
|
14
|
+
export declare function tildify(pathWithTilde: string): string;
|
|
15
|
+
export declare function resolvePathWithVariables(pathWithVariables: string): string;
|
|
16
|
+
export declare function addVariablesToPath(pathWithoutVariables: string): string;
|
|
17
|
+
export declare function unhome(pathWithHome: string): string;
|
|
18
|
+
export declare function areArraysEqual(isElementEqual: ((desired: unknown, current: unknown) => boolean) | undefined, desired: unknown, current: unknown): boolean;
|