@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.
@@ -0,0 +1,366 @@
1
+ // src/dx/api-contract.ts
2
+ var Schema = class {
3
+ constructor() {
4
+ /** @internal */
5
+ this._isOptional = false;
6
+ }
7
+ safeParse(val) {
8
+ try {
9
+ let result = this._parse(val === void 0 ? this._default : val, "");
10
+ if (this._transform) result = this._transform(result);
11
+ return { success: true, data: result, errors: [] };
12
+ } catch (e) {
13
+ return { success: false, errors: parseErrors(e) };
14
+ }
15
+ }
16
+ parse(val) {
17
+ const r = this.safeParse(val);
18
+ if (!r.success) throw new Error(r.errors.map((e) => (e.path ? `${e.path}: ` : "") + e.message).join("; "));
19
+ return r.data;
20
+ }
21
+ optional() {
22
+ return new OptionalSchema(this);
23
+ }
24
+ nullable() {
25
+ return new NullableSchema(this);
26
+ }
27
+ default(val) {
28
+ const c2 = Object.create(this);
29
+ c2._default = val;
30
+ return c2;
31
+ }
32
+ describe(desc) {
33
+ const c2 = Object.create(this);
34
+ c2._description = desc;
35
+ return c2;
36
+ }
37
+ transform(fn) {
38
+ const c2 = Object.create(this);
39
+ c2._transform = fn;
40
+ return c2;
41
+ }
42
+ toJSONSchema() {
43
+ return {};
44
+ }
45
+ };
46
+ function parseErrors(e) {
47
+ if (e instanceof ValidationException) return e.errors;
48
+ return [{ path: "", message: e?.message ?? "Unknown error" }];
49
+ }
50
+ var ValidationException = class extends Error {
51
+ constructor(errors) {
52
+ super(errors.map((e) => e.message).join("; "));
53
+ this.errors = errors;
54
+ }
55
+ };
56
+ function fail(path, message) {
57
+ throw new ValidationException([{ path, message }]);
58
+ }
59
+ var OptionalSchema = class extends Schema {
60
+ constructor(inner) {
61
+ super();
62
+ this.inner = inner;
63
+ }
64
+ _parse(val, path) {
65
+ if (val === void 0 || val === null) return void 0;
66
+ return this.inner._parse(val, path);
67
+ }
68
+ };
69
+ var NullableSchema = class extends Schema {
70
+ constructor(inner) {
71
+ super();
72
+ this.inner = inner;
73
+ }
74
+ _parse(val, path) {
75
+ if (val === null || val === void 0) return null;
76
+ return this.inner._parse(val, path);
77
+ }
78
+ };
79
+ var StringSchema = class extends Schema {
80
+ constructor() {
81
+ super(...arguments);
82
+ this._email = false;
83
+ this._url = false;
84
+ this._trim = false;
85
+ }
86
+ _parse(val, path) {
87
+ if (typeof val !== "string") fail(path, `Expected string, got ${typeof val}`);
88
+ let s = val;
89
+ if (this._trim) s = s.trim();
90
+ if (this._min !== void 0 && s.length < this._min) fail(path, `Too short (min ${this._min})`);
91
+ if (this._max !== void 0 && s.length > this._max) fail(path, `Too long (max ${this._max})`);
92
+ if (this._enum && !this._enum.includes(s)) fail(path, `Must be one of: ${this._enum.join(", ")}`);
93
+ if (this._regex && !this._regex.test(s)) fail(path, `Does not match ${this._regex}`);
94
+ if (this._email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) fail(path, "Invalid email address");
95
+ if (this._url) {
96
+ try {
97
+ new URL(s);
98
+ } catch {
99
+ fail(path, "Invalid URL");
100
+ }
101
+ }
102
+ return s;
103
+ }
104
+ min(n) {
105
+ const c2 = Object.create(this);
106
+ c2._min = n;
107
+ return c2;
108
+ }
109
+ max(n) {
110
+ const c2 = Object.create(this);
111
+ c2._max = n;
112
+ return c2;
113
+ }
114
+ length(n) {
115
+ return this.min(n).max(n);
116
+ }
117
+ enum(vals) {
118
+ const c2 = Object.create(this);
119
+ c2._enum = vals;
120
+ return c2;
121
+ }
122
+ regex(r) {
123
+ const c2 = Object.create(this);
124
+ c2._regex = r;
125
+ return c2;
126
+ }
127
+ email() {
128
+ const c2 = Object.create(this);
129
+ c2._email = true;
130
+ return c2;
131
+ }
132
+ url() {
133
+ const c2 = Object.create(this);
134
+ c2._url = true;
135
+ return c2;
136
+ }
137
+ trim() {
138
+ const c2 = Object.create(this);
139
+ c2._trim = true;
140
+ return c2;
141
+ }
142
+ toJSONSchema() {
143
+ return { type: "string", ...this._min ? { minLength: this._min } : {}, ...this._max ? { maxLength: this._max } : {}, ...this._enum ? { enum: this._enum } : {}, ...this._email ? { format: "email" } : {}, ...this._url ? { format: "uri" } : {} };
144
+ }
145
+ };
146
+ var NumberSchema = class extends Schema {
147
+ constructor() {
148
+ super(...arguments);
149
+ this._int = false;
150
+ this._positive = false;
151
+ }
152
+ _parse(val, path) {
153
+ const n = typeof val === "string" ? parseFloat(val) : val;
154
+ if (typeof n !== "number" || isNaN(n)) fail(path, `Expected number, got ${typeof val}`);
155
+ if (this._int && !Number.isInteger(n)) fail(path, "Expected integer");
156
+ if (this._positive && n <= 0) fail(path, "Must be positive");
157
+ if (this._min !== void 0 && n < this._min) fail(path, `Too small (min ${this._min})`);
158
+ if (this._max !== void 0 && n > this._max) fail(path, `Too large (max ${this._max})`);
159
+ return n;
160
+ }
161
+ min(n) {
162
+ const c2 = Object.create(this);
163
+ c2._min = n;
164
+ return c2;
165
+ }
166
+ max(n) {
167
+ const c2 = Object.create(this);
168
+ c2._max = n;
169
+ return c2;
170
+ }
171
+ int() {
172
+ const c2 = Object.create(this);
173
+ c2._int = true;
174
+ return c2;
175
+ }
176
+ positive() {
177
+ const c2 = Object.create(this);
178
+ c2._positive = true;
179
+ return c2;
180
+ }
181
+ toJSONSchema() {
182
+ return { type: this._int ? "integer" : "number", ...this._min !== void 0 ? { minimum: this._min } : {}, ...this._max !== void 0 ? { maximum: this._max } : {} };
183
+ }
184
+ };
185
+ var BooleanSchema = class extends Schema {
186
+ _parse(val, path) {
187
+ if (val === "true" || val === 1 || val === "1") return true;
188
+ if (val === "false" || val === 0 || val === "0") return false;
189
+ if (typeof val !== "boolean") fail(path, `Expected boolean, got ${typeof val}`);
190
+ return val;
191
+ }
192
+ toJSONSchema() {
193
+ return { type: "boolean" };
194
+ }
195
+ };
196
+ var ObjectSchema = class _ObjectSchema extends Schema {
197
+ constructor(shape, isStrict = false) {
198
+ super();
199
+ this.shape = shape;
200
+ this._isStrict = isStrict;
201
+ }
202
+ _parse(val, path) {
203
+ if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path, `Expected object`);
204
+ const obj = val;
205
+ const result = {};
206
+ const errors = [];
207
+ for (const [key, schema] of Object.entries(this.shape)) {
208
+ try {
209
+ result[key] = schema._parse(obj[key], path ? `${path}.${key}` : key);
210
+ } catch (e) {
211
+ errors.push(...parseErrors(e));
212
+ }
213
+ }
214
+ if (this._isStrict) {
215
+ const knownKeys = new Set(Object.keys(this.shape));
216
+ for (const k of Object.keys(obj)) if (!knownKeys.has(k)) errors.push({ path: path ? `${path}.${k}` : k, message: "Unknown field" });
217
+ }
218
+ if (errors.length) throw new ValidationException(errors);
219
+ return result;
220
+ }
221
+ extend(extra) {
222
+ return new _ObjectSchema({ ...this.shape, ...extra });
223
+ }
224
+ pick(keys) {
225
+ const s = {};
226
+ keys.forEach((k) => s[k] = this.shape[k]);
227
+ return new _ObjectSchema(s);
228
+ }
229
+ omit(keys) {
230
+ const s = { ...this.shape };
231
+ keys.forEach((k) => delete s[k]);
232
+ return new _ObjectSchema(s);
233
+ }
234
+ strict() {
235
+ return new _ObjectSchema(this.shape, true);
236
+ }
237
+ toJSONSchema() {
238
+ const props = {};
239
+ const required = [];
240
+ for (const [k, v] of Object.entries(this.shape)) {
241
+ props[k] = v.toJSONSchema?.() ?? {};
242
+ if (!(v instanceof OptionalSchema) && !(v instanceof NullableSchema)) required.push(k);
243
+ }
244
+ return { type: "object", properties: props, ...required.length ? { required } : {} };
245
+ }
246
+ };
247
+ var ArraySchema = class extends Schema {
248
+ constructor(item) {
249
+ super();
250
+ this.item = item;
251
+ }
252
+ _parse(val, path) {
253
+ if (!Array.isArray(val)) fail(path, `Expected array`);
254
+ if (this._min !== void 0 && val.length < this._min) fail(path, `Too few items (min ${this._min})`);
255
+ if (this._max !== void 0 && val.length > this._max) fail(path, `Too many items (max ${this._max})`);
256
+ const errors = [];
257
+ const result = [];
258
+ val.forEach((item, i) => {
259
+ try {
260
+ result.push(this.item._parse(item, `${path}[${i}]`));
261
+ } catch (e) {
262
+ errors.push(...parseErrors(e));
263
+ }
264
+ });
265
+ if (errors.length) throw new ValidationException(errors);
266
+ return result;
267
+ }
268
+ min(n) {
269
+ const c2 = Object.create(this);
270
+ c2._min = n;
271
+ return c2;
272
+ }
273
+ max(n) {
274
+ const c2 = Object.create(this);
275
+ c2._max = n;
276
+ return c2;
277
+ }
278
+ nonempty() {
279
+ return this.min(1);
280
+ }
281
+ toJSONSchema() {
282
+ return { type: "array", items: this.item.toJSONSchema?.() ?? {} };
283
+ }
284
+ };
285
+ var RecordSchema = class extends Schema {
286
+ constructor(valueSchema) {
287
+ super();
288
+ this.valueSchema = valueSchema;
289
+ }
290
+ _parse(val, path) {
291
+ if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path, "Expected object");
292
+ const result = {};
293
+ for (const [k, v] of Object.entries(val)) {
294
+ if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
295
+ result[k] = this.valueSchema._parse(v, `${path}.${k}`);
296
+ }
297
+ return result;
298
+ }
299
+ };
300
+ var LiteralSchema = class extends Schema {
301
+ constructor(literal) {
302
+ super();
303
+ this.literal = literal;
304
+ }
305
+ _parse(val, path) {
306
+ if (val !== this.literal) fail(path, `Expected ${JSON.stringify(this.literal)}, got ${JSON.stringify(val)}`);
307
+ return val;
308
+ }
309
+ };
310
+ var UnionSchema = class extends Schema {
311
+ constructor(options) {
312
+ super();
313
+ this.options = options;
314
+ }
315
+ _parse(val, path) {
316
+ for (const opt of this.options) {
317
+ try {
318
+ return opt._parse(val, path);
319
+ } catch {
320
+ continue;
321
+ }
322
+ }
323
+ fail(path, `Value does not match any union variant`);
324
+ }
325
+ };
326
+ var c = {
327
+ string: () => new StringSchema(),
328
+ number: () => new NumberSchema(),
329
+ boolean: () => new BooleanSchema(),
330
+ object: (shape) => new ObjectSchema(shape),
331
+ array: (item) => new ArraySchema(item),
332
+ record: (val) => new RecordSchema(val),
333
+ literal: (v) => new LiteralSchema(v),
334
+ union: (opts) => new UnionSchema(opts),
335
+ any: () => new class extends Schema {
336
+ _parse(v) {
337
+ return v;
338
+ }
339
+ }()
340
+ };
341
+ function validateBody(schema) {
342
+ return function(req, res, next) {
343
+ const result = schema.safeParse(req.body);
344
+ if (!result.success) return res.status(400).json({ error: "Validation failed", details: result.errors });
345
+ req.body = result.data;
346
+ next();
347
+ };
348
+ }
349
+ function validateQuery(schema) {
350
+ return function(req, res, next) {
351
+ const result = schema.safeParse(req.query);
352
+ if (!result.success) return res.status(400).json({ error: "Invalid query parameters", details: result.errors });
353
+ req.query = result.data;
354
+ next();
355
+ };
356
+ }
357
+ async function typedFetch(url, schema, init) {
358
+ if (typeof fetch === "undefined") throw new Error("[devguard] typedFetch requires Node.js >= 18 or a global fetch polyfill. See: https://nodejs.org/en/blog/announcements/v18-release-announce");
359
+ const response = await fetch(url, init);
360
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
361
+ const json = await response.json();
362
+ const data = schema.parse(json);
363
+ return { data, response };
364
+ }
365
+
366
+ export { Schema, c, typedFetch, validateBody, validateQuery };
@@ -0,0 +1,35 @@
1
+ import { verifyLockfile, scanProject, enforceExactPins, checkTokenAge, loadTokensFromEnv } from './chunk-D7GNA6TS.js';
2
+ import * as path from 'path';
3
+
4
+ async function runAllChecks(root = process.cwd()) {
5
+ const resolvedRoot = path.resolve(root);
6
+ const [lockfile, hookFindings, unpinned] = await Promise.all([
7
+ Promise.resolve(verifyLockfile(resolvedRoot)),
8
+ Promise.resolve(scanProject(resolvedRoot)),
9
+ Promise.resolve(enforceExactPins(path.join(resolvedRoot, "package.json")))
10
+ ]);
11
+ const tokenNames = (process.env.DEVGUARD_TOKENS ?? "NPM_TOKEN,GITHUB_TOKEN").split(",").map((s) => s.trim()).filter(Boolean);
12
+ const tokenAlerts = checkTokenAge(loadTokensFromEnv(tokenNames));
13
+ const stale = tokenAlerts.filter((a) => a.status === "stale").map((a) => a.name);
14
+ const expiringSoon = tokenAlerts.filter((a) => a.status === "expiring_soon").map((a) => a.name);
15
+ const criticals = hookFindings.filter((h) => h.severity === "critical").length;
16
+ const passedAll = lockfile.valid && hookFindings.length === 0 && unpinned.length === 0 && stale.length === 0;
17
+ let score = 100;
18
+ if (!lockfile.valid) score -= 30;
19
+ score -= Math.min(40, criticals * 15 + (hookFindings.length - criticals) * 5);
20
+ score -= Math.min(15, unpinned.length * 2);
21
+ score -= stale.length * 10;
22
+ score -= expiringSoon.length * 3;
23
+ score = Math.max(0, score);
24
+ return {
25
+ lockfile: { valid: lockfile.valid, tampered: lockfile.tampered, missing: lockfile.missing },
26
+ hooks: { count: hookFindings.length, criticals, findings: hookFindings },
27
+ pins: { unpinned: unpinned.map((v) => v.name + "@" + v.specifier), autoFixAvailable: true },
28
+ tokens: { stale, expiringSoon },
29
+ passedAll,
30
+ score,
31
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString()
32
+ };
33
+ }
34
+
35
+ export { runAllChecks };