importmapify 1.4.0 → 1.6.0

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
@@ -21,12 +21,14 @@ per matched source file instead. This package performs that expansion and writes
21
21
 
22
22
  ```sh
23
23
  npm install importmapify
24
+ deno add jsr:@kjanat/importmapify
24
25
  ```
25
26
 
26
27
  ## CLI
27
28
 
28
29
  ```sh
29
30
  npx importmapify --root . --out import_map.json
31
+ deno run -A jsr:@kjanat/importmapify [...]
30
32
  ```
31
33
 
32
34
  | Flag | Alias | Meaning | Default |
@@ -39,6 +41,8 @@ npx importmapify --root . --out import_map.json
39
41
  | `--scope prefix::key=value` | `-s` | Scoped import override; repeatable | none |
40
42
  | `--condition name` | `-c` | Condition tried when a target is a conditional object; repeatable | `import`, `default` |
41
43
  | `--ext name` | `-e` | Restrict pattern matches to these file extensions; repeatable | all files |
44
+ | `--filter regex` | `-f` | Regular expression a pattern target path must match; repeatable | all files |
45
+ | `--indent value` | | Indentation as a number of spaces or the word `tab` | tab |
42
46
  | `--config path` | `-C` | Config file path; skips discovery | auto-discovered |
43
47
  | `--no-config` | | Skip config file discovery | off |
44
48
  | `--check` | | Exit 1 if the output file is stale, without writing it | off |
@@ -84,9 +88,9 @@ Then `npx importmapify` reads it automatically. Point at a specific file with `-
84
88
  `--no-config`.
85
89
 
86
90
  Explicitly-passed flags override the config, which overrides built-in defaults: `--import`/`--package`/`--scope` merge
87
- onto the config's records with flag keys winning; `--condition`/`--ext` replace; `--root`/`--manifest`/`--out` win when
88
- passed. Configs are imported natively; a TypeScript config needs a type-stripping runtime (Bun, Deno, or Node \>= 22.6);
89
- on older Node use a `.mjs`/`.cjs`/`.js` config.
91
+ onto the config's records with flag keys winning; `--condition`/`--ext`/`--filter` replace;
92
+ `--root`/`--manifest`/`--out` win when passed. Configs are imported natively; a TypeScript config needs a type-stripping
93
+ runtime (Bun, Deno, or Node \>= 22.6); on older Node use a `.mjs`/`.cjs`/`.js` config.
90
94
 
91
95
  ## Library
92
96
 
@@ -126,15 +130,17 @@ const out = writeImportMap({
126
130
  | `additionalImports` | Extra entries merged in after manifest expansion and packages; these win on key collision. | none |
127
131
  | `scopes` | Scope prefixes mapped to scope-specific import overrides. | none |
128
132
  | `relativeTo` | Directory the written targets are rebased against. | `root` |
129
- | `extensions` | File extensions, with or without a leading dot, that pattern targets may match. | all files |
133
+ | `extensions` | Extension whitelist limiting which pattern targets are kept. | all files |
134
+ | `filter` | `RegExp`/predicate matchers a pattern target path must pass; combines with `extensions`. | all files |
130
135
 
131
- `WriteImportMapOptions` extends the above with an optional `out`, defaulting to `import_map.json` like the CLI. It is
132
- resolved against `root` and accepts a relative path, an absolute path, or a `file://` URL. `writeImportMap` rebases
133
- automatically against `out`'s directory, so a nested `out` (for example `.cache/maps/import_map.json`) still produces
134
- targets that resolve correctly from the map's own location.
136
+ `WriteImportMapOptions` extends the above with an optional `out`, defaulting to `import_map.json` like the CLI, and an
137
+ optional `indent` with `JSON.stringify` space semantics (a number of spaces or a literal string), defaulting to a tab.
138
+ `out` is resolved against `root` and accepts a relative path, an absolute path, or a `file://` URL. `writeImportMap`
139
+ rebases automatically against `out`'s directory, so a nested `out` (for example `.cache/maps/import_map.json`) still
140
+ produces targets that resolve correctly from the map's own location.
135
141
 
136
- `Config` extends `Partial<WriteImportMapOptions>` with an optional `hooks` field the shape a config file's default
137
- export and `defineConfig` take, with every field optional. An omitted `root` defaults to the config file's own
142
+ `Config` extends `Partial<WriteImportMapOptions>` with an optional `hooks` field. It is the shape a config file's
143
+ default export and `defineConfig` take, with every field optional. An omitted `root` defaults to the config file's own
138
144
  directory; the CLI supplies the remaining defaults.
139
145
 
140
146
  `defineConfig` returns its argument unchanged; it exists only to type a config object for export and reuse without a
@@ -172,6 +178,17 @@ import { createImportMap } from 'importmapify';
172
178
  createImportMap({ root: import.meta.dirname, extensions: ['ts', 'tsx'] });
173
179
  ```
174
180
 
181
+ `filter` takes `RegExp` or predicate matchers tested against each candidate target path; a target is kept only when
182
+ every matcher accepts it. Combined with `extensions`, this drops hashed build chunks a `dist/*` pattern would otherwise
183
+ enumerate:
184
+
185
+ ```ts
186
+ import { createImportMap } from 'importmapify';
187
+
188
+ // Keep .js targets whose path is not an internal chunk like ./dist/internal-qo9O8jzH.js.
189
+ createImportMap({ root: import.meta.dirname, extensions: ['js'], filter: [/^(?!.*internal)/] });
190
+ ```
191
+
175
192
  Build one conformant pair directly with `packageEntries`:
176
193
 
177
194
  ```ts
package/deno.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/denoland/deno/refs/heads/main/cli/schemas/config-file.v1.json",
3
3
  "name": "@kjanat/importmapify",
4
- "version": "1.4.0",
4
+ "version": "1.6.0",
5
5
  "exports": "./src/mod.ts",
6
6
  "publish": {
7
7
  "include": [
@@ -16,26 +16,38 @@
16
16
  "compilerOptions": {
17
17
  "lib": ["ESNext", "DOM", "deno.ns"]
18
18
  },
19
- "importMap": "./import_map.json",
19
+ "importMap": "import_map.json",
20
20
  "license": "MIT",
21
21
  "tasks": {
22
22
  "build": {
23
- "description": "build for npm distribution",
24
- "command": "bun run build",
25
- "files": ["src/**", "package.json", "bun.lock"],
26
- "output": ["dist/**", "deno.json"]
23
+ "description": "build for npm distribution (requires bun)",
24
+ "command": "bun run build"
27
25
  },
28
26
  "publish": {
29
27
  "command": "deno publish --config deno.json --allow-dirty",
30
- "description": "Publish package to jsr",
31
- "dependencies": ["importmapify"]
28
+ "description": "Publish package to jsr"
32
29
  },
33
30
  "importmapify": {
34
- "description": "generate importmap for deno",
35
- "command": "bun scripts/generate-importmap.ts",
36
- "files": ["package.json", "scripts/generate-importmap.ts", "src/**"],
37
- "output": ["import_map.json"]
38
- }
31
+ "description": "generate importmap for deno (requires bun)",
32
+ "command": "bun scripts/generate-importmap.ts"
33
+ },
34
+ "doc:html": {
35
+ "command": "sh -c 'set -e; root=$PWD; [ -f dist/mod.d.mts ] || bun run build; tmp=$(mktemp -d); { awk \"NR>1{print} /\\*\\//{if(NR>1) exit}\" src/mod.ts; cat dist/mod.d.mts; } > \"$tmp/importmapify.d.ts\"; cd \"$tmp\"; deno doc --html --name=\"$(jq -r .name \"$root/package.json\")\" --category-docs=\"$root/category-docs.json\" --output=\"$root/.denodocs/\" ./importmapify.d.ts; cd \"$root\"; deno doc --json $(jq -r \".exports | .[]? // .\" deno.json) > \"$tmp/nodes.json\" 2>/dev/null; bun scripts/link-source-buttons.mjs .denodocs \"$tmp/nodes.json\" \"$(git rev-parse HEAD)\"; bun scripts/inject-favicon.mjs .denodocs assets/favicon.svg; bunx vite-svg-to-ico generate assets/favicon.svg --out-dir .denodocs; rm -rf \"$tmp\"'",
36
+ "description": "generate categorized HTML docs into .denodocs (requires bun)"
37
+ },
38
+ "doc:json": {
39
+ "command": "mkdir -p .denodocs && deno doc --name=\"$(jq -r .name package.json)\" --json $(jq -r '.exports | .[]? // .' deno.json) > .denodocs/documentation.json",
40
+ "description": "emit JSON doc nodes into .denodocs"
41
+ },
42
+ "doc:lint": {
43
+ "command": "deno doc --lint $(jq -r '.exports | .[]? // .' deno.json)",
44
+ "description": "lint docs on exported symbols"
45
+ },
46
+ "format:docs": {
47
+ "command": "bunx dprint fmt --includes-override '.denodocs/**/*.{html,css,js,json}' --excludes-override='!.denodocs/**/*.{html,css,js,json}' --no-gitignore --allow-no-files",
48
+ "description": "format the output docs (requires bun)"
49
+ },
50
+ "ci:docs": "run -s doc:html doc:json format:docs"
39
51
  },
40
52
  "lint": {
41
53
  "rules": {
@@ -43,7 +55,7 @@
43
55
  }
44
56
  },
45
57
  "preferPackageJson": false,
46
- "homepage": "https://github.com/kjanat/importmapify#readme",
58
+ "homepage": "https://importmapify.kjanat.dev",
47
59
  "repository": {
48
60
  "type": "git",
49
61
  "url": "git+https://github.com/kjanat/importmapify.git"
package/dist/mod.d.mts CHANGED
@@ -1,67 +1,335 @@
1
- //#region src/map.d.ts
2
- /** A deterministic Deno import map generated from package import entries. */
1
+ //#region src/types.d.ts
2
+ /**
3
+ * A deterministic Deno import map generated from package import entries, following the
4
+ * {@link https://html.spec.whatwg.org/multipage/webappapis.html#import-maps | Import Maps Standard}.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import type { ImportMapDocument } from 'jsr:@kjanat/importmapify';
9
+ *
10
+ * const doc: ImportMapDocument = {
11
+ * imports: { '#lib/bytes': './src/lib/bytes.ts' },
12
+ * scopes: { './tests/': { '#lib/bytes': './tests/stub/bytes.ts' } },
13
+ * };
14
+ * ```
15
+ * @category Generate
16
+ */
3
17
  interface ImportMapDocument {
4
- /** Exact specifier-to-target mappings, sorted by specifier. */
18
+ /**
19
+ * Exact specifier-to-target mappings, sorted by specifier, as in Deno's
20
+ * {@link https://docs.deno.com/runtime/reference/deno_json/#custom-path-mappings | custom path mappings}.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const imports = { '#lib/bytes': './src/lib/bytes.ts' };
25
+ * ```
26
+ */
5
27
  readonly imports: Readonly<Record<string, string>>;
6
- /** Scope prefixes mapped to sorted, scope-specific import overrides. */
28
+ /**
29
+ * Scope prefixes mapped to sorted, scope-specific import overrides, as in Deno's
30
+ * {@link https://docs.deno.com/runtime/reference/deno_json/#scoped-mappings | scoped mappings}.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const scopes = { './tests/': { '#lib/bytes': './tests/stub/bytes.ts' } };
35
+ * ```
36
+ */
7
37
  readonly scopes?: Readonly<Record<string, Readonly<Record<string, string>>>>;
8
38
  }
9
- /** Options for creating an import map without writing it to disk. */
39
+ /**
40
+ * A filesystem location as a path string or `file://` URL, accepted wherever an option names a
41
+ * directory or file.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import type { PathOrUrl } from 'jsr:@kjanat/importmapify';
46
+ *
47
+ * const root: PathOrUrl = new URL('..', import.meta.url);
48
+ * ```
49
+ * @category Options
50
+ */
51
+ type PathOrUrl = string | URL;
52
+ /**
53
+ * A matcher for {@link CreateImportMapOptions.filter | filter}, tested against a candidate target
54
+ * path such as `./dist/internal-qo9O8jzH.js`. A target is kept only when every matcher accepts it.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import type { TargetFilter } from 'jsr:@kjanat/importmapify';
59
+ *
60
+ * // Drop hashed internal chunks like ./dist/internal-qo9O8jzH.js.
61
+ * const filter: readonly TargetFilter[] = [/^(?!.*internal)/];
62
+ * ```
63
+ * @category Options
64
+ */
65
+ type TargetFilter = RegExp | ((target: string) => boolean);
66
+ /**
67
+ * Options for creating an import map without writing it to disk.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { createImportMap, type CreateImportMapOptions } from 'jsr:@kjanat/importmapify';
72
+ *
73
+ * const options: CreateImportMapOptions = {
74
+ * root: import.meta.dirname,
75
+ * conditions: ['deno', 'import', 'default'],
76
+ * extensions: ['ts', 'tsx'],
77
+ * };
78
+ *
79
+ * createImportMap(options);
80
+ * ```
81
+ * @category Options
82
+ */
10
83
  interface CreateImportMapOptions {
11
- /** Project directory containing the package manifest, as a path or `file://` URL. */
12
- readonly root: string | URL;
13
- /** Manifest path relative to {@link root}. Defaults to `package.json`. */
84
+ /**
85
+ * Project directory containing the package {@linkcode manifest}, as a path or `file://` URL.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * const root = import.meta.dirname;
90
+ * ```
91
+ */
92
+ readonly root: PathOrUrl;
93
+ /**
94
+ * Manifest path relative to {@link root}. Defaults to `package.json`.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const manifest = 'deno.json';
99
+ * ```
100
+ * @defaultValue `'package.json'`
101
+ */
14
102
  readonly manifest?: string;
15
- /** Conditional import keys to try in order. Defaults to `import`, then `default`. */
103
+ /**
104
+ * Conditional import keys to try in order. Defaults to `import`, then `default`.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const conditions = ['deno', 'import', 'default'];
109
+ * ```
110
+ * @defaultValue `['import', 'default']`
111
+ */
16
112
  readonly conditions?: readonly string[];
17
- /** Package specifiers mapped to targets, each expanded to a conformant bare and trailing-slash pair. */
113
+ /**
114
+ * Package specifiers mapped to targets, each expanded to a conformant bare and trailing-slash pair.
115
+ * Defaults to none.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * const packages = { dreamcli: 'jsr:@kjanat/dreamcli@^3' };
120
+ * ```
121
+ * @defaultValue `{}`
122
+ */
18
123
  readonly packages?: Readonly<Record<string, string>>;
19
- /** Explicit entries merged after manifest imports and packages, overriding duplicate keys. */
124
+ /**
125
+ * Explicit entries merged after manifest imports and packages, overriding duplicate keys. Defaults to none.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const additionalImports = { '#config': './src/config.ts' };
130
+ * ```
131
+ * @defaultValue `{}`
132
+ */
20
133
  readonly additionalImports?: Readonly<Record<string, string>>;
21
- /** Scope-specific import overrides keyed by scope prefix. */
134
+ /**
135
+ * Scope-specific import overrides keyed by scope prefix, following Deno's
136
+ * {@link https://docs.deno.com/runtime/reference/deno_json/#scoped-mappings | scoped mappings}. Defaults to none.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const scopes = { './tests/': { '#lib/bytes': './tests/stub/bytes.ts' } };
141
+ * ```
142
+ * @defaultValue `{}`
143
+ */
22
144
  readonly scopes?: Readonly<Record<string, Readonly<Record<string, string>>>>;
23
- /** Directory against which relative targets are rebased, as a path or `file://` URL. Defaults to {@link root}. */
24
- readonly relativeTo?: string | URL;
25
- /** File extensions, with or without a leading dot, that pattern targets may match. Unset matches every file. */
145
+ /**
146
+ * Directory the generated import map will be read from, as a path or `file://` URL.
147
+ *
148
+ * Manifest targets are written relative to {@link root}, but Deno resolves an import map's relative targets
149
+ * from the location of the map file. Each relative target is rewritten to be relative to this directory
150
+ * instead, so it still points at the right file once the map moves. `writeImportMap` and the CLI set
151
+ * this automatically to the output file's directory; set it yourself only when placing an in-memory map
152
+ * somewhere other than {@link root}. Defaults to {@link root}.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // Map root is /proj, target is ./src/bytes.ts, map will live in /proj/.cache.
157
+ * const relativeTo = '/proj/.cache';
158
+ * // The emitted target becomes ../src/bytes.ts, which resolves back to /proj/src/bytes.ts.
159
+ * ```
160
+ * @defaultValue {@link root}
161
+ */
162
+ readonly relativeTo?: PathOrUrl;
163
+ /**
164
+ * Extension whitelist limiting which pattern targets are kept, with or without leading dots.
165
+ * Combines with {@link filter}. Unset keeps every extension.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const extensions = ['ts', 'tsx'];
170
+ * ```
171
+ * @defaultValue `[]`
172
+ */
26
173
  readonly extensions?: readonly string[];
174
+ /**
175
+ * Matchers a candidate pattern target must pass, each tested against the target path. A target is kept
176
+ * only when every matcher accepts it. Combines with {@link extensions}. Unset keeps every target.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * // Keep .js targets that are not hashed internal chunks.
181
+ * const options = { extensions: ['js'], filter: [/^(?!.*internal)/] };
182
+ * ```
183
+ * @defaultValue `[]`
184
+ */
185
+ readonly filter?: readonly TargetFilter[];
27
186
  }
28
- /** Options for creating and writing an import map. */
187
+ /**
188
+ * Options for creating and writing an import map.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * import { writeImportMap, type WriteImportMapOptions } from 'jsr:@kjanat/importmapify';
193
+ *
194
+ * const options: WriteImportMapOptions = {
195
+ * root: import.meta.dirname,
196
+ * out: '.cache/maps/import_map.json',
197
+ * };
198
+ *
199
+ * writeImportMap(options);
200
+ * ```
201
+ * @category Options
202
+ */
29
203
  interface WriteImportMapOptions extends CreateImportMapOptions {
30
204
  /**
31
205
  * Output path, resolved against {@link CreateImportMapOptions.root | root}. Accepts a relative path, an
32
206
  * absolute path, a `file://` URL string, or a {@link URL}. Defaults to `import_map.json`.
207
+ *
208
+ * Deno loads the written file through the `importMap` field in `deno.json` or the `--import-map` flag; see
209
+ * {@link https://docs.deno.com/runtime/fundamentals/modules/#differentiating-between-imports-or-importmap-in-deno.json-and---import-map-option | Deno: imports vs importMap}.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * const out = '.cache/maps/import_map.json';
214
+ * ```
215
+ * @defaultValue `'import_map.json'`
216
+ */
217
+ readonly out?: PathOrUrl;
218
+ /**
219
+ * Indentation for the serialized map, with the semantics of `JSON.stringify`'s
220
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#space | space parameter}:
221
+ * a number of spaces per level or a literal indent string. Defaults to a tab.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const indent = 2;
226
+ * ```
227
+ * @defaultValue `'\t'`
33
228
  */
34
- readonly out?: string | URL;
229
+ readonly indent?: string | number;
35
230
  }
36
- /** Resolved paths shared by every generation hook. */
231
+ /**
232
+ * Resolved paths shared by every generation hook.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * import type { HookContext } from 'jsr:@kjanat/importmapify';
237
+ *
238
+ * const logRoot = (ctx: HookContext) => console.log(`scanning ${ctx.root}, writing ${ctx.out}`);
239
+ * ```
240
+ * @category Configuration
241
+ */
37
242
  interface HookContext {
38
- /** Absolute project root that gets scanned. */
243
+ /**
244
+ * Absolute project root that gets scanned.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * const root = '/home/me/project';
249
+ * ```
250
+ */
39
251
  readonly root: string;
40
- /** Absolute output path the map resolves against. */
252
+ /**
253
+ * Absolute output path the map resolves against.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * const out = '/home/me/project/import_map.json';
258
+ * ```
259
+ */
41
260
  readonly out: string;
42
261
  }
43
262
  /**
44
263
  * Lifecycle hooks the CLI runs around import map generation, modeled on tsdown's hooks.
45
264
  *
46
265
  * Each hook may be async; the CLI awaits it. The synchronous library functions ignore hooks.
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * // Build generated targets before the scan so they land in the map.
270
+ * import { execSync } from 'node:child_process';
271
+ * import { defineConfig } from 'jsr:@kjanat/importmapify';
272
+ *
273
+ * export default defineConfig({
274
+ * hooks: {
275
+ * 'generate:before': () => execSync('deno task build', { stdio: 'inherit' }),
276
+ * },
277
+ * });
278
+ * ```
279
+ * @category Configuration
47
280
  */
48
281
  interface ImportMapHooks {
49
- /** Runs before the filesystem is scanned. Build pattern targets here so they exist when patterns expand. */
282
+ /**
283
+ * Runs before the filesystem is scanned. Build pattern targets here so they exist when patterns expand.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * const onBefore = (ctx) => console.log('building targets under', ctx.root);
288
+ * ```
289
+ */
50
290
  readonly "generate:before": (context: HookContext) => void | Promise<void>;
51
- /** Runs after the map is generated and emitted. */
291
+ /**
292
+ * Runs after the map is generated and emitted.
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * const onDone = (ctx) => console.log('mapped', Object.keys(ctx.map.imports).length, 'imports');
297
+ * ```
298
+ */
52
299
  readonly "generate:done": (context: HookContext & {
53
300
  readonly map: ImportMapDocument;
54
301
  }) => void | Promise<void>;
55
302
  }
56
303
  /**
57
- * An importmapify config file: the shape a config file's default export and {@linkcode defineConfig} take.
304
+ * An importmapify config file: the shape a config file's default export and `defineConfig` take.
58
305
  * Every field is optional; the config loader and CLI supply {@linkcode CreateImportMapOptions.root | root}
59
306
  * and the remaining defaults.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * // importmapify.config.ts; root is omitted, so it defaults to this file's directory.
311
+ * import { defineConfig } from 'jsr:@kjanat/importmapify';
312
+ *
313
+ * export default defineConfig({
314
+ * packages: { dreamcli: 'jsr:@kjanat/dreamcli@^3' },
315
+ * extensions: ['ts'],
316
+ * });
317
+ * ```
318
+ * @category Configuration
60
319
  */
61
320
  interface Config extends Partial<WriteImportMapOptions> {
62
- /** Lifecycle hooks the CLI runs around generation. Ignored by {@link writeImportMap} and {@link createImportMap}. */
321
+ /**
322
+ * Lifecycle hooks the CLI runs around generation. Ignored by `writeImportMap` and `createImportMap`.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * const hooks = { 'generate:before': (ctx) => console.log(ctx.root) };
327
+ * ```
328
+ */
63
329
  readonly hooks?: Partial<ImportMapHooks>;
64
330
  }
331
+ //#endregion
332
+ //#region src/map.d.ts
65
333
  /**
66
334
  * Expand a package's exact and patterned `imports` into a Deno import map.
67
335
  *
@@ -83,27 +351,28 @@ interface Config extends Partial<WriteImportMapOptions> {
83
351
  *
84
352
  * @param options Project, manifest, condition, and rebasing options.
85
353
  * @returns A sorted import map document.
354
+ * @category Generate
86
355
  */
87
356
  declare function createImportMap(options: CreateImportMapOptions): ImportMapDocument;
88
357
  /**
89
- * Serialize an import map as stable, tab-indented JSON with a trailing newline.
358
+ * Serialize an import map as stable JSON with a trailing newline.
90
359
  *
91
360
  * @example
92
361
  * ```ts
93
- * // Format an import map for stdout.
362
+ * // Format an import map for stdout, indented with two spaces.
94
363
  * import { formatImportMap } from 'jsr:@kjanat/importmapify';
95
364
  *
96
- * const text = formatImportMap({
97
- * imports: { '#config': './src/config.ts' },
98
- * });
365
+ * const text = formatImportMap({ imports: { '#config': './src/config.ts' } }, 2);
99
366
  *
100
367
  * console.log(text);
101
368
  * ```
102
369
  *
103
370
  * @param map Import map to serialize.
371
+ * @param indent Indentation with `JSON.stringify` space semantics; defaults to a tab.
104
372
  * @returns Formatted JSON ready to print or write.
373
+ * @category Generate
105
374
  */
106
- declare function formatImportMap(map: ImportMapDocument): string;
375
+ declare function formatImportMap(map: ImportMapDocument, indent?: string | number): string;
107
376
  /**
108
377
  * Create an import map and write it to disk, creating parent directories as needed.
109
378
  *
@@ -121,6 +390,7 @@ declare function formatImportMap(map: ImportMapDocument): string;
121
390
  *
122
391
  * @param options Creation options plus the optional output path.
123
392
  * @returns The absolute path of the written file.
393
+ * @category Generate
124
394
  */
125
395
  declare function writeImportMap(options: WriteImportMapOptions): string;
126
396
  /**
@@ -140,6 +410,7 @@ declare function writeImportMap(options: WriteImportMapOptions): string;
140
410
  * @param name Bare package specifier.
141
411
  * @param target Exact target for {@link name}.
142
412
  * @returns The bare entry and its trailing-slash subpath entry.
413
+ * @category Generate
143
414
  */
144
415
  declare function packageEntries(name: string, target: string): Record<string, string>;
145
416
  /**
@@ -158,10 +429,24 @@ declare function packageEntries(name: string, target: string): Record<string, st
158
429
  * writeImportMap(config);
159
430
  * ```
160
431
  *
432
+ * @example
433
+ * ```ts
434
+ * // Config file with a build hook: generate:before runs before the CLI scans.
435
+ * import { execSync } from 'node:child_process';
436
+ * import { defineConfig } from 'jsr:@kjanat/importmapify';
437
+ *
438
+ * export default defineConfig({
439
+ * hooks: {
440
+ * 'generate:before': () => execSync('deno task build', { stdio: 'inherit' }),
441
+ * },
442
+ * });
443
+ * ```
444
+ *
161
445
  * @param config Import map configuration; every field is optional.
162
- * @returns The same `config` value with its exact type preserved, so a config that includes `root` stays
446
+ * @returns The same {@linkcode config} value with its exact type preserved, so a config that includes {@linkcode CreateImportMapOptions.root} stays
163
447
  * assignable to {@link writeImportMap} while one that omits it is still a valid config file.
448
+ * @category Configuration
164
449
  */
165
450
  declare function defineConfig<T extends Config>(config: T): T;
166
451
  //#endregion
167
- export { type Config, type CreateImportMapOptions, type HookContext, type ImportMapDocument, type ImportMapHooks, type WriteImportMapOptions, createImportMap, defineConfig, formatImportMap, packageEntries, writeImportMap };
452
+ export { type Config, type CreateImportMapOptions, type HookContext, type ImportMapDocument, type ImportMapHooks, type PathOrUrl, type TargetFilter, type WriteImportMapOptions, createImportMap, defineConfig, formatImportMap, packageEntries, writeImportMap };
package/dist/mod.mjs CHANGED
@@ -118,6 +118,10 @@ function rebaseTarget(root, relativeTo, target) {
118
118
  }
119
119
  //#endregion
120
120
  //#region src/config.ts
121
+ /** Set `key` on `target` unless the value is `undefined`, keeping the key/value pairing type-checked. */
122
+ function assign(target, key, value) {
123
+ if (value !== void 0) target[key] = value;
124
+ }
121
125
  const CONFIG_BASENAMES = ["importmapify.config", ".importmapify"];
122
126
  const CONFIG_EXTENSIONS = [
123
127
  "mjs",
@@ -169,9 +173,18 @@ function asString(value) {
169
173
  function asPath(value) {
170
174
  return typeof value === "string" || value instanceof URL ? value : void 0;
171
175
  }
176
+ function asIndent(value) {
177
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
178
+ }
172
179
  function asStringArray(value) {
173
180
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
174
181
  }
182
+ function isTargetFilter(value) {
183
+ return value instanceof RegExp || typeof value === "function";
184
+ }
185
+ function asTargetFilters(value) {
186
+ return Array.isArray(value) && value.every(isTargetFilter) ? value : void 0;
187
+ }
175
188
  function asStringRecord(value) {
176
189
  if (!isRecord(value)) return;
177
190
  const record = {};
@@ -191,6 +204,11 @@ function asScopes(value) {
191
204
  }
192
205
  return scopes;
193
206
  }
207
+ function resolveRoot(root, configDir) {
208
+ if (root === void 0) return configDir;
209
+ if (typeof root === "string" && !path.isAbsolute(root) && !root.startsWith("file://")) return path.resolve(configDir, root);
210
+ return root;
211
+ }
194
212
  function isHook(value) {
195
213
  return typeof value === "function";
196
214
  }
@@ -214,29 +232,18 @@ function asHooks(value) {
214
232
  * @returns The subset of {@link WriteImportMapOptions} the config declares, plus any hooks.
215
233
  */
216
234
  function configToOptions(config, configDir) {
217
- const result = {};
218
- const root = asPath(config.root);
219
- if (root === void 0) result.root = configDir;
220
- else if (typeof root === "string" && !path.isAbsolute(root) && !root.startsWith("file://")) result.root = path.resolve(configDir, root);
221
- else result.root = root;
222
- const manifest = asString(config.manifest);
223
- if (manifest !== void 0) result.manifest = manifest;
224
- const out = asPath(config.out);
225
- if (out !== void 0) result.out = out;
226
- const relativeTo = asPath(config.relativeTo);
227
- if (relativeTo !== void 0) result.relativeTo = relativeTo;
228
- const conditions = asStringArray(config.conditions);
229
- if (conditions !== void 0) result.conditions = conditions;
230
- const extensions = asStringArray(config.extensions);
231
- if (extensions !== void 0) result.extensions = extensions;
232
- const packages = asStringRecord(config.packages);
233
- if (packages !== void 0) result.packages = packages;
234
- const additionalImports = asStringRecord(config.additionalImports);
235
- if (additionalImports !== void 0) result.additionalImports = additionalImports;
236
- const scopes = asScopes(config.scopes);
237
- if (scopes !== void 0) result.scopes = scopes;
238
- const hooks = asHooks(config.hooks);
239
- if (hooks !== void 0) result.hooks = hooks;
235
+ const result = { root: resolveRoot(asPath(config.root), configDir) };
236
+ assign(result, "manifest", asString(config.manifest));
237
+ assign(result, "out", asPath(config.out));
238
+ assign(result, "indent", asIndent(config.indent));
239
+ assign(result, "relativeTo", asPath(config.relativeTo));
240
+ assign(result, "conditions", asStringArray(config.conditions));
241
+ assign(result, "extensions", asStringArray(config.extensions));
242
+ assign(result, "filter", asTargetFilters(config.filter));
243
+ assign(result, "packages", asStringRecord(config.packages));
244
+ assign(result, "additionalImports", asStringRecord(config.additionalImports));
245
+ assign(result, "scopes", asScopes(config.scopes));
246
+ assign(result, "hooks", asHooks(config.hooks));
240
247
  return result;
241
248
  }
242
249
  /**
@@ -258,12 +265,26 @@ function mergeScopes(base, overrides) {
258
265
  //#endregion
259
266
  //#region src/map.ts
260
267
  const DEFAULT_CONDITIONS = ["import", "default"];
268
+ /** Default output filename for a written import map. */
261
269
  const DEFAULT_OUT = "import_map.json";
262
270
  const SCHEME_PREFIX = /^(jsr|npm):(?!\/)/;
271
+ /**
272
+ * Normalize a location to a filesystem path.
273
+ *
274
+ * @param value Path string, `file://` URL string, or {@link URL}.
275
+ * @returns The filesystem path.
276
+ */
263
277
  function toPath(value) {
264
278
  if (typeof value === "string") return value.startsWith("file://") ? fileURLToPath(value) : value;
265
279
  return fileURLToPath(value.href);
266
280
  }
281
+ /**
282
+ * Resolve an output location against a project root.
283
+ *
284
+ * @param root Project root directory.
285
+ * @param out Output location, relative or absolute.
286
+ * @returns The absolute output path.
287
+ */
267
288
  function resolveOut(root, out) {
268
289
  return path.resolve(toPath(root), toPath(out));
269
290
  }
@@ -293,17 +314,28 @@ function readManifest(manifestPath) {
293
314
  if (!isRecord(parsed)) throw new Error(`Manifest at ${manifestPath} must be a JSON object`);
294
315
  return parsed;
295
316
  }
296
- function extensionFilter(extensions) {
317
+ function matcherKeeps(matcher, target) {
318
+ return matcher instanceof RegExp ? matcher.test(target) : matcher(target);
319
+ }
320
+ /**
321
+ * Build a predicate deciding whether a candidate target survives: its extension must be in the
322
+ * whitelist (if any extensions are given), and every {@link TargetFilter} must also accept it.
323
+ */
324
+ function targetFilter(extensions, filters) {
297
325
  const allowed = new Set(extensions.map((ext) => ext.startsWith(".") ? ext : `.${ext}`));
298
- return (file) => allowed.has(path.extname(file));
326
+ return (target) => (allowed.size === 0 || allowed.has(path.extname(target))) && filters.every((matcher) => matcherKeeps(matcher, target));
299
327
  }
300
- function expandManifestImport(root, key, rawValue, { conditions, extensions }) {
328
+ function expandManifestImport(root, key, rawValue, { conditions, extensions, filter }) {
301
329
  const value = typeof rawValue === "string" ? rawValue : resolveCondition(rawValue, conditions);
302
330
  if (value === void 0) return {};
303
331
  const pattern = parsePattern(key, value);
304
332
  if (pattern === void 0) return { [key]: value };
305
- const files = filesUnder(path.join(root, pattern.targetDirectory));
306
- return expandPattern(pattern, extensions.length > 0 ? files.filter(extensionFilter(extensions)) : files);
333
+ const expanded = expandPattern(pattern, filesUnder(path.join(root, pattern.targetDirectory)));
334
+ if (extensions.length === 0 && filter.length === 0) return expanded;
335
+ const keep = targetFilter(extensions, filter);
336
+ const filtered = {};
337
+ for (const [specifier, target] of Object.entries(expanded)) if (keep(target)) filtered[specifier] = target;
338
+ return filtered;
307
339
  }
308
340
  function keyBaseLength(key) {
309
341
  const star = key.indexOf("*");
@@ -374,6 +406,7 @@ function rebaseScopePrefix(root, relativeTo, scope) {
374
406
  *
375
407
  * @param options Project, manifest, condition, and rebasing options.
376
408
  * @returns A sorted import map document.
409
+ * @category Generate
377
410
  */
378
411
  function createImportMap(options) {
379
412
  const root = toPath(options.root);
@@ -383,37 +416,38 @@ function createImportMap(options) {
383
416
  const relativeTo = options.relativeTo === void 0 ? root : toPath(options.relativeTo);
384
417
  const imports = expandManifest(root, relativeTo, manifestImports, {
385
418
  conditions,
386
- extensions: options.extensions ?? []
419
+ extensions: options.extensions ?? [],
420
+ filter: options.filter ?? []
387
421
  });
388
422
  for (const [key, value] of Object.entries(collectAdditional(options))) imports[key] = rebaseTarget(root, relativeTo, value);
389
423
  const scopes = buildScopes$1(root, relativeTo, options.scopes ?? {});
390
424
  const sortedImports = sortEntries(imports);
391
- const sortedScopes = Object.fromEntries(Object.entries(scopes).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
425
+ const sortedScopes = sortEntries(scopes);
392
426
  return Object.keys(sortedScopes).length === 0 ? { imports: sortedImports } : {
393
427
  imports: sortedImports,
394
428
  scopes: sortedScopes
395
429
  };
396
430
  }
397
431
  /**
398
- * Serialize an import map as stable, tab-indented JSON with a trailing newline.
432
+ * Serialize an import map as stable JSON with a trailing newline.
399
433
  *
400
434
  * @example
401
435
  * ```ts
402
- * // Format an import map for stdout.
436
+ * // Format an import map for stdout, indented with two spaces.
403
437
  * import { formatImportMap } from 'jsr:@kjanat/importmapify';
404
438
  *
405
- * const text = formatImportMap({
406
- * imports: { '#config': './src/config.ts' },
407
- * });
439
+ * const text = formatImportMap({ imports: { '#config': './src/config.ts' } }, 2);
408
440
  *
409
441
  * console.log(text);
410
442
  * ```
411
443
  *
412
444
  * @param map Import map to serialize.
445
+ * @param indent Indentation with `JSON.stringify` space semantics; defaults to a tab.
413
446
  * @returns Formatted JSON ready to print or write.
447
+ * @category Generate
414
448
  */
415
- function formatImportMap(map) {
416
- return `${JSON.stringify(map, null, " ")}\n`;
449
+ function formatImportMap(map, indent = " ") {
450
+ return `${JSON.stringify(map, null, indent)}\n`;
417
451
  }
418
452
  /**
419
453
  * Create an import map and write it to disk, creating parent directories as needed.
@@ -432,6 +466,7 @@ function formatImportMap(map) {
432
466
  *
433
467
  * @param options Creation options plus the optional output path.
434
468
  * @returns The absolute path of the written file.
469
+ * @category Generate
435
470
  */
436
471
  function writeImportMap(options) {
437
472
  const out = resolveOut(options.root, options.out ?? "import_map.json");
@@ -441,7 +476,7 @@ function writeImportMap(options) {
441
476
  relativeTo
442
477
  });
443
478
  fs.mkdirSync(path.dirname(out), { recursive: true });
444
- fs.writeFileSync(out, formatImportMap(map));
479
+ fs.writeFileSync(out, formatImportMap(map, options.indent));
445
480
  return out;
446
481
  }
447
482
  function directoryTarget(target) {
@@ -464,6 +499,7 @@ function directoryTarget(target) {
464
499
  * @param name Bare package specifier.
465
500
  * @param target Exact target for {@link name}.
466
501
  * @returns The bare entry and its trailing-slash subpath entry.
502
+ * @category Generate
467
503
  */
468
504
  function packageEntries(name, target) {
469
505
  return {
@@ -487,9 +523,23 @@ function packageEntries(name, target) {
487
523
  * writeImportMap(config);
488
524
  * ```
489
525
  *
526
+ * @example
527
+ * ```ts
528
+ * // Config file with a build hook: generate:before runs before the CLI scans.
529
+ * import { execSync } from 'node:child_process';
530
+ * import { defineConfig } from 'jsr:@kjanat/importmapify';
531
+ *
532
+ * export default defineConfig({
533
+ * hooks: {
534
+ * 'generate:before': () => execSync('deno task build', { stdio: 'inherit' }),
535
+ * },
536
+ * });
537
+ * ```
538
+ *
490
539
  * @param config Import map configuration; every field is optional.
491
- * @returns The same `config` value with its exact type preserved, so a config that includes `root` stays
540
+ * @returns The same {@linkcode config} value with its exact type preserved, so a config that includes {@linkcode CreateImportMapOptions.root} stays
492
541
  * assignable to {@link writeImportMap} while one that omits it is still a valid config file.
542
+ * @category Configuration
493
543
  */
494
544
  function defineConfig(config) {
495
545
  return config;
@@ -497,7 +547,8 @@ function defineConfig(config) {
497
547
  //#endregion
498
548
  //#region src/cli.ts
499
549
  const EXAMPLE_TOKEN = /(?:'[^']*'|"[^"]*"|\S)+/g;
500
- const JSON_MODE = argv.includes("--json");
550
+ const SPACE_COUNT = /^\d+$/;
551
+ const JSON_MODE = (argv.includes("--") ? argv.slice(0, argv.indexOf("--")) : argv).includes("--json");
501
552
  function highlightExample(example) {
502
553
  if (JSON_MODE) return example;
503
554
  const tokens = example.match(EXAMPLE_TOKEN);
@@ -569,11 +620,34 @@ function preferExplicit(explicit, flagValue, configValue) {
569
620
  function preferArray(flagValue, configValue) {
570
621
  return flagValue !== void 0 && flagValue.length > 0 ? flagValue : configValue;
571
622
  }
623
+ function parseIndent(raw) {
624
+ if (raw === "tab") return " ";
625
+ if (SPACE_COUNT.test(raw)) return Number(raw);
626
+ throw new CLIError(`--indent expects a number of spaces or "tab", got "${raw}"`, {
627
+ code: "invalid-indent-flag",
628
+ exitCode: 2
629
+ });
630
+ }
631
+ function compileFilters(raws) {
632
+ return raws.map((raw) => {
633
+ try {
634
+ return new RegExp(raw);
635
+ } catch (cause) {
636
+ throw new CLIError(`--filter expects a valid regular expression, got "${raw}"`, {
637
+ code: "invalid-filter-flag",
638
+ exitCode: 2,
639
+ cause
640
+ });
641
+ }
642
+ });
643
+ }
572
644
  /** Resolve final import map options by layering explicit flags over a discovered config over defaults. */
573
645
  async function resolveGenerateOptions(flags) {
574
646
  const base = await loadConfigOptions(flags.root, flags.config, flags["no-config"] ?? false);
575
647
  const conditions = preferArray(flags.condition, base.conditions);
576
648
  const extensions = preferArray(flags.ext, base.extensions);
649
+ const filter = preferArray(flags.filter === void 0 ? void 0 : compileFilters(flags.filter), base.filter);
650
+ const indent = flags.indent === void 0 ? base.indent : parseIndent(flags.indent);
577
651
  const options = {
578
652
  root: preferExplicit(flags.root !== cwd(), flags.root, base.root),
579
653
  manifest: preferExplicit(flags.manifest !== "package.json", flags.manifest, base.manifest),
@@ -588,17 +662,19 @@ async function resolveGenerateOptions(flags) {
588
662
  },
589
663
  scopes: mergeScopes(base.scopes, buildScopes(flags.scope ?? []))
590
664
  };
591
- if (base.relativeTo !== void 0) options.relativeTo = base.relativeTo;
592
- if (conditions !== void 0) options.conditions = conditions;
593
- if (extensions !== void 0) options.extensions = extensions;
594
- if (base.hooks !== void 0) options.hooks = base.hooks;
665
+ assign(options, "relativeTo", base.relativeTo);
666
+ assign(options, "indent", indent);
667
+ assign(options, "conditions", conditions);
668
+ assign(options, "extensions", extensions);
669
+ assign(options, "filter", filter);
670
+ assign(options, "hooks", base.hooks);
595
671
  return options;
596
672
  }
597
673
  /** DreamCLI command that writes, checks, or prints an expanded Deno import map. */
598
674
  const generateCommand = command("generate").description("Expand package.json subpath-pattern imports into a Deno import map.").flag("root", flag.path({
599
675
  mustExist: true,
600
676
  type: "directory"
601
- }).default(cwd()).describe("Project root containing the manifest.").alias("r")).flag("manifest", flag.string().default("package.json").describe("Manifest path, relative to root.").alias("m")).flag("out", flag.string().default(DEFAULT_OUT).describe("Output path resolved against root; relative, absolute, or file:// URL.").alias("o")).flag("import", flag.array(flag.string()).describe("Additional import entry as key=value. Repeatable.").alias("i")).flag("package", flag.array(flag.string()).describe("Package as name=target, expanded to a conformant bare and trailing-slash pair. Repeatable.").alias("p")).flag("ext", flag.array(flag.string()).describe("Restrict pattern matches to these file extensions. Repeatable.").alias("e")).flag("scope", flag.array(flag.string()).describe("Scoped import as prefix::key=value. Repeatable.").alias("s")).flag("condition", flag.array(flag.string()).describe("Condition to try when a target is a conditional object. Repeatable.").alias("c")).flag("config", flag.string().describe("Config file path; skips discovery.").alias("C")).flag("no-config", flag.boolean().describe("Skip config file discovery.")).flag("check", flag.boolean().describe("Exit 1 if the output file is stale instead of writing it.")).flag("stdout", flag.boolean().describe("Print the import map to stdout instead of writing it.")).flag("quiet", flag.boolean().describe("Suppress the confirmation message on stderr.").alias("q")).example(highlightExample("importmapify --stdout"), "Print the generated import map").example(highlightExample(`importmapify --package 'dreamcli=jsr:@kjanat/dreamcli@^3' --scope './tests/::dreamcli/testkit=jsr:@kjanat/dreamcli@^3/testkit'`), "Add global and test-scoped dependencies").example(highlightExample("importmapify --check"), "Fail when the generated file is stale").action(async ({ flags, out }) => {
677
+ }).default(cwd()).describe("Project root containing the manifest.").alias("r")).flag("manifest", flag.string().default("package.json").describe("Manifest path, relative to root.").alias("m")).flag("out", flag.string().default(DEFAULT_OUT).describe("Output path resolved against root; relative, absolute, or file:// URL.").alias("o")).flag("indent", flag.string().describe("Indentation as a number of spaces or the word \"tab\".")).flag("import", flag.array(flag.string()).describe("Additional import entry as key=value. Repeatable.").alias("i")).flag("package", flag.array(flag.string()).describe("Package as name=target, expanded to a conformant bare and trailing-slash pair. Repeatable.").alias("p")).flag("ext", flag.array(flag.string()).describe("Restrict pattern matches to these file extensions. Repeatable.").alias("e")).flag("filter", flag.array(flag.string()).describe("Regular expression a pattern target path must match. Repeatable.").alias("f")).flag("scope", flag.array(flag.string()).describe("Scoped import as prefix::key=value. Repeatable.").alias("s")).flag("condition", flag.array(flag.string()).describe("Condition to try when a target is a conditional object. Repeatable.").alias("c")).flag("config", flag.string().describe("Config file path; skips discovery.").alias("C")).flag("no-config", flag.boolean().describe("Skip config file discovery.")).flag("check", flag.boolean().describe("Exit 1 if the output file is stale instead of writing it.")).flag("stdout", flag.boolean().describe("Print the import map to stdout instead of writing it.")).flag("quiet", flag.boolean().describe("Suppress the confirmation message on stderr.").alias("q")).example(highlightExample("importmapify --stdout"), "Print the generated import map").example(highlightExample(`importmapify --package 'dreamcli=jsr:@kjanat/dreamcli@^3' --scope './tests/::dreamcli/testkit=jsr:@kjanat/dreamcli@^3/testkit'`), "Add global and test-scoped dependencies").example(highlightExample("importmapify --check"), "Fail when the generated file is stale").action(async ({ flags, out }) => {
602
678
  const { log, warn, error, setExitCode } = out;
603
679
  const { check, stdout, quiet } = flags;
604
680
  if (check && stdout) throw new CLIError("--check and --stdout are mutually exclusive", {
@@ -617,7 +693,7 @@ const generateCommand = command("generate").description("Expand package.json sub
617
693
  };
618
694
  await options.hooks?.["generate:before"]?.(hookContext);
619
695
  const map = createImportMap(createOptions);
620
- const text = formatImportMap(map);
696
+ const text = formatImportMap(map, options.indent);
621
697
  if (stdout) log(text);
622
698
  else if (check) {
623
699
  if ((existsSync(outPath) ? readFileSync(outPath, "utf8") : void 0) !== text) {
@@ -637,9 +713,22 @@ const generateCommand = command("generate").description("Expand package.json sub
637
713
  //#endregion
638
714
  //#region src/mod.ts
639
715
  /**
716
+ *
717
+ * [![NPM](https://img.shields.io/npm/v/importmapify?logo=npm&labelColor=CB3837&color=black)][npm]
718
+ * [![JSR](https://img.shields.io/jsr/v/@kjanat/importmapify?logoColor=083344&logo=jsr&logoSize=auto&label=&labelColor=f7df1e&color=black)][jsr]
719
+ * [![CI](https://github.com/kjanat/importmapify/actions/workflows/publish.yml/badge.svg)][ci]
720
+ * [![Socket](https://badge.socket.dev/npm/package/importmapify)][socket]
721
+ *
722
+ * [ci]: https://github.com/kjanat/importmapify/actions/workflows/publish.yml
723
+ * [npm]: https://npm.im/importmapify
724
+ * [jsr]: https://jsr.io/@kjanat/importmapify
725
+ * [socket]: https://socket.dev/npm/package/importmapify
726
+ *
727
+ *
640
728
  * Expand package import patterns into explicit Deno import map entries.
641
729
  *
642
- * Use the library API to create, format, or write deterministic import maps.
730
+ * Use the library API to create, format, or write deterministic import maps that conform to the {@link https://html.spec.whatwg.org/multipage/webappapis.html#import-maps | Import Maps Standard}.
731
+ *
643
732
  * This module also runs the `importmapify` CLI when executed directly.
644
733
  *
645
734
  * @example
@@ -651,6 +740,12 @@ const generateCommand = command("generate").description("Expand package.json sub
651
740
  * console.log(importMap.imports);
652
741
  * ```
653
742
  *
743
+ * @example
744
+ * ```sh
745
+ * // Access these docs programmatically
746
+ * deno doc jsr:@kjanat/importmapify
747
+ * ```
748
+ *
654
749
  * @module importmapify
655
750
  */
656
751
  const cli$1 = cli("importmapify").manifest({
package/import_map.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "#src/expand": "./src/expand.ts",
8
8
  "#src/map": "./src/map.ts",
9
9
  "#src/mod": "./src/mod.ts",
10
+ "#src/types": "./src/types.ts",
10
11
  "@types/bun": "npm:@types/bun@1.3.14",
11
12
  "ansispeck": "jsr:@kjanat/ansispeck@0.2.0",
12
13
  "ansispeck/": "jsr:/@kjanat/ansispeck@0.2.0/",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "importmapify",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Expand package.json subpath-pattern imports into explicit Deno import map entries.",
5
5
  "keywords": [
6
6
  "deno",
@@ -11,7 +11,7 @@
11
11
  "dreamcli",
12
12
  "ansispeck"
13
13
  ],
14
- "homepage": "https://github.com/kjanat/importmapify#readme",
14
+ "homepage": "https://importmapify.kjanat.dev",
15
15
  "bugs": {
16
16
  "url": "https://github.com/kjanat/importmapify/issues"
17
17
  },