@keynv/cli 0.1.0-rc.11 → 0.1.0-rc.12
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/index.js +680 -367
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
-
import 'crypto';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
4
|
import { readFileSync, existsSync, statSync, writeFileSync, unlinkSync, mkdirSync, rmSync, readdirSync } from 'fs';
|
|
5
5
|
import { hostname, platform, homedir } from 'os';
|
|
6
|
-
import { isAbsolute, resolve, join, dirname, basename } from 'path';
|
|
6
|
+
import { relative, isAbsolute, resolve, join, dirname, basename } from 'path';
|
|
7
7
|
import { Entry } from '@napi-rs/keyring';
|
|
8
8
|
import { styleText } from 'util';
|
|
9
9
|
import j2, { stdin, stdout } from 'process';
|
|
@@ -1056,7 +1056,7 @@ var require_lib = __commonJS({
|
|
|
1056
1056
|
var VERSION, AGENT;
|
|
1057
1057
|
var init_version = __esm({
|
|
1058
1058
|
"src/version.ts"() {
|
|
1059
|
-
VERSION = "0.1.0-rc.
|
|
1059
|
+
VERSION = "0.1.0-rc.12" ;
|
|
1060
1060
|
AGENT = `keynv-cli/${VERSION}`;
|
|
1061
1061
|
}
|
|
1062
1062
|
});
|
|
@@ -1146,10 +1146,10 @@ var init_parser = __esm({
|
|
|
1146
1146
|
"../../packages/core/dist/reference/parser.js"() {
|
|
1147
1147
|
PROJECT_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
1148
1148
|
ENV_RE = /^[a-z0-9][a-z0-9-]{0,23}$/;
|
|
1149
|
-
KEY_RE = /^[a-
|
|
1149
|
+
KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
1150
1150
|
TRAILING_BOUNDARY = "(?!\\w|\\.[a-z0-9])";
|
|
1151
|
-
TEXT_FIND_SOURCE = `(?<![\\w.@/])@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-
|
|
1152
|
-
ARGV_FIND_SOURCE = `@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-
|
|
1151
|
+
TEXT_FIND_SOURCE = `(?<![\\w.@/])@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}${TRAILING_BOUNDARY}`;
|
|
1152
|
+
ARGV_FIND_SOURCE = `@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}${TRAILING_BOUNDARY}`;
|
|
1153
1153
|
TEXT_FIND_RE = new RegExp(TEXT_FIND_SOURCE, "g");
|
|
1154
1154
|
ARGV_FIND_RE = new RegExp(ARGV_FIND_SOURCE, "g");
|
|
1155
1155
|
}
|
|
@@ -5536,7 +5536,15 @@ function clearCredentialsFile() {
|
|
|
5536
5536
|
if (existsSync(legacy)) rmSync(legacy, { force: true });
|
|
5537
5537
|
try {
|
|
5538
5538
|
entry().deletePassword();
|
|
5539
|
-
|
|
5539
|
+
return true;
|
|
5540
|
+
} catch (err) {
|
|
5541
|
+
process.stderr.write(
|
|
5542
|
+
`keynv: warning \u2014 could not remove OS keychain entry (${err instanceof Error ? err.message : String(err)}).
|
|
5543
|
+
You may need to remove the '${SERVICE}' / '${KEY_ACCOUNT}' entry manually
|
|
5544
|
+
via your OS credential manager (Windows Credential Manager, macOS Keychain, or libsecret).
|
|
5545
|
+
`
|
|
5546
|
+
);
|
|
5547
|
+
return false;
|
|
5540
5548
|
}
|
|
5541
5549
|
}
|
|
5542
5550
|
var SERVICE, KEY_ACCOUNT;
|
|
@@ -5579,8 +5587,9 @@ async function saveCredentials(creds) {
|
|
|
5579
5587
|
cache = creds;
|
|
5580
5588
|
}
|
|
5581
5589
|
function clearCredentials() {
|
|
5582
|
-
clearCredentialsFile();
|
|
5590
|
+
const ok = clearCredentialsFile();
|
|
5583
5591
|
cache = null;
|
|
5592
|
+
return ok;
|
|
5584
5593
|
}
|
|
5585
5594
|
var cache;
|
|
5586
5595
|
var init_store = __esm({
|
|
@@ -5723,6 +5732,21 @@ var init_format = __esm({
|
|
|
5723
5732
|
"src/ui/format.ts"() {
|
|
5724
5733
|
}
|
|
5725
5734
|
});
|
|
5735
|
+
function walkUp(startDir, predicate) {
|
|
5736
|
+
let dir = resolve(startDir);
|
|
5737
|
+
for (let i = 0; i < 64; i++) {
|
|
5738
|
+
const result = predicate(dir);
|
|
5739
|
+
if (result !== null && result !== void 0) return result;
|
|
5740
|
+
const parent = dirname(dir);
|
|
5741
|
+
if (parent === dir) return null;
|
|
5742
|
+
dir = parent;
|
|
5743
|
+
}
|
|
5744
|
+
return null;
|
|
5745
|
+
}
|
|
5746
|
+
var init_fs = __esm({
|
|
5747
|
+
"src/util/fs.ts"() {
|
|
5748
|
+
}
|
|
5749
|
+
});
|
|
5726
5750
|
function parseEnvFile(content, filename) {
|
|
5727
5751
|
const normalized = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
5728
5752
|
const lines = normalized.split(/\r?\n/);
|
|
@@ -5781,20 +5805,14 @@ function parseEnvFile(content, filename) {
|
|
|
5781
5805
|
return entries;
|
|
5782
5806
|
}
|
|
5783
5807
|
function findEnvFile(startDir) {
|
|
5784
|
-
|
|
5785
|
-
for (let i = 0; i < 64; i++) {
|
|
5808
|
+
return walkUp(startDir, (dir) => {
|
|
5786
5809
|
const candidate = join(dir, ENV_FILE_BASENAME);
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
} catch {
|
|
5791
|
-
}
|
|
5810
|
+
try {
|
|
5811
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
5812
|
+
} catch {
|
|
5792
5813
|
}
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
dir = parent;
|
|
5796
|
-
}
|
|
5797
|
-
return null;
|
|
5814
|
+
return null;
|
|
5815
|
+
});
|
|
5798
5816
|
}
|
|
5799
5817
|
function loadEnvFile(opts) {
|
|
5800
5818
|
if (opts.disabled) return null;
|
|
@@ -5819,6 +5837,7 @@ var MAX_FILE_BYTES, KEY_RE2, ENV_FILE_BASENAME, EnvFileParseError, EnvFileNotFou
|
|
|
5819
5837
|
var init_envFile = __esm({
|
|
5820
5838
|
"src/exec/envFile.ts"() {
|
|
5821
5839
|
init_dist();
|
|
5840
|
+
init_fs();
|
|
5822
5841
|
MAX_FILE_BYTES = 1e6;
|
|
5823
5842
|
KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5824
5843
|
ENV_FILE_BASENAME = ".keynv.env";
|
|
@@ -5855,6 +5874,132 @@ var init_envFile = __esm({
|
|
|
5855
5874
|
}
|
|
5856
5875
|
});
|
|
5857
5876
|
|
|
5877
|
+
// src/init/heuristics.ts
|
|
5878
|
+
function shannonEntropyBits(s) {
|
|
5879
|
+
if (s.length === 0) return 0;
|
|
5880
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5881
|
+
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
5882
|
+
let h2 = 0;
|
|
5883
|
+
for (const c2 of counts.values()) {
|
|
5884
|
+
const p2 = c2 / s.length;
|
|
5885
|
+
h2 -= p2 * Math.log2(p2);
|
|
5886
|
+
}
|
|
5887
|
+
return h2;
|
|
5888
|
+
}
|
|
5889
|
+
function nameMatchesLiteralPrefix(name) {
|
|
5890
|
+
const upper = name.toUpperCase();
|
|
5891
|
+
return NAME_LITERAL_PREFIX.some((p2) => upper.startsWith(p2));
|
|
5892
|
+
}
|
|
5893
|
+
function nameMatchesSecretSuffix(name) {
|
|
5894
|
+
const upper = name.toUpperCase();
|
|
5895
|
+
for (const { suffix, hint } of NAME_SECRET_SUFFIXES) {
|
|
5896
|
+
if (upper.endsWith(suffix) || upper.endsWith(`_${suffix}`)) {
|
|
5897
|
+
return { matched: true, hint };
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
return { matched: false, hint: "" };
|
|
5901
|
+
}
|
|
5902
|
+
function classifyEntry(name, value) {
|
|
5903
|
+
const upper = name.toUpperCase();
|
|
5904
|
+
if (NAME_FRAMEWORK_MANAGED.has(upper)) {
|
|
5905
|
+
return { verdict: "skip", hint: "framework/shell-managed" };
|
|
5906
|
+
}
|
|
5907
|
+
if (NAME_LITERAL_EXACT.has(upper)) {
|
|
5908
|
+
return { verdict: "literal", hint: "common config var" };
|
|
5909
|
+
}
|
|
5910
|
+
if (nameMatchesLiteralPrefix(name)) {
|
|
5911
|
+
return { verdict: "literal", hint: "public env, build-time bundled" };
|
|
5912
|
+
}
|
|
5913
|
+
if (value.length === 0) {
|
|
5914
|
+
return { verdict: "literal", hint: "empty" };
|
|
5915
|
+
}
|
|
5916
|
+
for (const { re, hint } of VALUE_PATTERNS) {
|
|
5917
|
+
if (re.test(value)) return { verdict: "secret", hint };
|
|
5918
|
+
}
|
|
5919
|
+
const suffix = nameMatchesSecretSuffix(name);
|
|
5920
|
+
if (suffix.matched) {
|
|
5921
|
+
return { verdict: "secret", hint: suffix.hint };
|
|
5922
|
+
}
|
|
5923
|
+
if (NAME_DB_URL.test(name)) {
|
|
5924
|
+
return { verdict: "secret", hint: "database URL" };
|
|
5925
|
+
}
|
|
5926
|
+
if (value.length >= 32) {
|
|
5927
|
+
const bits = shannonEntropyBits(value);
|
|
5928
|
+
if (bits >= 3.5) {
|
|
5929
|
+
return { verdict: "secret", hint: `${value.length}-char random-looking string` };
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
return { verdict: "ambiguous", hint: "" };
|
|
5933
|
+
}
|
|
5934
|
+
function previewValue(value, max = 40) {
|
|
5935
|
+
if (value.length === 0) return "(empty)";
|
|
5936
|
+
if (value.length <= max) return value;
|
|
5937
|
+
return `${value.slice(0, max - 1)}\u2026`;
|
|
5938
|
+
}
|
|
5939
|
+
var NAME_FRAMEWORK_MANAGED, NAME_LITERAL_EXACT, NAME_LITERAL_PREFIX, NAME_SECRET_SUFFIXES, NAME_DB_URL, VALUE_PATTERNS;
|
|
5940
|
+
var init_heuristics = __esm({
|
|
5941
|
+
"src/init/heuristics.ts"() {
|
|
5942
|
+
NAME_FRAMEWORK_MANAGED = /* @__PURE__ */ new Set([
|
|
5943
|
+
"NODE_ENV",
|
|
5944
|
+
"PORT",
|
|
5945
|
+
"HOST",
|
|
5946
|
+
"HOSTNAME",
|
|
5947
|
+
"PATH",
|
|
5948
|
+
"HOME",
|
|
5949
|
+
"USER",
|
|
5950
|
+
"SHELL",
|
|
5951
|
+
"PWD",
|
|
5952
|
+
"CI"
|
|
5953
|
+
]);
|
|
5954
|
+
NAME_LITERAL_EXACT = /* @__PURE__ */ new Set([
|
|
5955
|
+
"DEBUG",
|
|
5956
|
+
"LOG_LEVEL",
|
|
5957
|
+
"LOGLEVEL",
|
|
5958
|
+
"TZ",
|
|
5959
|
+
"LANG",
|
|
5960
|
+
"LC_ALL",
|
|
5961
|
+
"NODE_OPTIONS",
|
|
5962
|
+
"NPM_CONFIG_LOGLEVEL",
|
|
5963
|
+
"TS_NODE_PROJECT"
|
|
5964
|
+
]);
|
|
5965
|
+
NAME_LITERAL_PREFIX = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_", "EXPO_PUBLIC_"];
|
|
5966
|
+
NAME_SECRET_SUFFIXES = [
|
|
5967
|
+
{ suffix: "PRIVATE_KEY", hint: "private key" },
|
|
5968
|
+
{ suffix: "API_KEY", hint: "API key" },
|
|
5969
|
+
{ suffix: "ACCESS_KEY", hint: "access key" },
|
|
5970
|
+
{ suffix: "SECRET_KEY", hint: "secret key" },
|
|
5971
|
+
{ suffix: "SECRET", hint: "secret" },
|
|
5972
|
+
{ suffix: "PASSWORD", hint: "password" },
|
|
5973
|
+
{ suffix: "PASSPHRASE", hint: "passphrase" },
|
|
5974
|
+
{ suffix: "TOKEN", hint: "token" },
|
|
5975
|
+
{ suffix: "KEY", hint: "key" },
|
|
5976
|
+
{ suffix: "CREDENTIALS", hint: "credentials" },
|
|
5977
|
+
{ suffix: "AUTH", hint: "auth" },
|
|
5978
|
+
{ suffix: "DSN", hint: "connection string" }
|
|
5979
|
+
];
|
|
5980
|
+
NAME_DB_URL = /^(DATABASE|DB|POSTGRES|MYSQL|MONGO|REDIS)_URL$/i;
|
|
5981
|
+
VALUE_PATTERNS = [
|
|
5982
|
+
{ re: /^sk-proj-/, hint: "OpenAI project key" },
|
|
5983
|
+
{ re: /^sk-[A-Za-z0-9]{20,}/, hint: "OpenAI key" },
|
|
5984
|
+
{ re: /^sk_live_/, hint: "Stripe live key" },
|
|
5985
|
+
{ re: /^sk_test_/, hint: "Stripe test key" },
|
|
5986
|
+
{ re: /^pk_live_/, hint: "Stripe publishable \u2014 often public, double-check" },
|
|
5987
|
+
{ re: /^xoxb-/, hint: "Slack bot token" },
|
|
5988
|
+
{ re: /^xoxp-/, hint: "Slack user token" },
|
|
5989
|
+
{ re: /^ghp_[A-Za-z0-9]{30,}/, hint: "GitHub personal access token" },
|
|
5990
|
+
{ re: /^github_pat_/, hint: "GitHub fine-grained PAT" },
|
|
5991
|
+
{ re: /^gho_/, hint: "GitHub OAuth token" },
|
|
5992
|
+
{ re: /^AKIA[0-9A-Z]{16}$/, hint: "AWS access key id" },
|
|
5993
|
+
{ re: /^ASIA[0-9A-Z]{16}$/, hint: "AWS temporary key id" },
|
|
5994
|
+
{ re: /^AIza[0-9A-Za-z_-]{35}$/, hint: "Google API key" },
|
|
5995
|
+
{ re: /^ya29\./, hint: "Google OAuth access token" },
|
|
5996
|
+
{ re: /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, hint: "JWT" },
|
|
5997
|
+
{ re: /^-----BEGIN [A-Z ]+PRIVATE KEY-----/, hint: "PEM private key" },
|
|
5998
|
+
{ re: /^([a-z]+):\/\/[^@]+:[^@]+@/i, hint: "connection string with credentials" }
|
|
5999
|
+
];
|
|
6000
|
+
}
|
|
6001
|
+
});
|
|
6002
|
+
|
|
5858
6003
|
// ../../node_modules/.pnpm/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
|
|
5859
6004
|
var getCodePointsLength, isFullWidth, isWideNotCJKTNotEmoji;
|
|
5860
6005
|
var init_utils = __esm({
|
|
@@ -7099,22 +7244,19 @@ var init_aiContext = __esm({
|
|
|
7099
7244
|
}
|
|
7100
7245
|
});
|
|
7101
7246
|
function findProjectRoot(startDir) {
|
|
7102
|
-
|
|
7103
|
-
for (let i = 0; i < 64; i++) {
|
|
7247
|
+
const result = walkUp(startDir, (dir) => {
|
|
7104
7248
|
for (const marker of PROJECT_MARKERS) {
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
return buildRoot(dir, marker);
|
|
7249
|
+
if (existsSync(join(dir, marker))) {
|
|
7250
|
+
return { dir, marker };
|
|
7108
7251
|
}
|
|
7109
7252
|
}
|
|
7110
7253
|
if (existsSync(join(dir, GIT_MARKER))) {
|
|
7111
|
-
return
|
|
7254
|
+
return { dir, marker: GIT_MARKER };
|
|
7112
7255
|
}
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
return null;
|
|
7256
|
+
return null;
|
|
7257
|
+
});
|
|
7258
|
+
if (!result) return null;
|
|
7259
|
+
return buildRoot(result.dir, result.marker);
|
|
7118
7260
|
}
|
|
7119
7261
|
function buildRoot(dir, marker) {
|
|
7120
7262
|
let suggestedName = basename(dir);
|
|
@@ -7199,6 +7341,7 @@ function hasExistingKeynvEnv(rootDir) {
|
|
|
7199
7341
|
var PROJECT_MARKERS, GIT_MARKER, ENV_GLOB, ENV_EXAMPLE, KEYNV_ENV_BASENAME;
|
|
7200
7342
|
var init_detect = __esm({
|
|
7201
7343
|
"src/init/detect.ts"() {
|
|
7344
|
+
init_fs();
|
|
7202
7345
|
PROJECT_MARKERS = [
|
|
7203
7346
|
"package.json",
|
|
7204
7347
|
"pyproject.toml",
|
|
@@ -7216,132 +7359,6 @@ var init_detect = __esm({
|
|
|
7216
7359
|
}
|
|
7217
7360
|
});
|
|
7218
7361
|
|
|
7219
|
-
// src/init/heuristics.ts
|
|
7220
|
-
function shannonEntropyBits(s) {
|
|
7221
|
-
if (s.length === 0) return 0;
|
|
7222
|
-
const counts = /* @__PURE__ */ new Map();
|
|
7223
|
-
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
7224
|
-
let h2 = 0;
|
|
7225
|
-
for (const c2 of counts.values()) {
|
|
7226
|
-
const p2 = c2 / s.length;
|
|
7227
|
-
h2 -= p2 * Math.log2(p2);
|
|
7228
|
-
}
|
|
7229
|
-
return h2;
|
|
7230
|
-
}
|
|
7231
|
-
function nameMatchesLiteralPrefix(name) {
|
|
7232
|
-
const upper = name.toUpperCase();
|
|
7233
|
-
return NAME_LITERAL_PREFIX.some((p2) => upper.startsWith(p2));
|
|
7234
|
-
}
|
|
7235
|
-
function nameMatchesSecretSuffix(name) {
|
|
7236
|
-
const upper = name.toUpperCase();
|
|
7237
|
-
for (const { suffix, hint } of NAME_SECRET_SUFFIXES) {
|
|
7238
|
-
if (upper.endsWith(suffix) || upper.endsWith(`_${suffix}`)) {
|
|
7239
|
-
return { matched: true, hint };
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
return { matched: false, hint: "" };
|
|
7243
|
-
}
|
|
7244
|
-
function classifyEntry(name, value) {
|
|
7245
|
-
const upper = name.toUpperCase();
|
|
7246
|
-
if (NAME_FRAMEWORK_MANAGED.has(upper)) {
|
|
7247
|
-
return { verdict: "skip", hint: "framework/shell-managed" };
|
|
7248
|
-
}
|
|
7249
|
-
if (NAME_LITERAL_EXACT.has(upper)) {
|
|
7250
|
-
return { verdict: "literal", hint: "common config var" };
|
|
7251
|
-
}
|
|
7252
|
-
if (nameMatchesLiteralPrefix(name)) {
|
|
7253
|
-
return { verdict: "literal", hint: "public env, build-time bundled" };
|
|
7254
|
-
}
|
|
7255
|
-
if (value.length === 0) {
|
|
7256
|
-
return { verdict: "literal", hint: "empty" };
|
|
7257
|
-
}
|
|
7258
|
-
for (const { re, hint } of VALUE_PATTERNS) {
|
|
7259
|
-
if (re.test(value)) return { verdict: "secret", hint };
|
|
7260
|
-
}
|
|
7261
|
-
const suffix = nameMatchesSecretSuffix(name);
|
|
7262
|
-
if (suffix.matched) {
|
|
7263
|
-
return { verdict: "secret", hint: suffix.hint };
|
|
7264
|
-
}
|
|
7265
|
-
if (NAME_DB_URL.test(name)) {
|
|
7266
|
-
return { verdict: "secret", hint: "database URL" };
|
|
7267
|
-
}
|
|
7268
|
-
if (value.length >= 32) {
|
|
7269
|
-
const bits = shannonEntropyBits(value);
|
|
7270
|
-
if (bits >= 3.5) {
|
|
7271
|
-
return { verdict: "secret", hint: `${value.length}-char random-looking string` };
|
|
7272
|
-
}
|
|
7273
|
-
}
|
|
7274
|
-
return { verdict: "ambiguous", hint: "" };
|
|
7275
|
-
}
|
|
7276
|
-
function previewValue(value, max = 40) {
|
|
7277
|
-
if (value.length === 0) return "(empty)";
|
|
7278
|
-
if (value.length <= max) return value;
|
|
7279
|
-
return `${value.slice(0, max - 1)}\u2026`;
|
|
7280
|
-
}
|
|
7281
|
-
var NAME_FRAMEWORK_MANAGED, NAME_LITERAL_EXACT, NAME_LITERAL_PREFIX, NAME_SECRET_SUFFIXES, NAME_DB_URL, VALUE_PATTERNS;
|
|
7282
|
-
var init_heuristics = __esm({
|
|
7283
|
-
"src/init/heuristics.ts"() {
|
|
7284
|
-
NAME_FRAMEWORK_MANAGED = /* @__PURE__ */ new Set([
|
|
7285
|
-
"NODE_ENV",
|
|
7286
|
-
"PORT",
|
|
7287
|
-
"HOST",
|
|
7288
|
-
"HOSTNAME",
|
|
7289
|
-
"PATH",
|
|
7290
|
-
"HOME",
|
|
7291
|
-
"USER",
|
|
7292
|
-
"SHELL",
|
|
7293
|
-
"PWD",
|
|
7294
|
-
"CI"
|
|
7295
|
-
]);
|
|
7296
|
-
NAME_LITERAL_EXACT = /* @__PURE__ */ new Set([
|
|
7297
|
-
"DEBUG",
|
|
7298
|
-
"LOG_LEVEL",
|
|
7299
|
-
"LOGLEVEL",
|
|
7300
|
-
"TZ",
|
|
7301
|
-
"LANG",
|
|
7302
|
-
"LC_ALL",
|
|
7303
|
-
"NODE_OPTIONS",
|
|
7304
|
-
"NPM_CONFIG_LOGLEVEL",
|
|
7305
|
-
"TS_NODE_PROJECT"
|
|
7306
|
-
]);
|
|
7307
|
-
NAME_LITERAL_PREFIX = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_", "EXPO_PUBLIC_"];
|
|
7308
|
-
NAME_SECRET_SUFFIXES = [
|
|
7309
|
-
{ suffix: "PRIVATE_KEY", hint: "private key" },
|
|
7310
|
-
{ suffix: "API_KEY", hint: "API key" },
|
|
7311
|
-
{ suffix: "ACCESS_KEY", hint: "access key" },
|
|
7312
|
-
{ suffix: "SECRET_KEY", hint: "secret key" },
|
|
7313
|
-
{ suffix: "SECRET", hint: "secret" },
|
|
7314
|
-
{ suffix: "PASSWORD", hint: "password" },
|
|
7315
|
-
{ suffix: "PASSPHRASE", hint: "passphrase" },
|
|
7316
|
-
{ suffix: "TOKEN", hint: "token" },
|
|
7317
|
-
{ suffix: "KEY", hint: "key" },
|
|
7318
|
-
{ suffix: "CREDENTIALS", hint: "credentials" },
|
|
7319
|
-
{ suffix: "AUTH", hint: "auth" },
|
|
7320
|
-
{ suffix: "DSN", hint: "connection string" }
|
|
7321
|
-
];
|
|
7322
|
-
NAME_DB_URL = /^(DATABASE|DB|POSTGRES|MYSQL|MONGO|REDIS)_URL$/i;
|
|
7323
|
-
VALUE_PATTERNS = [
|
|
7324
|
-
{ re: /^sk-proj-/, hint: "OpenAI project key" },
|
|
7325
|
-
{ re: /^sk-[A-Za-z0-9]{20,}/, hint: "OpenAI key" },
|
|
7326
|
-
{ re: /^sk_live_/, hint: "Stripe live key" },
|
|
7327
|
-
{ re: /^sk_test_/, hint: "Stripe test key" },
|
|
7328
|
-
{ re: /^pk_live_/, hint: "Stripe publishable \u2014 often public, double-check" },
|
|
7329
|
-
{ re: /^xoxb-/, hint: "Slack bot token" },
|
|
7330
|
-
{ re: /^xoxp-/, hint: "Slack user token" },
|
|
7331
|
-
{ re: /^ghp_[A-Za-z0-9]{30,}/, hint: "GitHub personal access token" },
|
|
7332
|
-
{ re: /^github_pat_/, hint: "GitHub fine-grained PAT" },
|
|
7333
|
-
{ re: /^gho_/, hint: "GitHub OAuth token" },
|
|
7334
|
-
{ re: /^AKIA[0-9A-Z]{16}$/, hint: "AWS access key id" },
|
|
7335
|
-
{ re: /^ASIA[0-9A-Z]{16}$/, hint: "AWS temporary key id" },
|
|
7336
|
-
{ re: /^AIza[0-9A-Za-z_-]{35}$/, hint: "Google API key" },
|
|
7337
|
-
{ re: /^ya29\./, hint: "Google OAuth access token" },
|
|
7338
|
-
{ re: /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, hint: "JWT" },
|
|
7339
|
-
{ re: /^-----BEGIN [A-Z ]+PRIVATE KEY-----/, hint: "PEM private key" },
|
|
7340
|
-
{ re: /^([a-z]+):\/\/[^@]+:[^@]+@/i, hint: "connection string with credentials" }
|
|
7341
|
-
];
|
|
7342
|
-
}
|
|
7343
|
-
});
|
|
7344
|
-
|
|
7345
7362
|
// src/init/scriptWrap.ts
|
|
7346
7363
|
function analyzeScript(name, command) {
|
|
7347
7364
|
const trimmed = command.trim();
|
|
@@ -7989,7 +8006,8 @@ function composeKeynvEnv(opts) {
|
|
|
7989
8006
|
const { uploadedAliases, literals, mergeWithExisting } = opts;
|
|
7990
8007
|
const lines = [];
|
|
7991
8008
|
if (mergeWithExisting !== null) {
|
|
7992
|
-
const
|
|
8009
|
+
const normalized = mergeWithExisting.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
8010
|
+
const trimmed = normalized.replace(/\n+$/, "");
|
|
7993
8011
|
if (trimmed.length > 0) {
|
|
7994
8012
|
lines.push(...trimmed.split("\n"));
|
|
7995
8013
|
lines.push("");
|
|
@@ -8099,8 +8117,27 @@ async function runBrowserAuth(serverUrl) {
|
|
|
8099
8117
|
}
|
|
8100
8118
|
const start = await startRes.json();
|
|
8101
8119
|
const opened = openBrowser(start.verification_uri_complete);
|
|
8102
|
-
if (
|
|
8103
|
-
|
|
8120
|
+
if (opened) {
|
|
8121
|
+
process.stderr.write(
|
|
8122
|
+
`
|
|
8123
|
+
Your code: ${start.user_code}
|
|
8124
|
+
Complete auth in your browser, then return here.
|
|
8125
|
+
|
|
8126
|
+
`
|
|
8127
|
+
);
|
|
8128
|
+
} else {
|
|
8129
|
+
process.stderr.write(
|
|
8130
|
+
`
|
|
8131
|
+
Could not open a browser automatically.
|
|
8132
|
+
Open this URL manually:
|
|
8133
|
+
|
|
8134
|
+
${start.verification_uri_complete}
|
|
8135
|
+
|
|
8136
|
+
Your code: ${start.user_code}
|
|
8137
|
+
Waiting for you to complete auth in the browser...
|
|
8138
|
+
|
|
8139
|
+
`
|
|
8140
|
+
);
|
|
8104
8141
|
}
|
|
8105
8142
|
const deadline = Date.now() + start.expires_in * 1e3;
|
|
8106
8143
|
const intervalMs = Math.max(1, start.interval) * 1e3;
|
|
@@ -28679,7 +28716,7 @@ var require_utils_webcrypto = __commonJS({
|
|
|
28679
28716
|
var nodeCrypto = __require("crypto");
|
|
28680
28717
|
module.exports = {
|
|
28681
28718
|
postgresMd5PasswordHash,
|
|
28682
|
-
randomBytes,
|
|
28719
|
+
randomBytes: randomBytes2,
|
|
28683
28720
|
deriveKey,
|
|
28684
28721
|
sha256,
|
|
28685
28722
|
hashByName,
|
|
@@ -28689,7 +28726,7 @@ var require_utils_webcrypto = __commonJS({
|
|
|
28689
28726
|
var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
|
|
28690
28727
|
var subtleCrypto = webCrypto.subtle;
|
|
28691
28728
|
var textEncoder = new TextEncoder();
|
|
28692
|
-
function
|
|
28729
|
+
function randomBytes2(length) {
|
|
28693
28730
|
return webCrypto.getRandomValues(Buffer.alloc(length));
|
|
28694
28731
|
}
|
|
28695
28732
|
async function md5(string) {
|
|
@@ -42554,7 +42591,7 @@ var init_audit2 = __esm({
|
|
|
42554
42591
|
async function runLoginFlow(client, options = {}) {
|
|
42555
42592
|
const server = options.server ?? DEFAULT_SERVER_URL;
|
|
42556
42593
|
const s = ft();
|
|
42557
|
-
s.start("
|
|
42594
|
+
s.start("Waiting for authorization");
|
|
42558
42595
|
let creds;
|
|
42559
42596
|
try {
|
|
42560
42597
|
creds = await runBrowserAuth(server);
|
|
@@ -42814,13 +42851,17 @@ async function runMenu() {
|
|
|
42814
42851
|
if (u) R2.message(`${u.email} (${u.org_role}) @ ${u.server_url}`);
|
|
42815
42852
|
}
|
|
42816
42853
|
if (didLogin) {
|
|
42817
|
-
const
|
|
42818
|
-
|
|
42819
|
-
|
|
42820
|
-
|
|
42821
|
-
|
|
42822
|
-
|
|
42823
|
-
|
|
42854
|
+
const root = findProjectRoot(process.cwd());
|
|
42855
|
+
const alreadyInitialized = root !== null && hasExistingKeynvEnv(root.path);
|
|
42856
|
+
if (!alreadyInitialized) {
|
|
42857
|
+
const setup = await ue({
|
|
42858
|
+
message: "Set up this project now?",
|
|
42859
|
+
initialValue: true
|
|
42860
|
+
});
|
|
42861
|
+
if (!q(setup) && setup) {
|
|
42862
|
+
const { runInitFlow: runInitFlow2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42863
|
+
await runInitFlow2(client, { cwd: process.cwd(), dryRun: false, noScripts: false });
|
|
42864
|
+
}
|
|
42824
42865
|
}
|
|
42825
42866
|
}
|
|
42826
42867
|
while (true) {
|
|
@@ -42889,6 +42930,7 @@ var init_menu = __esm({
|
|
|
42889
42930
|
"src/ui/menu.ts"() {
|
|
42890
42931
|
init_dist5();
|
|
42891
42932
|
init_http();
|
|
42933
|
+
init_detect();
|
|
42892
42934
|
init_store();
|
|
42893
42935
|
init_version();
|
|
42894
42936
|
init_audit2();
|
|
@@ -44719,11 +44761,10 @@ async function resolveAllAliases(client, argv2, extraStrings = []) {
|
|
|
44719
44761
|
return resolved;
|
|
44720
44762
|
}
|
|
44721
44763
|
function substitute(text, resolved) {
|
|
44764
|
+
const sorted = [...resolved].sort((a, b2) => b2.alias.literal.length - a.alias.literal.length);
|
|
44722
44765
|
let out = text;
|
|
44723
|
-
for (const r of
|
|
44724
|
-
|
|
44725
|
-
out = out.split(r.alias.literal).join(r.value);
|
|
44726
|
-
}
|
|
44766
|
+
for (const r of sorted) {
|
|
44767
|
+
out = out.split(r.alias.literal).join(r.value);
|
|
44727
44768
|
}
|
|
44728
44769
|
return out;
|
|
44729
44770
|
}
|
|
@@ -44826,10 +44867,18 @@ var BUILTIN_LINE_PATTERNS = BUILTIN_PATTERNS.filter((p2) => !p2.multiline);
|
|
|
44826
44867
|
|
|
44827
44868
|
// ../../packages/redactor/dist/entropy.js
|
|
44828
44869
|
var TOKEN_BOUNDARY_RE = /[\s,;:'"<>(){}[\]=]+/;
|
|
44870
|
+
var DEFAULT_EXCLUDE_PREFIXES = [
|
|
44871
|
+
"sha1-",
|
|
44872
|
+
"sha256:",
|
|
44873
|
+
"sha256-",
|
|
44874
|
+
"sha384-",
|
|
44875
|
+
"sha512-"
|
|
44876
|
+
];
|
|
44829
44877
|
var DEFAULTS = {
|
|
44830
44878
|
enabled: true,
|
|
44831
44879
|
minLength: 24,
|
|
44832
|
-
minBitsPerChar: 4.5
|
|
44880
|
+
minBitsPerChar: 4.5,
|
|
44881
|
+
excludePrefixes: DEFAULT_EXCLUDE_PREFIXES
|
|
44833
44882
|
};
|
|
44834
44883
|
function shannonEntropy(s) {
|
|
44835
44884
|
if (s.length === 0)
|
|
@@ -44862,9 +44911,13 @@ function findEntropyMatches(text, opts = {}) {
|
|
|
44862
44911
|
const end = i;
|
|
44863
44912
|
const token = text.slice(start, end);
|
|
44864
44913
|
if (token.length >= cfg.minLength) {
|
|
44865
|
-
const
|
|
44866
|
-
|
|
44867
|
-
|
|
44914
|
+
const lower = token.toLowerCase();
|
|
44915
|
+
const excluded = cfg.excludePrefixes.some((p2) => lower.startsWith(p2));
|
|
44916
|
+
if (!excluded) {
|
|
44917
|
+
const h2 = shannonEntropy(token);
|
|
44918
|
+
if (h2 >= cfg.minBitsPerChar) {
|
|
44919
|
+
matches.push({ start, end, token });
|
|
44920
|
+
}
|
|
44868
44921
|
}
|
|
44869
44922
|
}
|
|
44870
44923
|
}
|
|
@@ -45008,7 +45061,19 @@ var ENV_ALLOWLIST = [
|
|
|
45008
45061
|
"PWD",
|
|
45009
45062
|
"OLDPWD",
|
|
45010
45063
|
"TMPDIR",
|
|
45011
|
-
"SSH_AUTH_SOCK"
|
|
45064
|
+
"SSH_AUTH_SOCK",
|
|
45065
|
+
// Windows-specific
|
|
45066
|
+
"USERPROFILE",
|
|
45067
|
+
"USERNAME",
|
|
45068
|
+
"COMPUTERNAME",
|
|
45069
|
+
"TEMP",
|
|
45070
|
+
"TMP",
|
|
45071
|
+
"SYSTEMROOT",
|
|
45072
|
+
"SYSTEMDRIVE",
|
|
45073
|
+
"WINDIR",
|
|
45074
|
+
"COMSPEC",
|
|
45075
|
+
"APPDATA",
|
|
45076
|
+
"LOCALAPPDATA"
|
|
45012
45077
|
];
|
|
45013
45078
|
function spawnPrivileged(opts) {
|
|
45014
45079
|
const startedAt = Date.now();
|
|
@@ -45024,7 +45089,8 @@ function spawnPrivileged(opts) {
|
|
|
45024
45089
|
const child = spawn(opts.command, opts.args, {
|
|
45025
45090
|
env: env2,
|
|
45026
45091
|
stdio,
|
|
45027
|
-
detached: false
|
|
45092
|
+
detached: false,
|
|
45093
|
+
shell: process.platform === "win32"
|
|
45028
45094
|
});
|
|
45029
45095
|
const literals = opts.resolved.map((r) => r.value).filter((v2) => v2.length > 0);
|
|
45030
45096
|
if (!opts.noRedact && child.stdout) {
|
|
@@ -45152,7 +45218,11 @@ spelled \`--from\` to avoid the collision.)
|
|
|
45152
45218
|
`);
|
|
45153
45219
|
return 2;
|
|
45154
45220
|
}
|
|
45155
|
-
|
|
45221
|
+
this.context.stderr.write(
|
|
45222
|
+
`keynv: unexpected error loading env file: ${err instanceof Error ? err.message : String(err)}
|
|
45223
|
+
`
|
|
45224
|
+
);
|
|
45225
|
+
return 2;
|
|
45156
45226
|
}
|
|
45157
45227
|
const viaEnvSpecs = [];
|
|
45158
45228
|
for (const spec of this.viaEnv ?? []) {
|
|
@@ -45224,11 +45294,18 @@ spelled \`--from\` to avoid the collision.)
|
|
|
45224
45294
|
injectedEnv[spec.name] = value;
|
|
45225
45295
|
}
|
|
45226
45296
|
if (envFileLoaded && !this.quiet) {
|
|
45227
|
-
const
|
|
45228
|
-
const
|
|
45297
|
+
const aliasEntries = envFileLoaded.entries.filter((e2) => e2.isAlias);
|
|
45298
|
+
const plainEntries = envFileLoaded.entries.filter((e2) => !e2.isAlias);
|
|
45299
|
+
const displayPath = relative(process.cwd(), envFileLoaded.path) || envFileLoaded.path;
|
|
45300
|
+
const parts = [];
|
|
45301
|
+
if (aliasEntries.length > 0) {
|
|
45302
|
+
parts.push(aliasEntries.map((e2) => `${e2.name}=${e2.value}`).join(", ") + " (vault)");
|
|
45303
|
+
}
|
|
45304
|
+
if (plainEntries.length > 0) {
|
|
45305
|
+
parts.push(plainEntries.map((e2) => e2.name).join(", ") + " (plain)");
|
|
45306
|
+
}
|
|
45229
45307
|
this.context.stderr.write(
|
|
45230
|
-
`keynv: loaded ${
|
|
45231
|
-
`
|
|
45308
|
+
`keynv: loaded ${displayPath}` + (parts.length > 0 ? ` \u2014 ${parts.join("; ")}` : "") + "\n"
|
|
45232
45309
|
);
|
|
45233
45310
|
}
|
|
45234
45311
|
const timeoutS = this.timeout ? Number.parseInt(this.timeout, 10) : void 0;
|
|
@@ -45268,10 +45345,142 @@ function signalNumber(sig) {
|
|
|
45268
45345
|
}
|
|
45269
45346
|
|
|
45270
45347
|
// src/commands/init.ts
|
|
45348
|
+
init_dist();
|
|
45271
45349
|
init_http();
|
|
45350
|
+
init_envFile();
|
|
45351
|
+
init_heuristics();
|
|
45272
45352
|
init_init();
|
|
45273
45353
|
init_cancel();
|
|
45274
45354
|
init_tty();
|
|
45355
|
+
|
|
45356
|
+
// src/commands/project.ts
|
|
45357
|
+
init_http();
|
|
45358
|
+
init_format();
|
|
45359
|
+
function isProjectId(input) {
|
|
45360
|
+
return input.startsWith("p_") || /^[a-f0-9]{20,}$/i.test(input);
|
|
45361
|
+
}
|
|
45362
|
+
async function resolveProjectId(client, input) {
|
|
45363
|
+
if (isProjectId(input)) {
|
|
45364
|
+
return input;
|
|
45365
|
+
}
|
|
45366
|
+
const data = await client.request("/v1/projects");
|
|
45367
|
+
const match = data.projects.find((p2) => p2.name === input);
|
|
45368
|
+
if (!match) throw new Error(`project not found: ${input}`);
|
|
45369
|
+
return match.id;
|
|
45370
|
+
}
|
|
45371
|
+
var ProjectListCommand = class extends Command {
|
|
45372
|
+
static paths = [["project", "list"]];
|
|
45373
|
+
static usage = Command.Usage({
|
|
45374
|
+
description: "List projects visible to the current user."
|
|
45375
|
+
});
|
|
45376
|
+
json = options_exports.Boolean("--json", false);
|
|
45377
|
+
async execute() {
|
|
45378
|
+
const client = new ApiClient();
|
|
45379
|
+
const data = await client.request("/v1/projects");
|
|
45380
|
+
if (this.json) {
|
|
45381
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45382
|
+
`);
|
|
45383
|
+
return 0;
|
|
45384
|
+
}
|
|
45385
|
+
if (data.projects.length === 0) {
|
|
45386
|
+
this.context.stdout.write("no projects\n");
|
|
45387
|
+
return 0;
|
|
45388
|
+
}
|
|
45389
|
+
this.context.stdout.write(
|
|
45390
|
+
`${table(
|
|
45391
|
+
["name", "id", "created_at"],
|
|
45392
|
+
data.projects.map((p2) => [p2.name, p2.id, p2.created_at])
|
|
45393
|
+
)}
|
|
45394
|
+
`
|
|
45395
|
+
);
|
|
45396
|
+
return 0;
|
|
45397
|
+
}
|
|
45398
|
+
};
|
|
45399
|
+
var ProjectCreateCommand = class extends Command {
|
|
45400
|
+
static paths = [["project", "create"]];
|
|
45401
|
+
static usage = Command.Usage({
|
|
45402
|
+
description: "Create a new project with one or more environments.",
|
|
45403
|
+
examples: [["Two envs", "$0 project create billing --env dev --env prod:production:approval"]]
|
|
45404
|
+
});
|
|
45405
|
+
name = options_exports.String();
|
|
45406
|
+
envs = options_exports.Array("--env", {
|
|
45407
|
+
description: 'Environment spec: name[:tier[:approval]]. Tier \u2208 {production,non-production}; "approval" sets require_approval=true.'
|
|
45408
|
+
});
|
|
45409
|
+
async execute() {
|
|
45410
|
+
const envs = (this.envs ?? ["dev"]).map((spec) => {
|
|
45411
|
+
const [name, tier, approval] = spec.split(":");
|
|
45412
|
+
return {
|
|
45413
|
+
name: name ?? "",
|
|
45414
|
+
tier: tier ?? "non-production",
|
|
45415
|
+
require_approval: approval === "approval"
|
|
45416
|
+
};
|
|
45417
|
+
});
|
|
45418
|
+
const client = new ApiClient();
|
|
45419
|
+
const result = await client.request("/v1/projects", {
|
|
45420
|
+
method: "POST",
|
|
45421
|
+
body: { name: this.name, environments: envs }
|
|
45422
|
+
});
|
|
45423
|
+
this.context.stdout.write(`created project ${result.name} (${result.id})
|
|
45424
|
+
`);
|
|
45425
|
+
for (const e2 of envs) {
|
|
45426
|
+
this.context.stdout.write(
|
|
45427
|
+
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45428
|
+
`
|
|
45429
|
+
);
|
|
45430
|
+
}
|
|
45431
|
+
return 0;
|
|
45432
|
+
}
|
|
45433
|
+
};
|
|
45434
|
+
var ProjectDescribeCommand = class extends Command {
|
|
45435
|
+
static paths = [["project", "describe"]];
|
|
45436
|
+
static usage = Command.Usage({
|
|
45437
|
+
description: "Show metadata for one project.",
|
|
45438
|
+
examples: [
|
|
45439
|
+
["By ID", "$0 project describe p_go6rqgwz0wlokdsl55ikn"],
|
|
45440
|
+
["By name", "$0 project describe myproject"]
|
|
45441
|
+
]
|
|
45442
|
+
});
|
|
45443
|
+
id = options_exports.String();
|
|
45444
|
+
json = options_exports.Boolean("--json", false);
|
|
45445
|
+
async execute() {
|
|
45446
|
+
const client = new ApiClient();
|
|
45447
|
+
const projectId2 = await resolveProjectId(client, this.id);
|
|
45448
|
+
const data = await client.request(`/v1/projects/${projectId2}`);
|
|
45449
|
+
if (this.json) {
|
|
45450
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45451
|
+
`);
|
|
45452
|
+
return 0;
|
|
45453
|
+
}
|
|
45454
|
+
this.context.stdout.write(`project: ${data.name} (${data.id})
|
|
45455
|
+
`);
|
|
45456
|
+
for (const e2 of data.environments) {
|
|
45457
|
+
this.context.stdout.write(
|
|
45458
|
+
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45459
|
+
`
|
|
45460
|
+
);
|
|
45461
|
+
}
|
|
45462
|
+
return 0;
|
|
45463
|
+
}
|
|
45464
|
+
};
|
|
45465
|
+
var ProjectDeleteCommand = class extends Command {
|
|
45466
|
+
static paths = [["project", "delete"]];
|
|
45467
|
+
static usage = Command.Usage({ description: "Soft-delete a project." });
|
|
45468
|
+
id = options_exports.String();
|
|
45469
|
+
force = options_exports.Boolean("--force", false);
|
|
45470
|
+
async execute() {
|
|
45471
|
+
if (!this.force) {
|
|
45472
|
+
this.context.stderr.write("keynv: refusing to delete without --force\n");
|
|
45473
|
+
return 2;
|
|
45474
|
+
}
|
|
45475
|
+
const client = new ApiClient();
|
|
45476
|
+
await client.request(`/v1/projects/${this.id}`, { method: "DELETE" });
|
|
45477
|
+
this.context.stdout.write(`deleted project ${this.id}
|
|
45478
|
+
`);
|
|
45479
|
+
return 0;
|
|
45480
|
+
}
|
|
45481
|
+
};
|
|
45482
|
+
|
|
45483
|
+
// src/commands/init.ts
|
|
45275
45484
|
var InitCommand = class extends Command {
|
|
45276
45485
|
static paths = [["init"]];
|
|
45277
45486
|
static usage = Command.Usage({
|
|
@@ -45285,6 +45494,11 @@ package.json scripts with \`keynv exec\`.
|
|
|
45285
45494
|
Safe to re-run: existing .keynv.env entries are preserved; new
|
|
45286
45495
|
entries are appended below a marker.
|
|
45287
45496
|
|
|
45497
|
+
--dry-run prints the secrets that would be uploaded and the alias
|
|
45498
|
+
mappings that would be written to .keynv.env, then exits without
|
|
45499
|
+
touching any files or making any network calls. Use it to preview
|
|
45500
|
+
what init will do before committing.
|
|
45501
|
+
|
|
45288
45502
|
Requires an interactive terminal (clack TUI). For scripted
|
|
45289
45503
|
migration, use the lower-level \`keynv project\` and \`keynv secret\`
|
|
45290
45504
|
commands directly.
|
|
@@ -45292,28 +45506,47 @@ commands directly.
|
|
|
45292
45506
|
examples: [
|
|
45293
45507
|
["Walk the current project", "$0 init"],
|
|
45294
45508
|
["Preview without writing or uploading", "$0 init --dry-run"],
|
|
45295
|
-
["Skip the package.json script-wrapping step", "$0 init --no-scripts"]
|
|
45509
|
+
["Skip the package.json script-wrapping step", "$0 init --no-scripts"],
|
|
45510
|
+
["Non-interactive (CI/CD)", "$0 init --env-file .env --project myproject --env dev"]
|
|
45296
45511
|
]
|
|
45297
45512
|
});
|
|
45298
45513
|
dryRun = options_exports.Boolean("--dry-run", false, {
|
|
45299
|
-
description: "
|
|
45514
|
+
description: "Scan .env files and print the secrets that would be uploaded and the alias mappings that would be written \u2014 no files written, no vault calls made."
|
|
45300
45515
|
});
|
|
45301
45516
|
noScripts = options_exports.Boolean("--no-scripts", false, {
|
|
45302
45517
|
description: "Skip the package.json script-wrapping step."
|
|
45303
45518
|
});
|
|
45519
|
+
envFile = options_exports.String("--env-file", {
|
|
45520
|
+
description: "Path to .env file to migrate (non-interactive mode)."
|
|
45521
|
+
});
|
|
45522
|
+
project = options_exports.String("--project", {
|
|
45523
|
+
description: "Project name or ID (non-interactive mode)."
|
|
45524
|
+
});
|
|
45525
|
+
env = options_exports.String("--env", {
|
|
45526
|
+
description: "Environment name for --env-file (default: dev)."
|
|
45527
|
+
});
|
|
45528
|
+
secret = options_exports.Array("--secret", {
|
|
45529
|
+
description: "KEY=value secret to upload (non-interactive). Can be specified multiple times."
|
|
45530
|
+
});
|
|
45304
45531
|
async execute() {
|
|
45305
|
-
if (!isInteractive()) {
|
|
45306
|
-
this.context.stderr.write(
|
|
45307
|
-
"keynv init requires an interactive terminal. Use the lower-level commands (`keynv project`, `keynv secret`) for scripted setup.\n"
|
|
45308
|
-
);
|
|
45309
|
-
return 1;
|
|
45310
|
-
}
|
|
45311
45532
|
const client = new ApiClient();
|
|
45312
45533
|
await client.ensureHydrated();
|
|
45313
45534
|
if (!client.isLoggedIn) {
|
|
45314
45535
|
this.context.stderr.write("keynv: not logged in. Run `keynv login` first.\n");
|
|
45315
45536
|
return 1;
|
|
45316
45537
|
}
|
|
45538
|
+
const hasEnvFile = this.envFile != null;
|
|
45539
|
+
const hasSecrets = this.secret != null && this.secret.length > 0;
|
|
45540
|
+
const isNonInteractive = hasEnvFile || hasSecrets;
|
|
45541
|
+
if (isNonInteractive) {
|
|
45542
|
+
return this.runNonInteractive(client);
|
|
45543
|
+
}
|
|
45544
|
+
if (!isInteractive()) {
|
|
45545
|
+
this.context.stderr.write(
|
|
45546
|
+
"keynv init requires an interactive terminal. Use --env-file or --secret for scripted setup.\n"
|
|
45547
|
+
);
|
|
45548
|
+
return 1;
|
|
45549
|
+
}
|
|
45317
45550
|
try {
|
|
45318
45551
|
const outcome = await runInitFlow(client, {
|
|
45319
45552
|
cwd: process.cwd(),
|
|
@@ -45329,6 +45562,97 @@ commands directly.
|
|
|
45329
45562
|
return 1;
|
|
45330
45563
|
}
|
|
45331
45564
|
}
|
|
45565
|
+
async runNonInteractive(client) {
|
|
45566
|
+
const projectName = this.project;
|
|
45567
|
+
if (!projectName) {
|
|
45568
|
+
this.context.stderr.write("keynv: --project is required in non-interactive mode.\n");
|
|
45569
|
+
return 1;
|
|
45570
|
+
}
|
|
45571
|
+
const resolved = await resolveProjectId(client, projectName);
|
|
45572
|
+
if (!resolved) {
|
|
45573
|
+
this.context.stderr.write(`keynv: project not found: ${projectName}
|
|
45574
|
+
`);
|
|
45575
|
+
return 1;
|
|
45576
|
+
}
|
|
45577
|
+
const projectId2 = resolved;
|
|
45578
|
+
const envName = this.env ?? "dev";
|
|
45579
|
+
const secrets = [];
|
|
45580
|
+
if (this.envFile) {
|
|
45581
|
+
try {
|
|
45582
|
+
const content = readFileSync(this.envFile, "utf8");
|
|
45583
|
+
const entries = parseEnvFile(content, this.envFile);
|
|
45584
|
+
for (const e2 of entries) {
|
|
45585
|
+
if (classifyEntry(e2.name, e2.value).verdict !== "skip") {
|
|
45586
|
+
secrets.push({ name: e2.name, value: e2.value });
|
|
45587
|
+
}
|
|
45588
|
+
}
|
|
45589
|
+
} catch (err) {
|
|
45590
|
+
this.context.stderr.write(
|
|
45591
|
+
`keynv: cannot read ${this.envFile}: ${err instanceof Error ? err.message : String(err)}
|
|
45592
|
+
`
|
|
45593
|
+
);
|
|
45594
|
+
return 1;
|
|
45595
|
+
}
|
|
45596
|
+
}
|
|
45597
|
+
if (this.secret) {
|
|
45598
|
+
for (const spec of this.secret) {
|
|
45599
|
+
const eq = spec.indexOf("=");
|
|
45600
|
+
if (eq <= 0) {
|
|
45601
|
+
this.context.stderr.write(`keynv: invalid --secret '${spec}', expected KEY=value
|
|
45602
|
+
`);
|
|
45603
|
+
return 1;
|
|
45604
|
+
}
|
|
45605
|
+
const name = spec.slice(0, eq);
|
|
45606
|
+
const value = spec.slice(eq + 1);
|
|
45607
|
+
secrets.push({ name, value });
|
|
45608
|
+
}
|
|
45609
|
+
}
|
|
45610
|
+
if (secrets.length === 0) {
|
|
45611
|
+
this.context.stdout.write("keynv: nothing to migrate.\n");
|
|
45612
|
+
return 0;
|
|
45613
|
+
}
|
|
45614
|
+
if (this.dryRun) {
|
|
45615
|
+
this.context.stdout.write(
|
|
45616
|
+
`keynv: dry-run \u2014 would upload ${secrets.length} secret(s) to project ${projectName} (${projectId2}) in env ${envName}
|
|
45617
|
+
`
|
|
45618
|
+
);
|
|
45619
|
+
for (const { name } of secrets) {
|
|
45620
|
+
const aliasKey = name.toLowerCase().replace(/_/g, "-");
|
|
45621
|
+
this.context.stdout.write(` ${name}=@${projectName}.${envName}.${aliasKey}
|
|
45622
|
+
`);
|
|
45623
|
+
}
|
|
45624
|
+
return 0;
|
|
45625
|
+
}
|
|
45626
|
+
let uploaded = 0;
|
|
45627
|
+
const failed = [];
|
|
45628
|
+
for (const { name, value } of secrets) {
|
|
45629
|
+
const aliasKey = name.toLowerCase().replace(/_/g, "-");
|
|
45630
|
+
const alias2 = buildAlias({ project: projectName, environment: envName, key: aliasKey });
|
|
45631
|
+
if (!alias2) {
|
|
45632
|
+
failed.push(`${name} (invalid alias key: ${aliasKey})`);
|
|
45633
|
+
continue;
|
|
45634
|
+
}
|
|
45635
|
+
try {
|
|
45636
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
45637
|
+
method: "POST",
|
|
45638
|
+
body: { env: envName, key: aliasKey, value }
|
|
45639
|
+
});
|
|
45640
|
+
uploaded++;
|
|
45641
|
+
} catch (err) {
|
|
45642
|
+
failed.push(`${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
45643
|
+
}
|
|
45644
|
+
}
|
|
45645
|
+
this.context.stdout.write(
|
|
45646
|
+
`keynv: uploaded ${uploaded}/${secrets.length} secret(s) to ${projectName}.${envName}
|
|
45647
|
+
`
|
|
45648
|
+
);
|
|
45649
|
+
if (failed.length > 0) {
|
|
45650
|
+
for (const f of failed) this.context.stderr.write(` failed: ${f}
|
|
45651
|
+
`);
|
|
45652
|
+
return 1;
|
|
45653
|
+
}
|
|
45654
|
+
return 0;
|
|
45655
|
+
}
|
|
45332
45656
|
};
|
|
45333
45657
|
|
|
45334
45658
|
// src/commands/login.ts
|
|
@@ -45574,7 +45898,8 @@ var WhoamiCommand = class extends Command {
|
|
|
45574
45898
|
}
|
|
45575
45899
|
this.context.stdout.write(`user: ${data.email} (${data.id})
|
|
45576
45900
|
`);
|
|
45577
|
-
|
|
45901
|
+
const orgDisplay = data.org_name ? `${data.org_name} (${data.org_id})` : data.org_id;
|
|
45902
|
+
this.context.stdout.write(`org: ${orgDisplay}
|
|
45578
45903
|
`);
|
|
45579
45904
|
this.context.stdout.write(`role: ${data.org_role}
|
|
45580
45905
|
`);
|
|
@@ -45583,7 +45908,8 @@ var WhoamiCommand = class extends Command {
|
|
|
45583
45908
|
} else {
|
|
45584
45909
|
this.context.stdout.write("memberships:\n");
|
|
45585
45910
|
for (const m of data.memberships) {
|
|
45586
|
-
|
|
45911
|
+
const proj = m.project_name ? `${m.project_name} (${m.project_id})` : m.project_id;
|
|
45912
|
+
this.context.stdout.write(` ${proj}: ${m.role}
|
|
45587
45913
|
`);
|
|
45588
45914
|
}
|
|
45589
45915
|
}
|
|
@@ -45594,14 +45920,6 @@ var WhoamiCommand = class extends Command {
|
|
|
45594
45920
|
// src/commands/member.ts
|
|
45595
45921
|
init_http();
|
|
45596
45922
|
init_format();
|
|
45597
|
-
async function findProjectIdByName(client, name) {
|
|
45598
|
-
const data = await client.request(
|
|
45599
|
-
"/v1/projects"
|
|
45600
|
-
);
|
|
45601
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
45602
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
45603
|
-
return match.id;
|
|
45604
|
-
}
|
|
45605
45923
|
var MemberAddCommand = class extends Command {
|
|
45606
45924
|
static paths = [["member", "add"]];
|
|
45607
45925
|
static usage = Command.Usage({
|
|
@@ -45618,7 +45936,7 @@ var MemberAddCommand = class extends Command {
|
|
|
45618
45936
|
return 1;
|
|
45619
45937
|
}
|
|
45620
45938
|
const client = new ApiClient();
|
|
45621
|
-
const projectId2 = await
|
|
45939
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
45622
45940
|
await client.request(`/v1/projects/${projectId2}/members`, {
|
|
45623
45941
|
method: "POST",
|
|
45624
45942
|
body: { email: this.email, role: role2 }
|
|
@@ -45635,7 +45953,7 @@ var MemberRemoveCommand = class extends Command {
|
|
|
45635
45953
|
email = options_exports.String();
|
|
45636
45954
|
async execute() {
|
|
45637
45955
|
const client = new ApiClient();
|
|
45638
|
-
const projectId2 = await
|
|
45956
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
45639
45957
|
const members = await client.request(`/v1/projects/${projectId2}/members`);
|
|
45640
45958
|
const target = members.members.find((m) => m.email === this.email);
|
|
45641
45959
|
if (!target) {
|
|
@@ -45658,7 +45976,7 @@ var MemberListCommand = class extends Command {
|
|
|
45658
45976
|
json = options_exports.Boolean("--json", false);
|
|
45659
45977
|
async execute() {
|
|
45660
45978
|
const client = new ApiClient();
|
|
45661
|
-
const projectId2 = await
|
|
45979
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
45662
45980
|
const data = await client.request(`/v1/projects/${projectId2}/members`);
|
|
45663
45981
|
if (this.json) {
|
|
45664
45982
|
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
@@ -45679,114 +45997,6 @@ var MemberListCommand = class extends Command {
|
|
|
45679
45997
|
return 0;
|
|
45680
45998
|
}
|
|
45681
45999
|
};
|
|
45682
|
-
|
|
45683
|
-
// src/commands/project.ts
|
|
45684
|
-
init_http();
|
|
45685
|
-
init_format();
|
|
45686
|
-
var ProjectListCommand = class extends Command {
|
|
45687
|
-
static paths = [["project", "list"]];
|
|
45688
|
-
static usage = Command.Usage({
|
|
45689
|
-
description: "List projects visible to the current user."
|
|
45690
|
-
});
|
|
45691
|
-
json = options_exports.Boolean("--json", false);
|
|
45692
|
-
async execute() {
|
|
45693
|
-
const client = new ApiClient();
|
|
45694
|
-
const data = await client.request("/v1/projects");
|
|
45695
|
-
if (this.json) {
|
|
45696
|
-
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45697
|
-
`);
|
|
45698
|
-
return 0;
|
|
45699
|
-
}
|
|
45700
|
-
if (data.projects.length === 0) {
|
|
45701
|
-
this.context.stdout.write("no projects\n");
|
|
45702
|
-
return 0;
|
|
45703
|
-
}
|
|
45704
|
-
this.context.stdout.write(
|
|
45705
|
-
`${table(
|
|
45706
|
-
["name", "id", "created_at"],
|
|
45707
|
-
data.projects.map((p2) => [p2.name, p2.id, p2.created_at])
|
|
45708
|
-
)}
|
|
45709
|
-
`
|
|
45710
|
-
);
|
|
45711
|
-
return 0;
|
|
45712
|
-
}
|
|
45713
|
-
};
|
|
45714
|
-
var ProjectCreateCommand = class extends Command {
|
|
45715
|
-
static paths = [["project", "create"]];
|
|
45716
|
-
static usage = Command.Usage({
|
|
45717
|
-
description: "Create a new project with one or more environments.",
|
|
45718
|
-
examples: [["Two envs", "$0 project create billing --env dev --env prod:production:approval"]]
|
|
45719
|
-
});
|
|
45720
|
-
name = options_exports.String();
|
|
45721
|
-
envs = options_exports.Array("--env", {
|
|
45722
|
-
description: 'Environment spec: name[:tier[:approval]]. Tier \u2208 {production,non-production}; "approval" sets require_approval=true.'
|
|
45723
|
-
});
|
|
45724
|
-
async execute() {
|
|
45725
|
-
const envs = (this.envs ?? ["dev"]).map((spec) => {
|
|
45726
|
-
const [name, tier, approval] = spec.split(":");
|
|
45727
|
-
return {
|
|
45728
|
-
name: name ?? "",
|
|
45729
|
-
tier: tier ?? "non-production",
|
|
45730
|
-
require_approval: approval === "approval"
|
|
45731
|
-
};
|
|
45732
|
-
});
|
|
45733
|
-
const client = new ApiClient();
|
|
45734
|
-
const result = await client.request("/v1/projects", {
|
|
45735
|
-
method: "POST",
|
|
45736
|
-
body: { name: this.name, environments: envs }
|
|
45737
|
-
});
|
|
45738
|
-
this.context.stdout.write(`created project ${result.name} (${result.id})
|
|
45739
|
-
`);
|
|
45740
|
-
for (const e2 of envs) {
|
|
45741
|
-
this.context.stdout.write(
|
|
45742
|
-
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45743
|
-
`
|
|
45744
|
-
);
|
|
45745
|
-
}
|
|
45746
|
-
return 0;
|
|
45747
|
-
}
|
|
45748
|
-
};
|
|
45749
|
-
var ProjectDescribeCommand = class extends Command {
|
|
45750
|
-
static paths = [["project", "describe"]];
|
|
45751
|
-
static usage = Command.Usage({ description: "Show metadata for one project." });
|
|
45752
|
-
id = options_exports.String();
|
|
45753
|
-
json = options_exports.Boolean("--json", false);
|
|
45754
|
-
async execute() {
|
|
45755
|
-
const client = new ApiClient();
|
|
45756
|
-
const data = await client.request(`/v1/projects/${this.id}`);
|
|
45757
|
-
if (this.json) {
|
|
45758
|
-
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45759
|
-
`);
|
|
45760
|
-
return 0;
|
|
45761
|
-
}
|
|
45762
|
-
this.context.stdout.write(`project: ${data.name} (${data.id})
|
|
45763
|
-
`);
|
|
45764
|
-
for (const e2 of data.environments) {
|
|
45765
|
-
this.context.stdout.write(
|
|
45766
|
-
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45767
|
-
`
|
|
45768
|
-
);
|
|
45769
|
-
}
|
|
45770
|
-
return 0;
|
|
45771
|
-
}
|
|
45772
|
-
};
|
|
45773
|
-
var ProjectDeleteCommand = class extends Command {
|
|
45774
|
-
static paths = [["project", "delete"]];
|
|
45775
|
-
static usage = Command.Usage({ description: "Soft-delete a project." });
|
|
45776
|
-
id = options_exports.String();
|
|
45777
|
-
force = options_exports.Boolean("--force", false);
|
|
45778
|
-
async execute() {
|
|
45779
|
-
if (!this.force) {
|
|
45780
|
-
this.context.stderr.write("keynv: refusing to delete without --force\n");
|
|
45781
|
-
return 2;
|
|
45782
|
-
}
|
|
45783
|
-
const client = new ApiClient();
|
|
45784
|
-
await client.request(`/v1/projects/${this.id}`, { method: "DELETE" });
|
|
45785
|
-
this.context.stdout.write(`deleted project ${this.id}
|
|
45786
|
-
`);
|
|
45787
|
-
return 0;
|
|
45788
|
-
}
|
|
45789
|
-
};
|
|
45790
46000
|
var RedactCommand = class extends Command {
|
|
45791
46001
|
static paths = [["redact"]];
|
|
45792
46002
|
static usage = Command.Usage({
|
|
@@ -45829,11 +46039,9 @@ var RedactStreamCommand = class extends Command {
|
|
|
45829
46039
|
static usage = Command.Usage({
|
|
45830
46040
|
description: "Stream stdin through the line-buffered redactor to stdout.",
|
|
45831
46041
|
details: `
|
|
45832
|
-
|
|
45833
|
-
|
|
45834
|
-
|
|
45835
|
-
line structure. Multi-line patterns are NOT applied here (see the
|
|
45836
|
-
streaming-mode limitation in @keynv/redactor).
|
|
46042
|
+
Pipe any output through the line-buffered redactor to stdout.
|
|
46043
|
+
Preserves original line structure. Multi-line patterns are NOT applied
|
|
46044
|
+
here (see the streaming-mode limitation in the redactor package).
|
|
45837
46045
|
`
|
|
45838
46046
|
});
|
|
45839
46047
|
async execute() {
|
|
@@ -45854,12 +46062,7 @@ init_cancel();
|
|
|
45854
46062
|
init_pickProject();
|
|
45855
46063
|
init_pickSecret();
|
|
45856
46064
|
init_tty();
|
|
45857
|
-
|
|
45858
|
-
const data = await client.request("/v1/projects");
|
|
45859
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
45860
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
45861
|
-
return match.id;
|
|
45862
|
-
}
|
|
46065
|
+
var ALIAS_FORMAT_HINT = "Format: @<project>.<env>.<KEY> (project/env: lowercase kebab-case; key: letters, digits, _ or -)";
|
|
45863
46066
|
function missingAlias(stderr) {
|
|
45864
46067
|
stderr.write("keynv: missing <alias> (TTY required for interactive prompt).\n");
|
|
45865
46068
|
return 1;
|
|
@@ -45908,7 +46111,8 @@ var SecretCreateCommand = class extends Command {
|
|
|
45908
46111
|
}
|
|
45909
46112
|
const parsed = parseAlias(alias2);
|
|
45910
46113
|
if (!parsed) {
|
|
45911
|
-
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46114
|
+
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46115
|
+
${ALIAS_FORMAT_HINT}
|
|
45912
46116
|
`);
|
|
45913
46117
|
return 1;
|
|
45914
46118
|
}
|
|
@@ -45921,7 +46125,7 @@ var SecretCreateCommand = class extends Command {
|
|
|
45921
46125
|
} else if (value === void 0) {
|
|
45922
46126
|
value = await promptHidden("value: ");
|
|
45923
46127
|
}
|
|
45924
|
-
const projectId2 = await
|
|
46128
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
45925
46129
|
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
45926
46130
|
method: "POST",
|
|
45927
46131
|
body: { env: parsed.environment, key: parsed.key, value }
|
|
@@ -45955,10 +46159,11 @@ var SecretGetCommand = class extends Command {
|
|
|
45955
46159
|
const parsed = parseAlias(alias2);
|
|
45956
46160
|
if (!parsed) {
|
|
45957
46161
|
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46162
|
+
${ALIAS_FORMAT_HINT}
|
|
45958
46163
|
`);
|
|
45959
46164
|
return 1;
|
|
45960
46165
|
}
|
|
45961
|
-
const projectId2 = await
|
|
46166
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
45962
46167
|
const data = await client.request(
|
|
45963
46168
|
`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}`
|
|
45964
46169
|
);
|
|
@@ -45999,7 +46204,7 @@ var SecretListCommand = class extends Command {
|
|
|
45999
46204
|
throw err;
|
|
46000
46205
|
}
|
|
46001
46206
|
}
|
|
46002
|
-
const projectId2 = await
|
|
46207
|
+
const projectId2 = await resolveProjectId(client, projectName);
|
|
46003
46208
|
const data = await client.request(`/v1/projects/${projectId2}/secrets`);
|
|
46004
46209
|
if (this.json) {
|
|
46005
46210
|
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
@@ -46043,6 +46248,7 @@ var SecretRotateCommand = class extends Command {
|
|
|
46043
46248
|
const parsed = parseAlias(alias2);
|
|
46044
46249
|
if (!parsed) {
|
|
46045
46250
|
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46251
|
+
${ALIAS_FORMAT_HINT}
|
|
46046
46252
|
`);
|
|
46047
46253
|
return 1;
|
|
46048
46254
|
}
|
|
@@ -46058,7 +46264,7 @@ var SecretRotateCommand = class extends Command {
|
|
|
46058
46264
|
} else {
|
|
46059
46265
|
value = await promptHidden("new value: ");
|
|
46060
46266
|
}
|
|
46061
|
-
const projectId2 = await
|
|
46267
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46062
46268
|
const data = await client.request(
|
|
46063
46269
|
`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}/rotate`,
|
|
46064
46270
|
{ method: "POST", body: { new_value: value } }
|
|
@@ -46089,10 +46295,11 @@ var SecretDeleteCommand = class extends Command {
|
|
|
46089
46295
|
const parsed = parseAlias(alias2);
|
|
46090
46296
|
if (!parsed) {
|
|
46091
46297
|
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46298
|
+
${ALIAS_FORMAT_HINT}
|
|
46092
46299
|
`);
|
|
46093
46300
|
return 1;
|
|
46094
46301
|
}
|
|
46095
|
-
const projectId2 = await
|
|
46302
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46096
46303
|
await client.request(`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}`, {
|
|
46097
46304
|
method: "DELETE"
|
|
46098
46305
|
});
|
|
@@ -46101,6 +46308,140 @@ var SecretDeleteCommand = class extends Command {
|
|
|
46101
46308
|
return 0;
|
|
46102
46309
|
}
|
|
46103
46310
|
};
|
|
46311
|
+
function generateSecret(bytes = 32) {
|
|
46312
|
+
return randomBytes(bytes).toString("base64");
|
|
46313
|
+
}
|
|
46314
|
+
var ServerInitCommand = class extends Command {
|
|
46315
|
+
static paths = [["server", "init"]];
|
|
46316
|
+
static usage = Command.Usage({
|
|
46317
|
+
description: "Generate secrets and deployment files for a new keynv server.",
|
|
46318
|
+
details: `
|
|
46319
|
+
Interactive onboarding wizard that generates the configuration
|
|
46320
|
+
a fresh keynv server needs: JWT signing secret, master encryption
|
|
46321
|
+
key, and deployment templates.
|
|
46322
|
+
|
|
46323
|
+
Outputs:
|
|
46324
|
+
\u2022 A .keynv-server.env file you feed into your deployment
|
|
46325
|
+
(Docker Compose, Coolify, or bare-metal).
|
|
46326
|
+
\u2022 Optionally a docker-compose.yml in the current directory.
|
|
46327
|
+
|
|
46328
|
+
Non-interactive with --yes to skip prompts.
|
|
46329
|
+
`,
|
|
46330
|
+
examples: [
|
|
46331
|
+
["Interactive wizard", "$0 server init"],
|
|
46332
|
+
["Non-interactive (CI/scripts)", "$0 server init --yes"],
|
|
46333
|
+
["Docker Compose output", "$0 server init --compose"],
|
|
46334
|
+
["Custom output path", "$0 server init --out ./config/env"]
|
|
46335
|
+
]
|
|
46336
|
+
});
|
|
46337
|
+
yes = options_exports.Boolean("--yes", false, {
|
|
46338
|
+
description: "Skip all prompts (non-interactive)."
|
|
46339
|
+
});
|
|
46340
|
+
compose = options_exports.Boolean("--compose", false, {
|
|
46341
|
+
description: "Also write a docker-compose.yml to the current directory."
|
|
46342
|
+
});
|
|
46343
|
+
out = options_exports.String("--out", {
|
|
46344
|
+
description: "Write server env file to this path (default: .keynv-server.env)."
|
|
46345
|
+
});
|
|
46346
|
+
async execute() {
|
|
46347
|
+
const jwtSecret = generateSecret(48);
|
|
46348
|
+
const masterKey = generateSecret(32);
|
|
46349
|
+
const webSessionSecret = generateSecret(48);
|
|
46350
|
+
const outPath = this.out ?? ".keynv-server.env";
|
|
46351
|
+
const contents = [
|
|
46352
|
+
"# Generated by `keynv server init`.",
|
|
46353
|
+
"# Keep this file secret \u2014 it contains cryptographic key material.",
|
|
46354
|
+
`KEYNV_JWT_SECRET=${jwtSecret}`,
|
|
46355
|
+
`KEYNV_MASTER_KEY=${masterKey}`,
|
|
46356
|
+
"",
|
|
46357
|
+
"# Web dashboard session secret (required for Next.js JWE session tokens).",
|
|
46358
|
+
`KEYNV_WEB_SESSION_SECRET=${webSessionSecret}`,
|
|
46359
|
+
"",
|
|
46360
|
+
"# Server configuration (uncomment and adjust):",
|
|
46361
|
+
"# KEYNV_PORT=8080",
|
|
46362
|
+
"# KEYNV_WEB_URL=https://keynv.example.com",
|
|
46363
|
+
"# KEYNV_PUBLIC_REGISTRATION=false",
|
|
46364
|
+
"# KEYNV_DB_PATH=./keynv.db",
|
|
46365
|
+
"# KEYNV_MASTER_KEY_FILE=./master.key",
|
|
46366
|
+
"# KEYNV_RATE_LIMIT_PER_MINUTE=120",
|
|
46367
|
+
"# KEYNV_ARGON2_MEMORY_KIB=46080",
|
|
46368
|
+
"# KEYNV_ARGON2_TIME_COST=3"
|
|
46369
|
+
].join("\n");
|
|
46370
|
+
const { writeFileSync: writeFileSync4 } = await import('fs');
|
|
46371
|
+
writeFileSync4(outPath, `${contents}
|
|
46372
|
+
`);
|
|
46373
|
+
this.context.stdout.write(`Wrote ${outPath}
|
|
46374
|
+
|
|
46375
|
+
`);
|
|
46376
|
+
this.context.stdout.write("Generated secrets:\n");
|
|
46377
|
+
this.context.stdout.write(
|
|
46378
|
+
` KEYNV_JWT_SECRET ${jwtSecret.slice(0, 8)}... (base64, 48 bytes)
|
|
46379
|
+
`
|
|
46380
|
+
);
|
|
46381
|
+
this.context.stdout.write(
|
|
46382
|
+
` KEYNV_MASTER_KEY ${masterKey.slice(0, 8)}... (base64, 32 bytes)
|
|
46383
|
+
`
|
|
46384
|
+
);
|
|
46385
|
+
this.context.stdout.write(
|
|
46386
|
+
` KEYNV_WEB_SESSION_SECRET ${webSessionSecret.slice(0, 8)}... (base64, 48 bytes)
|
|
46387
|
+
`
|
|
46388
|
+
);
|
|
46389
|
+
this.context.stdout.write("\nNext steps:\n");
|
|
46390
|
+
this.context.stdout.write(` 1. Feed ${outPath} into your deployment:
|
|
46391
|
+
`);
|
|
46392
|
+
this.context.stdout.write(" docker compose --env-file .keynv-server.env up -d\n");
|
|
46393
|
+
this.context.stdout.write(" or load it into Coolify as environment variables.\n");
|
|
46394
|
+
this.context.stdout.write(" 2. Bootstrap the first owner:\n");
|
|
46395
|
+
this.context.stdout.write(
|
|
46396
|
+
" docker exec -it <server-container> node dist/bootstrap.js \\\n"
|
|
46397
|
+
);
|
|
46398
|
+
this.context.stdout.write(" --owner-email alice@example.com \\\n");
|
|
46399
|
+
this.context.stdout.write(' --owner-password "<a long random password>" \\\n');
|
|
46400
|
+
this.context.stdout.write(' --org-name "Acme Inc"\n');
|
|
46401
|
+
this.context.stdout.write(" 3. Install the CLI and log in:\n");
|
|
46402
|
+
this.context.stdout.write(" npm install -g @keynv/cli\n");
|
|
46403
|
+
this.context.stdout.write(" keynv login --server https://keynv.example.com\n");
|
|
46404
|
+
this.context.stdout.write(" 4. Onboard your first project:\n");
|
|
46405
|
+
this.context.stdout.write(" cd your-project && keynv init\n");
|
|
46406
|
+
if (this.compose) {
|
|
46407
|
+
const composeContent = `# keynv server + Litestream sidecar
|
|
46408
|
+
# Generated by \`keynv server init\`.
|
|
46409
|
+
# Load with: docker compose --env-file .keynv-server.env up -d
|
|
46410
|
+
|
|
46411
|
+
services:
|
|
46412
|
+
server:
|
|
46413
|
+
image: ghcr.io/keynv-labs/keynv-server:latest
|
|
46414
|
+
restart: unless-stopped
|
|
46415
|
+
ports:
|
|
46416
|
+
- '8080:8080'
|
|
46417
|
+
env_file:
|
|
46418
|
+
- .keynv-server.env
|
|
46419
|
+
volumes:
|
|
46420
|
+
- keynv-data:/data
|
|
46421
|
+
|
|
46422
|
+
litestream:
|
|
46423
|
+
image: litestream/litestream:latest
|
|
46424
|
+
restart: unless-stopped
|
|
46425
|
+
volumes:
|
|
46426
|
+
- keynv-data:/data
|
|
46427
|
+
command: replicate
|
|
46428
|
+
environment:
|
|
46429
|
+
LITESTREAM_DB_PATH: /data/keynv.db
|
|
46430
|
+
LITESTREAM_REPLICA_URL: s3://your-bucket/keynv
|
|
46431
|
+
AWS_ACCESS_KEY_ID: \${LITESTREAM_AWS_ACCESS_KEY_ID}
|
|
46432
|
+
AWS_SECRET_ACCESS_KEY: \${LITESTREAM_AWS_SECRET_ACCESS_KEY}
|
|
46433
|
+
|
|
46434
|
+
volumes:
|
|
46435
|
+
keynv-data:
|
|
46436
|
+
`;
|
|
46437
|
+
writeFileSync4("docker-compose.yml", composeContent);
|
|
46438
|
+
this.context.stdout.write(
|
|
46439
|
+
"\nWrote docker-compose.yml (update Litestream S3 config before deploying).\n"
|
|
46440
|
+
);
|
|
46441
|
+
}
|
|
46442
|
+
return 0;
|
|
46443
|
+
}
|
|
46444
|
+
};
|
|
46104
46445
|
|
|
46105
46446
|
// src/commands/test.ts
|
|
46106
46447
|
init_dist();
|
|
@@ -46453,18 +46794,9 @@ var TESTERS = [
|
|
|
46453
46794
|
sshTester,
|
|
46454
46795
|
httpTester
|
|
46455
46796
|
];
|
|
46456
|
-
function findTester(type) {
|
|
46457
|
-
return TESTERS.find((t) => t.type === type) ?? null;
|
|
46458
|
-
}
|
|
46459
46797
|
|
|
46460
46798
|
// src/commands/test.ts
|
|
46461
46799
|
init_http();
|
|
46462
|
-
async function findProjectIdByName3(client, name) {
|
|
46463
|
-
const data = await client.request("/v1/projects");
|
|
46464
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
46465
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
46466
|
-
return match.id;
|
|
46467
|
-
}
|
|
46468
46800
|
function parseTargets(specs) {
|
|
46469
46801
|
const out = {};
|
|
46470
46802
|
for (const spec of specs) {
|
|
@@ -46518,12 +46850,17 @@ the error message.
|
|
|
46518
46850
|
);
|
|
46519
46851
|
return 2;
|
|
46520
46852
|
}
|
|
46521
|
-
const tester =
|
|
46853
|
+
const tester = TESTERS.find((t) => t.type === this.as);
|
|
46522
46854
|
if (!tester) {
|
|
46523
46855
|
this.context.stderr.write(`keynv: unknown tester '${this.as}'
|
|
46524
46856
|
`);
|
|
46525
46857
|
return 1;
|
|
46526
46858
|
}
|
|
46859
|
+
const timeoutMs = this.timeout ? Number.parseInt(this.timeout, 10) * 1e3 : void 0;
|
|
46860
|
+
if (this.timeout && (timeoutMs === void 0 || Number.isNaN(timeoutMs))) {
|
|
46861
|
+
this.context.stderr.write("keynv: invalid --timeout (expected integer seconds)\n");
|
|
46862
|
+
return 2;
|
|
46863
|
+
}
|
|
46527
46864
|
let target;
|
|
46528
46865
|
try {
|
|
46529
46866
|
target = parseTargets(this.targets ?? []);
|
|
@@ -46540,7 +46877,7 @@ the error message.
|
|
|
46540
46877
|
}
|
|
46541
46878
|
let value;
|
|
46542
46879
|
try {
|
|
46543
|
-
const projectId2 = await
|
|
46880
|
+
const projectId2 = await resolveProjectId(client, parsedAlias.project);
|
|
46544
46881
|
const data = await client.request(
|
|
46545
46882
|
`/v1/projects/${projectId2}/secrets/${parsedAlias.environment}/${parsedAlias.key}`
|
|
46546
46883
|
);
|
|
@@ -46551,11 +46888,6 @@ the error message.
|
|
|
46551
46888
|
`);
|
|
46552
46889
|
return 1;
|
|
46553
46890
|
}
|
|
46554
|
-
const timeoutMs = this.timeout ? Number.parseInt(this.timeout, 10) * 1e3 : void 0;
|
|
46555
|
-
if (this.timeout && (timeoutMs === void 0 || Number.isNaN(timeoutMs))) {
|
|
46556
|
-
this.context.stderr.write("keynv: invalid --timeout (expected integer seconds)\n");
|
|
46557
|
-
return 2;
|
|
46558
|
-
}
|
|
46559
46891
|
const result = await runTest({
|
|
46560
46892
|
tester,
|
|
46561
46893
|
secret: { alias: parsedAlias.literal, value },
|
|
@@ -46597,25 +46929,6 @@ the error message.
|
|
|
46597
46929
|
}
|
|
46598
46930
|
};
|
|
46599
46931
|
|
|
46600
|
-
// src/commands/ui.ts
|
|
46601
|
-
init_tty();
|
|
46602
|
-
init_menu();
|
|
46603
|
-
var UICommand = class extends Command {
|
|
46604
|
-
static paths = [["ui"]];
|
|
46605
|
-
static usage = Command.Usage({
|
|
46606
|
-
description: "Open the interactive menu (also runs by default when no args are given)."
|
|
46607
|
-
});
|
|
46608
|
-
async execute() {
|
|
46609
|
-
if (!isInteractive()) {
|
|
46610
|
-
this.context.stdout.write(
|
|
46611
|
-
"keynv \u2014 AI-safe secrets management.\nRun `keynv --help` for the full command list, or run `keynv` in an interactive\nterminal to open the menu.\n"
|
|
46612
|
-
);
|
|
46613
|
-
return 0;
|
|
46614
|
-
}
|
|
46615
|
-
return runMenu();
|
|
46616
|
-
}
|
|
46617
|
-
};
|
|
46618
|
-
|
|
46619
46932
|
// src/index.ts
|
|
46620
46933
|
init_format();
|
|
46621
46934
|
init_version();
|
|
@@ -46649,7 +46962,7 @@ cli.register(InitCommand);
|
|
|
46649
46962
|
cli.register(RedactCommand);
|
|
46650
46963
|
cli.register(RedactStreamCommand);
|
|
46651
46964
|
cli.register(TestCommand);
|
|
46652
|
-
cli.register(
|
|
46965
|
+
cli.register(ServerInitCommand);
|
|
46653
46966
|
var argv = process.argv.slice(2);
|
|
46654
46967
|
if (argv.length === 0) {
|
|
46655
46968
|
const { runMenu: runMenu2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
|