declapract-typescript-ehmpathy 0.49.14 → 0.49.15
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/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/.test/assets/repo-with-hand-patch/declapract.use.yml +4 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/.test/assets/repo-with-hand-patch/package.json +17 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/.test/assets/repo-with-hand-patch/patches/left-pad@1.3.0.patch +6 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/.test/assets/repo-with-hand-patch/patches/pg-pool@3.14.0.patch +12 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/__snapshots__/pg-pool-patch.declapract.integration.test.ts.snap +147 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/best-practice/.declapract.readme.md +22 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/best-practice/package.json +5 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/best-practice/patches/pg-pool.patch +21 -0
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/pg-pool-patch.declapract.integration.test.ts +884 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/declapract.use.yml +4 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/package.json +15 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy.patch +6 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy@1.39.1.patch +6 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/type-fns@1.21.3.patch +6 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/pnpm-lock.yaml +19 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/__snapshots__/stale-patched-dependency.declapract.integration.test.ts.snap +39 -0
- package/dist/.test/assets/expo-fullusecase/declarations/practices/pnpm/stale-patched-dependency.declapract.integration.test.ts +53 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/declapract.use.yml +4 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/package.json +15 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy.patch +6 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy@1.39.1.patch +6 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/type-fns@1.21.3.patch +6 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/pnpm-lock.yaml +19 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/__snapshots__/stale-patched-dependency.declapract.integration.test.ts.snap +39 -0
- package/dist/.test/assets/expo-migration/declarations/practices/pnpm/stale-patched-dependency.declapract.integration.test.ts +53 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/declapract.use.yml +4 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/package.json +15 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy.patch +6 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy@1.39.1.patch +6 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/type-fns@1.21.3.patch +6 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/pnpm-lock.yaml +19 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/__snapshots__/stale-patched-dependency.declapract.integration.test.ts.snap +39 -0
- package/dist/.test/assets/pnpm/declarations/practices/pnpm/stale-patched-dependency.declapract.integration.test.ts +53 -0
- package/dist/__snapshots__/typecheckBestPracticeTemplates.declapract.integration.test.ts.snap +80 -0
- package/dist/practices/persist-with-rds/.test/assets/repo-with-hand-patch/declapract.use.yml +4 -0
- package/dist/practices/persist-with-rds/.test/assets/repo-with-hand-patch/package.json +17 -0
- package/dist/practices/persist-with-rds/.test/assets/repo-with-hand-patch/patches/left-pad@1.3.0.patch +6 -0
- package/dist/practices/persist-with-rds/.test/assets/repo-with-hand-patch/patches/pg-pool@3.14.0.patch +12 -0
- package/dist/practices/persist-with-rds/__snapshots__/pg-pool-patch.declapract.integration.test.ts.snap +147 -0
- package/dist/practices/persist-with-rds/best-practice/.declapract.readme.md +22 -0
- package/dist/practices/persist-with-rds/best-practice/package.json +5 -0
- package/dist/practices/persist-with-rds/best-practice/patches/pg-pool.patch +21 -0
- package/dist/practices/persist-with-rds/pg-pool-patch.declapract.integration.test.ts +884 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/declapract.use.yml +4 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/package.json +15 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy.patch +6 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/rhachet-roles-ehmpathy@1.39.1.patch +6 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/patches/type-fns@1.21.3.patch +6 -0
- package/dist/practices/pnpm/.test/assets/repo-stale-patch-beside-bare/pnpm-lock.yaml +19 -0
- package/dist/practices/pnpm/__snapshots__/stale-patched-dependency.declapract.integration.test.ts.snap +39 -0
- package/dist/practices/pnpm/stale-patched-dependency.declapract.integration.test.ts +53 -0
- package/dist/typecheckBestPracticeTemplates.declapract.integration.test.ts +181 -19
- package/package.json +6 -6
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repo-stale-patch-beside-bare",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"packageManager": "pnpm@10.34.5",
|
|
5
|
+
"pnpm": {
|
|
6
|
+
"overrides": {
|
|
7
|
+
"react-refresh": "0.14.2"
|
|
8
|
+
},
|
|
9
|
+
"patchedDependencies": {
|
|
10
|
+
"rhachet-roles-ehmpathy": "patches/rhachet-roles-ehmpathy.patch",
|
|
11
|
+
"rhachet-roles-ehmpathy@1.39.1": "patches/rhachet-roles-ehmpathy@1.39.1.patch",
|
|
12
|
+
"type-fns@1.21.3": "patches/type-fns@1.21.3.patch"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
lockfileVersion: '9.0'
|
|
2
|
+
|
|
3
|
+
settings:
|
|
4
|
+
autoInstallPeers: true
|
|
5
|
+
excludeLinksFromLockfile: false
|
|
6
|
+
|
|
7
|
+
packages:
|
|
8
|
+
|
|
9
|
+
rhachet-roles-ehmpathy@1.40.0:
|
|
10
|
+
resolution: {integrity: sha512-a}
|
|
11
|
+
|
|
12
|
+
type-fns@1.21.3:
|
|
13
|
+
resolution: {integrity: sha512-b}
|
|
14
|
+
|
|
15
|
+
snapshots:
|
|
16
|
+
|
|
17
|
+
rhachet-roles-ehmpathy@1.40.0: {}
|
|
18
|
+
|
|
19
|
+
type-fns@1.21.3: {}
|
|
@@ -36,3 +36,42 @@ exports[`pnpm — a stale patch never blocks an install given: [case1] a repo wi
|
|
|
36
36
|
}
|
|
37
37
|
"
|
|
38
38
|
`;
|
|
39
|
+
|
|
40
|
+
exports[`pnpm — a stale patch never blocks an install given: [case2] a repo with a bare-name patch key beside a stale exact key of the same package when: [t0] the pnpm practice is applied then: the before + after contents match snapshot: package.json — after 1`] = `
|
|
41
|
+
"{
|
|
42
|
+
"name": "repo-stale-patch-beside-bare",
|
|
43
|
+
"version": "1.0.0",
|
|
44
|
+
"packageManager": "pnpm@10.34.5",
|
|
45
|
+
"pnpm": {
|
|
46
|
+
"overrides": {
|
|
47
|
+
"react-refresh": "0.14.2"
|
|
48
|
+
},
|
|
49
|
+
"patchedDependencies": {
|
|
50
|
+
"rhachet-roles-ehmpathy": "patches/rhachet-roles-ehmpathy.patch",
|
|
51
|
+
"type-fns@1.21.3": "patches/type-fns@1.21.3.patch"
|
|
52
|
+
},
|
|
53
|
+
"allowUnusedPatches": true,
|
|
54
|
+
"ignorePatchFailures": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
"
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
exports[`pnpm — a stale patch never blocks an install given: [case2] a repo with a bare-name patch key beside a stale exact key of the same package when: [t0] the pnpm practice is applied then: the before + after contents match snapshot: package.json — before 1`] = `
|
|
61
|
+
"{
|
|
62
|
+
"name": "repo-stale-patch-beside-bare",
|
|
63
|
+
"version": "1.0.0",
|
|
64
|
+
"packageManager": "pnpm@10.34.5",
|
|
65
|
+
"pnpm": {
|
|
66
|
+
"overrides": {
|
|
67
|
+
"react-refresh": "0.14.2"
|
|
68
|
+
},
|
|
69
|
+
"patchedDependencies": {
|
|
70
|
+
"rhachet-roles-ehmpathy": "patches/rhachet-roles-ehmpathy.patch",
|
|
71
|
+
"rhachet-roles-ehmpathy@1.39.1": "patches/rhachet-roles-ehmpathy@1.39.1.patch",
|
|
72
|
+
"type-fns@1.21.3": "patches/type-fns@1.21.3.patch"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
"
|
|
77
|
+
`;
|
|
@@ -79,4 +79,57 @@ describe('pnpm — a stale patch never blocks an install', () => {
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
given('[case2] a repo with a bare-name patch key beside a stale exact key of the same package', () => {
|
|
84
|
+
// the shape persist-with-rds ships for pg-pool: a bare key, which no lockfile can move past
|
|
85
|
+
const tempDir = genTempRepoWithDeclarationsOutside({
|
|
86
|
+
slug: 'pnpm-stale-patch-beside-bare',
|
|
87
|
+
clone: './src/practices/pnpm/.test/assets/repo-stale-patch-beside-bare',
|
|
88
|
+
declarations: './src/.test/assets/pnpm/declarations',
|
|
89
|
+
symlink: [{ at: 'node_modules', to: 'node_modules' }],
|
|
90
|
+
});
|
|
91
|
+
const stalePatch = 'patches/rhachet-roles-ehmpathy@1.39.1.patch';
|
|
92
|
+
const barePatch = 'patches/rhachet-roles-ehmpathy.patch';
|
|
93
|
+
|
|
94
|
+
const state = useBeforeAll(async () => {
|
|
95
|
+
const config = path.join(tempDir, 'declapract.use.yml');
|
|
96
|
+
const read = () =>
|
|
97
|
+
fs.readFile(path.join(tempDir, 'package.json'), 'utf-8');
|
|
98
|
+
const before = await read();
|
|
99
|
+
await executeApply({ config });
|
|
100
|
+
const after1 = await read();
|
|
101
|
+
const exists1 = {
|
|
102
|
+
stale: existsSync(path.join(tempDir, stalePatch)),
|
|
103
|
+
bare: existsSync(path.join(tempDir, barePatch)),
|
|
104
|
+
};
|
|
105
|
+
await executeApply({ config });
|
|
106
|
+
const after2 = await read();
|
|
107
|
+
return { before, after1, after2, exists1 };
|
|
108
|
+
}, 170_000);
|
|
109
|
+
|
|
110
|
+
when('[t0] the pnpm practice is applied', () => {
|
|
111
|
+
then('the stale exact key is retired; the bare-name key is kept', () => {
|
|
112
|
+
const patched = JSON.parse(state.after1).pnpm.patchedDependencies;
|
|
113
|
+
expect(patched).toEqual({
|
|
114
|
+
'rhachet-roles-ehmpathy': barePatch,
|
|
115
|
+
'type-fns@1.21.3': 'patches/type-fns@1.21.3.patch',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
then('the stale patch file is deleted; the bare-name file is kept', () => {
|
|
120
|
+
expect(state.exists1).toEqual({ stale: false, bare: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
then('the before + after contents match snapshot', () => {
|
|
124
|
+
expect(state.before).toMatchSnapshot('package.json — before');
|
|
125
|
+
expect(state.after1).toMatchSnapshot('package.json — after');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
when('[t1] the practice is applied a second time', () => {
|
|
130
|
+
then('the second apply is a no-op', () => {
|
|
131
|
+
expect(state.after2).toEqual(state.after1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
82
135
|
});
|
|
@@ -4,6 +4,7 @@ import fs from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
6
|
import { executeApply, executePlan } from 'declapract';
|
|
7
|
+
import { ConstraintError, MalfunctionError } from 'helpful-errors';
|
|
7
8
|
import { genTempDir, given, then, useBeforeAll, when } from 'test-fns';
|
|
8
9
|
|
|
9
10
|
import { asPackageJSON } from './utils/asPackageJSON';
|
|
@@ -12,6 +13,10 @@ import { asPackageJSON } from './utils/asPackageJSON';
|
|
|
12
13
|
* .what = the pnpm this repo pins in its own `packageManager`, run via npx
|
|
13
14
|
* .why = a ci test job restores node_modules from cache and puts no pnpm on its PATH, so a bare
|
|
14
15
|
* `pnpm` spawn fails ENOENT. npx fetches the pinned version, so the run needs no host pnpm
|
|
16
|
+
* .note = `--loglevel=error` sits before the package spec, so it quiets npx's own npm and reaches
|
|
17
|
+
* neither pnpm nor tsc: npx reads the emitted `.npmrc`, and a newer host npm warns on each
|
|
18
|
+
* pnpm-only key in it (`node-linker`, `public-hoist-pattern`), which says naught of the
|
|
19
|
+
* templates and would vary each snapshot with the host's npm
|
|
15
20
|
*/
|
|
16
21
|
const PNPM_VIA_NPX = (() => {
|
|
17
22
|
const packageManager: string = asPackageJSON(
|
|
@@ -23,7 +28,10 @@ const PNPM_VIA_NPX = (() => {
|
|
|
23
28
|
},
|
|
24
29
|
{ practice: 'typecheck-best-practice-templates' },
|
|
25
30
|
).packageManager;
|
|
26
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
command: 'npx',
|
|
33
|
+
argsBefore: ['--yes', '--loglevel=error', packageManager],
|
|
34
|
+
};
|
|
27
35
|
})();
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -55,6 +63,12 @@ const PNPM_VIA_NPX = (() => {
|
|
|
55
63
|
* .teeth = restore `/\bs3\s*:/` as the s3 key pattern in `cache/bad-practices/cache-directory-options`
|
|
56
64
|
* — the config template `src/utils/config/getOneCacheDirectory.ts` is flagged in each
|
|
57
65
|
* usecase that ships it.
|
|
66
|
+
* .what = also runs each usecase's emitted cycle check (`test:lint:cycles`) on the installed tree,
|
|
67
|
+
* and clamps that no cycle runs through pg-pool. dpdm walks node_modules (`exclude: []`),
|
|
68
|
+
* so a cycle in a shipped prod dep reddens every adopter's lint gate at once. every other
|
|
69
|
+
* cycle it finds is held in a snapshot, so a new one shows in review.
|
|
70
|
+
* .teeth = drop the `pg-pool` key from `persist-with-rds/best-practice/package.json`
|
|
71
|
+
* `pnpm.patchedDependencies` — `lambda-service-with-rds` reports `pg -> pg-pool`.
|
|
58
72
|
* .note = the test lives at `src/` (not under `src/.test/`): jest skips dot dirs, so a test there
|
|
59
73
|
* would never run.
|
|
60
74
|
*/
|
|
@@ -70,6 +84,10 @@ const USECASES = [
|
|
|
70
84
|
] as const;
|
|
71
85
|
type Usecase = (typeof USECASES)[number];
|
|
72
86
|
|
|
87
|
+
// the usecases whose install holds pg-pool; `[t3]` checks this set against each real install, so
|
|
88
|
+
// a usecase that gains pg reddens until it joins, and its pg-pool verdict is never vacuous
|
|
89
|
+
const USECASES_WITH_PG_POOL: readonly Usecase[] = ['lambda-service-with-rds'];
|
|
90
|
+
|
|
73
91
|
// every `@declapract{variable.*}` any practice in any usecase references
|
|
74
92
|
const VARIABLES_YML = `
|
|
75
93
|
variables:
|
|
@@ -114,6 +132,8 @@ const runInRepo = (input: {
|
|
|
114
132
|
const child = spawn(input.command, input.args, {
|
|
115
133
|
cwd: input.cwd,
|
|
116
134
|
env: { ...process.env, CI: 'true', FORCE_COLOR: '0' },
|
|
135
|
+
// a stalled registry kills the child (status null) instead of a silent park of the run
|
|
136
|
+
timeout: 900_000,
|
|
117
137
|
});
|
|
118
138
|
const chunks: string[] = [];
|
|
119
139
|
child.stdout.on('data', (chunk) => chunks.push(String(chunk)));
|
|
@@ -144,6 +164,39 @@ const asMaskedOutput = (input: { output: string; dirs: string[] }): string =>
|
|
|
144
164
|
input.output,
|
|
145
165
|
);
|
|
146
166
|
|
|
167
|
+
/**
|
|
168
|
+
* .what = the package each segment of one dpdm cycle line sits in, joined in cycle order
|
|
169
|
+
* .why = a raw cycle line names pnpm store dirs whose suffix hashes shift with every peer set;
|
|
170
|
+
* the packages alone are the stable fact a snapshot can hold
|
|
171
|
+
*/
|
|
172
|
+
const asCyclePackages = (input: { line: string }): string =>
|
|
173
|
+
input.line
|
|
174
|
+
.split(' -> ')
|
|
175
|
+
.map((segment) => {
|
|
176
|
+
const afterStore = segment.trim().split('node_modules/').pop() ?? '';
|
|
177
|
+
const match = afterStore.match(/^((?:@[^/]+\/)?[^/]+)/);
|
|
178
|
+
return match?.[1] ?? segment.trim();
|
|
179
|
+
})
|
|
180
|
+
.join(' -> ');
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* .what = the verdict of one `test:lint:cycles` run: whether dpdm analyzed a tree, and each
|
|
184
|
+
* cycle it found, as package chains
|
|
185
|
+
* .why = a bare repo of a usecase that emits no `src/**\/*.ts` gives dpdm no entry file, which it
|
|
186
|
+
* reports as a failure; that is not a cycle, so the summary keeps the two apart
|
|
187
|
+
*/
|
|
188
|
+
const asCycleSummary = (input: {
|
|
189
|
+
output: string;
|
|
190
|
+
}): { analyzed: boolean; cycles: string[] } => ({
|
|
191
|
+
analyzed: input.output.includes('Analyze done!'),
|
|
192
|
+
cycles: input.output
|
|
193
|
+
.split('\n')
|
|
194
|
+
.map((line) => line.match(/^\s*\d+\)\s+(.+)$/)?.[1])
|
|
195
|
+
.filter((line): line is string => line !== undefined)
|
|
196
|
+
.map((line) => asCyclePackages({ line }))
|
|
197
|
+
.sort(), // dpdm reports cycles in traversal order, which shifts between runs
|
|
198
|
+
});
|
|
199
|
+
|
|
147
200
|
/**
|
|
148
201
|
* .what = applies one usecase into its temp repo, installs the emitted deps, and compiles
|
|
149
202
|
* .why = the three steps an adopter takes before their first `tsc`
|
|
@@ -155,8 +208,11 @@ const genTypecheckResult = async (input: {
|
|
|
155
208
|
}): Promise<{
|
|
156
209
|
config: string;
|
|
157
210
|
tsconfigFound: boolean;
|
|
211
|
+
holdsPgPool: boolean;
|
|
212
|
+
srcImportsPg: boolean;
|
|
158
213
|
install: { status: number | null; output: string };
|
|
159
214
|
compile: { status: number | null; output: string };
|
|
215
|
+
cycles: { status: number | null; output: string };
|
|
160
216
|
}> => {
|
|
161
217
|
// declare the usecase this repo adopts, from declarations that live outside the repo
|
|
162
218
|
// .note = declapract joins `declarations` onto the config's dir, so the path is relative, and both
|
|
@@ -225,17 +281,48 @@ const genTypecheckResult = async (input: {
|
|
|
225
281
|
],
|
|
226
282
|
});
|
|
227
283
|
|
|
284
|
+
const compiledAt = Date.now();
|
|
285
|
+
|
|
286
|
+
// run the emitted cycle check, as the adopter's `npm run test:lint` does
|
|
287
|
+
const cycles = await runInRepo({
|
|
288
|
+
cwd,
|
|
289
|
+
command: PNPM_VIA_NPX.command,
|
|
290
|
+
args: [...PNPM_VIA_NPX.argsBefore, 'run', 'test:lint:cycles'],
|
|
291
|
+
});
|
|
292
|
+
|
|
228
293
|
// report the per-phase cost, so a slow usecase is visible in the run log
|
|
229
294
|
console.log(`typecheck-templates ${input.usecase} phase cost`, {
|
|
230
295
|
applyMs: appliedAt - startedAt,
|
|
231
296
|
installMs: installedAt - appliedAt,
|
|
232
|
-
compileMs:
|
|
297
|
+
compileMs: compiledAt - installedAt,
|
|
298
|
+
cyclesMs: Date.now() - compiledAt,
|
|
233
299
|
});
|
|
234
300
|
|
|
301
|
+
// whether the install holds pg-pool at all; pnpm keeps a transitive dep only in its store
|
|
302
|
+
const pnpmStore = path.join(cwd, 'node_modules', '.pnpm');
|
|
303
|
+
const holdsPgPool =
|
|
304
|
+
existsSync(pnpmStore) &&
|
|
305
|
+
(await fs.readdir(pnpmStore)).some((name) => name.startsWith('pg-pool@'));
|
|
306
|
+
|
|
307
|
+
// whether any emitted src file imports pg; without one, dpdm never reaches pg-pool
|
|
308
|
+
const srcDir = path.join(cwd, 'src');
|
|
309
|
+
const srcFiles = existsSync(srcDir)
|
|
310
|
+
? (await fs.readdir(srcDir, { recursive: true }))
|
|
311
|
+
.map(String)
|
|
312
|
+
.filter((file) => file.endsWith('.ts'))
|
|
313
|
+
: [];
|
|
314
|
+
const srcImportsPg = (
|
|
315
|
+
await Promise.all(
|
|
316
|
+
srcFiles.map((file) => fs.readFile(path.join(srcDir, file), 'utf-8')),
|
|
317
|
+
)
|
|
318
|
+
).some((contents) => /from 'pg'/.test(contents));
|
|
319
|
+
|
|
235
320
|
const dirs = [cwd, input.tempDir];
|
|
236
321
|
return {
|
|
237
322
|
config,
|
|
238
323
|
tsconfigFound,
|
|
324
|
+
holdsPgPool,
|
|
325
|
+
srcImportsPg,
|
|
239
326
|
install: {
|
|
240
327
|
status: install.status,
|
|
241
328
|
output: asMaskedOutput({ output: install.output, dirs }),
|
|
@@ -244,6 +331,10 @@ const genTypecheckResult = async (input: {
|
|
|
244
331
|
status: compile.status,
|
|
245
332
|
output: asMaskedOutput({ output: compile.output, dirs }),
|
|
246
333
|
},
|
|
334
|
+
cycles: {
|
|
335
|
+
status: cycles.status,
|
|
336
|
+
output: asMaskedOutput({ output: cycles.output, dirs }),
|
|
337
|
+
},
|
|
247
338
|
};
|
|
248
339
|
};
|
|
249
340
|
|
|
@@ -326,8 +417,8 @@ describe('typecheck best-practice templates', () => {
|
|
|
326
417
|
});
|
|
327
418
|
|
|
328
419
|
// read one usecase's result; a usecase whose preparation threw fails each of its thens loud
|
|
329
|
-
const getScene = (usecase: Usecase) => {
|
|
330
|
-
const result = settled[usecase];
|
|
420
|
+
const getScene = (input: { usecase: Usecase }) => {
|
|
421
|
+
const result = settled[input.usecase];
|
|
331
422
|
if (result.status === 'rejected') throw result.reason;
|
|
332
423
|
return result.value;
|
|
333
424
|
};
|
|
@@ -358,8 +449,8 @@ describe('typecheck best-practice templates', () => {
|
|
|
358
449
|
});
|
|
359
450
|
|
|
360
451
|
// read one usecase's plan flags; a plan that threw fails each of its thens loud
|
|
361
|
-
const getPlan = (usecase: Usecase) => {
|
|
362
|
-
const result = plans[usecase];
|
|
452
|
+
const getPlan = (input: { usecase: Usecase }) => {
|
|
453
|
+
const result = plans[input.usecase];
|
|
363
454
|
if (result.status === 'rejected') throw result.reason;
|
|
364
455
|
return result.value;
|
|
365
456
|
};
|
|
@@ -368,55 +459,126 @@ describe('typecheck best-practice templates', () => {
|
|
|
368
459
|
given(`[usecase] ${usecase} applied to a bare repo`, () => {
|
|
369
460
|
when('[t0] the emitted deps are installed', () => {
|
|
370
461
|
then('the install exits 0', () => {
|
|
371
|
-
const scene = getScene(usecase);
|
|
462
|
+
const scene = getScene({ usecase });
|
|
372
463
|
if (scene.install.status !== 0)
|
|
373
|
-
throw new
|
|
374
|
-
|
|
375
|
-
|
|
464
|
+
throw new MalfunctionError(`pnpm install failed for ${usecase}`, {
|
|
465
|
+
status: scene.install.status,
|
|
466
|
+
output: scene.install.output,
|
|
467
|
+
hint: 'read the install output above; a registry or cache fault is the usual cause',
|
|
468
|
+
});
|
|
376
469
|
expect(scene.install.status).toEqual(0);
|
|
377
470
|
});
|
|
378
471
|
});
|
|
379
472
|
|
|
380
473
|
when('[t1] the emitted tree is compiled with the emitted tsconfig', () => {
|
|
381
474
|
then('the usecase emits a tsconfig.json', () => {
|
|
382
|
-
expect(getScene(usecase).tsconfigFound).toEqual(true);
|
|
475
|
+
expect(getScene({ usecase }).tsconfigFound).toEqual(true);
|
|
383
476
|
});
|
|
384
477
|
|
|
385
478
|
then('the tsc diagnostics match snapshot', () => {
|
|
386
|
-
expect(getScene(usecase).compile.output).toMatchSnapshot(
|
|
479
|
+
expect(getScene({ usecase }).compile.output).toMatchSnapshot(
|
|
387
480
|
`${usecase} — tsc --noEmit diagnostics`,
|
|
388
481
|
);
|
|
389
482
|
});
|
|
390
483
|
|
|
391
484
|
then('tsc --noEmit exits 0', () => {
|
|
392
|
-
const scene = getScene(usecase);
|
|
485
|
+
const scene = getScene({ usecase });
|
|
393
486
|
if (scene.compile.status !== 0)
|
|
394
|
-
throw new
|
|
395
|
-
|
|
396
|
-
|
|
487
|
+
throw new ConstraintError(`tsc --noEmit failed for ${usecase}`, {
|
|
488
|
+
status: scene.compile.status,
|
|
489
|
+
output: scene.compile.output,
|
|
490
|
+
hint: 'a template the usecase ships does not compile; the diagnostics name it',
|
|
491
|
+
});
|
|
397
492
|
expect(scene.compile.status).toEqual(0);
|
|
398
493
|
});
|
|
399
494
|
});
|
|
400
495
|
|
|
401
496
|
when('[t2] the twice-applied repo is planned again', () => {
|
|
402
497
|
then('no file is flagged for an automatic fix (the apply converges)', () => {
|
|
403
|
-
expect(getPlan(usecase).automatic).toEqual([]);
|
|
498
|
+
expect(getPlan({ usecase }).automatic).toEqual([]);
|
|
404
499
|
});
|
|
405
500
|
|
|
406
501
|
then('no file the apply wrote is flagged by its own usecase', () => {
|
|
407
502
|
const cwd = realpathSync(tempDirs[usecase].repo);
|
|
408
|
-
const flaggedWritten = getPlan(usecase).manual.filter((file) =>
|
|
503
|
+
const flaggedWritten = getPlan({ usecase }).manual.filter((file) =>
|
|
409
504
|
existsSync(path.join(cwd, file)),
|
|
410
505
|
);
|
|
411
506
|
expect(flaggedWritten).toEqual([]);
|
|
412
507
|
});
|
|
413
508
|
|
|
414
509
|
then('the files left for a human to author match snapshot', () => {
|
|
415
|
-
expect(getPlan(usecase).manual).toMatchSnapshot(
|
|
510
|
+
expect(getPlan({ usecase }).manual).toMatchSnapshot(
|
|
416
511
|
`${usecase} — files flagged for a human`,
|
|
417
512
|
);
|
|
418
513
|
});
|
|
419
514
|
});
|
|
515
|
+
|
|
516
|
+
when('[t3] the emitted cycle check runs, as `npm run test:lint` does', () => {
|
|
517
|
+
then('the install holds pg-pool exactly where the usecase is declared to', () => {
|
|
518
|
+
const scene = getScene({ usecase });
|
|
519
|
+
const declared = USECASES_WITH_PG_POOL.includes(usecase);
|
|
520
|
+
if (scene.holdsPgPool !== declared)
|
|
521
|
+
throw new ConstraintError(`pg-pool presence drifted for ${usecase}`, {
|
|
522
|
+
holdsPgPool: scene.holdsPgPool,
|
|
523
|
+
declared,
|
|
524
|
+
hint: 'update USECASES_WITH_PG_POOL to match the install, so the pg-pool verdict below stays real',
|
|
525
|
+
});
|
|
526
|
+
expect(scene.holdsPgPool).toEqual(declared);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
then('it reports no cycle through pg-pool', () => {
|
|
530
|
+
const { cycles } = asCycleSummary({
|
|
531
|
+
output: getScene({ usecase }).cycles.output,
|
|
532
|
+
});
|
|
533
|
+
const cyclesThroughPgPool = cycles.filter((cycle) =>
|
|
534
|
+
cycle.includes('pg-pool'),
|
|
535
|
+
);
|
|
536
|
+
if (cyclesThroughPgPool.length > 0)
|
|
537
|
+
throw new ConstraintError(`pg-pool cycle found for ${usecase}`, {
|
|
538
|
+
cyclesThroughPgPool,
|
|
539
|
+
hint: 'the shipped patches/pg-pool.patch did not apply; check its key in persist-with-rds package.json, and whether pg-pool moved the line (pnpm warns by name)',
|
|
540
|
+
});
|
|
541
|
+
expect(cyclesThroughPgPool).toEqual([]);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (USECASES_WITH_PG_POOL.includes(usecase))
|
|
545
|
+
then('it analyzes the tree, so the pg-pool verdict is real', () => {
|
|
546
|
+
expect(
|
|
547
|
+
asCycleSummary({ output: getScene({ usecase }).cycles.output })
|
|
548
|
+
.analyzed,
|
|
549
|
+
).toEqual(true);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (USECASES_WITH_PG_POOL.includes(usecase))
|
|
553
|
+
then('its emitted src imports pg, so dpdm can reach pg-pool', () => {
|
|
554
|
+
expect(getScene({ usecase }).srcImportsPg).toEqual(true);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// a drift in dpdm's cycle line format would parse to zero cycles and pass the pg-pool verdict blind
|
|
558
|
+
then('the parsed cycles agree with the verdict dpdm prints', () => {
|
|
559
|
+
const { output } = getScene({ usecase }).cycles;
|
|
560
|
+
const summary = asCycleSummary({ output });
|
|
561
|
+
if (!summary.analyzed) return expect(summary.cycles).toEqual([]);
|
|
562
|
+
const noneFound = output.includes('no circular dependency was found');
|
|
563
|
+
if (noneFound !== (summary.cycles.length === 0))
|
|
564
|
+
throw new MalfunctionError(`cycle parse drifted for ${usecase}`, {
|
|
565
|
+
noneFound,
|
|
566
|
+
cyclesParsed: summary.cycles.length,
|
|
567
|
+
hint: 'dpdm changed its cycle line format; update the line pattern in asCycleSummary',
|
|
568
|
+
});
|
|
569
|
+
expect(noneFound).toEqual(summary.cycles.length === 0);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// .note = the exit status rides in the snapshot, so a pass/fail flip shows in review. it is not
|
|
573
|
+
// asserted 0: a bare repo with no src entry, and cycles at floor versions outside pg-pool,
|
|
574
|
+
// each exit the check 1 for causes this clamp does not own
|
|
575
|
+
then('the exit status and the cycles it finds match snapshot', () => {
|
|
576
|
+
expect({
|
|
577
|
+
status: getScene({ usecase }).cycles.status,
|
|
578
|
+
...asCycleSummary({ output: getScene({ usecase }).cycles.output }),
|
|
579
|
+
}).toMatchSnapshot(`${usecase} — test:lint:cycles summary`);
|
|
580
|
+
});
|
|
581
|
+
});
|
|
420
582
|
});
|
|
421
583
|
});
|
|
422
584
|
});
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
5
|
"private": false,
|
|
6
|
-
"version": "0.49.
|
|
6
|
+
"version": "0.49.15",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "src/index.js",
|
|
9
9
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -77,15 +77,15 @@
|
|
|
77
77
|
"esbuild-register": "3.6.0",
|
|
78
78
|
"husky": "8.0.3",
|
|
79
79
|
"jest": "30.2.0",
|
|
80
|
-
"rhachet": "1.
|
|
80
|
+
"rhachet": "1.50.1",
|
|
81
81
|
"rhachet-brains-anthropic": "0.4.3",
|
|
82
82
|
"rhachet-brains-fireworksai": "0.2.3",
|
|
83
|
-
"rhachet-brains-openrouter": "0.1.
|
|
83
|
+
"rhachet-brains-openrouter": "0.1.4",
|
|
84
84
|
"rhachet-brains-xai": "0.3.3",
|
|
85
|
-
"rhachet-roles-bhrain": "0.
|
|
85
|
+
"rhachet-roles-bhrain": "0.42.1",
|
|
86
86
|
"rhachet-roles-bhrowser": "0.1.0",
|
|
87
|
-
"rhachet-roles-bhuild": "0.23.
|
|
88
|
-
"rhachet-roles-ehmpathy": "1.39.
|
|
87
|
+
"rhachet-roles-bhuild": "0.23.3",
|
|
88
|
+
"rhachet-roles-ehmpathy": "1.39.9",
|
|
89
89
|
"rhachet-roles-ghlitch": "0.3.0",
|
|
90
90
|
"rhachet-roles-rhachet": "0.1.7",
|
|
91
91
|
"sdk-aws-lambda": "0.7.2",
|