@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 +19 -0
- package/index.d.ts +11 -1
- package/index.js +94 -206
- package/package.json +5 -1
- package/.claude/settings.local.json +0 -17
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
|
-
|
|
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
|
-
*
|
|
205
|
-
*
|
|
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
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
console.
|
|
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
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
261
|
+
process.exit(1);
|
|
396
262
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.
|
|
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
|
-
}
|