importmapify 0.1.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/LICENSE +22 -0
- package/README.md +158 -0
- package/dist/import-map.mjs +1 -0
- package/dist/importmapify.mjs +2 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.mjs +1 -0
- package/package.json +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Kaj Kowalski
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to
|
|
8
|
+
deal in the Software without restriction, including without limitation the
|
|
9
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
10
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
22
|
+
IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# importmapify
|
|
2
|
+
|
|
3
|
+
[][npm]
|
|
4
|
+
[][ci]
|
|
5
|
+
[][socket]
|
|
6
|
+
|
|
7
|
+
Expand `package.json` subpath-pattern imports into the explicit entries a
|
|
8
|
+
[Deno import map] needs.
|
|
9
|
+
|
|
10
|
+
Node resolves subpath patterns like `#lib/*` -> `./src/lib/*.ts` internally.
|
|
11
|
+
Deno's import maps only support exact keys and trailing-slash prefixes, so
|
|
12
|
+
tools that expect an import map (`deno doc`, `deno check`, the Deno LSP) need
|
|
13
|
+
one entry per matched source file instead. This package performs that
|
|
14
|
+
expansion and writes a deterministically sorted import map.
|
|
15
|
+
|
|
16
|
+
[Deno import map]: https://docs.deno.com/runtime/fundamentals/modules/#differentiating-between-imports-or-importmap-in-deno.json-and---import-map-option
|
|
17
|
+
[ci]: https://github.com/kjanat/importmapify/actions/workflows/publish.yml
|
|
18
|
+
[npm]: https://npm.im/importmapify
|
|
19
|
+
[socket]: https://socket.dev/npm/package/importmapify
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install importmapify
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## CLI
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npx importmapify --root . --out deno.import_map.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Flag | Alias | Meaning | Default |
|
|
34
|
+
| -------------------- | ----- | ----------------------------------------------------------------- | ---------------------- |
|
|
35
|
+
| `--root` | `-r` | Project root containing the manifest | current directory |
|
|
36
|
+
| `--manifest` | `-m` | Manifest path, relative to root | `package.json` |
|
|
37
|
+
| `--out` | `-o` | Output path, relative to root | `deno.import_map.json` |
|
|
38
|
+
| `--import key=value` | `-i` | Extra import entry; repeatable | none |
|
|
39
|
+
| `--condition name` | `-c` | Condition tried when a target is a conditional object; repeatable | `import`, `default` |
|
|
40
|
+
| `--check` | | Exit 1 if the output file is stale, without writing it | off |
|
|
41
|
+
| `--stdout` | | Print the map instead of writing it | off |
|
|
42
|
+
|
|
43
|
+
Run `npx importmapify --help` for the full reference, or `npx importmapify
|
|
44
|
+
completions` for shell completions.
|
|
45
|
+
|
|
46
|
+
## Library
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { writeImportMap } from 'importmapify';
|
|
50
|
+
|
|
51
|
+
const out = writeImportMap({
|
|
52
|
+
root: import.meta.dirname,
|
|
53
|
+
out: 'deno.import_map.json',
|
|
54
|
+
additionalImports: {
|
|
55
|
+
'bun:test': './node_modules/bun-types/test.d.ts',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Export | Signature | Purpose |
|
|
61
|
+
| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
|
|
62
|
+
| `createImportMap` | `(options: CreateImportMapOptions) => ImportMapDocument` | Build the import map in memory. |
|
|
63
|
+
| `formatImportMap` | `(map: ImportMapDocument) => string` | Serialize to the canonical sorted, tab-indented JSON text. |
|
|
64
|
+
| `writeImportMap` | `(options: WriteImportMapOptions) => string` | Build, serialize, and write to disk; returns the written path. |
|
|
65
|
+
|
|
66
|
+
`CreateImportMapOptions`:
|
|
67
|
+
|
|
68
|
+
| Option | Meaning | Default |
|
|
69
|
+
| ------------------- | ------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
70
|
+
| `root` | Base directory for the manifest, source targets, and rebasing. | required |
|
|
71
|
+
| `manifest` | Manifest path, relative to `root`. | `package.json` |
|
|
72
|
+
| `conditions` | Condition names tried, in order, against conditional targets (`{"import": ..., "default": ...}`). | `['import', 'default']` |
|
|
73
|
+
| `additionalImports` | Extra entries merged in after manifest expansion; these win on key collision. | none |
|
|
74
|
+
| `relativeTo` | Directory the written targets are rebased against. | `root` |
|
|
75
|
+
|
|
76
|
+
`WriteImportMapOptions` extends the above with `out`, the output path relative
|
|
77
|
+
to `root`. `writeImportMap` rebases automatically against `out`'s directory,
|
|
78
|
+
so a nested `out` (for example `.cache/maps/deno.import_map.json`) still
|
|
79
|
+
produces targets that resolve correctly from the map's own location.
|
|
80
|
+
|
|
81
|
+
## What it generates
|
|
82
|
+
|
|
83
|
+
Given this manifest:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"imports": {
|
|
88
|
+
"#config": "./src/config.ts",
|
|
89
|
+
"#lib/*": "./src/lib/*.ts"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
and these files:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
src/
|
|
98
|
+
├── config.ts
|
|
99
|
+
└── lib/
|
|
100
|
+
├── bytes.ts
|
|
101
|
+
└── codecs/
|
|
102
|
+
└── hex.ts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
the generated map is:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"imports": {
|
|
110
|
+
"#config": "./src/config.ts",
|
|
111
|
+
"#lib/bytes": "./src/lib/bytes.ts",
|
|
112
|
+
"#lib/bytes.ts": "./src/lib/bytes.ts",
|
|
113
|
+
"#lib/codecs/hex": "./src/lib/codecs/hex.ts",
|
|
114
|
+
"#lib/codecs/hex.ts": "./src/lib/codecs/hex.ts"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A key with its own suffix, such as `#lib/*.js` targeting `./src/lib/*.ts`,
|
|
120
|
+
produces both the renamed specifier and the real filename: `#lib/bytes.js`
|
|
121
|
+
and `#lib/bytes.ts`, both pointing at `./src/lib/bytes.ts`.
|
|
122
|
+
|
|
123
|
+
## Scope and constraints
|
|
124
|
+
|
|
125
|
+
- Only the manifest's top-level `imports` field is read.
|
|
126
|
+
- Conditional targets resolve via `conditions`, tried in order, recursively.
|
|
127
|
+
A target matching no condition is skipped.
|
|
128
|
+
- Pattern keys and targets must both contain `*`, or neither should; a
|
|
129
|
+
mismatch throws.
|
|
130
|
+
- Expandable pattern targets must point to local files beneath `root`.
|
|
131
|
+
Target directories are scanned recursively; a missing directory produces
|
|
132
|
+
no entries.
|
|
133
|
+
- Relative targets (`./...`, `../...`) are rebased against `relativeTo`.
|
|
134
|
+
Bare specifiers, `npm:`/`jsr:`/`node:` specifiers, and absolute URLs pass
|
|
135
|
+
through unchanged.
|
|
136
|
+
- If exact and expanded entries produce the same key, the later manifest
|
|
137
|
+
entry wins.
|
|
138
|
+
- Output entries are sorted by UTF-16 code unit for stable diffs.
|
|
139
|
+
|
|
140
|
+
## Use the generated map
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
deno doc --import-map=deno.import_map.json src/index.ts
|
|
144
|
+
deno check --import-map=deno.import_map.json src/
|
|
145
|
+
deno run --import-map=deno.import_map.json src/main.ts
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
or reference it once in `deno.json`:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"importMap": "./deno.import_map.json"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";function n(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function r(e){let t=e.targetSuffix.split(`*`),r=t[0]??``,i=t.slice(1).map(e=>`\\k<wildcard>${n(e)}`).join(``);return RegExp(`^${n(e.targetPrefix)}(?<wildcard>.*)${n(r)}${i}$`)}function i(e,n){let r=e.indexOf(`*`),i=n.indexOf(`*`);if(r===-1&&i===-1)return;if(r===-1||i===-1)throw Error(`Import pattern mismatch: "${e}" -> "${n}"; both sides must contain "*", or neither should.`);let a=n.slice(0,i),o=a.endsWith(`/`)?a.slice(0,-1):t.posix.dirname(a);return{keyPrefix:e.slice(0,r),keySuffix:e.slice(r+1),targetDirectory:o===``?`.`:o,targetPrefix:a,targetSuffix:n.slice(i+1)}}function a(e,t){let n={},i=r(e),a=e.targetDirectory===`.`?`./`:`${e.targetDirectory}/`;for(let r of t){let t=`${a}${r}`,o=i.exec(t)?.groups?.wildcard;o!==void 0&&(n[`${e.keyPrefix}${o}${e.keySuffix}`]=t,n[`${e.keyPrefix}${r}`]=t)}return n}function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e,t){if(typeof e==`string`)return e;if(o(e))for(let n of t){if(!(n in e))continue;let r=s(e[n],t);if(r!==void 0)return r}}function c(e){return e.startsWith(`./`)||e.startsWith(`../`)}function l(e,n,r){if(!c(r))return r;let i=t.resolve(e,r),a=t.relative(n,i).split(t.sep).join(`/`);return a.startsWith(`.`)?a:`./${a}`}const u=[`import`,`default`];function d(n,r=``){if(!e.existsSync(n))return[];let i=[];for(let a of e.readdirSync(n,{withFileTypes:!0})){let e=r===``?a.name:`${r}/${a.name}`;a.isDirectory()?i.push(...d(t.join(n,a.name),e)):i.push(e)}return i}function f(t){let n;try{n=e.readFileSync(t,`utf8`)}catch(e){throw Error(`Cannot read manifest at ${t}`,{cause:e})}let r;try{r=JSON.parse(n)}catch(e){throw Error(`Cannot parse manifest at ${t} as JSON`,{cause:e})}if(!o(r))throw Error(`Manifest at ${t} must be a JSON object`);return r}function p(e){let n=f(t.join(e.root,e.manifest??`package.json`)),r=o(n.imports)?n.imports:{},c=e.conditions?.length?e.conditions:u,p=e.relativeTo??e.root,m={},h=(t,n)=>{m[t]=l(e.root,p,n)};for(let[n,o]of Object.entries(r)){let r=typeof o==`string`?o:s(o,c);if(r===void 0)continue;let l=i(n,r);if(l===void 0){h(n,r);continue}let u=t.join(e.root,l.targetDirectory);for(let[e,t]of Object.entries(a(l,d(u))))h(e,t)}for(let[t,n]of Object.entries(e.additionalImports??{}))h(t,n);return{imports:Object.fromEntries(Object.entries(m).sort(([e],[t])=>e<t?-1:+(e>t)))}}function m(e){return`${JSON.stringify(e,null,` `)}\n`}function h(n){let r=t.join(n.root,n.out),i=n.relativeTo??t.dirname(r),a=p({...n,relativeTo:i});return e.mkdirSync(t.dirname(r),{recursive:!0}),e.writeFileSync(r,m(a)),r}export{m as n,h as r,p as t};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{n as e,r as t,t as n}from"./import-map.mjs";import r from"node:fs";import i from"node:path";import{CLIError as a,cli as o,command as s,flag as c}from"dreamcli";function l(e){let t=e.indexOf(`=`);if(t===-1)throw new a(`--import expects key=value, got "${e}"`,{code:`invalid-import-flag`,exitCode:2});return[e.slice(0,t),e.slice(t+1)]}const u=s(`generate`).description(`Expand package.json subpath-pattern imports into a Deno import map.`).flag(`root`,c.path({mustExist:!0,type:`directory`}).default(process.cwd()).describe(`Project root containing the manifest.`).alias(`r`)).flag(`manifest`,c.string().default(`package.json`).describe(`Manifest path, relative to root.`).alias(`m`)).flag(`out`,c.string().default(`deno.import_map.json`).describe(`Output path, relative to root.`).alias(`o`)).flag(`import`,c.array(c.string()).describe(`Additional import entry as key=value. Repeatable.`).alias(`i`)).flag(`condition`,c.array(c.string()).describe(`Condition to try when a target is a conditional object. Repeatable.`).alias(`c`)).flag(`check`,c.boolean().describe(`Exit 1 if the output file is stale instead of writing it.`)).flag(`stdout`,c.boolean().describe(`Print the import map to stdout instead of writing it.`)).action(({flags:o,out:s})=>{if(o.check&&o.stdout)throw new a(`--check and --stdout are mutually exclusive`,{code:`conflicting-flags`,exitCode:2});let c=o.root,u=i.join(c,o.out),d=i.dirname(u),f=Object.fromEntries((o.import??[]).map(l)),p={root:c,manifest:o.manifest,conditions:o.condition,additionalImports:f,relativeTo:d};if(o.stdout){s.log(e(n(p)));return}if(o.check){let t=e(n(p));if((r.existsSync(u)?r.readFileSync(u,`utf8`):void 0)!==t){s.error(`${u} is stale`),s.setExitCode(1);return}s.log(`${u} is up to date`);return}let m=t({...p,out:o.out});s.log(`Wrote ${m}`)});o(`importmapify`).manifest({from:import.meta.url}).default(u).completions().run();export{};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/import-map.d.ts
|
|
2
|
+
interface ImportMapDocument {
|
|
3
|
+
readonly imports: Readonly<Record<string, string>>;
|
|
4
|
+
}
|
|
5
|
+
interface CreateImportMapOptions {
|
|
6
|
+
readonly root: string;
|
|
7
|
+
readonly manifest?: string;
|
|
8
|
+
readonly conditions?: readonly string[];
|
|
9
|
+
readonly additionalImports?: Readonly<Record<string, string>>;
|
|
10
|
+
readonly relativeTo?: string;
|
|
11
|
+
}
|
|
12
|
+
interface WriteImportMapOptions extends CreateImportMapOptions {
|
|
13
|
+
readonly out: string;
|
|
14
|
+
}
|
|
15
|
+
declare function createImportMap(options: CreateImportMapOptions): ImportMapDocument;
|
|
16
|
+
declare function formatImportMap(map: ImportMapDocument): string;
|
|
17
|
+
declare function writeImportMap(options: WriteImportMapOptions): string;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { type CreateImportMapOptions, type ImportMapDocument, type WriteImportMapOptions, createImportMap, formatImportMap, writeImportMap };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n}from"./import-map.mjs";export{n as createImportMap,e as formatImportMap,t as writeImportMap};
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "importmapify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Expand package.json subpath-pattern imports into explicit Deno import map entries.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deno",
|
|
7
|
+
"import-map",
|
|
8
|
+
"importmap",
|
|
9
|
+
"subpath-imports",
|
|
10
|
+
"cli",
|
|
11
|
+
"dreamcli",
|
|
12
|
+
"ansispeck"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/kjanat/importmapify#readme",
|
|
15
|
+
"bugs": "https://github.com/kjanat/importmapify/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/kjanat/importmapify.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Kaj Kowalski",
|
|
23
|
+
"email": "info@kajkowalski.nl",
|
|
24
|
+
"url": "https://github.com/kjanat"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"type": "module",
|
|
28
|
+
"imports": {
|
|
29
|
+
"#pkg": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"default": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"types": "./dist/index.d.mts",
|
|
39
|
+
"bin": {
|
|
40
|
+
"importmapify": "dist/importmapify.mjs"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"bd": "tsdown",
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"build:watch": "tsdown --watch",
|
|
49
|
+
"check": "biome check",
|
|
50
|
+
"check:fix": "biome check --fix",
|
|
51
|
+
"fmt": "dprint fmt",
|
|
52
|
+
"lint": "biome lint",
|
|
53
|
+
"prepack": "bun --bun bd -l error",
|
|
54
|
+
"prepublishOnly": "[ -n \"${GITHUB_ACTIONS:-}\" ] || { printf '%s\\n' 'manual npm publish blocked; use release workflow for provenance' >&2; exit 1; }",
|
|
55
|
+
"runbin": "node \"$(jq -r '.bin.[]' package.json)\" --help",
|
|
56
|
+
"test": "bun test --coverage",
|
|
57
|
+
"test:coverage": "bun test --coverage",
|
|
58
|
+
"test:smoke": "node scripts/smoke.mjs",
|
|
59
|
+
"test:watch": "bun test --watch",
|
|
60
|
+
"typecheck": "bunx @typescript/native --noEmit"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"dreamcli": "npm:@kjanat/dreamcli@^3.0.0-rc.9"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
67
|
+
"@biomejs/biome": "2.5.2 || ^2.5.4",
|
|
68
|
+
"@types/bun": "^1.3.14",
|
|
69
|
+
"@types/node": "^26.1.1",
|
|
70
|
+
"@typescript/native": "npm:typescript@^7",
|
|
71
|
+
"dprint": "^0.55.2",
|
|
72
|
+
"publint": "^0.3.21",
|
|
73
|
+
"sort-package-json": "^4.0.0",
|
|
74
|
+
"tsdown": "^0.22.7",
|
|
75
|
+
"typescript": ">=6,<7",
|
|
76
|
+
"unplugin-unused": "^0.5.7"
|
|
77
|
+
},
|
|
78
|
+
"packageManager": "bun@1.3.14",
|
|
79
|
+
"engines": {
|
|
80
|
+
"bun": ">=1.3",
|
|
81
|
+
"node": ">=24"
|
|
82
|
+
},
|
|
83
|
+
"volta": {
|
|
84
|
+
"node": "26.5.0",
|
|
85
|
+
"npm": "11.18.0"
|
|
86
|
+
},
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public",
|
|
89
|
+
"provenance": true,
|
|
90
|
+
"registry": "https://registry.npmjs.org/",
|
|
91
|
+
"tag": "latest"
|
|
92
|
+
}
|
|
93
|
+
}
|