@hkdigital/lib-core 0.5.65 → 0.5.67

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
@@ -277,6 +277,43 @@ pnpm run upgrade:all # Update all packages
277
277
  pnpm run publish:npm # Version bump and publish to npm
278
278
  ```
279
279
 
280
+ ### Import Validation
281
+
282
+ The library includes a validation script to enforce consistent import
283
+ patterns. Run it in your project:
284
+
285
+ ```bash
286
+ node node_modules/@hkdigital/lib-core/scripts/validate-imports.mjs
287
+ ```
288
+
289
+ **Validation rules (enforced for `src/lib/` files only):**
290
+
291
+ 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.,
294
+ `.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
297
+
298
+ **Routes are exempt from strict rules:**
299
+
300
+ Files in `src/routes/` can use relative imports freely, including parent
301
+ navigation and index.js imports. Since SvelteKit doesn't provide a
302
+ `$routes` alias, relative imports are the standard pattern for route
303
+ files.
304
+
305
+ **Example output:**
306
+
307
+ ```
308
+ src/lib/ui/components/Button.svelte:7
309
+ from '$lib/ui/primitives/buttons'
310
+ => from '$lib/ui/primitives/buttons/index.js'
311
+
312
+ src/routes/explorer/[...path]/+page.svelte:4
313
+ from '../components/index.js'
314
+ ✅ Allowed in routes
315
+ ```
316
+
280
317
  ### Import Patterns and Export Structure
281
318
 
282
319
  **Public exports use domain-specific files matching folder names:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.65",
3
+ "version": "0.5.67",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -94,6 +94,8 @@ async function validateFile(filePath) {
94
94
  const relativePath = relative(PROJECT_ROOT, filePath);
95
95
  const isTestFile = filePath.endsWith('.test.js') ||
96
96
  filePath.endsWith('.spec.js');
97
+ const isInLib = filePath.includes('/src/lib/');
98
+ const isInRoutes = filePath.includes('/src/routes/');
97
99
 
98
100
  // Check each line for import statements
99
101
  const lines = content.split('\n');
@@ -125,17 +127,20 @@ async function validateFile(filePath) {
125
127
  }
126
128
 
127
129
  // Check 1: Cross-domain relative imports (3+ levels up)
128
- if (importPath.match(/^\.\.\/\.\.\/\.\.\//)) {
130
+ // Only enforce for lib files
131
+ if (isInLib && importPath.match(/^\.\.\/\.\.\/\.\.\//)) {
129
132
  errors.push(
130
- `${relativePath}:${lineNum} - Cross-domain relative import ` +
131
- `(use $lib/ instead)`
133
+ `${relativePath}:${lineNum}\n` +
134
+ ` from '${importPath}'\n` +
135
+ ` => Use $lib/ for cross-domain imports`
132
136
  );
133
137
  continue;
134
138
  }
135
139
 
136
- // Check 2: Local index.js imports (skip for test files)
140
+ // Check 2: Local index.js imports (skip for test files and routes)
137
141
  // Allow ./index.js and ./subfolder/index.js but flag parent navigation
138
- if (!isTestFile && importPath.match(/\/index\.js$/)) {
142
+ // Only enforce for lib files
143
+ if (isInLib && !isTestFile && importPath.match(/\/index\.js$/)) {
139
144
  // Allow same-directory and child directory imports
140
145
  // Examples: ./index.js, ./subfolder/index.js, ./(meta)/index.js
141
146
  // Flag parent navigation: ../index.js, ../../index.js
@@ -143,8 +148,9 @@ async function validateFile(filePath) {
143
148
 
144
149
  if (isParentNavigation) {
145
150
  errors.push(
146
- `${relativePath}:${lineNum} - Parent index.js import ` +
147
- `(use $lib/ or import specific file)`
151
+ `${relativePath}:${lineNum}\n` +
152
+ ` from '${importPath}'\n` +
153
+ ` => Use $lib/ or import specific file instead`
148
154
  );
149
155
  continue;
150
156
  }
@@ -254,8 +260,9 @@ async function validateFile(filePath) {
254
260
  if (stats.isFile()) {
255
261
  const correctImport = baseImportPath + ext;
256
262
  errors.push(
257
- `${relativePath}:${lineNum} - Missing non-standard ` +
258
- `extension (use '${correctImport}')`
263
+ `${relativePath}:${lineNum}\n` +
264
+ ` from '${importPath}'\n` +
265
+ ` => from '${correctImport}'`
259
266
  );
260
267
  foundNonStandard = true;
261
268
  break;
@@ -280,24 +287,37 @@ async function validateFile(filePath) {
280
287
  if (!hasAnyExtension) {
281
288
  // Check if it's a directory with index.js
282
289
  if (await isDirectoryWithIndex(fsPath)) {
283
- // Don't suggest ../path/index.js (parent navigation)
284
- // Suggest creating export file instead
285
- const wouldBeParentNavigation = importPath.match(/^\.\.\//);
290
+ // For $lib/ imports, suggest creating a barrel export file
291
+ // following the library pattern: folder → matching .js file
292
+ if (importPath.startsWith('$lib/')) {
293
+ errors.push(
294
+ `${relativePath}:${lineNum}\n` +
295
+ ` from '${importPath}'\n` +
296
+ ` => Create export file: ${importPath}.js (remove index.js)`
297
+ );
298
+ continue;
299
+ }
286
300
 
301
+ // For relative imports with parent navigation,
302
+ // suggest creating export file
303
+ const wouldBeParentNavigation = importPath.match(/^\.\.\//);
287
304
  if (wouldBeParentNavigation) {
288
305
  errors.push(
289
- `${relativePath}:${lineNum} - Directory import requires ` +
290
- `parent navigation (create export file like '${importPath}.js' ` +
291
- `or import specific file)`
306
+ `${relativePath}:${lineNum}\n` +
307
+ ` from '${importPath}'\n` +
308
+ ` => Create export file: ${importPath}.js or ` +
309
+ `import specific file`
292
310
  );
293
311
  continue;
294
312
  }
295
313
 
314
+ // For local relative imports (./path), suggest index.js
296
315
  const suggestion = importPath.endsWith('/') ?
297
316
  `${importPath}index.js` : `${importPath}/index.js`;
298
317
  errors.push(
299
- `${relativePath}:${lineNum} - Directory import ` +
300
- `(write explicitly: '${suggestion}')`
318
+ `${relativePath}:${lineNum}\n` +
319
+ ` from '${importPath}'\n` +
320
+ ` => from '${suggestion}'`
301
321
  );
302
322
  continue;
303
323
  }
@@ -336,8 +356,9 @@ async function validateFile(filePath) {
336
356
 
337
357
  if (!fileExists) {
338
358
  errors.push(
339
- `${relativePath}:${lineNum} - Import path does not exist: ` +
340
- `'${importPath}'`
359
+ `${relativePath}:${lineNum}\n` +
360
+ ` from '${importPath}'\n` +
361
+ ` => Import path does not exist`
341
362
  );
342
363
  }
343
364
  }
@@ -361,8 +382,8 @@ async function main() {
361
382
 
362
383
  if (allErrors.length > 0) {
363
384
  console.error('❌ Found import path violations:\n');
364
- allErrors.forEach(error => console.error(` ${error}`));
365
- console.error(`\n${allErrors.length} error(s) found.`);
385
+ allErrors.forEach(error => console.error(`${error}\n`));
386
+ console.error(`${allErrors.length} error(s) found.`);
366
387
  process.exit(1);
367
388
  }
368
389