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 +27 -10
- package/deno.json +26 -14
- package/dist/mod.d.mts +317 -32
- package/dist/mod.mjs +142 -47
- package/import_map.json +1 -0
- package/package.json +2 -2
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;
|
|
88
|
-
passed. Configs are imported natively; a TypeScript config needs a type-stripping
|
|
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` |
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
+
"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": "
|
|
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
|
-
|
|
37
|
-
|
|
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://
|
|
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/
|
|
2
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
/**
|
|
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
|
|
229
|
+
readonly indent?: string | number;
|
|
35
230
|
}
|
|
36
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
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 (
|
|
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
|
|
306
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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]
|
|
718
|
+
* [][jsr]
|
|
719
|
+
* [][ci]
|
|
720
|
+
* [][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.
|
|
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://
|
|
14
|
+
"homepage": "https://importmapify.kjanat.dev",
|
|
15
15
|
"bugs": {
|
|
16
16
|
"url": "https://github.com/kjanat/importmapify/issues"
|
|
17
17
|
},
|