ember.nvp 0.3.0 → 0.5.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.
@@ -2,8 +2,8 @@
2
2
  "solution": {
3
3
  "ember.nvp": {
4
4
  "impact": "minor",
5
- "oldVersion": "0.2.1",
6
- "newVersion": "0.3.0",
5
+ "oldVersion": "0.4.0",
6
+ "newVersion": "0.5.0",
7
7
  "tagName": "latest",
8
8
  "constraints": [
9
9
  {
@@ -12,28 +12,14 @@
12
12
  },
13
13
  {
14
14
  "impact": "patch",
15
- "reason": "Appears in changelog section :bug: Bug Fix"
15
+ "reason": "Appears in changelog section :house: Internal"
16
16
  }
17
17
  ],
18
18
  "pkgJSONPath": "./package.json"
19
19
  },
20
20
  "@nullvoxpopuli/ember-vite": {
21
- "impact": "minor",
22
- "oldVersion": "1.0.0",
23
- "newVersion": "1.1.0",
24
- "tagName": "latest",
25
- "constraints": [
26
- {
27
- "impact": "minor",
28
- "reason": "Appears in changelog section :rocket: Enhancement"
29
- },
30
- {
31
- "impact": "patch",
32
- "reason": "Appears in changelog section :bug: Bug Fix"
33
- }
34
- ],
35
- "pkgJSONPath": "./packages/vite/package.json"
21
+ "oldVersion": "1.0.3"
36
22
  }
37
23
  },
38
- "description": "## Release (2026-06-01)\n\n* ember.nvp 0.3.0 (minor)\n* @nullvoxpopuli/ember-vite 1.1.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`, `@nullvoxpopuli/ember-vite`\n * [#32](https://github.com/NullVoxPopuli/ember.nvp/pull/32) Add fast vite plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* `ember.nvp`\n * [#24](https://github.com/NullVoxPopuli/ember.nvp/pull/24) Add .gitignore to git layer ([@tcjr](https://github.com/tcjr))\n\n#### :bug: Bug Fix\n* `@nullvoxpopuli/ember-vite`, `ember.nvp`\n * [#35](https://github.com/NullVoxPopuli/ember.nvp/pull/35) Fix CI failures on the fast vite plugin PR ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n\n#### Committers: 3\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- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n"
24
+ "description": "## Release (2026-06-29)\n\n* ember.nvp 0.5.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#55](https://github.com/NullVoxPopuli/ember.nvp/pull/55) Upgrade @clack/prompts ([@tcjr](https://github.com/tcjr))\n\n#### :house: Internal\n* `ember.nvp`\n * [#53](https://github.com/NullVoxPopuli/ember.nvp/pull/53) Add typescript to root ([@tcjr](https://github.com/tcjr))\n\n#### Committers: 1\n- Tom Carter ([@tcjr](https://github.com/tcjr))\n"
39
25
  }
package/CHANGELOG.md CHANGED
@@ -1,23 +1,113 @@
1
1
  # Changelog
2
2
 
3
- ## Release (2026-06-01)
3
+ ## Release (2026-06-29)
4
+
5
+ * ember.nvp 0.5.0 (minor)
6
+
7
+ #### :rocket: Enhancement
8
+ * `ember.nvp`
9
+ * [#55](https://github.com/NullVoxPopuli/ember.nvp/pull/55) Upgrade @clack/prompts ([@tcjr](https://github.com/tcjr))
10
+
11
+ #### :house: Internal
12
+ * `ember.nvp`
13
+ * [#53](https://github.com/NullVoxPopuli/ember.nvp/pull/53) Add typescript to root ([@tcjr](https://github.com/tcjr))
14
+
15
+ #### Committers: 1
16
+ - Tom Carter ([@tcjr](https://github.com/tcjr))
17
+
18
+ ## Release (2026-06-23)
19
+
20
+ * ember.nvp 0.4.0 (minor)
21
+
22
+ #### :rocket: Enhancement
23
+ * `ember.nvp`
24
+ * [#48](https://github.com/NullVoxPopuli/ember.nvp/pull/48) replaceOrUpdate ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
25
+
26
+ #### :bug: Bug Fix
27
+ * `ember.nvp`
28
+ * [#52](https://github.com/NullVoxPopuli/ember.nvp/pull/52) Fix test-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
29
+
30
+ #### :house: Internal
31
+ * `ember.nvp`
32
+ * [#50](https://github.com/NullVoxPopuli/ember.nvp/pull/50) Tests for qunit ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
33
+ * [#49](https://github.com/NullVoxPopuli/ember.nvp/pull/49) Prepare Release ([@github-actions[bot]](https://github.com/apps/github-actions))
34
+
35
+ #### Committers: 2
36
+ - GitHub Actions [Bot] ([@github-actions](https://github.com/apps/github-actions))
37
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
38
+
39
+ ## Release (2026-06-22)
4
40
 
5
41
  * ember.nvp 0.3.0 (minor)
6
- * @nullvoxpopuli/ember-vite 1.1.0 (minor)
7
42
 
8
43
  #### :rocket: Enhancement
9
- * `ember.nvp`, `@nullvoxpopuli/ember-vite`
10
- * [#32](https://github.com/NullVoxPopuli/ember.nvp/pull/32) Add fast vite plugin ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
11
44
  * `ember.nvp`
12
- * [#24](https://github.com/NullVoxPopuli/ember.nvp/pull/24) Add .gitignore to git layer ([@tcjr](https://github.com/tcjr))
45
+ * [#48](https://github.com/NullVoxPopuli/ember.nvp/pull/48) replaceOrUpdate ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
46
+
47
+ #### Committers: 1
48
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
49
+
50
+ ## Release (2026-06-22)
51
+
52
+ * ember.nvp 0.2.6 (patch)
53
+ * @nullvoxpopuli/ember-vite 1.0.3 (patch)
13
54
 
14
55
  #### :bug: Bug Fix
15
56
  * `@nullvoxpopuli/ember-vite`, `ember.nvp`
16
- * [#35](https://github.com/NullVoxPopuli/ember.nvp/pull/35) Fix CI failures on the fast vite plugin PR ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
57
+ * [#46](https://github.com/NullVoxPopuli/ember.nvp/pull/46) compile ts authored packages ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
17
58
 
18
- #### Committers: 3
19
- - @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))
20
- - Tom Carter ([@tcjr](https://github.com/tcjr))
59
+ #### Committers: 1
60
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
61
+
62
+ ## Release (2026-06-22)
63
+
64
+ * ember.nvp 0.2.5 (patch)
65
+
66
+ #### :bug: Bug Fix
67
+ * `ember.nvp`
68
+ * [#44](https://github.com/NullVoxPopuli/ember.nvp/pull/44) Fix new git ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
69
+
70
+ #### Committers: 1
71
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
72
+
73
+ ## Release (2026-06-22)
74
+
75
+ * ember.nvp 0.2.4 (patch)
76
+
77
+ #### :bug: Bug Fix
78
+ * `ember.nvp`
79
+ * [#42](https://github.com/NullVoxPopuli/ember.nvp/pull/42) fix falsey depndencies ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
80
+
81
+ #### Committers: 1
82
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
83
+
84
+ ## Release (2026-06-22)
85
+
86
+ * ember.nvp 0.2.3 (patch)
87
+ * @nullvoxpopuli/ember-vite 1.0.2 (patch)
88
+
89
+ #### :bug: Bug Fix
90
+ * `@nullvoxpopuli/ember-vite`, `ember.nvp`
91
+ * [#41](https://github.com/NullVoxPopuli/ember.nvp/pull/41) Fix version range usage ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
92
+ * `ember.nvp`
93
+ * [#40](https://github.com/NullVoxPopuli/ember.nvp/pull/40) Ensure imports exist ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
94
+
95
+ #### Committers: 1
96
+ - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
97
+
98
+ ## Release (2026-06-01)
99
+
100
+ * ember.nvp 0.2.2 (patch)
101
+ * @nullvoxpopuli/ember-vite 1.0.1 (patch)
102
+
103
+ #### :house: Internal
104
+ * `@nullvoxpopuli/ember-vite`
105
+ * [#36](https://github.com/NullVoxPopuli/ember.nvp/pull/36) Fix npm provenance failure for @nullvoxpopuli/ember-vite publish ([@Copilot](https://github.com/apps/copilot-swe-agent))
106
+ * `ember.nvp`, `@nullvoxpopuli/ember-vite`
107
+ * [#37](https://github.com/NullVoxPopuli/ember.nvp/pull/37) Revert "Prepare Release" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
108
+
109
+ #### Committers: 2
110
+ - Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))
21
111
  - [@NullVoxPopuli](https://github.com/NullVoxPopuli)
22
112
 
23
113
  ## Release (2026-05-15)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember.nvp",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",
@@ -19,7 +19,7 @@
19
19
  "exports": "./src/index.js",
20
20
  "bin": "./src/cli/index.js",
21
21
  "dependencies": {
22
- "@clack/prompts": "^0.11.0",
22
+ "@clack/prompts": "^1.6.0",
23
23
  "babel-remove-types": "^2.0.0",
24
24
  "content-tag": "^4.2.0",
25
25
  "ember-apply": "^2.17.0",
@@ -39,6 +39,7 @@
39
39
  "prettier-plugin-ember-template-tag": "^2.1.6",
40
40
  "release-plan": "^0.18.0",
41
41
  "rimraf": "^6.1.3",
42
+ "typescript": "^5.9.3",
42
43
  "vite": "^8.0.14",
43
44
  "vitest": "^4.1.7"
44
45
  },
@@ -31,6 +31,12 @@ Then delete from your package.json: `@rollup/plugin-babel`, `@embroider/core`, `
31
31
  > [!NOTE]
32
32
  > babel related deps are still needed for linting, so don't delete those
33
33
 
34
+ ## Requirements
35
+
36
+ - `type=module` in your ember app
37
+ - babel config must be named `babel.config.js`
38
+ - `@embroider/vite` is up to date~ish
39
+
34
40
  ## Configuration
35
41
 
36
42
  The `ember()` plugin takes the following options:
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@nullvoxpopuli/ember-vite",
3
- "version": "1.1.0",
3
+ "version": "1.0.3",
4
4
  "description": "Meta plugin for ember projects focusing on speed.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
+ "publishConfig": {
8
+ "main": "dist/index.js"
9
+ },
7
10
  "keywords": [
8
11
  "vite",
9
12
  "ember",
@@ -11,9 +14,18 @@
11
14
  ],
12
15
  "author": "NullVoxPopuli",
13
16
  "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/NullVoxPopuli/ember.nvp",
20
+ "directory": "packages/vite"
21
+ },
22
+ "scripts": {
23
+ "build": "rolldown --config",
24
+ "prepack": "pnpm build"
25
+ },
14
26
  "packageManager": "pnpm@10.28.0",
15
27
  "dependencies": {
16
- "@babel/core": "^7.28.6",
28
+ "@babel/core": "^7.28.10",
17
29
  "@embroider/core": "^4.4.2",
18
30
  "@embroider/vite": "^1.5.0",
19
31
  "@rollup/plugin-babel": "^7.1.0",
@@ -25,6 +37,7 @@
25
37
  "node": ">= 24"
26
38
  },
27
39
  "devDependencies": {
40
+ "rolldown": "^1.1.2",
28
41
  "typescript": "^5.9.3",
29
42
  "vite": "^8.0.14"
30
43
  }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from "rolldown";
2
+
3
+ import manifest from "./package.json" with { type: "json" };
4
+
5
+ const deps = Object.keys(manifest.dependencies || {});
6
+
7
+ export default defineConfig({
8
+ input: "index.ts",
9
+ output: {
10
+ format: "esm",
11
+ file: "dist/index.js",
12
+ codeSplitting: false,
13
+ },
14
+ external(id) {
15
+ if (id.startsWith("node:")) return true;
16
+ if (deps.some((dep) => id.startsWith(dep))) return true;
17
+ },
18
+ });
@@ -91,6 +91,7 @@ async function makeJavaScript(project) {
91
91
  await project.removeFile("tsconfig.json");
92
92
 
93
93
  await packageJson.modify((json) => {
94
+ json.imports ||= {};
94
95
  json.imports["#config"] = "./app/config.js";
95
96
  }, project.directory);
96
97
  }
@@ -105,7 +106,11 @@ async function upgradeDependencies(project) {
105
106
  let existing = await packageJson.read(project.directory);
106
107
 
107
108
  await packageJson.modify(async (json) => {
108
- Object.assign(json.dependencies, await getLatest(existing.dependencies));
109
- Object.assign(json.devDependencies, await getLatest(existing.devDependencies));
109
+ if (json.dependencies) {
110
+ Object.assign(json.dependencies, await getLatest(existing.dependencies));
111
+ }
112
+ if (json.devDependencies) {
113
+ Object.assign(json.devDependencies, await getLatest(existing.devDependencies));
114
+ }
110
115
  }, project.directory);
111
116
  }
package/src/cli/args.js CHANGED
@@ -30,10 +30,15 @@ const { values } = parseArgs({
30
30
  type: "string",
31
31
  choices: ["npm", "pnpm"],
32
32
  },
33
+
34
+ replaceOrUpdate: {
35
+ type: "string",
36
+ choices: ["replace", "update"],
37
+ },
33
38
  },
34
39
  });
35
40
 
36
- const { name, type, layers = [], packageManager, path, confirm } = values;
41
+ const { replaceOrUpdate, name, type, layers = [], packageManager, path, confirm } = values;
37
42
 
38
43
  export const answers = {
39
44
  name,
@@ -42,6 +47,7 @@ export const answers = {
42
47
  packageManager,
43
48
  path,
44
49
  confirm,
50
+ replaceOrUpdate,
45
51
  };
46
52
 
47
53
  /**
@@ -1,4 +1,4 @@
1
- import { mkdir } from "node:fs/promises";
1
+ import { mkdir, rm } from "node:fs/promises";
2
2
 
3
3
  import baseApp from "#bases/minimal-app";
4
4
  import baseLibrary from "#bases/minimal-library";
@@ -8,8 +8,13 @@ import { hasGit } from "#utils/git.js";
8
8
  * Generate project files by running layer functions
9
9
  *
10
10
  * @param {import('#utils/project.js').Project} project
11
+ * @param {string} [replaceOrUpdate]
11
12
  */
12
- export async function generateProject(project) {
13
+ export async function generateProject(project, replaceOrUpdate) {
14
+ if (replaceOrUpdate === "replace") {
15
+ await rm(project.directory, { recursive: true });
16
+ }
17
+
13
18
  await mkdir(project.directory, { recursive: true });
14
19
 
15
20
  switch (project.desires.type) {
package/src/cli/index.js CHANGED
@@ -10,6 +10,7 @@ import { askPath } from "./questions/path.js";
10
10
  import { askIfOK } from "./questions/ok.js";
11
11
  import { styleText } from "node:util";
12
12
  import { Project } from "#utils/project.js";
13
+ import { askReplaceOrUpdate } from "./questions/replace-or-update.js";
13
14
 
14
15
  /**
15
16
  * This whole file's primary purpose is to be an interactive CLI
@@ -25,6 +26,7 @@ async function main() {
25
26
 
26
27
  const projectName = await askName();
27
28
  const projectPath = await askPath(projectName);
29
+ const replaceOrUpdate = await askReplaceOrUpdate(projectPath);
28
30
  const projectType = await askProjectType();
29
31
  const selectedLayers = await askLayers();
30
32
  const packageManager = await askPackageManager();
@@ -44,7 +46,7 @@ async function main() {
44
46
 
45
47
  try {
46
48
  // Generate the project
47
- await generateProject(project);
49
+ await generateProject(project, replaceOrUpdate);
48
50
 
49
51
  s.stop("Project created!");
50
52
 
@@ -1,5 +1,4 @@
1
- import * as p from "@clack/prompts";
2
-
1
+ import { cancel, isCancel, text } from "@clack/prompts";
3
2
  import packageNameRegex from "package-name-regex";
4
3
  import { answers, printArgInUse } from "#args";
5
4
 
@@ -14,12 +13,12 @@ export async function askName() {
14
13
  return answers.name;
15
14
  }
16
15
  }
17
- const projectName = await p.text({
16
+ const projectName = await text({
18
17
  message: "What is your project name?",
19
18
  placeholder: DEFAULT,
20
19
  defaultValue: DEFAULT,
21
20
  validate(value) {
22
- if (value.length === 0) return;
21
+ if (!value || value.length === 0) return;
23
22
 
24
23
  let isValid = packageNameRegex.test(value);
25
24
 
@@ -29,8 +28,8 @@ export async function askName() {
29
28
  },
30
29
  });
31
30
 
32
- if (p.isCancel(projectName)) {
33
- p.cancel("Operation cancelled");
31
+ if (isCancel(projectName)) {
32
+ cancel("Operation cancelled");
34
33
  return process.exit(0);
35
34
  }
36
35
 
@@ -1,5 +1,4 @@
1
- import * as p from "@clack/prompts";
2
-
1
+ import { cancel, isCancel, text } from "@clack/prompts";
3
2
  import { join } from "node:path";
4
3
  import { answers, printArgInUse } from "#args";
5
4
  import { cwd } from "#utils/cwd.js";
@@ -29,12 +28,12 @@ export async function askPath(name) {
29
28
 
30
29
  const defaultValue = join(cwd, name);
31
30
 
32
- const answer = await p.text({
31
+ const answer = await text({
33
32
  message: "Where would you like to place your project?",
34
33
  placeholder: defaultValue.replace(cwd, "."),
35
34
  defaultValue: defaultValue,
36
35
  validate(value) {
37
- if (value.length === 0) return;
36
+ if (!value || value.length === 0) return;
38
37
 
39
38
  if (!isValid(value)) {
40
39
  return `Path must start with a '.' or '/' (be either a relative or absolute path)`;
@@ -42,8 +41,8 @@ export async function askPath(name) {
42
41
  },
43
42
  });
44
43
 
45
- if (p.isCancel(answer)) {
46
- p.cancel("Operation cancelled");
44
+ if (isCancel(answer)) {
45
+ cancel("Operation cancelled");
47
46
  return process.exit(0);
48
47
  }
49
48
 
@@ -0,0 +1,56 @@
1
+ import * as p from "@clack/prompts";
2
+
3
+ import { answers, printArgInUse } from "#args";
4
+ import { existsSync } from "node:fs";
5
+
6
+ /**
7
+ *
8
+ * @param {string | undefined} value
9
+ * @returns
10
+ */
11
+ function isValid(value) {
12
+ if (!value) return false;
13
+
14
+ return value === "replace" || value === "update";
15
+ }
16
+
17
+ /**
18
+ * @param {string} projectPath -- the path the project will be generated in
19
+ */
20
+ export async function askReplaceOrUpdate(projectPath) {
21
+ if (!existsSync(projectPath)) {
22
+ return;
23
+ }
24
+
25
+ if (answers.replaceOrUpdate) {
26
+ if (isValid(answers.replaceOrUpdate)) {
27
+ printArgInUse("replaceOrUpdate", answers.replaceOrUpdate);
28
+
29
+ return answers.replaceOrUpdate;
30
+ }
31
+ }
32
+
33
+ const answer = await p.select({
34
+ message: "Replace or update at the selected path",
35
+ initialValue: "update",
36
+ options: [
37
+ {
38
+ value: "update",
39
+ label: "update",
40
+ hint: "Updates the project in the target directory, if one exists",
41
+ },
42
+ {
43
+ value: "replace",
44
+ label: "replace",
45
+ hint: "Deletes the target directory and generates a new project",
46
+ },
47
+ ],
48
+ });
49
+
50
+ if (p.isCancel(answer)) {
51
+ p.cancel("Operation cancelled");
52
+ return process.exit(0);
53
+ }
54
+
55
+ return answer;
56
+ }
@@ -0,0 +1 @@
1
+ node_modules
@@ -1,7 +1,7 @@
1
- import { files } from "ember-apply";
2
1
  import { hasGit, initGit, isInGit } from "#utils/git.js";
3
2
  import { formatLabel } from "#utils/cli.js";
4
3
  import { join } from "node:path";
4
+ import { cp } from "node:fs/promises";
5
5
 
6
6
  export default {
7
7
  label: formatLabel("git init"),
@@ -24,8 +24,12 @@ export default {
24
24
 
25
25
  let initOk = initGit(project.directory);
26
26
  if (initOk) {
27
- await files.applyFolder(join(import.meta.dirname, "files"), project.directory);
27
+ let gitIgnorePath = join(import.meta.dirname, "files/gitignore");
28
+ let targetPath = join(project.directory, ".gitignore");
29
+
30
+ await cp(gitIgnorePath, targetPath, { force: true });
28
31
  }
32
+
29
33
  return initOk;
30
34
  },
31
35
 
@@ -12,9 +12,6 @@ import * as QUnit from "qunit";
12
12
  import { setup } from "qunit-dom";
13
13
  import { setupEmberOnerrorValidation, start as qunitStart } from "ember-qunit";
14
14
  import { setTesting } from "@embroider/macros";
15
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
16
- // @ts-expect-error
17
- import { getGlobalConfig } from "@embroider/macros/src/addon/runtime";
18
15
 
19
16
  import Application from "#app/app";
20
17
  import config from "#config";
@@ -43,19 +40,8 @@ export function start() {
43
40
  config.locationType = "none";
44
41
  config.APP.rootElement = "#ember-testing";
45
42
  config.APP.autoboot = false;
46
- setTesting(true);
47
-
48
- const theMacrosGlobal = getGlobalConfig();
49
-
50
- /**
51
- * Caveats:
52
- * - https://github.com/embroider-build/embroider/issues/2660
53
- * - https://github.com/embroider-build/embroider/issues/1998
54
- *
55
- */
56
- theMacrosGlobal["@embroider/macros"] ||= {};
57
- theMacrosGlobal["@embroider/macros"].isTesting ||= true;
58
43
 
44
+ setTesting(true);
59
45
  setApplication(Application.create(config.APP));
60
46
  setup(QUnit.assert);
61
47
  setupEmberOnerrorValidation();
package/src/utils/npm.js CHANGED
@@ -16,6 +16,7 @@ export async function getLatest(deps) {
16
16
  let results = await Promise.all(
17
17
  Object.entries(deps).map(async ([dep, range]) => {
18
18
  let existing = CACHE[dep]?.[range];
19
+
19
20
  if (existing) {
20
21
  return [dep, existing];
21
22
  }
@@ -37,6 +38,9 @@ export async function getLatest(deps) {
37
38
  if (needsLocalLink && dep === "@nullvoxpopuli/ember-vite") {
38
39
  version = "link:" + resolve(join(import.meta.dirname, "../../packages/vite"));
39
40
  } else {
41
+ if (range == "workspace:*") {
42
+ range = "latest";
43
+ }
40
44
  version = await latestVersion(dep, { version: range });
41
45
  }
42
46
 
@@ -0,0 +1,92 @@
1
+ import { describe, it, expect, afterAll } from "vitest";
2
+ import { generate, layers, expectIsSetup } from "#test-helpers";
3
+ import { generateProject } from "ember.nvp";
4
+ import { writeFile } from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { rimraf } from "rimraf";
8
+
9
+ /**
10
+ * The replace-or-update question only matters when a project already exists
11
+ * at the target path. These tests generate a project, assert the baseline
12
+ * state, then regenerate over it the way the CLI does once the user has
13
+ * answered the question, and assert the resulting project output is correct
14
+ * for each answer.
15
+ */
16
+ describe("generating over an existing project", () => {
17
+ const prettier = layers.find((layer) => layer.name === "prettier")!;
18
+ const dirs: string[] = [];
19
+
20
+ afterAll(async () => {
21
+ if (process.env.CI) return;
22
+
23
+ for (const dir of dirs) {
24
+ await rimraf(dir, { maxRetries: 3, retryDelay: 100 });
25
+ }
26
+ });
27
+
28
+ it("'update' regenerates in place, keeping unrelated files", async () => {
29
+ const project = await generate({ type: "app", layers: ["prettier"] });
30
+ dirs.push(project.directory);
31
+
32
+ const userFile = join(project.directory, "my-untracked-file.txt");
33
+ await writeFile(userFile, "the user added this");
34
+
35
+ // Baseline: a valid project exists, with the desired layer and the
36
+ // user's own file present.
37
+ expect(existsSync(join(project.directory, "package.json")), "baseline package.json").toBe(true);
38
+ expect(existsSync(userFile), "baseline user file").toBe(true);
39
+ await expectIsSetup(project, prettier);
40
+
41
+ await generateProject(project, "update");
42
+
43
+ // After update: the pre-existing project and the user's file are
44
+ // preserved, and it is still a valid, fully-set-up project.
45
+ expect(existsSync(userFile), "unrelated file preserved by update").toBe(true);
46
+ expect(existsSync(join(project.directory, "package.json"))).toBe(true);
47
+ await expectIsSetup(project, prettier);
48
+ });
49
+
50
+ it("'replace' wipes the directory and produces a fresh project", async () => {
51
+ const project = await generate({ type: "app", layers: ["prettier"] });
52
+ dirs.push(project.directory);
53
+
54
+ const userFile = join(project.directory, "my-untracked-file.txt");
55
+ await writeFile(userFile, "the user added this");
56
+
57
+ // Baseline: a valid project exists, with the desired layer and the
58
+ // user's own file present.
59
+ expect(existsSync(join(project.directory, "package.json")), "baseline package.json").toBe(true);
60
+ expect(existsSync(userFile), "baseline user file").toBe(true);
61
+ await expectIsSetup(project, prettier);
62
+
63
+ await generateProject(project, "replace");
64
+
65
+ // After replace: everything that was there before is gone, replaced by a
66
+ // freshly generated, valid project with the desired layers.
67
+ expect(existsSync(userFile), "stale file removed by replace").toBe(false);
68
+ expect(existsSync(join(project.directory, "package.json"))).toBe(true);
69
+ await expectIsSetup(project, prettier);
70
+ });
71
+
72
+ it("defaults to a non-destructive update when no answer is given", async () => {
73
+ const project = await generate({ type: "app", layers: ["prettier"] });
74
+ dirs.push(project.directory);
75
+
76
+ const userFile = join(project.directory, "my-untracked-file.txt");
77
+ await writeFile(userFile, "the user added this");
78
+
79
+ // Baseline: a valid project exists, with the desired layer and the
80
+ // user's own file present.
81
+ expect(existsSync(join(project.directory, "package.json")), "baseline package.json").toBe(true);
82
+ expect(existsSync(userFile), "baseline user file").toBe(true);
83
+ await expectIsSetup(project, prettier);
84
+
85
+ await generateProject(project);
86
+
87
+ // After a no-arg regenerate: nothing is destroyed.
88
+ expect(existsSync(userFile), "unrelated file preserved by default").toBe(true);
89
+ expect(existsSync(join(project.directory, "package.json"))).toBe(true);
90
+ await expectIsSetup(project, prettier);
91
+ });
92
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, it, afterAll, expect } from "vitest";
2
+ import { generate } from "#test-helpers";
3
+ import { rimraf } from "rimraf";
4
+ import { execa } from "execa";
5
+
6
+ /**
7
+ * The replace-or-update question only matters when a project already exists
8
+ * at the target path. These tests generate a project, assert the baseline
9
+ * state, then regenerate over it the way the CLI does once the user has
10
+ * answered the question, and assert the resulting project output is correct
11
+ * for each answer.
12
+ */
13
+ describe("layer: qunit", () => {
14
+ const dirs: string[] = [];
15
+
16
+ afterAll(async () => {
17
+ if (process.env.CI) return;
18
+
19
+ for (const dir of dirs) {
20
+ await rimraf(dir, { maxRetries: 3, retryDelay: 100 });
21
+ }
22
+ });
23
+
24
+ it("it boots and run tests", async () => {
25
+ const project = await generate({ type: "app", layers: ["qunit"] });
26
+ dirs.push(project.directory);
27
+
28
+ let install = await execa("pnpm install", { cwd: project.directory, shell: true });
29
+ expect(install.exitCode).toBe(0);
30
+
31
+ let test = await execa("pnpm test", { cwd: project.directory, shell: true });
32
+ expect(test.exitCode).toBe(0);
33
+ });
34
+ });
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
- else
20
- exec node "$basedir/../typescript/bin/tsc" "$@"
21
- fi
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
- else
20
- exec node "$basedir/../typescript/bin/tsserver" "$@"
21
- fi
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/bin/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules/vite/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/vite@8.0.14_@types+node@25.0.8_esbuild@0.27.2_jiti@2.6.1_terser@5.46.0/node_modules:/home/runner/work/ember.nvp/ember.nvp/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
19
- else
20
- exec node "$basedir/../vite/bin/vite.js" "$@"
21
- fi