@craft-ts/dev-tools 0.7.0-beta.13 → 0.7.0-beta.16

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.
Files changed (89) hide show
  1. package/README.md +60 -10
  2. package/package.json +8 -2
  3. package/src/bin/agent-selector.d.ts +22 -0
  4. package/src/bin/agent-selector.js +100 -0
  5. package/src/bin/agent-selector.js.map +1 -0
  6. package/src/bin/craft-graph.js +74 -8
  7. package/src/bin/craft-graph.js.map +1 -1
  8. package/src/bin/craft.d.ts +22 -0
  9. package/src/bin/craft.js +231 -28
  10. package/src/bin/craft.js.map +1 -1
  11. package/src/eslint-rules/anchor-has-href.cjs +37 -3
  12. package/src/eslint-rules/craft-computed-name-match.cjs +26 -5
  13. package/src/eslint-rules/craft-name-match-utils.cjs +18 -9
  14. package/src/eslint-rules/hyperscript-walk.cjs +3 -3
  15. package/src/eslint-rules/index.cjs +46 -1
  16. package/src/eslint-rules/no-auth-token-in-local-storage.cjs +17 -0
  17. package/src/eslint-rules/no-craft-computed-side-effects.cjs +12 -5
  18. package/src/eslint-rules/no-craft-service-component-same-file.cjs +138 -0
  19. package/src/eslint-rules/no-effect-import-in-frontend.cjs +52 -0
  20. package/src/eslint-rules/no-effect-in-params.cjs +31 -1
  21. package/src/eslint-rules/no-explicit-effect-type.cjs +137 -0
  22. package/src/eslint-rules/no-free-has.cjs +47 -0
  23. package/src/eslint-rules/no-heading-level-skip.cjs +6 -6
  24. package/src/eslint-rules/no-raw-class.cjs +76 -0
  25. package/src/eslint-rules/no-raw-css-value.cjs +163 -0
  26. package/src/eslint-rules/no-raw-user-url.cjs +114 -0
  27. package/src/eslint-rules/no-trust-forwarded-headers.cjs +43 -0
  28. package/src/eslint-rules/no-unsafe-html.cjs +93 -0
  29. package/src/eslint-rules/no-unsafe-transfer-state.cjs +54 -0
  30. package/src/eslint-rules/prefer-craft-template-blocks.cjs +8 -8
  31. package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +57 -14
  32. package/src/eslint-rules/prefer-inline-effect-insertion.cjs +263 -0
  33. package/src/eslint-rules/prefer-inline-route-providers.cjs +138 -0
  34. package/src/eslint-rules/recommended-config.cjs +6 -0
  35. package/src/eslint-rules/require-reactive-template-bindings.cjs +8 -8
  36. package/src/eslint-rules/require-route-security-policy.cjs +87 -0
  37. package/src/eslint-rules/require-server-function-timeout.cjs +39 -0
  38. package/src/eslint-rules/security-config.cjs +14 -0
  39. package/src/eslint-rules/security-rule-utils.cjs +21 -0
  40. package/src/eslint-rules/style-file-boundary.cjs +75 -0
  41. package/src/eslint-rules/sync-effect-body.cjs +343 -0
  42. package/src/index.d.ts +4 -0
  43. package/src/index.js +4 -0
  44. package/src/index.js.map +1 -1
  45. package/src/scripts/architecture-graph.js +8 -2
  46. package/src/scripts/architecture-graph.js.map +1 -1
  47. package/src/scripts/create/create-project.d.ts +60 -1
  48. package/src/scripts/create/create-project.js +2082 -184
  49. package/src/scripts/create/create-project.js.map +1 -1
  50. package/src/scripts/data-flow-graph.d.ts +1 -1
  51. package/src/scripts/effect-dependency-graph.d.ts +1 -1
  52. package/src/scripts/security-check.d.ts +30 -0
  53. package/src/scripts/security-check.js +223 -0
  54. package/src/scripts/security-check.js.map +1 -0
  55. package/src/scripts/style-architecture.d.ts +56 -0
  56. package/src/scripts/style-architecture.js +142 -0
  57. package/src/scripts/style-architecture.js.map +1 -0
  58. package/src/scripts/style-graph.d.ts +99 -0
  59. package/src/scripts/style-graph.js +179 -0
  60. package/src/scripts/style-graph.js.map +1 -0
  61. package/src/scripts/style-report.d.ts +55 -0
  62. package/src/scripts/style-report.js +67 -0
  63. package/src/scripts/style-report.js.map +1 -0
  64. package/src/scripts/template/migrate-template.js +1 -1
  65. package/src/scripts/template/migrate-template.js.map +1 -1
  66. package/src/scripts/angular-brand-codemod.ts +0 -3985
  67. package/src/scripts/architecture/migrate-architecture.ts +0 -653
  68. package/src/scripts/architecture-graph.ts +0 -3136
  69. package/src/scripts/components/migrate-components.ts +0 -227
  70. package/src/scripts/components/migration-diagnostic.ts +0 -8
  71. package/src/scripts/create/create-project.ts +0 -849
  72. package/src/scripts/data-flow-graph.ts +0 -240
  73. package/src/scripts/dependency-graph.ts +0 -5908
  74. package/src/scripts/effect-dependency-graph.ts +0 -630
  75. package/src/scripts/migrate.ts +0 -162
  76. package/src/scripts/migration-workspace.ts +0 -93
  77. package/src/scripts/primitives/migrate-named-primitives.ts +0 -627
  78. package/src/scripts/primitives/migrate-primitive-generators.ts +0 -410
  79. package/src/scripts/primitives/migrate-primitives.ts +0 -655
  80. package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +0 -499
  81. package/src/scripts/primitives/migration-diagnostic.ts +0 -15
  82. package/src/scripts/routes/migrate-routes.ts +0 -895
  83. package/src/scripts/routes/migration-diagnostic.ts +0 -25
  84. package/src/scripts/routes/route-command.ts +0 -1807
  85. package/src/scripts/routes/verify-routes.ts +0 -1511
  86. package/src/scripts/services/config.ts +0 -60
  87. package/src/scripts/services/migrate-services.ts +0 -1348
  88. package/src/scripts/services/migration-diagnostic.ts +0 -28
  89. package/src/scripts/template/migrate-template.ts +0 -418
@@ -0,0 +1,21 @@
1
+ function report(context, node, message) {
2
+ context.report({ node, message });
3
+ }
4
+
5
+ function propertyName(node) {
6
+ if (!node) return undefined;
7
+ if (!node.computed && node.property && node.property.type === 'Identifier') {
8
+ return node.property.name;
9
+ }
10
+ if (node.computed && node.property && node.property.type === 'Literal') {
11
+ return String(node.property.value);
12
+ }
13
+ return undefined;
14
+ }
15
+
16
+ function isCallNamed(node, name) {
17
+ return node && node.type === 'CallExpression' &&
18
+ node.callee && node.callee.type === 'Identifier' && node.callee.name === name;
19
+ }
20
+
21
+ module.exports = { report, propertyName, isCallNamed };
@@ -0,0 +1,75 @@
1
+ const ALLOWED_MODULES = ['@craft-ts/style'];
2
+
3
+ /**
4
+ * A `*.style.ts` may import style vocabulary and nothing else.
5
+ *
6
+ * This is not a taste rule: the build plugin **imports these modules in Node**
7
+ * to read the values they register. An application import would drag component
8
+ * code, DI, or browser globals into that evaluation, and the emitter would
9
+ * either crash or — worse — succeed while running application side effects at
10
+ * build time.
11
+ *
12
+ * Relative imports are allowed only between style files, so a design system can
13
+ * be split across several of them.
14
+ */
15
+ module.exports = {
16
+ meta: {
17
+ type: 'problem',
18
+ docs: {
19
+ description:
20
+ 'Restrict *.style.ts files to style vocabulary imports so the build plugin can evaluate them in Node.',
21
+ },
22
+ schema: [
23
+ {
24
+ type: 'object',
25
+ properties: {
26
+ allow: { type: 'array', items: { type: 'string' } },
27
+ suffix: { type: 'string' },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ ],
32
+ messages: {
33
+ applicationImport:
34
+ "A style file may only import style vocabulary, and '{{source}}' is not. The build plugin imports this file in Node to read the values it registers; an application import would run application code at build time. Move what you need into the sheet, or into another *.style.ts.",
35
+ },
36
+ },
37
+ create(context) {
38
+ const options = context.options[0] ?? {};
39
+ const allowed = new Set([...ALLOWED_MODULES, ...(options.allow ?? [])]);
40
+ const suffix = options.suffix ?? '.style.ts';
41
+ const filename = context.filename ?? context.getFilename();
42
+
43
+ if (!filename.endsWith(suffix)) return {};
44
+
45
+ const check = (node, source) => {
46
+ if (typeof source !== 'string') return;
47
+ if (allowed.has(source)) return;
48
+ // A relative import is fine as long as it lands on another style file:
49
+ // splitting a design system across files must stay possible.
50
+ if (source.startsWith('.') && source.endsWith('.style')) return;
51
+ if (source.startsWith('.') && source.endsWith(suffix.slice(0, -3)))
52
+ return;
53
+ context.report({
54
+ node,
55
+ messageId: 'applicationImport',
56
+ data: { source },
57
+ });
58
+ };
59
+
60
+ return {
61
+ ImportDeclaration(node) {
62
+ check(node, node.source.value);
63
+ },
64
+ ExportNamedDeclaration(node) {
65
+ if (node.source) check(node, node.source.value);
66
+ },
67
+ ExportAllDeclaration(node) {
68
+ if (node.source) check(node, node.source.value);
69
+ },
70
+ ImportExpression(node) {
71
+ if (node.source.type === 'Literal') check(node, node.source.value);
72
+ },
73
+ };
74
+ },
75
+ };
@@ -0,0 +1,343 @@
1
+ // ---------------------------------------------------------------------------
2
+ // `SyncOp` in `R` is a CLAIM: an author writing `Effect<A, E, SyncOp>` promises
3
+ // the Effect never suspends. The type system cannot check that claim — a
4
+ // service member closes its dependencies at Layer construction, so an async
5
+ // member and a pure one both surface as `R = never` and stay indistinguishable.
6
+ //
7
+ // This rule reads the body instead, all branches at once, which is the thing a
8
+ // unit test cannot do: it needs no fixture, and a branch that only suspends on
9
+ // a cold cache is as visible as one that always does.
10
+ //
11
+ // It works by ELIMINATION. Requirements union through `Effect.gen`, so anything
12
+ // yielded that is itself declared synchronous carries `SyncOp` and passes for
13
+ // free. What is left ambiguous is `R = never`: either a pure constructor
14
+ // (`Effect.succeed`) or an async member whose Layer swallowed its dependencies.
15
+ // The first list is short and closed, so it is the one spelled out here; the
16
+ // rest is refused, and the author either declares it or moves it to a loader.
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const EFFECT_PACKAGE = 'effect';
20
+ const CRAFT_EFFECT_PACKAGE = '@craft-ts/effect';
21
+
22
+ /** Constructors that cannot suspend — the closed list `R = never` needs. */
23
+ const PURE_EFFECT_CONSTRUCTORS = new Set([
24
+ 'succeed',
25
+ 'sync',
26
+ 'fail',
27
+ 'failSync',
28
+ 'die',
29
+ 'dieSync',
30
+ 'void',
31
+ 'try',
32
+ 'succeedNone',
33
+ 'succeedSome',
34
+ 'fromNullable',
35
+ ]);
36
+
37
+ /** Constructors that always suspend — named so the message can be specific. */
38
+ const ASYNC_EFFECT_CONSTRUCTORS = new Set([
39
+ 'promise',
40
+ 'tryPromise',
41
+ 'sleep',
42
+ 'async',
43
+ 'callback',
44
+ 'never',
45
+ 'delay',
46
+ 'timeout',
47
+ ]);
48
+
49
+ const SYNC_OP_PATTERN = /\bSyncOp\b/;
50
+
51
+ /** `ts.SymbolFlags.Class`. */
52
+ const SYMBOL_FLAG_CLASS = 32;
53
+
54
+ module.exports = {
55
+ meta: {
56
+ type: 'problem',
57
+ docs: {
58
+ description:
59
+ 'Keep a body declared synchronous (SyncOp) free of anything that may suspend.',
60
+ },
61
+ schema: [],
62
+ messages: {
63
+ asyncConstructor:
64
+ 'Effect.{{name}}(...) always suspends: it cannot appear in a body declared synchronous (SyncOp).\nMove the asynchronous work to a loader (queryEffect / mutationEffect / asyncProcessEffect).',
65
+ notDeclaredSync:
66
+ 'This body is declared synchronous (SyncOp) but yields {{what}}, which nothing declares synchronous.\nAdd SyncOp to its requirements if it is pure, or move the work to a loader (queryEffect / mutationEffect / asyncProcessEffect).',
67
+ },
68
+ },
69
+
70
+ create(context) {
71
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
72
+ const parserServices = sourceCode.parserServices ?? context.parserServices;
73
+ const checker = parserServices?.program?.getTypeChecker?.();
74
+ const nodeMap = parserServices?.esTreeNodeToTSNodeMap;
75
+ const keys = sourceCode.visitorKeys ?? {};
76
+
77
+ const effectBindings = new Set();
78
+ const syncOpBindings = new Set();
79
+
80
+ return {
81
+ ImportDeclaration(node) {
82
+ if (node.importKind === 'type') return;
83
+ const source = node.source.value;
84
+
85
+ for (const specifier of node.specifiers) {
86
+ if (
87
+ specifier.type === 'ImportNamespaceSpecifier' &&
88
+ source === EFFECT_PACKAGE
89
+ ) {
90
+ effectBindings.add(specifier.local.name);
91
+ continue;
92
+ }
93
+
94
+ if (specifier.type !== 'ImportSpecifier') continue;
95
+ const imported = getIdentifierName(specifier.imported);
96
+
97
+ if (source === EFFECT_PACKAGE && imported === 'Effect') {
98
+ effectBindings.add(specifier.local.name);
99
+ }
100
+ if (source === CRAFT_EFFECT_PACKAGE && imported === 'SyncOp') {
101
+ syncOpBindings.add(specifier.local.name);
102
+ }
103
+ }
104
+ },
105
+
106
+ CallExpression(node) {
107
+ if (!isEffectMemberCall(node.callee, 'gen')) return;
108
+
109
+ const body = node.arguments.find((argument) =>
110
+ ['FunctionExpression', 'ArrowFunctionExpression'].includes(
111
+ argument?.type,
112
+ ),
113
+ );
114
+ if (!body) return;
115
+
116
+ const delegatedYields = collectDelegatedYields(body);
117
+ if (!isDeclaredSynchronous(node, delegatedYields)) return;
118
+
119
+ for (const yielded of delegatedYields) {
120
+ inspectYield(yielded);
121
+ }
122
+ },
123
+ };
124
+
125
+ // A body is declared synchronous either by yielding the marker itself — the
126
+ // spelling for an inferred `R` — or by the shape it is contextually typed
127
+ // against already spelling `SyncOp`, in which case the implementation needs
128
+ // no ceremony at all.
129
+ function isDeclaredSynchronous(genCall, delegatedYields) {
130
+ if (delegatedYields.some(isSyncOpReference)) return true;
131
+ return contextualTypeDeclaresSync(genCall);
132
+ }
133
+
134
+ function contextualTypeDeclaresSync(genCall) {
135
+ if (!checker || !nodeMap) return false;
136
+
137
+ // Walk out to the function whose return type the shape constrains.
138
+ let current = genCall.parent;
139
+ while (
140
+ current &&
141
+ !['ArrowFunctionExpression', 'FunctionExpression'].includes(
142
+ current.type,
143
+ )
144
+ ) {
145
+ if (current.type === 'CallExpression') return false;
146
+ current = current.parent;
147
+ }
148
+ if (!current) return false;
149
+
150
+ const tsNode = nodeMap.get(current);
151
+ if (!tsNode) return false;
152
+
153
+ const contextual = checker.getContextualType?.(tsNode);
154
+ return Boolean(contextual && SYNC_OP_PATTERN.test(safeToString(contextual)));
155
+ }
156
+
157
+ function inspectYield(yielded) {
158
+ if (isSyncOpReference(yielded)) return;
159
+
160
+ const asyncConstructor = getEffectConstructorName(
161
+ yielded,
162
+ ASYNC_EFFECT_CONSTRUCTORS,
163
+ );
164
+ if (asyncConstructor) {
165
+ context.report({
166
+ node: yielded,
167
+ messageId: 'asyncConstructor',
168
+ data: { name: asyncConstructor },
169
+ });
170
+ return;
171
+ }
172
+
173
+ if (getEffectConstructorName(yielded, PURE_EFFECT_CONSTRUCTORS)) return;
174
+ // `yield* SomeService` resolves a tag from the context in place; it is the
175
+ // Layer that already ran, not a suspension.
176
+ if (isServiceTagReference(yielded)) return;
177
+ if (typeDeclaresSync(yielded)) return;
178
+ // Without type information the rule cannot tell a pure member from an
179
+ // async one, and guessing either way is worse than staying silent.
180
+ if (!checker || !nodeMap) return;
181
+ // Not an Effect at all (a craft primitive relayed through the body).
182
+ if (!isEffectType(yielded)) return;
183
+
184
+ context.report({
185
+ node: yielded,
186
+ messageId: 'notDeclaredSync',
187
+ data: { what: describe(yielded) },
188
+ });
189
+ }
190
+
191
+ function isSyncOpReference(node) {
192
+ return node?.type === 'Identifier' && syncOpBindings.has(node.name);
193
+ }
194
+
195
+ function isEffectMemberCall(callee, memberName) {
196
+ return (
197
+ callee?.type === 'MemberExpression' &&
198
+ !callee.computed &&
199
+ callee.object.type === 'Identifier' &&
200
+ effectBindings.has(callee.object.name) &&
201
+ getIdentifierName(callee.property) === memberName
202
+ );
203
+ }
204
+
205
+ function getEffectConstructorName(node, names) {
206
+ const call = node?.type === 'CallExpression' ? node : undefined;
207
+ const callee = call ? call.callee : node;
208
+
209
+ if (
210
+ callee?.type !== 'MemberExpression' ||
211
+ callee.computed ||
212
+ callee.object.type !== 'Identifier' ||
213
+ !effectBindings.has(callee.object.name)
214
+ ) {
215
+ return undefined;
216
+ }
217
+
218
+ const name = getIdentifierName(callee.property);
219
+ return name && names.has(name) ? name : undefined;
220
+ }
221
+
222
+ // A bare identifier that resolves to a CLASS is a `Context.Service` tag:
223
+ // `yield* Pricing` reads it from the context in place — the Layer already
224
+ // ran. A bare identifier merely holding an Effect is not a class, and falls
225
+ // through to the type check below.
226
+ function isServiceTagReference(node) {
227
+ if (node?.type !== 'Identifier' || !checker || !nodeMap) return false;
228
+
229
+ const tsNode = nodeMap.get(node);
230
+ if (!tsNode) return false;
231
+
232
+ const symbol = checker.getSymbolAtLocation(tsNode);
233
+ // ts.SyntaxFlags.Class — compared numerically to keep the rule free of a
234
+ // typescript import, like the other typed rules in this folder.
235
+ return Boolean(symbol && (symbol.flags & SYMBOL_FLAG_CLASS) !== 0);
236
+ }
237
+
238
+ function typeDeclaresSync(node) {
239
+ if (!checker || !nodeMap) return false;
240
+ const type = getType(node);
241
+ return Boolean(type && SYNC_OP_PATTERN.test(safeToString(type)));
242
+ }
243
+
244
+ function isEffectType(node) {
245
+ const type = getType(node);
246
+ if (!type) return false;
247
+ const text = safeToString(type);
248
+ if (/\bEffect(?:\.Effect)?\s*</.test(text)) return true;
249
+ const symbol = type.aliasSymbol ?? type.getSymbol?.();
250
+ const name = symbol && String(symbol.escapedName ?? symbol.name);
251
+ return name === 'Effect';
252
+ }
253
+
254
+ function getType(node) {
255
+ if (!checker || !nodeMap || !node) return undefined;
256
+ const tsNode = nodeMap.get(node);
257
+ if (!tsNode) return undefined;
258
+ try {
259
+ return checker.getTypeAtLocation(tsNode);
260
+ } catch {
261
+ return undefined;
262
+ }
263
+ }
264
+
265
+ function safeToString(type) {
266
+ try {
267
+ return checker.typeToString(type);
268
+ } catch {
269
+ return '';
270
+ }
271
+ }
272
+
273
+ function describe(node) {
274
+ const text = sourceCode.getText(node);
275
+ return text.length > 60 ? `${text.slice(0, 57)}…` : text;
276
+ }
277
+
278
+ function collectDelegatedYields(functionNode) {
279
+ const found = [];
280
+ walk(
281
+ functionNode,
282
+ (child) => {
283
+ // A nested `Effect.gen` carries its own declaration and gets its own
284
+ // visit — descending into it here would blame the wrong body.
285
+ if (child !== functionNode && isEffectGenCall(child)) {
286
+ return 'skip';
287
+ }
288
+
289
+ if (child.type === 'YieldExpression' && child.delegate) {
290
+ const argument = unwrap(child.argument);
291
+ if (argument) found.push(argument);
292
+ }
293
+
294
+ return undefined;
295
+ },
296
+ keys,
297
+ );
298
+ return found;
299
+ }
300
+
301
+ function isEffectGenCall(node) {
302
+ return node.type === 'CallExpression' && isEffectMemberCall(node.callee, 'gen');
303
+ }
304
+ },
305
+ };
306
+
307
+ function getIdentifierName(node) {
308
+ if (!node) return undefined;
309
+ if (node.type === 'Identifier') return node.name;
310
+ if (node.type === 'Literal' && typeof node.value === 'string') {
311
+ return node.value;
312
+ }
313
+ return undefined;
314
+ }
315
+
316
+ function unwrap(node) {
317
+ let current = node;
318
+ while (
319
+ current &&
320
+ (current.type === 'TSAsExpression' ||
321
+ current.type === 'TSNonNullExpression' ||
322
+ current.type === 'TSSatisfiesExpression')
323
+ ) {
324
+ current = current.expression;
325
+ }
326
+ return current;
327
+ }
328
+
329
+ function walk(node, visit, keys) {
330
+ if (!node || typeof node.type !== 'string') return;
331
+ if (visit(node) === 'skip') return;
332
+
333
+ const childKeys = keys[node.type] ?? Object.keys(node);
334
+ for (const key of childKeys) {
335
+ if (key === 'parent') continue;
336
+ const value = node[key];
337
+ if (Array.isArray(value)) {
338
+ for (const child of value) walk(child, visit, keys);
339
+ } else if (value && typeof value.type === 'string') {
340
+ walk(value, visit, keys);
341
+ }
342
+ }
343
+ }
package/src/index.d.ts CHANGED
@@ -17,5 +17,9 @@ export * from './scripts/components/migration-diagnostic.js';
17
17
  export * from './scripts/architecture/migrate-architecture.js';
18
18
  export * from './scripts/dependency-graph.js';
19
19
  export * from './scripts/architecture-graph.js';
20
+ export * from './scripts/style-graph.js';
21
+ export * from './scripts/style-architecture.js';
22
+ export * from './scripts/style-report.js';
23
+ export * from './scripts/security-check.js';
20
24
  export * from './scripts/create/create-project.js';
21
25
  export * from './template-migration.js';
package/src/index.js CHANGED
@@ -18,6 +18,10 @@ export * from './scripts/components/migration-diagnostic.js';
18
18
  export * from './scripts/architecture/migrate-architecture.js';
19
19
  export * from './scripts/dependency-graph.js';
20
20
  export * from './scripts/architecture-graph.js';
21
+ export * from './scripts/style-graph.js';
22
+ export * from './scripts/style-architecture.js';
23
+ export * from './scripts/style-report.js';
24
+ export * from './scripts/security-check.js';
21
25
  export * from './scripts/create/create-project.js';
22
26
  export * from './template-migration.js';
23
27
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/dev-tools/src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/dev-tools/src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kDAAkD,CAAC;AACjE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AACnD,cAAc,yBAAyB,CAAC"}
@@ -1161,8 +1161,14 @@ export function primitiveLoaderRequirementViolations(graph, rule) {
1161
1161
  const label = primitiveDisplayName(primitive);
1162
1162
  if (allowed.has(label) || allowed.has(primitive.label))
1163
1163
  return [];
1164
- const loaderEdges = graph.edges.filter((edge) => edge.from === primitive.id &&
1165
- edge.details?.['resourceRole'] === 'loader');
1164
+ const loaderEdges = graph.edges.filter((edge) => {
1165
+ if (edge.from !== primitive.id)
1166
+ return false;
1167
+ const role = edge.details?.['resourceRole'];
1168
+ return (role === 'loader' ||
1169
+ (primitive.details?.['primitive'] === 'computedEffect' &&
1170
+ role === 'computed'));
1171
+ });
1166
1172
  const satisfied = loaderEdges.some((edge) => {
1167
1173
  const target = nodesById.get(edge.to);
1168
1174
  if (!target)