@devshub198211/devguard 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/ai.cjs +867 -0
- package/dist/ai.d.cts +169 -0
- package/dist/ai.d.ts +169 -0
- package/dist/ai.js +2 -0
- package/dist/api-contract-5kJEwFIh.d.cts +157 -0
- package/dist/api-contract-5kJEwFIh.d.ts +157 -0
- package/dist/auth.cjs +787 -0
- package/dist/auth.d.cts +245 -0
- package/dist/auth.d.ts +245 -0
- package/dist/auth.js +1 -0
- package/dist/chunk-3SMY53XX.js +747 -0
- package/dist/chunk-4WCL5IUZ.js +493 -0
- package/dist/chunk-6IXDDYYA.js +345 -0
- package/dist/chunk-D7GNA6TS.js +611 -0
- package/dist/chunk-KSFZPDFO.js +366 -0
- package/dist/chunk-MT3VUCLS.js +35 -0
- package/dist/cli.cjs +1162 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +270 -0
- package/dist/dx.cjs +747 -0
- package/dist/dx.d.cts +96 -0
- package/dist/dx.d.ts +96 -0
- package/dist/dx.js +2 -0
- package/dist/index.cjs +2655 -0
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +6 -0
- package/dist/security.cjs +654 -0
- package/dist/security.d.cts +114 -0
- package/dist/security.d.ts +114 -0
- package/dist/security.js +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as https from 'https';
|
|
4
|
+
import * as http from 'http';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
|
|
7
|
+
// src/dx/env-safe.ts
|
|
8
|
+
function parseDotenv(content) {
|
|
9
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
10
|
+
if (typeof content !== "string") return result;
|
|
11
|
+
const lines = content.split(/\r?\n/);
|
|
12
|
+
for (const raw of lines) {
|
|
13
|
+
const line = raw.trim();
|
|
14
|
+
if (!line || line.startsWith("#")) continue;
|
|
15
|
+
const eqIdx = line.indexOf("=");
|
|
16
|
+
if (eqIdx === -1) continue;
|
|
17
|
+
const key = line.slice(0, eqIdx).trim();
|
|
18
|
+
let val = line.slice(eqIdx + 1).trim();
|
|
19
|
+
if (!key || key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
20
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key)) continue;
|
|
21
|
+
if (!val.startsWith('"') && !val.startsWith("'")) {
|
|
22
|
+
val = val.split(/\s+#/)[0].trim();
|
|
23
|
+
}
|
|
24
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
25
|
+
val = val.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ");
|
|
26
|
+
}
|
|
27
|
+
result[key] = val;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
function loadDotenvFile(filePath) {
|
|
32
|
+
const resolved = path.resolve(filePath);
|
|
33
|
+
if (!fs.existsSync(resolved)) return /* @__PURE__ */ Object.create(null);
|
|
34
|
+
try {
|
|
35
|
+
const stat = fs.statSync(resolved);
|
|
36
|
+
if (!stat.isFile() || stat.size > 1 * 1024 * 1024) return /* @__PURE__ */ Object.create(null);
|
|
37
|
+
return parseDotenv(fs.readFileSync(resolved, "utf-8"));
|
|
38
|
+
} catch {
|
|
39
|
+
return /* @__PURE__ */ Object.create(null);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function coerce(key, raw, spec) {
|
|
43
|
+
const val = raw.trim();
|
|
44
|
+
if (spec.type === "string") {
|
|
45
|
+
if (spec.minLength !== void 0 && val.length < spec.minLength) throw new Error(`${key}: too short (min ${spec.minLength} chars)`);
|
|
46
|
+
if (spec.maxLength !== void 0 && val.length > spec.maxLength) throw new Error(`${key}: too long (max ${spec.maxLength} chars)`);
|
|
47
|
+
if (spec.enum && !spec.enum.includes(val)) throw new Error(`${key}: must be one of [${spec.enum.join(", ")}]`);
|
|
48
|
+
if (spec.regex && !spec.regex.test(val)) throw new Error(`${key}: does not match expected pattern`);
|
|
49
|
+
return val;
|
|
50
|
+
}
|
|
51
|
+
if (spec.type === "number" || spec.type === "integer") {
|
|
52
|
+
const n = spec.type === "integer" ? parseInt(val, 10) : parseFloat(val);
|
|
53
|
+
if (isNaN(n)) throw new Error(`${key}: expected ${spec.type}`);
|
|
54
|
+
if (!isFinite(n)) throw new Error(`${key}: value must be finite`);
|
|
55
|
+
if (spec.min !== void 0 && n < spec.min) throw new Error(`${key}: too small (min ${spec.min})`);
|
|
56
|
+
if (spec.max !== void 0 && n > spec.max) throw new Error(`${key}: too large (max ${spec.max})`);
|
|
57
|
+
return n;
|
|
58
|
+
}
|
|
59
|
+
if (spec.type === "boolean") {
|
|
60
|
+
if (["true", "1", "yes", "on"].includes(val.toLowerCase())) return true;
|
|
61
|
+
if (["false", "0", "no", "off"].includes(val.toLowerCase())) return false;
|
|
62
|
+
throw new Error(`${key}: expected boolean (true/false/1/0/yes/no)`);
|
|
63
|
+
}
|
|
64
|
+
if (spec.type === "url") {
|
|
65
|
+
try {
|
|
66
|
+
new URL(val);
|
|
67
|
+
return val;
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(`${key}: invalid URL`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (spec.type === "email") {
|
|
73
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) throw new Error(`${key}: invalid email`);
|
|
74
|
+
return val;
|
|
75
|
+
}
|
|
76
|
+
if (spec.type === "json") {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(val);
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error(`${key}: invalid JSON`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (spec.type === "csv") {
|
|
84
|
+
return val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
return val;
|
|
87
|
+
}
|
|
88
|
+
function parseEnv(schema, source = process.env) {
|
|
89
|
+
const errors = [];
|
|
90
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
91
|
+
for (const [key, spec] of Object.entries(schema)) {
|
|
92
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
93
|
+
const raw = source[key] ?? spec.default;
|
|
94
|
+
const isRequired = spec.required !== false && spec.default === void 0;
|
|
95
|
+
if (raw === void 0 || raw === "") {
|
|
96
|
+
if (isRequired) {
|
|
97
|
+
const hint = spec.example ? ` (example: ${spec.example})` : "";
|
|
98
|
+
const desc = spec.description ? ` \u2014 ${spec.description}` : "";
|
|
99
|
+
errors.push(` \u2717 ${key}: required but missing${desc}${hint}`);
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
result[key] = coerce(key, raw, spec);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const errorMsg = e.message ?? "validation failed";
|
|
107
|
+
if (spec.secret) {
|
|
108
|
+
errors.push(` \u2717 ${key}: ${errorMsg}`);
|
|
109
|
+
} else {
|
|
110
|
+
errors.push(` \u2717 ${errorMsg} (got: "${raw}")`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (errors.length > 0) {
|
|
115
|
+
const msg = `
|
|
116
|
+
\u{1F6A8} env-safe: ${errors.length} environment variable error(s):
|
|
117
|
+
|
|
118
|
+
${errors.join("\n")}
|
|
119
|
+
|
|
120
|
+
Fix these in your .env file before starting.
|
|
121
|
+
`;
|
|
122
|
+
throw new Error(msg);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
function loadEnv(schema, opts) {
|
|
127
|
+
const { envFiles = [".env", ".env.local"], override = false } = opts ?? {};
|
|
128
|
+
const merged = /* @__PURE__ */ Object.create(null);
|
|
129
|
+
for (const file of envFiles) {
|
|
130
|
+
if (typeof file !== "string") continue;
|
|
131
|
+
const fullPath = path.resolve(process.cwd(), file);
|
|
132
|
+
const parsed = loadDotenvFile(fullPath);
|
|
133
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
134
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
135
|
+
if (override || !(k in merged)) merged[k] = v;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const combined = { ...merged };
|
|
139
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
140
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
141
|
+
if (override || !(k in merged)) combined[k] = v;
|
|
142
|
+
}
|
|
143
|
+
return parseEnv(schema, combined);
|
|
144
|
+
}
|
|
145
|
+
function generateExample(schema) {
|
|
146
|
+
const lines = ["# Generated by devguard env-safe", ""];
|
|
147
|
+
for (const [key, spec] of Object.entries(schema)) {
|
|
148
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
149
|
+
if (spec.description) lines.push(`# ${spec.description}`);
|
|
150
|
+
const hint = spec.example ?? (spec.enum ? spec.enum[0] : spec.type.toUpperCase());
|
|
151
|
+
const req = spec.required !== false && !spec.default ? " (required)" : spec.default ? ` (default: ${spec.default})` : "";
|
|
152
|
+
lines.push(`# Type: ${spec.type}${req}`);
|
|
153
|
+
lines.push(`${key}=${hint}`);
|
|
154
|
+
lines.push("");
|
|
155
|
+
}
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
var LEVEL_NUM = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
|
|
159
|
+
var LEVEL_COLOR = { trace: "\x1B[90m", debug: "\x1B[36m", info: "\x1B[32m", warn: "\x1B[33m", error: "\x1B[31m", fatal: "\x1B[35m" };
|
|
160
|
+
var RESET = "\x1B[0m";
|
|
161
|
+
var PROTECTED_FIELDS = /* @__PURE__ */ new Set(["level", "levelNum", "msg", "time", "pid", "hostname"]);
|
|
162
|
+
function redactValue(val) {
|
|
163
|
+
if (typeof val === "string" && val.length > 4) return val.slice(0, 2) + "****";
|
|
164
|
+
return "****";
|
|
165
|
+
}
|
|
166
|
+
function safeStringify(obj) {
|
|
167
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
168
|
+
try {
|
|
169
|
+
return JSON.stringify(obj, (_key, value) => {
|
|
170
|
+
if (typeof value === "object" && value !== null) {
|
|
171
|
+
if (seen.has(value)) return "[Circular]";
|
|
172
|
+
seen.add(value);
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "bigint") return value.toString();
|
|
175
|
+
return value;
|
|
176
|
+
});
|
|
177
|
+
} catch {
|
|
178
|
+
return '"[Unserializable]"';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
var OTLPExporter = class {
|
|
182
|
+
constructor(cfg) {
|
|
183
|
+
this.batch = [];
|
|
184
|
+
this._warnCount = 0;
|
|
185
|
+
this._isFlushing = false;
|
|
186
|
+
this.timer = null;
|
|
187
|
+
this.cfg = { headers: {}, batchSize: 100, flushIntervalMs: 5e3, ...cfg };
|
|
188
|
+
try {
|
|
189
|
+
new URL(this.cfg.endpoint);
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error(`Invalid OTLP endpoint URL: ${cfg.endpoint}`);
|
|
192
|
+
}
|
|
193
|
+
this.timer = setInterval(() => this.flush(), this.cfg.flushIntervalMs);
|
|
194
|
+
if (this.timer.unref) this.timer.unref();
|
|
195
|
+
}
|
|
196
|
+
push(record) {
|
|
197
|
+
this.batch.push(record);
|
|
198
|
+
if (this.batch.length >= this.cfg.batchSize) this.flush();
|
|
199
|
+
}
|
|
200
|
+
flush() {
|
|
201
|
+
if (this.batch.length === 0) return;
|
|
202
|
+
if (this._isFlushing) return;
|
|
203
|
+
this._isFlushing = true;
|
|
204
|
+
const records = this.batch.splice(0);
|
|
205
|
+
try {
|
|
206
|
+
const body = JSON.stringify({ resourceLogs: [{ resource: {}, scopeLogs: [{ logRecords: records.map((r) => ({
|
|
207
|
+
timeUnixNano: String(Date.parse(r.time) * 1e6),
|
|
208
|
+
severityNumber: r.levelNum,
|
|
209
|
+
severityText: r.level.toUpperCase(),
|
|
210
|
+
body: { stringValue: r.msg },
|
|
211
|
+
attributes: Object.entries(r).filter(([k]) => !PROTECTED_FIELDS.has(k)).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } }))
|
|
212
|
+
})) }] }] });
|
|
213
|
+
const url = new URL(this.cfg.endpoint);
|
|
214
|
+
const lib = url.protocol === "https:" ? https : http;
|
|
215
|
+
const req = lib.request({
|
|
216
|
+
hostname: url.hostname,
|
|
217
|
+
port: url.port,
|
|
218
|
+
path: url.pathname,
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Content-Type": "application/json", ...this.cfg.headers }
|
|
221
|
+
});
|
|
222
|
+
req.on("error", (e) => {
|
|
223
|
+
if (this._warnCount++ < 3) {
|
|
224
|
+
process.stderr.write(`[devguard/log-otlp] OTLP export failed: ${e.message} (further errors suppressed)
|
|
225
|
+
`);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
req.write(body);
|
|
229
|
+
req.end();
|
|
230
|
+
} finally {
|
|
231
|
+
this._isFlushing = false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
destroy() {
|
|
235
|
+
if (this.timer) clearInterval(this.timer);
|
|
236
|
+
this.timer = null;
|
|
237
|
+
this.flush();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var _hostname = os.hostname();
|
|
241
|
+
var Logger = class _Logger {
|
|
242
|
+
constructor(opts, bindings = {}) {
|
|
243
|
+
this.opts = opts;
|
|
244
|
+
this.minLevel = LEVEL_NUM[opts.level ?? "info"];
|
|
245
|
+
this.redactSet = new Set(opts.redact ?? []);
|
|
246
|
+
this.exporter = opts.otlp ? new OTLPExporter(opts.otlp) : null;
|
|
247
|
+
this.dest = opts.destination ?? process.stdout;
|
|
248
|
+
this.bindings = bindings;
|
|
249
|
+
}
|
|
250
|
+
log(level, msg, data) {
|
|
251
|
+
if (LEVEL_NUM[level] < this.minLevel) return;
|
|
252
|
+
if (this.opts.output === "silent") return;
|
|
253
|
+
let traceCtx = {};
|
|
254
|
+
try {
|
|
255
|
+
traceCtx = this.opts.traceContext?.() ?? {};
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
const record = {
|
|
259
|
+
level,
|
|
260
|
+
levelNum: LEVEL_NUM[level],
|
|
261
|
+
msg,
|
|
262
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
263
|
+
pid: process.pid,
|
|
264
|
+
hostname: _hostname,
|
|
265
|
+
...this.bindings,
|
|
266
|
+
...traceCtx,
|
|
267
|
+
...this.opts.service ? { service: this.opts.service } : {}
|
|
268
|
+
};
|
|
269
|
+
if (data) {
|
|
270
|
+
for (const [k, v] of Object.entries(data)) {
|
|
271
|
+
if (PROTECTED_FIELDS.has(k)) continue;
|
|
272
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
273
|
+
record[k] = this.redactSet.has(k) ? redactValue(v) : v;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const output = this.opts.output ?? (process.env.NODE_ENV === "development" || process.stdout.isTTY ? "pretty" : "json");
|
|
277
|
+
if (output === "pretty") {
|
|
278
|
+
const color = LEVEL_COLOR[level];
|
|
279
|
+
const ts = record.time.slice(11, 23);
|
|
280
|
+
const extras = Object.entries(record).filter(([k]) => !PROTECTED_FIELDS.has(k));
|
|
281
|
+
const extraStr = extras.length ? " " + extras.map(([k, v]) => `${k}=${safeStringify(v)}`).join(" ") : "";
|
|
282
|
+
this.dest.write(`${color}${ts} ${level.toUpperCase().padEnd(5)}${RESET} ${msg}${extraStr}
|
|
283
|
+
`);
|
|
284
|
+
} else {
|
|
285
|
+
this.dest.write(safeStringify(record) + "\n");
|
|
286
|
+
}
|
|
287
|
+
this.exporter?.push(record);
|
|
288
|
+
}
|
|
289
|
+
trace(msg, data) {
|
|
290
|
+
this.log("trace", msg, data);
|
|
291
|
+
}
|
|
292
|
+
debug(msg, data) {
|
|
293
|
+
this.log("debug", msg, data);
|
|
294
|
+
}
|
|
295
|
+
info(msg, data) {
|
|
296
|
+
this.log("info", msg, data);
|
|
297
|
+
}
|
|
298
|
+
warn(msg, data) {
|
|
299
|
+
this.log("warn", msg, data);
|
|
300
|
+
}
|
|
301
|
+
error(msg, data) {
|
|
302
|
+
this.log("error", msg, data);
|
|
303
|
+
}
|
|
304
|
+
fatal(msg, data) {
|
|
305
|
+
this.log("fatal", msg, data);
|
|
306
|
+
}
|
|
307
|
+
/** Create a child logger with additional bound fields */
|
|
308
|
+
child(bindings) {
|
|
309
|
+
return new _Logger(this.opts, { ...this.bindings, ...bindings });
|
|
310
|
+
}
|
|
311
|
+
/** Attach trace context for automatic injection */
|
|
312
|
+
withTrace(ctx) {
|
|
313
|
+
return new _Logger({ ...this.opts, traceContext: () => ctx }, this.bindings);
|
|
314
|
+
}
|
|
315
|
+
/** Flush OTLP buffer before process exit */
|
|
316
|
+
flush() {
|
|
317
|
+
this.exporter?.flush();
|
|
318
|
+
}
|
|
319
|
+
destroy() {
|
|
320
|
+
this.exporter?.destroy();
|
|
321
|
+
}
|
|
322
|
+
/** Middleware: log all HTTP requests */
|
|
323
|
+
requestMiddleware() {
|
|
324
|
+
const log = this;
|
|
325
|
+
return function logRequest(req, res, next) {
|
|
326
|
+
const start = Date.now();
|
|
327
|
+
res.on("finish", () => {
|
|
328
|
+
log.info("http request", {
|
|
329
|
+
method: req.method,
|
|
330
|
+
url: req.url,
|
|
331
|
+
status: res.statusCode,
|
|
332
|
+
durationMs: Date.now() - start,
|
|
333
|
+
ip: req.ip ?? req.connection?.remoteAddress,
|
|
334
|
+
userAgent: req.headers["user-agent"]
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
next();
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
function createLogger(opts = {}) {
|
|
342
|
+
return new Logger(opts);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export { Logger, createLogger, generateExample, loadDotenvFile, loadEnv, parseDotenv, parseEnv };
|