getmyenv 0.1.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 +13 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +9 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +105 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +8 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +64 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/backup.d.ts +7 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +35 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/export.d.ts +9 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +48 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +58 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +35 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +13 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/migrate.d.ts +13 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +127 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/restore.d.ts +8 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +61 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/run.d.ts +8 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +164 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +76 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/lib/api.d.ts +27 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +97 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/credentials.d.ts +9 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +46 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/env-name.d.ts +3 -0
- package/dist/lib/env-name.d.ts.map +1 -0
- package/dist/lib/env-name.js +5 -0
- package/dist/lib/env-name.js.map +1 -0
- package/dist/lib/fs.d.ts +56 -0
- package/dist/lib/fs.d.ts.map +1 -0
- package/dist/lib/fs.js +501 -0
- package/dist/lib/fs.js.map +1 -0
- package/dist/lib/identity.d.ts +12 -0
- package/dist/lib/identity.d.ts.map +1 -0
- package/dist/lib/identity.js +38 -0
- package/dist/lib/identity.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Credentials = {
|
|
2
|
+
token: string;
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function readCredentials(): Credentials | null;
|
|
6
|
+
export declare function writeCredentials(creds: Credentials): string;
|
|
7
|
+
export declare function clearCredentials(): boolean;
|
|
8
|
+
export declare function credentialsFilePath(): string;
|
|
9
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAMF,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAgBpD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAQ3D;AAED,wBAAgB,gBAAgB,IAAI,OAAO,CAK1C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getConfigDir } from "./fs.js";
|
|
4
|
+
function credentialsPath() {
|
|
5
|
+
return path.join(getConfigDir(), "credentials.json");
|
|
6
|
+
}
|
|
7
|
+
export function readCredentials() {
|
|
8
|
+
const file = credentialsPath();
|
|
9
|
+
if (!fs.existsSync(file))
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
+
if (!data.token || typeof data.token !== "string")
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
token: data.token.trim(),
|
|
17
|
+
apiUrl: typeof data.apiUrl === "string" && data.apiUrl.trim()
|
|
18
|
+
? data.apiUrl.trim()
|
|
19
|
+
: undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function writeCredentials(creds) {
|
|
27
|
+
const file = credentialsPath();
|
|
28
|
+
const payload = { token: creds.token.trim() };
|
|
29
|
+
if (creds.apiUrl?.trim())
|
|
30
|
+
payload.apiUrl = creds.apiUrl.trim();
|
|
31
|
+
fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", {
|
|
32
|
+
mode: 0o600,
|
|
33
|
+
});
|
|
34
|
+
return file;
|
|
35
|
+
}
|
|
36
|
+
export function clearCredentials() {
|
|
37
|
+
const file = credentialsPath();
|
|
38
|
+
if (!fs.existsSync(file))
|
|
39
|
+
return false;
|
|
40
|
+
fs.unlinkSync(file);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
export function credentialsFilePath() {
|
|
44
|
+
return credentialsPath();
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAOvC,SAAS,eAAe;IACtB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,kBAAkB,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC/D,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACxB,MAAM,EACJ,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;gBACnD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;gBACpB,CAAC,CAAC,SAAS;SAChB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAkB;IACjD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAgB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IAC3D,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE;QAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC/D,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;QAC9D,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-name.d.ts","sourceRoot":"","sources":["../../src/lib/env-name.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-name.js","sourceRoot":"","sources":["../../src/lib/env-name.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC"}
|
package/dist/lib/fs.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type ProjectMeta = {
|
|
2
|
+
projectId?: string;
|
|
3
|
+
projectName?: string;
|
|
4
|
+
apiUrl?: string;
|
|
5
|
+
protectionMode?: "simple" | "advanced";
|
|
6
|
+
};
|
|
7
|
+
export type RequirementsMeta = {
|
|
8
|
+
required: string[];
|
|
9
|
+
};
|
|
10
|
+
export declare function getProjectRoot(cwd?: string): string;
|
|
11
|
+
export declare function getGetenvDir(cwd?: string): string;
|
|
12
|
+
export declare function readProjectMeta(cwd?: string): ProjectMeta | null;
|
|
13
|
+
export declare function writeProjectMeta(meta: ProjectMeta, cwd?: string): void;
|
|
14
|
+
export declare function writeRequirements(required: string[], cwd?: string): void;
|
|
15
|
+
export declare function readRequirements(cwd?: string): string[];
|
|
16
|
+
/** OS user config for machine identity - never in the repo. */
|
|
17
|
+
export declare function getConfigDir(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Read a secret from stdin (TTY-hidden, or piped via --vault-password-stdin).
|
|
20
|
+
* Refuses to echo secrets on a non-TTY unless allowStdin is true.
|
|
21
|
+
*/
|
|
22
|
+
export declare function promptHidden(question: string, opts?: {
|
|
23
|
+
allowStdin?: boolean;
|
|
24
|
+
}): Promise<string>;
|
|
25
|
+
export declare function promptConfirm(question: string, opts?: {
|
|
26
|
+
defaultYes?: boolean;
|
|
27
|
+
}): Promise<boolean>;
|
|
28
|
+
/** 1-based numbered choice. Empty input selects defaultIndex (0-based). */
|
|
29
|
+
export declare function promptChoice(question: string, options: string[], defaultIndex?: number): Promise<number>;
|
|
30
|
+
export declare function ensureGitignoreHasEnv(cwd?: string): void;
|
|
31
|
+
/** First-class languages shown on landing / docs. */
|
|
32
|
+
export type DetectedLanguage = "node" | "python" | "ruby" | "rust" | "go" | "php" | "java" | "dotnet";
|
|
33
|
+
/** Detect project languages from framework markers (names only, no values). */
|
|
34
|
+
export declare function detectLanguages(cwd?: string): Array<{
|
|
35
|
+
language: DetectedLanguage;
|
|
36
|
+
label: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** Native env-read snippet for a language (for migrate / docs messaging). */
|
|
39
|
+
export declare function envReadSnippet(language: DetectedLanguage | null, name: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Detect env var names from .env* and common source patterns across languages.
|
|
42
|
+
* NEVER returns or collects secret values.
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectEnvNames(cwd?: string): string[];
|
|
45
|
+
export declare function parseEnvFile(filePath: string): Record<string, string>;
|
|
46
|
+
export declare function isEnvSampleFile(filePath: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Recursively find .env* files under cwd (including .example / .sample after a pull).
|
|
49
|
+
* Skips common junk directories and *.migrated.local.
|
|
50
|
+
*/
|
|
51
|
+
export declare function listEnvFiles(cwd?: string): string[];
|
|
52
|
+
/** First candidate from listEnvFiles, or null. */
|
|
53
|
+
export declare function findEnvFile(cwd?: string): string | null;
|
|
54
|
+
/** Infer CLI -e name from an env file basename. */
|
|
55
|
+
export declare function inferEnvNameFromFile(filePath: string): string;
|
|
56
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,wBAAgB,cAAc,CAAC,GAAG,SAAgB,GAAG,MAAM,CAE1D;AAED,wBAAgB,YAAY,CAAC,GAAG,SAAgB,GAAG,MAAM,CAExD;AAED,wBAAgB,eAAe,CAAC,GAAG,SAAgB,GAAG,WAAW,GAAG,IAAI,CAIvE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,SAAgB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAAE,EAClB,GAAG,SAAgB,GAClB,IAAI,CASN;AAED,wBAAgB,gBAAgB,CAAC,GAAG,SAAgB,GAAG,MAAM,EAAE,CAK9D;AAED,+DAA+D;AAC/D,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,MAAM,CAAC,CA2CjB;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EAAE,EACjB,YAAY,SAAI,GACf,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,SAAgB,GAAG,IAAI,CAc/D;AAED,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,IAAI,GACJ,KAAK,GACL,MAAM,GACN,QAAQ,CAAC;AAuGb,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,GAAG,SAAgB,GAAG,KAAK,CAAC;IAC1D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAmBD;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,IAAI,EAAE,MAAM,GACX,MAAM,CAoBR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,SAAgB,GAAG,MAAM,EAAE,CAwD5D;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBrE;AAsCD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAsBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,SAAgB,GAAG,MAAM,EAAE,CA6B1D;AAED,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,GAAG,SAAgB,GAAG,MAAM,GAAG,IAAI,CAE9D;AAED,mDAAmD;AACnD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK7D"}
|
package/dist/lib/fs.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import { LOCAL_DIR } from "@getmyenv/shared";
|
|
6
|
+
export function getProjectRoot(cwd = process.cwd()) {
|
|
7
|
+
return cwd;
|
|
8
|
+
}
|
|
9
|
+
export function getGetenvDir(cwd = process.cwd()) {
|
|
10
|
+
return path.join(cwd, LOCAL_DIR);
|
|
11
|
+
}
|
|
12
|
+
export function readProjectMeta(cwd = process.cwd()) {
|
|
13
|
+
const file = path.join(getGetenvDir(cwd), "project.json");
|
|
14
|
+
if (!fs.existsSync(file))
|
|
15
|
+
return null;
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
export function writeProjectMeta(meta, cwd = process.cwd()) {
|
|
19
|
+
const dir = getGetenvDir(cwd);
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
+
fs.writeFileSync(path.join(dir, "project.json"), JSON.stringify(meta, null, 2) + "\n", { mode: 0o600 });
|
|
22
|
+
}
|
|
23
|
+
export function writeRequirements(required, cwd = process.cwd()) {
|
|
24
|
+
const dir = getGetenvDir(cwd);
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
+
const data = { required };
|
|
27
|
+
fs.writeFileSync(path.join(dir, "requirements.json"), JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
export function readRequirements(cwd = process.cwd()) {
|
|
30
|
+
const file = path.join(getGetenvDir(cwd), "requirements.json");
|
|
31
|
+
if (!fs.existsSync(file))
|
|
32
|
+
return [];
|
|
33
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
34
|
+
return data.required ?? [];
|
|
35
|
+
}
|
|
36
|
+
/** OS user config for machine identity - never in the repo. */
|
|
37
|
+
export function getConfigDir() {
|
|
38
|
+
const base = process.env.GETMYENV_CONFIG_DIR?.trim() ||
|
|
39
|
+
path.join(os.homedir(), ".config", "getmyenv");
|
|
40
|
+
fs.mkdirSync(base, { recursive: true, mode: 0o700 });
|
|
41
|
+
return base;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read a secret from stdin (TTY-hidden, or piped via --vault-password-stdin).
|
|
45
|
+
* Refuses to echo secrets on a non-TTY unless allowStdin is true.
|
|
46
|
+
*/
|
|
47
|
+
export async function promptHidden(question, opts) {
|
|
48
|
+
const stdin = process.stdin;
|
|
49
|
+
if (!stdin.isTTY) {
|
|
50
|
+
if (!opts?.allowStdin) {
|
|
51
|
+
throw new Error("Refusing to read a secret without a TTY. Re-run in an interactive terminal, or pipe the value with --vault-password-stdin.");
|
|
52
|
+
}
|
|
53
|
+
const chunks = [];
|
|
54
|
+
for await (const chunk of stdin) {
|
|
55
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
56
|
+
}
|
|
57
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
58
|
+
}
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const rl = readline.createInterface({
|
|
61
|
+
input: process.stdin,
|
|
62
|
+
output: process.stdout,
|
|
63
|
+
});
|
|
64
|
+
process.stdout.write(question);
|
|
65
|
+
let value = "";
|
|
66
|
+
stdin.setRawMode?.(true);
|
|
67
|
+
stdin.resume();
|
|
68
|
+
stdin.on("data", (char) => {
|
|
69
|
+
const c = char.toString("utf8");
|
|
70
|
+
if (c === "\n" || c === "\r" || c === "\u0004") {
|
|
71
|
+
stdin.setRawMode?.(false);
|
|
72
|
+
stdin.pause();
|
|
73
|
+
stdin.removeAllListeners("data");
|
|
74
|
+
process.stdout.write("\n");
|
|
75
|
+
rl.close();
|
|
76
|
+
resolve(value);
|
|
77
|
+
}
|
|
78
|
+
else if (c === "\u0003") {
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
else if (c === "\u007f" || c === "\b") {
|
|
82
|
+
value = value.slice(0, -1);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
value += c;
|
|
86
|
+
process.stdout.write("*");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export function promptConfirm(question, opts) {
|
|
92
|
+
const defaultYes = opts?.defaultYes === true;
|
|
93
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
const rl = readline.createInterface({
|
|
96
|
+
input: process.stdin,
|
|
97
|
+
output: process.stdout,
|
|
98
|
+
});
|
|
99
|
+
rl.question(`${question} ${hint} `, (answer) => {
|
|
100
|
+
rl.close();
|
|
101
|
+
const trimmed = answer.trim();
|
|
102
|
+
if (!trimmed) {
|
|
103
|
+
resolve(defaultYes);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (defaultYes) {
|
|
107
|
+
resolve(!/^n(o)?$/i.test(trimmed));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
resolve(/^y(es)?$/i.test(trimmed));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** 1-based numbered choice. Empty input selects defaultIndex (0-based). */
|
|
115
|
+
export function promptChoice(question, options, defaultIndex = 0) {
|
|
116
|
+
if (options.length === 0) {
|
|
117
|
+
throw new Error("No options to choose from.");
|
|
118
|
+
}
|
|
119
|
+
if (options.length === 1)
|
|
120
|
+
return Promise.resolve(0);
|
|
121
|
+
const def = Math.min(Math.max(defaultIndex, 0), options.length - 1);
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
const rl = readline.createInterface({
|
|
124
|
+
input: process.stdin,
|
|
125
|
+
output: process.stdout,
|
|
126
|
+
});
|
|
127
|
+
for (let i = 0; i < options.length; i++) {
|
|
128
|
+
console.log(` ${i + 1}) ${options[i]}`);
|
|
129
|
+
}
|
|
130
|
+
rl.question(`${question} [${def + 1}]: `, (answer) => {
|
|
131
|
+
rl.close();
|
|
132
|
+
const trimmed = answer.trim();
|
|
133
|
+
if (!trimmed) {
|
|
134
|
+
resolve(def);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const n = Number.parseInt(trimmed, 10);
|
|
138
|
+
if (!Number.isFinite(n) || n < 1 || n > options.length) {
|
|
139
|
+
resolve(def);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
resolve(n - 1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export function ensureGitignoreHasEnv(cwd = process.cwd()) {
|
|
147
|
+
const gi = path.join(cwd, ".gitignore");
|
|
148
|
+
const entries = [".env", ".env.local", ".env.*.local", ".getmyenv/"];
|
|
149
|
+
let content = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : "";
|
|
150
|
+
let changed = false;
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (!content.split(/\r?\n/).includes(entry)) {
|
|
153
|
+
content += (content.endsWith("\n") || content === "" ? "" : "\n") + entry + "\n";
|
|
154
|
+
changed = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (changed) {
|
|
158
|
+
fs.writeFileSync(gi, content);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const FRAMEWORK_MARKERS = [
|
|
162
|
+
{ language: "node", label: "Node.js", files: ["package.json"] },
|
|
163
|
+
{
|
|
164
|
+
language: "python",
|
|
165
|
+
label: "Python",
|
|
166
|
+
files: ["pyproject.toml", "requirements.txt", "Pipfile", "setup.py"],
|
|
167
|
+
},
|
|
168
|
+
{ language: "rust", label: "Rust", files: ["Cargo.toml"] },
|
|
169
|
+
{ language: "go", label: "Go", files: ["go.mod"] },
|
|
170
|
+
{ language: "ruby", label: "Ruby", files: ["Gemfile"] },
|
|
171
|
+
{ language: "php", label: "PHP", files: ["composer.json"] },
|
|
172
|
+
{
|
|
173
|
+
language: "java",
|
|
174
|
+
label: "Java",
|
|
175
|
+
files: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
language: "dotnet",
|
|
179
|
+
label: ".NET",
|
|
180
|
+
files: [], // detected via *.csproj below
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
const SOURCE_SCANNERS = [
|
|
184
|
+
{
|
|
185
|
+
extensions: /\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
|
186
|
+
patterns: [
|
|
187
|
+
/process\.env\.([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
188
|
+
/process\.env\[["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\]/g,
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
extensions: /\.py$/,
|
|
193
|
+
patterns: [
|
|
194
|
+
/os\.environ\[["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\]/g,
|
|
195
|
+
/os\.environ\.get\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]/g,
|
|
196
|
+
/os\.getenv\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
extensions: /\.rb$/,
|
|
201
|
+
patterns: [
|
|
202
|
+
/ENV\[["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\]/g,
|
|
203
|
+
/ENV\.fetch\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]/g,
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
extensions: /\.go$/,
|
|
208
|
+
patterns: [
|
|
209
|
+
/os\.Getenv\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
210
|
+
/os\.LookupEnv\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
extensions: /\.rs$/,
|
|
215
|
+
patterns: [
|
|
216
|
+
/(?:std::)?env::var\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
217
|
+
/(?:std::)?env::var_os\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
extensions: /\.php$/,
|
|
222
|
+
patterns: [
|
|
223
|
+
/getenv\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
224
|
+
/\$_ENV\[["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\]/g,
|
|
225
|
+
/\$_SERVER\[["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\]/g,
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
extensions: /\.java$/,
|
|
230
|
+
patterns: [/System\.getenv\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
extensions: /\.cs$/,
|
|
234
|
+
patterns: [
|
|
235
|
+
/Environment\.GetEnvironmentVariable\(["'`]([A-Za-z_][A-Za-z0-9_]*)["'`]\)/g,
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
const SKIP_DIRS = new Set([
|
|
240
|
+
"node_modules",
|
|
241
|
+
".git",
|
|
242
|
+
"dist",
|
|
243
|
+
"build",
|
|
244
|
+
"target",
|
|
245
|
+
"vendor",
|
|
246
|
+
".venv",
|
|
247
|
+
"venv",
|
|
248
|
+
"__pycache__",
|
|
249
|
+
".next",
|
|
250
|
+
"coverage",
|
|
251
|
+
]);
|
|
252
|
+
/** Detect project languages from framework markers (names only, no values). */
|
|
253
|
+
export function detectLanguages(cwd = process.cwd()) {
|
|
254
|
+
const found = [];
|
|
255
|
+
for (const marker of FRAMEWORK_MARKERS) {
|
|
256
|
+
if (marker.language === "dotnet") {
|
|
257
|
+
try {
|
|
258
|
+
const hasCsproj = fs
|
|
259
|
+
.readdirSync(cwd)
|
|
260
|
+
.some((f) => f.endsWith(".csproj") || f.endsWith(".fsproj"));
|
|
261
|
+
if (hasCsproj)
|
|
262
|
+
found.push({ language: "dotnet", label: ".NET" });
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
/* ignore */
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (marker.files.some((f) => fs.existsSync(path.join(cwd, f)))) {
|
|
270
|
+
found.push({ language: marker.language, label: marker.label });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return found;
|
|
274
|
+
}
|
|
275
|
+
/** Native env-read snippet for a language (for migrate / docs messaging). */
|
|
276
|
+
export function envReadSnippet(language, name) {
|
|
277
|
+
switch (language) {
|
|
278
|
+
case "python":
|
|
279
|
+
return `os.environ["${name}"]`;
|
|
280
|
+
case "ruby":
|
|
281
|
+
return `ENV["${name}"]`;
|
|
282
|
+
case "go":
|
|
283
|
+
return `os.Getenv("${name}")`;
|
|
284
|
+
case "rust":
|
|
285
|
+
return `std::env::var("${name}")`;
|
|
286
|
+
case "php":
|
|
287
|
+
return `getenv("${name}")`;
|
|
288
|
+
case "java":
|
|
289
|
+
return `System.getenv("${name}")`;
|
|
290
|
+
case "dotnet":
|
|
291
|
+
return `Environment.GetEnvironmentVariable("${name}")`;
|
|
292
|
+
case "node":
|
|
293
|
+
default:
|
|
294
|
+
return `process.env.${name}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Detect env var names from .env* and common source patterns across languages.
|
|
299
|
+
* NEVER returns or collects secret values.
|
|
300
|
+
*/
|
|
301
|
+
export function detectEnvNames(cwd = process.cwd()) {
|
|
302
|
+
const names = new Set();
|
|
303
|
+
const envFiles = [
|
|
304
|
+
".env",
|
|
305
|
+
".env.example",
|
|
306
|
+
".env.local",
|
|
307
|
+
".env.development",
|
|
308
|
+
".env.production",
|
|
309
|
+
];
|
|
310
|
+
for (const file of envFiles) {
|
|
311
|
+
const p = path.join(cwd, file);
|
|
312
|
+
if (!fs.existsSync(p))
|
|
313
|
+
continue;
|
|
314
|
+
const text = fs.readFileSync(p, "utf8");
|
|
315
|
+
for (const line of text.split(/\r?\n/)) {
|
|
316
|
+
const trimmed = line.trim();
|
|
317
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
318
|
+
continue;
|
|
319
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
320
|
+
if (match?.[1])
|
|
321
|
+
names.add(match[1]);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const walk = (dir, depth = 0) => {
|
|
325
|
+
if (depth > 3)
|
|
326
|
+
return;
|
|
327
|
+
let entries;
|
|
328
|
+
try {
|
|
329
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
for (const ent of entries) {
|
|
335
|
+
if (SKIP_DIRS.has(ent.name))
|
|
336
|
+
continue;
|
|
337
|
+
const full = path.join(dir, ent.name);
|
|
338
|
+
if (ent.isDirectory()) {
|
|
339
|
+
walk(full, depth + 1);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
for (const scanner of SOURCE_SCANNERS) {
|
|
343
|
+
if (!scanner.extensions.test(ent.name))
|
|
344
|
+
continue;
|
|
345
|
+
try {
|
|
346
|
+
const src = fs.readFileSync(full, "utf8");
|
|
347
|
+
for (const re of scanner.patterns) {
|
|
348
|
+
re.lastIndex = 0;
|
|
349
|
+
let m;
|
|
350
|
+
while ((m = re.exec(src))) {
|
|
351
|
+
if (m[1])
|
|
352
|
+
names.add(m[1]);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
/* ignore unreadable */
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
walk(cwd);
|
|
364
|
+
return [...names].sort();
|
|
365
|
+
}
|
|
366
|
+
export function parseEnvFile(filePath) {
|
|
367
|
+
if (!fs.existsSync(filePath))
|
|
368
|
+
return {};
|
|
369
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
370
|
+
const out = {};
|
|
371
|
+
for (const line of text.split(/\r?\n/)) {
|
|
372
|
+
const trimmed = line.trim();
|
|
373
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
374
|
+
continue;
|
|
375
|
+
const eq = trimmed.indexOf("=");
|
|
376
|
+
if (eq <= 0)
|
|
377
|
+
continue;
|
|
378
|
+
const key = trimmed.slice(0, eq).trim();
|
|
379
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
380
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
381
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
382
|
+
value = value.slice(1, -1);
|
|
383
|
+
}
|
|
384
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
385
|
+
out[key] = value;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
const ENV_SKIP_DIRS = new Set([
|
|
391
|
+
"node_modules",
|
|
392
|
+
".git",
|
|
393
|
+
".next",
|
|
394
|
+
".next-staging",
|
|
395
|
+
".next-prev",
|
|
396
|
+
"dist",
|
|
397
|
+
"build",
|
|
398
|
+
"coverage",
|
|
399
|
+
".turbo",
|
|
400
|
+
".venv",
|
|
401
|
+
"venv",
|
|
402
|
+
".pnpm-store",
|
|
403
|
+
".cache",
|
|
404
|
+
"target",
|
|
405
|
+
"vendor",
|
|
406
|
+
]);
|
|
407
|
+
const ENV_BASENAME_PRIORITY = [
|
|
408
|
+
".env.local",
|
|
409
|
+
".env",
|
|
410
|
+
".env.development.local",
|
|
411
|
+
".env.development",
|
|
412
|
+
".env.production.local",
|
|
413
|
+
".env.production",
|
|
414
|
+
];
|
|
415
|
+
const MAX_ENV_SCAN_DEPTH = 6;
|
|
416
|
+
const MAX_ENV_CANDIDATES = 50;
|
|
417
|
+
function isEnvCandidateName(name) {
|
|
418
|
+
if (!name.startsWith(".env"))
|
|
419
|
+
return false;
|
|
420
|
+
if (name.endsWith(".migrated.local"))
|
|
421
|
+
return false;
|
|
422
|
+
return /^\.env($|\.)/.test(name);
|
|
423
|
+
}
|
|
424
|
+
export function isEnvSampleFile(filePath) {
|
|
425
|
+
const base = path.basename(filePath).toLowerCase();
|
|
426
|
+
return (base.endsWith(".example") ||
|
|
427
|
+
base.endsWith(".sample") ||
|
|
428
|
+
base.endsWith(".template"));
|
|
429
|
+
}
|
|
430
|
+
function basenamePriority(name) {
|
|
431
|
+
const idx = ENV_BASENAME_PRIORITY.indexOf(name);
|
|
432
|
+
return idx === -1 ? ENV_BASENAME_PRIORITY.length : idx;
|
|
433
|
+
}
|
|
434
|
+
function compareEnvCandidates(a, b, cwd) {
|
|
435
|
+
const relA = path.relative(cwd, a);
|
|
436
|
+
const relB = path.relative(cwd, b);
|
|
437
|
+
const depthA = relA.split(path.sep).length;
|
|
438
|
+
const depthB = relB.split(path.sep).length;
|
|
439
|
+
if (depthA !== depthB)
|
|
440
|
+
return depthA - depthB;
|
|
441
|
+
const sampleA = isEnvSampleFile(a) ? 1 : 0;
|
|
442
|
+
const sampleB = isEnvSampleFile(b) ? 1 : 0;
|
|
443
|
+
if (sampleA !== sampleB)
|
|
444
|
+
return sampleA - sampleB;
|
|
445
|
+
const pri = basenamePriority(path.basename(a)) - basenamePriority(path.basename(b));
|
|
446
|
+
if (pri !== 0)
|
|
447
|
+
return pri;
|
|
448
|
+
return relA.localeCompare(relB);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Recursively find .env* files under cwd (including .example / .sample after a pull).
|
|
452
|
+
* Skips common junk directories and *.migrated.local.
|
|
453
|
+
*/
|
|
454
|
+
export function listEnvFiles(cwd = process.cwd()) {
|
|
455
|
+
const root = path.resolve(cwd);
|
|
456
|
+
const found = [];
|
|
457
|
+
function walk(dir, depth) {
|
|
458
|
+
if (found.length >= MAX_ENV_CANDIDATES)
|
|
459
|
+
return;
|
|
460
|
+
if (depth > MAX_ENV_SCAN_DEPTH)
|
|
461
|
+
return;
|
|
462
|
+
let entries;
|
|
463
|
+
try {
|
|
464
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
for (const ent of entries) {
|
|
470
|
+
if (found.length >= MAX_ENV_CANDIDATES)
|
|
471
|
+
return;
|
|
472
|
+
const name = ent.name;
|
|
473
|
+
if (ent.isDirectory()) {
|
|
474
|
+
if (ENV_SKIP_DIRS.has(name))
|
|
475
|
+
continue;
|
|
476
|
+
walk(path.join(dir, name), depth + 1);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (!ent.isFile() || !isEnvCandidateName(name))
|
|
480
|
+
continue;
|
|
481
|
+
found.push(path.join(dir, name));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
walk(root, 0);
|
|
485
|
+
found.sort((a, b) => compareEnvCandidates(a, b, root));
|
|
486
|
+
return found;
|
|
487
|
+
}
|
|
488
|
+
/** First candidate from listEnvFiles, or null. */
|
|
489
|
+
export function findEnvFile(cwd = process.cwd()) {
|
|
490
|
+
return listEnvFiles(cwd)[0] ?? null;
|
|
491
|
+
}
|
|
492
|
+
/** Infer CLI -e name from an env file basename. */
|
|
493
|
+
export function inferEnvNameFromFile(filePath) {
|
|
494
|
+
const base = path.basename(filePath).toLowerCase();
|
|
495
|
+
if (base.includes("production"))
|
|
496
|
+
return "production";
|
|
497
|
+
if (base.includes("development") || base.includes("dev."))
|
|
498
|
+
return "development";
|
|
499
|
+
return "development";
|
|
500
|
+
}
|
|
501
|
+
//# sourceMappingURL=fs.js.map
|