ember.nvp 0.0.0 → 0.2.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 (92) hide show
  1. package/.github/workflows/ci.yml +41 -0
  2. package/.github/workflows/plan-release.yml +61 -0
  3. package/.github/workflows/publish.yml +41 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc.js +10 -0
  7. package/.release-plan.json +30 -0
  8. package/CHANGELOG.md +54 -0
  9. package/README.md +159 -1
  10. package/RELEASE.md +27 -0
  11. package/package.json +51 -3
  12. package/pnpm-workspace.yaml +3 -0
  13. package/src/bases/README.md +5 -0
  14. package/src/bases/minimal-app/app.test.ts +203 -0
  15. package/src/bases/minimal-app/files/.env.development +8 -0
  16. package/src/bases/minimal-app/files/app/app.ts +21 -0
  17. package/src/bases/minimal-app/files/app/config.ts +30 -0
  18. package/src/bases/minimal-app/files/app/router.ts +51 -0
  19. package/src/bases/minimal-app/files/app/templates/application.gts +5 -0
  20. package/src/bases/minimal-app/files/babel.config.js +43 -0
  21. package/src/bases/minimal-app/files/index.html +17 -0
  22. package/src/bases/minimal-app/files/package.json +57 -0
  23. package/src/bases/minimal-app/files/tsconfig.json +12 -0
  24. package/src/bases/minimal-app/files/vite.config.mjs +13 -0
  25. package/src/bases/minimal-app/index.js +111 -0
  26. package/src/bases/minimal-library/index.js +10 -0
  27. package/src/cli/args.js +57 -0
  28. package/src/cli/generator.js +74 -0
  29. package/src/cli/index.js +79 -0
  30. package/src/cli/questions/layers.js +64 -0
  31. package/src/cli/questions/name.js +38 -0
  32. package/src/cli/questions/ok.js +38 -0
  33. package/src/cli/questions/package-manager.js +42 -0
  34. package/src/cli/questions/path.js +59 -0
  35. package/src/cli/questions/project-type.js +55 -0
  36. package/src/consolidators/linting.js +51 -0
  37. package/src/index.js +2 -0
  38. package/src/layers/eslint-bundled-ember/files/eslint.config.js +14 -0
  39. package/src/layers/eslint-bundled-ember/index.js +91 -0
  40. package/src/layers/eslint-bundled-nvp/app.test.ts +91 -0
  41. package/src/layers/eslint-bundled-nvp/files/eslint.config.js +16 -0
  42. package/src/layers/eslint-bundled-nvp/index.js +91 -0
  43. package/src/layers/eslint-ejected/files/eslint.config.js +37 -0
  44. package/src/layers/eslint-ejected/index.js +95 -0
  45. package/src/layers/git/index.js +33 -0
  46. package/src/layers/github-actions/app.test.ts +73 -0
  47. package/src/layers/github-actions/index.js +200 -0
  48. package/src/layers/index.js +47 -0
  49. package/src/layers/prettier/files/.prettierignore +18 -0
  50. package/src/layers/prettier/files/.prettierrc.js +4 -0
  51. package/src/layers/prettier/index.js +65 -0
  52. package/src/layers/qunit/app.test.ts +90 -0
  53. package/src/layers/qunit/files/run-tests.mjs +30 -0
  54. package/src/layers/qunit/files/testem.cjs +26 -0
  55. package/src/layers/qunit/files/tests/index.html +30 -0
  56. package/src/layers/qunit/files/tests/test-helper.ts +66 -0
  57. package/src/layers/qunit/files/tests/unit/.gitkeep +0 -0
  58. package/src/layers/qunit/index.js +102 -0
  59. package/src/layers/release-plan/index.js +5 -0
  60. package/src/layers/renovate/files/app.json5 +14 -0
  61. package/src/layers/renovate/files/library.json5 +16 -0
  62. package/src/layers/renovate/index.js +68 -0
  63. package/src/layers/typescript/app.test.ts +93 -0
  64. package/src/layers/typescript/index.js +145 -0
  65. package/src/layers/vitest/files/tests/integration/.gitkeep +0 -0
  66. package/src/layers/vitest/files/tests/test-helper.ts +5 -0
  67. package/src/layers/vitest/files/tests/unit/.gitkeep +0 -0
  68. package/src/layers/vitest/files/vitest.config.mjs +18 -0
  69. package/src/layers/vitest/index.js +66 -0
  70. package/src/utils/babel.js +76 -0
  71. package/src/utils/buffer.js +15 -0
  72. package/src/utils/cli.js +45 -0
  73. package/src/utils/cwd.js +1 -0
  74. package/src/utils/fs.js +85 -0
  75. package/src/utils/git.js +41 -0
  76. package/src/utils/git.test.ts +36 -0
  77. package/src/utils/manifest.js +9 -0
  78. package/src/utils/npm.js +29 -0
  79. package/src/utils/object.js +10 -0
  80. package/src/utils/project.js +249 -0
  81. package/src/utils/remove-types.js +90 -0
  82. package/src/utils/types.ts +48 -0
  83. package/test/ember.nvp/cli.test.ts +12 -0
  84. package/test/ember.nvp/layers.test.ts +35 -0
  85. package/test/ember.nvp/permutations.test.ts +200 -0
  86. package/test/manual.sh +11 -0
  87. package/test/test-helpers/permutate.test.ts +70 -0
  88. package/test/test-helpers.ts +175 -0
  89. package/tsconfig.json +15 -0
  90. package/vitest.config.js +9 -0
  91. /package/src/layers/{eslint/index.js → qunit/files/tests/application/.gitkeep} +0 -0
  92. /package/src/layers/{minimal/index.js → qunit/files/tests/rendering/.gitkeep} +0 -0
@@ -0,0 +1,4 @@
1
+ export default {
2
+ printWidth: 100,
3
+ plugins: ["prettier-plugin-ember-template-tag"],
4
+ };
@@ -0,0 +1,65 @@
1
+ import { packageJson, files } from "ember-apply";
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";
7
+
8
+ /**
9
+ * Prettier Layer
10
+ *
11
+ * Adds Prettier for code formatting
12
+ */
13
+ export default {
14
+ label: formatLabel("Prettier", "code formatting"),
15
+ hint: "the defaults",
16
+
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);
22
+
23
+ await packageJson.addDevDependencies(
24
+ await getLatest({
25
+ prettier: "^3.8.1",
26
+ "prettier-plugin-ember-template-tag": "^2.1.2",
27
+ }),
28
+ project.directory,
29
+ );
30
+
31
+ await packageJson.addScripts(
32
+ {
33
+ format: "prettier --write .",
34
+ "lint:prettier": "prettier --check .",
35
+ },
36
+ project.directory,
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;
64
+ },
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,30 @@
1
+ import { chromium } from "playwright";
2
+
3
+ const browser = await chromium.launch();
4
+ const page = await browser.newPage();
5
+
6
+ page.on("console", (msg) => console.log(msg.text()));
7
+
8
+ await page.goto("http://localhost:4173/tests");
9
+
10
+ await page.waitForFunction(
11
+ () => {
12
+ const elem = document.getElementById("qunit-testresult");
13
+ return elem?.classList.contains("complete");
14
+ },
15
+ { timeout: 30000 },
16
+ );
17
+
18
+ const failed = await page.evaluate(() => {
19
+ const elem = document.getElementById("qunit-testresult");
20
+ return elem?.querySelector(".failed")?.textContent;
21
+ });
22
+
23
+ await browser.close();
24
+
25
+ if (failed && parseInt(failed) > 0) {
26
+ console.error(`❌ ${failed} test(s) failed`);
27
+ process.exit(1);
28
+ }
29
+
30
+ console.log("✅ All tests passed!");
@@ -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
+ }
@@ -0,0 +1,30 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Tests</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ </head>
8
+ <body>
9
+ <div id="qunit"></div>
10
+ <div id="qunit-fixture">
11
+ <div id="ember-testing-container">
12
+ <div id="ember-testing"></div>
13
+ </div>
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>
29
+ </body>
30
+ </html>
@@ -0,0 +1,66 @@
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";
11
+ import * as QUnit from "qunit";
12
+ import { setup } from "qunit-dom";
13
+ import { setupEmberOnerrorValidation, start as qunitStart } from "ember-qunit";
14
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15
+ // @ts-expect-error
16
+ import { getGlobalConfig } from "@embroider/macros/src/addon/runtime";
17
+
18
+ import Application from "#app/app";
19
+ import config, { enterTestMode } from "#config";
20
+
21
+ Object.assign(window, {
22
+ visit,
23
+ getSettledState,
24
+ getPendingWaiterState,
25
+ currentURL,
26
+ currentRouteName,
27
+ getOwner,
28
+ snapshotTimers: (label?: string) => {
29
+ console.debug(
30
+ label ?? "snapshotTimers",
31
+ JSON.parse(
32
+ JSON.stringify({
33
+ settled: getSettledState(),
34
+ waiters: getPendingWaiterState(),
35
+ }),
36
+ ),
37
+ );
38
+ },
39
+ });
40
+
41
+ export function start() {
42
+ enterTestMode();
43
+
44
+ const theMacrosGlobal = getGlobalConfig();
45
+
46
+ /**
47
+ * Caveats:
48
+ * - https://github.com/embroider-build/embroider/issues/2660
49
+ * - https://github.com/embroider-build/embroider/issues/1998
50
+ *
51
+ */
52
+ theMacrosGlobal["@embroider/macros"] ||= {};
53
+ theMacrosGlobal["@embroider/macros"].isTesting ||= true;
54
+
55
+ setApplication(Application.create(config.APP));
56
+ setup(QUnit.assert);
57
+ setupEmberOnerrorValidation();
58
+
59
+ QUnit.moduleStart(({ name }) => console.group(name));
60
+ QUnit.testStart(({ name }) => console.group(name));
61
+ QUnit.testDone(() => console.groupEnd());
62
+ QUnit.moduleDone(() => console.groupEnd());
63
+ QUnit.testDone(resetOnerror);
64
+
65
+ qunitStart();
66
+ }
File without changes
@@ -0,0 +1,102 @@
1
+ import { applyFolderTo } from "#utils/fs.js";
2
+ import { getLatest } from "#utils/npm.js";
3
+ import { packageJson, files } from "ember-apply";
4
+ import { join } from "node:path";
5
+
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
+ };
18
+
19
+ /**
20
+ * @type {import('#types').Layer}
21
+ */
22
+ export default {
23
+ label: "QUnit",
24
+
25
+ async run(project) {
26
+ let filePath = join(import.meta.dirname, "files");
27
+ await applyFolderTo(filePath, project);
28
+ let ts = await project.hasOrWantsLayer("typescript");
29
+
30
+ await packageJson.addDevDependencies(
31
+ await getLatest({
32
+ ...deps,
33
+ ...(ts ? tsDeps : {}),
34
+ }),
35
+ project.directory,
36
+ );
37
+
38
+ // Add scripts
39
+ await packageJson.addScripts(
40
+ {
41
+ "build:test": "vite build --mode development",
42
+ "test:ci": "testem ci --port 0",
43
+ test: "pnpm build:test && pnpm test:ci",
44
+ },
45
+ project.directory,
46
+ );
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
+ },
102
+ };
@@ -0,0 +1,5 @@
1
+ export default {
2
+ label: "release-plan",
3
+ description: "release automation so that you can release anywhere",
4
+ run() {},
5
+ };
@@ -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
+ });