@chamesh2020/keystone 0.0.1
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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/cli.cjs +584 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +561 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +482 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +432 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Package version — kept in sync with package.json */
|
|
2
|
+
declare const version = "0.0.1";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface representing the key bundle structure.
|
|
6
|
+
*/
|
|
7
|
+
interface ProjectKeyBundle {
|
|
8
|
+
projectId: string;
|
|
9
|
+
auth: {
|
|
10
|
+
privateJwk: JsonWebKey;
|
|
11
|
+
publicJwk: JsonWebKey;
|
|
12
|
+
};
|
|
13
|
+
encryption: {
|
|
14
|
+
privateJwk: JsonWebKey;
|
|
15
|
+
publicJwk: JsonWebKey;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validates that a parsed object conforms to the ProjectKeyBundle schema.
|
|
20
|
+
* Throws a descriptive error if the shape is invalid.
|
|
21
|
+
*/
|
|
22
|
+
declare function validateKeyBundle(data: unknown): ProjectKeyBundle;
|
|
23
|
+
/**
|
|
24
|
+
* Generates an Ed25519 key pair for authentication and an RSA-OAEP key pair for encryption.
|
|
25
|
+
* Both are returned as JWK (JSON Web Key) objects.
|
|
26
|
+
*/
|
|
27
|
+
declare function generateProjectKeys(projectId: string): Promise<ProjectKeyBundle>;
|
|
28
|
+
/**
|
|
29
|
+
* Encrypt plaintext using hybrid encryption (AES-256-GCM + RSA-OAEP envelope).
|
|
30
|
+
*
|
|
31
|
+
* 1. Generates a random 256-bit AES-GCM key
|
|
32
|
+
* 2. Encrypts the plaintext with AES-256-GCM (no size limit)
|
|
33
|
+
* 3. Wraps the AES key with RSA-OAEP (only 32 bytes — well within the 190-byte limit)
|
|
34
|
+
* 4. Returns `ks1:<base64(wrappedKey ‖ iv ‖ aesCiphertext)>`
|
|
35
|
+
*/
|
|
36
|
+
declare function encryptSecret(plainText: string, rsaPubJwk: JsonWebKey): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Decrypt ciphertext using hybrid decryption (AES-256-GCM + RSA-OAEP envelope).
|
|
39
|
+
* Supports both the new hybrid format (`ks1:` prefix) and legacy RSA-only format
|
|
40
|
+
* for backward compatibility with previously stored secrets.
|
|
41
|
+
*/
|
|
42
|
+
declare function decryptSecret(ciphertext: string, rsaPrivJwk: JsonWebKey): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Sign payload using Ed25519 private key
|
|
45
|
+
*/
|
|
46
|
+
declare function signMessage(messageStr: string, ed25519PrivJwk: JsonWebKey): Promise<string>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sends the public verification and public encryption keys to create a new project namespace on the server.
|
|
50
|
+
*/
|
|
51
|
+
declare function initProject(projectId: string, keys: ProjectKeyBundle, baseUrl: string): Promise<{
|
|
52
|
+
message: string;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Encrypts the secret locally, signs the metadata package, and uploads it to the backend.
|
|
56
|
+
*/
|
|
57
|
+
declare function storeSecret(projectId: string, secretName: string, plainText: string, keys: ProjectKeyBundle, baseUrl: string): Promise<{
|
|
58
|
+
message: string;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Verifies identity with a signed timestamp, retrieves, and decrypts the secret.
|
|
62
|
+
*/
|
|
63
|
+
declare function retrieveSecret(projectId: string, secretName: string, keys: ProjectKeyBundle, baseUrl: string): Promise<string>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parses a dotenv file content into key-value pairs.
|
|
67
|
+
*/
|
|
68
|
+
declare function parseDotEnv(content: string): Record<string, string>;
|
|
69
|
+
/**
|
|
70
|
+
* Finds and loads .env and .env.local files in the current working directory,
|
|
71
|
+
* loading variables into process.env if they are not already set.
|
|
72
|
+
*/
|
|
73
|
+
declare function loadEnvFiles(cwd?: string): Promise<void>;
|
|
74
|
+
interface LoadSecretsOptions {
|
|
75
|
+
credentialsPath?: string;
|
|
76
|
+
baseUrl?: string;
|
|
77
|
+
cwd?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Loads .env files, reads credentials, and fetches secrets from the Keystone vault
|
|
81
|
+
* for any environment variables with value 'LOAD_FROM_KEYSTONE' or 'LOAD FROM KEYSTONE'.
|
|
82
|
+
*/
|
|
83
|
+
declare function loadSecrets(options?: LoadSecretsOptions): Promise<Record<string, string>>;
|
|
84
|
+
/**
|
|
85
|
+
* Snake_case alias for loadSecrets
|
|
86
|
+
*/
|
|
87
|
+
declare const load_secrets: typeof loadSecrets;
|
|
88
|
+
/**
|
|
89
|
+
* Appends or updates an environment variable key with 'LOAD_FROM_KEYSTONE' placeholder
|
|
90
|
+
* in the first found dotenv file (e.g. .env, .env.local) or creates a new .env file.
|
|
91
|
+
*/
|
|
92
|
+
declare function updateEnvFile(secretName: string, cwd?: string): Promise<void>;
|
|
93
|
+
|
|
94
|
+
export { type ProjectKeyBundle, decryptSecret, encryptSecret, generateProjectKeys, initProject, loadEnvFiles, loadSecrets, load_secrets, parseDotEnv, retrieveSecret, signMessage, storeSecret, updateEnvFile, validateKeyBundle, version };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
var version = "0.0.1";
|
|
3
|
+
|
|
4
|
+
// src/crypto.ts
|
|
5
|
+
import { webcrypto } from "crypto";
|
|
6
|
+
var { subtle } = webcrypto;
|
|
7
|
+
function validateKeyBundle(data) {
|
|
8
|
+
if (!data || typeof data !== "object") {
|
|
9
|
+
throw new Error("Invalid credentials: expected a JSON object");
|
|
10
|
+
}
|
|
11
|
+
const obj = data;
|
|
12
|
+
if (typeof obj.projectId !== "string" || !obj.projectId) {
|
|
13
|
+
throw new Error('Invalid credentials: missing or invalid "projectId"');
|
|
14
|
+
}
|
|
15
|
+
validateJwkPair(obj.auth, "auth");
|
|
16
|
+
validateJwkPair(obj.encryption, "encryption");
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
function validateJwkPair(pair, name) {
|
|
20
|
+
if (!pair || typeof pair !== "object") {
|
|
21
|
+
throw new Error(`Invalid credentials: missing "${name}" key pair`);
|
|
22
|
+
}
|
|
23
|
+
const p = pair;
|
|
24
|
+
if (!p.privateJwk || typeof p.privateJwk !== "object") {
|
|
25
|
+
throw new Error(`Invalid credentials: missing "${name}.privateJwk"`);
|
|
26
|
+
}
|
|
27
|
+
if (!p.publicJwk || typeof p.publicJwk !== "object") {
|
|
28
|
+
throw new Error(`Invalid credentials: missing "${name}.publicJwk"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function generateProjectKeys(projectId) {
|
|
32
|
+
const authPair = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
33
|
+
"sign",
|
|
34
|
+
"verify"
|
|
35
|
+
]);
|
|
36
|
+
const encPair = await subtle.generateKey(
|
|
37
|
+
{
|
|
38
|
+
name: "RSA-OAEP",
|
|
39
|
+
modulusLength: 2048,
|
|
40
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
41
|
+
hash: "SHA-256"
|
|
42
|
+
},
|
|
43
|
+
true,
|
|
44
|
+
["encrypt", "decrypt"]
|
|
45
|
+
);
|
|
46
|
+
const authPrivateJwk = await subtle.exportKey("jwk", authPair.privateKey);
|
|
47
|
+
const authPublicJwk = await subtle.exportKey("jwk", authPair.publicKey);
|
|
48
|
+
delete authPrivateJwk.alg;
|
|
49
|
+
delete authPublicJwk.alg;
|
|
50
|
+
const encPrivateJwk = await subtle.exportKey("jwk", encPair.privateKey);
|
|
51
|
+
const encPublicJwk = await subtle.exportKey("jwk", encPair.publicKey);
|
|
52
|
+
delete encPrivateJwk.alg;
|
|
53
|
+
delete encPublicJwk.alg;
|
|
54
|
+
return {
|
|
55
|
+
projectId,
|
|
56
|
+
auth: {
|
|
57
|
+
privateJwk: authPrivateJwk,
|
|
58
|
+
publicJwk: authPublicJwk
|
|
59
|
+
},
|
|
60
|
+
encryption: {
|
|
61
|
+
privateJwk: encPrivateJwk,
|
|
62
|
+
publicJwk: encPublicJwk
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function bufToHex(buffer) {
|
|
67
|
+
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
68
|
+
}
|
|
69
|
+
var HYBRID_PREFIX = "ks1:";
|
|
70
|
+
var RSA_CIPHERTEXT_LENGTH = 256;
|
|
71
|
+
var AES_GCM_IV_LENGTH = 12;
|
|
72
|
+
async function encryptSecret(plainText, rsaPubJwk) {
|
|
73
|
+
const rsaPublicKey = await subtle.importKey(
|
|
74
|
+
"jwk",
|
|
75
|
+
rsaPubJwk,
|
|
76
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
77
|
+
false,
|
|
78
|
+
["encrypt"]
|
|
79
|
+
);
|
|
80
|
+
const aesKey = await subtle.generateKey(
|
|
81
|
+
{ name: "AES-GCM", length: 256 },
|
|
82
|
+
true,
|
|
83
|
+
["encrypt"]
|
|
84
|
+
);
|
|
85
|
+
const iv = webcrypto.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH));
|
|
86
|
+
const encoder = new TextEncoder();
|
|
87
|
+
const aesCiphertext = new Uint8Array(
|
|
88
|
+
await subtle.encrypt(
|
|
89
|
+
{ name: "AES-GCM", iv },
|
|
90
|
+
aesKey,
|
|
91
|
+
encoder.encode(plainText)
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
const rawAesKey = await subtle.exportKey("raw", aesKey);
|
|
95
|
+
const wrappedKey = new Uint8Array(
|
|
96
|
+
await subtle.encrypt({ name: "RSA-OAEP" }, rsaPublicKey, rawAesKey)
|
|
97
|
+
);
|
|
98
|
+
const packed = new Uint8Array(
|
|
99
|
+
wrappedKey.length + iv.length + aesCiphertext.length
|
|
100
|
+
);
|
|
101
|
+
packed.set(wrappedKey, 0);
|
|
102
|
+
packed.set(iv, wrappedKey.length);
|
|
103
|
+
packed.set(aesCiphertext, wrappedKey.length + iv.length);
|
|
104
|
+
return HYBRID_PREFIX + Buffer.from(packed).toString("base64");
|
|
105
|
+
}
|
|
106
|
+
async function decryptSecret(ciphertext, rsaPrivJwk) {
|
|
107
|
+
const rsaPrivateKey = await subtle.importKey(
|
|
108
|
+
"jwk",
|
|
109
|
+
rsaPrivJwk,
|
|
110
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
111
|
+
false,
|
|
112
|
+
["decrypt"]
|
|
113
|
+
);
|
|
114
|
+
if (ciphertext.startsWith(HYBRID_PREFIX)) {
|
|
115
|
+
const packed = Buffer.from(
|
|
116
|
+
ciphertext.slice(HYBRID_PREFIX.length),
|
|
117
|
+
"base64"
|
|
118
|
+
);
|
|
119
|
+
if (packed.length < RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH + 1) {
|
|
120
|
+
throw new Error("Invalid hybrid ciphertext: data too short");
|
|
121
|
+
}
|
|
122
|
+
const wrappedKey = packed.subarray(0, RSA_CIPHERTEXT_LENGTH);
|
|
123
|
+
const iv = packed.subarray(
|
|
124
|
+
RSA_CIPHERTEXT_LENGTH,
|
|
125
|
+
RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH
|
|
126
|
+
);
|
|
127
|
+
const aesCiphertext = packed.subarray(
|
|
128
|
+
RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH
|
|
129
|
+
);
|
|
130
|
+
const rawAesKey = await subtle.decrypt(
|
|
131
|
+
{ name: "RSA-OAEP" },
|
|
132
|
+
rsaPrivateKey,
|
|
133
|
+
wrappedKey
|
|
134
|
+
);
|
|
135
|
+
const aesKey = await subtle.importKey(
|
|
136
|
+
"raw",
|
|
137
|
+
rawAesKey,
|
|
138
|
+
{ name: "AES-GCM", length: 256 },
|
|
139
|
+
false,
|
|
140
|
+
["decrypt"]
|
|
141
|
+
);
|
|
142
|
+
const decryptedBuffer2 = await subtle.decrypt(
|
|
143
|
+
{ name: "AES-GCM", iv },
|
|
144
|
+
aesKey,
|
|
145
|
+
aesCiphertext
|
|
146
|
+
);
|
|
147
|
+
return new TextDecoder().decode(decryptedBuffer2);
|
|
148
|
+
}
|
|
149
|
+
const ciphertextBuffer = Buffer.from(ciphertext, "base64");
|
|
150
|
+
const decryptedBuffer = await subtle.decrypt(
|
|
151
|
+
{ name: "RSA-OAEP" },
|
|
152
|
+
rsaPrivateKey,
|
|
153
|
+
ciphertextBuffer
|
|
154
|
+
);
|
|
155
|
+
return new TextDecoder().decode(decryptedBuffer);
|
|
156
|
+
}
|
|
157
|
+
async function signMessage(messageStr, ed25519PrivJwk) {
|
|
158
|
+
const privateKey = await subtle.importKey(
|
|
159
|
+
"jwk",
|
|
160
|
+
ed25519PrivJwk,
|
|
161
|
+
{ name: "Ed25519" },
|
|
162
|
+
false,
|
|
163
|
+
["sign"]
|
|
164
|
+
);
|
|
165
|
+
const encoder = new TextEncoder();
|
|
166
|
+
const sigBuffer = await subtle.sign(
|
|
167
|
+
{ name: "Ed25519" },
|
|
168
|
+
privateKey,
|
|
169
|
+
encoder.encode(messageStr)
|
|
170
|
+
);
|
|
171
|
+
return bufToHex(sigBuffer);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/api.ts
|
|
175
|
+
async function initProject(projectId, keys, baseUrl) {
|
|
176
|
+
const response = await fetch(`${baseUrl}/init?projectId=${projectId}`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: { "Content-Type": "application/json" },
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
authPubJwk: keys.auth.publicJwk,
|
|
181
|
+
encPubJwk: keys.encryption.publicJwk
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
let errorMessage;
|
|
186
|
+
try {
|
|
187
|
+
const err = await response.json();
|
|
188
|
+
errorMessage = err.error || response.statusText;
|
|
189
|
+
} catch {
|
|
190
|
+
errorMessage = `${response.status} ${response.statusText}`;
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`Init Failed: ${errorMessage}`);
|
|
193
|
+
}
|
|
194
|
+
return await response.json();
|
|
195
|
+
}
|
|
196
|
+
async function storeSecret(projectId, secretName, plainText, keys, baseUrl) {
|
|
197
|
+
const ciphertext = await encryptSecret(plainText, keys.encryption.publicJwk);
|
|
198
|
+
const sigPayload = `store:${secretName}:${ciphertext}`;
|
|
199
|
+
const signature = await signMessage(sigPayload, keys.auth.privateJwk);
|
|
200
|
+
const response = await fetch(
|
|
201
|
+
`${baseUrl}/store?projectId=${projectId}&secretName=${secretName}`,
|
|
202
|
+
{
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: { "Content-Type": "application/json" },
|
|
205
|
+
body: JSON.stringify({ ciphertext, signature })
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
let errorMessage;
|
|
210
|
+
try {
|
|
211
|
+
const err = await response.json();
|
|
212
|
+
errorMessage = err.error || response.statusText;
|
|
213
|
+
} catch {
|
|
214
|
+
errorMessage = `${response.status} ${response.statusText}`;
|
|
215
|
+
}
|
|
216
|
+
throw new Error(`Store Failed: ${errorMessage}`);
|
|
217
|
+
}
|
|
218
|
+
return await response.json();
|
|
219
|
+
}
|
|
220
|
+
async function retrieveSecret(projectId, secretName, keys, baseUrl) {
|
|
221
|
+
const challenge = Date.now().toString();
|
|
222
|
+
const signature = await signMessage(challenge, keys.auth.privateJwk);
|
|
223
|
+
const response = await fetch(
|
|
224
|
+
`${baseUrl}/retrieve?projectId=${projectId}&secretName=${secretName}`,
|
|
225
|
+
{
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: { "Content-Type": "application/json" },
|
|
228
|
+
body: JSON.stringify({ challenge, signature })
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
let errorMessage;
|
|
233
|
+
try {
|
|
234
|
+
const err = await response.json();
|
|
235
|
+
errorMessage = err.error || response.statusText;
|
|
236
|
+
} catch {
|
|
237
|
+
errorMessage = `${response.status} ${response.statusText}`;
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Retrieve Failed: ${errorMessage}`);
|
|
240
|
+
}
|
|
241
|
+
const { ciphertext } = await response.json();
|
|
242
|
+
return await decryptSecret(ciphertext, keys.encryption.privateJwk);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/env.ts
|
|
246
|
+
import * as fs from "fs/promises";
|
|
247
|
+
import * as path from "path";
|
|
248
|
+
function parseDotEnv(content) {
|
|
249
|
+
const env = {};
|
|
250
|
+
const lines = content.split(/\r?\n/);
|
|
251
|
+
for (const line of lines) {
|
|
252
|
+
const trimmed = line.trim();
|
|
253
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const cleanLine = trimmed.startsWith("export ") ? trimmed.substring(7).trim() : trimmed;
|
|
257
|
+
const match = cleanLine.match(/^([^=]+)=(.*)$/);
|
|
258
|
+
if (!match) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const val1 = match[1];
|
|
262
|
+
const val2 = match[2];
|
|
263
|
+
if (val1 === void 0 || val2 === void 0) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const key = val1.trim();
|
|
267
|
+
let value = val2.trim();
|
|
268
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
269
|
+
value = value.substring(1, value.length - 1);
|
|
270
|
+
}
|
|
271
|
+
env[key] = value;
|
|
272
|
+
}
|
|
273
|
+
return env;
|
|
274
|
+
}
|
|
275
|
+
async function loadEnvFiles(cwd = process.cwd()) {
|
|
276
|
+
const originalEnv = { ...process.env };
|
|
277
|
+
const nodeEnv = process.env.NODE_ENV || "";
|
|
278
|
+
const filesToLoad = [".env"];
|
|
279
|
+
if (nodeEnv !== "test") {
|
|
280
|
+
filesToLoad.push(".env.local");
|
|
281
|
+
}
|
|
282
|
+
if (nodeEnv) {
|
|
283
|
+
filesToLoad.push(`.env.${nodeEnv}`);
|
|
284
|
+
filesToLoad.push(`.env.${nodeEnv}.local`);
|
|
285
|
+
}
|
|
286
|
+
const loadedEnv = {};
|
|
287
|
+
for (const file of filesToLoad) {
|
|
288
|
+
const filePath = path.join(cwd, file);
|
|
289
|
+
try {
|
|
290
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
291
|
+
const parsed = parseDotEnv(content);
|
|
292
|
+
for (const [key, val] of Object.entries(parsed)) {
|
|
293
|
+
loadedEnv[key] = val;
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const [key, val] of Object.entries(loadedEnv)) {
|
|
299
|
+
if (originalEnv[key] === void 0) {
|
|
300
|
+
process.env[key] = val;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function loadSecrets(options = {}) {
|
|
305
|
+
const cwd = options.cwd || process.cwd();
|
|
306
|
+
await loadEnvFiles(cwd);
|
|
307
|
+
const credPath = options.credentialsPath || process.env.KEYSTONE_CREDENTIALS || path.join(cwd, ".keystone", "project-credentials.json");
|
|
308
|
+
const baseUrl = options.baseUrl || process.env.KEYSTONE_URL || "https://keystone.chames.dev";
|
|
309
|
+
const placeholders = [];
|
|
310
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
311
|
+
if (value) {
|
|
312
|
+
const upperVal = value.trim().toUpperCase();
|
|
313
|
+
if (upperVal === "LOAD_FROM_KEYSTONE" || upperVal === "LOAD FROM KEYSTONE") {
|
|
314
|
+
placeholders.push(key);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (placeholders.length === 0) {
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
321
|
+
let keys;
|
|
322
|
+
try {
|
|
323
|
+
const credContent = await fs.readFile(credPath, "utf-8");
|
|
324
|
+
keys = validateKeyBundle(JSON.parse(credContent));
|
|
325
|
+
} catch (err) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Failed to read credentials from ${credPath}: ${err.message}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
const loadedSecrets = {};
|
|
331
|
+
for (const key of placeholders) {
|
|
332
|
+
try {
|
|
333
|
+
const decrypted = await retrieveSecret(
|
|
334
|
+
keys.projectId,
|
|
335
|
+
key,
|
|
336
|
+
keys,
|
|
337
|
+
baseUrl
|
|
338
|
+
);
|
|
339
|
+
process.env[key] = decrypted;
|
|
340
|
+
loadedSecrets[key] = decrypted;
|
|
341
|
+
} catch (err) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Failed to load secret '${key}' from Keystone: ${err.message}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return loadedSecrets;
|
|
348
|
+
}
|
|
349
|
+
var load_secrets = loadSecrets;
|
|
350
|
+
async function updateEnvFile(secretName, cwd = process.cwd()) {
|
|
351
|
+
const possibleFiles = [
|
|
352
|
+
".env",
|
|
353
|
+
".env.local",
|
|
354
|
+
".env.development.local",
|
|
355
|
+
".env.development",
|
|
356
|
+
".env.dev"
|
|
357
|
+
];
|
|
358
|
+
let targetFile = ".env";
|
|
359
|
+
let content = "";
|
|
360
|
+
let fileFound = false;
|
|
361
|
+
for (const file of possibleFiles) {
|
|
362
|
+
const filePath = path.join(cwd, file);
|
|
363
|
+
try {
|
|
364
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
365
|
+
targetFile = file;
|
|
366
|
+
fileFound = true;
|
|
367
|
+
break;
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const targetPath = path.join(cwd, targetFile);
|
|
372
|
+
const placeholder = "LOAD_FROM_KEYSTONE";
|
|
373
|
+
if (!fileFound) {
|
|
374
|
+
await fs.writeFile(targetPath, `${secretName}=${placeholder}
|
|
375
|
+
`, "utf-8");
|
|
376
|
+
console.error(
|
|
377
|
+
`Created ${targetFile} and added ${secretName}=${placeholder}`
|
|
378
|
+
);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const lines = content.split(/\r?\n/);
|
|
382
|
+
let keyIndex = -1;
|
|
383
|
+
const keyRegex = new RegExp(`^(?:export\\s+)?${secretName}\\s*=`);
|
|
384
|
+
for (let i = 0; i < lines.length; i++) {
|
|
385
|
+
const line = lines[i];
|
|
386
|
+
if (line !== void 0 && keyRegex.test(line)) {
|
|
387
|
+
keyIndex = i;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (keyIndex !== -1) {
|
|
392
|
+
const line = lines[keyIndex];
|
|
393
|
+
if (line !== void 0) {
|
|
394
|
+
const parts = line.split("=");
|
|
395
|
+
const currentValue = parts.slice(1).join("=").trim();
|
|
396
|
+
const cleanValue = currentValue.startsWith('"') && currentValue.endsWith('"') || currentValue.startsWith("'") && currentValue.endsWith("'") ? currentValue.substring(1, currentValue.length - 1) : currentValue;
|
|
397
|
+
if (cleanValue.toUpperCase() !== placeholder) {
|
|
398
|
+
lines[keyIndex] = `${secretName}=${placeholder}`;
|
|
399
|
+
await fs.writeFile(targetPath, lines.join("\n"), "utf-8");
|
|
400
|
+
console.error(
|
|
401
|
+
`Updated ${secretName} to ${placeholder} in ${targetFile}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
const separator = content.endsWith("\n") || content === "" ? "" : "\n";
|
|
407
|
+
await fs.writeFile(
|
|
408
|
+
targetPath,
|
|
409
|
+
`${content}${separator}${secretName}=${placeholder}
|
|
410
|
+
`,
|
|
411
|
+
"utf-8"
|
|
412
|
+
);
|
|
413
|
+
console.error(`Added ${secretName}=${placeholder} to ${targetFile}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
export {
|
|
417
|
+
decryptSecret,
|
|
418
|
+
encryptSecret,
|
|
419
|
+
generateProjectKeys,
|
|
420
|
+
initProject,
|
|
421
|
+
loadEnvFiles,
|
|
422
|
+
loadSecrets,
|
|
423
|
+
load_secrets,
|
|
424
|
+
parseDotEnv,
|
|
425
|
+
retrieveSecret,
|
|
426
|
+
signMessage,
|
|
427
|
+
storeSecret,
|
|
428
|
+
updateEnvFile,
|
|
429
|
+
validateKeyBundle,
|
|
430
|
+
version
|
|
431
|
+
};
|
|
432
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/crypto.ts","../src/api.ts","../src/env.ts"],"sourcesContent":["/** Package version — kept in sync with package.json */\nexport const version = \"0.0.1\";\n","import { webcrypto } from \"crypto\";\nconst { subtle } = webcrypto;\n\n/**\n * Interface representing the key bundle structure.\n */\nexport interface ProjectKeyBundle {\n projectId: string;\n auth: {\n privateJwk: JsonWebKey;\n publicJwk: JsonWebKey;\n };\n encryption: {\n privateJwk: JsonWebKey;\n publicJwk: JsonWebKey;\n };\n}\n\n/**\n * Validates that a parsed object conforms to the ProjectKeyBundle schema.\n * Throws a descriptive error if the shape is invalid.\n */\nexport function validateKeyBundle(data: unknown): ProjectKeyBundle {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"Invalid credentials: expected a JSON object\");\n }\n\n const obj = data as Record<string, unknown>;\n\n if (typeof obj.projectId !== \"string\" || !obj.projectId) {\n throw new Error('Invalid credentials: missing or invalid \"projectId\"');\n }\n\n validateJwkPair(obj.auth, \"auth\");\n validateJwkPair(obj.encryption, \"encryption\");\n\n return data as ProjectKeyBundle;\n}\n\nfunction validateJwkPair(pair: unknown, name: string): void {\n if (!pair || typeof pair !== \"object\") {\n throw new Error(`Invalid credentials: missing \"${name}\" key pair`);\n }\n const p = pair as Record<string, unknown>;\n if (!p.privateJwk || typeof p.privateJwk !== \"object\") {\n throw new Error(`Invalid credentials: missing \"${name}.privateJwk\"`);\n }\n if (!p.publicJwk || typeof p.publicJwk !== \"object\") {\n throw new Error(`Invalid credentials: missing \"${name}.publicJwk\"`);\n }\n}\n\n/**\n * Generates an Ed25519 key pair for authentication and an RSA-OAEP key pair for encryption.\n * Both are returned as JWK (JSON Web Key) objects.\n */\nexport async function generateProjectKeys(\n projectId: string,\n): Promise<ProjectKeyBundle> {\n // 1. Generate Auth Pair (Ed25519)\n const authPair = (await subtle.generateKey({ name: \"Ed25519\" }, true, [\n \"sign\",\n \"verify\",\n ])) as CryptoKeyPair;\n\n // 2. Generate Encryption Pair (RSA-OAEP 2048)\n const encPair = (await subtle.generateKey(\n {\n name: \"RSA-OAEP\",\n modulusLength: 2048,\n publicExponent: new Uint8Array([1, 0, 1]),\n hash: \"SHA-256\",\n },\n true,\n [\"encrypt\", \"decrypt\"],\n )) as CryptoKeyPair;\n\n const authPrivateJwk = await subtle.exportKey(\"jwk\", authPair.privateKey);\n const authPublicJwk = await subtle.exportKey(\"jwk\", authPair.publicKey);\n delete authPrivateJwk.alg;\n delete authPublicJwk.alg;\n\n const encPrivateJwk = await subtle.exportKey(\"jwk\", encPair.privateKey);\n const encPublicJwk = await subtle.exportKey(\"jwk\", encPair.publicKey);\n delete encPrivateJwk.alg;\n delete encPublicJwk.alg;\n\n return {\n projectId,\n auth: {\n privateJwk: authPrivateJwk,\n publicJwk: authPublicJwk,\n },\n encryption: {\n privateJwk: encPrivateJwk,\n publicJwk: encPublicJwk,\n },\n };\n}\n\n/**\n * Convert array buffer to Hex string\n */\nfunction bufToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/** Hybrid ciphertext format prefix — enables backward-compatible detection */\nconst HYBRID_PREFIX = \"ks1:\";\n\n/** RSA-2048 ciphertext is always 256 bytes */\nconst RSA_CIPHERTEXT_LENGTH = 256;\n\n/** AES-GCM uses a 12-byte IV (recommended by NIST) */\nconst AES_GCM_IV_LENGTH = 12;\n\n/**\n * Encrypt plaintext using hybrid encryption (AES-256-GCM + RSA-OAEP envelope).\n *\n * 1. Generates a random 256-bit AES-GCM key\n * 2. Encrypts the plaintext with AES-256-GCM (no size limit)\n * 3. Wraps the AES key with RSA-OAEP (only 32 bytes — well within the 190-byte limit)\n * 4. Returns `ks1:<base64(wrappedKey ‖ iv ‖ aesCiphertext)>`\n */\nexport async function encryptSecret(\n plainText: string,\n rsaPubJwk: JsonWebKey,\n): Promise<string> {\n // Import RSA public key for key wrapping\n const rsaPublicKey = await subtle.importKey(\n \"jwk\",\n rsaPubJwk,\n { name: \"RSA-OAEP\", hash: \"SHA-256\" },\n false,\n [\"encrypt\"],\n );\n\n // Generate a one-time AES-256-GCM key\n const aesKey = await subtle.generateKey(\n { name: \"AES-GCM\", length: 256 },\n true,\n [\"encrypt\"],\n );\n\n // Generate a random 12-byte IV\n const iv = webcrypto.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH));\n\n // Encrypt plaintext with AES-256-GCM\n const encoder = new TextEncoder();\n const aesCiphertext = new Uint8Array(\n await subtle.encrypt(\n { name: \"AES-GCM\", iv },\n aesKey,\n encoder.encode(plainText),\n ),\n );\n\n // Export AES key as raw 32 bytes and wrap it with RSA-OAEP\n const rawAesKey = await subtle.exportKey(\"raw\", aesKey);\n const wrappedKey = new Uint8Array(\n await subtle.encrypt({ name: \"RSA-OAEP\" }, rsaPublicKey, rawAesKey),\n );\n\n // Pack: wrappedKey (256 bytes) ‖ iv (12 bytes) ‖ aesCiphertext (variable)\n const packed = new Uint8Array(\n wrappedKey.length + iv.length + aesCiphertext.length,\n );\n packed.set(wrappedKey, 0);\n packed.set(iv, wrappedKey.length);\n packed.set(aesCiphertext, wrappedKey.length + iv.length);\n\n return HYBRID_PREFIX + Buffer.from(packed).toString(\"base64\");\n}\n\n/**\n * Decrypt ciphertext using hybrid decryption (AES-256-GCM + RSA-OAEP envelope).\n * Supports both the new hybrid format (`ks1:` prefix) and legacy RSA-only format\n * for backward compatibility with previously stored secrets.\n */\nexport async function decryptSecret(\n ciphertext: string,\n rsaPrivJwk: JsonWebKey,\n): Promise<string> {\n const rsaPrivateKey = await subtle.importKey(\n \"jwk\",\n rsaPrivJwk,\n { name: \"RSA-OAEP\", hash: \"SHA-256\" },\n false,\n [\"decrypt\"],\n );\n\n // Hybrid format: ks1:<base64(wrappedKey ‖ iv ‖ aesCiphertext)>\n if (ciphertext.startsWith(HYBRID_PREFIX)) {\n const packed = Buffer.from(\n ciphertext.slice(HYBRID_PREFIX.length),\n \"base64\",\n );\n\n if (packed.length < RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH + 1) {\n throw new Error(\"Invalid hybrid ciphertext: data too short\");\n }\n\n const wrappedKey = packed.subarray(0, RSA_CIPHERTEXT_LENGTH);\n const iv = packed.subarray(\n RSA_CIPHERTEXT_LENGTH,\n RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH,\n );\n const aesCiphertext = packed.subarray(\n RSA_CIPHERTEXT_LENGTH + AES_GCM_IV_LENGTH,\n );\n\n // Unwrap AES key with RSA-OAEP\n const rawAesKey = await subtle.decrypt(\n { name: \"RSA-OAEP\" },\n rsaPrivateKey,\n wrappedKey,\n );\n\n // Import AES key\n const aesKey = await subtle.importKey(\n \"raw\",\n rawAesKey,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"decrypt\"],\n );\n\n // Decrypt with AES-GCM\n const decryptedBuffer = await subtle.decrypt(\n { name: \"AES-GCM\", iv },\n aesKey,\n aesCiphertext,\n );\n\n return new TextDecoder().decode(decryptedBuffer);\n }\n\n // Legacy format: plain base64-encoded RSA ciphertext\n const ciphertextBuffer = Buffer.from(ciphertext, \"base64\");\n const decryptedBuffer = await subtle.decrypt(\n { name: \"RSA-OAEP\" },\n rsaPrivateKey,\n ciphertextBuffer,\n );\n\n return new TextDecoder().decode(decryptedBuffer);\n}\n\n/**\n * Sign payload using Ed25519 private key\n */\nexport async function signMessage(\n messageStr: string,\n ed25519PrivJwk: JsonWebKey,\n): Promise<string> {\n const privateKey = await subtle.importKey(\n \"jwk\",\n ed25519PrivJwk,\n { name: \"Ed25519\" },\n false,\n [\"sign\"],\n );\n\n const encoder = new TextEncoder();\n const sigBuffer = await subtle.sign(\n { name: \"Ed25519\" },\n privateKey,\n encoder.encode(messageStr),\n );\n\n return bufToHex(sigBuffer);\n}\n","import {\n encryptSecret,\n signMessage,\n decryptSecret,\n ProjectKeyBundle,\n} from \"./crypto.js\";\n\n/**\n * Sends the public verification and public encryption keys to create a new project namespace on the server.\n */\nexport async function initProject(\n projectId: string,\n keys: ProjectKeyBundle,\n baseUrl: string,\n): Promise<{ message: string }> {\n const response = await fetch(`${baseUrl}/init?projectId=${projectId}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n authPubJwk: keys.auth.publicJwk,\n encPubJwk: keys.encryption.publicJwk,\n }),\n });\n\n if (!response.ok) {\n let errorMessage: string;\n try {\n const err = await response.json();\n errorMessage = err.error || response.statusText;\n } catch {\n errorMessage = `${response.status} ${response.statusText}`;\n }\n throw new Error(`Init Failed: ${errorMessage}`);\n }\n return await response.json();\n}\n\n/**\n * Encrypts the secret locally, signs the metadata package, and uploads it to the backend.\n */\nexport async function storeSecret(\n projectId: string,\n secretName: string,\n plainText: string,\n keys: ProjectKeyBundle,\n baseUrl: string,\n): Promise<{ message: string }> {\n const ciphertext = await encryptSecret(plainText, keys.encryption.publicJwk);\n const sigPayload = `store:${secretName}:${ciphertext}`;\n const signature = await signMessage(sigPayload, keys.auth.privateJwk);\n\n const response = await fetch(\n `${baseUrl}/store?projectId=${projectId}&secretName=${secretName}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ ciphertext, signature }),\n },\n );\n\n if (!response.ok) {\n let errorMessage: string;\n try {\n const err = await response.json();\n errorMessage = err.error || response.statusText;\n } catch {\n errorMessage = `${response.status} ${response.statusText}`;\n }\n throw new Error(`Store Failed: ${errorMessage}`);\n }\n return await response.json();\n}\n\n/**\n * Verifies identity with a signed timestamp, retrieves, and decrypts the secret.\n */\nexport async function retrieveSecret(\n projectId: string,\n secretName: string,\n keys: ProjectKeyBundle,\n baseUrl: string,\n): Promise<string> {\n const challenge = Date.now().toString();\n const signature = await signMessage(challenge, keys.auth.privateJwk);\n\n const response = await fetch(\n `${baseUrl}/retrieve?projectId=${projectId}&secretName=${secretName}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ challenge, signature }),\n },\n );\n\n if (!response.ok) {\n let errorMessage: string;\n try {\n const err = await response.json();\n errorMessage = err.error || response.statusText;\n } catch {\n errorMessage = `${response.status} ${response.statusText}`;\n }\n throw new Error(`Retrieve Failed: ${errorMessage}`);\n }\n\n const { ciphertext } = await response.json();\n return await decryptSecret(ciphertext, keys.encryption.privateJwk);\n}\n","import * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { retrieveSecret } from \"./api.js\";\nimport { ProjectKeyBundle, validateKeyBundle } from \"./crypto.js\";\n\n/**\n * Parses a dotenv file content into key-value pairs.\n */\nexport function parseDotEnv(content: string): Record<string, string> {\n const env: Record<string, string> = {};\n const lines = content.split(/\\r?\\n/);\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n // Handle 'export ' prefix\n const cleanLine = trimmed.startsWith(\"export \")\n ? trimmed.substring(7).trim()\n : trimmed;\n const match = cleanLine.match(/^([^=]+)=(.*)$/);\n if (!match) {\n continue;\n }\n const val1 = match[1];\n const val2 = match[2];\n if (val1 === undefined || val2 === undefined) {\n continue;\n }\n const key = val1.trim();\n let value = val2.trim();\n // Strip surrounding quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.substring(1, value.length - 1);\n }\n env[key] = value;\n }\n return env;\n}\n\n/**\n * Finds and loads .env and .env.local files in the current working directory,\n * loading variables into process.env if they are not already set.\n */\nexport async function loadEnvFiles(cwd = process.cwd()): Promise<void> {\n const originalEnv = { ...process.env };\n const nodeEnv = process.env.NODE_ENV || \"\";\n\n // Define standard env files in order of increasing priority:\n // .env -> .env.local -> .env.${NODE_ENV} -> .env.${NODE_ENV}.local\n const filesToLoad: string[] = [\".env\"];\n if (nodeEnv !== \"test\") {\n filesToLoad.push(\".env.local\");\n }\n if (nodeEnv) {\n filesToLoad.push(`.env.${nodeEnv}`);\n filesToLoad.push(`.env.${nodeEnv}.local`);\n }\n\n const loadedEnv: Record<string, string> = {};\n\n for (const file of filesToLoad) {\n const filePath = path.join(cwd, file);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const parsed = parseDotEnv(content);\n for (const [key, val] of Object.entries(parsed)) {\n loadedEnv[key] = val; // later files override earlier ones\n }\n } catch {\n // Ignore if file does not exist\n }\n }\n\n // Set in process.env only if not set in original env\n for (const [key, val] of Object.entries(loadedEnv)) {\n if (originalEnv[key] === undefined) {\n process.env[key] = val;\n }\n }\n}\n\nexport interface LoadSecretsOptions {\n credentialsPath?: string;\n baseUrl?: string;\n cwd?: string;\n}\n\n/**\n * Loads .env files, reads credentials, and fetches secrets from the Keystone vault\n * for any environment variables with value 'LOAD_FROM_KEYSTONE' or 'LOAD FROM KEYSTONE'.\n */\nexport async function loadSecrets(\n options: LoadSecretsOptions = {},\n): Promise<Record<string, string>> {\n const cwd = options.cwd || process.cwd();\n\n // Load standard .env files first\n await loadEnvFiles(cwd);\n\n // Locate credentials path\n const credPath =\n options.credentialsPath ||\n process.env.KEYSTONE_CREDENTIALS ||\n path.join(cwd, \".keystone\", \"project-credentials.json\");\n\n // Locate base URL\n const baseUrl =\n options.baseUrl ||\n process.env.KEYSTONE_URL ||\n \"https://keystone.chames.dev\";\n\n // Scan process.env for placeholders\n const placeholders: string[] = [];\n for (const [key, value] of Object.entries(process.env)) {\n if (value) {\n const upperVal = value.trim().toUpperCase();\n if (\n upperVal === \"LOAD_FROM_KEYSTONE\" ||\n upperVal === \"LOAD FROM KEYSTONE\"\n ) {\n placeholders.push(key);\n }\n }\n }\n\n // If no placeholders found, we're done\n if (placeholders.length === 0) {\n return {};\n }\n\n // Read credentials file\n let keys: ProjectKeyBundle;\n try {\n const credContent = await fs.readFile(credPath, \"utf-8\");\n keys = validateKeyBundle(JSON.parse(credContent));\n } catch (err: any) {\n throw new Error(\n `Failed to read credentials from ${credPath}: ${err.message}`,\n );\n }\n\n const loadedSecrets: Record<string, string> = {};\n\n // Fetch each secret and inject into process.env\n for (const key of placeholders) {\n try {\n const decrypted = await retrieveSecret(\n keys.projectId,\n key,\n keys,\n baseUrl,\n );\n process.env[key] = decrypted;\n loadedSecrets[key] = decrypted;\n } catch (err: any) {\n throw new Error(\n `Failed to load secret '${key}' from Keystone: ${err.message}`,\n );\n }\n }\n\n return loadedSecrets;\n}\n\n/**\n * Snake_case alias for loadSecrets\n */\nexport const load_secrets = loadSecrets;\n\n/**\n * Appends or updates an environment variable key with 'LOAD_FROM_KEYSTONE' placeholder\n * in the first found dotenv file (e.g. .env, .env.local) or creates a new .env file.\n */\nexport async function updateEnvFile(\n secretName: string,\n cwd = process.cwd(),\n): Promise<void> {\n const possibleFiles = [\n \".env\",\n \".env.local\",\n \".env.development.local\",\n \".env.development\",\n \".env.dev\",\n ];\n\n let targetFile = \".env\";\n let content = \"\";\n let fileFound = false;\n\n for (const file of possibleFiles) {\n const filePath = path.join(cwd, file);\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n targetFile = file;\n fileFound = true;\n break;\n } catch {\n // ignore, try next file\n }\n }\n\n const targetPath = path.join(cwd, targetFile);\n const placeholder = \"LOAD_FROM_KEYSTONE\";\n\n if (!fileFound) {\n await fs.writeFile(targetPath, `${secretName}=${placeholder}\\n`, \"utf-8\");\n console.error(\n `Created ${targetFile} and added ${secretName}=${placeholder}`,\n );\n return;\n }\n\n const lines = content.split(/\\r?\\n/);\n let keyIndex = -1;\n const keyRegex = new RegExp(`^(?:export\\\\s+)?${secretName}\\\\s*=`);\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line !== undefined && keyRegex.test(line)) {\n keyIndex = i;\n break;\n }\n }\n\n if (keyIndex !== -1) {\n const line = lines[keyIndex];\n if (line !== undefined) {\n const parts = line.split(\"=\");\n const currentValue = parts.slice(1).join(\"=\").trim();\n const cleanValue =\n (currentValue.startsWith('\"') && currentValue.endsWith('\"')) ||\n (currentValue.startsWith(\"'\") && currentValue.endsWith(\"'\"))\n ? currentValue.substring(1, currentValue.length - 1)\n : currentValue;\n\n if (cleanValue.toUpperCase() !== placeholder) {\n lines[keyIndex] = `${secretName}=${placeholder}`;\n await fs.writeFile(targetPath, lines.join(\"\\n\"), \"utf-8\");\n console.error(\n `Updated ${secretName} to ${placeholder} in ${targetFile}`,\n );\n }\n }\n } else {\n const separator = content.endsWith(\"\\n\") || content === \"\" ? \"\" : \"\\n\";\n await fs.writeFile(\n targetPath,\n `${content}${separator}${secretName}=${placeholder}\\n`,\n \"utf-8\",\n );\n console.error(`Added ${secretName}=${placeholder} to ${targetFile}`);\n }\n}\n"],"mappings":";AACO,IAAM,UAAU;;;ACDvB,SAAS,iBAAiB;AAC1B,IAAM,EAAE,OAAO,IAAI;AAqBZ,SAAS,kBAAkB,MAAiC;AACjE,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,QAAM,MAAM;AAEZ,MAAI,OAAO,IAAI,cAAc,YAAY,CAAC,IAAI,WAAW;AACvD,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,kBAAgB,IAAI,MAAM,MAAM;AAChC,kBAAgB,IAAI,YAAY,YAAY;AAE5C,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAe,MAAoB;AAC1D,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,MAAM,iCAAiC,IAAI,YAAY;AAAA,EACnE;AACA,QAAM,IAAI;AACV,MAAI,CAAC,EAAE,cAAc,OAAO,EAAE,eAAe,UAAU;AACrD,UAAM,IAAI,MAAM,iCAAiC,IAAI,cAAc;AAAA,EACrE;AACA,MAAI,CAAC,EAAE,aAAa,OAAO,EAAE,cAAc,UAAU;AACnD,UAAM,IAAI,MAAM,iCAAiC,IAAI,aAAa;AAAA,EACpE;AACF;AAMA,eAAsB,oBACpB,WAC2B;AAE3B,QAAM,WAAY,MAAM,OAAO,YAAY,EAAE,MAAM,UAAU,GAAG,MAAM;AAAA,IACpE;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,UAAW,MAAM,OAAO;AAAA,IAC5B;AAAA,MACE,MAAM;AAAA,MACN,eAAe;AAAA,MACf,gBAAgB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC;AAAA,MACxC,MAAM;AAAA,IACR;AAAA,IACA;AAAA,IACA,CAAC,WAAW,SAAS;AAAA,EACvB;AAEA,QAAM,iBAAiB,MAAM,OAAO,UAAU,OAAO,SAAS,UAAU;AACxE,QAAM,gBAAgB,MAAM,OAAO,UAAU,OAAO,SAAS,SAAS;AACtE,SAAO,eAAe;AACtB,SAAO,cAAc;AAErB,QAAM,gBAAgB,MAAM,OAAO,UAAU,OAAO,QAAQ,UAAU;AACtE,QAAM,eAAe,MAAM,OAAO,UAAU,OAAO,QAAQ,SAAS;AACpE,SAAO,cAAc;AACrB,SAAO,aAAa;AAEpB,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,MACJ,YAAY;AAAA,MACZ,WAAW;AAAA,IACb;AAAA,IACA,YAAY;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA,IACb;AAAA,EACF;AACF;AAKA,SAAS,SAAS,QAA6B;AAC7C,SAAO,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC,EACrC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACZ;AAGA,IAAM,gBAAgB;AAGtB,IAAM,wBAAwB;AAG9B,IAAM,oBAAoB;AAU1B,eAAsB,cACpB,WACA,WACiB;AAEjB,QAAM,eAAe,MAAM,OAAO;AAAA,IAChC;AAAA,IACA;AAAA,IACA,EAAE,MAAM,YAAY,MAAM,UAAU;AAAA,IACpC;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAGA,QAAM,SAAS,MAAM,OAAO;AAAA,IAC1B,EAAE,MAAM,WAAW,QAAQ,IAAI;AAAA,IAC/B;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAGA,QAAM,KAAK,UAAU,gBAAgB,IAAI,WAAW,iBAAiB,CAAC;AAGtE,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,gBAAgB,IAAI;AAAA,IACxB,MAAM,OAAO;AAAA,MACX,EAAE,MAAM,WAAW,GAAG;AAAA,MACtB;AAAA,MACA,QAAQ,OAAO,SAAS;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,YAAY,MAAM,OAAO,UAAU,OAAO,MAAM;AACtD,QAAM,aAAa,IAAI;AAAA,IACrB,MAAM,OAAO,QAAQ,EAAE,MAAM,WAAW,GAAG,cAAc,SAAS;AAAA,EACpE;AAGA,QAAM,SAAS,IAAI;AAAA,IACjB,WAAW,SAAS,GAAG,SAAS,cAAc;AAAA,EAChD;AACA,SAAO,IAAI,YAAY,CAAC;AACxB,SAAO,IAAI,IAAI,WAAW,MAAM;AAChC,SAAO,IAAI,eAAe,WAAW,SAAS,GAAG,MAAM;AAEvD,SAAO,gBAAgB,OAAO,KAAK,MAAM,EAAE,SAAS,QAAQ;AAC9D;AAOA,eAAsB,cACpB,YACA,YACiB;AACjB,QAAM,gBAAgB,MAAM,OAAO;AAAA,IACjC;AAAA,IACA;AAAA,IACA,EAAE,MAAM,YAAY,MAAM,UAAU;AAAA,IACpC;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAGA,MAAI,WAAW,WAAW,aAAa,GAAG;AACxC,UAAM,SAAS,OAAO;AAAA,MACpB,WAAW,MAAM,cAAc,MAAM;AAAA,MACrC;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,wBAAwB,oBAAoB,GAAG;AACjE,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AAEA,UAAM,aAAa,OAAO,SAAS,GAAG,qBAAqB;AAC3D,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,wBAAwB;AAAA,IAC1B;AACA,UAAM,gBAAgB,OAAO;AAAA,MAC3B,wBAAwB;AAAA,IAC1B;AAGA,UAAM,YAAY,MAAM,OAAO;AAAA,MAC7B,EAAE,MAAM,WAAW;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,EAAE,MAAM,WAAW,QAAQ,IAAI;AAAA,MAC/B;AAAA,MACA,CAAC,SAAS;AAAA,IACZ;AAGA,UAAMA,mBAAkB,MAAM,OAAO;AAAA,MACnC,EAAE,MAAM,WAAW,GAAG;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AAEA,WAAO,IAAI,YAAY,EAAE,OAAOA,gBAAe;AAAA,EACjD;AAGA,QAAM,mBAAmB,OAAO,KAAK,YAAY,QAAQ;AACzD,QAAM,kBAAkB,MAAM,OAAO;AAAA,IACnC,EAAE,MAAM,WAAW;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AAEA,SAAO,IAAI,YAAY,EAAE,OAAO,eAAe;AACjD;AAKA,eAAsB,YACpB,YACA,gBACiB;AACjB,QAAM,aAAa,MAAM,OAAO;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,EAAE,MAAM,UAAU;AAAA,IAClB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,YAAY,MAAM,OAAO;AAAA,IAC7B,EAAE,MAAM,UAAU;AAAA,IAClB;AAAA,IACA,QAAQ,OAAO,UAAU;AAAA,EAC3B;AAEA,SAAO,SAAS,SAAS;AAC3B;;;ACvQA,eAAsB,YACpB,WACA,MACA,SAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,mBAAmB,SAAS,IAAI;AAAA,IACrE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,YAAY,KAAK,KAAK;AAAA,MACtB,WAAW,KAAK,WAAW;AAAA,IAC7B,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,qBAAe,IAAI,SAAS,SAAS;AAAA,IACvC,QAAQ;AACN,qBAAe,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC1D;AACA,UAAM,IAAI,MAAM,gBAAgB,YAAY,EAAE;AAAA,EAChD;AACA,SAAO,MAAM,SAAS,KAAK;AAC7B;AAKA,eAAsB,YACpB,WACA,YACA,WACA,MACA,SAC8B;AAC9B,QAAM,aAAa,MAAM,cAAc,WAAW,KAAK,WAAW,SAAS;AAC3E,QAAM,aAAa,SAAS,UAAU,IAAI,UAAU;AACpD,QAAM,YAAY,MAAM,YAAY,YAAY,KAAK,KAAK,UAAU;AAEpE,QAAM,WAAW,MAAM;AAAA,IACrB,GAAG,OAAO,oBAAoB,SAAS,eAAe,UAAU;AAAA,IAChE;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,UAAU,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,qBAAe,IAAI,SAAS,SAAS;AAAA,IACvC,QAAQ;AACN,qBAAe,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC1D;AACA,UAAM,IAAI,MAAM,iBAAiB,YAAY,EAAE;AAAA,EACjD;AACA,SAAO,MAAM,SAAS,KAAK;AAC7B;AAKA,eAAsB,eACpB,WACA,YACA,MACA,SACiB;AACjB,QAAM,YAAY,KAAK,IAAI,EAAE,SAAS;AACtC,QAAM,YAAY,MAAM,YAAY,WAAW,KAAK,KAAK,UAAU;AAEnE,QAAM,WAAW,MAAM;AAAA,IACrB,GAAG,OAAO,uBAAuB,SAAS,eAAe,UAAU;AAAA,IACnE;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,WAAW,UAAU,CAAC;AAAA,IAC/C;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,qBAAe,IAAI,SAAS,SAAS;AAAA,IACvC,QAAQ;AACN,qBAAe,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC1D;AACA,UAAM,IAAI,MAAM,oBAAoB,YAAY,EAAE;AAAA,EACpD;AAEA,QAAM,EAAE,WAAW,IAAI,MAAM,SAAS,KAAK;AAC3C,SAAO,MAAM,cAAc,YAAY,KAAK,WAAW,UAAU;AACnE;;;AC3GA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAOf,SAAS,YAAY,SAAyC;AACnE,QAAM,MAA8B,CAAC;AACrC,QAAM,QAAQ,QAAQ,MAAM,OAAO;AACnC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,GAAG;AACvC;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ,WAAW,SAAS,IAC1C,QAAQ,UAAU,CAAC,EAAE,KAAK,IAC1B;AACJ,UAAM,QAAQ,UAAU,MAAM,gBAAgB;AAC9C,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AACA,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,UAAa,SAAS,QAAW;AAC5C;AAAA,IACF;AACA,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,QAAQ,KAAK,KAAK;AAEtB,QACG,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC5C;AACA,cAAQ,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC;AAAA,IAC7C;AACA,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAMA,eAAsB,aAAa,MAAM,QAAQ,IAAI,GAAkB;AACrE,QAAM,cAAc,EAAE,GAAG,QAAQ,IAAI;AACrC,QAAM,UAAU,QAAQ,IAAI,YAAY;AAIxC,QAAM,cAAwB,CAAC,MAAM;AACrC,MAAI,YAAY,QAAQ;AACtB,gBAAY,KAAK,YAAY;AAAA,EAC/B;AACA,MAAI,SAAS;AACX,gBAAY,KAAK,QAAQ,OAAO,EAAE;AAClC,gBAAY,KAAK,QAAQ,OAAO,QAAQ;AAAA,EAC1C;AAEA,QAAM,YAAoC,CAAC;AAE3C,aAAW,QAAQ,aAAa;AAC9B,UAAM,WAAgB,UAAK,KAAK,IAAI;AACpC,QAAI;AACF,YAAM,UAAU,MAAS,YAAS,UAAU,OAAO;AACnD,YAAM,SAAS,YAAY,OAAO;AAClC,iBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,kBAAU,GAAG,IAAI;AAAA,MACnB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClD,QAAI,YAAY,GAAG,MAAM,QAAW;AAClC,cAAQ,IAAI,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAYA,eAAsB,YACpB,UAA8B,CAAC,GACE;AACjC,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AAGvC,QAAM,aAAa,GAAG;AAGtB,QAAM,WACJ,QAAQ,mBACR,QAAQ,IAAI,wBACP,UAAK,KAAK,aAAa,0BAA0B;AAGxD,QAAM,UACJ,QAAQ,WACR,QAAQ,IAAI,gBACZ;AAGF,QAAM,eAAyB,CAAC;AAChC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AACtD,QAAI,OAAO;AACT,YAAM,WAAW,MAAM,KAAK,EAAE,YAAY;AAC1C,UACE,aAAa,wBACb,aAAa,sBACb;AACA,qBAAa,KAAK,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO,CAAC;AAAA,EACV;AAGA,MAAI;AACJ,MAAI;AACF,UAAM,cAAc,MAAS,YAAS,UAAU,OAAO;AACvD,WAAO,kBAAkB,KAAK,MAAM,WAAW,CAAC;AAAA,EAClD,SAAS,KAAU;AACjB,UAAM,IAAI;AAAA,MACR,mCAAmC,QAAQ,KAAK,IAAI,OAAO;AAAA,IAC7D;AAAA,EACF;AAEA,QAAM,gBAAwC,CAAC;AAG/C,aAAW,OAAO,cAAc;AAC9B,QAAI;AACF,YAAM,YAAY,MAAM;AAAA,QACtB,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,cAAQ,IAAI,GAAG,IAAI;AACnB,oBAAc,GAAG,IAAI;AAAA,IACvB,SAAS,KAAU;AACjB,YAAM,IAAI;AAAA,QACR,0BAA0B,GAAG,oBAAoB,IAAI,OAAO;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,IAAM,eAAe;AAM5B,eAAsB,cACpB,YACA,MAAM,QAAQ,IAAI,GACH;AACf,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,aAAa;AACjB,MAAI,UAAU;AACd,MAAI,YAAY;AAEhB,aAAW,QAAQ,eAAe;AAChC,UAAM,WAAgB,UAAK,KAAK,IAAI;AACpC,QAAI;AACF,gBAAU,MAAS,YAAS,UAAU,OAAO;AAC7C,mBAAa;AACb,kBAAY;AACZ;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,aAAkB,UAAK,KAAK,UAAU;AAC5C,QAAM,cAAc;AAEpB,MAAI,CAAC,WAAW;AACd,UAAS,aAAU,YAAY,GAAG,UAAU,IAAI,WAAW;AAAA,GAAM,OAAO;AACxE,YAAQ;AAAA,MACN,WAAW,UAAU,cAAc,UAAU,IAAI,WAAW;AAAA,IAC9D;AACA;AAAA,EACF;AAEA,QAAM,QAAQ,QAAQ,MAAM,OAAO;AACnC,MAAI,WAAW;AACf,QAAM,WAAW,IAAI,OAAO,mBAAmB,UAAU,OAAO;AAEhE,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,UAAa,SAAS,KAAK,IAAI,GAAG;AAC7C,iBAAW;AACX;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,IAAI;AACnB,UAAM,OAAO,MAAM,QAAQ;AAC3B,QAAI,SAAS,QAAW;AACtB,YAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,YAAM,eAAe,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,KAAK;AACnD,YAAM,aACH,aAAa,WAAW,GAAG,KAAK,aAAa,SAAS,GAAG,KACzD,aAAa,WAAW,GAAG,KAAK,aAAa,SAAS,GAAG,IACtD,aAAa,UAAU,GAAG,aAAa,SAAS,CAAC,IACjD;AAEN,UAAI,WAAW,YAAY,MAAM,aAAa;AAC5C,cAAM,QAAQ,IAAI,GAAG,UAAU,IAAI,WAAW;AAC9C,cAAS,aAAU,YAAY,MAAM,KAAK,IAAI,GAAG,OAAO;AACxD,gBAAQ;AAAA,UACN,WAAW,UAAU,OAAO,WAAW,OAAO,UAAU;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AACL,UAAM,YAAY,QAAQ,SAAS,IAAI,KAAK,YAAY,KAAK,KAAK;AAClE,UAAS;AAAA,MACP;AAAA,MACA,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,IAAI,WAAW;AAAA;AAAA,MAClD;AAAA,IACF;AACA,YAAQ,MAAM,SAAS,UAAU,IAAI,WAAW,OAAO,UAAU,EAAE;AAAA,EACrE;AACF;","names":["decryptedBuffer"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chamesh2020/keystone",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A CLI tool built with TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"keystone": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"lint": "eslint src/",
|
|
33
|
+
"format": "prettier --write .",
|
|
34
|
+
"format:check": "prettier --check .",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"test:coverage": "vitest run --coverage",
|
|
39
|
+
"prepublishOnly": "npm run build",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"release": "npm run build && npm publish --access public"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [],
|
|
44
|
+
"author": "",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": ""
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.0.0",
|
|
55
|
+
"@types/node": "^22.20.0",
|
|
56
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
57
|
+
"eslint": "^9.0.0",
|
|
58
|
+
"prettier": "^3.4.0",
|
|
59
|
+
"tsup": "^8.4.0",
|
|
60
|
+
"typescript": "^5.7.0",
|
|
61
|
+
"typescript-eslint": "^8.0.0",
|
|
62
|
+
"vitest": "^3.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|