@bobfrankston/importgen 0.1.25 → 0.1.27

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.
@@ -9,7 +9,9 @@
9
9
  "Bash(ls:*)",
10
10
  "Bash(where extractids:*)",
11
11
  "Bash(cd \"y:/dev/utils/importgen\" && npx tsc --noEmit 2>&1222)",
12
- "Bash(cd y:/dev/utils/importgen && npm run release 2>&1exit)"
12
+ "Bash(cd y:/dev/utils/importgen && npm run release 2>&1exit)",
13
+ "Bash(cd y:/dev/utils/importgen && npx tsc --noEmit 2>&1)",
14
+ "Bash(cd:*)"
13
15
  ]
14
16
  }
15
17
  }
package/README.md CHANGED
@@ -25,7 +25,7 @@ npm install -g @bobfrankston/importgen
25
25
  Run from your project directory (containing `package.json` and your HTML file):
26
26
 
27
27
  ```bash
28
- importgen [htmlfile] [--watch|-w] [--version|-v]
28
+ importgen [htmlfile] [--watch|-w] [--freeze] [--unfreeze] [--version|-v]
29
29
  ```
30
30
 
31
31
  ```bash
@@ -42,6 +42,12 @@ importgen default.htm --watch
42
42
 
43
43
  # Show version
44
44
  importgen -v
45
+
46
+ # Freeze: replace symlinks/junctions with real copies for deployment
47
+ importgen --freeze
48
+
49
+ # Unfreeze: restore symlinks and run npm install
50
+ importgen --unfreeze
45
51
  ```
46
52
 
47
53
  `importgen` reads dependencies from `package.json` in the current directory and injects the import map into the HTML file.
@@ -129,6 +135,14 @@ Add a `.dependencies` field to `package.json` to override resolution paths for s
129
135
 
130
136
  The import map will use the `.dependencies` path instead of the `node_modules` path for that package.
131
137
 
138
+ ## Freeze / Unfreeze
139
+
140
+ `--freeze` prepares a project for deployment by replacing all symlinks and junctions in `node_modules` with real directory copies. This is useful when `file:` or `workspace:` dependencies point to local packages via symlinks that won't exist on a deployment target.
141
+
142
+ Freeze also adds a `preinstall` guard script to `package.json` that prevents accidental `npm install` from overwriting the frozen copies. If a `preinstall` script already exists, it is saved as `frozen-preinstall` so it can be restored later.
143
+
144
+ `--unfreeze` reverses the process: it removes the preinstall guard (restoring any original preinstall script), then runs `npm install` to re-establish normal symlinked dependencies.
145
+
132
146
  ## VS Code tasks.json Integration
133
147
 
134
148
  Add `importgen --watch` as a background task that starts automatically when you open the project. Combined with `tsc --watch`, your import map stays current as you develop.
package/index.js CHANGED
@@ -5,8 +5,13 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
+ import { execSync } from 'node:child_process';
8
9
  import chokidar from 'chokidar';
9
10
  import packageJson from './package.json' with { type: 'json' };
11
+ /** Timestamp prefix for log messages */
12
+ function ts() {
13
+ return `[${new Date().toLocaleTimeString()}]`;
14
+ }
10
15
  /**
11
16
  * Resolve dependency path based on version specifier
12
17
  */
@@ -90,7 +95,7 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, no
90
95
  let depPath = resolveDependencyPath(packageDir, depName, versionToUse);
91
96
  if (!depPath) {
92
97
  const warning = `Could not follow path for ${depName} (${versionToUse})`;
93
- console.warn(`[generate-importmap] Warning: ${warning}`);
98
+ console.warn(`${ts()} [generate-importmap] Warning: ${warning}`);
94
99
  warnings.push(warning);
95
100
  continue;
96
101
  }
@@ -98,7 +103,7 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, no
98
103
  const dependencyBackupPath = depPath + '.dependency';
99
104
  if (fs.existsSync(dependencyBackupPath)) {
100
105
  const warning = `Using backup path ${dependencyBackupPath}`;
101
- console.warn(`[generate-importmap] Warning: ${warning}`);
106
+ console.warn(`${ts()} [generate-importmap] Warning: ${warning}`);
102
107
  warnings.push(warning);
103
108
  depPath = dependencyBackupPath;
104
109
  }
@@ -109,7 +114,7 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, no
109
114
  const depPackageJsonPath = path.join(depPath, 'package.json');
110
115
  if (!fs.existsSync(depPackageJsonPath)) {
111
116
  const warning = `Could not follow path for ${depName} - no package.json found at ${depPath}`;
112
- console.warn(`[generate-importmap] Warning: ${warning}`);
117
+ console.warn(`${ts()} [generate-importmap] Warning: ${warning}`);
113
118
  warnings.push(warning);
114
119
  continue;
115
120
  }
@@ -134,7 +139,7 @@ function collectDependencies(packageJsonPath, visited, dependencies, htmlDir, no
134
139
  }
135
140
  }
136
141
  catch (e) {
137
- console.error(`[generate-importmap] Error processing ${packageJsonPath}:`, e.message);
142
+ console.error(`${ts()} [generate-importmap] Error processing ${packageJsonPath}:`, e.message);
138
143
  throw e; // Propagate so callers can avoid overwriting HTML with empty map
139
144
  }
140
145
  }
@@ -183,14 +188,14 @@ function generateImportMap(packageJsonPath, htmlFilePath) {
183
188
  }
184
189
  // Write back to HTML file
185
190
  fs.writeFileSync(htmlFilePath, html, 'utf-8');
186
- console.log('[generate-importmap] Updated import map');
191
+ console.log(`${ts()} [generate-importmap] Updated import map`);
187
192
  console.log(` Scanned ${visited.size} dependencies, generated ${dependencies.size} entries`);
188
193
  if (dependencies.size > 0) {
189
194
  console.log(' Packages:', Array.from(dependencies.keys()).join(', '));
190
195
  }
191
196
  }
192
197
  catch (e) {
193
- console.error('[generate-importmap] Error:', e.message);
198
+ console.error(`${ts()} [generate-importmap] Error:`, e.message);
194
199
  process.exit(1);
195
200
  }
196
201
  return result;
@@ -202,7 +207,7 @@ function generateImportMap(packageJsonPath, htmlFilePath) {
202
207
  function freezeDependencies(packageJsonPath) {
203
208
  const nodeModulesDir = path.join(process.cwd(), 'node_modules');
204
209
  if (!fs.existsSync(nodeModulesDir)) {
205
- console.error('[importgen] Error: node_modules not found');
210
+ console.error(`${ts()} [importgen] Error: node_modules not found`);
206
211
  process.exit(1);
207
212
  }
208
213
  /** Check if a directory entry is a junction or symlink by comparing realpath to its path */
@@ -219,7 +224,7 @@ function freezeDependencies(packageJsonPath) {
219
224
  }
220
225
  }
221
226
  catch (e) {
222
- console.error(`[importgen] Error checking ${entryPath}: ${e.message}`);
227
+ console.error(`${ts()} [importgen] Error checking ${entryPath}: ${e.message}`);
223
228
  }
224
229
  return false;
225
230
  }
@@ -228,11 +233,11 @@ function freezeDependencies(packageJsonPath) {
228
233
  if (!isLinked(entryPath))
229
234
  return;
230
235
  const target = fs.realpathSync(entryPath);
231
- console.log(` Freezing ${displayName} (${target})`);
236
+ console.log(`${ts()} Freezing ${displayName} (${target})`);
232
237
  fs.rmSync(entryPath, { recursive: true });
233
238
  fs.cpSync(target, entryPath, { recursive: true });
234
239
  }
235
- console.log('[importgen] Freezing dependencies...');
240
+ console.log(`${ts()} [importgen] Freezing dependencies...`);
236
241
  let frozenCount = 0;
237
242
  const entries = fs.readdirSync(nodeModulesDir);
238
243
  for (const entry of entries) {
@@ -257,7 +262,7 @@ function freezeDependencies(packageJsonPath) {
257
262
  frozenCount++;
258
263
  }
259
264
  }
260
- console.log(`[importgen] Frozen ${frozenCount} linked packages`);
265
+ console.log(`${ts()} [importgen] Frozen ${frozenCount} linked packages`);
261
266
  // Add preinstall guard to package.json
262
267
  try {
263
268
  const pkgRaw = fs.readFileSync(packageJsonPath, 'utf-8');
@@ -265,16 +270,53 @@ function freezeDependencies(packageJsonPath) {
265
270
  if (!pkg.scripts)
266
271
  pkg.scripts = {};
267
272
  if (pkg.scripts.preinstall) {
268
- console.warn('[importgen] Warning: preinstall script already exists, skipping guard');
273
+ // Rename existing preinstall so unfreeze can restore it
274
+ pkg.scripts['frozen-preinstall'] = pkg.scripts.preinstall;
275
+ console.log(`${ts()} [importgen] Saved existing preinstall script as "frozen-preinstall"`);
276
+ }
277
+ pkg.scripts.preinstall = 'echo FROZEN: dependencies were frozen by importgen. Use importgen --unfreeze to restore. && exit 1';
278
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
279
+ console.log(`${ts()} [importgen] Added preinstall guard to package.json`);
280
+ }
281
+ catch (e) {
282
+ console.error(`${ts()} [importgen] Error updating package.json: ${e.message}`);
283
+ }
284
+ }
285
+ /**
286
+ * Unfreeze: remove preinstall guard, restore original preinstall if saved, then run npm install.
287
+ */
288
+ function unfreezeDependencies(packageJsonPath) {
289
+ try {
290
+ const pkgRaw = fs.readFileSync(packageJsonPath, 'utf-8');
291
+ const pkg = JSON.parse(pkgRaw);
292
+ if (!pkg.scripts?.preinstall) {
293
+ console.log(`${ts()} [importgen] No preinstall guard found — nothing to unfreeze`);
294
+ return;
295
+ }
296
+ if (pkg.scripts['frozen-preinstall']) {
297
+ pkg.scripts.preinstall = pkg.scripts['frozen-preinstall'];
298
+ delete pkg.scripts['frozen-preinstall'];
299
+ console.log(`${ts()} [importgen] Restored original preinstall script`);
269
300
  }
270
301
  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');
302
+ delete pkg.scripts.preinstall;
303
+ console.log(`${ts()} [importgen] Removed preinstall guard`);
274
304
  }
305
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
306
+ }
307
+ catch (e) {
308
+ console.error(`${ts()} [importgen] Error updating package.json: ${e.message}`);
309
+ process.exit(1);
310
+ }
311
+ // Run npm install to restore proper dependencies
312
+ console.log(`${ts()} [importgen] Running npm install...`);
313
+ try {
314
+ execSync('npm install', { stdio: 'inherit', cwd: process.cwd() });
315
+ console.log(`${ts()} [importgen] Unfreeze complete`);
275
316
  }
276
317
  catch (e) {
277
- console.error(`[importgen] Error updating package.json: ${e.message}`);
318
+ console.error(`${ts()} [importgen] npm install failed: ${e.message}`);
319
+ process.exit(1);
278
320
  }
279
321
  }
280
322
  // Parse CLI arguments
@@ -283,20 +325,21 @@ if (args.includes('-v') || args.includes('--version')) {
283
325
  console.log(`importgen ${packageJson.version}`);
284
326
  process.exit(0);
285
327
  }
286
- const knownFlags = new Set(['-w', '--watch', '--freeze', '-freeze', '-v', '--version']);
328
+ const knownFlags = new Set(['-w', '--watch', '--freeze', '-freeze', '--unfreeze', '-unfreeze', '-v', '--version']);
287
329
  // Report unrecognized flags as errors
288
330
  const unknownArgs = args.filter(a => a.startsWith('-') && !knownFlags.has(a));
289
331
  if (unknownArgs.length > 0) {
290
- console.error(`[importgen] Error: unrecognized argument(s): ${unknownArgs.join(', ')}`);
291
- console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [-v|--version]`);
332
+ console.error(`${ts()} [importgen] Error: unrecognized argument(s): ${unknownArgs.join(', ')}`);
333
+ console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [--unfreeze] [-v|--version]`);
292
334
  process.exit(1);
293
335
  }
294
336
  const watchMode = args.includes('-w') || args.includes('--watch');
295
337
  const freezeMode = args.includes('--freeze') || args.includes('-freeze');
338
+ const unfreezeMode = args.includes('--unfreeze') || args.includes('-unfreeze');
296
339
  const positionalArgs = args.filter(a => !a.startsWith('-'));
297
340
  if (positionalArgs.length > 1) {
298
- console.error(`[importgen] Error: too many arguments: ${positionalArgs.join(', ')}`);
299
- console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [-v|--version]`);
341
+ console.error(`${ts()} [importgen] Error: too many arguments: ${positionalArgs.join(', ')}`);
342
+ console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [--unfreeze] [-v|--version]`);
300
343
  process.exit(1);
301
344
  }
302
345
  const htmlArg = positionalArgs[0];
@@ -307,19 +350,23 @@ const htmlFileName = htmlArg || possibleHtmlFiles.find(f => fs.existsSync(path.j
307
350
  const htmlFilePath = htmlFileName ? path.join(process.cwd(), htmlFileName) : null;
308
351
  // Check if files exist
309
352
  if (!fs.existsSync(packageJsonPath)) {
310
- console.error('[generate-importmap] Error: package.json not found in current directory');
353
+ console.error(`${ts()} [generate-importmap] Error: package.json not found in current directory`);
311
354
  process.exit(1);
312
355
  }
313
356
  if (freezeMode) {
314
357
  freezeDependencies(packageJsonPath);
315
358
  process.exit(0);
316
359
  }
360
+ if (unfreezeMode) {
361
+ unfreezeDependencies(packageJsonPath);
362
+ process.exit(0);
363
+ }
317
364
  if (!htmlFilePath || !fs.existsSync(htmlFilePath)) {
318
365
  if (htmlArg) {
319
- console.error(`[generate-importmap] Error: ${htmlArg} not found in current directory`);
366
+ console.error(`${ts()} [generate-importmap] Error: ${htmlArg} not found in current directory`);
320
367
  }
321
368
  else {
322
- console.error(`[generate-importmap] Error: No HTML file found. Looking for: ${possibleHtmlFiles.join(', ')}`);
369
+ console.error(`${ts()} [generate-importmap] Error: No HTML file found. Looking for: ${possibleHtmlFiles.join(', ')}`);
323
370
  }
324
371
  process.exit(1);
325
372
  }
@@ -362,7 +409,7 @@ if (watchMode) {
362
409
  watcher.on('unlink', onChange);
363
410
  watcher.on('ready', () => {
364
411
  const total = 1 + watchedDepDirs.size;
365
- console.log(`[generate-importmap] Watching ${total} directories for changes...`);
412
+ console.log(`${ts()} [generate-importmap] Watching ${total} directories for changes...`);
366
413
  if (watchedDepDirs.size > 0) {
367
414
  for (const dir of watchedDepDirs) {
368
415
  console.log(` ${dir}`);
@@ -370,7 +417,7 @@ if (watchMode) {
370
417
  }
371
418
  });
372
419
  watcher.on('error', (error) => {
373
- console.error('[generate-importmap] Watcher error:', error.message);
420
+ console.error(`${ts()} [generate-importmap] Watcher error:`, error.message);
374
421
  });
375
422
  }
376
423
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/importgen",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
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": {
@@ -34,7 +34,7 @@
34
34
  "chokidar": "^4.0.3"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/node": "^25.2.1"
37
+ "@types/node": "^25.3.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"