@hkdigital/lib-core 0.5.67 → 0.5.69

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/README.md CHANGED
@@ -280,20 +280,68 @@ pnpm run publish:npm # Version bump and publish to npm
280
280
  ### Import Validation
281
281
 
282
282
  The library includes a validation script to enforce consistent import
283
- patterns. Run it in your project:
283
+ patterns across both your project files and external `@hkdigital/*`
284
+ package imports.
285
+
286
+ **Add to your project's package.json:**
287
+
288
+ ```json
289
+ {
290
+ "scripts": {
291
+ "lint:imports": "node node_modules/@hkdigital/lib-core/scripts/validate-imports.mjs"
292
+ }
293
+ }
294
+ ```
295
+
296
+ **Run validation:**
284
297
 
285
298
  ```bash
299
+ # Using npm script (recommended)
300
+ pnpm run lint:imports
301
+
302
+ # Or directly
286
303
  node node_modules/@hkdigital/lib-core/scripts/validate-imports.mjs
287
304
  ```
288
305
 
289
306
  **Validation rules (enforced for `src/lib/` files only):**
290
307
 
291
308
  1. **Cross-domain imports** - Use `$lib/` instead of `../../../`
292
- 2. **Parent index.js imports** - Use `$lib/` or import specific files
293
- 3. **Non-standard extensions** - Include full extension (e.g.,
309
+ 2. **Prefer barrel exports** - Use higher-level export files when available
310
+ 3. **Parent index.js imports** - Use `$lib/` or import specific files
311
+ 4. **Non-standard extensions** - Include full extension (e.g.,
294
312
  `.svelte.js`)
295
- 4. **Directory imports** - Write explicitly (e.g., `./path/index.js`)
296
- 5. **File existence** - All import paths must resolve to existing files
313
+ 5. **Directory imports** - Write explicitly or create barrel export file
314
+ 6. **File existence** - All import paths must resolve to existing files
315
+ 7. **External package optimization** - Suggests barrel exports for
316
+ `@hkdigital/*` packages
317
+
318
+ **Barrel export preference:**
319
+
320
+ The validator suggests using the highest-level barrel export file that
321
+ exports your target. This encourages shorter imports that can be
322
+ combined:
323
+
324
+ ```js
325
+ // Internal imports - instead of deep imports:
326
+ import ProfileBlocks from '$lib/ui/components/profile-blocks/ProfileBlocks.svelte';
327
+ import Button from '$lib/ui/primitives/buttons/Button.svelte';
328
+
329
+ // Use barrel exports:
330
+ import { ProfileBlocks } from '$lib/ui/components.js';
331
+ import { Button } from '$lib/ui/primitives.js';
332
+
333
+ // External imports - instead of deep imports:
334
+ import { TextButton } from '@hkdigital/lib-core/ui/primitives/buttons/index.js';
335
+ import { TextInput } from '@hkdigital/lib-core/ui/primitives/inputs/index.js';
336
+
337
+ // Use barrel exports:
338
+ import { TextButton, TextInput } from '@hkdigital/lib-core/ui/primitives.js';
339
+ ```
340
+
341
+ The validator checks from highest to lowest level (`$lib/ui.js` →
342
+ `$lib/ui/components.js` → `$lib/ui/components/profile-blocks.js`) and
343
+ suggests the highest-level file that exports your target. The same
344
+ logic applies to external `@hkdigital/*` packages.
297
345
 
298
346
  **Routes are exempt from strict rules:**
299
347
 
@@ -305,15 +353,33 @@ files.
305
353
  **Example output:**
306
354
 
307
355
  ```
308
- src/lib/ui/components/Button.svelte:7
309
- from '$lib/ui/primitives/buttons'
310
- => from '$lib/ui/primitives/buttons/index.js'
356
+ src/lib/ui/panels/Panel.svelte:6
357
+ from '../../../components/profile-blocks/ProfileBlocks.svelte'
358
+ => from '$lib/ui/components.js' (use barrel export)
359
+
360
+ src/lib/ui/pages/Profile.svelte:8
361
+ from '$lib/ui/components/profile-blocks/ProfileBlocks.svelte'
362
+ => from '$lib/ui/components.js' (use barrel export for shorter imports)
363
+
364
+ src/lib/forms/LoginForm.svelte:4
365
+ from '@hkdigital/lib-core/ui/primitives/buttons/index.js'
366
+ => from '@hkdigital/lib-core/ui/primitives.js' (use barrel export)
311
367
 
312
368
  src/routes/explorer/[...path]/+page.svelte:4
313
369
  from '../components/index.js'
314
370
  ✅ Allowed in routes
315
371
  ```
316
372
 
373
+ **What gets checked for external packages:**
374
+
375
+ The validator only suggests barrel exports for:
376
+ - Explicit `index.js` imports
377
+ - Component files (`.svelte`)
378
+ - Class files (capitalized `.js` files)
379
+
380
+ Intentional imports like `helpers.js`, `config.js`, or other lowercase
381
+ utility files are assumed to be the public API and won't be flagged.
382
+
317
383
  ### Import Patterns and Export Structure
318
384
 
319
385
  **Public exports use domain-specific files matching folder names:**
package/claude.md CHANGED
@@ -112,18 +112,24 @@ This is a modern SvelteKit library built with Svelte 5 and Skeleton.dev v3 compo
112
112
  - Use `$lib/domain/...` imports for cross-domain references (e.g., `$lib/media/image.js`, `$lib/network/http.js`)
113
113
  - Use relative imports (`./` or `../`) when staying within the same main folder under `$lib`
114
114
  - **Always include file extensions** (`.js`, `.svelte`) in import statements
115
+ - **Prefer barrel exports** - Use higher-level export files when available (e.g., `$lib/ui/components.js` instead of `$lib/ui/components/profile/ProfileBlocks.svelte`)
115
116
  - **For cross-domain imports, use specific export files** (e.g., `parsers.js`, `valibot.js`) rather than directory-only paths - this ensures compatibility outside the library
116
117
  - **For local imports within the same domain**, import specific files directly (e.g., `./ClassName.js`) rather than using local index files
117
118
  - Examples:
119
+ - ✅ `import { ProfileBlocks } from '$lib/ui/components.js'` (barrel export - preferred)
120
+ - ✅ `import { Button } from '$lib/ui/primitives.js'` (barrel export - preferred)
118
121
  - ✅ `import { ImageLoader } from '$lib/media/image.js'` (cross-domain import)
119
122
  - ✅ `import ImageLoader from './ImageLoader.svelte.js'` (local import within same domain)
120
123
  - ✅ `import IterableTree from './IterableTree.js'` (local import within same domain)
121
124
  - ✅ `import { v } from '$lib/valibot/valibot.js'` (cross-domain with specific export file)
122
125
  - ✅ `import { HumanUrl, Email } from '$lib/valibot/parsers.js'` (cross-domain with specific export file)
126
+ - ❌ `import ProfileBlocks from '$lib/ui/components/profile/ProfileBlocks.svelte'` (deep import when barrel exists)
123
127
  - ❌ `import { v, HumanUrl } from '$lib/valibot'` (missing specific export file)
124
128
  - ❌ `import { IterableTree } from './index.js'` (local index when specific file should be used)
125
129
  - ❌ `import something from '../../media/image.js'` (cross-domain relative import)
126
130
 
131
+ **Import validation:** Run `node scripts/validate-imports.mjs` to validate import patterns. The validator checks for barrel export files at each level and suggests the highest-level file that exports your target. These rules are enforced for `src/lib/` files only. Files in `src/routes/` can use relative imports freely. See README.md "Import Validation" section for usage in other projects.
132
+
127
133
  ## Class Export Conventions
128
134
  - **All classes should be default exports**: `export default class ClassName`
129
135
  - **Import classes without destructuring**: `import ClassName from './ClassName.js'`
@@ -1,5 +1,4 @@
1
- import MemoryResponseCache from '../cache/MemoryResponseCache.js';
2
- import IndexedDbCache from '../cache/IndexedDbCache.js';
1
+ import { MemoryResponseCache, IndexedDbCache } from '../cache.js';
3
2
 
4
3
  import { browser } from '$app/environment';
5
4
 
@@ -53,4 +53,4 @@ export default class FiniteStateMachine extends EventEmitter {
53
53
  export type TransitionData = import("./typedef.js").TransitionData;
54
54
  export type OnEnterCallback = import("./typedef.js").OnEnterCallback;
55
55
  export type OnExitCallback = import("./typedef.js").OnExitCallback;
56
- import EventEmitter from '../../../generic/events/classes/EventEmitter.js';
56
+ import { EventEmitter } from '../../../generic/events.js';
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { isTestEnv } from '../../../util/env.js';
8
- import EventEmitter from '../../../generic/events/classes/EventEmitter.js';
8
+ import { EventEmitter } from '../../../generic/events.js';
9
9
  import { ENTER, EXIT } from './constants.js';
10
10
 
11
11
  /** @typedef {import('./typedef.js').TransitionData} TransitionData */
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { onMount } from 'svelte';
9
- import { findFirst } from '../../../util/array/index.js';
9
+ import { findFirst } from '../../../util/array.js';
10
10
 
11
11
  /**
12
12
  * @type {{
@@ -20,7 +20,7 @@
20
20
  * </AppLayout>
21
21
  */
22
22
 
23
- import { useResizeObserver } from '../../../util/svelte/observe/index.js';
23
+ import { useResizeObserver } from '../../../util/svelte.js';
24
24
 
25
25
  /**
26
26
  * @typedef AppLayoutProps
@@ -1,7 +1,6 @@
1
1
  <script>
2
- import { ImageLoader } from '../../../network/loaders.js';
3
- import { ImageVariantsLoader } from '../../../network/loaders.js';
4
- import { toSingleImageMeta } from '../../../network/loaders/image/utils/index.js';
2
+ import { ImageLoader, ImageVariantsLoader } from '../../../network/loaders.js';
3
+ import { toSingleImageMeta } from '../../../network/loaders/image.js';
5
4
 
6
5
  /**
7
6
  * @type {{
@@ -1,8 +1,6 @@
1
- import { tick } from 'svelte';
1
+ import { tick, untrack } from 'svelte';
2
2
 
3
- import { findFirst } from '../../../util/array/index.js';
4
-
5
- import { untrack } from 'svelte';
3
+ import { findFirst } from '../../../util/array.js';
6
4
 
7
5
  import { HkPromise } from '../../../generic/promises.js';
8
6
 
@@ -2,7 +2,7 @@ import { tick } from 'svelte';
2
2
 
3
3
  import * as expect from '../../../util/expect.js';
4
4
 
5
- import { pushNotEmpty } from '../../../util/array/index.js';
5
+ import { pushNotEmpty } from '../../../util/array.js';
6
6
 
7
7
  import { TRANSITION_CSS, FADE_IN, FADE_OUT } from './constants.js';
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { defineStateContext } from '../../../state/context.js';
2
2
 
3
- import { useResizeObserver } from '../../../util/svelte/observe/index.js';
3
+ import { useResizeObserver } from '../../../util/svelte.js';
4
4
 
5
5
  /* ------------------------------------------------------- Define state class */
6
6
 
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import Button from '../button/Button.svelte';
3
3
 
4
- import { SteezeIcon } from '../../icons/index.js';
4
+ import { SteezeIcon } from '../../../primitives.js';
5
5
 
6
6
  /**
7
7
  * @type {{
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Star, ExclamationTriangle, CheckCircle } from '@steeze-ui/heroicons';
6
6
 
7
- import { HkIcon } from '../../icons/index.js';
7
+ import { HkIcon } from '../../../primitives.js';
8
8
 
9
9
  import {
10
10
  PRISTINE,
@@ -1,4 +1,4 @@
1
- import { PATH_SEPARATOR } from '../object/index.js';
1
+ import { PATH_SEPARATOR } from '../object.js';
2
2
 
3
3
  /**
4
4
  * Convert a path string to an array path
@@ -1,6 +1,6 @@
1
1
  import * as expect from '../expect.js';
2
2
 
3
- import { toArrayPath } from '../array/index.js';
3
+ import { toArrayPath } from '../array.js';
4
4
 
5
5
  import { objectGet, PATH_SEPARATOR } from '../object.js';
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.67",
3
+ "version": "0.5.69",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -6,6 +6,12 @@ import { join, relative, resolve, dirname } from 'node:path';
6
6
  const PROJECT_ROOT = process.cwd();
7
7
  const SRC_DIR = join(PROJECT_ROOT, 'src');
8
8
 
9
+ /**
10
+ * Scopes to validate for barrel exports
11
+ * Any package under these scopes will be checked
12
+ */
13
+ const EXTERNAL_SCOPES_TO_VALIDATE = ['@hkdigital'];
14
+
9
15
  /**
10
16
  * Find all JS and Svelte files recursively
11
17
  *
@@ -31,6 +37,66 @@ async function findFiles(dir) {
31
37
  return files;
32
38
  }
33
39
 
40
+ /**
41
+ * Find highest-level barrel export file that exports the target
42
+ *
43
+ * For a path like $lib/ui/components/profile-blocks/ProfileBlocks.svelte:
44
+ * - Check $lib/ui.js
45
+ * - Check $lib/ui/components.js
46
+ * - Check $lib/ui/components/profile-blocks.js
47
+ *
48
+ * @param {string} importPath - Import path (e.g., $lib/ui/components/...)
49
+ * @param {string} filePath - Source file making the import
50
+ *
51
+ * @returns {Promise<string|null>} Barrel file path or null
52
+ */
53
+ async function findBarrelExportFile(importPath, filePath) {
54
+ if (!importPath.startsWith('$lib/')) return null;
55
+
56
+ // Remove $lib/ prefix and extract path segments
57
+ const pathWithoutLib = importPath.replace('$lib/', '');
58
+ const parts = pathWithoutLib.split('/');
59
+
60
+ // Extract the target filename (last part, possibly with extension)
61
+ const targetFile = parts[parts.length - 1];
62
+ const targetBase = targetFile.replace(/\.(js|svelte)$/, '');
63
+
64
+ // Check from highest level down (but skip the filename itself)
65
+ for (let i = 1; i < parts.length; i++) {
66
+ const barrelPath = '$lib/' + parts.slice(0, i).join('/') + '.js';
67
+ const fsBarrelPath = join(
68
+ PROJECT_ROOT,
69
+ barrelPath.replace('$lib/', 'src/lib/')
70
+ );
71
+
72
+ try {
73
+ const stats = await stat(fsBarrelPath);
74
+ if (stats.isFile()) {
75
+ // Check if this barrel file exports our target
76
+ const content = await readFile(fsBarrelPath, 'utf-8');
77
+
78
+ // Look for exports that match the target file
79
+ // Patterns:
80
+ // export { ProfileBlocks } from './path/ProfileBlocks.svelte';
81
+ // export * from './path/ProfileBlocks.svelte';
82
+ // More flexible pattern to catch various export styles
83
+ const exportPattern = new RegExp(
84
+ `export\\s+(?:\\*|\\{[^}]*\\})\\s+from\\s+['"](?:.*/)?(${targetBase}(?:\\.(?:js|svelte))?)['"]`,
85
+ 'gm'
86
+ );
87
+
88
+ if (exportPattern.test(content)) {
89
+ return barrelPath;
90
+ }
91
+ }
92
+ } catch {
93
+ // File doesn't exist or can't be read, continue
94
+ }
95
+ }
96
+
97
+ return null;
98
+ }
99
+
34
100
  /**
35
101
  * Check if import path resolves to directory with index.js
36
102
  *
@@ -81,6 +147,137 @@ async function isDirectoryWithIndex(fsPath) {
81
147
  return false;
82
148
  }
83
149
 
150
+ /**
151
+ * Extract imported names from import statement
152
+ *
153
+ * @param {string} line - Import statement line
154
+ *
155
+ * @returns {string[]} Array of imported names
156
+ */
157
+ function extractImportNames(line) {
158
+ const names = [];
159
+
160
+ // Default import: import Foo from '...'
161
+ const defaultMatch = line.match(/import\s+(\w+)\s+from/);
162
+ if (defaultMatch) {
163
+ names.push(defaultMatch[1]);
164
+ }
165
+
166
+ // Named imports: import { Foo, Bar } from '...'
167
+ const namedMatch = line.match(/import\s+\{([^}]+)\}\s+from/);
168
+ if (namedMatch) {
169
+ const namedImports = namedMatch[1]
170
+ .split(',')
171
+ .map(name => {
172
+ // Handle 'as' aliases: { Foo as Bar } → extract 'Foo'
173
+ const parts = name.trim().split(/\s+as\s+/);
174
+ return parts[0].trim();
175
+ })
176
+ .filter(name => name && name !== '*');
177
+ names.push(...namedImports);
178
+ }
179
+
180
+ return names;
181
+ }
182
+
183
+ /**
184
+ * Find highest-level barrel export in external package
185
+ *
186
+ * For @hkdigital/lib-core/ui/primitives/buttons/index.js:
187
+ * - Check @hkdigital/lib-core/ui/primitives.js
188
+ * - Check @hkdigital/lib-core/ui.js
189
+ *
190
+ * @param {string} importPath - External import path
191
+ * @param {string} targetName - Name of export to find
192
+ *
193
+ * @returns {Promise<string|null>} Suggested barrel path or null
194
+ */
195
+ async function findExternalBarrelExport(importPath, targetName) {
196
+ // Extract package name (handle scoped packages)
197
+ const parts = importPath.split('/');
198
+ let pkgName;
199
+ let pathInPackage;
200
+
201
+ if (importPath.startsWith('@')) {
202
+ // Scoped package: @scope/package/path/to/file
203
+ pkgName = `${parts[0]}/${parts[1]}`;
204
+ pathInPackage = parts.slice(2);
205
+ } else {
206
+ // Regular package: package/path/to/file
207
+ pkgName = parts[0];
208
+ pathInPackage = parts.slice(1);
209
+ }
210
+
211
+ // Check if this scope should be validated
212
+ const scope = pkgName.startsWith('@') ?
213
+ pkgName.split('/')[0] : null;
214
+ if (scope && !EXTERNAL_SCOPES_TO_VALIDATE.includes(scope)) {
215
+ return null;
216
+ }
217
+
218
+ // If no path in package, nothing to suggest
219
+ if (pathInPackage.length === 0) return null;
220
+
221
+ const nodeModulesPath = join(PROJECT_ROOT, 'node_modules', pkgName);
222
+
223
+ // Extract target to find (last part without extension)
224
+ const lastPart = pathInPackage[pathInPackage.length - 1];
225
+ const targetBase = lastPart.replace(/\.(js|svelte)$/, '');
226
+
227
+ // Only check for specific import types (matches internal logic)
228
+ // 1. Explicit index.js imports
229
+ // 2. Component files (.svelte)
230
+ // 3. Class files (capitalized .js)
231
+ let shouldCheck = false;
232
+
233
+ if (lastPart === 'index.js') {
234
+ shouldCheck = true;
235
+ } else if (lastPart.endsWith('.svelte')) {
236
+ shouldCheck = true;
237
+ } else if (lastPart.match(/^[A-Z][^/]*\.js$/)) {
238
+ shouldCheck = true;
239
+ }
240
+
241
+ if (!shouldCheck) return null;
242
+
243
+ // Try progressively higher-level barrel files
244
+ for (let i = 1; i < pathInPackage.length; i++) {
245
+ const barrelPath = pathInPackage.slice(0, i).join('/') + '.js';
246
+ const fsBarrelPath = join(nodeModulesPath, barrelPath);
247
+
248
+ try {
249
+ const stats = await stat(fsBarrelPath);
250
+ if (stats.isFile()) {
251
+ const content = await readFile(fsBarrelPath, 'utf-8');
252
+
253
+ // Check if this barrel exports our target
254
+ // Patterns to match:
255
+ // export { TextButton } from './path';
256
+ // export * from './path';
257
+ const exportPatterns = [
258
+ // Named export with exact name
259
+ new RegExp(
260
+ `export\\s+\\{[^}]*\\b${targetName}\\b[^}]*\\}`,
261
+ 'm'
262
+ ),
263
+ // Re-export all
264
+ /export\s+\*\s+from/,
265
+ // Default export
266
+ new RegExp(`export\\s+default\\s+${targetName}\\b`, 'm')
267
+ ];
268
+
269
+ if (exportPatterns.some(pattern => pattern.test(content))) {
270
+ return `${pkgName}/${barrelPath}`;
271
+ }
272
+ }
273
+ } catch {
274
+ // File doesn't exist, continue
275
+ }
276
+ }
277
+
278
+ return null;
279
+ }
280
+
84
281
  /**
85
282
  * Validate import paths in a file
86
283
  *
@@ -119,21 +316,69 @@ async function validateFile(filePath) {
119
316
  // Strip query parameters (Vite asset imports like ?preset=render)
120
317
  const importPath = importPathRaw.split('?')[0];
121
318
 
122
- // Skip external packages (no ./ or $lib prefix)
123
- if (!importPath.startsWith('./') &&
124
- !importPath.startsWith('../') &&
125
- !importPath.startsWith('$lib/')) {
319
+ // Check external packages from configured scopes
320
+ const isExternalPackage = !importPath.startsWith('./') &&
321
+ !importPath.startsWith('../') &&
322
+ !importPath.startsWith('$lib/');
323
+
324
+ if (isExternalPackage) {
325
+ // Extract package name/scope
326
+ const parts = importPath.split('/');
327
+ const scope = importPath.startsWith('@') ? parts[0] : null;
328
+
329
+ // Check if this scope should be validated
330
+ if (scope && EXTERNAL_SCOPES_TO_VALIDATE.includes(scope)) {
331
+ // Extract imported names from the import statement
332
+ const importedNames = extractImportNames(line);
333
+
334
+ // Check each imported name for barrel exports
335
+ for (const importedName of importedNames) {
336
+ const barrelPath = await findExternalBarrelExport(
337
+ importPath,
338
+ importedName
339
+ );
340
+
341
+ if (barrelPath) {
342
+ errors.push(
343
+ `${relativePath}:${lineNum}\n` +
344
+ ` from '${importPath}'\n` +
345
+ ` => from '${barrelPath}' (use barrel export)`
346
+ );
347
+ break; // Only report once per line
348
+ }
349
+ }
350
+ }
351
+
352
+ // Skip further validation for external packages
126
353
  continue;
127
354
  }
128
355
 
129
356
  // Check 1: Cross-domain relative imports (3+ levels up)
130
357
  // Only enforce for lib files
131
358
  if (isInLib && importPath.match(/^\.\.\/\.\.\/\.\.\//)) {
132
- errors.push(
133
- `${relativePath}:${lineNum}\n` +
134
- ` from '${importPath}'\n` +
135
- ` => Use $lib/ for cross-domain imports`
359
+ // Convert relative path to $lib/ path
360
+ const fsPath = resolve(dirname(filePath), importPath);
361
+ const libPath = fsPath.replace(
362
+ join(PROJECT_ROOT, 'src/lib/'),
363
+ '$lib/'
136
364
  );
365
+
366
+ // Check for barrel export files
367
+ const barrelFile = await findBarrelExportFile(libPath, filePath);
368
+
369
+ if (barrelFile) {
370
+ errors.push(
371
+ `${relativePath}:${lineNum}\n` +
372
+ ` from '${importPath}'\n` +
373
+ ` => from '${barrelFile}' (use barrel export)`
374
+ );
375
+ } else {
376
+ errors.push(
377
+ `${relativePath}:${lineNum}\n` +
378
+ ` from '${importPath}'\n` +
379
+ ` => from '${libPath}'`
380
+ );
381
+ }
137
382
  continue;
138
383
  }
139
384
 
@@ -323,7 +568,52 @@ async function validateFile(filePath) {
323
568
  }
324
569
  }
325
570
 
326
- // Check 5: File existence (after all other checks)
571
+ // Check 5: Suggest barrel exports for deep $lib/ imports
572
+ // Only for specific cases, not for named export files
573
+ if (isInLib && importPath.startsWith('$lib/')) {
574
+ const pathWithoutLib = importPath.replace('$lib/', '');
575
+ const segments = pathWithoutLib.split('/');
576
+
577
+ // Only suggest barrel exports for:
578
+ // 1. Directory imports (no extension, resolves to index.js)
579
+ // 2. Explicit index.js imports
580
+ // 3. Deep component/class files (.svelte or capitalized .js files)
581
+ let shouldCheckBarrel = false;
582
+
583
+ if (segments.length >= 3) {
584
+ const lastSegment = segments[segments.length - 1];
585
+
586
+ // Case 1: Explicit index.js import
587
+ if (lastSegment === 'index.js') {
588
+ shouldCheckBarrel = true;
589
+ }
590
+ // Case 2: Directory import (will be caught by Check 4)
591
+ // Case 3: Component or class file (.svelte or capitalized)
592
+ else if (lastSegment.endsWith('.svelte')) {
593
+ shouldCheckBarrel = true;
594
+ } else if (lastSegment.match(/^[A-Z][^/]*\.js$/)) {
595
+ // Capitalized .js file (likely a class)
596
+ shouldCheckBarrel = true;
597
+ }
598
+ // Skip named export files like methods.js, http.js, etc.
599
+ // These are lowercase and ARE the public API
600
+ }
601
+
602
+ if (shouldCheckBarrel) {
603
+ const barrelFile = await findBarrelExportFile(importPath, filePath);
604
+
605
+ if (barrelFile) {
606
+ errors.push(
607
+ `${relativePath}:${lineNum}\n` +
608
+ ` from '${importPath}'\n` +
609
+ ` => from '${barrelFile}' (use barrel export for shorter imports)`
610
+ );
611
+ continue;
612
+ }
613
+ }
614
+ }
615
+
616
+ // Check 6: File existence (after all other checks)
327
617
  // Verify that the import path resolves to an existing file
328
618
  // Follow Node.js/Vite resolution order
329
619
  let fileExists = false;