pkgbld 2.0.0 → 2.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/README.md +46 -2
- package/package.json +1 -1
- package/src/build-configuration.js +11 -0
- package/src/build-entries.js +73 -16
- package/src/builtin-plugins/externals.js +3 -2
- package/src/builtin-plugins/package-imports.js +79 -0
- package/src/builtin-plugins/resolve.js +10 -2
- package/src/get-plugins.js +2 -1
- package/src/get-rollup-configs.js +44 -7
- package/src/options/index.js +11 -0
- package/src/package-imports.js +240 -0
- package/src/priorities.js +1 -0
- package/src/process-pkg.js +11 -4
- package/src/types.js +6 -1
- package/types/index.d.ts +13 -1
package/README.md
CHANGED
|
@@ -43,6 +43,30 @@ For TypeScript or TSX sources, also install
|
|
|
43
43
|
|
|
44
44
|
`pkgbld` expects the name field to be filled in the package.json file. `exports` field defines what entries/outputs should be built for this package.
|
|
45
45
|
|
|
46
|
+
### Private package imports
|
|
47
|
+
|
|
48
|
+
When `package.json` has an `imports` map, `pkgbld` builds its local JavaScript targets alongside the public `exports` entries. For example:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"name": "example",
|
|
53
|
+
"type": "module",
|
|
54
|
+
"imports": {
|
|
55
|
+
"#utils": "./dist/utils.mjs",
|
|
56
|
+
"#env": {
|
|
57
|
+
"node": "./dist/env.node.mjs",
|
|
58
|
+
"default": "./dist/env.browser.mjs"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With the default directories, provide `src/utils.js`, `src/env.node.js`, and `src/env.browser.js` (or supported TypeScript sources with a transform plugin). Both `#env` branches are built at the declared paths. An import of `#utils` or `#env` from this package stays as a `#` specifier in the output, so the runtime selects the matching branch. The authored `imports` map is preserved.
|
|
65
|
+
|
|
66
|
+
Exact and wildcard keys, nested conditions, and fallback arrays are supported. Repeated local targets are built once. `null` and package-specifier targets are left to runtime resolution. Local `.mjs` targets require `es`; `.cjs` targets require `cjs`; `.js` targets follow the package `type` that pkgbld writes (including an inferred `"module"` for an ESM-only build). Percent-encoded target paths refer to their decoded filenames, and URL query or fragment suffixes remain in the authored map without becoming part of the output filename. A target whose format is excluded by `--formats`, whose source is missing, or whose output path is invalid or conflicts with another entry fails the build with its manifest location. Local assets and `.d.ts` targets are not generated by this JavaScript entry process; supply them through their own producer.
|
|
67
|
+
|
|
68
|
+
Keys beginning `#/` work on Node.js 24.14+ and 25.4+; older supported Node.js versions reject those specifiers at runtime. Use names such as `#utils` when supporting Node.js 20.
|
|
69
|
+
|
|
46
70
|
## CLI options
|
|
47
71
|
|
|
48
72
|
### umd
|
|
@@ -83,7 +107,9 @@ Supported targets for this option: `es`, `cjs` and `umd`.
|
|
|
83
107
|
pkgbld --formats=es
|
|
84
108
|
```
|
|
85
109
|
|
|
86
|
-
Defines what formats to build
|
|
110
|
+
Defines what formats to build: `es`, `cjs`, and `umd`. Use `--umd` to select UMD entry points.
|
|
111
|
+
|
|
112
|
+
Private import targets must have their required format included here. They are emitted at their declared paths independently of the output filename patterns below.
|
|
87
113
|
|
|
88
114
|
### preprocess
|
|
89
115
|
|
|
@@ -131,6 +157,24 @@ pkgbld --include-externals=lodash
|
|
|
131
157
|
|
|
132
158
|
Bundles all or specified externals into a package.
|
|
133
159
|
|
|
160
|
+
This does not inline this package's own `#` imports when package-import handling is enabled. A bundled dependency's `#` imports are resolved against that dependency's map.
|
|
161
|
+
|
|
162
|
+
### no-imports
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
pkgbld --no-imports
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Disables local target discovery and `#` externalization for this package. The authored `imports` map is preserved. This restores the earlier source-import behavior and does not affect `--conditions`.
|
|
169
|
+
|
|
170
|
+
### conditions
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
pkgbld --conditions=node,development
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Adds build-time conditions when resolving bundled dependencies, including their `#` imports. Comma-separated values supplement the resolver's built-in conditions. With no flag, the resolver keeps its existing implicit `production` condition, or `development` when selected by `NODE_ENV`. The order of keys in a dependency's map determines which matching branch wins. These conditions do not choose which branches of this package's own `imports` map are built.
|
|
177
|
+
|
|
134
178
|
### eject
|
|
135
179
|
|
|
136
180
|
```
|
|
@@ -200,7 +244,7 @@ pkgbld --no-exports
|
|
|
200
244
|
Do not add exports field in package.json.
|
|
201
245
|
|
|
202
246
|
This also disables entry-point discovery from an existing `exports` field. Only the top-level `src/index` entry point is built
|
|
203
|
-
unless a plugin provides additional inputs.
|
|
247
|
+
unless a plugin provides additional inputs or enabled package imports declare local JavaScript targets.
|
|
204
248
|
|
|
205
249
|
## Build plugin interface
|
|
206
250
|
|
package/package.json
CHANGED
|
@@ -101,6 +101,10 @@ function createDefaultDraft() {
|
|
|
101
101
|
includeExternals: defaults.includeExternals,
|
|
102
102
|
removeLegalComments: false,
|
|
103
103
|
},
|
|
104
|
+
resolution: {
|
|
105
|
+
imports: defaults.imports,
|
|
106
|
+
conditions: [...defaults.conditions],
|
|
107
|
+
},
|
|
104
108
|
packageJson: {
|
|
105
109
|
update: true,
|
|
106
110
|
format: defaults.formatPackageJson,
|
|
@@ -124,6 +128,9 @@ function createDefaultDraft() {
|
|
|
124
128
|
* @param {PackageJson} packageJson
|
|
125
129
|
*/
|
|
126
130
|
function applyPackageMetadata(draft, packageJson) {
|
|
131
|
+
if (packageJson.imports !== undefined) {
|
|
132
|
+
draft.resolution.imports = true;
|
|
133
|
+
}
|
|
127
134
|
if (typeof packageJson.umd === 'string') {
|
|
128
135
|
draft.outputs.umdEntries.push('index');
|
|
129
136
|
draft.outputs.formats.push('umd');
|
|
@@ -186,6 +193,8 @@ function applyCli(draft, flags, provided) {
|
|
|
186
193
|
if (provided.includeExternals) {
|
|
187
194
|
draft.transforms.includeExternals = /** @type {boolean | string[]} */ (flags.includeExternals);
|
|
188
195
|
}
|
|
196
|
+
if (provided.imports) draft.resolution.imports = /** @type {boolean} */ (flags.imports);
|
|
197
|
+
if (provided.conditions) draft.resolution.conditions = [.../** @type {string[]} */ (flags.conditions)];
|
|
189
198
|
if (provided.eject) draft.execution.eject = /** @type {boolean} */ (flags.eject);
|
|
190
199
|
if (provided.tsConfig) draft.typescript.updateConfig = /** @type {boolean} */ (flags.tsConfig);
|
|
191
200
|
if (provided.updatePackageJson) draft.packageJson.update = /** @type {boolean} */ (flags.updatePackageJson);
|
|
@@ -209,6 +218,7 @@ function normalize(draft) {
|
|
|
209
218
|
if (Array.isArray(draft.outputs?.sourcemaps)) draft.outputs.sourcemaps = unique(draft.outputs.sourcemaps);
|
|
210
219
|
if (Array.isArray(draft.transforms?.compress)) draft.transforms.compress = unique(draft.transforms.compress);
|
|
211
220
|
if (Array.isArray(draft.transforms?.preprocess)) draft.transforms.preprocess = unique(draft.transforms.preprocess);
|
|
221
|
+
if (Array.isArray(draft.resolution?.conditions)) draft.resolution.conditions = unique(draft.resolution.conditions);
|
|
212
222
|
if (Array.isArray(draft.transforms.includeExternals)) {
|
|
213
223
|
draft.transforms.includeExternals = unique(draft.transforms.includeExternals);
|
|
214
224
|
}
|
|
@@ -240,6 +250,7 @@ function validate(draft, packageJson) {
|
|
|
240
250
|
validateFormats(draft.transforms.compress, 'transforms.compress', issues);
|
|
241
251
|
validateStrings(draft.outputs.umdEntries, 'outputs.umdEntries', issues);
|
|
242
252
|
validateStrings(draft.transforms.preprocess, 'transforms.preprocess', issues);
|
|
253
|
+
validateStrings(draft.resolution.conditions, 'resolution.conditions', issues);
|
|
243
254
|
if (draft.outputs.umdEntries.length > 0 && typeof packageJson.name !== 'string') {
|
|
244
255
|
issues.push({ code: 'PACKAGE_NAME_REQUIRED', path: 'package.name', message: 'a package name is required for UMD entries' });
|
|
245
256
|
}
|
package/src/build-entries.js
CHANGED
|
@@ -10,9 +10,10 @@ import { isExists } from './helpers.js';
|
|
|
10
10
|
* @typedef {import('./types.js').BuildEntryContributions} BuildEntryContributions
|
|
11
11
|
* @typedef {import('./types.js').BuildEntryIssue} BuildEntryIssue
|
|
12
12
|
* @typedef {import('./types.js').BuildFormat} BuildFormat
|
|
13
|
+
* @typedef {import('./types.js').ImportTarget} ImportTarget
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
const sourceFileExtensions = /** @type {const} */ (['ts', 'tsx', 'js', 'jsx', 'cjs', 'mjs']);
|
|
16
|
+
export const sourceFileExtensions = /** @type {const} */ (['ts', 'tsx', 'js', 'jsx', 'cjs', 'mjs']);
|
|
16
17
|
|
|
17
18
|
export class BuildEntryError extends Error {
|
|
18
19
|
/** @param {BuildEntryIssue[]} issues */
|
|
@@ -28,18 +29,25 @@ export class BuildEntryError extends Error {
|
|
|
28
29
|
* one immutable catalog.
|
|
29
30
|
*
|
|
30
31
|
* @param {readonly string[]} packageEntryNames
|
|
32
|
+
* @param {readonly ImportTarget[]} importTargets
|
|
31
33
|
* @param {BuildConfiguration} configuration
|
|
32
34
|
* @param {(contributions: BuildEntryContributions) => void} contribute
|
|
33
35
|
* @returns {Promise<BuildEntries>}
|
|
34
36
|
*/
|
|
35
|
-
export async function resolveBuildEntries(packageEntryNames, configuration, contribute) {
|
|
36
|
-
/** @type {(BuildEntryContribution & { issuePath: string })[]} */
|
|
37
|
-
const specifications = packageEntryNames.map((name, index) => ({
|
|
37
|
+
export async function resolveBuildEntries(packageEntryNames, importTargets, configuration, contribute) {
|
|
38
|
+
/** @type {(BuildEntryContribution & { issuePath: string; origin: 'export' | 'plugin'; manifestPath: string })[]} */
|
|
39
|
+
const specifications = packageEntryNames.map((name, index) => ({
|
|
40
|
+
name,
|
|
41
|
+
issuePath: `package.entries[${index}]`,
|
|
42
|
+
origin: 'export',
|
|
43
|
+
manifestPath: `package.exports[${JSON.stringify(name === 'index' ? '.' : `./${name}`)}]`,
|
|
44
|
+
}));
|
|
38
45
|
let contributionIndex = 0;
|
|
39
46
|
const contributions = {
|
|
40
47
|
/** @param {BuildEntryContribution} contribution */
|
|
41
48
|
add(contribution) {
|
|
42
|
-
|
|
49
|
+
const issuePath = `plugins.entries[${contributionIndex}]`;
|
|
50
|
+
specifications.push({ ...contribution, issuePath, origin: 'plugin', manifestPath: issuePath });
|
|
43
51
|
contributionIndex += 1;
|
|
44
52
|
},
|
|
45
53
|
};
|
|
@@ -87,6 +95,8 @@ export async function resolveBuildEntries(packageEntryNames, configuration, cont
|
|
|
87
95
|
}
|
|
88
96
|
const entry = Object.freeze({
|
|
89
97
|
name,
|
|
98
|
+
origin: specification.origin,
|
|
99
|
+
manifestPath: specification.manifestPath,
|
|
90
100
|
sourcePath: source.sourcePath,
|
|
91
101
|
extension: source.extension,
|
|
92
102
|
outputPaths: Object.freeze(outputPaths),
|
|
@@ -95,13 +105,47 @@ export async function resolveBuildEntries(packageEntryNames, configuration, cont
|
|
|
95
105
|
byName.set(name, entry);
|
|
96
106
|
}
|
|
97
107
|
|
|
108
|
+
for (const target of importTargets) {
|
|
109
|
+
const name = `@imports/${target.outputPath.slice(2)}`;
|
|
110
|
+
if (byName.has(name)) {
|
|
111
|
+
issues.push({
|
|
112
|
+
code: 'DUPLICATE_BUILD_ENTRY',
|
|
113
|
+
path: target.issuePath,
|
|
114
|
+
name,
|
|
115
|
+
message: `Build entry ${JSON.stringify(name)} is declared more than once`,
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const source = await resolveSource(target.sourceName, undefined, configuration.paths.sourceDir);
|
|
120
|
+
if (!source) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: 'SOURCE_NOT_FOUND',
|
|
123
|
+
path: target.issuePath,
|
|
124
|
+
name,
|
|
125
|
+
message: `Import target ${JSON.stringify(target.outputPath)} has no supported source file`,
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const entry = Object.freeze({
|
|
130
|
+
name,
|
|
131
|
+
origin: /** @type {const} */ ('import'),
|
|
132
|
+
manifestPath: target.issuePath,
|
|
133
|
+
sourcePath: source.sourcePath,
|
|
134
|
+
extension: source.extension,
|
|
135
|
+
outputPaths: Object.freeze({ [target.format]: target.outputPath }),
|
|
136
|
+
});
|
|
137
|
+
values.push(entry);
|
|
138
|
+
byName.set(name, entry);
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
validateSelections(byName, configuration.outputs.umdEntries, 'outputs.umdEntries', issues);
|
|
99
142
|
validateSelections(byName, configuration.transforms.preprocess, 'transforms.preprocess', issues);
|
|
100
|
-
validateOutputPaths(values, issues);
|
|
143
|
+
const shared = validateOutputPaths(values, issues);
|
|
101
144
|
|
|
102
145
|
if (issues.length > 0) throw new BuildEntryError(issues);
|
|
103
146
|
|
|
104
|
-
const
|
|
147
|
+
for (const entry of shared) byName.delete(entry.name);
|
|
148
|
+
const frozenValues = Object.freeze(values.filter(entry => !shared.has(entry)));
|
|
105
149
|
return Object.freeze({
|
|
106
150
|
values: frozenValues,
|
|
107
151
|
/** @param {string} name */
|
|
@@ -113,7 +157,7 @@ export async function resolveBuildEntries(packageEntryNames, configuration, cont
|
|
|
113
157
|
code: 'SELECTED_BUILD_ENTRY_NOT_FOUND',
|
|
114
158
|
path: 'entries',
|
|
115
159
|
name,
|
|
116
|
-
message: `Build entry ${JSON.stringify(name)} was not discovered; available entries: ${
|
|
160
|
+
message: `Build entry ${JSON.stringify(name)} was not discovered; available entries: ${frozenValues.map(entry => entry.name).join(', ')}`,
|
|
117
161
|
},
|
|
118
162
|
]);
|
|
119
163
|
}
|
|
@@ -195,20 +239,33 @@ function validateSelections(byName, names, selectionPath, issues) {
|
|
|
195
239
|
* @param {BuildEntryIssue[]} issues
|
|
196
240
|
*/
|
|
197
241
|
function validateOutputPaths(entries, issues) {
|
|
242
|
+
/** @type {Map<string, { entry: BuildEntry; format: string }>} */
|
|
198
243
|
const owners = new Map();
|
|
244
|
+
/** @type {Set<BuildEntry>} */
|
|
245
|
+
const shared = new Set();
|
|
199
246
|
for (const entry of entries) {
|
|
200
247
|
for (const [format, outputPath] of Object.entries(entry.outputPaths)) {
|
|
201
|
-
const
|
|
248
|
+
const normalizedPath = path.resolve(outputPath);
|
|
249
|
+
const owner = owners.get(normalizedPath);
|
|
202
250
|
if (owner) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
251
|
+
if (
|
|
252
|
+
entry.origin === 'import' &&
|
|
253
|
+
owner.format === format &&
|
|
254
|
+
path.resolve(entry.sourcePath) === path.resolve(owner.entry.sourcePath)
|
|
255
|
+
) {
|
|
256
|
+
shared.add(entry);
|
|
257
|
+
} else {
|
|
258
|
+
issues.push({
|
|
259
|
+
code: 'OUTPUT_PATH_COLLISION',
|
|
260
|
+
path: entry.manifestPath,
|
|
261
|
+
name: entry.name,
|
|
262
|
+
message: `Output path ${JSON.stringify(outputPath)} conflicts with ${owner.entry.manifestPath}`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
209
265
|
} else {
|
|
210
|
-
owners.set(
|
|
266
|
+
owners.set(normalizedPath, { entry, format });
|
|
211
267
|
}
|
|
212
268
|
}
|
|
213
269
|
}
|
|
270
|
+
return shared;
|
|
214
271
|
}
|
|
@@ -26,7 +26,7 @@ export function curry(fn, ...args) {
|
|
|
26
26
|
* @param {PackageProcessingResult} packageResult
|
|
27
27
|
*/
|
|
28
28
|
export default async function (provider, configuration, packageResult) {
|
|
29
|
-
const inputs = packageResult.entries.values.map(entry => entry.sourcePath);
|
|
29
|
+
const inputs = packageResult.entries.values.filter(entry => entry.origin !== 'import').map(entry => entry.sourcePath);
|
|
30
30
|
if (configuration.transforms.includeExternals === true) {
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
@@ -42,7 +42,7 @@ export default async function (provider, configuration, packageResult) {
|
|
|
42
42
|
provider.provide(
|
|
43
43
|
() =>
|
|
44
44
|
pluginExternals(
|
|
45
|
-
configuration.transforms.includeExternals === false
|
|
45
|
+
configuration.transforms.includeExternals === false && !configuration.resolution.imports
|
|
46
46
|
? {}
|
|
47
47
|
: (/** @type {string} */ id, /** @type {boolean} */ external, /** @type {string} */ importer) =>
|
|
48
48
|
includeExternals(importer, external, id, configuration)
|
|
@@ -86,6 +86,7 @@ export default async function (provider, configuration, packageResult) {
|
|
|
86
86
|
* @param {BuildConfiguration} configuration
|
|
87
87
|
*/
|
|
88
88
|
function includeExternals(_importer, external, id, configuration) {
|
|
89
|
+
if (configuration.resolution.imports && id.startsWith('#')) return false;
|
|
89
90
|
if (configuration.transforms.includeExternals === false) return external;
|
|
90
91
|
if (!external) return false;
|
|
91
92
|
const internals = /** @type {readonly string[]} */ (configuration.transforms.includeExternals);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { Priority } from '../priorities.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../types.js').BuildConfiguration} BuildConfiguration
|
|
8
|
+
* @typedef {import('../types.js').Provider} Provider
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Provider} provider
|
|
13
|
+
* @param {BuildConfiguration} configuration
|
|
14
|
+
*/
|
|
15
|
+
export default function (provider, configuration) {
|
|
16
|
+
if (!configuration.resolution.imports) return;
|
|
17
|
+
|
|
18
|
+
provider.globalImport('node:fs/promises', 'fs');
|
|
19
|
+
provider.globalImport('path', 'path');
|
|
20
|
+
const createPlugin = /** @type {typeof createPackageImportsPlugin} */ (
|
|
21
|
+
provider.globalSetup(createPackageImportsPlugin) ?? createPackageImportsPlugin
|
|
22
|
+
);
|
|
23
|
+
provider.provide(() => createPlugin(), Priority.packageImports);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Keep the package's private specifiers for runtime resolution. Each importer
|
|
28
|
+
* is assigned to its nearest real package boundary, so linked dependencies and
|
|
29
|
+
* nested workspace packages retain ownership of their own private imports.
|
|
30
|
+
*/
|
|
31
|
+
export function createPackageImportsPlugin() {
|
|
32
|
+
const packageRoot = fs.realpath(process.cwd());
|
|
33
|
+
/** @type {Map<string, string | null>} */
|
|
34
|
+
const owners = new Map();
|
|
35
|
+
return {
|
|
36
|
+
name: 'pkgbld:package-imports',
|
|
37
|
+
/** @param {string} id @param {string | undefined} importer */
|
|
38
|
+
async resolveId(id, importer) {
|
|
39
|
+
if (!id.startsWith('#') || !importer || importer.includes('\0')) return null;
|
|
40
|
+
let realImporter;
|
|
41
|
+
try {
|
|
42
|
+
realImporter = await fs.realpath(importer.split('?', 1)[0]);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (['ENOENT', 'EINVAL'].includes(/** @type {NodeJS.ErrnoException} */ (error).code ?? '')) return null;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
const owner = await findOwner(path.dirname(realImporter));
|
|
48
|
+
return owner === (await packageRoot) ? { id, external: true } : null;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** @param {string} start */
|
|
53
|
+
async function findOwner(start) {
|
|
54
|
+
let directory = start;
|
|
55
|
+
/** @type {string[]} */
|
|
56
|
+
const visited = [];
|
|
57
|
+
while (true) {
|
|
58
|
+
if (owners.has(directory)) {
|
|
59
|
+
const owner = /** @type {string | null} */ (owners.get(directory));
|
|
60
|
+
for (const visitedDirectory of visited) owners.set(visitedDirectory, owner);
|
|
61
|
+
return owner;
|
|
62
|
+
}
|
|
63
|
+
visited.push(directory);
|
|
64
|
+
try {
|
|
65
|
+
await fs.access(path.join(directory, 'package.json'));
|
|
66
|
+
for (const visitedDirectory of visited) owners.set(visitedDirectory, directory);
|
|
67
|
+
return directory;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code !== 'ENOENT') throw error;
|
|
70
|
+
}
|
|
71
|
+
const parent = path.dirname(directory);
|
|
72
|
+
if (parent === directory) {
|
|
73
|
+
for (const visitedDirectory of visited) owners.set(visitedDirectory, null);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
directory = parent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -2,13 +2,21 @@ import { Priority } from '../priorities.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import('../types.js').Provider} Provider
|
|
5
|
+
* @typedef {import('../types.js').BuildConfiguration} BuildConfiguration
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {Provider} provider
|
|
10
|
+
* @param {BuildConfiguration} configuration
|
|
9
11
|
*/
|
|
10
|
-
export default async function (provider) {
|
|
12
|
+
export default async function (provider, configuration) {
|
|
11
13
|
const pluginResolve = await provider.import('@rollup/plugin-node-resolve');
|
|
12
14
|
|
|
13
|
-
provider.provide(
|
|
15
|
+
provider.provide(
|
|
16
|
+
() =>
|
|
17
|
+
configuration.resolution.conditions.length > 0
|
|
18
|
+
? pluginResolve({ exportConditions: [...configuration.resolution.conditions] })
|
|
19
|
+
: pluginResolve(),
|
|
20
|
+
Priority.resolve
|
|
21
|
+
);
|
|
14
22
|
}
|
package/src/get-plugins.js
CHANGED
|
@@ -3,6 +3,7 @@ import clean from './builtin-plugins/clean.js';
|
|
|
3
3
|
import commonjs from './builtin-plugins/commonjs.js';
|
|
4
4
|
import externals from './builtin-plugins/externals.js';
|
|
5
5
|
import json from './builtin-plugins/json.js';
|
|
6
|
+
import packageImports from './builtin-plugins/package-imports.js';
|
|
6
7
|
import preprocess from './builtin-plugins/preprocess.js';
|
|
7
8
|
import resolve from './builtin-plugins/resolve.js';
|
|
8
9
|
import terser from './builtin-plugins/terser.js';
|
|
@@ -12,7 +13,7 @@ import terser from './builtin-plugins/terser.js';
|
|
|
12
13
|
* @typedef {import('./types.js').Provider} Provider
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
export const plugins = [clean, commonjs, externals, preprocess, resolve, terser, binify, json];
|
|
16
|
+
export const plugins = [clean, commonjs, externals, preprocess, packageImports, resolve, terser, binify, json];
|
|
16
17
|
|
|
17
18
|
const noop = () => undefined;
|
|
18
19
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
1
3
|
import refiner from '@slimlib/refine-partition';
|
|
2
4
|
|
|
3
5
|
import { plugins as pluginFactories } from './get-plugins.js';
|
|
@@ -21,8 +23,11 @@ import { areSetsEqual, toArray } from './helpers.js';
|
|
|
21
23
|
* @param {BuildPluginLifecycle} pluginLifecycle
|
|
22
24
|
*/
|
|
23
25
|
export async function getRollupConfigs([provider, plugins], packageResult, configuration, helpers, pluginLifecycle) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
+
const publicEntries = packageResult.entries.values.filter(entry => entry.origin !== 'import');
|
|
27
|
+
const privateEntries = packageResult.entries.values.filter(entry => entry.origin === 'import');
|
|
28
|
+
const inputs = publicEntries.map(entry => entry.sourcePath);
|
|
29
|
+
const publicInputSet = new Set(inputs);
|
|
30
|
+
const entriesBySourcePath = new Map(publicEntries.map(entry => [entry.sourcePath, entry]));
|
|
26
31
|
const factoryInProgress = [];
|
|
27
32
|
|
|
28
33
|
const fileNamePatterns = /** @type {{ [key in InternalModuleFormat]: string }} */ ({
|
|
@@ -43,7 +48,7 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
43
48
|
const expandInputs = new Set();
|
|
44
49
|
|
|
45
50
|
for (const plugin of plugins) {
|
|
46
|
-
if (plugin.format && plugin.inputs?.
|
|
51
|
+
if (plugin.format && plugin.inputs?.some(input => publicInputSet.has(input)) && !plugin.outputPlugin) {
|
|
47
52
|
for (const format of toArray(plugin.format)) {
|
|
48
53
|
expandInputs.add(format);
|
|
49
54
|
}
|
|
@@ -56,6 +61,8 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
56
61
|
|
|
57
62
|
for (const plugin of plugins) {
|
|
58
63
|
if (plugin.format && !plugin.outputPlugin) {
|
|
64
|
+
const publicPluginInputs = plugin.inputs?.filter(input => publicInputSet.has(input));
|
|
65
|
+
if (plugin.inputs && publicPluginInputs?.length === 0) continue;
|
|
59
66
|
const formats = toArray(plugin.format);
|
|
60
67
|
if (!plugin.inputs || plugin.inputs.length === 0) {
|
|
61
68
|
refineNext(doExpandInputs(formats));
|
|
@@ -64,7 +71,7 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
64
71
|
} else {
|
|
65
72
|
const expanded = [];
|
|
66
73
|
for (const format of formats) {
|
|
67
|
-
for (const input of
|
|
74
|
+
for (const input of /** @type {string[]} */ (publicPluginInputs)) {
|
|
68
75
|
expanded.push(`${format}.${input}`);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
@@ -124,7 +131,7 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
124
131
|
partitions.push({ formats: [...mapFormatInputs.keys()], inputs: [.../** @type {Set<string>} */ (prevInputs)] });
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
const publicConfigs = partitions.map(({ formats, inputs }) => {
|
|
128
135
|
return {
|
|
129
136
|
input: Object.fromEntries(
|
|
130
137
|
inputs.map(input => [/** @type {import('./types.js').BuildEntry} */ (entriesBySourcePath.get(input)).name, input])
|
|
@@ -143,6 +150,30 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
143
150
|
};
|
|
144
151
|
});
|
|
145
152
|
|
|
153
|
+
const privateConfigs = privateEntries.map(entry => {
|
|
154
|
+
const [[format, outputPath]] = Object.entries(entry.outputPaths);
|
|
155
|
+
const input = entry.sourcePath;
|
|
156
|
+
const selectedFormat = /** @type {InternalModuleFormat} */ (format);
|
|
157
|
+
return {
|
|
158
|
+
input,
|
|
159
|
+
output: [
|
|
160
|
+
{
|
|
161
|
+
format: selectedFormat,
|
|
162
|
+
dir: configuration.paths.outputDir,
|
|
163
|
+
entryFileNames: path
|
|
164
|
+
.relative(path.resolve(configuration.paths.outputDir), path.resolve(outputPath))
|
|
165
|
+
.replaceAll('\\', '/'),
|
|
166
|
+
plugins: getPlugins([selectedFormat], [input], true, true),
|
|
167
|
+
sourcemap: configuration.outputs.sourcemaps.includes(/** @type {import('./types.js').BuildFormat} */ (selectedFormat)),
|
|
168
|
+
...getExtraOutputSettings(selectedFormat, [input]),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
plugins: getPlugins([selectedFormat], [input], false, true),
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return [...publicConfigs, ...privateConfigs];
|
|
176
|
+
|
|
146
177
|
/**
|
|
147
178
|
* @param {InternalModuleFormat} format
|
|
148
179
|
* @param {string[]} inputs
|
|
@@ -176,14 +207,20 @@ export async function getRollupConfigs([provider, plugins], packageResult, confi
|
|
|
176
207
|
* @param {InternalModuleFormat[]} formats
|
|
177
208
|
* @param {string[]} inputs
|
|
178
209
|
* @param {boolean} outputPlugin
|
|
210
|
+
* @param {boolean} [privateOutput]
|
|
179
211
|
*/
|
|
180
|
-
function getPlugins(formats, inputs, outputPlugin) {
|
|
212
|
+
function getPlugins(formats, inputs, outputPlugin, privateOutput = false) {
|
|
181
213
|
const filteredPlugins = [];
|
|
182
214
|
for (const plugin of plugins) {
|
|
183
215
|
if (!!plugin.outputPlugin === outputPlugin) {
|
|
216
|
+
const publicPluginInputs = plugin.inputs?.filter(input => publicInputSet.has(input));
|
|
184
217
|
if (
|
|
185
218
|
(!plugin.format || toArray(plugin.format).some(format => formats.includes(format))) &&
|
|
186
|
-
(!plugin.inputs ||
|
|
219
|
+
(!plugin.inputs ||
|
|
220
|
+
plugin.inputs.length === 0 ||
|
|
221
|
+
(privateOutput
|
|
222
|
+
? plugin.inputs.some(input => inputs.includes(input))
|
|
223
|
+
: publicPluginInputs?.length > 0 && publicPluginInputs.every(input => inputs.includes(input))))
|
|
187
224
|
) {
|
|
188
225
|
filteredPlugins.push({
|
|
189
226
|
instance: plugin.plugin(),
|
package/src/options/index.js
CHANGED
|
@@ -59,6 +59,8 @@ export const cliFlagsDefaults = {
|
|
|
59
59
|
src: 'src',
|
|
60
60
|
bin: /** @type {string[] | undefined} */ (undefined),
|
|
61
61
|
includeExternals: /** @type {boolean | string[]} */ (false),
|
|
62
|
+
imports: false,
|
|
63
|
+
conditions: /** @type {string[]} */ ([]),
|
|
62
64
|
eject: false,
|
|
63
65
|
tsConfig: false,
|
|
64
66
|
updatePackageJson: true,
|
|
@@ -118,6 +120,15 @@ export const cliFlags = {
|
|
|
118
120
|
description: 'Include all/specified externals into the result bundle(s)',
|
|
119
121
|
optionalValue: true,
|
|
120
122
|
},
|
|
123
|
+
imports: {
|
|
124
|
+
type: /** @type {'boolean'} */ ('boolean'),
|
|
125
|
+
description: 'Build and preserve package imports',
|
|
126
|
+
default: cliFlagsDefaults.imports,
|
|
127
|
+
},
|
|
128
|
+
conditions: {
|
|
129
|
+
type: /** @type {(value: string) => string[]} */ (CommaSeparatedString),
|
|
130
|
+
description: 'Additional conditions for resolving bundled dependencies',
|
|
131
|
+
},
|
|
121
132
|
eject: {
|
|
122
133
|
type: /** @type {'boolean'} */ ('boolean'),
|
|
123
134
|
description: 'Eject config',
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { BuildEntryError, sourceFileExtensions } from './build-entries.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./types.js').BuildConfiguration} BuildConfiguration
|
|
9
|
+
* @typedef {import('./types.js').BuildEntryIssue} BuildEntryIssue
|
|
10
|
+
* @typedef {import('./types.js').ImportTarget} ImportTarget
|
|
11
|
+
* @typedef {import('./types.js').BuildFormat} BuildFormat
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const javascriptExtensions = new Set(['.js', '.mjs', '.cjs']);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find every local JavaScript leaf of an authored package imports map.
|
|
18
|
+
* Non-JavaScript and package-specifier leaves remain the responsibility of
|
|
19
|
+
* their runtime resolver or producer.
|
|
20
|
+
*
|
|
21
|
+
* @param {unknown} imports
|
|
22
|
+
* @param {BuildConfiguration} configuration
|
|
23
|
+
* @param {unknown} packageType
|
|
24
|
+
* @returns {Promise<ImportTarget[]>}
|
|
25
|
+
*/
|
|
26
|
+
export async function collectPackageImportTargets(imports, configuration, packageType) {
|
|
27
|
+
if (imports === undefined || !configuration.resolution.imports) return [];
|
|
28
|
+
|
|
29
|
+
/** @type {BuildEntryIssue[]} */
|
|
30
|
+
const issues = [];
|
|
31
|
+
/** @type {Map<string, ImportTarget>} */
|
|
32
|
+
const targets = new Map();
|
|
33
|
+
const leaves = parseImportMap(imports, issues);
|
|
34
|
+
for (const leaf of leaves) {
|
|
35
|
+
for (const target of await expandLocalTarget(leaf, configuration, packageType, issues)) {
|
|
36
|
+
if (!targets.has(target.outputPath)) targets.set(target.outputPath, target);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (issues.length > 0) throw new BuildEntryError(issues);
|
|
41
|
+
return [...targets.values()];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Walk the authored map without selecting runtime conditions. Every local
|
|
46
|
+
* branch may need its own output, while package specifiers remain mappings.
|
|
47
|
+
* @param {unknown} imports
|
|
48
|
+
* @param {BuildEntryIssue[]} issues
|
|
49
|
+
*/
|
|
50
|
+
function parseImportMap(imports, issues) {
|
|
51
|
+
/** @type {{ value: string; issuePath: string; wildcardKey: boolean }[]} */
|
|
52
|
+
const leaves = [];
|
|
53
|
+
if (!isRecord(imports)) {
|
|
54
|
+
issues.push({ code: 'INVALID_IMPORT_MAP', path: 'package.imports', message: 'must be an object' });
|
|
55
|
+
return leaves;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const [key, value] of Object.entries(imports)) {
|
|
59
|
+
const issuePath = `package.imports[${JSON.stringify(key)}]`;
|
|
60
|
+
const stars = key.match(/\*/g)?.length ?? 0;
|
|
61
|
+
if (!key.startsWith('#') || key === '#' || key.endsWith('/') || stars > 1) {
|
|
62
|
+
issues.push({ code: 'INVALID_IMPORT_KEY', path: issuePath, message: `Invalid package import key ${JSON.stringify(key)}` });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
visit(value, issuePath, stars > 0);
|
|
66
|
+
}
|
|
67
|
+
return leaves;
|
|
68
|
+
|
|
69
|
+
/** @param {unknown} value @param {string} issuePath @param {boolean} wildcardKey */
|
|
70
|
+
function visit(value, issuePath, wildcardKey) {
|
|
71
|
+
if (value === null) return;
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
for (const [index, leaf] of value.entries()) visit(leaf, `${issuePath}[${index}]`, wildcardKey);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (isRecord(value)) {
|
|
77
|
+
for (const [condition, leaf] of Object.entries(value)) {
|
|
78
|
+
if (isArrayIndex(condition)) {
|
|
79
|
+
issues.push({
|
|
80
|
+
code: 'INVALID_IMPORT_CONDITION',
|
|
81
|
+
path: `${issuePath}.${condition}`,
|
|
82
|
+
message: `Integer condition key ${JSON.stringify(condition)} is invalid in a package imports map`,
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
visit(leaf, `${issuePath}.${condition}`, wildcardKey);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (typeof value !== 'string') {
|
|
91
|
+
issues.push({ code: 'INVALID_IMPORT_TARGET', path: issuePath, message: 'must be a string, null, array, or condition object' });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!value.startsWith('./')) {
|
|
95
|
+
if (value.startsWith('.') || value.startsWith('/') || value.includes('\\') || value.length === 0 || URL.canParse(value)) {
|
|
96
|
+
issues.push({
|
|
97
|
+
code: 'INVALID_IMPORT_TARGET',
|
|
98
|
+
path: issuePath,
|
|
99
|
+
message: `Invalid package import target ${JSON.stringify(value)}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
leaves.push({ value, issuePath, wildcardKey });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @param {string} key */
|
|
109
|
+
function isArrayIndex(key) {
|
|
110
|
+
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < 2 ** 32 - 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate a local URL target, then expand its output pattern against source
|
|
115
|
+
* names. Final source-extension selection happens in resolveBuildEntries.
|
|
116
|
+
* @param {{ value: string; issuePath: string; wildcardKey: boolean }} leaf
|
|
117
|
+
* @param {BuildConfiguration} configuration
|
|
118
|
+
* @param {unknown} packageType
|
|
119
|
+
* @param {BuildEntryIssue[]} issues
|
|
120
|
+
* @returns {Promise<ImportTarget[]>}
|
|
121
|
+
*/
|
|
122
|
+
async function expandLocalTarget({ value, issuePath, wildcardKey }, configuration, packageType, issues) {
|
|
123
|
+
const pathPart = value.split(/[?#]/, 1)[0];
|
|
124
|
+
const rawSegments = pathPart.slice(2).split('/');
|
|
125
|
+
let outputPath;
|
|
126
|
+
try {
|
|
127
|
+
if (
|
|
128
|
+
pathPart.includes('\\') ||
|
|
129
|
+
rawSegments.some(segment => {
|
|
130
|
+
const decoded = decodeURIComponent(segment);
|
|
131
|
+
return (
|
|
132
|
+
decoded === '' ||
|
|
133
|
+
decoded === '.' ||
|
|
134
|
+
decoded === '..' ||
|
|
135
|
+
decoded.toLowerCase() === 'node_modules' ||
|
|
136
|
+
decoded.includes('/') ||
|
|
137
|
+
decoded.includes('\\')
|
|
138
|
+
);
|
|
139
|
+
})
|
|
140
|
+
) {
|
|
141
|
+
throw new Error('invalid path segment');
|
|
142
|
+
}
|
|
143
|
+
outputPath = fileURLToPath(new URL(value, pathToFileURL(path.join(process.cwd(), 'package.json'))));
|
|
144
|
+
} catch {
|
|
145
|
+
issues.push({
|
|
146
|
+
code: 'INVALID_IMPORT_TARGET',
|
|
147
|
+
path: issuePath,
|
|
148
|
+
message: `Invalid local package import target ${JSON.stringify(value)}`,
|
|
149
|
+
});
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const extension = path.extname(outputPath);
|
|
154
|
+
if (!javascriptExtensions.has(extension)) return [];
|
|
155
|
+
|
|
156
|
+
const outputDir = path.resolve(configuration.paths.outputDir);
|
|
157
|
+
if (!outputPath.startsWith(`${outputDir}${path.sep}`) || (pathPart.includes('*') && !wildcardKey)) {
|
|
158
|
+
issues.push({
|
|
159
|
+
code: 'INVALID_IMPORT_TARGET',
|
|
160
|
+
path: issuePath,
|
|
161
|
+
message: `Local JavaScript target ${JSON.stringify(value)} must be a safe path beneath ${configuration.paths.outputDir}`,
|
|
162
|
+
});
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const format = /** @type {BuildFormat} */ (
|
|
167
|
+
extension === '.mjs' ? 'es' : extension === '.cjs' ? 'cjs' : packageType === 'module' ? 'es' : 'cjs'
|
|
168
|
+
);
|
|
169
|
+
if (!configuration.outputs.formats.includes(format)) {
|
|
170
|
+
issues.push({
|
|
171
|
+
code: 'EXCLUDED_IMPORT_FORMAT',
|
|
172
|
+
path: issuePath,
|
|
173
|
+
message: `Local target ${JSON.stringify(value)} requires ${format}, which is excluded by outputs.formats`,
|
|
174
|
+
});
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const relativeOutput = path.relative(outputDir, outputPath).replaceAll('\\', '/');
|
|
179
|
+
const sourceName = relativeOutput.slice(0, -extension.length);
|
|
180
|
+
if (pathPart.includes('*')) {
|
|
181
|
+
const sourceNames = await findMatchingSourceNames(configuration.paths.sourceDir, sourceName);
|
|
182
|
+
if (sourceNames.length === 0) {
|
|
183
|
+
issues.push({
|
|
184
|
+
code: 'SOURCE_NOT_FOUND',
|
|
185
|
+
path: issuePath,
|
|
186
|
+
message: `Import target ${JSON.stringify(value)} has no supported source file`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return sourceNames.map(matchedName => ({
|
|
190
|
+
sourceName: matchedName,
|
|
191
|
+
outputPath: `./${configuration.paths.outputDir}/${matchedName}${extension}`,
|
|
192
|
+
format,
|
|
193
|
+
issuePath,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
return [{ sourceName, outputPath: `./${configuration.paths.outputDir}/${relativeOutput}`, format, issuePath }];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @param {unknown} value @returns {value is Record<string, unknown>} */
|
|
200
|
+
function isRecord(value) {
|
|
201
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @param {string} sourceDir @param {string} pattern */
|
|
205
|
+
async function findMatchingSourceNames(sourceDir, pattern) {
|
|
206
|
+
const parts = pattern.split('*').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
207
|
+
const matcher = new RegExp(
|
|
208
|
+
`^${parts[0]}(.+?)${parts[1]}${parts
|
|
209
|
+
.slice(2)
|
|
210
|
+
.map(part => `\\1${part}`)
|
|
211
|
+
.join('')}$`
|
|
212
|
+
);
|
|
213
|
+
/** @type {Set<string>} */
|
|
214
|
+
const names = new Set();
|
|
215
|
+
await walk(sourceDir, '');
|
|
216
|
+
return [...names].sort();
|
|
217
|
+
|
|
218
|
+
/** @param {string} directory @param {string} relative */
|
|
219
|
+
async function walk(directory, relative) {
|
|
220
|
+
let entries;
|
|
221
|
+
try {
|
|
222
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT') return;
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const nextRelative = relative ? `${relative}/${entry.name}` : entry.name;
|
|
229
|
+
if (entry.isDirectory()) {
|
|
230
|
+
await walk(path.join(directory, entry.name), nextRelative);
|
|
231
|
+
} else if (entry.isFile()) {
|
|
232
|
+
const extension = path.posix.extname(nextRelative).slice(1);
|
|
233
|
+
if (sourceFileExtensions.includes(/** @type {typeof sourceFileExtensions[number]} */ (extension))) {
|
|
234
|
+
const name = nextRelative.slice(0, -(extension.length + 1));
|
|
235
|
+
if (matcher.test(name)) names.add(name);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/priorities.js
CHANGED
package/src/process-pkg.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { createLogger, LogLevel } from '@niceties/logger';
|
|
4
4
|
|
|
5
5
|
import { resolveBuildEntries } from './build-entries.js';
|
|
6
|
+
import { collectPackageImportTargets } from './package-imports.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {import('type-fest').JsonObject} JsonObject
|
|
@@ -42,6 +43,9 @@ export async function processPackage(pkg, configuration, pluginLifecycle) {
|
|
|
42
43
|
process.exit(-1);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
const finalPackageType = allowEsm && !allowCjs && typeof pkg.type !== 'string' ? 'module' : pkg.type;
|
|
47
|
+
const importTargets = await collectPackageImportTargets(pkg.imports, configuration, finalPackageType);
|
|
48
|
+
|
|
45
49
|
if (!Array.isArray(pkg.files)) {
|
|
46
50
|
pkg.files = [];
|
|
47
51
|
}
|
|
@@ -71,7 +75,7 @@ export async function processPackage(pkg, configuration, pluginLifecycle) {
|
|
|
71
75
|
entryNames.push(indexId);
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
const entries = await resolveBuildEntries(entryNames, configuration, contributions =>
|
|
78
|
+
const entries = await resolveBuildEntries(entryNames, importTargets, configuration, contributions =>
|
|
75
79
|
pluginLifecycle.contributeEntries(contributions, configuration)
|
|
76
80
|
);
|
|
77
81
|
const indexEntry = entries.require(indexId);
|
|
@@ -171,14 +175,15 @@ export async function processPackage(pkg, configuration, pluginLifecycle) {
|
|
|
171
175
|
pkg.bin = /** @type {string} */ (executableOutputs[0]);
|
|
172
176
|
}
|
|
173
177
|
} else if (packageJson.executables.mode === 'infer' && allowCjs && entries.values.length > 0) {
|
|
178
|
+
const executableEntries = entries.values.filter(entry => entry.origin !== 'import');
|
|
174
179
|
if (typeof pkg.bin === 'string') {
|
|
175
|
-
if (
|
|
180
|
+
if (executableEntries.some(entry => pkg.bin === entry.outputPaths.cjs)) {
|
|
176
181
|
executableOutputs = [pkg.bin];
|
|
177
182
|
}
|
|
178
183
|
} else if (typeof pkg.bin === 'object' && pkg.bin !== null) {
|
|
179
184
|
executableOutputs = /** @type {string[]} */ (
|
|
180
185
|
Object.values(pkg.bin).filter(
|
|
181
|
-
value => typeof value === 'string' &&
|
|
186
|
+
value => typeof value === 'string' && executableEntries.some(entry => value === entry.outputPaths.cjs)
|
|
182
187
|
)
|
|
183
188
|
);
|
|
184
189
|
}
|
|
@@ -189,7 +194,9 @@ export async function processPackage(pkg, configuration, pluginLifecycle) {
|
|
|
189
194
|
typeof pkg.directories.bin === 'string'
|
|
190
195
|
) {
|
|
191
196
|
if (path.resolve(pkg.directories.bin) === path.resolve(paths.outputDir)) {
|
|
192
|
-
executableOutputs.push(
|
|
197
|
+
executableOutputs.push(
|
|
198
|
+
...executableEntries.flatMap(entry => (entry.outputPaths.cjs == null ? [] : [entry.outputPaths.cjs]))
|
|
199
|
+
);
|
|
193
200
|
executableOutputs = Array.from(new Set(executableOutputs));
|
|
194
201
|
}
|
|
195
202
|
}
|
package/src/types.js
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
* includeExternals: boolean | string[];
|
|
61
61
|
* removeLegalComments: boolean;
|
|
62
62
|
* };
|
|
63
|
+
* resolution: { imports: boolean; conditions: string[] };
|
|
63
64
|
* packageJson: {
|
|
64
65
|
* update: boolean;
|
|
65
66
|
* format: boolean;
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
* includeExternals: boolean | readonly string[];
|
|
88
89
|
* removeLegalComments: boolean;
|
|
89
90
|
* }>;
|
|
91
|
+
* resolution: Readonly<{ imports: boolean; conditions: readonly string[] }>;
|
|
90
92
|
* packageJson: Readonly<{
|
|
91
93
|
* update: boolean;
|
|
92
94
|
* format: boolean;
|
|
@@ -117,6 +119,8 @@
|
|
|
117
119
|
/**
|
|
118
120
|
* @typedef {Readonly<{
|
|
119
121
|
* name: string;
|
|
122
|
+
* origin: 'export' | 'import' | 'plugin';
|
|
123
|
+
* manifestPath: string;
|
|
120
124
|
* sourcePath: string;
|
|
121
125
|
* extension: BuildEntryExtension;
|
|
122
126
|
* outputPaths: Readonly<Partial<Record<BuildFormat, string>>>;
|
|
@@ -125,7 +129,8 @@
|
|
|
125
129
|
|
|
126
130
|
/** @typedef {{ name: string; sourcePath?: string }} BuildEntryContribution */
|
|
127
131
|
/** @typedef {{ add(contribution: BuildEntryContribution): void }} BuildEntryContributions */
|
|
128
|
-
/** @typedef {{
|
|
132
|
+
/** @typedef {{ sourceName: string; outputPath: string; format: BuildFormat; issuePath: string }} ImportTarget */
|
|
133
|
+
/** @typedef {{ code: 'INVALID_BUILD_ENTRY_NAME' | 'DUPLICATE_BUILD_ENTRY' | 'SOURCE_NOT_FOUND' | 'SELECTED_BUILD_ENTRY_NOT_FOUND' | 'OUTPUT_PATH_COLLISION' | 'INVALID_IMPORT_MAP' | 'INVALID_IMPORT_KEY' | 'INVALID_IMPORT_TARGET' | 'EXCLUDED_IMPORT_FORMAT'; path: string; message: string; name?: string }} BuildEntryIssue */
|
|
129
134
|
|
|
130
135
|
/**
|
|
131
136
|
* @typedef {Readonly<{
|
package/types/index.d.ts
CHANGED
|
@@ -78,6 +78,10 @@ declare module 'pkgbld' {
|
|
|
78
78
|
includeExternals: boolean | string[];
|
|
79
79
|
removeLegalComments: boolean;
|
|
80
80
|
};
|
|
81
|
+
resolution: {
|
|
82
|
+
imports: boolean;
|
|
83
|
+
conditions: string[];
|
|
84
|
+
};
|
|
81
85
|
packageJson: {
|
|
82
86
|
update: boolean;
|
|
83
87
|
format: boolean;
|
|
@@ -118,6 +122,10 @@ declare module 'pkgbld' {
|
|
|
118
122
|
includeExternals: boolean | readonly string[];
|
|
119
123
|
removeLegalComments: boolean;
|
|
120
124
|
}>;
|
|
125
|
+
resolution: Readonly<{
|
|
126
|
+
imports: boolean;
|
|
127
|
+
conditions: readonly string[];
|
|
128
|
+
}>;
|
|
121
129
|
packageJson: Readonly<{
|
|
122
130
|
update: boolean;
|
|
123
131
|
format: boolean;
|
|
@@ -149,6 +157,8 @@ declare module 'pkgbld' {
|
|
|
149
157
|
type PluginSharedState_1 = Map<unknown, unknown>;
|
|
150
158
|
type BuildEntry_1 = Readonly<{
|
|
151
159
|
name: string;
|
|
160
|
+
origin: "export" | "import" | "plugin";
|
|
161
|
+
manifestPath: string;
|
|
152
162
|
sourcePath: string;
|
|
153
163
|
extension: BuildEntryExtension;
|
|
154
164
|
outputPaths: Readonly<Partial<Record<BuildFormat_1, string>>>;
|
|
@@ -161,7 +171,7 @@ declare module 'pkgbld' {
|
|
|
161
171
|
add(contribution: BuildEntryContribution_1): void;
|
|
162
172
|
};
|
|
163
173
|
type BuildEntryIssue_1 = {
|
|
164
|
-
code: "INVALID_BUILD_ENTRY_NAME" | "DUPLICATE_BUILD_ENTRY" | "SOURCE_NOT_FOUND" | "SELECTED_BUILD_ENTRY_NOT_FOUND" | "OUTPUT_PATH_COLLISION";
|
|
174
|
+
code: "INVALID_BUILD_ENTRY_NAME" | "DUPLICATE_BUILD_ENTRY" | "SOURCE_NOT_FOUND" | "SELECTED_BUILD_ENTRY_NOT_FOUND" | "OUTPUT_PATH_COLLISION" | "INVALID_IMPORT_MAP" | "INVALID_IMPORT_KEY" | "INVALID_IMPORT_TARGET" | "EXCLUDED_IMPORT_FORMAT";
|
|
165
175
|
path: string;
|
|
166
176
|
message: string;
|
|
167
177
|
name?: string;
|
|
@@ -245,6 +255,8 @@ declare module 'pkgbld/options' {
|
|
|
245
255
|
let src: string;
|
|
246
256
|
let bin: string[] | undefined;
|
|
247
257
|
let includeExternals: boolean | string[];
|
|
258
|
+
let imports: boolean;
|
|
259
|
+
let conditions: string[];
|
|
248
260
|
let eject: boolean;
|
|
249
261
|
let tsConfig: boolean;
|
|
250
262
|
let updatePackageJson: boolean;
|