@bobfrankston/importgen 0.1.27 → 0.1.29

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
@@ -103,6 +103,25 @@ The browser sees `date-utils`, looks it up in the import map, and loads `./node_
103
103
 
104
104
  The key point: your TypeScript source files are the same whether they run in Node or the browser. `importgen` just provides the mapping the browser needs to find the packages.
105
105
 
106
+ ## Programmatic API
107
+
108
+ `importgen` can be called from another application (e.g., `npmglobalize`) instead of the CLI:
109
+
110
+ ```typescript
111
+ import { importgen } from "@bobfrankston/importgen";
112
+
113
+ // Generate import map for a specific directory
114
+ importgen("/path/to/my/app");
115
+
116
+ // With explicit HTML filename
117
+ importgen("/path/to/my/app", "default.htm");
118
+
119
+ // Uses process.cwd() if no directory given
120
+ importgen();
121
+ ```
122
+
123
+ The function throws on missing `package.json` or HTML file (instead of calling `process.exit`), so callers can handle errors. Returns a `GenerateResult` with resolved dependency directories.
124
+
106
125
  ## How Entry Points Are Resolved
107
126
 
108
127
  For each dependency, `importgen` reads that package's `package.json` and picks the entry point in this order:
package/index.d.ts CHANGED
@@ -3,5 +3,15 @@
3
3
  * Generate ES Module import map from package.json dependencies
4
4
  * Injects into HTML files for native browser module loading
5
5
  */
6
- export {};
6
+ /** Result from generateImportMap including paths for watch mode */
7
+ export interface GenerateResult {
8
+ depDirs: string[]; /** Resolved dependency directories */
9
+ }
10
+ /**
11
+ * API: run importgen on a directory.
12
+ * @param appDir - directory containing package.json and HTML file (defaults to cwd)
13
+ * @param htmlFile - HTML filename to update (auto-detected if omitted)
14
+ * @returns GenerateResult with resolved dependency directories
15
+ */
16
+ export declare function importgen(appDir?: string, htmlFile?: string): GenerateResult;
7
17
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
- import { execSync } from 'node:child_process';
9
8
  import chokidar from 'chokidar';
10
9
  import packageJson from './package.json' with { type: 'json' };
11
10
  /** Timestamp prefix for log messages */
@@ -201,226 +200,115 @@ function generateImportMap(packageJsonPath, htmlFilePath) {
201
200
  return result;
202
201
  }
203
202
  /**
204
- * Freeze dependencies: replace junctions/symlinks in node_modules with real copies.
205
- * Also adds a preinstall guard to package.json to prevent npm install from undoing the freeze.
203
+ * API: run importgen on a directory.
204
+ * @param appDir - directory containing package.json and HTML file (defaults to cwd)
205
+ * @param htmlFile - HTML filename to update (auto-detected if omitted)
206
+ * @returns GenerateResult with resolved dependency directories
206
207
  */
207
- function freezeDependencies(packageJsonPath) {
208
- const nodeModulesDir = path.join(process.cwd(), 'node_modules');
209
- if (!fs.existsSync(nodeModulesDir)) {
210
- console.error(`${ts()} [importgen] Error: node_modules not found`);
211
- process.exit(1);
208
+ export function importgen(appDir, htmlFile) {
209
+ const dir = appDir || process.cwd();
210
+ const pkgPath = path.join(dir, 'package.json');
211
+ if (!fs.existsSync(pkgPath)) {
212
+ throw new Error(`package.json not found in ${dir}`);
212
213
  }
213
- /** Check if a directory entry is a junction or symlink by comparing realpath to its path */
214
- function isLinked(entryPath) {
215
- try {
216
- const stat = fs.lstatSync(entryPath);
217
- // Symlink check (works on Linux/Mac symlinks)
218
- if (stat.isSymbolicLink())
219
- return true;
220
- // Junction check (Windows): realpath differs from the entry path
221
- if (stat.isDirectory()) {
222
- const real = fs.realpathSync(entryPath);
223
- return path.resolve(real) !== path.resolve(entryPath);
224
- }
225
- }
226
- catch (e) {
227
- console.error(`${ts()} [importgen] Error checking ${entryPath}: ${e.message}`);
228
- }
229
- return false;
230
- }
231
- /** Freeze a single entry if it's a junction/symlink */
232
- function freezeEntry(entryPath, displayName) {
233
- if (!isLinked(entryPath))
234
- return;
235
- const target = fs.realpathSync(entryPath);
236
- console.log(`${ts()} Freezing ${displayName} (${target})`);
237
- fs.rmSync(entryPath, { recursive: true });
238
- fs.cpSync(target, entryPath, { recursive: true });
239
- }
240
- console.log(`${ts()} [importgen] Freezing dependencies...`);
241
- let frozenCount = 0;
242
- const entries = fs.readdirSync(nodeModulesDir);
243
- for (const entry of entries) {
244
- const entryPath = path.join(nodeModulesDir, entry);
245
- if (entry.startsWith('@')) {
246
- // Scoped package — walk one level deeper
247
- if (!fs.statSync(entryPath).isDirectory())
248
- continue;
249
- const scopedEntries = fs.readdirSync(entryPath);
250
- for (const scoped of scopedEntries) {
251
- const scopedPath = path.join(entryPath, scoped);
252
- const before = isLinked(scopedPath);
253
- freezeEntry(scopedPath, `${entry}/${scoped}`);
254
- if (before)
255
- frozenCount++;
256
- }
257
- }
258
- else {
259
- const before = isLinked(entryPath);
260
- freezeEntry(entryPath, entry);
261
- if (before)
262
- frozenCount++;
263
- }
264
- }
265
- console.log(`${ts()} [importgen] Frozen ${frozenCount} linked packages`);
266
- // Add preinstall guard to package.json
267
- try {
268
- const pkgRaw = fs.readFileSync(packageJsonPath, 'utf-8');
269
- const pkg = JSON.parse(pkgRaw);
270
- if (!pkg.scripts)
271
- pkg.scripts = {};
272
- if (pkg.scripts.preinstall) {
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}`);
214
+ const possibleHtmlFiles = ['index.html', 'default.html', 'default.htm'];
215
+ const htmlName = htmlFile || possibleHtmlFiles.find(f => fs.existsSync(path.join(dir, f)));
216
+ const htmlPath = htmlName ? path.join(dir, htmlName) : null;
217
+ if (!htmlPath || !fs.existsSync(htmlPath)) {
218
+ throw new Error(htmlFile
219
+ ? `${htmlFile} not found in ${dir}`
220
+ : `No HTML file found in ${dir}. Looking for: ${possibleHtmlFiles.join(', ')}`);
283
221
  }
222
+ return generateImportMap(pkgPath, htmlPath);
284
223
  }
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`);
300
- }
301
- else {
302
- delete pkg.scripts.preinstall;
303
- console.log(`${ts()} [importgen] Removed preinstall guard`);
304
- }
305
- fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
224
+ // CLI entry point
225
+ if (import.meta.main) {
226
+ const args = process.argv.slice(2);
227
+ if (args.includes('-v') || args.includes('--version')) {
228
+ console.log(`importgen ${packageJson.version}`);
229
+ process.exit(0);
306
230
  }
307
- catch (e) {
308
- console.error(`${ts()} [importgen] Error updating package.json: ${e.message}`);
231
+ const knownFlags = new Set(['-w', '--watch', '-v', '--version']);
232
+ const unknownArgs = args.filter(a => a.startsWith('-') && !knownFlags.has(a));
233
+ if (unknownArgs.length > 0) {
234
+ console.error(`${ts()} [importgen] Error: unrecognized argument(s): ${unknownArgs.join(', ')}`);
235
+ console.error(` Usage: importgen [htmlfile] [-w|--watch] [-v|--version]`);
309
236
  process.exit(1);
310
237
  }
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`);
316
- }
317
- catch (e) {
318
- console.error(`${ts()} [importgen] npm install failed: ${e.message}`);
238
+ const watchMode = args.includes('-w') || args.includes('--watch');
239
+ const positionalArgs = args.filter(a => !a.startsWith('-'));
240
+ if (positionalArgs.length > 1) {
241
+ console.error(`${ts()} [importgen] Error: too many arguments: ${positionalArgs.join(', ')}`);
242
+ console.error(` Usage: importgen [htmlfile] [-w|--watch] [-v|--version]`);
319
243
  process.exit(1);
320
244
  }
321
- }
322
- // Parse CLI arguments
323
- const args = process.argv.slice(2);
324
- if (args.includes('-v') || args.includes('--version')) {
325
- console.log(`importgen ${packageJson.version}`);
326
- process.exit(0);
327
- }
328
- const knownFlags = new Set(['-w', '--watch', '--freeze', '-freeze', '--unfreeze', '-unfreeze', '-v', '--version']);
329
- // Report unrecognized flags as errors
330
- const unknownArgs = args.filter(a => a.startsWith('-') && !knownFlags.has(a));
331
- if (unknownArgs.length > 0) {
332
- console.error(`${ts()} [importgen] Error: unrecognized argument(s): ${unknownArgs.join(', ')}`);
333
- console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [--unfreeze] [-v|--version]`);
334
- process.exit(1);
335
- }
336
- const watchMode = args.includes('-w') || args.includes('--watch');
337
- const freezeMode = args.includes('--freeze') || args.includes('-freeze');
338
- const unfreezeMode = args.includes('--unfreeze') || args.includes('-unfreeze');
339
- const positionalArgs = args.filter(a => !a.startsWith('-'));
340
- if (positionalArgs.length > 1) {
341
- console.error(`${ts()} [importgen] Error: too many arguments: ${positionalArgs.join(', ')}`);
342
- console.error(` Usage: importgen [htmlfile] [-w|--watch] [--freeze] [--unfreeze] [-v|--version]`);
343
- process.exit(1);
344
- }
345
- const htmlArg = positionalArgs[0];
346
- const packageJsonPath = path.join(process.cwd(), 'package.json');
347
- // Resolve HTML file: explicit argument, or search for common names
348
- const possibleHtmlFiles = ['index.html', 'default.html', 'default.htm'];
349
- const htmlFileName = htmlArg || possibleHtmlFiles.find(f => fs.existsSync(path.join(process.cwd(), f)));
350
- const htmlFilePath = htmlFileName ? path.join(process.cwd(), htmlFileName) : null;
351
- // Check if files exist
352
- if (!fs.existsSync(packageJsonPath)) {
353
- console.error(`${ts()} [generate-importmap] Error: package.json not found in current directory`);
354
- process.exit(1);
355
- }
356
- if (freezeMode) {
357
- freezeDependencies(packageJsonPath);
358
- process.exit(0);
359
- }
360
- if (unfreezeMode) {
361
- unfreezeDependencies(packageJsonPath);
362
- process.exit(0);
363
- }
364
- if (!htmlFilePath || !fs.existsSync(htmlFilePath)) {
365
- if (htmlArg) {
366
- console.error(`${ts()} [generate-importmap] Error: ${htmlArg} not found in current directory`);
367
- }
368
- else {
369
- console.error(`${ts()} [generate-importmap] Error: No HTML file found. Looking for: ${possibleHtmlFiles.join(', ')}`);
245
+ const htmlArg = positionalArgs[0];
246
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
247
+ const possibleHtmlFiles = ['index.html', 'default.html', 'default.htm'];
248
+ const htmlFileName = htmlArg || possibleHtmlFiles.find(f => fs.existsSync(path.join(process.cwd(), f)));
249
+ const htmlFilePath = htmlFileName ? path.join(process.cwd(), htmlFileName) : null;
250
+ if (!fs.existsSync(packageJsonPath)) {
251
+ console.error(`${ts()} [generate-importmap] Error: package.json not found in current directory`);
252
+ process.exit(1);
370
253
  }
371
- process.exit(1);
372
- }
373
- if (watchMode) {
374
- let result = generateImportMap(packageJsonPath, htmlFilePath);
375
- let watchedDepDirs = new Set();
376
- const watcher = chokidar.watch([packageJsonPath], {
377
- persistent: true,
378
- ignoreInitial: true
379
- });
380
- /** Update watcher to include/remove dependency directories */
381
- function syncWatchedDeps(depDirs) {
382
- const newDirs = new Set(depDirs);
383
- // Remove dirs no longer in dependency list
384
- for (const dir of watchedDepDirs) {
385
- if (!newDirs.has(dir)) {
386
- watcher.unwatch(dir);
387
- }
254
+ if (!htmlFilePath || !fs.existsSync(htmlFilePath)) {
255
+ if (htmlArg) {
256
+ console.error(`${ts()} [generate-importmap] Error: ${htmlArg} not found in current directory`);
388
257
  }
389
- // Add new dirs
390
- for (const dir of newDirs) {
391
- if (!watchedDepDirs.has(dir)) {
392
- watcher.add(dir);
393
- }
258
+ else {
259
+ console.error(`${ts()} [generate-importmap] Error: No HTML file found. Looking for: ${possibleHtmlFiles.join(', ')}`);
394
260
  }
395
- watchedDepDirs = newDirs;
261
+ process.exit(1);
396
262
  }
397
- syncWatchedDeps(result.depDirs);
398
- let debounceTimer = null;
399
- const onChange = () => {
400
- if (debounceTimer)
401
- clearTimeout(debounceTimer);
402
- debounceTimer = setTimeout(() => {
403
- result = generateImportMap(packageJsonPath, htmlFilePath);
404
- syncWatchedDeps(result.depDirs);
405
- }, 100);
406
- };
407
- watcher.on('change', onChange);
408
- watcher.on('add', onChange);
409
- watcher.on('unlink', onChange);
410
- watcher.on('ready', () => {
411
- const total = 1 + watchedDepDirs.size;
412
- console.log(`${ts()} [generate-importmap] Watching ${total} directories for changes...`);
413
- if (watchedDepDirs.size > 0) {
263
+ if (watchMode) {
264
+ let result = generateImportMap(packageJsonPath, htmlFilePath);
265
+ let watchedDepDirs = new Set();
266
+ const watcher = chokidar.watch([packageJsonPath], {
267
+ persistent: true,
268
+ ignoreInitial: true
269
+ });
270
+ function syncWatchedDeps(depDirs) {
271
+ const newDirs = new Set(depDirs);
414
272
  for (const dir of watchedDepDirs) {
415
- console.log(` ${dir}`);
273
+ if (!newDirs.has(dir)) {
274
+ watcher.unwatch(dir);
275
+ }
416
276
  }
277
+ for (const dir of newDirs) {
278
+ if (!watchedDepDirs.has(dir)) {
279
+ watcher.add(dir);
280
+ }
281
+ }
282
+ watchedDepDirs = newDirs;
417
283
  }
418
- });
419
- watcher.on('error', (error) => {
420
- console.error(`${ts()} [generate-importmap] Watcher error:`, error.message);
421
- });
422
- }
423
- else {
424
- generateImportMap(packageJsonPath, htmlFilePath);
284
+ syncWatchedDeps(result.depDirs);
285
+ let debounceTimer = null;
286
+ const onChange = () => {
287
+ if (debounceTimer)
288
+ clearTimeout(debounceTimer);
289
+ debounceTimer = setTimeout(() => {
290
+ result = generateImportMap(packageJsonPath, htmlFilePath);
291
+ syncWatchedDeps(result.depDirs);
292
+ }, 100);
293
+ };
294
+ watcher.on('change', onChange);
295
+ watcher.on('add', onChange);
296
+ watcher.on('unlink', onChange);
297
+ watcher.on('ready', () => {
298
+ const total = 1 + watchedDepDirs.size;
299
+ console.log(`${ts()} [generate-importmap] Watching ${total} directories for changes...`);
300
+ if (watchedDepDirs.size > 0) {
301
+ for (const dir of watchedDepDirs) {
302
+ console.log(` ${dir}`);
303
+ }
304
+ }
305
+ });
306
+ watcher.on('error', (error) => {
307
+ console.error(`${ts()} [generate-importmap] Watcher error:`, error.message);
308
+ });
309
+ }
310
+ else {
311
+ generateImportMap(packageJsonPath, htmlFilePath);
312
+ }
425
313
  }
426
314
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@bobfrankston/importgen",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Generate ES Module import maps from package.json dependencies for native browser module loading",
5
5
  "main": "index.js",
6
+ "exports": {
7
+ ".": "./index.js"
8
+ },
6
9
  "bin": {
7
10
  "importgen": "index.js"
8
11
  },
9
12
  "type": "module",
10
13
  "scripts": {
14
+ "start": "node index.js --watch",
11
15
  "build": "tsc",
12
16
  "watch": "tsc -w",
13
17
  "preversion": "npm run build",
@@ -1,17 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm run build:*)",
5
- "Bash(npmglobalize)",
6
- "Bash(npx tsc)",
7
- "Bash(node index.js:*)",
8
- "Bash(dir:*)",
9
- "Bash(ls:*)",
10
- "Bash(where extractids:*)",
11
- "Bash(cd \"y:/dev/utils/importgen\" && npx tsc --noEmit 2>&1222)",
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:*)"
15
- ]
16
- }
17
- }