envlock-core 0.6.0 → 0.6.2
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/cli/index.js +21 -29
- package/dist/index.cjs +195 -0
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +20 -28
- package/package.json +8 -3
package/dist/cli/index.js
CHANGED
|
@@ -60,51 +60,43 @@ ${installHint}`);
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/invoke.ts
|
|
63
|
-
function runWithSecrets(options) {
|
|
63
|
+
async function runWithSecrets(options) {
|
|
64
64
|
const { envFile, environment, onePasswordEnvId, command, args } = options;
|
|
65
|
-
checkBinary(
|
|
66
|
-
"dotenvx",
|
|
67
|
-
"Install dotenvx: npm install -g @dotenvx/dotenvx\nOr add it as a dev dependency."
|
|
68
|
-
);
|
|
69
65
|
const privateKeyVar = `DOTENV_PRIVATE_KEY_${environment.toUpperCase()}`;
|
|
70
66
|
const keyAlreadyInjected = !!process.env[privateKeyVar];
|
|
71
|
-
|
|
72
|
-
if (keyAlreadyInjected) {
|
|
73
|
-
log.debug(`Spawning: dotenvx run -f ${envFile} -- ${command} ${args.join(" ")}`);
|
|
74
|
-
result = spawnSync(
|
|
75
|
-
"dotenvx",
|
|
76
|
-
["run", "-f", envFile, "--", command, ...args],
|
|
77
|
-
{ stdio: "inherit" }
|
|
78
|
-
);
|
|
79
|
-
if (result.error) {
|
|
80
|
-
throw new Error(`[envlock] Failed to spawn 'dotenvx': ${result.error.message}`);
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
67
|
+
if (!keyAlreadyInjected) {
|
|
83
68
|
checkBinary(
|
|
84
69
|
"op",
|
|
85
70
|
"Install 1Password CLI: brew install --cask 1password-cli@beta\nThen sign in: op signin"
|
|
86
71
|
);
|
|
87
|
-
log.debug(`
|
|
88
|
-
|
|
72
|
+
log.debug(`Re-invoking via: op run --environment ${onePasswordEnvId}`);
|
|
73
|
+
const result2 = spawnSync(
|
|
89
74
|
"op",
|
|
90
75
|
[
|
|
91
76
|
"run",
|
|
92
77
|
"--environment",
|
|
93
78
|
onePasswordEnvId,
|
|
94
79
|
"--",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"--",
|
|
100
|
-
command,
|
|
101
|
-
...args
|
|
80
|
+
process.execPath,
|
|
81
|
+
...process.execArgv,
|
|
82
|
+
process.argv[1],
|
|
83
|
+
...process.argv.slice(2)
|
|
102
84
|
],
|
|
103
85
|
{ stdio: "inherit" }
|
|
104
86
|
);
|
|
105
|
-
if (
|
|
106
|
-
throw new Error(`[envlock] Failed to spawn 'op': ${
|
|
87
|
+
if (result2.error) {
|
|
88
|
+
throw new Error(`[envlock] Failed to spawn 'op': ${result2.error.message}`);
|
|
107
89
|
}
|
|
90
|
+
process.exit(result2.status ?? 1);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
log.debug(`Decrypting ${envFile} via dotenvx`);
|
|
94
|
+
const { config } = await import("@dotenvx/dotenvx");
|
|
95
|
+
config({ path: envFile });
|
|
96
|
+
log.debug(`Spawning: ${command} ${args.join(" ")}`);
|
|
97
|
+
const result = spawnSync(command, args, { stdio: "inherit" });
|
|
98
|
+
if (result.error) {
|
|
99
|
+
throw new Error(`[envlock] Failed to spawn '${command}': ${result.error.message}`);
|
|
108
100
|
}
|
|
109
101
|
process.exit(result.status ?? 1);
|
|
110
102
|
}
|
|
@@ -239,7 +231,7 @@ async function run(argv, cwd = process.cwd()) {
|
|
|
239
231
|
log.debug(`Environment: ${environment}`);
|
|
240
232
|
log.debug(`Env file: ${envFile}`);
|
|
241
233
|
log.debug(`Command: ${command} ${args.join(" ")}`);
|
|
242
|
-
runWithSecrets({ envFile, environment, onePasswordEnvId, command, args });
|
|
234
|
+
await runWithSecrets({ envFile, environment, onePasswordEnvId, command, args });
|
|
243
235
|
}
|
|
244
236
|
var _resolvedArgv1 = (() => {
|
|
245
237
|
try {
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
ENVIRONMENTS: () => ENVIRONMENTS,
|
|
34
|
+
checkBinary: () => checkBinary,
|
|
35
|
+
findFreePort: () => findFreePort,
|
|
36
|
+
hasBinary: () => hasBinary,
|
|
37
|
+
log: () => log,
|
|
38
|
+
runWithSecrets: () => runWithSecrets,
|
|
39
|
+
setVerbose: () => setVerbose,
|
|
40
|
+
validateEnvFilePath: () => validateEnvFilePath,
|
|
41
|
+
validateOnePasswordEnvId: () => validateOnePasswordEnvId
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(src_exports);
|
|
44
|
+
|
|
45
|
+
// src/invoke.ts
|
|
46
|
+
var import_node_child_process2 = require("child_process");
|
|
47
|
+
|
|
48
|
+
// src/detect.ts
|
|
49
|
+
var import_node_child_process = require("child_process");
|
|
50
|
+
|
|
51
|
+
// src/logger.ts
|
|
52
|
+
var verbose = false;
|
|
53
|
+
function setVerbose(flag) {
|
|
54
|
+
verbose = flag;
|
|
55
|
+
}
|
|
56
|
+
var log = {
|
|
57
|
+
debug: (msg) => {
|
|
58
|
+
if (verbose) process.stderr.write(`[envlock:debug] ${msg}
|
|
59
|
+
`);
|
|
60
|
+
},
|
|
61
|
+
info: (msg) => {
|
|
62
|
+
process.stderr.write(`[envlock] ${msg}
|
|
63
|
+
`);
|
|
64
|
+
},
|
|
65
|
+
warn: (msg) => {
|
|
66
|
+
process.stderr.write(`[envlock] Warning: ${msg}
|
|
67
|
+
`);
|
|
68
|
+
},
|
|
69
|
+
error: (msg) => {
|
|
70
|
+
process.stderr.write(`[envlock] Error: ${msg}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/detect.ts
|
|
76
|
+
var WHICH = process.platform === "win32" ? "where" : "which";
|
|
77
|
+
function hasBinary(name) {
|
|
78
|
+
try {
|
|
79
|
+
(0, import_node_child_process.execFileSync)(WHICH, [name], { stdio: "pipe" });
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function checkBinary(name, installHint) {
|
|
86
|
+
if (!hasBinary(name)) {
|
|
87
|
+
throw new Error(`[envlock] '${name}' not found in PATH.
|
|
88
|
+
${installHint}`);
|
|
89
|
+
}
|
|
90
|
+
log.debug(`Binary check: ${name} found`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/invoke.ts
|
|
94
|
+
async function runWithSecrets(options) {
|
|
95
|
+
const { envFile, environment, onePasswordEnvId, command, args } = options;
|
|
96
|
+
const privateKeyVar = `DOTENV_PRIVATE_KEY_${environment.toUpperCase()}`;
|
|
97
|
+
const keyAlreadyInjected = !!process.env[privateKeyVar];
|
|
98
|
+
if (!keyAlreadyInjected) {
|
|
99
|
+
checkBinary(
|
|
100
|
+
"op",
|
|
101
|
+
"Install 1Password CLI: brew install --cask 1password-cli@beta\nThen sign in: op signin"
|
|
102
|
+
);
|
|
103
|
+
log.debug(`Re-invoking via: op run --environment ${onePasswordEnvId}`);
|
|
104
|
+
const result2 = (0, import_node_child_process2.spawnSync)(
|
|
105
|
+
"op",
|
|
106
|
+
[
|
|
107
|
+
"run",
|
|
108
|
+
"--environment",
|
|
109
|
+
onePasswordEnvId,
|
|
110
|
+
"--",
|
|
111
|
+
process.execPath,
|
|
112
|
+
...process.execArgv,
|
|
113
|
+
process.argv[1],
|
|
114
|
+
...process.argv.slice(2)
|
|
115
|
+
],
|
|
116
|
+
{ stdio: "inherit" }
|
|
117
|
+
);
|
|
118
|
+
if (result2.error) {
|
|
119
|
+
throw new Error(`[envlock] Failed to spawn 'op': ${result2.error.message}`);
|
|
120
|
+
}
|
|
121
|
+
process.exit(result2.status ?? 1);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
log.debug(`Decrypting ${envFile} via dotenvx`);
|
|
125
|
+
const { config } = await import("@dotenvx/dotenvx");
|
|
126
|
+
config({ path: envFile });
|
|
127
|
+
log.debug(`Spawning: ${command} ${args.join(" ")}`);
|
|
128
|
+
const result = (0, import_node_child_process2.spawnSync)(command, args, { stdio: "inherit" });
|
|
129
|
+
if (result.error) {
|
|
130
|
+
throw new Error(`[envlock] Failed to spawn '${command}': ${result.error.message}`);
|
|
131
|
+
}
|
|
132
|
+
process.exit(result.status ?? 1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/validate.ts
|
|
136
|
+
var import_node_path = require("path");
|
|
137
|
+
var OP_ENV_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
138
|
+
function validateOnePasswordEnvId(id) {
|
|
139
|
+
if (!id || !OP_ENV_ID_PATTERN.test(id)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[envlock] Invalid onePasswordEnvId: "${id}". Must be a lowercase alphanumeric string (hyphens allowed), e.g. 'ca6uypwvab5mevel44gqdc2zae'.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function validateEnvFilePath(envFile, cwd) {
|
|
146
|
+
if (envFile.includes("\0")) {
|
|
147
|
+
throw new Error(`[envlock] Invalid env file path: null bytes are not allowed.`);
|
|
148
|
+
}
|
|
149
|
+
const resolved = (0, import_node_path.resolve)(cwd, envFile);
|
|
150
|
+
const base = (0, import_node_path.resolve)(cwd);
|
|
151
|
+
const rel = (0, import_node_path.relative)(base, resolved);
|
|
152
|
+
if (rel.startsWith("..") || (0, import_node_path.isAbsolute)(rel)) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`[envlock] Invalid env file path: "${envFile}" resolves outside the project directory.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/types.ts
|
|
160
|
+
var ENVIRONMENTS = {
|
|
161
|
+
development: "development",
|
|
162
|
+
staging: "staging",
|
|
163
|
+
production: "production"
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/find-port.ts
|
|
167
|
+
var import_node_net = require("net");
|
|
168
|
+
function isPortFree(port) {
|
|
169
|
+
return new Promise((resolve2) => {
|
|
170
|
+
const server = (0, import_node_net.createServer)();
|
|
171
|
+
server.once("error", () => resolve2(false));
|
|
172
|
+
server.once("listening", () => server.close(() => resolve2(true)));
|
|
173
|
+
server.listen(port, "127.0.0.1");
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function findFreePort(preferred) {
|
|
177
|
+
for (let port = preferred; port <= preferred + 10; port++) {
|
|
178
|
+
if (await isPortFree(port)) return port;
|
|
179
|
+
}
|
|
180
|
+
throw new Error(
|
|
181
|
+
`[envlock] No free port found in range ${preferred}\u2013${preferred + 10}.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
185
|
+
0 && (module.exports = {
|
|
186
|
+
ENVIRONMENTS,
|
|
187
|
+
checkBinary,
|
|
188
|
+
findFreePort,
|
|
189
|
+
hasBinary,
|
|
190
|
+
log,
|
|
191
|
+
runWithSecrets,
|
|
192
|
+
setVerbose,
|
|
193
|
+
validateEnvFilePath,
|
|
194
|
+
validateOnePasswordEnvId
|
|
195
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
declare const ENVIRONMENTS: {
|
|
2
|
+
readonly development: "development";
|
|
3
|
+
readonly staging: "staging";
|
|
4
|
+
readonly production: "production";
|
|
5
|
+
};
|
|
6
|
+
type Environment = keyof typeof ENVIRONMENTS;
|
|
7
|
+
interface EnvlockOptions {
|
|
8
|
+
onePasswordEnvId: string;
|
|
9
|
+
envFiles?: Partial<Record<Environment, string>>;
|
|
10
|
+
}
|
|
11
|
+
interface EnvlockConfig {
|
|
12
|
+
/**
|
|
13
|
+
* Your 1Password Environment ID.
|
|
14
|
+
* Can alternatively be set via the ENVLOCK_OP_ENV_ID environment variable.
|
|
15
|
+
*/
|
|
16
|
+
onePasswordEnvId?: string;
|
|
17
|
+
envFiles?: Partial<Record<Environment, string>>;
|
|
18
|
+
commands?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface RunWithSecretsOptions {
|
|
22
|
+
envFile: string;
|
|
23
|
+
environment: Environment;
|
|
24
|
+
onePasswordEnvId: string;
|
|
25
|
+
command: string;
|
|
26
|
+
args: string[];
|
|
27
|
+
}
|
|
28
|
+
declare function runWithSecrets(options: RunWithSecretsOptions): Promise<void>;
|
|
29
|
+
|
|
30
|
+
declare function hasBinary(name: string): boolean;
|
|
31
|
+
declare function checkBinary(name: string, installHint: string): void;
|
|
32
|
+
|
|
33
|
+
declare function validateOnePasswordEnvId(id: string): void;
|
|
34
|
+
declare function validateEnvFilePath(envFile: string, cwd: string): void;
|
|
35
|
+
|
|
36
|
+
declare function setVerbose(flag: boolean): void;
|
|
37
|
+
declare const log: {
|
|
38
|
+
debug: (msg: string) => void;
|
|
39
|
+
info: (msg: string) => void;
|
|
40
|
+
warn: (msg: string) => void;
|
|
41
|
+
error: (msg: string) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
declare function findFreePort(preferred: number): Promise<number>;
|
|
45
|
+
|
|
46
|
+
export { ENVIRONMENTS, type Environment, type EnvlockConfig, type EnvlockOptions, type RunWithSecretsOptions, checkBinary, findFreePort, hasBinary, log, runWithSecrets, setVerbose, validateEnvFilePath, validateOnePasswordEnvId };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ interface RunWithSecretsOptions {
|
|
|
25
25
|
command: string;
|
|
26
26
|
args: string[];
|
|
27
27
|
}
|
|
28
|
-
declare function runWithSecrets(options: RunWithSecretsOptions): void
|
|
28
|
+
declare function runWithSecrets(options: RunWithSecretsOptions): Promise<void>;
|
|
29
29
|
|
|
30
30
|
declare function hasBinary(name: string): boolean;
|
|
31
31
|
declare function checkBinary(name: string, installHint: string): void;
|
package/dist/index.js
CHANGED
|
@@ -47,51 +47,43 @@ ${installHint}`);
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// src/invoke.ts
|
|
50
|
-
function runWithSecrets(options) {
|
|
50
|
+
async function runWithSecrets(options) {
|
|
51
51
|
const { envFile, environment, onePasswordEnvId, command, args } = options;
|
|
52
|
-
checkBinary(
|
|
53
|
-
"dotenvx",
|
|
54
|
-
"Install dotenvx: npm install -g @dotenvx/dotenvx\nOr add it as a dev dependency."
|
|
55
|
-
);
|
|
56
52
|
const privateKeyVar = `DOTENV_PRIVATE_KEY_${environment.toUpperCase()}`;
|
|
57
53
|
const keyAlreadyInjected = !!process.env[privateKeyVar];
|
|
58
|
-
|
|
59
|
-
if (keyAlreadyInjected) {
|
|
60
|
-
log.debug(`Spawning: dotenvx run -f ${envFile} -- ${command} ${args.join(" ")}`);
|
|
61
|
-
result = spawnSync(
|
|
62
|
-
"dotenvx",
|
|
63
|
-
["run", "-f", envFile, "--", command, ...args],
|
|
64
|
-
{ stdio: "inherit" }
|
|
65
|
-
);
|
|
66
|
-
if (result.error) {
|
|
67
|
-
throw new Error(`[envlock] Failed to spawn 'dotenvx': ${result.error.message}`);
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
54
|
+
if (!keyAlreadyInjected) {
|
|
70
55
|
checkBinary(
|
|
71
56
|
"op",
|
|
72
57
|
"Install 1Password CLI: brew install --cask 1password-cli@beta\nThen sign in: op signin"
|
|
73
58
|
);
|
|
74
|
-
log.debug(`
|
|
75
|
-
|
|
59
|
+
log.debug(`Re-invoking via: op run --environment ${onePasswordEnvId}`);
|
|
60
|
+
const result2 = spawnSync(
|
|
76
61
|
"op",
|
|
77
62
|
[
|
|
78
63
|
"run",
|
|
79
64
|
"--environment",
|
|
80
65
|
onePasswordEnvId,
|
|
81
66
|
"--",
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"--",
|
|
87
|
-
command,
|
|
88
|
-
...args
|
|
67
|
+
process.execPath,
|
|
68
|
+
...process.execArgv,
|
|
69
|
+
process.argv[1],
|
|
70
|
+
...process.argv.slice(2)
|
|
89
71
|
],
|
|
90
72
|
{ stdio: "inherit" }
|
|
91
73
|
);
|
|
92
|
-
if (
|
|
93
|
-
throw new Error(`[envlock] Failed to spawn 'op': ${
|
|
74
|
+
if (result2.error) {
|
|
75
|
+
throw new Error(`[envlock] Failed to spawn 'op': ${result2.error.message}`);
|
|
94
76
|
}
|
|
77
|
+
process.exit(result2.status ?? 1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
log.debug(`Decrypting ${envFile} via dotenvx`);
|
|
81
|
+
const { config } = await import("@dotenvx/dotenvx");
|
|
82
|
+
config({ path: envFile });
|
|
83
|
+
log.debug(`Spawning: ${command} ${args.join(" ")}`);
|
|
84
|
+
const result = spawnSync(command, args, { stdio: "inherit" });
|
|
85
|
+
if (result.error) {
|
|
86
|
+
throw new Error(`[envlock] Failed to spawn '${command}': ${result.error.message}`);
|
|
95
87
|
}
|
|
96
88
|
process.exit(result.status ?? 1);
|
|
97
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envlock-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core 1Password + dotenvx secret injection logic for envlock",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
},
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"require": "./dist/index.cjs",
|
|
27
|
+
"import": "./dist/index.js"
|
|
27
28
|
}
|
|
28
29
|
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
29
31
|
"files": [
|
|
30
32
|
"dist"
|
|
31
33
|
],
|
|
@@ -35,6 +37,9 @@
|
|
|
35
37
|
"test": "vitest run",
|
|
36
38
|
"test:watch": "vitest"
|
|
37
39
|
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@dotenvx/dotenvx": "^1.59.1"
|
|
42
|
+
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"@types/node": "^20.14.10",
|
|
40
45
|
"tsup": "^8.0.0",
|