@fjall/eslint-plugin 31.0.0 → 33.0.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.
@@ -45,7 +45,7 @@ export default {
45
45
  },
46
46
  messages: {
47
47
  forbiddenReexport:
48
- "Symbol `{{name}}` is ClickHouse-internal plumbing and must not appear on the public surface. Consume via the DatabaseFactory or a typed connector instead. See aiDocs/patterns/clickhouse-database-factory-pattern.md."
48
+ "Symbol `{{name}}` is ClickHouse-internal plumbing and must not appear on the public surface. Consume via the DatabaseFactory or a typed connector instead."
49
49
  },
50
50
  schema: []
51
51
  },
@@ -7,7 +7,7 @@
7
7
  * first instance launches BEFORE the hook is attached, and the hook fires
8
8
  * zero notifications for that instance.
9
9
  *
10
- * Production-confirmed in account 985539798308 on 2026-05-19: the ClickHouse
10
+ * Production-confirmed on 2026-05-19: the ClickHouse
11
11
  * PDV LAUNCHING hook never fired for the first instance — the Lambda log
12
12
  * group only saw a TEST_NOTIFICATION ~30s after instance launch. The volume
13
13
  * was never attached, cloud-init timed out, ECS agent never started.
@@ -38,7 +38,7 @@ export default {
38
38
  },
39
39
  messages: {
40
40
  l2AsgHook:
41
- "ASG L2 `addLifecycleHook(id, props)` emits a standalone AWS::AutoScaling::LifecycleHook resource that creates AFTER the ASG — the first instance launches before the hook is attached (production-confirmed regression, account 985539798308, 2026-05-19). Use `attachInlineAsgLifecycleHook` from `lib/resources/aws/compute/asgInlineLifecycleHook.ts`, which appends to `CfnAutoScalingGroup.lifecycleHookSpecificationList` atomically with the ASG."
41
+ "ASG L2 `addLifecycleHook(id, props)` emits a standalone AWS::AutoScaling::LifecycleHook resource that creates AFTER the ASG — the first instance launches before the hook is attached (production-confirmed regression, 2026-05-19). Use `attachInlineAsgLifecycleHook` from `lib/resources/aws/compute/asgInlineLifecycleHook.ts`, which appends to `CfnAutoScalingGroup.lifecycleHookSpecificationList` atomically with the ASG."
42
42
  },
43
43
  schema: []
44
44
  },
@@ -38,7 +38,7 @@ export default {
38
38
  },
39
39
  messages: {
40
40
  rawBlockDeviceVolume:
41
- "Raw `{{name}}` import from `{{source}}` is forbidden. Use `safeEbs` from `lib/resources/aws/compute/blockDeviceVolume` for ephemeral / root volumes, or `PersistentDataVolume` (Phase 1.75) for data that must survive instance refresh. See aiDocs/patterns/infrastructure-wrapper-routing-pattern.md."
41
+ "Raw `{{name}}` import from `{{source}}` is forbidden. Use `safeEbs` from `lib/resources/aws/compute/blockDeviceVolume` for ephemeral / root volumes, or `PersistentDataVolume` (Phase 1.75) for data that must survive instance refresh."
42
42
  },
43
43
  schema: []
44
44
  },
@@ -53,7 +53,7 @@ export default {
53
53
  },
54
54
  messages: {
55
55
  rawCdkProperty:
56
- "Public property `{{name}}` exposes raw CDK plumbing. Mark it `private readonly` and surface a typed accessor (ARN, endpoint, connector) instead. See aiDocs/patterns/clickhouse-database-factory-pattern.md § Hygiene rules."
56
+ "Public property `{{name}}` exposes raw CDK plumbing. Mark it `private readonly` and surface a typed accessor (ARN, endpoint, connector) instead."
57
57
  },
58
58
  schema: []
59
59
  },
@@ -63,7 +63,7 @@ export default {
63
63
  },
64
64
  messages: {
65
65
  optionalInConstructId:
66
- "Construct ID interpolates `props.{{field}}` which is optionally typed; when the caller omits this field the literal string 'undefined' is baked into the CloudFormation logical ID. Resolve in the constructor (`this.{{field}}Value = props.{{field}} ?? <fallback>`) and use `this.{{field}}Value` here. See aiDocs/research/2026-05-17-review-deltas.md § M-9."
66
+ "Construct ID interpolates `props.{{field}}` which is optionally typed; when the caller omits this field the literal string 'undefined' is baked into the CloudFormation logical ID. Resolve in the constructor (`this.{{field}}Value = props.{{field}} ?? <fallback>`) and use `this.{{field}}Value` here."
67
67
  },
68
68
  schema: []
69
69
  },
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Catches `z.enum([<string-literal>, ...])` declarations whose literal set
5
5
  * matches any named SCREAMING_SNAKE_CASE `as const` tuple in
6
- * `generator/src/schemas/constants.ts` or `sharedTypes.ts` — covers the
6
+ * `generator/src/schemas/constants.ts`, `sharedTypes.ts` or
7
+ * `util/src/backupTiers.ts` (the backup tier vocabulary's home) — covers the
7
8
  * `_TYPES`, `_TIERS`, `_PRESETS`, `_PROVIDERS`, `_ENGINES`, `_METHODS`,
8
9
  * `_NAMES`, `_INTERVALS`, `_ARCHITECTURES`, `_VALUES` suffix families. Any
9
10
  * exported tuple matching `/^[A-Z][A-Z0-9_]*$/` participates; suffix is not
@@ -20,12 +21,15 @@
20
21
  * arm and `z.enum(["AWS_IAM","NONE"])` at another) are themselves a
21
22
  * silent-drift hazard — both should route through the named tuple.
22
23
  *
23
- * The registry is built dynamically at rule-init time by reading
24
- * `constants.ts` + `sharedTypes.ts` relative to this rule file. Adding a
24
+ * The registry is built dynamically at rule-init time by reading the
25
+ * source files in `REGISTRY_SOURCES` relative to this rule file. Adding a
25
26
  * new `<NAME> = [...] as const` tuple (any SCREAMING_SNAKE_CASE name) to
26
- * either source file automatically extends the rule's catch surface — no
27
- * rule edits needed. Spread/concat forms (`APP_TYPES = [...TIER_NAMES, X]`)
28
- * are intentionally not extracted they would yield partial literal sets.
27
+ * any of them automatically extends the rule's catch surface — no rule
28
+ * edits needed. A tuple that moves home (an alias such as
29
+ * `BACKUP_VAULT_TIERS = BACKUP_TIERS` is not a literal tuple) needs its new
30
+ * home added to the list, or the vocabulary silently drops out of the
31
+ * registry. Spread/concat forms (`APP_TYPES = [...TIER_NAMES, X]`) are
32
+ * intentionally not extracted — they would yield partial literal sets.
29
33
  *
30
34
  * Codifies typescript-standards.md § "Zod-Derived Type Guards (MANDATORY)".
31
35
  */
@@ -45,19 +49,31 @@ function getRegistry() {
45
49
  return registryCache;
46
50
  }
47
51
 
52
+ // Earlier entries win the name reported for a literal set, so the canonical
53
+ // home of a vocabulary comes first: `@fjall/util` owns the backup tier tuple
54
+ // and the generator re-exports it.
55
+ const REGISTRY_SOURCES = [
56
+ ["util", "src", "backupTiers.ts"],
57
+ ["generator", "src", "schemas", "constants.ts"],
58
+ ["generator", "src", "schemas", "sharedTypes.ts"]
59
+ ];
60
+
48
61
  function buildRegistry() {
49
- const schemasDir = join(__dirname, "..", "..", "generator", "src", "schemas");
62
+ const monorepoRoot = join(__dirname, "..", "..");
50
63
  const registry = new Map();
64
+ const sources = REGISTRY_SOURCES.map((segments) =>
65
+ join(monorepoRoot, ...segments)
66
+ );
51
67
  // Source tree absent → published-package context, no-op silently; the warning
52
- // below is reserved for the dir present but a file unreadable.
53
- if (!existsSync(schemasDir)) return registry;
68
+ // below is reserved for a source present but unreadable.
69
+ if (!sources.some((path) => existsSync(path))) return registry;
54
70
 
55
- const sources = [
56
- join(schemasDir, "constants.ts"),
57
- join(schemasDir, "sharedTypes.ts")
58
- ];
59
71
  const failures = [];
60
72
  for (const path of sources) {
73
+ if (!existsSync(path)) {
74
+ failures.push({ path, error: "missing" });
75
+ continue;
76
+ }
61
77
  let text;
62
78
  try {
63
79
  text = readFileSync(path, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/eslint-plugin",
3
- "version": "31.0.0",
3
+ "version": "33.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",