ultraenv 1.0.0
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 +2058 -0
- package/bin/ultraenv.mjs +3 -0
- package/dist/chunk-2USZPWLZ.js +288 -0
- package/dist/chunk-3UV2QNJL.js +270 -0
- package/dist/chunk-3VYXPTYV.js +179 -0
- package/dist/chunk-4XUYMRK5.js +366 -0
- package/dist/chunk-5G2DU52U.js +189 -0
- package/dist/chunk-6KS56D6E.js +172 -0
- package/dist/chunk-AWN6ADV7.js +328 -0
- package/dist/chunk-CHVO6NWI.js +203 -0
- package/dist/chunk-CIFMBJ4H.js +3975 -0
- package/dist/chunk-GC7RXHLA.js +253 -0
- package/dist/chunk-HFXQGJY3.js +445 -0
- package/dist/chunk-IGFVP24Q.js +91 -0
- package/dist/chunk-IKPTKALB.js +78 -0
- package/dist/chunk-JB7RKV3C.js +66 -0
- package/dist/chunk-MNVFG7H4.js +611 -0
- package/dist/chunk-MSXMESFP.js +1910 -0
- package/dist/chunk-N5PAV4NM.js +127 -0
- package/dist/chunk-NBOABPHM.js +158 -0
- package/dist/chunk-OMAOROL4.js +49 -0
- package/dist/chunk-R7PZRSZ7.js +105 -0
- package/dist/chunk-TE7HPLA6.js +73 -0
- package/dist/chunk-TMT5KCO3.js +101 -0
- package/dist/chunk-UEWYFN6A.js +189 -0
- package/dist/chunk-WMHN5RW2.js +128 -0
- package/dist/chunk-XC65ORJ5.js +70 -0
- package/dist/chunk-YMMP4VQL.js +118 -0
- package/dist/chunk-YN2KGTCB.js +33 -0
- package/dist/chunk-YTICOB5M.js +65 -0
- package/dist/chunk-YVWLXFUT.js +107 -0
- package/dist/ci-check-sync-VBMSVWIV.js +48 -0
- package/dist/ci-scan-24MT5XGS.js +41 -0
- package/dist/ci-setup-C2NKEFRD.js +135 -0
- package/dist/ci-validate-7AW24LSQ.js +57 -0
- package/dist/cli/index.cjs +9217 -0
- package/dist/cli/index.d.cts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +339 -0
- package/dist/comparator-RDKX3OI7.js +13 -0
- package/dist/completion-MW35C2XO.js +168 -0
- package/dist/config-O5YRQP5Z.js +13 -0
- package/dist/debug-PTPXAF3K.js +131 -0
- package/dist/declaration-LEME4AFZ.js +10 -0
- package/dist/doctor-FZAUPKHS.js +129 -0
- package/dist/envs-compare-5K3HESX5.js +49 -0
- package/dist/envs-create-2XXHXMGA.js +58 -0
- package/dist/envs-list-NQM5252B.js +59 -0
- package/dist/envs-switch-6L2AQYID.js +50 -0
- package/dist/envs-validate-FL73Q76T.js +89 -0
- package/dist/fs-VH7ATUS3.js +31 -0
- package/dist/generator-LFZBMZZS.js +14 -0
- package/dist/git-BZS4DPAI.js +30 -0
- package/dist/help-3XJBXEHE.js +121 -0
- package/dist/index.cjs +12907 -0
- package/dist/index.d.cts +2562 -0
- package/dist/index.d.ts +2562 -0
- package/dist/index.js +3212 -0
- package/dist/init-Y7JQ2KYJ.js +146 -0
- package/dist/install-hook-SKXIV6NV.js +111 -0
- package/dist/json-schema-I26YNQBH.js +10 -0
- package/dist/key-manager-O3G55WPU.js +25 -0
- package/dist/middleware/express.cjs +103 -0
- package/dist/middleware/express.d.cts +115 -0
- package/dist/middleware/express.d.ts +115 -0
- package/dist/middleware/express.js +8 -0
- package/dist/middleware/fastify.cjs +91 -0
- package/dist/middleware/fastify.d.cts +111 -0
- package/dist/middleware/fastify.d.ts +111 -0
- package/dist/middleware/fastify.js +8 -0
- package/dist/module-IDIZPP4M.js +10 -0
- package/dist/protect-NCWPM6VC.js +161 -0
- package/dist/scan-TRLY36TT.js +58 -0
- package/dist/schema/index.cjs +4074 -0
- package/dist/schema/index.d.cts +1244 -0
- package/dist/schema/index.d.ts +1244 -0
- package/dist/schema/index.js +152 -0
- package/dist/sync-TMHMTLH2.js +186 -0
- package/dist/typegen-SQOSXBWM.js +80 -0
- package/dist/validate-IOAM5HWS.js +100 -0
- package/dist/vault-decrypt-U6HJZNBV.js +111 -0
- package/dist/vault-diff-B3ZOQTWI.js +132 -0
- package/dist/vault-encrypt-GUSLCSKS.js +112 -0
- package/dist/vault-init-GUBOTOUL.js +106 -0
- package/dist/vault-rekey-DAHT7JCN.js +132 -0
- package/dist/vault-status-GDLRU2OK.js +90 -0
- package/dist/vault-verify-CD76FJSF.js +102 -0
- package/package.json +106 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware/fastify.ts
|
|
21
|
+
var fastify_exports = {};
|
|
22
|
+
__export(fastify_exports, {
|
|
23
|
+
createUltraenvPlugin: () => createUltraenvPlugin,
|
|
24
|
+
ultraenvPlugin: () => ultraenvPlugin
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(fastify_exports);
|
|
27
|
+
function ultraenvPlugin(fastify, options = {}, done) {
|
|
28
|
+
const {
|
|
29
|
+
expose = true,
|
|
30
|
+
prefix = "PUBLIC_",
|
|
31
|
+
additionalPrefixes = [],
|
|
32
|
+
allowList = [],
|
|
33
|
+
denyList = [],
|
|
34
|
+
exposeNodeEnv = true,
|
|
35
|
+
source = process.env,
|
|
36
|
+
schema: _schema
|
|
37
|
+
} = options;
|
|
38
|
+
if (!expose) {
|
|
39
|
+
done();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const allPrefixes = [prefix];
|
|
43
|
+
for (const p of additionalPrefixes) {
|
|
44
|
+
allPrefixes.push(p);
|
|
45
|
+
}
|
|
46
|
+
const allowSet = new Set(allowList.map((k) => k.toUpperCase()));
|
|
47
|
+
const denySet = new Set(denyList.map((k) => k.toUpperCase()));
|
|
48
|
+
const filteredEnv = buildFastifyEnv(
|
|
49
|
+
source,
|
|
50
|
+
allPrefixes,
|
|
51
|
+
allowSet,
|
|
52
|
+
denySet,
|
|
53
|
+
exposeNodeEnv
|
|
54
|
+
);
|
|
55
|
+
fastify.decorate("env", filteredEnv);
|
|
56
|
+
done();
|
|
57
|
+
}
|
|
58
|
+
function buildFastifyEnv(source, prefixes, allowSet, denySet, exposeNodeEnv) {
|
|
59
|
+
const env = {};
|
|
60
|
+
for (const [key, value] of Object.entries(source)) {
|
|
61
|
+
if (value === void 0 || value === "") continue;
|
|
62
|
+
const upperKey = key.toUpperCase();
|
|
63
|
+
if (denySet.has(upperKey)) continue;
|
|
64
|
+
if (allowSet.has(upperKey)) {
|
|
65
|
+
env[key] = value;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (exposeNodeEnv && upperKey === "NODE_ENV") {
|
|
69
|
+
env[key] = value;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const p of prefixes) {
|
|
73
|
+
if (upperKey.startsWith(p.toUpperCase())) {
|
|
74
|
+
env[key] = value;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return env;
|
|
80
|
+
}
|
|
81
|
+
function createUltraenvPlugin(_opts = {}) {
|
|
82
|
+
return {
|
|
83
|
+
name: "ultraenv",
|
|
84
|
+
handler: ultraenvPlugin
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
createUltraenvPlugin,
|
|
90
|
+
ultraenvPlugin
|
|
91
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Options for the ultraenv Fastify plugin */
|
|
2
|
+
interface UltraenvPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Whether to expose env vars on the fastify instance.
|
|
5
|
+
* Default: true
|
|
6
|
+
*/
|
|
7
|
+
expose?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Prefix to filter public variables by.
|
|
10
|
+
* Only env vars starting with this prefix will be included in fastify.env.
|
|
11
|
+
* Default: 'PUBLIC_'
|
|
12
|
+
*/
|
|
13
|
+
prefix?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Additional prefixes to include.
|
|
16
|
+
* Default: []
|
|
17
|
+
*/
|
|
18
|
+
additionalPrefixes?: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Explicit allow-list of env var names to always include.
|
|
21
|
+
* Default: []
|
|
22
|
+
*/
|
|
23
|
+
allowList?: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Explicit deny-list of env var names to always exclude.
|
|
26
|
+
* Default: []
|
|
27
|
+
*/
|
|
28
|
+
denyList?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Whether to include NODE_ENV.
|
|
31
|
+
* Default: true
|
|
32
|
+
*/
|
|
33
|
+
exposeNodeEnv?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Custom env source. Defaults to process.env.
|
|
36
|
+
*/
|
|
37
|
+
source?: Record<string, string | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* An optional JSON schema for validating environment variables.
|
|
40
|
+
* If provided, the plugin will validate all env vars against this schema
|
|
41
|
+
* at registration time and throw if validation fails.
|
|
42
|
+
*
|
|
43
|
+
* The schema should follow the standard JSON Schema format.
|
|
44
|
+
*/
|
|
45
|
+
schema?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
/** The filtered env object attached to the Fastify instance */
|
|
48
|
+
interface FastifyEnv {
|
|
49
|
+
/** The filtered environment variables */
|
|
50
|
+
[key: string]: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fastify plugin that decorates the Fastify instance with a filtered
|
|
54
|
+
* environment variable object.
|
|
55
|
+
*
|
|
56
|
+
* The env object is built once at plugin registration time and attached
|
|
57
|
+
* as `fastify.env`. Individual route handlers can then access filtered
|
|
58
|
+
* env vars safely.
|
|
59
|
+
*
|
|
60
|
+
* @param fastify - The Fastify instance
|
|
61
|
+
* @param options - Plugin options
|
|
62
|
+
* @param done - Callback to signal plugin registration is complete
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import Fastify from 'fastify';
|
|
67
|
+
* import { ultraenvPlugin } from 'ultraenv/fastify';
|
|
68
|
+
*
|
|
69
|
+
* const fastify = Fastify();
|
|
70
|
+
* fastify.register(ultraenvPlugin, {
|
|
71
|
+
* prefix: 'PUBLIC_',
|
|
72
|
+
* additionalPrefixes: ['NEXT_PUBLIC_'],
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // In routes:
|
|
76
|
+
* fastify.get('/api/config', async (request, reply) => {
|
|
77
|
+
* return { apiUrl: fastify.env.PUBLIC_API_URL };
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function ultraenvPlugin(fastify: FastifyInstance, options: UltraenvPluginOptions | undefined, done: () => void): void;
|
|
82
|
+
/**
|
|
83
|
+
* Minimal Fastify instance interface needed by the plugin.
|
|
84
|
+
* This avoids a hard dependency on the 'fastify' package while
|
|
85
|
+
* maintaining type safety.
|
|
86
|
+
*/
|
|
87
|
+
interface FastifyInstance {
|
|
88
|
+
/**
|
|
89
|
+
* Decorate the Fastify instance with a new property.
|
|
90
|
+
*/
|
|
91
|
+
decorate(property: string, value: unknown): FastifyInstance;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Helper to create a properly typed Fastify plugin object.
|
|
95
|
+
* This is the recommended way to register the plugin with TypeScript.
|
|
96
|
+
*
|
|
97
|
+
* @param opts - Plugin options
|
|
98
|
+
* @returns A Fastify plugin object compatible with fastify.register()
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { createUltraenvPlugin } from 'ultraenv/fastify';
|
|
103
|
+
* fastify.register(createUltraenvPlugin({ prefix: 'PUBLIC_' }));
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare function createUltraenvPlugin(_opts?: UltraenvPluginOptions): {
|
|
107
|
+
name: string;
|
|
108
|
+
handler: (instance: FastifyInstance, options: UltraenvPluginOptions | undefined, done: () => void) => void;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { type FastifyEnv, type FastifyInstance, type UltraenvPluginOptions, createUltraenvPlugin, ultraenvPlugin };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Options for the ultraenv Fastify plugin */
|
|
2
|
+
interface UltraenvPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Whether to expose env vars on the fastify instance.
|
|
5
|
+
* Default: true
|
|
6
|
+
*/
|
|
7
|
+
expose?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Prefix to filter public variables by.
|
|
10
|
+
* Only env vars starting with this prefix will be included in fastify.env.
|
|
11
|
+
* Default: 'PUBLIC_'
|
|
12
|
+
*/
|
|
13
|
+
prefix?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Additional prefixes to include.
|
|
16
|
+
* Default: []
|
|
17
|
+
*/
|
|
18
|
+
additionalPrefixes?: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Explicit allow-list of env var names to always include.
|
|
21
|
+
* Default: []
|
|
22
|
+
*/
|
|
23
|
+
allowList?: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Explicit deny-list of env var names to always exclude.
|
|
26
|
+
* Default: []
|
|
27
|
+
*/
|
|
28
|
+
denyList?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Whether to include NODE_ENV.
|
|
31
|
+
* Default: true
|
|
32
|
+
*/
|
|
33
|
+
exposeNodeEnv?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Custom env source. Defaults to process.env.
|
|
36
|
+
*/
|
|
37
|
+
source?: Record<string, string | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* An optional JSON schema for validating environment variables.
|
|
40
|
+
* If provided, the plugin will validate all env vars against this schema
|
|
41
|
+
* at registration time and throw if validation fails.
|
|
42
|
+
*
|
|
43
|
+
* The schema should follow the standard JSON Schema format.
|
|
44
|
+
*/
|
|
45
|
+
schema?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
/** The filtered env object attached to the Fastify instance */
|
|
48
|
+
interface FastifyEnv {
|
|
49
|
+
/** The filtered environment variables */
|
|
50
|
+
[key: string]: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fastify plugin that decorates the Fastify instance with a filtered
|
|
54
|
+
* environment variable object.
|
|
55
|
+
*
|
|
56
|
+
* The env object is built once at plugin registration time and attached
|
|
57
|
+
* as `fastify.env`. Individual route handlers can then access filtered
|
|
58
|
+
* env vars safely.
|
|
59
|
+
*
|
|
60
|
+
* @param fastify - The Fastify instance
|
|
61
|
+
* @param options - Plugin options
|
|
62
|
+
* @param done - Callback to signal plugin registration is complete
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import Fastify from 'fastify';
|
|
67
|
+
* import { ultraenvPlugin } from 'ultraenv/fastify';
|
|
68
|
+
*
|
|
69
|
+
* const fastify = Fastify();
|
|
70
|
+
* fastify.register(ultraenvPlugin, {
|
|
71
|
+
* prefix: 'PUBLIC_',
|
|
72
|
+
* additionalPrefixes: ['NEXT_PUBLIC_'],
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // In routes:
|
|
76
|
+
* fastify.get('/api/config', async (request, reply) => {
|
|
77
|
+
* return { apiUrl: fastify.env.PUBLIC_API_URL };
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function ultraenvPlugin(fastify: FastifyInstance, options: UltraenvPluginOptions | undefined, done: () => void): void;
|
|
82
|
+
/**
|
|
83
|
+
* Minimal Fastify instance interface needed by the plugin.
|
|
84
|
+
* This avoids a hard dependency on the 'fastify' package while
|
|
85
|
+
* maintaining type safety.
|
|
86
|
+
*/
|
|
87
|
+
interface FastifyInstance {
|
|
88
|
+
/**
|
|
89
|
+
* Decorate the Fastify instance with a new property.
|
|
90
|
+
*/
|
|
91
|
+
decorate(property: string, value: unknown): FastifyInstance;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Helper to create a properly typed Fastify plugin object.
|
|
95
|
+
* This is the recommended way to register the plugin with TypeScript.
|
|
96
|
+
*
|
|
97
|
+
* @param opts - Plugin options
|
|
98
|
+
* @returns A Fastify plugin object compatible with fastify.register()
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { createUltraenvPlugin } from 'ultraenv/fastify';
|
|
103
|
+
* fastify.register(createUltraenvPlugin({ prefix: 'PUBLIC_' }));
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare function createUltraenvPlugin(_opts?: UltraenvPluginOptions): {
|
|
107
|
+
name: string;
|
|
108
|
+
handler: (instance: FastifyInstance, options: UltraenvPluginOptions | undefined, done: () => void) => void;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { type FastifyEnv, type FastifyInstance, type UltraenvPluginOptions, createUltraenvPlugin, ultraenvPlugin };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
drawBox
|
|
3
|
+
} from "./chunk-TMT5KCO3.js";
|
|
4
|
+
import {
|
|
5
|
+
getTrackedFiles,
|
|
6
|
+
isGitIgnored,
|
|
7
|
+
isGitRepository
|
|
8
|
+
} from "./chunk-R7PZRSZ7.js";
|
|
9
|
+
import {
|
|
10
|
+
bold,
|
|
11
|
+
green,
|
|
12
|
+
red,
|
|
13
|
+
yellow
|
|
14
|
+
} from "./chunk-OMAOROL4.js";
|
|
15
|
+
import {
|
|
16
|
+
writeError,
|
|
17
|
+
writeJson,
|
|
18
|
+
writeLine
|
|
19
|
+
} from "./chunk-YN2KGTCB.js";
|
|
20
|
+
import {
|
|
21
|
+
exists,
|
|
22
|
+
readFile
|
|
23
|
+
} from "./chunk-3VYXPTYV.js";
|
|
24
|
+
import "./chunk-5G2DU52U.js";
|
|
25
|
+
|
|
26
|
+
// src/cli/commands/protect.ts
|
|
27
|
+
import { resolve, join } from "path";
|
|
28
|
+
var PROTECTED_PATTERNS = [
|
|
29
|
+
".env",
|
|
30
|
+
".env.local",
|
|
31
|
+
".env.*.local",
|
|
32
|
+
".env.vault",
|
|
33
|
+
".env.keys"
|
|
34
|
+
];
|
|
35
|
+
async function run(args, ctx) {
|
|
36
|
+
try {
|
|
37
|
+
const cwd = args.flags["--cwd"] ?? ctx.cwd;
|
|
38
|
+
const baseDir = resolve(cwd);
|
|
39
|
+
if (ctx.outputFormat === "json") {
|
|
40
|
+
return await checkProtectionJson(baseDir);
|
|
41
|
+
}
|
|
42
|
+
writeLine(bold("\u{1F6E1}\uFE0F .gitignore Protection Check"));
|
|
43
|
+
writeLine("");
|
|
44
|
+
const isGit = await isGitRepository(baseDir);
|
|
45
|
+
if (!isGit) {
|
|
46
|
+
writeLine(yellow(" \u26A0 Not a git repository. Protection check is limited."));
|
|
47
|
+
writeLine("");
|
|
48
|
+
}
|
|
49
|
+
const gitignorePath = join(baseDir, ".gitignore");
|
|
50
|
+
const gitignoreExists = await exists(gitignorePath);
|
|
51
|
+
if (!gitignoreExists) {
|
|
52
|
+
writeError(red(" \u2717 No .gitignore file found!"));
|
|
53
|
+
writeLine("");
|
|
54
|
+
const box = drawBox([
|
|
55
|
+
"Your project has no .gitignore file.",
|
|
56
|
+
"Secrets in .env files could be committed to version control.",
|
|
57
|
+
"Create a .gitignore with ultraenv entries:",
|
|
58
|
+
"",
|
|
59
|
+
" ultraenv init"
|
|
60
|
+
], { title: "DANGER", border: "double" });
|
|
61
|
+
writeLine(box);
|
|
62
|
+
writeLine("");
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
writeLine(green(" \u2713 .gitignore exists"));
|
|
66
|
+
const gitignoreContent = await readFile(gitignorePath);
|
|
67
|
+
const issues = [];
|
|
68
|
+
const protectedEntries = [];
|
|
69
|
+
for (const pattern of PROTECTED_PATTERNS) {
|
|
70
|
+
if (gitignoreContent.includes(pattern)) {
|
|
71
|
+
protectedEntries.push(pattern);
|
|
72
|
+
} else {
|
|
73
|
+
issues.push(pattern);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
writeLine(green(` \u2713 ${protectedEntries.length}/${PROTECTED_PATTERNS.length} patterns protected`));
|
|
77
|
+
const envFiles = [".env", ".env.local", ".env.production", ".env.development"];
|
|
78
|
+
const notIgnored = [];
|
|
79
|
+
for (const envFile of envFiles) {
|
|
80
|
+
const envPath = join(baseDir, envFile);
|
|
81
|
+
const fileExists = await exists(envPath);
|
|
82
|
+
if (!fileExists) continue;
|
|
83
|
+
const ignored = await isGitIgnored(envFile, baseDir);
|
|
84
|
+
if (!ignored) {
|
|
85
|
+
notIgnored.push(envFile);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (notIgnored.length > 0) {
|
|
89
|
+
writeLine("");
|
|
90
|
+
writeError(red(` \u2717 ${notIgnored.length} .env file(s) are NOT gitignored:`));
|
|
91
|
+
for (const f of notIgnored) {
|
|
92
|
+
writeError(red(` - ${f}`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (isGit) {
|
|
96
|
+
const trackedFiles = await getTrackedFiles(baseDir);
|
|
97
|
+
const trackedEnvFiles = trackedFiles.filter(
|
|
98
|
+
(f) => f.startsWith(".env") && !f.endsWith(".example")
|
|
99
|
+
);
|
|
100
|
+
if (trackedEnvFiles.length > 0) {
|
|
101
|
+
writeLine("");
|
|
102
|
+
writeError(red(bold(` \u26A0 ${trackedEnvFiles.length} .env file(s) are tracked by git:`)));
|
|
103
|
+
for (const f of trackedEnvFiles) {
|
|
104
|
+
writeError(red(` - ${f}`));
|
|
105
|
+
}
|
|
106
|
+
writeLine("");
|
|
107
|
+
const warnBox = drawBox([
|
|
108
|
+
"Tracked .env files may contain secrets.",
|
|
109
|
+
"Remove them from version control immediately:",
|
|
110
|
+
"",
|
|
111
|
+
" git rm --cached .env*",
|
|
112
|
+
' git commit -m "Remove tracked .env files"'
|
|
113
|
+
], { title: "WARNING", border: "double" });
|
|
114
|
+
writeLine(warnBox);
|
|
115
|
+
writeLine("");
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (issues.length > 0) {
|
|
120
|
+
writeLine("");
|
|
121
|
+
writeLine(yellow(` Missing patterns:`));
|
|
122
|
+
for (const p of issues) {
|
|
123
|
+
writeLine(yellow(` - ${p}`));
|
|
124
|
+
}
|
|
125
|
+
writeLine("");
|
|
126
|
+
}
|
|
127
|
+
if (notIgnored.length === 0 && issues.length === 0) {
|
|
128
|
+
writeLine("");
|
|
129
|
+
writeLine(green(bold(" \u2705 All .env files are properly protected")));
|
|
130
|
+
}
|
|
131
|
+
writeLine("");
|
|
132
|
+
return notIgnored.length > 0 ? 1 : 0;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
135
|
+
writeError(red(` Protection check error: ${msg}`));
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function checkProtectionJson(baseDir) {
|
|
140
|
+
const gitignorePath = join(baseDir, ".gitignore");
|
|
141
|
+
const gitignoreExists = await exists(gitignorePath);
|
|
142
|
+
let content = "";
|
|
143
|
+
let issues = [];
|
|
144
|
+
if (gitignoreExists) {
|
|
145
|
+
content = await readFile(gitignorePath);
|
|
146
|
+
}
|
|
147
|
+
for (const pattern of PROTECTED_PATTERNS) {
|
|
148
|
+
if (!content.includes(pattern)) {
|
|
149
|
+
issues.push(pattern);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
writeJson({
|
|
153
|
+
protected: issues.length === 0,
|
|
154
|
+
gitignoreExists,
|
|
155
|
+
missingPatterns: issues
|
|
156
|
+
});
|
|
157
|
+
return issues.length === 0 ? 0 : 1;
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
run
|
|
161
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatScanResult,
|
|
3
|
+
scan
|
|
4
|
+
} from "./chunk-MSXMESFP.js";
|
|
5
|
+
import "./chunk-R7PZRSZ7.js";
|
|
6
|
+
import "./chunk-TE7HPLA6.js";
|
|
7
|
+
import {
|
|
8
|
+
bold,
|
|
9
|
+
red
|
|
10
|
+
} from "./chunk-OMAOROL4.js";
|
|
11
|
+
import {
|
|
12
|
+
writeError,
|
|
13
|
+
writeLine
|
|
14
|
+
} from "./chunk-YN2KGTCB.js";
|
|
15
|
+
|
|
16
|
+
// src/cli/commands/scan.ts
|
|
17
|
+
import { resolve } from "path";
|
|
18
|
+
async function run(args, ctx) {
|
|
19
|
+
try {
|
|
20
|
+
const cwd = args.flags["--cwd"] ?? ctx.cwd;
|
|
21
|
+
const baseDir = resolve(cwd);
|
|
22
|
+
const scope = args.flags["--scope"] ?? "files";
|
|
23
|
+
const outputFormat = args.flags["--format"] ?? (ctx.outputFormat === "json" ? "json" : "terminal");
|
|
24
|
+
const include = args.flags["--include"];
|
|
25
|
+
const exclude = args.flags["--exclude"];
|
|
26
|
+
const scanOptions = {
|
|
27
|
+
cwd: baseDir,
|
|
28
|
+
paths: args.positional.length > 0 ? args.positional : void 0,
|
|
29
|
+
include: include ? [include] : void 0,
|
|
30
|
+
exclude: exclude ? [exclude] : void 0,
|
|
31
|
+
scanGitHistory: scope === "git-history" || scope === "all",
|
|
32
|
+
scanStaged: scope === "staged",
|
|
33
|
+
diffFrom: scope === "diff" ? args.flags["--from"] ?? "HEAD" : void 0,
|
|
34
|
+
diffTo: scope === "diff" ? args.flags["--to"] : void 0,
|
|
35
|
+
failFast: args.flags["--fail-fast"] ?? false,
|
|
36
|
+
outputFormat
|
|
37
|
+
};
|
|
38
|
+
writeLine(bold("\u{1F510} Scanning for secrets..."));
|
|
39
|
+
writeLine("");
|
|
40
|
+
const result = await scan(scanOptions);
|
|
41
|
+
const formatted = formatScanResult(result, outputFormat);
|
|
42
|
+
process.stdout.write(formatted + "\n");
|
|
43
|
+
if (result.found) {
|
|
44
|
+
writeLine("");
|
|
45
|
+
writeError(red(bold(` Found ${result.secrets.length} potential secret(s)!`)));
|
|
46
|
+
writeLine("");
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
52
|
+
writeError(red(` Scan error: ${msg}`));
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
run
|
|
58
|
+
};
|