ember.nvp 1.12.0 → 1.13.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 +1 -1
- package/package.json +2 -1
- package/src/bases/index.js +32 -0
- package/src/bases/minimal-app/index.js +14 -1
- package/src/bases/minimal-custom-element/index.js +14 -0
- package/src/bases/minimal-extension/index.js +15 -1
- package/src/bases/minimal-library/index.js +20 -1
- package/src/cli/generator.js +2 -18
- package/src/cli/index.js +1 -1
- package/src/cli/questions/project-type.js +4 -2
- package/src/utils/fs.js +18 -1
- package/src/utils/types.ts +12 -0
- package/test/ember.nvp/cli.test.ts +14 -7
- package/test/ember.nvp/detect-project-type.test.ts +86 -0
- package/test/ember.nvp/layer-qunit.test.ts +19 -11
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.12.0",
|
|
6
|
+
"newVersion": "1.13.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-09-30)\n\n* ember.nvp 1.
|
|
26
|
+
"description": "## Release (2026-09-30)\n\n* ember.nvp 1.13.0 (minor)\n\n#### :rocket: Enhancement\n* `ember.nvp`\n * [#154](https://github.com/NullVoxPopuli/ember.nvp/pull/154) Default the project type to the detected type on update ([@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
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Release (2026-09-30)
|
|
4
4
|
|
|
5
|
+
* ember.nvp 1.13.0 (minor)
|
|
6
|
+
|
|
7
|
+
#### :rocket: Enhancement
|
|
8
|
+
* `ember.nvp`
|
|
9
|
+
* [#154](https://github.com/NullVoxPopuli/ember.nvp/pull/154) Default the project type to the detected type on update ([@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
|
+
|
|
14
|
+
## Release (2026-09-30)
|
|
15
|
+
|
|
5
16
|
* ember.nvp 1.12.0 (minor)
|
|
6
17
|
|
|
7
18
|
#### :rocket: Enhancement
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ You can run `ember.nvp` on top of an existing project to add layers to it.
|
|
|
40
40
|
|
|
41
41
|
Run it inside the project. When the current directory has a `package.json`, the first question is what to do with it:
|
|
42
42
|
|
|
43
|
-
- **update**: update the project in this directory. The
|
|
43
|
+
- **update**: update the project in this directory. The name starts as the `package.json` name, and the type starts as the type that ember.nvp detects (app, library, browser extension, or custom element).
|
|
44
44
|
- **replace**: delete this directory and generate a new project in it.
|
|
45
45
|
- **new**: generate a new project in another directory.
|
|
46
46
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember.nvp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"imports": {
|
|
11
11
|
"#args": "./src/cli/args.js",
|
|
12
|
+
"#bases": "./src/bases/index.js",
|
|
12
13
|
"#bases/*": "./src/bases/*/index.js",
|
|
13
14
|
"#consolidators/*": "./src/consolidators/*",
|
|
14
15
|
"#layers": "./src/layers/index.js",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import app from "./minimal-app/index.js";
|
|
2
|
+
import customElement from "./minimal-custom-element/index.js";
|
|
3
|
+
import extension from "./minimal-extension/index.js";
|
|
4
|
+
import library from "./minimal-library/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The base for each project type.
|
|
8
|
+
*
|
|
9
|
+
* Detection asks the bases in this order,
|
|
10
|
+
* so each base comes before the bases whose markers it also has:
|
|
11
|
+
* - an extension has the index.html and app/ folder of an app
|
|
12
|
+
* - a custom element builds with tsdown, like a library
|
|
13
|
+
* - a library can have an app/ folder (classic addons) or an index.html (a docs app)
|
|
14
|
+
*
|
|
15
|
+
* @type {Record<import('#types').ProjectType, import('#types').Base>}
|
|
16
|
+
*/
|
|
17
|
+
export const bases = {
|
|
18
|
+
extension,
|
|
19
|
+
"custom-element": customElement,
|
|
20
|
+
library,
|
|
21
|
+
app,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} directory an existing project
|
|
26
|
+
* @returns {import('#types').ProjectType | undefined}
|
|
27
|
+
*/
|
|
28
|
+
export function detectProjectType(directory) {
|
|
29
|
+
for (let [type, base] of Object.entries(bases)) {
|
|
30
|
+
if (base.detect(directory)) return /** @type {import('#types').ProjectType} */ (type);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { packageJson, js } from "ember-apply";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { getLatest } from "#utils/npm.js";
|
|
4
|
-
import { applyFolderTo } from "#utils/fs.js";
|
|
5
|
+
import { applyFolderTo, readJSON } from "#utils/fs.js";
|
|
5
6
|
import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -14,11 +15,23 @@ import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
|
14
15
|
* - No linting or formatting
|
|
15
16
|
* - No ember-welcome-page
|
|
16
17
|
* - No warp-drive (opt-in if needed)
|
|
18
|
+
*
|
|
19
|
+
* @type {import('#types').Base}
|
|
17
20
|
*/
|
|
18
21
|
export default {
|
|
19
22
|
label: "Minimal App Base",
|
|
20
23
|
description: "Bare minimum Ember app structure",
|
|
21
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} directory
|
|
27
|
+
*/
|
|
28
|
+
detect(directory) {
|
|
29
|
+
return (
|
|
30
|
+
readJSON(join(directory, "package.json"))?.["ember-addon"]?.type === "app" ||
|
|
31
|
+
existsSync(join(directory, "app"))
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
|
|
22
35
|
/**
|
|
23
36
|
* 1. Apply files
|
|
24
37
|
* a. Remove TS if needed
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { packageJson } from "ember-apply";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { getLatest } from "#utils/npm.js";
|
|
@@ -26,11 +27,24 @@ const TEMPLATE_TAG_NAME = "minimal-custom-element";
|
|
|
26
27
|
* - No linting or formatting
|
|
27
28
|
*
|
|
28
29
|
* Testing, linting, formatting, etc. are opt-in via layers.
|
|
30
|
+
*
|
|
31
|
+
* @type {import('#types').Base}
|
|
29
32
|
*/
|
|
30
33
|
export default {
|
|
31
34
|
label: "Minimal Custom Element Base",
|
|
32
35
|
description: "An Ember component packaged as a custom element (web component)",
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} directory
|
|
39
|
+
*/
|
|
40
|
+
detect(directory) {
|
|
41
|
+
return ["src/register.ts", "src/register.js"].some((file) => {
|
|
42
|
+
let path = join(directory, file);
|
|
43
|
+
|
|
44
|
+
return existsSync(path) && readFileSync(path, "utf-8").includes("customElements.define");
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
|
|
34
48
|
/**
|
|
35
49
|
* 1. Apply files
|
|
36
50
|
* a. Remove TS if needed
|
|
@@ -2,7 +2,7 @@ import { packageJson } from "ember-apply";
|
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getLatest } from "#utils/npm.js";
|
|
5
|
-
import { applyFolderTo } from "#utils/fs.js";
|
|
5
|
+
import { applyFolderTo, readJSON } from "#utils/fs.js";
|
|
6
6
|
import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -17,11 +17,25 @@ import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
|
17
17
|
* - No testing framework
|
|
18
18
|
* - No linting or formatting
|
|
19
19
|
* - No ember-welcome-page
|
|
20
|
+
*
|
|
21
|
+
* @type {import('#types').Base}
|
|
20
22
|
*/
|
|
21
23
|
export default {
|
|
22
24
|
label: "Minimal Extension Base",
|
|
23
25
|
description: "Bare minimum browser extension structure with an Ember popup",
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* A web app can have a manifest.json too (for a PWA),
|
|
29
|
+
* but only a browser extension's manifest has `manifest_version`.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} directory
|
|
32
|
+
*/
|
|
33
|
+
detect(directory) {
|
|
34
|
+
return ["manifest.json", "public/manifest.json"].some(
|
|
35
|
+
(file) => readJSON(join(directory, file))?.manifest_version !== undefined,
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
|
|
25
39
|
/**
|
|
26
40
|
* 1. Apply files
|
|
27
41
|
* a. Remove TS if needed
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { packageJson } from "ember-apply";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { getLatest } from "#utils/npm.js";
|
|
5
|
-
import { applyFolderTo } from "#utils/fs.js";
|
|
6
|
+
import { applyFolderTo, readJSON } from "#utils/fs.js";
|
|
6
7
|
import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -18,11 +19,29 @@ import { removeConfiguredPlugin } from "#utils/babel.js";
|
|
|
18
19
|
* - No linting or formatting
|
|
19
20
|
*
|
|
20
21
|
* Testing, linting, formatting, etc. are opt-in via layers.
|
|
22
|
+
*
|
|
23
|
+
* @type {import('#types').Base}
|
|
21
24
|
*/
|
|
22
25
|
export default {
|
|
23
26
|
label: "Minimal Library Base",
|
|
24
27
|
description: "Bare minimum Ember v2 library (addon) structure",
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* `ember-addon` marks v2 and classic addons.
|
|
31
|
+
* A library from this base has no `ember-addon`, only a tsdown config.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} directory
|
|
34
|
+
*/
|
|
35
|
+
detect(directory) {
|
|
36
|
+
let manifest = readJSON(join(directory, "package.json"));
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
manifest?.["ember-addon"]?.type === "addon" ||
|
|
40
|
+
Boolean(manifest?.keywords?.includes("ember-addon")) ||
|
|
41
|
+
existsSync(join(directory, "tsdown.config.js"))
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
|
|
26
45
|
/**
|
|
27
46
|
* 1. Apply files
|
|
28
47
|
* a. Remove TS if needed
|
package/src/cli/generator.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { mkdir, rm } from "node:fs/promises";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import baseExtension from "#bases/minimal-extension";
|
|
5
|
-
import baseLibrary from "#bases/minimal-library";
|
|
6
|
-
import baseCustomElement from "#bases/minimal-custom-element";
|
|
3
|
+
import { bases } from "#bases";
|
|
7
4
|
import { consolidateLintingScripts } from "../consolidators/linting.js";
|
|
8
5
|
import { hasGit } from "#utils/git.js";
|
|
9
6
|
/**
|
|
@@ -19,20 +16,7 @@ export async function generateProject(project, replaceOrUpdate) {
|
|
|
19
16
|
|
|
20
17
|
await mkdir(project.directory, { recursive: true });
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
case "app":
|
|
24
|
-
await baseApp.run(project);
|
|
25
|
-
break;
|
|
26
|
-
case "library":
|
|
27
|
-
await baseLibrary.run(project);
|
|
28
|
-
break;
|
|
29
|
-
case "extension":
|
|
30
|
-
await baseExtension.run(project);
|
|
31
|
-
break;
|
|
32
|
-
case "custom-element":
|
|
33
|
-
await baseCustomElement.run(project);
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
19
|
+
await bases[project.desires.type].run(project);
|
|
36
20
|
|
|
37
21
|
/**
|
|
38
22
|
* We could run these in a loop until there is no git diff
|
package/src/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ async function main() {
|
|
|
34
34
|
const projectName = await askName(here === "update" ? cwd : undefined);
|
|
35
35
|
const projectPath = here ? cwd : await askPath(projectName);
|
|
36
36
|
const replaceOrUpdate = here ?? (await askReplaceOrUpdate(projectPath));
|
|
37
|
-
const projectType = await askProjectType();
|
|
37
|
+
const projectType = await askProjectType(replaceOrUpdate === "update" ? projectPath : undefined);
|
|
38
38
|
const selectedLayers = await askLayers(projectType);
|
|
39
39
|
const packageManager = await askPackageManager();
|
|
40
40
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { answers, printArgInUse } from "#args";
|
|
3
|
+
import { detectProjectType } from "#bases";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* addon is alias for library
|
|
@@ -27,10 +28,10 @@ function isValid(selected) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* @param {string} [existingProject] a directory whose project type becomes the default answer
|
|
31
32
|
* @return {Promise<import('#types').ProjectType>}
|
|
32
33
|
*/
|
|
33
|
-
export async function askProjectType() {
|
|
34
|
+
export async function askProjectType(existingProject) {
|
|
34
35
|
if (isValid(answers.type)) {
|
|
35
36
|
printArgInUse("type", answers.type);
|
|
36
37
|
|
|
@@ -43,6 +44,7 @@ export async function askProjectType() {
|
|
|
43
44
|
|
|
44
45
|
const answer = await p.select({
|
|
45
46
|
message: "Which type of project?",
|
|
47
|
+
initialValue: existingProject ? detectProjectType(existingProject) : undefined,
|
|
46
48
|
options: [
|
|
47
49
|
{ value: "app", label: "web app", hint: "generates html, js, and css to deploy to the web" },
|
|
48
50
|
{
|
package/src/utils/fs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, lstatSync } from "node:fs";
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
2
|
import { readFile, glob, mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { parse as parsePath } from "node:path";
|
|
@@ -15,6 +15,23 @@ import { rewriteImportsToMatchFiles } from "./rewrite-imports.js";
|
|
|
15
15
|
*/
|
|
16
16
|
const EVERY_FILE = ["**/*", "**/.*", "**/.*/**/*"];
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Project detection reads files that can be missing or broken,
|
|
20
|
+
* and a guess must not stop the CLI.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} path
|
|
23
|
+
* @returns {any} undefined when the file is missing or is not JSON
|
|
24
|
+
*/
|
|
25
|
+
export function readJSON(path) {
|
|
26
|
+
if (!existsSync(path)) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
/**
|
|
19
36
|
* Modified version of applyFolder from ember-apply.
|
|
20
37
|
*
|
package/src/utils/types.ts
CHANGED
|
@@ -89,6 +89,18 @@ export interface Layer {
|
|
|
89
89
|
>;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
export interface Base {
|
|
93
|
+
label: string;
|
|
94
|
+
description: string;
|
|
95
|
+
/**
|
|
96
|
+
* Whether the directory holds a project of this base's type.
|
|
97
|
+
*
|
|
98
|
+
* When updating a project, the type question starts at the first base that detects it.
|
|
99
|
+
*/
|
|
100
|
+
detect: (directory: string) => boolean;
|
|
101
|
+
run: (project: Project) => Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
export interface DiscoveredLayer extends Layer {
|
|
93
105
|
/**
|
|
94
106
|
* The unique name of the layer.
|
|
@@ -154,14 +154,15 @@ test("cli exits on an invalid layer option flag, before writing anything", async
|
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
test("in a project directory, asks to update first and
|
|
157
|
+
test("in a project directory, asks to update first and defaults to the project's name and type", async () => {
|
|
158
158
|
let dir = await mktemp("cli-update-here");
|
|
159
|
-
await writeFile(
|
|
159
|
+
await writeFile(
|
|
160
|
+
join(dir, "package.json"),
|
|
161
|
+
JSON.stringify({ name: "@scope/my-lib", "ember-addon": { version: 2, type: "addon" } }),
|
|
162
|
+
);
|
|
160
163
|
|
|
161
|
-
// No --name or --
|
|
164
|
+
// No --name, --path, or --type, so the CLI asks about the project in this directory
|
|
162
165
|
let args = [
|
|
163
|
-
"--type",
|
|
164
|
-
"library",
|
|
165
166
|
"--layers",
|
|
166
167
|
"prettier",
|
|
167
168
|
"--packageManager",
|
|
@@ -171,7 +172,11 @@ test("in a project directory, asks to update first and fills in the project's na
|
|
|
171
172
|
"--write",
|
|
172
173
|
"yes",
|
|
173
174
|
];
|
|
174
|
-
let prompts = [
|
|
175
|
+
let prompts = [
|
|
176
|
+
"This directory has a package.json",
|
|
177
|
+
"What is your project name?",
|
|
178
|
+
"Which type of project?",
|
|
179
|
+
];
|
|
175
180
|
|
|
176
181
|
try {
|
|
177
182
|
let { execaPromise } = cli(args, { cwd: dir });
|
|
@@ -180,7 +185,7 @@ test("in a project directory, asks to update first and fills in the project's na
|
|
|
180
185
|
execaPromise.stdout?.on("data", (chunk) => {
|
|
181
186
|
output += chunk;
|
|
182
187
|
|
|
183
|
-
// Enter keeps the answer each prompt starts with: "update", then the
|
|
188
|
+
// Enter keeps the answer each prompt starts with: "update", the name, then the type
|
|
184
189
|
if (prompts[0] && stripVTControlCharacters(output).includes(prompts[0])) {
|
|
185
190
|
prompts.shift();
|
|
186
191
|
execaPromise.stdin?.write("\r");
|
|
@@ -195,6 +200,8 @@ test("in a project directory, asks to update first and fills in the project's na
|
|
|
195
200
|
text.indexOf("What is your project name?"),
|
|
196
201
|
);
|
|
197
202
|
expect(text).not.toContain("Where would you like to place your project?");
|
|
203
|
+
expect(text).toMatch(/Which type of project\?\n│ library\n/);
|
|
204
|
+
expect(existsSync(join(dir, "tsdown.config.js")), "the library base ran").toBe(true);
|
|
198
205
|
expect(text).not.toContain("cd ");
|
|
199
206
|
|
|
200
207
|
let manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf-8"));
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, afterAll } from "vitest";
|
|
2
|
+
import { generate, mktemp } from "#test-helpers";
|
|
3
|
+
import { detectProjectType } from "#bases";
|
|
4
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import type { ProjectType } from "#types";
|
|
7
|
+
|
|
8
|
+
const dirs: string[] = [];
|
|
9
|
+
|
|
10
|
+
afterAll(async () => {
|
|
11
|
+
if (process.env.CI) return;
|
|
12
|
+
|
|
13
|
+
for (const dir of dirs) {
|
|
14
|
+
await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function fixture(files: Record<string, string>) {
|
|
19
|
+
let dir = await mktemp("detect-project-type");
|
|
20
|
+
dirs.push(dir);
|
|
21
|
+
|
|
22
|
+
for (let [file, contents] of Object.entries(files)) {
|
|
23
|
+
await mkdir(dirname(join(dir, file)), { recursive: true });
|
|
24
|
+
await writeFile(join(dir, file), contents);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("detectProjectType", () => {
|
|
31
|
+
const types: ProjectType[] = ["app", "library", "extension", "custom-element"];
|
|
32
|
+
|
|
33
|
+
for (const type of types) {
|
|
34
|
+
it(`detects a generated ${type}`, async () => {
|
|
35
|
+
const project = await generate({ type });
|
|
36
|
+
dirs.push(project.directory);
|
|
37
|
+
|
|
38
|
+
expect(detectProjectType(project.directory)).toBe(type);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
it("detects a v2 addon with a docs app as a library", async () => {
|
|
43
|
+
const dir = await fixture({
|
|
44
|
+
"package.json": JSON.stringify({ "ember-addon": { version: 2, type: "addon" } }),
|
|
45
|
+
"index.html": "<!doctype html>",
|
|
46
|
+
"src/index.ts": "",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(detectProjectType(dir)).toBe("library");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("detects a classic addon as a library", async () => {
|
|
53
|
+
const dir = await fixture({
|
|
54
|
+
"package.json": JSON.stringify({ keywords: ["ember-addon"] }),
|
|
55
|
+
"addon/index.js": "",
|
|
56
|
+
"app/index.js": "",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(detectProjectType(dir)).toBe("library");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("detects a vite app", async () => {
|
|
63
|
+
const dir = await fixture({
|
|
64
|
+
"package.json": JSON.stringify({ "ember-addon": { version: 2, type: "app" } }),
|
|
65
|
+
"index.html": "<!doctype html>",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(detectProjectType(dir)).toBe("app");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("detects an app with a web app manifest as an app", async () => {
|
|
72
|
+
const dir = await fixture({
|
|
73
|
+
"package.json": "{}",
|
|
74
|
+
"app/app.ts": "",
|
|
75
|
+
"public/manifest.json": JSON.stringify({ name: "my-pwa", display: "standalone" }),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(detectProjectType(dir)).toBe("app");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("makes no guess for a project it does not know", async () => {
|
|
82
|
+
const dir = await fixture({ "package.json": JSON.stringify({ name: "a-node-script" }) });
|
|
83
|
+
|
|
84
|
+
expect(detectProjectType(dir)).toBe(undefined);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -131,7 +131,7 @@ describe("layer: qunit", () => {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
describe("app", () => {
|
|
134
|
-
it("it boots and run tests", async () => {
|
|
134
|
+
it("it boots and run tests", { timeout: 300_000 }, async () => {
|
|
135
135
|
const project = await generate({ type: "app", layers: ["qunit"] });
|
|
136
136
|
dirs.push(project.directory);
|
|
137
137
|
|
|
@@ -271,11 +271,15 @@ describe("layer: qunit", () => {
|
|
|
271
271
|
`);
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
it(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
it(
|
|
275
|
+
"installs, and tests generated against the library's exports pass",
|
|
276
|
+
{ timeout: 300_000 },
|
|
277
|
+
async () => {
|
|
278
|
+
await writeLibrarySource(project, "typescript");
|
|
279
|
+
await writeExampleTests(project, "typescript");
|
|
280
|
+
await installAndTest(project);
|
|
281
|
+
},
|
|
282
|
+
);
|
|
279
283
|
|
|
280
284
|
it("the publish build still ships precompileTemplate", async () => {
|
|
281
285
|
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|
|
@@ -363,11 +367,15 @@ describe("layer: qunit", () => {
|
|
|
363
367
|
`);
|
|
364
368
|
});
|
|
365
369
|
|
|
366
|
-
it(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
370
|
+
it(
|
|
371
|
+
"installs, and tests generated against the library's exports pass",
|
|
372
|
+
{ timeout: 300_000 },
|
|
373
|
+
async () => {
|
|
374
|
+
await writeLibrarySource(project, "javascript");
|
|
375
|
+
await writeExampleTests(project, "javascript");
|
|
376
|
+
await installAndTest(project);
|
|
377
|
+
},
|
|
378
|
+
);
|
|
371
379
|
|
|
372
380
|
it("the publish build still ships precompileTemplate", async () => {
|
|
373
381
|
let build = await execa("pnpm build", { cwd: project.directory, shell: true });
|