@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.
@@ -6,7 +6,9 @@
6
6
  "Bash(npx tsc)",
7
7
  "Bash(node index.js:*)",
8
8
  "Bash(dir:*)",
9
- "Bash(ls:*)"
9
+ "Bash(ls:*)",
10
+ "Bash(where extractids:*)",
11
+ "Bash(cd \"y:/dev/utils/importgen\" && npx tsc --noEmit 2>&1222)"
10
12
  ]
11
13
  }
12
14
  }
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
- const absoluteEntryPath = path.join(depPath, entryFile);
121
- let relativePath = './' + path.relative(htmlDir, absoluteEntryPath).replace(/\\/g, '/');
122
- dependencies.set(depName, relativePath);
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
- collectDependencies(packageJsonPath, visited, dependencies, htmlDir, warnings, depPaths);
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
- result = generateImportMap(packageJsonPath, htmlFilePath);
236
- syncWatchedDeps(result.depDirs);
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.22",
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": "git add -A && git diff-index --quiet HEAD || git commit -m 'Build for release' && npm version patch",
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",