@bobfrankston/importgen 0.1.22 → 0.1.24
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/settings.local.json +3 -1
- package/index.js +119 -11
- package/package.json +5 -3
package/index.js
CHANGED
|
@@ -67,9 +67,11 @@ function resolveEntryPoint(pkg, packageDir) {
|
|
|
67
67
|
return './index.js';
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Recursively collect all dependencies, avoiding circular references
|
|
70
|
+
* Recursively collect all dependencies, avoiding circular references.
|
|
71
|
+
* nodeModulesPrefix tracks the import map path prefix for nested deps
|
|
72
|
+
* (e.g., './node_modules' for root, './node_modules/@scope/pkg/node_modules' for nested).
|
|
71
73
|
*/
|
|
72
|
-
function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, warnings, depPaths) {
|
|
74
|
+
function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, nodeModulesPrefix, warnings, depPaths) {
|
|
73
75
|
try {
|
|
74
76
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
75
77
|
const packageDir = path.dirname(packageJsonPath);
|
|
@@ -84,7 +86,7 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, wa
|
|
|
84
86
|
// Check if .dependencies has an override path for this package
|
|
85
87
|
const overrideVersion = dotDeps[depName];
|
|
86
88
|
const versionToUse = overrideVersion || depVersion;
|
|
87
|
-
// Resolve dependency path
|
|
89
|
+
// Resolve dependency path (real filesystem location)
|
|
88
90
|
let depPath = resolveDependencyPath(packageDir, depName, versionToUse);
|
|
89
91
|
if (!depPath) {
|
|
90
92
|
const warning = `Could not follow path for ${depName} (${versionToUse})`;
|
|
@@ -115,17 +117,25 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, wa
|
|
|
115
117
|
const depPackageJson = JSON.parse(fs.readFileSync(depPackageJsonPath, 'utf-8'));
|
|
116
118
|
// Resolve entry point
|
|
117
119
|
const entryPoint = resolveEntryPoint(depPackageJson, depPath);
|
|
118
|
-
// Generate path relative to HTML dir using actual resolved location
|
|
119
120
|
const entryFile = entryPoint.startsWith('./') ? entryPoint.slice(2) : entryPoint;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// Generate import map path through node_modules (follows symlinks)
|
|
122
|
+
// Check if hoisted to top-level node_modules first
|
|
123
|
+
const topLevelDir = path.join(htmlDir, 'node_modules', depName);
|
|
124
|
+
let depPrefix;
|
|
125
|
+
if (fs.existsSync(topLevelDir)) {
|
|
126
|
+
depPrefix = `./node_modules/${depName}`;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
depPrefix = `${nodeModulesPrefix}/${depName}`;
|
|
130
|
+
}
|
|
131
|
+
dependencies.set(depName, `${depPrefix}/${entryFile}`);
|
|
123
132
|
// Recursively process this dependency's dependencies
|
|
124
|
-
collectDependencies(depPackageJsonPath, visited, dependencies, htmlDir, warnings, depPaths);
|
|
133
|
+
collectDependencies(depPackageJsonPath, visited, dependencies, htmlDir, `${depPrefix}/node_modules`, warnings, depPaths);
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
catch (e) {
|
|
128
137
|
console.error(`[generate-importmap] Error processing ${packageJsonPath}:`, e.message);
|
|
138
|
+
throw e; // Propagate so callers can avoid overwriting HTML with empty map
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
function generateImportMap(packageJsonPath, htmlFilePath) {
|
|
@@ -137,7 +147,13 @@ function generateImportMap(packageJsonPath, htmlFilePath) {
|
|
|
137
147
|
const depPaths = new Set();
|
|
138
148
|
const warnings = [];
|
|
139
149
|
// Recursively collect all dependencies
|
|
140
|
-
|
|
150
|
+
try {
|
|
151
|
+
collectDependencies(packageJsonPath, visited, dependencies, htmlDir, './node_modules', warnings, depPaths);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Root package.json unreadable (e.g., mid-write) — skip update to preserve existing HTML
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
141
157
|
result.depDirs = Array.from(depPaths);
|
|
142
158
|
// Convert Map to plain object for JSON
|
|
143
159
|
const imports = {};
|
|
@@ -179,6 +195,88 @@ function generateImportMap(packageJsonPath, htmlFilePath) {
|
|
|
179
195
|
}
|
|
180
196
|
return result;
|
|
181
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Freeze dependencies: replace junctions/symlinks in node_modules with real copies.
|
|
200
|
+
* Also adds a preinstall guard to package.json to prevent npm install from undoing the freeze.
|
|
201
|
+
*/
|
|
202
|
+
function freezeDependencies(packageJsonPath) {
|
|
203
|
+
const nodeModulesDir = path.join(process.cwd(), 'node_modules');
|
|
204
|
+
if (!fs.existsSync(nodeModulesDir)) {
|
|
205
|
+
console.error('[importgen] Error: node_modules not found');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
/** Check if a directory entry is a junction or symlink by comparing realpath to its path */
|
|
209
|
+
function isLinked(entryPath) {
|
|
210
|
+
try {
|
|
211
|
+
const stat = fs.lstatSync(entryPath);
|
|
212
|
+
// Symlink check (works on Linux/Mac symlinks)
|
|
213
|
+
if (stat.isSymbolicLink())
|
|
214
|
+
return true;
|
|
215
|
+
// Junction check (Windows): realpath differs from the entry path
|
|
216
|
+
if (stat.isDirectory()) {
|
|
217
|
+
const real = fs.realpathSync(entryPath);
|
|
218
|
+
return path.resolve(real) !== path.resolve(entryPath);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
console.error(`[importgen] Error checking ${entryPath}: ${e.message}`);
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
/** Freeze a single entry if it's a junction/symlink */
|
|
227
|
+
function freezeEntry(entryPath, displayName) {
|
|
228
|
+
if (!isLinked(entryPath))
|
|
229
|
+
return;
|
|
230
|
+
const target = fs.realpathSync(entryPath);
|
|
231
|
+
console.log(` Freezing ${displayName} (${target})`);
|
|
232
|
+
fs.rmSync(entryPath, { recursive: true });
|
|
233
|
+
fs.cpSync(target, entryPath, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
console.log('[importgen] Freezing dependencies...');
|
|
236
|
+
let frozenCount = 0;
|
|
237
|
+
const entries = fs.readdirSync(nodeModulesDir);
|
|
238
|
+
for (const entry of entries) {
|
|
239
|
+
const entryPath = path.join(nodeModulesDir, entry);
|
|
240
|
+
if (entry.startsWith('@')) {
|
|
241
|
+
// Scoped package — walk one level deeper
|
|
242
|
+
if (!fs.statSync(entryPath).isDirectory())
|
|
243
|
+
continue;
|
|
244
|
+
const scopedEntries = fs.readdirSync(entryPath);
|
|
245
|
+
for (const scoped of scopedEntries) {
|
|
246
|
+
const scopedPath = path.join(entryPath, scoped);
|
|
247
|
+
const before = isLinked(scopedPath);
|
|
248
|
+
freezeEntry(scopedPath, `${entry}/${scoped}`);
|
|
249
|
+
if (before)
|
|
250
|
+
frozenCount++;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const before = isLinked(entryPath);
|
|
255
|
+
freezeEntry(entryPath, entry);
|
|
256
|
+
if (before)
|
|
257
|
+
frozenCount++;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
console.log(`[importgen] Frozen ${frozenCount} linked packages`);
|
|
261
|
+
// Add preinstall guard to package.json
|
|
262
|
+
try {
|
|
263
|
+
const pkgRaw = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
264
|
+
const pkg = JSON.parse(pkgRaw);
|
|
265
|
+
if (!pkg.scripts)
|
|
266
|
+
pkg.scripts = {};
|
|
267
|
+
if (pkg.scripts.preinstall) {
|
|
268
|
+
console.warn('[importgen] Warning: preinstall script already exists, skipping guard');
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
pkg.scripts.preinstall = 'echo FROZEN: dependencies were frozen by importgen. Remove this script before running npm install. && exit 1';
|
|
272
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
273
|
+
console.log('[importgen] Added preinstall guard to package.json');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
console.error(`[importgen] Error updating package.json: ${e.message}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
182
280
|
// Parse CLI arguments
|
|
183
281
|
const args = process.argv.slice(2);
|
|
184
282
|
if (args.includes('-v') || args.includes('--version')) {
|
|
@@ -186,6 +284,7 @@ if (args.includes('-v') || args.includes('--version')) {
|
|
|
186
284
|
process.exit(0);
|
|
187
285
|
}
|
|
188
286
|
const watchMode = args.includes('-w') || args.includes('--watch');
|
|
287
|
+
const freezeMode = args.includes('--freeze');
|
|
189
288
|
const htmlArg = args.find(a => !a.startsWith('-'));
|
|
190
289
|
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
191
290
|
// Resolve HTML file: explicit argument, or search for common names
|
|
@@ -197,6 +296,10 @@ if (!fs.existsSync(packageJsonPath)) {
|
|
|
197
296
|
console.error('[generate-importmap] Error: package.json not found in current directory');
|
|
198
297
|
process.exit(1);
|
|
199
298
|
}
|
|
299
|
+
if (freezeMode) {
|
|
300
|
+
freezeDependencies(packageJsonPath);
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
200
303
|
if (!htmlFilePath || !fs.existsSync(htmlFilePath)) {
|
|
201
304
|
if (htmlArg) {
|
|
202
305
|
console.error(`[generate-importmap] Error: ${htmlArg} not found in current directory`);
|
|
@@ -231,9 +334,14 @@ if (watchMode) {
|
|
|
231
334
|
watchedDepDirs = newDirs;
|
|
232
335
|
}
|
|
233
336
|
syncWatchedDeps(result.depDirs);
|
|
337
|
+
let debounceTimer = null;
|
|
234
338
|
const onChange = () => {
|
|
235
|
-
|
|
236
|
-
|
|
339
|
+
if (debounceTimer)
|
|
340
|
+
clearTimeout(debounceTimer);
|
|
341
|
+
debounceTimer = setTimeout(() => {
|
|
342
|
+
result = generateImportMap(packageJsonPath, htmlFilePath);
|
|
343
|
+
syncWatchedDeps(result.depDirs);
|
|
344
|
+
}, 100);
|
|
237
345
|
};
|
|
238
346
|
watcher.on('change', onChange);
|
|
239
347
|
watcher.on('add', onChange);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/importgen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Generate ES Module import maps from package.json dependencies for native browser module loading",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
"watch": "tsc -w",
|
|
13
13
|
"preversion": "npm run build",
|
|
14
14
|
"postversion": "echo Version updated to $(node -p \"require('./package.json').version\")",
|
|
15
|
-
"release": "
|
|
16
|
-
"installer": "npm run release && npm install -g ."
|
|
15
|
+
"release": "npmglobalize",
|
|
16
|
+
"installer": "npm run release && npm install -g .",
|
|
17
|
+
"old-release": "git add -A && git diff-index --quiet HEAD || git commit -m 'Build for release' && npm version patch",
|
|
18
|
+
"old-installer": "npm run release && npm install -g ."
|
|
17
19
|
},
|
|
18
20
|
"keywords": [
|
|
19
21
|
"import-map",
|