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.
- package/.github/workflows/ci.yml +41 -0
- package/.github/workflows/plan-release.yml +61 -0
- package/.github/workflows/publish.yml +41 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc.js +10 -0
- package/.release-plan.json +30 -0
- package/CHANGELOG.md +54 -0
- package/README.md +159 -1
- package/RELEASE.md +27 -0
- package/package.json +51 -3
- 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 +30 -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/bases/minimal-app/files/babel.config.js +43 -0
- package/src/bases/minimal-app/files/index.html +17 -0
- package/src/bases/minimal-app/files/package.json +57 -0
- package/src/bases/minimal-app/files/tsconfig.json +12 -0
- package/src/bases/minimal-app/files/vite.config.mjs +13 -0
- 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 +74 -0
- package/src/cli/index.js +79 -0
- package/src/cli/questions/layers.js +64 -0
- package/src/cli/questions/name.js +38 -0
- package/src/cli/questions/ok.js +38 -0
- package/src/cli/questions/package-manager.js +42 -0
- 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/files/eslint.config.js +37 -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/.prettierignore +18 -0
- package/src/layers/prettier/files/.prettierrc.js +4 -0
- package/src/layers/prettier/index.js +65 -0
- package/src/layers/qunit/app.test.ts +90 -0
- package/src/layers/qunit/files/run-tests.mjs +30 -0
- package/src/layers/qunit/files/testem.cjs +26 -0
- package/src/layers/qunit/files/tests/index.html +30 -0
- package/src/layers/qunit/files/tests/test-helper.ts +66 -0
- package/src/layers/qunit/files/tests/unit/.gitkeep +0 -0
- package/src/layers/qunit/index.js +102 -0
- 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/tests/integration/.gitkeep +0 -0
- package/src/layers/vitest/files/tests/test-helper.ts +5 -0
- package/src/layers/vitest/files/tests/unit/.gitkeep +0 -0
- package/src/layers/vitest/files/vitest.config.mjs +18 -0
- package/src/layers/vitest/index.js +66 -0
- 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 +15 -0
- package/vitest.config.js +9 -0
- /package/src/layers/{eslint/index.js → qunit/files/tests/application/.gitkeep} +0 -0
- /package/src/layers/{minimal/index.js → qunit/files/tests/rendering/.gitkeep} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { permutate } from "#test-helpers";
|
|
3
|
+
|
|
4
|
+
describe("permutate", () => {
|
|
5
|
+
it("generates all permutations", () => {
|
|
6
|
+
const result = permutate(["a", "b", "c"]);
|
|
7
|
+
|
|
8
|
+
expect(result).toEqual([
|
|
9
|
+
["a"],
|
|
10
|
+
["a", "b"],
|
|
11
|
+
["a", "b", "c"],
|
|
12
|
+
["a", "c"],
|
|
13
|
+
["b"],
|
|
14
|
+
["b", "c"],
|
|
15
|
+
["c"],
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("skips duplicate entries", () => {
|
|
20
|
+
const result = permutate(["a", "b", "b"]);
|
|
21
|
+
|
|
22
|
+
// Should not include ["a", "b"] twice
|
|
23
|
+
const count = result.filter((perm) => perm.join(",") === "a,b").length;
|
|
24
|
+
expect(count).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("enforces mutual exclusivity for eslint-prefixed entries", () => {
|
|
28
|
+
const result = permutate([
|
|
29
|
+
"eslint-bundled-ember",
|
|
30
|
+
"eslint-bundled-nvp",
|
|
31
|
+
"eslint-ejected",
|
|
32
|
+
"other",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Check that no permutation has more than one eslint-prefixed entry
|
|
36
|
+
for (const permutation of result) {
|
|
37
|
+
const eslintCount = permutation.filter((item) => item.startsWith("eslint-")).length;
|
|
38
|
+
expect(eslintCount).toBeLessThanOrEqual(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("allows permutations with 0 or 1 eslint entry", () => {
|
|
43
|
+
const result = permutate(["eslint-bundled-ember", "eslint-bundled-nvp", "other"]);
|
|
44
|
+
|
|
45
|
+
const hasNoEslint = result.some((perm) => !perm.some((item) => item.startsWith("eslint-")));
|
|
46
|
+
const hasOneEslint = result.some(
|
|
47
|
+
(perm) => perm.filter((item) => item.startsWith("eslint-")).length === 1,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(hasNoEslint).toBe(true);
|
|
51
|
+
expect(hasOneEslint).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("generates valid permutations with mixed items", () => {
|
|
55
|
+
const result = permutate(["a", "eslint-foo", "eslint-bar", "b"]);
|
|
56
|
+
|
|
57
|
+
// Verify all permutations contain at most 1 eslint entry
|
|
58
|
+
for (const permutation of result) {
|
|
59
|
+
const eslintCount = permutation.filter((item) => item.startsWith("eslint-")).length;
|
|
60
|
+
expect(eslintCount).toBeLessThanOrEqual(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Should have permutations like: ["a"], ["a", "eslint-foo"], ["a", "b"], etc.
|
|
64
|
+
// but NOT ["a", "eslint-foo", "eslint-bar"]
|
|
65
|
+
const hasMultipleEslint = result.some(
|
|
66
|
+
(perm) => perm.filter((item) => item.startsWith("eslint-")).length > 1,
|
|
67
|
+
);
|
|
68
|
+
expect(hasMultipleEslint).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { styleText } from "node:util";
|
|
4
|
+
import { Readable, Writable } from "node:stream";
|
|
5
|
+
import { mkdtemp } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { generateProject, Project } from "ember.nvp";
|
|
8
|
+
import { discoverLayers } from "#layers";
|
|
9
|
+
import type { DiscoveredLayer } from "#types";
|
|
10
|
+
import { expect } from "vitest";
|
|
11
|
+
|
|
12
|
+
const minimalApp = "minimal-app";
|
|
13
|
+
const minimalAddon = "minimal-library";
|
|
14
|
+
|
|
15
|
+
export const bases = [minimalApp, minimalAddon];
|
|
16
|
+
|
|
17
|
+
export const layers = await discoverLayers();
|
|
18
|
+
|
|
19
|
+
export function permutate(toPermutate: string[]): string[][] {
|
|
20
|
+
const out: string[][] = [];
|
|
21
|
+
|
|
22
|
+
function backtrack(startIndex: number, prefix: string[], hasEslint: boolean) {
|
|
23
|
+
if (prefix.length > 0) out.push(prefix.slice());
|
|
24
|
+
|
|
25
|
+
for (let i = startIndex; i < toPermutate.length; i++) {
|
|
26
|
+
if (i > startIndex && toPermutate[i] === toPermutate[i - 1]) {
|
|
27
|
+
// skip duplicates (a,b,c == c,b,a)
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let option = toPermutate[i];
|
|
32
|
+
|
|
33
|
+
// something has gone wrong if this is undefined
|
|
34
|
+
// (silly TS not understanding loop bounds)
|
|
35
|
+
if (!option) continue;
|
|
36
|
+
|
|
37
|
+
const isEslint = option.startsWith("eslint-");
|
|
38
|
+
|
|
39
|
+
// Skip this option if it's an eslint entry and we already have one
|
|
40
|
+
// (enforce mutual exclusivity for eslint-prefixed entries)
|
|
41
|
+
if (isEslint && hasEslint) continue;
|
|
42
|
+
|
|
43
|
+
prefix.push(option);
|
|
44
|
+
backtrack(i + 1, prefix, hasEslint || isEslint);
|
|
45
|
+
prefix.pop();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
backtrack(0, [], false);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function mktemp(name = "ember.nvp_test-") {
|
|
54
|
+
return await mkdtemp(join(tmpdir(), `${name}-`));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function reapply(project: Project, layers: string[]) {
|
|
58
|
+
return await generate({
|
|
59
|
+
directory: project.directory,
|
|
60
|
+
layers,
|
|
61
|
+
name: project.name,
|
|
62
|
+
type: project.type,
|
|
63
|
+
packageManager: project.packageManager,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function generate({
|
|
68
|
+
layers: layerNames = [],
|
|
69
|
+
name = "my-app",
|
|
70
|
+
type = "app",
|
|
71
|
+
packageManager = "pnpm",
|
|
72
|
+
directory,
|
|
73
|
+
}: {
|
|
74
|
+
directory?: string;
|
|
75
|
+
layers?: string[];
|
|
76
|
+
name?: string;
|
|
77
|
+
type?: "app" | "library";
|
|
78
|
+
packageManager?: "pnpm" | "npm";
|
|
79
|
+
}): Promise<Project> {
|
|
80
|
+
const tempDir = directory ?? (await mktemp(name));
|
|
81
|
+
|
|
82
|
+
let selectedLayers = layers.filter((layer) => layerNames.includes(layer.name));
|
|
83
|
+
let project = new Project(tempDir, {
|
|
84
|
+
name,
|
|
85
|
+
type,
|
|
86
|
+
path: tempDir,
|
|
87
|
+
layers: selectedLayers,
|
|
88
|
+
packageManager,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await generateProject(project);
|
|
92
|
+
|
|
93
|
+
return project;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cliPath = join(import.meta.dirname, "../src/cli/index.js");
|
|
97
|
+
|
|
98
|
+
export function cli(args: string[] = []) {
|
|
99
|
+
let cmd = `node ${cliPath} ${args.join(" ")}`;
|
|
100
|
+
|
|
101
|
+
console.log(`Running '${styleText("cyan", cmd)}'`);
|
|
102
|
+
|
|
103
|
+
let output = new MockWritable();
|
|
104
|
+
let input = new MockReadable();
|
|
105
|
+
|
|
106
|
+
let execaPromise = execa("node", args, { shell: true, stdout: output, stdin: input });
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
execaPromise,
|
|
110
|
+
output,
|
|
111
|
+
input,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class MockWritable extends Writable {
|
|
116
|
+
public buffer: string[] = [];
|
|
117
|
+
public isTTY = false;
|
|
118
|
+
public columns = 80;
|
|
119
|
+
public rows = 20;
|
|
120
|
+
|
|
121
|
+
_write(
|
|
122
|
+
chunk: any,
|
|
123
|
+
_encoding: BufferEncoding,
|
|
124
|
+
callback: (error?: Error | null | undefined) => void,
|
|
125
|
+
): void {
|
|
126
|
+
this.buffer.push(chunk.toString());
|
|
127
|
+
callback();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class MockReadable extends Readable {
|
|
132
|
+
protected _buffer: unknown[] | null = [];
|
|
133
|
+
|
|
134
|
+
_read() {
|
|
135
|
+
if (this._buffer === null) {
|
|
136
|
+
this.push(null);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const val of this._buffer) {
|
|
141
|
+
this.push(val);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this._buffer = [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pushValue(val: unknown): void {
|
|
148
|
+
this._buffer?.push(val);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
close(): void {
|
|
152
|
+
this._buffer = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function build(project: Project, mode = "development") {
|
|
157
|
+
return await execa("pnpm", ["vite", "build", "--mode", mode], {
|
|
158
|
+
cwd: project.directory,
|
|
159
|
+
env: {
|
|
160
|
+
NODE_ENV: mode,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function expectIsSetup(project: Project, layer: DiscoveredLayer) {
|
|
166
|
+
let result = await layer.isSetup(project, true);
|
|
167
|
+
|
|
168
|
+
if (typeof result === "object") {
|
|
169
|
+
expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
|
|
170
|
+
expect(result.isSetup, `${layer.name} is setup`).toBe(true);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
expect(result, `${layer.name} is setup`).toBe(true);
|
|
175
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@ember/library-tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"checkJs": true
|
|
6
|
+
},
|
|
7
|
+
"types": ["ember-source/types", "@glint/ember-tsc/types", "vite/client", "vitest/importMeta"],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"node_modules",
|
|
10
|
+
"dist",
|
|
11
|
+
"src/**/layers/*/files/**",
|
|
12
|
+
"src/**/bases/*/files/**",
|
|
13
|
+
"my-app"
|
|
14
|
+
]
|
|
15
|
+
}
|
package/vitest.config.js
ADDED
|
File without changes
|
|
File without changes
|