ember.nvp 1.14.0 → 1.16.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.
Files changed (35) hide show
  1. package/.release-plan.json +37 -5
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +28 -0
  4. package/package.json +2 -1
  5. package/packages/rolldown/package.json +2 -2
  6. package/packages/rolldown/src/transform.test.ts +3 -3
  7. package/packages/vite/package.json +4 -4
  8. package/packages/vite/src/ember.test.ts +38 -0
  9. package/packages/vite/src/ember.ts +38 -5
  10. package/src/bases/minimal-app/files/package.json +2 -2
  11. package/src/bases/minimal-extension/files/package.json +2 -2
  12. package/src/cli/generator.js +10 -0
  13. package/src/cli/index.js +19 -2
  14. package/src/cli/questions/layers.js +3 -2
  15. package/src/cli/questions/migration.js +69 -0
  16. package/src/index.js +1 -0
  17. package/src/layers/qunit/index.js +1 -1
  18. package/src/migration-from/addons.js +154 -0
  19. package/src/migration-from/app.js +1117 -0
  20. package/src/migration-from/at-ember/addon-blueprint/index.js +515 -0
  21. package/src/migration-from/at-ember/addon-blueprint/rollup.js +188 -0
  22. package/src/migration-from/at-ember/app-blueprint/index.js +60 -0
  23. package/src/migration-from/at-ember-tooling/classic-build-addon-blueprint/index.js +601 -0
  24. package/src/migration-from/at-ember-tooling/classic-build-app-blueprint/index.js +118 -0
  25. package/src/migration-from/babel.js +207 -0
  26. package/src/migration-from/ember-cli-build.js +238 -0
  27. package/src/migration-from/files.js +120 -0
  28. package/src/migration-from/findings.js +69 -0
  29. package/src/migration-from/index.js +81 -0
  30. package/src/migration-from/javascript.js +217 -0
  31. package/src/migration-from/layers.js +170 -0
  32. package/src/migration-from/library.js +311 -0
  33. package/src/utils/types.ts +69 -0
  34. package/test/ember.nvp/migration-from-fixtures.ts +506 -0
  35. package/test/ember.nvp/migration-from.test.ts +475 -0
@@ -2,13 +2,17 @@
2
2
  "solution": {
3
3
  "ember.nvp": {
4
4
  "impact": "minor",
5
- "oldVersion": "1.13.0",
6
- "newVersion": "1.14.0",
5
+ "oldVersion": "1.15.0",
6
+ "newVersion": "1.16.0",
7
7
  "tagName": "latest",
8
8
  "constraints": [
9
9
  {
10
10
  "impact": "minor",
11
11
  "reason": "Appears in changelog section :rocket: Enhancement"
12
+ },
13
+ {
14
+ "impact": "patch",
15
+ "reason": "Appears in changelog section :bug: Bug Fix"
12
16
  }
13
17
  ],
14
18
  "pkgJSONPath": "./package.json"
@@ -17,11 +21,39 @@
17
21
  "oldVersion": "1.2.0"
18
22
  },
19
23
  "@nullvoxpopuli/ember-rolldown": {
20
- "oldVersion": "2.10.0"
24
+ "impact": "minor",
25
+ "oldVersion": "2.10.0",
26
+ "newVersion": "2.11.0",
27
+ "tagName": "latest",
28
+ "constraints": [
29
+ {
30
+ "impact": "minor",
31
+ "reason": "Appears in changelog section :rocket: Enhancement"
32
+ },
33
+ {
34
+ "impact": "patch",
35
+ "reason": "Appears in changelog section :bug: Bug Fix"
36
+ }
37
+ ],
38
+ "pkgJSONPath": "./packages/rolldown/package.json"
21
39
  },
22
40
  "@nullvoxpopuli/ember-vite": {
23
- "oldVersion": "1.2.0"
41
+ "impact": "minor",
42
+ "oldVersion": "1.2.0",
43
+ "newVersion": "1.3.0",
44
+ "tagName": "latest",
45
+ "constraints": [
46
+ {
47
+ "impact": "minor",
48
+ "reason": "Appears in changelog section :rocket: Enhancement"
49
+ },
50
+ {
51
+ "impact": "patch",
52
+ "reason": "Appears in changelog section :bug: Bug Fix"
53
+ }
54
+ ],
55
+ "pkgJSONPath": "./packages/vite/package.json"
24
56
  }
25
57
  },
26
- "description": "## Release (2026-09-30)\n\n* ember.nvp 1.14.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#156](https://github.com/NullVoxPopuli/ember.nvp/pull/156) Give TypeScript libraries with tests a publish tsconfig ([@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"
58
+ "description": "## Release (2026-10-12)\n\n* ember.nvp 1.16.0 (minor)\n* @nullvoxpopuli/ember-rolldown 2.11.0 (minor)\n* @nullvoxpopuli/ember-vite 1.3.0 (minor)\n\n#### :rocket: Enhancement\n* `@nullvoxpopuli/ember-rolldown`, `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#163](https://github.com/NullVoxPopuli/ember.nvp/pull/163) Update the embroider dependencies, give the config hook its plugin context ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### :bug: Bug Fix\n* `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#162](https://github.com/NullVoxPopuli/ember.nvp/pull/162) Find the embroider config plugin by name ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n* `@nullvoxpopuli/ember-rolldown`\n * [#160](https://github.com/NullVoxPopuli/ember.nvp/pull/160) Fix macOS tests ([@tcjr](https://github.com/tcjr))\n\n#### Committers: 2\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n- Tom Carter ([@tcjr](https://github.com/tcjr))\n"
27
59
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## Release (2026-10-12)
4
+
5
+ * ember.nvp 1.16.0 (minor)
6
+ * @nullvoxpopuli/ember-rolldown 2.11.0 (minor)
7
+ * @nullvoxpopuli/ember-vite 1.3.0 (minor)
8
+
9
+ #### :rocket: Enhancement
10
+ * `@nullvoxpopuli/ember-rolldown`, `@nullvoxpopuli/ember-vite`, `ember.nvp`
11
+ * [#163](https://github.com/NullVoxPopuli/ember.nvp/pull/163) Update the embroider dependencies, give the config hook its plugin context ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
12
+
13
+ #### :bug: Bug Fix
14
+ * `@nullvoxpopuli/ember-vite`, `ember.nvp`
15
+ * [#162](https://github.com/NullVoxPopuli/ember.nvp/pull/162) Find the embroider config plugin by name ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
16
+ * `@nullvoxpopuli/ember-rolldown`
17
+ * [#160](https://github.com/NullVoxPopuli/ember.nvp/pull/160) Fix macOS tests ([@tcjr](https://github.com/tcjr))
18
+
19
+ #### Committers: 2
20
+ - @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
21
+ - Tom Carter ([@tcjr](https://github.com/tcjr))
22
+
23
+ ## Release (2026-10-02)
24
+
25
+ * ember.nvp 1.15.0 (minor)
26
+
27
+ #### :rocket: Enhancement
28
+ * `ember.nvp`
29
+ * [#158](https://github.com/NullVoxPopuli/ember.nvp/pull/158) Migrate projects from the older blueprints ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
30
+
31
+ #### Committers: 1
32
+ - @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
33
+
3
34
  ## Release (2026-09-30)
4
35
 
5
36
  * 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.14.0",
3
+ "version": "1.16.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"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-rolldown",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "Meta plugin for building ember v2 libraries/addons with rolldown & tsdown.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@babel/core": "^7.28.10 || ^8.0.0",
47
47
  "@babel/plugin-transform-typescript": "^7.28.10 || ^8.0.0",
48
- "@embroider/core": "^4.4.2",
48
+ "@embroider/core": "^4.6.9",
49
49
  "@jridgewell/remapping": "^2.3.5",
50
50
  "@nullvoxpopuli/ember-build-tooling-utils": "workspace:*",
51
51
  "babel-plugin-ember-template-compilation": "^4.0.0",
@@ -1,6 +1,6 @@
1
1
  import { originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
2
2
  import { existsSync } from "node:fs";
3
- import { mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { mkdtemp, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import path from "node:path";
6
6
  import { rolldown } from "rolldown";
@@ -38,7 +38,7 @@ async function generate(
38
38
  files: Record<string, string>,
39
39
  { input = ["index.ts"], plugins = () => [], sourcemap = false }: BuildOptions = {},
40
40
  ): Promise<Build> {
41
- const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-build-"));
41
+ const dir = await realpath(await mkdtemp(path.join(tmpdir(), "ember-rolldown-build-")));
42
42
 
43
43
  for (const [relative, source] of Object.entries(files)) {
44
44
  const full = path.join(dir, relative);
@@ -347,7 +347,7 @@ describe("emberTransform (full plugin via rolldown)", () => {
347
347
  });
348
348
 
349
349
  it("emits sourcemaps for the specifier rewrite (no SOURCEMAP_BROKEN warnings)", async () => {
350
- const dir = await mkdtemp(path.join(tmpdir(), "ember-rolldown-map-"));
350
+ const dir = await realpath(await mkdtemp(path.join(tmpdir(), "ember-rolldown-map-")));
351
351
 
352
352
  const files = {
353
353
  // index.ts contains a `.gts` specifier, so it goes through the rewrite.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-vite",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Meta plugin for ember projects focusing on speed.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -26,9 +26,9 @@
26
26
  "packageManager": "pnpm@10.28.0",
27
27
  "dependencies": {
28
28
  "@babel/core": "^7.28.10 || ^8.0.0",
29
- "@embroider/core": "^4.4.2",
30
- "@embroider/macros": "^1.20.3",
31
- "@embroider/vite": "^1.5.0",
29
+ "@embroider/core": "^4.6.9",
30
+ "@embroider/macros": "^1.21.3",
31
+ "@embroider/vite": "^1.7.15",
32
32
  "@nullvoxpopuli/ember-build-tooling-utils": "workspace:*"
33
33
  },
34
34
  "engines": {
@@ -0,0 +1,38 @@
1
+ import { extensions } from "@embroider/vite";
2
+ import type { ConfigEnv, ConfigPluginContext } from "vite";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { ember } from "./ember.ts";
6
+
7
+ const ROLLDOWN = { meta: { rolldownVersion: "1.0.0" } } as ConfigPluginContext;
8
+
9
+ async function configure(env: ConfigEnv) {
10
+ const config: any = {};
11
+ const [plugin] = ember();
12
+
13
+ // @ts-expect-error the first entry is the config plugin
14
+ await plugin.config.call(ROLLDOWN, config, env);
15
+
16
+ return config;
17
+ }
18
+
19
+ describe("ember", () => {
20
+ /**
21
+ * These values come from the config hook of `@embroider/vite`.
22
+ * They are missing when that hook does not run.
23
+ */
24
+ it("applies the config from @embroider/vite", async () => {
25
+ const config = await configure({ command: "build", mode: "production" });
26
+
27
+ expect(config.resolve.extensions).toEqual(extensions);
28
+ expect(config.server.port).toBe(4200);
29
+ expect(config.optimizeDeps.exclude).toContain("@embroider/macros");
30
+ });
31
+
32
+ it("gives @embroider/vite the plugin context, so that it configures rolldown", async () => {
33
+ const config = await configure({ command: "build", mode: "production" });
34
+
35
+ expect(config.build.rolldownOptions.moduleTypes).toEqual({ ".ts": "js" });
36
+ expect(config.optimizeDeps.rolldownOptions.resolve.extensions).toEqual(extensions);
37
+ });
38
+ });
@@ -2,13 +2,43 @@ import { buildMacros } from "@embroider/macros/babel";
2
2
  import { ember as embroiderEmber, extensions, resolver, templateTag } from "@embroider/vite";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
- import type { ConfigEnv, ResolvedConfig } from "vite";
5
+ import type { ConfigEnv, ConfigPluginContext, ResolvedConfig, UserConfig } from "vite";
6
6
 
7
7
  import { maybeBabel } from "@nullvoxpopuli/ember-build-tooling-utils";
8
8
 
9
9
  import { testHtml } from "./test-html.ts";
10
10
 
11
- const emberConfig = embroiderEmber()[3]!;
11
+ const EMBER_CONFIG = "vite-plugin-ember-config";
12
+
13
+ type ConfigHook = (
14
+ this: ConfigPluginContext,
15
+ config: UserConfig,
16
+ env: ConfigEnv,
17
+ ) => Promise<unknown> | unknown;
18
+
19
+ /**
20
+ * `@embroider/vite` does not export its config hook on its own.
21
+ *
22
+ * The position of the plugin in the list changes between releases,
23
+ * so the name is the only stable way to find it.
24
+ */
25
+ function findEmberConfig(): ConfigHook {
26
+ for (const plugin of embroiderEmber()) {
27
+ if (plugin.name !== EMBER_CONFIG) continue;
28
+
29
+ const hook = plugin.config;
30
+ const handler = typeof hook === "function" ? hook : hook?.handler;
31
+
32
+ if (handler) return handler as ConfigHook;
33
+ }
34
+
35
+ throw new Error(
36
+ `@embroider/vite has no "${EMBER_CONFIG}" plugin with a config hook. ` +
37
+ `This version of @embroider/vite is not supported by @nullvoxpopuli/ember-vite.`,
38
+ );
39
+ }
40
+
41
+ const emberConfig = findEmberConfig();
12
42
 
13
43
  const cwd = process.cwd();
14
44
  function absolutePath(relativePath: string) {
@@ -173,9 +203,12 @@ export function ember(nvpConfig: Config = {}) {
173
203
  {
174
204
  // no compat
175
205
  name: "ember:nullvoxpopuli",
176
- async config(viteConfig: any, env: ConfigEnv) {
177
- // @ts-expect-error it exists
178
- await emberConfig.config(viteConfig, env);
206
+ async config(this: ConfigPluginContext, viteConfig: any, env: ConfigEnv) {
207
+ /**
208
+ * The hook reads the rolldown version from the plugin context,
209
+ * and takes its esbuild path without it.
210
+ */
211
+ await emberConfig.call(this, viteConfig, env);
179
212
 
180
213
  viteConfig.experimental ||= {};
181
214
  viteConfig.build ||= {};
@@ -25,7 +25,7 @@
25
25
  "start": "vite"
26
26
  },
27
27
  "dependencies": {
28
- "@embroider/macros": "^1.20.3",
28
+ "@embroider/macros": "^1.21.3",
29
29
  "@embroider/router": "^3.0.6",
30
30
  "@glimmer/component": "^2.1.1",
31
31
  "decorator-transforms": "^2.3.2",
@@ -37,7 +37,7 @@
37
37
  "@babel/plugin-transform-typescript": "^7.29.7",
38
38
  "@babel/runtime": "^7.29.7",
39
39
  "@ember/app-tsconfig": "^2.0.0",
40
- "@embroider/core": "^4.4.2",
40
+ "@embroider/core": "^4.6.9",
41
41
  "@glint/ember-tsc": "^1.7.5",
42
42
  "@glint/template": "^1.7.7",
43
43
  "@glint/tsserver-plugin": "^2.5.5",
@@ -26,7 +26,7 @@
26
26
  "start": "vite"
27
27
  },
28
28
  "dependencies": {
29
- "@embroider/macros": "^1.20.3",
29
+ "@embroider/macros": "^1.21.3",
30
30
  "@embroider/router": "^3.0.6",
31
31
  "@glimmer/component": "^2.1.1",
32
32
  "decorator-transforms": "^2.3.2",
@@ -38,7 +38,7 @@
38
38
  "@babel/plugin-transform-typescript": "^7.29.7",
39
39
  "@babel/runtime": "^7.29.7",
40
40
  "@ember/app-tsconfig": "^2.0.0",
41
- "@embroider/core": "^4.4.2",
41
+ "@embroider/core": "^4.6.9",
42
42
  "@glint/ember-tsc": "^1.7.5",
43
43
  "@glint/template": "^1.7.7",
44
44
  "@glint/tsserver-plugin": "^2.5.5",
@@ -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 selectedLayers = await askLayers(projectType);
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
@@ -1,3 +1,4 @@
1
1
  export { generateProject } from "./cli/generator.js";
2
2
  export { Project } from "./utils/project.js";
3
3
  export { Stage } from "./utils/stage.js";
4
+ export { checkMigration, formatFindings, MigrationError } from "./migration-from/index.js";
@@ -29,7 +29,7 @@ const tsDeps = {
29
29
  */
30
30
  const libraryDeps = {
31
31
  ...deps,
32
- "@embroider/macros": "^1.20.3",
32
+ "@embroider/macros": "^1.21.3",
33
33
  "@nullvoxpopuli/ember-vite": "workspace:*",
34
34
  "babel-plugin-ember-template-compilation": "^4.0.0",
35
35
  vite: "^8.0.14",