@macedon-technologies/batman 1.32.1 → 1.34.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/.jsii +2 -2
- package/lib/BatmanProject.js +1 -1
- package/lib/BootcampProject.js +1 -1
- package/lib/CodeOwnersFile.js +1 -1
- package/lib/DataDogAspectFile.js +5 -18
- package/lib/PrAutoMergeWorkflow.js +1 -1
- package/lib/ProductionDeployWorkflow.js +1 -1
- package/lib/SecretConstructFile.js +4 -54
- package/lib/SecretHandlerFiles.js +6 -173
- package/lib/SoftwareFactoryFiles.js +1 -1
- package/lib/StagingDeployWorkflow.js +1 -1
- package/lib/templates.d.ts +16 -0
- package/lib/templates.js +104 -0
- package/package.json +2 -1
- package/templates/app/architecture-rules/README.md +97 -0
- package/templates/app/architecture-rules/check.ts +218 -0
- package/templates/app/architecture-rules/rules.json +3 -0
- package/templates/app/architecture-rules/tsconfig.json +14 -0
- package/templates/app/openspec-gates/check-tasks-complete.ts +44 -0
- package/templates/app/openspec-gates/tsconfig.json +13 -0
- package/templates/app/package.json +74 -0
- package/templates/app/pnpm-lock.yaml +3888 -0
- package/templates/app/pnpm-workspace.yaml +7 -0
- package/templates/app/src/aspects/DataDogAspect.ts +14 -0
- package/templates/app/src/constructs/Secret.ts +50 -0
- package/templates/app/src/constructs/handler/Secret.Function.ts +38 -0
- package/templates/app/src/constructs/handler/secret/client.ts +5 -0
- package/templates/app/src/constructs/handler/secret/createSecretValue.ts +30 -0
- package/templates/app/src/constructs/handler/secret/didRandomPropertiesChange.ts +13 -0
- package/templates/app/src/constructs/handler/secret/updateSecretValue.ts +71 -0
- package/templates/app/swamp-bootstrap/pull-extensions.ts +68 -0
- package/templates/app/swamp-bootstrap/tsconfig.json +13 -0
- package/templates/app/test/DataDogAspect.test.ts +31 -0
- package/templates/app/test/Secret.test.ts +40 -0
- package/templates/app/test/didRandomPropertiesChange.test.ts +19 -0
- package/templates/app/test/tsconfig.json +13 -0
- package/templates/app/tsconfig.json +50 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Architecture rules
|
|
2
|
+
|
|
3
|
+
A data-driven registry of architectural constraints, enforced by
|
|
4
|
+
`pnpm validate:architecture` and run as part of the pre-PR verification
|
|
5
|
+
workflow (`workflows/workflow-pre-pr-verify.yaml`) before the swamp factory
|
|
6
|
+
opens a PR. New rules are new entries in `rules.json`, not new code — the
|
|
7
|
+
engine (`check.ts`) only needs a new case in `evaluateRule()` when a rule
|
|
8
|
+
needs a genuinely new kind of check.
|
|
9
|
+
|
|
10
|
+
## Rule shape
|
|
11
|
+
|
|
12
|
+
Two `check.type`s are implemented: `forbidden-import` (whole-repo import-graph
|
|
13
|
+
check) and `required-companion` (diff-based co-change check).
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"id": "unique-id",
|
|
18
|
+
"description": "What this enforces and why.",
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"severity": "low" | "medium" | "high" | "critical",
|
|
21
|
+
"check": {
|
|
22
|
+
"type": "forbidden-import",
|
|
23
|
+
"from": ["glob patterns selecting files this rule inspects"],
|
|
24
|
+
"forbid": ["glob patterns those files may not import from"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"id": "unique-id",
|
|
32
|
+
"description": "What this enforces and why.",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"severity": "low" | "medium" | "high" | "critical",
|
|
35
|
+
"check": {
|
|
36
|
+
"type": "required-companion",
|
|
37
|
+
"from": ["glob patterns selecting changed files that trigger the requirement"],
|
|
38
|
+
"requireOneOf": ["glob patterns -- at least one CHANGED file must match one of these"],
|
|
39
|
+
"exempt": ["optional -- from-files matching these globs don't trigger the requirement"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`required-companion` evaluates the set of files changed in the current diff
|
|
45
|
+
(`git diff --name-only --diff-filter=ACMR <baseRef>...HEAD`), not the whole
|
|
46
|
+
repo tree: if any changed file matches `from` (and doesn't match `exempt`) but
|
|
47
|
+
no changed file matches `requireOneOf`, that's a violation. The base ref
|
|
48
|
+
defaults to `origin/main` and can be overridden with the
|
|
49
|
+
`ARCHITECTURE_RULES_BASE_REF` environment variable. This means the checkout
|
|
50
|
+
running `pnpm validate:architecture` must have that base ref fetched and
|
|
51
|
+
resolvable (a shallow, single-branch checkout will make the check fail closed
|
|
52
|
+
with an error rather than silently reporting no violations) — make sure
|
|
53
|
+
whatever runs this task fetches enough history for `git diff <baseRef>...HEAD`
|
|
54
|
+
to succeed.
|
|
55
|
+
|
|
56
|
+
`from`/`requireOneOf`/`exempt` globs are matched against the paths `git
|
|
57
|
+
diff --name-only` reports, which are relative to the git repository's
|
|
58
|
+
top-level directory. This lines up with `from`'s own directory (the parent of
|
|
59
|
+
`architecture-rules/`) in the common case — a single project generated at the
|
|
60
|
+
repo root — but if `architecture-rules/` instead lives under a nested
|
|
61
|
+
subdirectory of a larger git repo, write globs relative to the git root, not
|
|
62
|
+
to that subdirectory.
|
|
63
|
+
|
|
64
|
+
**`required-companion` is a coverage check, not a correctness check.** It only
|
|
65
|
+
confirms that *some* changed file matches `requireOneOf` — it can't tell
|
|
66
|
+
whether that change is actually related to the triggering `from` change (a
|
|
67
|
+
trivial or unrelated edit to a file under `requireOneOf` satisfies the rule
|
|
68
|
+
just as well as a substantive one), and it can't tell whether a given `from`
|
|
69
|
+
change genuinely needed a companion at all (a pure refactor might not).
|
|
70
|
+
`exempt` covers *known, glob-expressible* exceptions, but not every false
|
|
71
|
+
positive is expressible as a glob — that judgment call still belongs to the
|
|
72
|
+
rule author (scoping `from`/`exempt`) and the reviewer (reading the companion
|
|
73
|
+
diff, not just trusting that the check passed). Also note that `main()`
|
|
74
|
+
fails the whole run on *any* violation regardless of `severity` — `severity`
|
|
75
|
+
is informational only, not a leniency lever — so start with a narrowly
|
|
76
|
+
scoped `from` (or `enabled: false`) and widen it as the convention proves
|
|
77
|
+
itself, the same guidance as for `forbidden-import`.
|
|
78
|
+
|
|
79
|
+
`enabled: false` rules are skipped (reported as "deferred") rather than
|
|
80
|
+
evaluated — use this for a rule you've named and described but don't yet
|
|
81
|
+
have a concrete convention to check.
|
|
82
|
+
|
|
83
|
+
## Current rules
|
|
84
|
+
|
|
85
|
+
None yet — `rules.json` ships empty. Add rules as your domain module
|
|
86
|
+
conventions solidify (e.g. an event-sourced/CQRS module layout under
|
|
87
|
+
`src/domains/<name>/{commands,events,aggregates,projections}/`).
|
|
88
|
+
|
|
89
|
+
## Adding a check type
|
|
90
|
+
|
|
91
|
+
`forbidden-import` (whole-repo import graph) and `required-companion`
|
|
92
|
+
(diff-based co-change) are the implemented `check.type`s. If a future rule
|
|
93
|
+
needs a data source neither provides (e.g. "every aggregate file must have a
|
|
94
|
+
matching events file", which is a repo-wide naming convention rather than
|
|
95
|
+
either an import-graph or a diff check), add a new `type` value, a matching
|
|
96
|
+
interface, and a case in `evaluateRule()` in `check.ts` — keep the engine
|
|
97
|
+
itself generic; rule-specific knowledge belongs in `rules.json`.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic architecture-rule engine. Rules live entirely in rules.json — this
|
|
3
|
+
* file only knows how to evaluate the check "types" that rules can declare.
|
|
4
|
+
* Add a new rule by adding a rules.json entry; add a new check type by adding
|
|
5
|
+
* a case in evaluateRule().
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import rulesFile from './rules.json';
|
|
11
|
+
|
|
12
|
+
interface ForbiddenImportCheck {
|
|
13
|
+
type: 'forbidden-import';
|
|
14
|
+
// Glob patterns (relative to repo root) selecting the files this rule inspects.
|
|
15
|
+
from: string[];
|
|
16
|
+
// Glob patterns (relative to repo root) that "from" files may not import from.
|
|
17
|
+
forbid: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RequiredCompanionCheck {
|
|
21
|
+
type: 'required-companion';
|
|
22
|
+
// Glob patterns selecting changed files that trigger the requirement.
|
|
23
|
+
from: string[];
|
|
24
|
+
// Glob patterns -- at least one CHANGED file must match one of these.
|
|
25
|
+
requireOneOf: string[];
|
|
26
|
+
// Optional -- 'from' files matching these globs don't trigger the requirement.
|
|
27
|
+
exempt?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Rule {
|
|
31
|
+
id: string;
|
|
32
|
+
description: string;
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
35
|
+
check: ForbiddenImportCheck | RequiredCompanionCheck;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Violation {
|
|
39
|
+
ruleId: string;
|
|
40
|
+
severity: string;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
45
|
+
const IGNORED_DIRS = new Set(['node_modules', '.git', '.swamp', 'cdk.out', 'lib', 'build', 'dist']);
|
|
46
|
+
const BASE_REF = process.env.ARCHITECTURE_RULES_BASE_REF || 'origin/main';
|
|
47
|
+
|
|
48
|
+
// Minimal glob -> RegExp: supports "*", "**", "**/" and literal path segments.
|
|
49
|
+
// No new dependency for this — the only pattern shapes rules.json ever needs
|
|
50
|
+
// are directory-prefix globs like "src/domains/*/commands/**".
|
|
51
|
+
function globToRegExp(glob: string): RegExp {
|
|
52
|
+
let out = '';
|
|
53
|
+
for (let i = 0; i < glob.length; i++) {
|
|
54
|
+
const c = glob[i];
|
|
55
|
+
if (c === '*' && glob[i + 1] === '*') {
|
|
56
|
+
i++;
|
|
57
|
+
if (glob[i + 1] === '/') {
|
|
58
|
+
out += '(?:.*/)?';
|
|
59
|
+
i++;
|
|
60
|
+
} else {
|
|
61
|
+
out += '.*';
|
|
62
|
+
}
|
|
63
|
+
} else if (c === '*') {
|
|
64
|
+
out += '[^/]*';
|
|
65
|
+
} else if (c === '?') {
|
|
66
|
+
out += '[^/]';
|
|
67
|
+
} else if ('.+^${}()|[]\\'.includes(c)) {
|
|
68
|
+
out += `\\${c}`;
|
|
69
|
+
} else {
|
|
70
|
+
out += c;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return new RegExp(`^${out}$`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function matchesAny(relFile: string, globs: string[]): boolean {
|
|
77
|
+
return globs.some((glob) => globToRegExp(glob).test(relFile));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toRepoRelative(absPath: string): string {
|
|
81
|
+
return path.relative(REPO_ROOT, absPath).split(path.sep).join('/');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Files changed relative to baseRef, sourced from git rather than the whole-tree
|
|
85
|
+
// walk() below -- required-companion checks care about what a PR touched, not
|
|
86
|
+
// the full repo. Throws (rather than returning []) if baseRef cannot be resolved,
|
|
87
|
+
// so an unfetched base ref fails the check instead of silently reporting no violations.
|
|
88
|
+
function getChangedFiles(baseRef: string): string[] {
|
|
89
|
+
let output: string;
|
|
90
|
+
try {
|
|
91
|
+
output = execFileSync('git', ['diff', '--name-only', '--diff-filter=ACMR', `${baseRef}...HEAD`], {
|
|
92
|
+
cwd: REPO_ROOT,
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`architecture-rules: failed to compute the changed-file set against base ref "${baseRef}" -- ` +
|
|
98
|
+
`is it fetched and resolvable in this checkout? (${(err as Error).message})`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return output.split('\n').map((line) => line.trim()).filter((line) => line.length > 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function walk(dir: string, acc: string[] = []): string[] {
|
|
105
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
106
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
107
|
+
const full = path.join(dir, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
walk(full, acc);
|
|
110
|
+
} else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
111
|
+
acc.push(full);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return acc;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const RELATIVE_IMPORT_RE = /(?:import|export)\s+(?:[^'";]*?\bfrom\s+)?['"](\.[^'"]+)['"]/g;
|
|
118
|
+
|
|
119
|
+
function findRelativeImports(source: string): string[] {
|
|
120
|
+
const specs: string[] = [];
|
|
121
|
+
RELATIVE_IMPORT_RE.lastIndex = 0;
|
|
122
|
+
let match: RegExpExecArray | null;
|
|
123
|
+
while ((match = RELATIVE_IMPORT_RE.exec(source))) {
|
|
124
|
+
specs.push(match[1]);
|
|
125
|
+
}
|
|
126
|
+
return specs;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// An import spec resolves to a module, not a specific file extension, so a
|
|
130
|
+
// forbid glob is checked against the bare resolved path plus its .ts/index.ts
|
|
131
|
+
// variants.
|
|
132
|
+
function resolvedCandidates(fromFile: string, spec: string): string[] {
|
|
133
|
+
const resolved = toRepoRelative(path.normalize(path.join(path.dirname(fromFile), spec)));
|
|
134
|
+
return [resolved, `${resolved}.ts`, `${resolved}/index.ts`];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function evaluateForbiddenImport(rule: Rule): Violation[] {
|
|
138
|
+
const { from, forbid } = rule.check as ForbiddenImportCheck;
|
|
139
|
+
if (from.length === 0 || forbid.length === 0) return [];
|
|
140
|
+
|
|
141
|
+
const violations: Violation[] = [];
|
|
142
|
+
for (const file of walk(REPO_ROOT)) {
|
|
143
|
+
const relFile = toRepoRelative(file);
|
|
144
|
+
if (!matchesAny(relFile, from)) continue;
|
|
145
|
+
|
|
146
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
147
|
+
for (const spec of findRelativeImports(source)) {
|
|
148
|
+
const candidates = resolvedCandidates(file, spec);
|
|
149
|
+
if (candidates.some((candidate) => matchesAny(candidate, forbid))) {
|
|
150
|
+
violations.push({
|
|
151
|
+
ruleId: rule.id,
|
|
152
|
+
severity: rule.severity,
|
|
153
|
+
message: `${relFile} imports "${spec}", which resolves under a path forbidden by rule "${rule.id}"`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return violations;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function evaluateRequiredCompanion(rule: Rule): Violation[] {
|
|
162
|
+
const { from, requireOneOf, exempt } = rule.check as RequiredCompanionCheck;
|
|
163
|
+
if (from.length === 0 || requireOneOf.length === 0) return [];
|
|
164
|
+
|
|
165
|
+
const changedFiles = getChangedFiles(BASE_REF);
|
|
166
|
+
const triggering = changedFiles.filter(
|
|
167
|
+
(file) => matchesAny(file, from) && !(exempt && matchesAny(file, exempt)),
|
|
168
|
+
);
|
|
169
|
+
if (triggering.length === 0) return [];
|
|
170
|
+
|
|
171
|
+
const satisfied = changedFiles.some((file) => matchesAny(file, requireOneOf));
|
|
172
|
+
if (satisfied) return [];
|
|
173
|
+
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
ruleId: rule.id,
|
|
177
|
+
severity: rule.severity,
|
|
178
|
+
message: `changed file(s) ${triggering.join(', ')} match rule "${rule.id}"'s "from" patterns, but no changed file matches its "requireOneOf" patterns`,
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function evaluateRule(rule: Rule): Violation[] {
|
|
184
|
+
switch (rule.check.type) {
|
|
185
|
+
case 'forbidden-import':
|
|
186
|
+
return evaluateForbiddenImport(rule);
|
|
187
|
+
case 'required-companion':
|
|
188
|
+
return evaluateRequiredCompanion(rule);
|
|
189
|
+
default:
|
|
190
|
+
throw new Error(`architecture-rules: unknown check.type "${(rule.check as { type: string }).type}" on rule "${rule.id}"`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function main(): number {
|
|
195
|
+
const rules = (rulesFile as { rules: Rule[] }).rules;
|
|
196
|
+
let evaluated = 0;
|
|
197
|
+
let deferred = 0;
|
|
198
|
+
const violations: Violation[] = [];
|
|
199
|
+
|
|
200
|
+
for (const rule of rules) {
|
|
201
|
+
if (!rule.enabled) {
|
|
202
|
+
deferred++;
|
|
203
|
+
console.log(`[deferred] ${rule.id}: ${rule.description}`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
evaluated++;
|
|
207
|
+
violations.push(...evaluateRule(rule));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`\narchitecture-rules: ${evaluated} rule(s) evaluated, ${deferred} deferred, ${violations.length} violation(s)`);
|
|
211
|
+
for (const violation of violations) {
|
|
212
|
+
console.error(` [${violation.severity}] ${violation.ruleId}: ${violation.message}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return violations.length > 0 ? 1 : 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
process.exit(main());
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate: fails unless every task in the named OpenSpec change's tasks.md is
|
|
3
|
+
* checked off. `openspec validate`/`openspec status` don't check this (an
|
|
4
|
+
* in-progress change is a normal, valid state) — this is the deterministic
|
|
5
|
+
* check the pre-pr pipeline needs before letting implementation proceed.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
interface OpenSpecChangeSummary {
|
|
10
|
+
name: string;
|
|
11
|
+
completedTasks: number;
|
|
12
|
+
totalTasks: number;
|
|
13
|
+
status: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function main(): number {
|
|
17
|
+
// pnpm's `-- <args>` forwarding (and projen's receiveArgs) can pass a
|
|
18
|
+
// literal "--" through as an argv token ahead of the real argument.
|
|
19
|
+
const slug = process.argv.slice(2).find((arg) => arg !== '--');
|
|
20
|
+
if (!slug) {
|
|
21
|
+
console.error('Usage: check-tasks-complete <change-slug>');
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const raw = execFileSync('openspec', ['list', '--json'], { encoding: 'utf8', shell: true });
|
|
26
|
+
const { changes } = JSON.parse(raw) as { changes: OpenSpecChangeSummary[] };
|
|
27
|
+
const change = changes.find((c) => c.name === slug);
|
|
28
|
+
|
|
29
|
+
if (!change) {
|
|
30
|
+
console.error(`OpenSpec change "${slug}" not found (openspec list --json)`);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
if (change.status !== 'complete') {
|
|
34
|
+
console.error(
|
|
35
|
+
`OpenSpec change "${slug}" has incomplete tasks: ${change.completedTasks}/${change.totalTasks}`,
|
|
36
|
+
);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`OpenSpec change "${slug}": all ${change.totalTasks} tasks complete`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.exit(main());
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@macedon-technologies/batman-template-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"description": "The files Batman copies into a generated project, as a working, tested app.",
|
|
5
|
+
"packageManager": "pnpm@10.33.0",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "tsc --noEmit -p tsconfig.json && tsc -p test/tsconfig.json && tsc -p architecture-rules && tsc -p openspec-gates && tsc -p swamp-bootstrap && jest"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@aws-sdk/client-secrets-manager": "*",
|
|
11
|
+
"@types/aws-lambda": "*",
|
|
12
|
+
"@types/express": "*",
|
|
13
|
+
"@types/pg": "*",
|
|
14
|
+
"aws-cdk-lib": "^2.224.0",
|
|
15
|
+
"constructs": "^10.5.1",
|
|
16
|
+
"datadog-cdk-constructs-v2": "*"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/jest": "*",
|
|
20
|
+
"@types/jsonwebtoken": "*",
|
|
21
|
+
"@types/node": "^22",
|
|
22
|
+
"esbuild": "*",
|
|
23
|
+
"jest": "*",
|
|
24
|
+
"jest-junit": "^17",
|
|
25
|
+
"ts-jest": "*",
|
|
26
|
+
"ts-node": "*",
|
|
27
|
+
"typescript": "*"
|
|
28
|
+
},
|
|
29
|
+
"jest": {
|
|
30
|
+
"coverageProvider": "v8",
|
|
31
|
+
"testMatch": [
|
|
32
|
+
"<rootDir>/@(src|test)/**/*(*.)@(spec|test).ts?(x)",
|
|
33
|
+
"<rootDir>/@(src|test)/**/__tests__/**/*.ts?(x)",
|
|
34
|
+
"<rootDir>/@(projenrc)/**/*(*.)@(spec|test).ts?(x)",
|
|
35
|
+
"<rootDir>/@(projenrc)/**/__tests__/**/*.ts?(x)"
|
|
36
|
+
],
|
|
37
|
+
"clearMocks": true,
|
|
38
|
+
"collectCoverage": true,
|
|
39
|
+
"coverageReporters": [
|
|
40
|
+
"json",
|
|
41
|
+
"lcov",
|
|
42
|
+
"clover",
|
|
43
|
+
"cobertura",
|
|
44
|
+
"text"
|
|
45
|
+
],
|
|
46
|
+
"coverageDirectory": "coverage",
|
|
47
|
+
"coveragePathIgnorePatterns": [
|
|
48
|
+
"/node_modules/"
|
|
49
|
+
],
|
|
50
|
+
"testPathIgnorePatterns": [
|
|
51
|
+
"/node_modules/"
|
|
52
|
+
],
|
|
53
|
+
"watchPathIgnorePatterns": [
|
|
54
|
+
"/node_modules/"
|
|
55
|
+
],
|
|
56
|
+
"reporters": [
|
|
57
|
+
"default",
|
|
58
|
+
[
|
|
59
|
+
"jest-junit",
|
|
60
|
+
{
|
|
61
|
+
"outputDirectory": "test-reports"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
],
|
|
65
|
+
"transform": {
|
|
66
|
+
"^.+\\.[t]sx?$": [
|
|
67
|
+
"ts-jest",
|
|
68
|
+
{
|
|
69
|
+
"tsconfig": "test/tsconfig.json"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|