ember.nvp 0.1.0 → 0.2.1
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/.github/workflows/ci.yml +41 -0
- package/.github/workflows/plan-release.yml +2 -2
- package/.github/workflows/publish.yml +4 -5
- package/.prettierignore +2 -1
- package/.prettierrc.js +10 -0
- package/.release-plan.json +6 -6
- package/CHANGELOG.md +53 -0
- package/README.md +79 -142
- package/package.json +28 -5
- package/pnpm-workspace.yaml +3 -0
- package/src/bases/README.md +5 -0
- package/src/bases/minimal-app/app.test.ts +203 -0
- package/src/bases/minimal-app/files/.env.development +8 -0
- package/src/bases/minimal-app/files/app/app.ts +21 -0
- package/src/bases/minimal-app/files/app/config.ts +17 -0
- package/src/bases/minimal-app/files/app/router.ts +51 -0
- package/src/bases/minimal-app/files/app/templates/application.gts +5 -0
- package/src/{layers/minimal/files-ts → bases/minimal-app/files}/babel.config.js +8 -5
- package/src/{layers/minimal → bases/minimal-app}/files/index.html +3 -5
- package/src/bases/minimal-app/files/package.json +57 -0
- package/src/{layers/minimal → bases/minimal-app}/files/tsconfig.json +0 -6
- package/src/bases/minimal-app/index.js +111 -0
- package/src/bases/minimal-library/index.js +10 -0
- package/src/cli/args.js +57 -0
- package/src/cli/generator.js +66 -11
- package/src/cli/index.js +44 -13
- package/src/cli/questions/layers.js +39 -50
- package/src/cli/questions/name.js +22 -6
- package/src/cli/questions/ok.js +38 -0
- package/src/cli/questions/package-manager.js +23 -2
- package/src/cli/questions/path.js +59 -0
- package/src/cli/questions/project-type.js +55 -0
- package/src/consolidators/linting.js +51 -0
- package/src/index.js +2 -0
- package/src/layers/eslint-bundled-ember/files/eslint.config.js +14 -0
- package/src/layers/eslint-bundled-ember/index.js +91 -0
- package/src/layers/eslint-bundled-nvp/app.test.ts +91 -0
- package/src/layers/eslint-bundled-nvp/files/eslint.config.js +16 -0
- package/src/layers/eslint-bundled-nvp/index.js +91 -0
- package/src/layers/eslint-ejected/index.js +95 -0
- package/src/layers/git/index.js +33 -0
- package/src/layers/github-actions/app.test.ts +73 -0
- package/src/layers/github-actions/index.js +200 -0
- package/src/layers/index.js +47 -0
- package/src/layers/prettier/files/.prettierrc.js +4 -0
- package/src/layers/prettier/index.js +45 -18
- package/src/layers/qunit/app.test.ts +90 -0
- package/src/layers/qunit/files/testem.cjs +26 -0
- package/src/layers/qunit/files/tests/index.html +16 -7
- package/src/layers/qunit/files/tests/test-helper.ts +65 -7
- package/src/layers/qunit/files/tests/unit/macros-test.ts +26 -0
- package/src/layers/qunit/index.js +83 -34
- package/src/layers/release-plan/index.js +5 -0
- package/src/layers/renovate/files/app.json5 +14 -0
- package/src/layers/renovate/files/library.json5 +16 -0
- package/src/layers/renovate/index.js +68 -0
- package/src/layers/typescript/app.test.ts +93 -0
- package/src/layers/typescript/index.js +145 -0
- package/src/layers/vitest/files/vitest.config.mjs +1 -5
- package/src/layers/vitest/index.js +43 -26
- package/src/utils/babel.js +76 -0
- package/src/utils/buffer.js +15 -0
- package/src/utils/cli.js +45 -0
- package/src/utils/cwd.js +1 -0
- package/src/utils/fs.js +85 -0
- package/src/utils/git.js +41 -0
- package/src/utils/git.test.ts +36 -0
- package/src/utils/manifest.js +9 -0
- package/src/utils/npm.js +29 -0
- package/src/utils/object.js +10 -0
- package/src/utils/project.js +249 -0
- package/src/utils/remove-types.js +90 -0
- package/src/utils/types.ts +48 -0
- package/test/ember.nvp/cli.test.ts +12 -0
- package/test/ember.nvp/layers.test.ts +35 -0
- package/test/ember.nvp/permutations.test.ts +200 -0
- package/test/manual.sh +11 -0
- package/test/test-helpers/permutate.test.ts +70 -0
- package/test/test-helpers.ts +175 -0
- package/tsconfig.json +10 -2
- package/vitest.config.js +9 -0
- package/src/layers/eslint/index.js +0 -42
- package/src/layers/minimal/files/app/app.ts +0 -10
- package/src/layers/minimal/files/app/config.ts +0 -18
- package/src/layers/minimal/files/app/router.ts +0 -9
- package/src/layers/minimal/files/app/templates/application.gts +0 -7
- package/src/layers/minimal/files-js/babel.config.js +0 -38
- package/src/layers/minimal/index.js +0 -89
- package/src/layers/prettier/files/.prettierrc.cjs +0 -15
- package/src/layers/qunit/files/tests/unit/.gitkeep +0 -0
- /package/src/{layers/minimal → bases/minimal-app}/files/vite.config.mjs +0 -0
- /package/src/layers/{eslint/files/eslint.config.mjs → eslint-ejected/files/eslint.config.js} +0 -0
- /package/src/layers/{minimal/files/app/routes → qunit/files/tests/application}/.gitkeep +0 -0
- /package/src/layers/qunit/files/tests/{integration → rendering}/.gitkeep +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { packageJson, files } from "ember-apply";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { getLatest } from "#utils/npm.js";
|
|
5
|
+
import { formatLabel } from "#utils/cli.js";
|
|
6
|
+
import { maybeLintWithConcurrently } from "#consolidators/linting.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Prettier Layer
|
|
@@ -10,29 +11,55 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
11
|
* Adds Prettier for code formatting
|
|
11
12
|
*/
|
|
12
13
|
export default {
|
|
13
|
-
label: "Prettier",
|
|
14
|
-
|
|
14
|
+
label: formatLabel("Prettier", "code formatting"),
|
|
15
|
+
hint: "the defaults",
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('#utils/project.js').Project} project
|
|
19
|
+
*/
|
|
20
|
+
async run(project) {
|
|
21
|
+
await files.applyFolder(join(import.meta.dirname, "files"), project.directory);
|
|
19
22
|
|
|
20
|
-
// Add devDependencies
|
|
21
23
|
await packageJson.addDevDependencies(
|
|
22
|
-
{
|
|
23
|
-
prettier: "^3.
|
|
24
|
-
"prettier-plugin-ember-template-tag": "^2.
|
|
25
|
-
},
|
|
26
|
-
|
|
24
|
+
await getLatest({
|
|
25
|
+
prettier: "^3.8.1",
|
|
26
|
+
"prettier-plugin-ember-template-tag": "^2.1.2",
|
|
27
|
+
}),
|
|
28
|
+
project.directory,
|
|
27
29
|
);
|
|
28
30
|
|
|
29
|
-
// Add scripts
|
|
30
31
|
await packageJson.addScripts(
|
|
31
32
|
{
|
|
32
33
|
format: "prettier --write .",
|
|
33
|
-
"
|
|
34
|
+
"lint:prettier": "prettier --check .",
|
|
34
35
|
},
|
|
35
|
-
|
|
36
|
+
project.directory,
|
|
36
37
|
);
|
|
38
|
+
|
|
39
|
+
await maybeLintWithConcurrently(project);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {import('#utils/project.js').Project} project
|
|
44
|
+
*/
|
|
45
|
+
async isSetup(project) {
|
|
46
|
+
let manifest = await packageJson.read(project.directory);
|
|
47
|
+
|
|
48
|
+
let scripts = Object.values(manifest.scripts ?? {}).filter((script) =>
|
|
49
|
+
script.includes("prettier"),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!scripts.some((script) => script.includes("--write"))) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (!scripts.some((script) => script.includes("--check"))) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!existsSync(join(project.directory, ".prettierrc.js"))) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
37
64
|
},
|
|
38
65
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { beforeAll, describe, it, expect as hardExpect, afterAll } from "vitest";
|
|
2
|
+
import { expectIsSetup, generate, layers, reapply } from "#test-helpers";
|
|
3
|
+
|
|
4
|
+
import type { Project } from "ember.nvp";
|
|
5
|
+
import { rm } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
const expect = hardExpect.soft;
|
|
8
|
+
|
|
9
|
+
let layer = layers.find((layer) => layer.name === "qunit")!;
|
|
10
|
+
|
|
11
|
+
describe("starting without qunit", () => {
|
|
12
|
+
let project: Project;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
project = await generate({
|
|
16
|
+
type: "app",
|
|
17
|
+
packageManager: "pnpm",
|
|
18
|
+
layers: ["git"],
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("applying qunit", async () => {
|
|
27
|
+
await reapply(project, ["qunit"]);
|
|
28
|
+
|
|
29
|
+
await expectIsSetup(project, layer);
|
|
30
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("starting with qunit", () => {
|
|
35
|
+
let project: Project;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
project = await generate({
|
|
39
|
+
type: "app",
|
|
40
|
+
packageManager: "pnpm",
|
|
41
|
+
layers: ["qunit", "git"],
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("reapplying causes no changes", async () => {
|
|
50
|
+
await reapply(project, ["qunit"]);
|
|
51
|
+
|
|
52
|
+
await expectIsSetup(project, layer);
|
|
53
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("package.json scripts", () => {
|
|
58
|
+
let project: Project;
|
|
59
|
+
|
|
60
|
+
beforeAll(async () => {
|
|
61
|
+
project = await generate({
|
|
62
|
+
type: "app",
|
|
63
|
+
packageManager: "pnpm",
|
|
64
|
+
layers: ["qunit", "git"],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let { exitCode } = await project.run("pnpm install");
|
|
68
|
+
|
|
69
|
+
hardExpect(exitCode).toBe(0);
|
|
70
|
+
|
|
71
|
+
await expectIsSetup(project, layer);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterAll(async () => {
|
|
75
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("running tests works", async () => {
|
|
79
|
+
{
|
|
80
|
+
let { exitCode, stderr, stdout } = await project.run("pnpm test");
|
|
81
|
+
|
|
82
|
+
if (exitCode !== 0) {
|
|
83
|
+
console.log(stderr);
|
|
84
|
+
console.log(stdout);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
expect(exitCode).toBe(0);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
if (typeof module !== "undefined") {
|
|
4
|
+
module.exports = {
|
|
5
|
+
test_page: "tests/index.html?hidepassed",
|
|
6
|
+
cwd: "dist",
|
|
7
|
+
disable_watching: true,
|
|
8
|
+
launch_in_ci: ["Chrome"],
|
|
9
|
+
launch_in_dev: ["Chrome"],
|
|
10
|
+
browser_start_timeout: 120,
|
|
11
|
+
browser_args: {
|
|
12
|
+
Chrome: {
|
|
13
|
+
ci: [
|
|
14
|
+
// --no-sandbox is needed when running Chrome inside a container
|
|
15
|
+
process.env.CI ? "--no-sandbox" : null,
|
|
16
|
+
"--headless",
|
|
17
|
+
"--disable-dev-shm-usage",
|
|
18
|
+
"--disable-software-rasterizer",
|
|
19
|
+
"--mute-audio",
|
|
20
|
+
"--remote-debugging-port=0",
|
|
21
|
+
"--window-size=1440,900",
|
|
22
|
+
].filter(Boolean),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html
|
|
2
|
+
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<title>
|
|
6
|
-
<meta name="description" content="" />
|
|
5
|
+
<title>Tests</title>
|
|
7
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
-
|
|
9
|
-
<link rel="stylesheet" href="https://unpkg.com/qunit@2/qunit/qunit.css" />
|
|
10
|
-
|
|
11
|
-
<script type="module" src="/@embroider/virtual/test-support.js"></script>
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<div id="qunit"></div>
|
|
@@ -17,5 +12,19 @@
|
|
|
17
12
|
<div id="ember-testing"></div>
|
|
18
13
|
</div>
|
|
19
14
|
</div>
|
|
15
|
+
|
|
16
|
+
<script src="/testem.js" integrity="" data-embroider-ignore></script>
|
|
17
|
+
<script type="module">
|
|
18
|
+
import "ember-testing";
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script type="module">
|
|
22
|
+
import { start } from "./test-helper";
|
|
23
|
+
// traditional test locations
|
|
24
|
+
import.meta.glob("./**/*.{js,ts,gjs,gts}", { eager: true });
|
|
25
|
+
// co-located test locations
|
|
26
|
+
import.meta.glob("./**/*-test.{js,ts,gjs,gts}", { eager: true, base: "../app" });
|
|
27
|
+
start();
|
|
28
|
+
</script>
|
|
20
29
|
</body>
|
|
21
30
|
</html>
|
|
@@ -1,12 +1,70 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { getOwner } from "@ember/owner";
|
|
2
|
+
import {
|
|
3
|
+
currentRouteName,
|
|
4
|
+
currentURL,
|
|
5
|
+
getSettledState,
|
|
6
|
+
resetOnerror,
|
|
7
|
+
setApplication,
|
|
8
|
+
visit,
|
|
9
|
+
} from "@ember/test-helpers";
|
|
10
|
+
import { getPendingWaiterState } from "@ember/test-waiters";
|
|
3
11
|
import * as QUnit from "qunit";
|
|
4
|
-
import { setApplication } from "@ember/test-helpers";
|
|
5
12
|
import { setup } from "qunit-dom";
|
|
6
|
-
import { start } from "ember-qunit";
|
|
13
|
+
import { setupEmberOnerrorValidation, start as qunitStart } from "ember-qunit";
|
|
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";
|
|
7
18
|
|
|
8
|
-
|
|
19
|
+
import Application from "#app/app";
|
|
20
|
+
import config from "#config";
|
|
9
21
|
|
|
10
|
-
|
|
22
|
+
Object.assign(window, {
|
|
23
|
+
visit,
|
|
24
|
+
getSettledState,
|
|
25
|
+
getPendingWaiterState,
|
|
26
|
+
currentURL,
|
|
27
|
+
currentRouteName,
|
|
28
|
+
getOwner,
|
|
29
|
+
snapshotTimers: (label?: string) => {
|
|
30
|
+
console.debug(
|
|
31
|
+
label ?? "snapshotTimers",
|
|
32
|
+
JSON.parse(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
settled: getSettledState(),
|
|
35
|
+
waiters: getPendingWaiterState(),
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
11
41
|
|
|
12
|
-
start()
|
|
42
|
+
export function start() {
|
|
43
|
+
config.locationType = "none";
|
|
44
|
+
config.APP.rootElement = "#ember-testing";
|
|
45
|
+
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
|
+
|
|
59
|
+
setApplication(Application.create(config.APP));
|
|
60
|
+
setup(QUnit.assert);
|
|
61
|
+
setupEmberOnerrorValidation();
|
|
62
|
+
|
|
63
|
+
QUnit.moduleStart(({ name }) => console.group(name));
|
|
64
|
+
QUnit.testStart(({ name }) => console.group(name));
|
|
65
|
+
QUnit.testDone(() => console.groupEnd());
|
|
66
|
+
QUnit.moduleDone(() => console.groupEnd());
|
|
67
|
+
QUnit.testDone(resetOnerror);
|
|
68
|
+
|
|
69
|
+
qunitStart();
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { module, test } from "qunit";
|
|
2
|
+
import { isTesting } from "@embroider/macros";
|
|
3
|
+
import { assert as emberAssert } from "@ember/debug";
|
|
4
|
+
|
|
5
|
+
module("@embroider/macros | runtime mode in tests", function () {
|
|
6
|
+
/**
|
|
7
|
+
* In compile-time mode `isTesting()` is inlined as `false` at build time,
|
|
8
|
+
* regardless of what the runtime global config says. Only when the test
|
|
9
|
+
* build runs the macros plugin in runtime mode (see babel.config.js +
|
|
10
|
+
* EMBER_ENV=test) does `isTesting()` reflect the value set by
|
|
11
|
+
* `setTesting(true)` in tests/test-helper.ts.
|
|
12
|
+
*/
|
|
13
|
+
test("isTesting() is true", function (assert) {
|
|
14
|
+
assert.strictEqual(isTesting(), true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `@ember/debug`'s `assert(description, condition)` is only active when
|
|
19
|
+
* DEBUG is true (i.e. non-production builds). Assertions are stripped
|
|
20
|
+
* from production bundles, so this also doubles as a sanity check that
|
|
21
|
+
* the test build keeps DEBUG enabled.
|
|
22
|
+
*/
|
|
23
|
+
test("@ember/debug assert throws when the condition is false", function (assert) {
|
|
24
|
+
assert.throws(() => emberAssert("string", false));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,53 +1,102 @@
|
|
|
1
|
+
import { applyFolderTo } from "#utils/fs.js";
|
|
2
|
+
import { getLatest } from "#utils/npm.js";
|
|
1
3
|
import { packageJson, files } from "ember-apply";
|
|
2
|
-
import {
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const deps = {
|
|
7
|
+
"@ember/test-helpers": "^5.4.1",
|
|
8
|
+
"@ember/test-waiters": "^4.1.0",
|
|
9
|
+
"ember-qunit": "^9.0.4",
|
|
10
|
+
"qunit-theme-ember": "^1.0.0",
|
|
11
|
+
qunit: "^2.25.0",
|
|
12
|
+
"qunit-dom": "3.5.0",
|
|
13
|
+
testem: "3.17.0",
|
|
14
|
+
};
|
|
15
|
+
const tsDeps = {
|
|
16
|
+
"@types/qunit": "^2.19.12",
|
|
17
|
+
};
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Adds QUnit testing support (Ember's traditional testing framework)
|
|
20
|
+
* @type {import('#types').Layer}
|
|
12
21
|
*/
|
|
13
22
|
export default {
|
|
14
23
|
label: "QUnit",
|
|
15
|
-
description: "Testing with QUnit",
|
|
16
|
-
|
|
17
|
-
async run({ targetDir, projectName }) {
|
|
18
|
-
// Apply test files
|
|
19
|
-
await files.applyFolder(join(__dirname, "files"), targetDir);
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await files.copyFileTo(testHtmlPath, { content: updated });
|
|
26
|
-
|
|
27
|
-
// Add dependencies
|
|
28
|
-
await packageJson.addDependencies(
|
|
29
|
-
{
|
|
30
|
-
"@ember/test-helpers": "^4.0.4",
|
|
31
|
-
},
|
|
32
|
-
targetDir,
|
|
33
|
-
);
|
|
25
|
+
async run(project) {
|
|
26
|
+
let filePath = join(import.meta.dirname, "files");
|
|
27
|
+
await applyFolderTo(filePath, project);
|
|
28
|
+
let ts = await project.hasOrWantsLayer("typescript");
|
|
34
29
|
|
|
35
|
-
// Add devDependencies
|
|
36
30
|
await packageJson.addDevDependencies(
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
targetDir,
|
|
31
|
+
await getLatest({
|
|
32
|
+
...deps,
|
|
33
|
+
...(ts ? tsDeps : {}),
|
|
34
|
+
}),
|
|
35
|
+
project.directory,
|
|
43
36
|
);
|
|
44
37
|
|
|
45
38
|
// Add scripts
|
|
46
39
|
await packageJson.addScripts(
|
|
47
40
|
{
|
|
48
|
-
test: "vite build
|
|
41
|
+
"build:test": "EMBER_ENV=test NODE_ENV=development vite build --mode development",
|
|
42
|
+
"test:ci": "testem ci --port 0",
|
|
43
|
+
test: "pnpm build:test && pnpm test:ci",
|
|
49
44
|
},
|
|
50
|
-
|
|
45
|
+
project.directory,
|
|
51
46
|
);
|
|
52
47
|
},
|
|
48
|
+
/**
|
|
49
|
+
* @overload
|
|
50
|
+
* @param {import('#utils/project.js').Project} project
|
|
51
|
+
* @param {true} explain
|
|
52
|
+
* @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* @overload
|
|
56
|
+
* @param {import('#utils/project.js').Project} project
|
|
57
|
+
* @param {boolean | undefined} [explain]
|
|
58
|
+
* @returns {Promise<boolean>}
|
|
59
|
+
*/
|
|
60
|
+
async isSetup(project, explain) {
|
|
61
|
+
const reasons = [];
|
|
62
|
+
let depsToCheck = [Object.keys(deps), project.wantsTypeScript ? Object.keys(tsDeps) : ""]
|
|
63
|
+
.flat()
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
|
|
66
|
+
let manifest = await packageJson.read(project.directory);
|
|
67
|
+
|
|
68
|
+
if (!manifest.scripts?.["build:test"]) {
|
|
69
|
+
if (!explain) return false;
|
|
70
|
+
|
|
71
|
+
reasons.push(`package.json is missing the "build:test" script`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!manifest.scripts?.["test:ci"]) {
|
|
75
|
+
if (!explain) return false;
|
|
76
|
+
|
|
77
|
+
reasons.push(`package.json is missing the "test:ci" script`);
|
|
78
|
+
}
|
|
79
|
+
if (!manifest.scripts?.["test"]) {
|
|
80
|
+
if (!explain) return false;
|
|
81
|
+
|
|
82
|
+
reasons.push(`package.json is missing the "test" script`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (let dep of depsToCheck) {
|
|
86
|
+
if (!manifest.devDependencies?.[dep]) {
|
|
87
|
+
if (!explain) return false;
|
|
88
|
+
|
|
89
|
+
reasons.push(`package.json is missing ${dep} in devDependencies`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (explain) {
|
|
94
|
+
return {
|
|
95
|
+
isSetup: reasons.length === 0,
|
|
96
|
+
reasons,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return reasons.length === 0;
|
|
101
|
+
},
|
|
53
102
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Docs:
|
|
2
|
+
// https://docs.renovatebot.com/configuration-options/
|
|
3
|
+
//
|
|
4
|
+
// This config: https://github.com/nullVoxPopuli/renovate
|
|
5
|
+
// - weekly automated updates
|
|
6
|
+
// - requires that packages be released for at least 5 days before opening a PR with that dependency
|
|
7
|
+
// - doesn't constantly rebase open PRs
|
|
8
|
+
// - provides opinionated package rules for reduced PR spam
|
|
9
|
+
// - opens a separate PR to re-roll your lockfile occasionally
|
|
10
|
+
// - appropriate for application development
|
|
11
|
+
{
|
|
12
|
+
extends: ["github>NullVoxPopuli/renovate:js.json5"],
|
|
13
|
+
packageRules: [],
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Docs:
|
|
2
|
+
// https://docs.renovatebot.com/configuration-options/
|
|
3
|
+
//
|
|
4
|
+
// This config: https://github.com/nullVoxPopuli/renovate
|
|
5
|
+
// - weekly automated updates
|
|
6
|
+
// - requires that packages be released for at least 5 days before opening a PR with that dependency
|
|
7
|
+
// - doesn't constantly rebase open PRs
|
|
8
|
+
// - provides opinionated package rules for reduced PR spam
|
|
9
|
+
// - opens a separate PR to re-roll your lockfile occasionally
|
|
10
|
+
// - appropriate for npm library development
|
|
11
|
+
// (auto-updating of engines, peerDependencies, and dependencies is disabled
|
|
12
|
+
// only devDependencies get automatically upgraded in order ot keep your repo stable)
|
|
13
|
+
{
|
|
14
|
+
extends: ["github>NullVoxPopuli/renovate:npm.json5"],
|
|
15
|
+
packageRules: [],
|
|
16
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { files } from "ember-apply";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @type {import('#types').Layer}
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
label: "renovate [bot]",
|
|
11
|
+
hint: "Automatically have dependencies upgrade for you on a schedule",
|
|
12
|
+
async run(project) {
|
|
13
|
+
if (hasRenovateConfig(project)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let source =
|
|
18
|
+
project.type === "app"
|
|
19
|
+
? join(import.meta.dirname, "files/app.json5")
|
|
20
|
+
: join(import.meta.dirname, "files/library.json5");
|
|
21
|
+
|
|
22
|
+
let destination = join(project.directory, ".github/renovate.json5");
|
|
23
|
+
|
|
24
|
+
await mkdir(join(project.directory, ".github"), { recursive: true });
|
|
25
|
+
await files.copyFileTo(destination, { source });
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @overload
|
|
30
|
+
* @param {import('#utils/project.js').Project} project
|
|
31
|
+
* @param {true} explain
|
|
32
|
+
* @returns {Promise<{ isSetup: boolean; reasons: string[] }>}
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* @overload
|
|
36
|
+
* @param {import('#utils/project.js').Project} project
|
|
37
|
+
* @param {boolean | undefined} [explain]
|
|
38
|
+
* @returns {Promise<boolean>}
|
|
39
|
+
*/
|
|
40
|
+
async isSetup(project, explain) {
|
|
41
|
+
const reasons = [];
|
|
42
|
+
|
|
43
|
+
if (!hasRenovateConfig(project)) {
|
|
44
|
+
reasons.push("renovate.json5 is missing");
|
|
45
|
+
|
|
46
|
+
if (!explain) return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (explain) {
|
|
50
|
+
return {
|
|
51
|
+
isSetup: reasons.length === 0,
|
|
52
|
+
reasons,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return reasons.length === 0;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
*
|
|
62
|
+
* @param {import('#utils/project.js').Project} project
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
function hasRenovateConfig(project) {
|
|
66
|
+
let destination = join(project.directory, ".github/renovate.json5");
|
|
67
|
+
return existsSync(destination);
|
|
68
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { beforeAll, describe, it, expect as hardExpect, afterAll } from "vitest";
|
|
2
|
+
import { generate, layers, reapply } from "#test-helpers";
|
|
3
|
+
|
|
4
|
+
import type { Project } from "ember.nvp";
|
|
5
|
+
import { rm } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
const expect = hardExpect.soft;
|
|
8
|
+
|
|
9
|
+
let githubActionsLayer = layers.find((layer) => layer.name === "github-actions")!;
|
|
10
|
+
|
|
11
|
+
describe("starting with javascript", () => {
|
|
12
|
+
let project: Project;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
project = await generate({
|
|
16
|
+
type: "app",
|
|
17
|
+
packageManager: "pnpm",
|
|
18
|
+
layers: ["git"],
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("applying typescript causes no changes", async () => {
|
|
27
|
+
await reapply(project, ["typescript"]);
|
|
28
|
+
|
|
29
|
+
let result = await githubActionsLayer.isSetup(project);
|
|
30
|
+
|
|
31
|
+
expect(result).toBe(true);
|
|
32
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
33
|
+
expect(project.hasFile("tsconfig.json")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("starting with typescript", () => {
|
|
38
|
+
let project: Project;
|
|
39
|
+
|
|
40
|
+
beforeAll(async () => {
|
|
41
|
+
project = await generate({
|
|
42
|
+
type: "app",
|
|
43
|
+
packageManager: "pnpm",
|
|
44
|
+
layers: ["typescript", "git"],
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("reapplying typescript causes no changes", async () => {
|
|
53
|
+
await reapply(project, ["typescript"]);
|
|
54
|
+
|
|
55
|
+
let result = await githubActionsLayer.isSetup(project);
|
|
56
|
+
|
|
57
|
+
expect(result).toBe(true);
|
|
58
|
+
expect(await project.gitHasDiff()).toBe(false);
|
|
59
|
+
expect(project.hasFile("tsconfig.json")).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("package.json scripts", () => {
|
|
64
|
+
let project: Project;
|
|
65
|
+
|
|
66
|
+
beforeAll(async () => {
|
|
67
|
+
project = await generate({
|
|
68
|
+
type: "app",
|
|
69
|
+
packageManager: "pnpm",
|
|
70
|
+
layers: ["typescript", "git"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let { exitCode } = await project.run("pnpm install");
|
|
74
|
+
|
|
75
|
+
hardExpect(exitCode).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterAll(async () => {
|
|
79
|
+
await rm(project.directory, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("type checking works", async () => {
|
|
83
|
+
let { exitCode } = await project.run("pnpm lint:types");
|
|
84
|
+
|
|
85
|
+
expect(exitCode).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("build works", async () => {
|
|
89
|
+
let { exitCode } = await project.run("pnpm build");
|
|
90
|
+
|
|
91
|
+
expect(exitCode).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
});
|