ember.nvp 1.14.0 → 1.15.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/.release-plan.json +3 -3
- package/CHANGELOG.md +11 -0
- package/README.md +28 -0
- package/package.json +2 -1
- package/src/cli/generator.js +10 -0
- package/src/cli/index.js +19 -2
- package/src/cli/questions/layers.js +3 -2
- package/src/cli/questions/migration.js +69 -0
- package/src/index.js +1 -0
- package/src/migration-from/addons.js +154 -0
- package/src/migration-from/app.js +1117 -0
- package/src/migration-from/at-ember/addon-blueprint/index.js +515 -0
- package/src/migration-from/at-ember/addon-blueprint/rollup.js +188 -0
- package/src/migration-from/at-ember/app-blueprint/index.js +60 -0
- package/src/migration-from/at-ember-tooling/classic-build-addon-blueprint/index.js +601 -0
- package/src/migration-from/at-ember-tooling/classic-build-app-blueprint/index.js +118 -0
- package/src/migration-from/babel.js +207 -0
- package/src/migration-from/ember-cli-build.js +238 -0
- package/src/migration-from/files.js +120 -0
- package/src/migration-from/findings.js +69 -0
- package/src/migration-from/index.js +81 -0
- package/src/migration-from/javascript.js +217 -0
- package/src/migration-from/layers.js +170 -0
- package/src/migration-from/library.js +311 -0
- package/src/utils/types.ts +69 -0
- package/test/ember.nvp/migration-from-fixtures.ts +506 -0
- package/test/ember.nvp/migration-from.test.ts +475 -0
package/.release-plan.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"solution": {
|
|
3
3
|
"ember.nvp": {
|
|
4
4
|
"impact": "minor",
|
|
5
|
-
"oldVersion": "1.
|
|
6
|
-
"newVersion": "1.
|
|
5
|
+
"oldVersion": "1.14.0",
|
|
6
|
+
"newVersion": "1.15.0",
|
|
7
7
|
"tagName": "latest",
|
|
8
8
|
"constraints": [
|
|
9
9
|
{
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"oldVersion": "1.2.0"
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
-
"description": "## Release (2026-
|
|
26
|
+
"description": "## Release (2026-10-02)\n\n* ember.nvp 1.15.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#158](https://github.com/NullVoxPopuli/ember.nvp/pull/158) Migrate projects from the older blueprints ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### Committers: 1\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n"
|
|
27
27
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release (2026-10-02)
|
|
4
|
+
|
|
5
|
+
* ember.nvp 1.15.0 (minor)
|
|
6
|
+
|
|
7
|
+
#### :rocket: Enhancement
|
|
8
|
+
* `ember.nvp`
|
|
9
|
+
* [#158](https://github.com/NullVoxPopuli/ember.nvp/pull/158) Migrate projects from the older blueprints ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
10
|
+
|
|
11
|
+
#### Committers: 1
|
|
12
|
+
- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
|
|
13
|
+
|
|
3
14
|
## Release (2026-09-30)
|
|
4
15
|
|
|
5
16
|
* ember.nvp 1.14.0 (minor)
|
package/README.md
CHANGED
|
@@ -59,6 +59,29 @@ The accepted changes land as uncommitted edits, for you to review with your own
|
|
|
59
59
|
New projects skip the confirmation, and are written as soon as generation succeeds.
|
|
60
60
|
For scripting, `--write yes` / `--write no` answers the confirmation up front.
|
|
61
61
|
|
|
62
|
+
### Migrating from an older blueprint
|
|
63
|
+
|
|
64
|
+
Run ember.nvp in a project from one of these blueprints, and choose update:
|
|
65
|
+
|
|
66
|
+
| Blueprint | Becomes |
|
|
67
|
+
| ------------------------------------------------------------------------------- | --------- |
|
|
68
|
+
| `@ember/addon-blueprint`, and the addon package of `@embroider/addon-blueprint` | a library |
|
|
69
|
+
| `@ember-tooling/classic-build-addon-blueprint` (classic addons) | a library |
|
|
70
|
+
| `@ember/app-blueprint` (Vite with `@embroider/compat`) | an app |
|
|
71
|
+
| `@ember-tooling/classic-build-app-blueprint` (classic apps) | an app |
|
|
72
|
+
|
|
73
|
+
ember.nvp checks the project before it asks about layers.
|
|
74
|
+
When the project uses something that ember.nvp does not support, such as v1 addons or `.hbs` templates, ember.nvp lists each item with what to do, and writes nothing.
|
|
75
|
+
Most of these items also work in the old setup, so you can fix them there first.
|
|
76
|
+
|
|
77
|
+
Otherwise, the migration keeps your code, and replaces the old build:
|
|
78
|
+
|
|
79
|
+
- libraries build with tsdown, and keep `addon-main.cjs`, so that apps with ember-cli can still use them
|
|
80
|
+
- apps build with Vite, without `@embroider/compat`
|
|
81
|
+
|
|
82
|
+
The layers that replace the old tooling, such as qunit for the tests, start selected.
|
|
83
|
+
After writing, ember.nvp lists what is left for you, such as settings to copy by hand.
|
|
84
|
+
|
|
62
85
|
### Wrapping
|
|
63
86
|
|
|
64
87
|
The provided CLI is only a wrapper around the exported `generateProject` function.
|
|
@@ -84,6 +107,11 @@ await generateProject(new Project(
|
|
|
84
107
|
|
|
85
108
|
All parts of the generator are idempotent, so running generators on existing projects _can_ no-op.
|
|
86
109
|
|
|
110
|
+
A project from an older blueprint is migrated first.
|
|
111
|
+
`generateProject` returns `{ todo }`, the list of what is left for the user.
|
|
112
|
+
When the project needs changes before it can migrate, `generateProject` throws a `MigrationError` with a `findings` list, and changes nothing.
|
|
113
|
+
`checkMigration(project)` gives the same lists without changing anything, and `formatFindings(findings)` prints them the way the CLI does.
|
|
114
|
+
|
|
87
115
|
To get the same don't-write-until-confirmed behavior as the CLI, wrap generation in a `Stage`.
|
|
88
116
|
A stage is a scratch copy of the target directory (without `node_modules` and `.git`).
|
|
89
117
|
Layers run against that copy, and the target directory is untouched until you commit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember.nvp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "ember project generator, alternative to the official blueprints, but more modern -- hopefully one day to upstream back in to ember-cli / official blueprints",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"#bases/*": "./src/bases/*/index.js",
|
|
14
14
|
"#consolidators/*": "./src/consolidators/*",
|
|
15
15
|
"#layers": "./src/layers/index.js",
|
|
16
|
+
"#migration-from": "./src/migration-from/index.js",
|
|
16
17
|
"#types": "./src/utils/types.ts",
|
|
17
18
|
"#utils/*": "./src/utils/*",
|
|
18
19
|
"#test-helpers": "./test/test-helpers.ts"
|
package/src/cli/generator.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { mkdir, rm } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import { bases } from "#bases";
|
|
4
|
+
import { migrate } from "#migration-from";
|
|
4
5
|
import { consolidateLintingScripts } from "../consolidators/linting.js";
|
|
5
6
|
import { hasGit } from "#utils/git.js";
|
|
6
7
|
/**
|
|
7
8
|
* Generate project files by running layer functions
|
|
8
9
|
*
|
|
10
|
+
* A project from an older blueprint is migrated first.
|
|
11
|
+
* When it uses features that ember.nvp does not support,
|
|
12
|
+
* this throws a `MigrationError` that lists them, before anything changes.
|
|
13
|
+
*
|
|
9
14
|
* @param {import('#utils/project.js').Project} project
|
|
10
15
|
* @param {string} [replaceOrUpdate]
|
|
16
|
+
* @returns {Promise<{ todo: import('#types').Finding[] }>} work left for the user after a migration
|
|
11
17
|
*/
|
|
12
18
|
export async function generateProject(project, replaceOrUpdate) {
|
|
13
19
|
if (replaceOrUpdate === "replace") {
|
|
@@ -16,6 +22,8 @@ export async function generateProject(project, replaceOrUpdate) {
|
|
|
16
22
|
|
|
17
23
|
await mkdir(project.directory, { recursive: true });
|
|
18
24
|
|
|
25
|
+
let migration = await migrate(project);
|
|
26
|
+
|
|
19
27
|
await bases[project.desires.type].run(project);
|
|
20
28
|
|
|
21
29
|
/**
|
|
@@ -51,6 +59,8 @@ export async function generateProject(project, replaceOrUpdate) {
|
|
|
51
59
|
`[ember.nvp] Consolidation commit -- Please report issues to https://github.com/NullVoxPopuli/ember.nvp/`,
|
|
52
60
|
);
|
|
53
61
|
}
|
|
62
|
+
|
|
63
|
+
return { todo: migration?.report.todo ?? [] };
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/**
|
package/src/cli/index.js
CHANGED
|
@@ -17,6 +17,8 @@ import { cwd } from "#utils/cwd.js";
|
|
|
17
17
|
import { Project } from "#utils/project.js";
|
|
18
18
|
import { Stage } from "#utils/stage.js";
|
|
19
19
|
import { askAboutProjectHere, askReplaceOrUpdate } from "./questions/replace-or-update.js";
|
|
20
|
+
import { checkForMigration, showTodo } from "./questions/migration.js";
|
|
21
|
+
import { MigrationError } from "#migration-from";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* This whole file's primary purpose is to be an interactive CLI
|
|
@@ -35,7 +37,11 @@ async function main() {
|
|
|
35
37
|
const projectPath = here ? cwd : await askPath(projectName);
|
|
36
38
|
const replaceOrUpdate = here ?? (await askReplaceOrUpdate(projectPath));
|
|
37
39
|
const projectType = await askProjectType(replaceOrUpdate === "update" ? projectPath : undefined);
|
|
38
|
-
const
|
|
40
|
+
const migrationLayers =
|
|
41
|
+
replaceOrUpdate === "update"
|
|
42
|
+
? await checkForMigration(projectPath, projectName, projectType)
|
|
43
|
+
: [];
|
|
44
|
+
const selectedLayers = await askLayers(projectType, migrationLayers);
|
|
39
45
|
const packageManager = await askPackageManager();
|
|
40
46
|
|
|
41
47
|
// Layer options start from what the project already uses
|
|
@@ -82,9 +88,12 @@ async function main() {
|
|
|
82
88
|
const s = p.spinner();
|
|
83
89
|
s.start("Creating your Ember app...");
|
|
84
90
|
|
|
91
|
+
/** @type {import('#types').Finding[]} */
|
|
92
|
+
let todo = [];
|
|
93
|
+
|
|
85
94
|
try {
|
|
86
95
|
// Generate the project (into the stage)
|
|
87
|
-
await generateProject(project);
|
|
96
|
+
({ todo } = await generateProject(project));
|
|
88
97
|
|
|
89
98
|
s.stop("Project generated");
|
|
90
99
|
} catch (err) {
|
|
@@ -92,6 +101,12 @@ async function main() {
|
|
|
92
101
|
|
|
93
102
|
await stage.discard();
|
|
94
103
|
|
|
104
|
+
// the message is the list for the user, and a stack trace adds nothing to it
|
|
105
|
+
if (err instanceof MigrationError) {
|
|
106
|
+
p.cancel(err.message);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
95
110
|
if (err instanceof Error) {
|
|
96
111
|
p.cancel(`Error: ${err.message}`);
|
|
97
112
|
if (err.stack) {
|
|
@@ -141,6 +156,8 @@ async function main() {
|
|
|
141
156
|
|
|
142
157
|
p.log.success(`Applied ${toApply.length} change${toApply.length === 1 ? "" : "s"}`);
|
|
143
158
|
|
|
159
|
+
showTodo(todo);
|
|
160
|
+
|
|
144
161
|
/**
|
|
145
162
|
* Converted / generated files are written expecting the project's own
|
|
146
163
|
* formatting and lint pass to run right away.
|
|
@@ -5,8 +5,9 @@ import * as p from "@clack/prompts";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {import('#types').ProjectType} projectType
|
|
8
|
+
* @param {string[]} [suggested] layers to select by default, beyond each layer's own default
|
|
8
9
|
*/
|
|
9
|
-
export async function askLayers(projectType) {
|
|
10
|
+
export async function askLayers(projectType, suggested = []) {
|
|
10
11
|
const optionalLayers = (await discoverLayers()).filter(
|
|
11
12
|
(layer) => typeof layer.run === "function",
|
|
12
13
|
);
|
|
@@ -37,7 +38,7 @@ export async function askLayers(projectType) {
|
|
|
37
38
|
const defaultValues = (
|
|
38
39
|
await Promise.all(
|
|
39
40
|
optionalLayers.map(async (layer) => {
|
|
40
|
-
let result = await layer.defaultValue?.(projectType);
|
|
41
|
+
let result = suggested.includes(layer.name) || (await layer.defaultValue?.(projectType));
|
|
41
42
|
|
|
42
43
|
return result && layer.name;
|
|
43
44
|
}),
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { styleText } from "node:util";
|
|
3
|
+
import { checkMigration, formatFindings } from "#migration-from";
|
|
4
|
+
import { Project } from "#utils/project.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A project from an older blueprint is migrated during the update.
|
|
8
|
+
*
|
|
9
|
+
* The check runs before the layer questions,
|
|
10
|
+
* so that a project that ember.nvp cannot migrate stops here,
|
|
11
|
+
* with the list of what to change first.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectPath
|
|
14
|
+
* @param {string} name
|
|
15
|
+
* @param {import('#types').ProjectType} type
|
|
16
|
+
* @returns {Promise<string[]>} layers that replace the old blueprint's tooling
|
|
17
|
+
*/
|
|
18
|
+
export async function checkForMigration(projectPath, name, type) {
|
|
19
|
+
let project = new Project(projectPath, {
|
|
20
|
+
name,
|
|
21
|
+
type,
|
|
22
|
+
path: projectPath,
|
|
23
|
+
layers: [],
|
|
24
|
+
packageManager: "pnpm",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let spinner = p.spinner();
|
|
28
|
+
|
|
29
|
+
spinner.start("Checking for an older blueprint");
|
|
30
|
+
|
|
31
|
+
let result = await checkMigration(project);
|
|
32
|
+
|
|
33
|
+
if (!result) {
|
|
34
|
+
spinner.stop("No older blueprint found");
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let { migration, report } = result;
|
|
39
|
+
|
|
40
|
+
if (report.unsupported.length > 0) {
|
|
41
|
+
spinner.stop(`This project is from ${migration.label}`);
|
|
42
|
+
p.log.error(
|
|
43
|
+
`ember.nvp cannot migrate this project yet. Change these first:\n\n` +
|
|
44
|
+
formatFindings(report.unsupported),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (report.todo.length > 0) {
|
|
48
|
+
p.log.info(
|
|
49
|
+
`After that, the migration leaves these for you:\n\n${formatFindings(report.todo)}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
p.cancel("Nothing was written");
|
|
54
|
+
return process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.stop(`Migrating from ${styleText("cyan", migration.label)}`);
|
|
58
|
+
|
|
59
|
+
return report.layers;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {import('#types').Finding[]} todo
|
|
64
|
+
*/
|
|
65
|
+
export function showTodo(todo) {
|
|
66
|
+
if (todo.length === 0) return;
|
|
67
|
+
|
|
68
|
+
p.note(formatFindings(todo), "Left to do after the migration");
|
|
69
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { existsSync, globSync } from "node:fs";
|
|
2
|
+
import { join, sep } from "node:path";
|
|
3
|
+
import { readJSON } from "#utils/fs.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} Dependency
|
|
7
|
+
* @property {string} name
|
|
8
|
+
* @property {Record<string, any>} manifest
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Packages with the shape of a v1 addon that work without ember-cli
|
|
13
|
+
*/
|
|
14
|
+
const WORKS_WITHOUT_EMBER_CLI = new Set(["@embroider/macros"]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ember-cli runs v1 addons as part of its build.
|
|
18
|
+
* ember.nvp builds without ember-cli, so a v1 addon never loads.
|
|
19
|
+
*
|
|
20
|
+
* @param {Record<string, any>} manifest
|
|
21
|
+
*/
|
|
22
|
+
export function isV1Addon(manifest) {
|
|
23
|
+
return (
|
|
24
|
+
Boolean(manifest.keywords?.includes("ember-addon")) &&
|
|
25
|
+
manifest["ember-addon"]?.version !== 2 &&
|
|
26
|
+
!WORKS_WITHOUT_EMBER_CLI.has(manifest.name)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The manifests of the dependencies that are installed in the project.
|
|
32
|
+
*
|
|
33
|
+
* A migration decides from the project's own files.
|
|
34
|
+
* An install is extra: when there is one, it tells which dependencies are v1 addons,
|
|
35
|
+
* and which modules the addons add to an app.
|
|
36
|
+
*
|
|
37
|
+
* Only the top level of node_modules is read.
|
|
38
|
+
* Every package manager puts a project's direct dependencies there.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} directory the project
|
|
41
|
+
* @param {Record<string, string>} dependencies the dependencies to look for, by name
|
|
42
|
+
* @returns {Dependency[]} empty when the project has no node_modules
|
|
43
|
+
*/
|
|
44
|
+
export function installedDependencies(directory, dependencies) {
|
|
45
|
+
let modules = join(directory, "node_modules");
|
|
46
|
+
|
|
47
|
+
if (!existsSync(modules)) return [];
|
|
48
|
+
|
|
49
|
+
/** @type {Dependency[]} */
|
|
50
|
+
let found = [];
|
|
51
|
+
|
|
52
|
+
for (let folder of globSync(["*", "@*/*"], { cwd: modules })) {
|
|
53
|
+
let name = folder.split(sep).join("/");
|
|
54
|
+
|
|
55
|
+
if (!(name in dependencies)) continue;
|
|
56
|
+
|
|
57
|
+
let manifest = readJSON(join(modules, folder, "package.json"));
|
|
58
|
+
|
|
59
|
+
if (manifest) found.push({ name, manifest });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {Dependency[]} dependencies
|
|
67
|
+
* @returns {string[]} such as `ember-power-select@6.0.1`
|
|
68
|
+
*/
|
|
69
|
+
export function v1AddonsIn(dependencies) {
|
|
70
|
+
/** @type {string[]} */
|
|
71
|
+
let v1 = [];
|
|
72
|
+
|
|
73
|
+
for (let { name, manifest } of dependencies) {
|
|
74
|
+
if (isV1Addon(manifest)) v1.push(`${name}@${manifest.version}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return v1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {object} AppModule
|
|
82
|
+
* @property {string} addon the package that provides it
|
|
83
|
+
* @property {string} path such as `services/session`
|
|
84
|
+
* @property {string | undefined} specifier the module to import it from
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Modules that v2 addons merge into an app through `ember-addon.app-js`.
|
|
89
|
+
*
|
|
90
|
+
* `@embroider/compat` merges them.
|
|
91
|
+
* An ember.nvp app does not, so the app must register the ones it looks up by name.
|
|
92
|
+
*
|
|
93
|
+
* @param {Dependency[]} dependencies
|
|
94
|
+
* @returns {AppModule[]}
|
|
95
|
+
*/
|
|
96
|
+
export function appModulesIn(dependencies) {
|
|
97
|
+
/** @type {AppModule[]} */
|
|
98
|
+
let modules = [];
|
|
99
|
+
|
|
100
|
+
for (let { name, manifest } of dependencies) {
|
|
101
|
+
let appJs = manifest["ember-addon"]?.["app-js"] ?? {};
|
|
102
|
+
|
|
103
|
+
for (let [appPath, file] of Object.entries(appJs)) {
|
|
104
|
+
modules.push({
|
|
105
|
+
addon: name,
|
|
106
|
+
path: appPath.replace(/^\.\//, "").replace(/\.\w+$/, ""),
|
|
107
|
+
specifier: exportedAs(name, manifest, String(file)),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return modules;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The specifier that reaches `target` through the package's `exports`.
|
|
117
|
+
*
|
|
118
|
+
* exports: { "./*": "./dist/*.js" }, target: ./dist/_app_/services/x.js
|
|
119
|
+
* → pkg/_app_/services/x
|
|
120
|
+
*
|
|
121
|
+
* @param {string} name
|
|
122
|
+
* @param {Record<string, any>} manifest
|
|
123
|
+
* @param {string} target a file in the package, such as `./dist/_app_/services/x.js`
|
|
124
|
+
* @returns {string | undefined}
|
|
125
|
+
*/
|
|
126
|
+
function exportedAs(name, manifest, target) {
|
|
127
|
+
let exports = manifest.exports;
|
|
128
|
+
|
|
129
|
+
if (!exports) return `${name}/${target.replace(/^\.\//, "")}`;
|
|
130
|
+
if (typeof exports !== "object") return;
|
|
131
|
+
|
|
132
|
+
for (let [key, value] of Object.entries(exports)) {
|
|
133
|
+
let path = typeof value === "string" ? value : (value?.import ?? value?.default);
|
|
134
|
+
|
|
135
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
136
|
+
|
|
137
|
+
if (!path.includes("*")) {
|
|
138
|
+
if (path === target) return `${name}/${key.replace(/^\.\//, "")}`;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let [before = "", after = ""] = path.split("*");
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
target.startsWith(before) &&
|
|
146
|
+
target.endsWith(after) &&
|
|
147
|
+
target.length >= before.length + after.length
|
|
148
|
+
) {
|
|
149
|
+
let matched = target.slice(before.length, target.length - after.length);
|
|
150
|
+
|
|
151
|
+
return `${name}/${key.replace(/^\.\//, "").replace("*", matched)}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|