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.
@@ -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/*/svc-*/database/role/cicd/for-plan/*\` and",
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/*/svc-*/database/role/cicd/for-plan/*` and
74
- * `.../for-apply/*`. that pin's arn needs literal `/` separators after `parameter/`, so a
75
- * dotted name cannot match it and the plan role gets ciphertext or a denial, forever. it
76
- * omits a tier segment on purpose: the aws ACCOUNT separates prep from prod, never the
77
- * name. so the for-plan name MUST stay byte-identical to the pin. the crud credential is
78
- * pinned by NO policy, so it keeps the dotted `${org}.${project}.${accessSlug}` name that
79
- * config/${env}.json matches — hence the three names differ in shape by design, not by
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/*/svc-*/database/role/cicd/for-plan/*\` and",
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/*/svc-*/database/role/cicd/for-plan/*` and
74
- * `.../for-apply/*`. that pin's arn needs literal `/` separators after `parameter/`, so a
75
- * dotted name cannot match it and the plan role gets ciphertext or a denial, forever. it
76
- * omits a tier segment on purpose: the aws ACCOUNT separates prep from prod, never the
77
- * name. so the for-plan name MUST stay byte-identical to the pin. the crud credential is
78
- * pinned by NO policy, so it keeps the dotted `${org}.${project}.${accessSlug}` name that
79
- * config/${env}.json matches — hence the three names differ in shape by design, not by
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.12",
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.6",
82
+ "rhachet-brains-fireworksai": "0.1.7",
83
83
  "rhachet-brains-xai": "0.3.3",
84
- "rhachet-roles-bhrain": "0.34.1",
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",