declapract-typescript-ehmpathy 0.49.12 → 0.49.13
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/__snapshots__/ssm-cicd-param-names.declapract.integration.test.ts.snap +1 -1
- package/dist/.test/assets/cicd-service/declarations/practices/persist-with-rds/best-practice/provision/aws/resources.parameters.ts +13 -8
- package/dist/bestPracticeTemplateParse.declapract.integration.test.ts +169 -0
- package/dist/practices/persist-with-rds/__snapshots__/ssm-cicd-param-names.declapract.integration.test.ts.snap +1 -1
- package/dist/practices/persist-with-rds/best-practice/provision/aws/resources.parameters.ts +13 -8
- package/package.json +3 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`persist-with-rds ssm cicd param names are slash-path, tierless given: [case1] the two cicd credentials when: [t0] resources.parameters.ts is read then: the emitted cicd + crud param-name lines match snapshot: ssm cicd + crud param names 1`] = `
|
|
4
4
|
[
|
|
5
|
-
"* with NO tier segment — \`parameter
|
|
5
|
+
"* with NO tier segment — \`parameter/<star>/svc-<star>/database/role/cicd/for-plan/<star>\`",
|
|
6
6
|
"secret({ name: \`\${namespace}.database.role.crud.password\` }),",
|
|
7
7
|
"name: \`/@declapract{variable.organizationName}/@declapract{variable.projectName}/database/role/cicd/for-plan/password\`,",
|
|
8
8
|
"name: \`/@declapract{variable.organizationName}/@declapract{variable.projectName}/database/role/cicd/for-apply/password\`,",
|
|
@@ -70,14 +70,19 @@ const secret = (input: { name: string }): DeclaredAwsSsmParameterSecure =>
|
|
|
70
70
|
* value-less-absent throw on apply cannot trigger in test. test sources its non-sensitive
|
|
71
71
|
* db creds straight from config/test.json and never reconciles these params.
|
|
72
72
|
* .note = the two cicd credentials are pinned in the prod plan role's iam policy as SLASH paths
|
|
73
|
-
* with NO tier segment — `parameter
|
|
74
|
-
* `.../for-apply
|
|
75
|
-
* dotted name cannot match it and the plan role gets ciphertext or a
|
|
76
|
-
* omits a tier segment on purpose: the aws ACCOUNT separates prep
|
|
77
|
-
* name. so the for-plan name MUST stay byte-identical to the pin.
|
|
78
|
-
* pinned by NO policy, so it keeps the dotted
|
|
79
|
-
* config/${env}.json matches — hence the
|
|
80
|
-
* accident.
|
|
73
|
+
* with NO tier segment — `parameter/<star>/svc-<star>/database/role/cicd/for-plan/<star>`
|
|
74
|
+
* and `.../for-apply/<star>`. that pin's arn needs literal `/` separators after
|
|
75
|
+
* `parameter/`, so a dotted name cannot match it and the plan role gets ciphertext or a
|
|
76
|
+
* denial, forever. it omits a tier segment on purpose: the aws ACCOUNT separates prep
|
|
77
|
+
* from prod, never the name. so the for-plan name MUST stay byte-identical to the pin.
|
|
78
|
+
* the crud credential is pinned by NO policy, so it keeps the dotted
|
|
79
|
+
* `${org}.${project}.${accessSlug}` name that config/${env}.json matches — hence the
|
|
80
|
+
* three names differ in shape by design, not by accident.
|
|
81
|
+
* .note = `<star>` stands in for the literal `*` wildcard of that arn, and must stay a stand-in.
|
|
82
|
+
* a literal `*` written immediately before a `/` closes this comment mid-prose, so every
|
|
83
|
+
* character after it is read as code and the file no longer parses — which breaks
|
|
84
|
+
* `pnpm fix` in every repo that adopts this template, with no repair available to them
|
|
85
|
+
* (declapract apply restores these bytes). see #613.
|
|
81
86
|
*/
|
|
82
87
|
export const getAllParameters = (input: {
|
|
83
88
|
accessSlug: string | null;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join, relative } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { given, then, when } from 'test-fns';
|
|
5
|
+
import * as ts from 'typescript';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getAllPathsUnderDir,
|
|
9
|
+
PRACTICE_TREE_SKIP_DIRS,
|
|
10
|
+
} from './utils/getAllPathsUnderDir';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* .what = clamps that every `.ts`/`.tsx` TEMPLATE a practice ships parses as valid typescript,
|
|
14
|
+
* and that no multi-line jsdoc inside one closes itself mid-line.
|
|
15
|
+
* .why = a template is copied verbatim into a consumer repo, where it is real source that the
|
|
16
|
+
* consumer's biome parses on every `pnpm fix`. a template that does not parse breaks
|
|
17
|
+
* `pnpm fix` in EVERY adopter at once, and no adopter can repair it for itself —
|
|
18
|
+
* `declapract apply` restores the shipped bytes (#613). this repo's own biome cannot
|
|
19
|
+
* catch it: `biome.jsonc` excludes the practice provision tree because templates carry
|
|
20
|
+
* `@declapract{}` syntax, so the templates are the one tree with no parse gate on it.
|
|
21
|
+
* this clamp is that gate.
|
|
22
|
+
* .note = the hazard class is wider than any one line: ANY template may carry a glob or an arn
|
|
23
|
+
* inside a block comment, and a `*` immediately before a `/` there terminates the comment
|
|
24
|
+
* mid-prose. every character after it is read as code. that is how #613 shipped:
|
|
25
|
+
* `persist-with-rds`'s `resources.parameters.ts` emitted 17 parse errors in every
|
|
26
|
+
* adopter. so this clamps the CLASS (all templates), never the instance.
|
|
27
|
+
* .teeth = write an iam arn or a glob into any template's jsdoc with a literal `*` immediately
|
|
28
|
+
* before a `/` — the shape #613 shipped in
|
|
29
|
+
* `persist-with-rds/best-practice/provision/aws/resources.parameters.ts` — and BOTH
|
|
30
|
+
* cases redden, with the offending path in the diff.
|
|
31
|
+
* .note = the two cases are separate nets over one defect class, deliberately. case1 (parse)
|
|
32
|
+
* reads the property an adopter actually suffers. case2 (jsdoc shape) catches the
|
|
33
|
+
* silent variant case1 cannot see: a comment that self-closes and whose prose tail
|
|
34
|
+
* happens to parse as valid code, so the file is green and the note is corrupted.
|
|
35
|
+
* .note = scope excludes `*.declapract.*` basenames — those are declapract MACHINERY (check
|
|
36
|
+
* declarations, unit clamps), never templates, and this repo's biome already parses
|
|
37
|
+
* the `*.declapract.ts` declarations. `bad-practices/` is excluded by construction:
|
|
38
|
+
* only `best-practice/` holds what ships.
|
|
39
|
+
* .note = this is an INTEGRATION test (`readdirSync` + `readFileSync` cross the filesystem
|
|
40
|
+
* boundary, per `rule.forbid.unit.remote-boundaries`), and it lives at `src/` — not
|
|
41
|
+
* under any practice — so declapract never walks it for emission (#583).
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// anchored on `__dirname` (this file sits at `src/`) rather than cwd, so a nested-scope jest run
|
|
45
|
+
// cannot silently walk the wrong tree and assert on zero files (a vacuous green).
|
|
46
|
+
const repoRoot = join(__dirname, '..');
|
|
47
|
+
const practicesDir = join(repoRoot, 'src/practices');
|
|
48
|
+
|
|
49
|
+
const isShippedTsTemplate = (path: string): boolean =>
|
|
50
|
+
/\.tsx?$/.test(path) &&
|
|
51
|
+
path.includes('/best-practice/') &&
|
|
52
|
+
!basename(path).includes('.declapract.');
|
|
53
|
+
|
|
54
|
+
const asDiagnosticSite = (diagnostic: ts.Diagnostic): string => {
|
|
55
|
+
if (!diagnostic.file) return '';
|
|
56
|
+
if (diagnostic.start === undefined) return '';
|
|
57
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
|
|
58
|
+
diagnostic.start,
|
|
59
|
+
);
|
|
60
|
+
return `:${line + 1}:${character + 1}`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const asParseErrorLabel = (input: {
|
|
64
|
+
path: string;
|
|
65
|
+
diagnostic: ts.Diagnostic;
|
|
66
|
+
}): string =>
|
|
67
|
+
[
|
|
68
|
+
relative(repoRoot, input.path),
|
|
69
|
+
asDiagnosticSite(input.diagnostic),
|
|
70
|
+
' ',
|
|
71
|
+
ts.flattenDiagnosticMessageText(input.diagnostic.messageText, ' '),
|
|
72
|
+
].join('');
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* .what = every syntactic error typescript reports for one template's source.
|
|
76
|
+
* .why = `transpileModule` runs the PARSER only — no type check, no module resolution — so a
|
|
77
|
+
* template that references packages the consumer installs (but this repo does not) is
|
|
78
|
+
* not a false red. the `@declapract{…}` placeholders sit inside string and template
|
|
79
|
+
* literals in every ts template, so they parse as ordinary string content.
|
|
80
|
+
*/
|
|
81
|
+
const getAllParseErrors = (input: { path: string; source: string }): string[] =>
|
|
82
|
+
(
|
|
83
|
+
ts.transpileModule(input.source, {
|
|
84
|
+
fileName: input.path,
|
|
85
|
+
reportDiagnostics: true,
|
|
86
|
+
compilerOptions: {
|
|
87
|
+
target: ts.ScriptTarget.ES2020,
|
|
88
|
+
jsx: ts.JsxEmit.Preserve,
|
|
89
|
+
},
|
|
90
|
+
}).diagnostics ?? []
|
|
91
|
+
)
|
|
92
|
+
.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)
|
|
93
|
+
.map((diagnostic) => asParseErrorLabel({ path: input.path, diagnostic }));
|
|
94
|
+
|
|
95
|
+
const asLineNumber = (input: { source: string; at: number }): number =>
|
|
96
|
+
input.source.slice(0, input.at).split('\n').length;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* .what = every multi-line jsdoc in one template whose closing delimiter lands mid-line instead
|
|
100
|
+
* of on a line of its own.
|
|
101
|
+
* .why = the house jsdoc shape closes on its own line, always. a close anywhere else on a
|
|
102
|
+
* multi-line block means the comment ended somewhere its author did not intend — which is
|
|
103
|
+
* exactly what a glob or an arn in the prose does.
|
|
104
|
+
* .note = only a jsdoc that OPENS a line counts. the same opener sequence mid-line is almost
|
|
105
|
+
* always a glob inside a string literal, and to read that as a comment would red on a
|
|
106
|
+
* non-defect. a single-line jsdoc is left to the parse case, which sees its tail as code.
|
|
107
|
+
*/
|
|
108
|
+
const getAllJsdocSelfCloses = (input: {
|
|
109
|
+
path: string;
|
|
110
|
+
source: string;
|
|
111
|
+
}): string[] =>
|
|
112
|
+
[...input.source.matchAll(/^[ \t]*\/\*\*/gm)].flatMap((opener) => {
|
|
113
|
+
const openAt = opener.index ?? 0;
|
|
114
|
+
// search from the final asterisk of the opener, so an empty block closes where it really does
|
|
115
|
+
const closeAt = input.source.indexOf('*/', openAt + opener[0].length - 1);
|
|
116
|
+
if (closeAt === -1) return []; // unterminated — the parse case owns that
|
|
117
|
+
const openLine = asLineNumber({ source: input.source, at: openAt });
|
|
118
|
+
const closeLine = asLineNumber({ source: input.source, at: closeAt });
|
|
119
|
+
if (openLine === closeLine) return []; // single-line jsdoc — the parse case owns it
|
|
120
|
+
const prefix = input.source.slice(
|
|
121
|
+
input.source.lastIndexOf('\n', closeAt) + 1,
|
|
122
|
+
closeAt,
|
|
123
|
+
);
|
|
124
|
+
if (/^[ \t]*$/.test(prefix)) return []; // the house shape: the delimiter alone on its line
|
|
125
|
+
return [
|
|
126
|
+
`${relative(repoRoot, input.path)}:${closeLine} jsdoc opened at line ${openLine} closes mid-line after «${prefix.trimStart()}»`,
|
|
127
|
+
];
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('every shipped ts template parses as valid typescript (#613)', () => {
|
|
131
|
+
const templates = getAllPathsUnderDir({
|
|
132
|
+
dir: practicesDir,
|
|
133
|
+
skip: PRACTICE_TREE_SKIP_DIRS,
|
|
134
|
+
})
|
|
135
|
+
.filter(isShippedTsTemplate)
|
|
136
|
+
.sort();
|
|
137
|
+
|
|
138
|
+
const sources = templates.map((path) => ({
|
|
139
|
+
path,
|
|
140
|
+
source: readFileSync(path, 'utf8'),
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
given('[case0] the src/practices best-practice tree', () => {
|
|
144
|
+
when('[t0] the ts templates are walked', () => {
|
|
145
|
+
// guards against a vacuous green: a walk that found zero files would pass both cases below
|
|
146
|
+
then('at least one shipped ts template is found', () => {
|
|
147
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
given('[case1] every shipped ts template', () => {
|
|
153
|
+
when('[t0] each is handed to the typescript parser', () => {
|
|
154
|
+
then('none reports a syntactic error', () => {
|
|
155
|
+
const offenders = sources.flatMap(getAllParseErrors);
|
|
156
|
+
expect(offenders).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
given('[case2] every multi-line jsdoc inside a shipped ts template', () => {
|
|
162
|
+
when('[t0] each block comment is located', () => {
|
|
163
|
+
then('none closes itself mid-line', () => {
|
|
164
|
+
const offenders = sources.flatMap(getAllJsdocSelfCloses);
|
|
165
|
+
expect(offenders).toEqual([]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`persist-with-rds ssm cicd param names are slash-path, tierless given: [case1] the two cicd credentials when: [t0] resources.parameters.ts is read then: the emitted cicd + crud param-name lines match snapshot: ssm cicd + crud param names 1`] = `
|
|
4
4
|
[
|
|
5
|
-
"* with NO tier segment — \`parameter
|
|
5
|
+
"* with NO tier segment — \`parameter/<star>/svc-<star>/database/role/cicd/for-plan/<star>\`",
|
|
6
6
|
"secret({ name: \`\${namespace}.database.role.crud.password\` }),",
|
|
7
7
|
"name: \`/@declapract{variable.organizationName}/@declapract{variable.projectName}/database/role/cicd/for-plan/password\`,",
|
|
8
8
|
"name: \`/@declapract{variable.organizationName}/@declapract{variable.projectName}/database/role/cicd/for-apply/password\`,",
|
|
@@ -70,14 +70,19 @@ const secret = (input: { name: string }): DeclaredAwsSsmParameterSecure =>
|
|
|
70
70
|
* value-less-absent throw on apply cannot trigger in test. test sources its non-sensitive
|
|
71
71
|
* db creds straight from config/test.json and never reconciles these params.
|
|
72
72
|
* .note = the two cicd credentials are pinned in the prod plan role's iam policy as SLASH paths
|
|
73
|
-
* with NO tier segment — `parameter
|
|
74
|
-
* `.../for-apply
|
|
75
|
-
* dotted name cannot match it and the plan role gets ciphertext or a
|
|
76
|
-
* omits a tier segment on purpose: the aws ACCOUNT separates prep
|
|
77
|
-
* name. so the for-plan name MUST stay byte-identical to the pin.
|
|
78
|
-
* pinned by NO policy, so it keeps the dotted
|
|
79
|
-
* config/${env}.json matches — hence the
|
|
80
|
-
* accident.
|
|
73
|
+
* with NO tier segment — `parameter/<star>/svc-<star>/database/role/cicd/for-plan/<star>`
|
|
74
|
+
* and `.../for-apply/<star>`. that pin's arn needs literal `/` separators after
|
|
75
|
+
* `parameter/`, so a dotted name cannot match it and the plan role gets ciphertext or a
|
|
76
|
+
* denial, forever. it omits a tier segment on purpose: the aws ACCOUNT separates prep
|
|
77
|
+
* from prod, never the name. so the for-plan name MUST stay byte-identical to the pin.
|
|
78
|
+
* the crud credential is pinned by NO policy, so it keeps the dotted
|
|
79
|
+
* `${org}.${project}.${accessSlug}` name that config/${env}.json matches — hence the
|
|
80
|
+
* three names differ in shape by design, not by accident.
|
|
81
|
+
* .note = `<star>` stands in for the literal `*` wildcard of that arn, and must stay a stand-in.
|
|
82
|
+
* a literal `*` written immediately before a `/` closes this comment mid-prose, so every
|
|
83
|
+
* character after it is read as code and the file no longer parses — which breaks
|
|
84
|
+
* `pnpm fix` in every repo that adopts this template, with no repair available to them
|
|
85
|
+
* (declapract apply restores these bytes). see #613.
|
|
81
86
|
*/
|
|
82
87
|
export const getAllParameters = (input: {
|
|
83
88
|
accessSlug: string | null;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "declapract-typescript-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
|
-
"version": "0.49.
|
|
5
|
+
"version": "0.49.13",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -79,9 +79,9 @@
|
|
|
79
79
|
"jest": "30.2.0",
|
|
80
80
|
"rhachet": "1.47.5",
|
|
81
81
|
"rhachet-brains-anthropic": "0.4.3",
|
|
82
|
-
"rhachet-brains-fireworksai": "0.1.
|
|
82
|
+
"rhachet-brains-fireworksai": "0.1.7",
|
|
83
83
|
"rhachet-brains-xai": "0.3.3",
|
|
84
|
-
"rhachet-roles-bhrain": "0.
|
|
84
|
+
"rhachet-roles-bhrain": "0.37.0",
|
|
85
85
|
"rhachet-roles-bhrowser": "0.1.0",
|
|
86
86
|
"rhachet-roles-bhuild": "0.21.36",
|
|
87
87
|
"rhachet-roles-ehmpathy": "1.38.12",
|