@hkdigital/lib-core 0.5.76 → 0.5.78
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/claude.md +45 -0
- package/package.json +1 -1
- package/scripts/README.md +65 -0
- package/scripts/validate-imports.mjs +125 -0
package/claude.md
CHANGED
|
@@ -130,6 +130,51 @@ This is a modern SvelteKit library built with Svelte 5 and Skeleton.dev v3 compo
|
|
|
130
130
|
|
|
131
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
132
|
|
|
133
|
+
### Critical: Aliases in Libraries vs Apps
|
|
134
|
+
|
|
135
|
+
**IMPORTANT**: Due to Vite/SvelteKit limitations, aliases in library
|
|
136
|
+
code (`src/lib/**`) must resolve to paths **inside the project folder**.
|
|
137
|
+
Aliases to external packages or paths outside the project break when
|
|
138
|
+
building libraries.
|
|
139
|
+
|
|
140
|
+
**The problem:**
|
|
141
|
+
```javascript
|
|
142
|
+
// ❌ These aliases don't work in src/lib/** files:
|
|
143
|
+
// svelte.config.js
|
|
144
|
+
alias: {
|
|
145
|
+
'$ext': 'node_modules/@hkdigital/lib-core/dist', // Outside project
|
|
146
|
+
'$pkg': '@hkdigital/lib-core' // Package name (doesn't resolve)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// In src/lib/** - BREAKS during build!
|
|
150
|
+
import { Button } from '$ext/ui/primitives.js';
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**The solution:**
|
|
154
|
+
- **In library code (`src/lib/**`)**: Use direct package imports
|
|
155
|
+
- **In app code (`src/routes/**`)**: Aliases work fine
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// ✅ Library code - use direct imports
|
|
159
|
+
import { Button } from '@hkdigital/lib-core/ui/primitives.js';
|
|
160
|
+
|
|
161
|
+
// ✅ App code - aliases OK (not built/published)
|
|
162
|
+
import { Button } from '$ext/ui/primitives.js';
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Local aliases work everywhere:**
|
|
166
|
+
```javascript
|
|
167
|
+
// svelte.config.js
|
|
168
|
+
alias: {
|
|
169
|
+
'$lib': 'src/lib', // ✅ Inside project - works everywhere
|
|
170
|
+
'$examples': 'src/routes/examples' // ✅ Inside project
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Why**: Vite/SvelteKit aliases are designed for internal project
|
|
175
|
+
structure. Build tools cannot properly handle aliases pointing outside
|
|
176
|
+
the project or to package names.
|
|
177
|
+
|
|
133
178
|
## Class Export Conventions
|
|
134
179
|
- **All classes should be default exports**: `export default class ClassName`
|
|
135
180
|
- **Import classes without destructuring**: `import ClassName from './ClassName.js'`
|
package/package.json
CHANGED
package/scripts/README.md
CHANGED
|
@@ -35,6 +35,8 @@ node scripts/validate-imports.mjs
|
|
|
35
35
|
etc. must be explicit
|
|
36
36
|
5. **Import paths must exist** - All imports must resolve to actual
|
|
37
37
|
files
|
|
38
|
+
6. **Aliases must resolve inside project** - Library code (`src/lib/`)
|
|
39
|
+
can only use aliases that resolve to paths inside the project folder
|
|
38
40
|
|
|
39
41
|
## Import Rules
|
|
40
42
|
|
|
@@ -180,6 +182,69 @@ import { current } from './existing-file.js';
|
|
|
180
182
|
them at build time or runtime. Helps prevent errors when refactoring
|
|
181
183
|
or moving files.
|
|
182
184
|
|
|
185
|
+
### Rule 6: Aliases must resolve inside the project
|
|
186
|
+
|
|
187
|
+
**Library code must only use aliases that resolve to paths inside the
|
|
188
|
+
project folder**
|
|
189
|
+
|
|
190
|
+
Aliases are designed for internal project structure, not external
|
|
191
|
+
packages. Due to Vite/SvelteKit limitations, aliases that point
|
|
192
|
+
outside the project folder or to package names cannot work correctly in
|
|
193
|
+
library code.
|
|
194
|
+
|
|
195
|
+
❌ Bad (aliases that don't work in `src/lib/`):
|
|
196
|
+
```javascript
|
|
197
|
+
// svelte.config.js
|
|
198
|
+
alias: {
|
|
199
|
+
// Points to node_modules (outside project)
|
|
200
|
+
'$ext-lib': 'node_modules/@some/library/dist',
|
|
201
|
+
|
|
202
|
+
// Package name (Vite treats as relative path, doesn't resolve)
|
|
203
|
+
'$pkg': '@some/library',
|
|
204
|
+
|
|
205
|
+
// Absolute path outside project
|
|
206
|
+
'$external': '/usr/local/lib/something'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// In src/lib/ui/MyComponent.svelte
|
|
210
|
+
import { Button } from '$ext-lib/ui/primitives.js';
|
|
211
|
+
// ^ Breaks when building library!
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
✅ Good (use direct package imports):
|
|
215
|
+
```javascript
|
|
216
|
+
// In src/lib/ui/MyComponent.svelte
|
|
217
|
+
import { Button } from '@some/library/ui/primitives.js';
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
✅ Good (local aliases work everywhere):
|
|
221
|
+
```javascript
|
|
222
|
+
// svelte.config.js
|
|
223
|
+
alias: {
|
|
224
|
+
'$lib': 'src/lib', // ✅ Inside project
|
|
225
|
+
'$examples': 'src/routes/examples' // ✅ Inside project
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// In src/lib/ui/MyComponent.svelte
|
|
229
|
+
import { helper } from '$lib/util/helpers.js'; // ✅ Works!
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
✅ Also good (aliases work fine in app code):
|
|
233
|
+
```javascript
|
|
234
|
+
// In src/routes/+page.svelte (not built/published)
|
|
235
|
+
import { Button } from '$ext-lib/ui/primitives.js';
|
|
236
|
+
// ✅ OK - app code isn't published
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Scope:**
|
|
240
|
+
- **Enforced in**: `src/lib/**` (library code that gets built)
|
|
241
|
+
- **Allowed in**: `src/routes/**` (app code, not published)
|
|
242
|
+
|
|
243
|
+
**Why:** Vite/SvelteKit aliases are designed for internal project
|
|
244
|
+
structure. Build tools (`@sveltejs/package`, Vite) cannot properly
|
|
245
|
+
handle aliases pointing outside the project folder or to package names.
|
|
246
|
+
For external dependencies, use direct imports.
|
|
247
|
+
|
|
183
248
|
## How Module Resolution Works
|
|
184
249
|
|
|
185
250
|
Understanding how Node.js and Vite resolve imports helps explain
|
|
@@ -23,6 +23,15 @@ const EXTERNAL_SCOPES_TO_VALIDATE = ['@hkdigital'];
|
|
|
23
23
|
*/
|
|
24
24
|
let PROJECT_ALIASES = {};
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Unsafe aliases that don't resolve inside the project folder
|
|
28
|
+
* These break when building libraries with @sveltejs/package
|
|
29
|
+
*
|
|
30
|
+
* Maps alias name to suggested fix (package name or explanation)
|
|
31
|
+
* @type {Map<string, string>}
|
|
32
|
+
*/
|
|
33
|
+
const UNSAFE_ALIASES = new Map();
|
|
34
|
+
|
|
26
35
|
/**
|
|
27
36
|
* Load aliases from svelte.config.js
|
|
28
37
|
*
|
|
@@ -47,6 +56,62 @@ async function loadAliases() {
|
|
|
47
56
|
return {};
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Extract package name from node_modules path
|
|
61
|
+
*
|
|
62
|
+
* @param {string} path - Path containing node_modules
|
|
63
|
+
*
|
|
64
|
+
* @returns {string|null} Package name or null
|
|
65
|
+
*/
|
|
66
|
+
function extractPackageNameFromPath(path) {
|
|
67
|
+
// Find node_modules in the path
|
|
68
|
+
const match = path.match(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/);
|
|
69
|
+
if (match) {
|
|
70
|
+
return match[1]; // Returns @scope/package or package
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect unsafe aliases that don't resolve inside the project
|
|
77
|
+
* Populates the UNSAFE_ALIASES map
|
|
78
|
+
*/
|
|
79
|
+
function detectUnsafeAliases() {
|
|
80
|
+
UNSAFE_ALIASES.clear();
|
|
81
|
+
|
|
82
|
+
for (const [alias, target] of Object.entries(PROJECT_ALIASES)) {
|
|
83
|
+
let suggestion = null;
|
|
84
|
+
|
|
85
|
+
// Resolve to absolute path
|
|
86
|
+
const resolvedPath = isAbsolute(target) ?
|
|
87
|
+
target : join(PROJECT_ROOT, target);
|
|
88
|
+
|
|
89
|
+
// Normalize both paths for comparison (resolve symlinks, etc.)
|
|
90
|
+
const normalizedResolved = resolve(resolvedPath);
|
|
91
|
+
const normalizedRoot = resolve(PROJECT_ROOT);
|
|
92
|
+
|
|
93
|
+
// Check if resolved path is inside the project folder
|
|
94
|
+
const isInsideProject = normalizedResolved.startsWith(normalizedRoot);
|
|
95
|
+
|
|
96
|
+
if (!isInsideProject) {
|
|
97
|
+
// Path is outside project - check if it's in node_modules
|
|
98
|
+
if (resolvedPath.includes('/node_modules/')) {
|
|
99
|
+
const packageName = extractPackageNameFromPath(resolvedPath);
|
|
100
|
+
if (packageName) {
|
|
101
|
+
suggestion = packageName;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// Outside project but not node_modules
|
|
105
|
+
suggestion = '(path outside project - use direct imports)';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (suggestion) {
|
|
109
|
+
UNSAFE_ALIASES.set(alias, suggestion);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
50
115
|
/**
|
|
51
116
|
* Resolve an alias path to its filesystem location
|
|
52
117
|
*
|
|
@@ -286,6 +351,7 @@ async function findExternalBarrelExport(importPath, targetName) {
|
|
|
286
351
|
// 1. Explicit index.js imports
|
|
287
352
|
// 2. Component files (.svelte)
|
|
288
353
|
// 3. Class files (capitalized .js)
|
|
354
|
+
// 4. Imports without extension (missing extension or directory)
|
|
289
355
|
let shouldCheck = false;
|
|
290
356
|
|
|
291
357
|
if (lastPart === 'index.js') {
|
|
@@ -294,6 +360,9 @@ async function findExternalBarrelExport(importPath, targetName) {
|
|
|
294
360
|
shouldCheck = true;
|
|
295
361
|
} else if (lastPart.match(/^[A-Z][^/]*\.js$/)) {
|
|
296
362
|
shouldCheck = true;
|
|
363
|
+
} else if (!lastPart.includes('.')) {
|
|
364
|
+
// No extension - could be missing or directory import
|
|
365
|
+
shouldCheck = true;
|
|
297
366
|
}
|
|
298
367
|
|
|
299
368
|
if (!shouldCheck) return null;
|
|
@@ -421,6 +490,7 @@ async function findAliasBarrelExport(importPath, targetName) {
|
|
|
421
490
|
// 1. Explicit index.js imports
|
|
422
491
|
// 2. Component files (.svelte)
|
|
423
492
|
// 3. Class files (capitalized .js)
|
|
493
|
+
// 4. Imports without extension (missing extension or directory)
|
|
424
494
|
let shouldCheck = false;
|
|
425
495
|
|
|
426
496
|
if (lastPart === 'index.js') {
|
|
@@ -429,6 +499,9 @@ async function findAliasBarrelExport(importPath, targetName) {
|
|
|
429
499
|
shouldCheck = true;
|
|
430
500
|
} else if (lastPart.match(/^[A-Z][^/]*\.js$/)) {
|
|
431
501
|
shouldCheck = true;
|
|
502
|
+
} else if (!lastPart.includes('.')) {
|
|
503
|
+
// No extension - could be missing or directory import
|
|
504
|
+
shouldCheck = true;
|
|
432
505
|
}
|
|
433
506
|
|
|
434
507
|
if (!shouldCheck) return null;
|
|
@@ -530,6 +603,42 @@ async function validateFile(filePath) {
|
|
|
530
603
|
);
|
|
531
604
|
|
|
532
605
|
if (isAliasImport) {
|
|
606
|
+
// Find which alias is being used
|
|
607
|
+
let matchedAlias = null;
|
|
608
|
+
for (const alias of Object.keys(PROJECT_ALIASES)) {
|
|
609
|
+
if (importPath === alias || importPath.startsWith(alias + '/')) {
|
|
610
|
+
matchedAlias = alias;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Check for unsafe alias usage in library code
|
|
616
|
+
if (isInLib && matchedAlias && UNSAFE_ALIASES.has(matchedAlias)) {
|
|
617
|
+
const suggestion = UNSAFE_ALIASES.get(matchedAlias);
|
|
618
|
+
const pathAfterAlias = importPath.slice(matchedAlias.length);
|
|
619
|
+
|
|
620
|
+
// Construct suggested import if it's a package name
|
|
621
|
+
let errorMsg;
|
|
622
|
+
if (suggestion.startsWith('(')) {
|
|
623
|
+
// Generic message (not a package name)
|
|
624
|
+
errorMsg = `${relativePath}:${lineNum}\n` +
|
|
625
|
+
` from '${importPath}'\n` +
|
|
626
|
+
` => ${suggestion}`;
|
|
627
|
+
} else {
|
|
628
|
+
// Package name - construct full import path
|
|
629
|
+
const suggestedImport = suggestion + pathAfterAlias;
|
|
630
|
+
errorMsg = `${relativePath}:${lineNum}\n` +
|
|
631
|
+
` from '${importPath}'\n` +
|
|
632
|
+
` => from '${suggestedImport}' ` +
|
|
633
|
+
`(alias resolves outside project)`;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
errors.push(errorMsg);
|
|
637
|
+
|
|
638
|
+
// Skip further validation for this import
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
|
|
533
642
|
// Extract imported names from the import statement
|
|
534
643
|
const importedNames = extractImportNames(line);
|
|
535
644
|
|
|
@@ -973,6 +1082,22 @@ async function main() {
|
|
|
973
1082
|
console.log();
|
|
974
1083
|
}
|
|
975
1084
|
|
|
1085
|
+
// Detect unsafe aliases (outside project folder)
|
|
1086
|
+
detectUnsafeAliases();
|
|
1087
|
+
|
|
1088
|
+
if (UNSAFE_ALIASES.size > 0) {
|
|
1089
|
+
console.log(
|
|
1090
|
+
'⚠️ Unsafe aliases detected (resolve outside project folder):'
|
|
1091
|
+
);
|
|
1092
|
+
for (const [alias, suggestion] of UNSAFE_ALIASES.entries()) {
|
|
1093
|
+
console.log(
|
|
1094
|
+
` ${alias} → ${suggestion} ` +
|
|
1095
|
+
`(breaks in src/lib/, OK in src/routes/)`
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
console.log();
|
|
1099
|
+
}
|
|
1100
|
+
|
|
976
1101
|
const files = await findFiles(SRC_DIR);
|
|
977
1102
|
const allErrors = [];
|
|
978
1103
|
|