@tyyyho/treg 0.1.2
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/README.md +92 -0
- package/package.json +42 -0
- package/scripts/init-project/cli.mjs +173 -0
- package/scripts/init-project/cli.test.mjs +116 -0
- package/scripts/init-project/frameworks/index.mjs +48 -0
- package/scripts/init-project/frameworks/next/index.mjs +10 -0
- package/scripts/init-project/frameworks/node/index.mjs +8 -0
- package/scripts/init-project/frameworks/nuxt/index.mjs +10 -0
- package/scripts/init-project/frameworks/react/index.mjs +35 -0
- package/scripts/init-project/frameworks/react/v18/index.mjs +6 -0
- package/scripts/init-project/frameworks/react/v19/index.mjs +6 -0
- package/scripts/init-project/frameworks/svelte/index.mjs +10 -0
- package/scripts/init-project/frameworks/vue/index.mjs +10 -0
- package/scripts/init-project/frameworks.test.mjs +63 -0
- package/scripts/init-project/index.mjs +89 -0
- package/scripts/init-project/mrm-core.mjs +5 -0
- package/scripts/init-project/mrm-rules/ai-skills.mjs +220 -0
- package/scripts/init-project/mrm-rules/ai-skills.test.mjs +91 -0
- package/scripts/init-project/mrm-rules/format.mjs +55 -0
- package/scripts/init-project/mrm-rules/husky.mjs +78 -0
- package/scripts/init-project/mrm-rules/index.mjs +35 -0
- package/scripts/init-project/mrm-rules/lint.mjs +18 -0
- package/scripts/init-project/mrm-rules/shared.mjs +61 -0
- package/scripts/init-project/mrm-rules/test-jest.mjs +75 -0
- package/scripts/init-project/mrm-rules/test-vitest.mjs +64 -0
- package/scripts/init-project/mrm-rules/typescript.mjs +44 -0
- package/scripts/init-project/package-manager.mjs +68 -0
- package/scripts/init-project/package-manager.test.mjs +21 -0
- package/scripts/init-project/utils.mjs +12 -0
- package/scripts/init-project/utils.test.mjs +22 -0
- package/scripts/init-project.mjs +7 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { packageJson } from "../mrm-core.mjs"
|
|
2
|
+
import { installPackages, withProjectCwd, writeFile } from "./shared.mjs"
|
|
3
|
+
|
|
4
|
+
function getJestConfig(testEnvironment) {
|
|
5
|
+
return `/** @type {import("jest").Config} */
|
|
6
|
+
const config = {
|
|
7
|
+
testEnvironment: "${testEnvironment}",
|
|
8
|
+
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
|
|
9
|
+
testMatch: ["**/*.test.[jt]s?(x)", "**/*.test.mjs"],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = config
|
|
13
|
+
`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getSetupFile(frameworkId) {
|
|
17
|
+
if (frameworkId === "react") {
|
|
18
|
+
return `require("@testing-library/jest-dom")
|
|
19
|
+
|
|
20
|
+
// Jest setup (add custom matchers or globals here)
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
return `// Jest setup (add custom matchers or globals here)
|
|
24
|
+
`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runTestJestRule(context) {
|
|
28
|
+
const { framework, projectDir, pm, force, dryRun } = context
|
|
29
|
+
const deps = ["jest"]
|
|
30
|
+
if (framework.testEnvironment === "jsdom") {
|
|
31
|
+
deps.push("jest-environment-jsdom")
|
|
32
|
+
}
|
|
33
|
+
if (framework.id === "react") {
|
|
34
|
+
deps.push("@testing-library/jest-dom")
|
|
35
|
+
}
|
|
36
|
+
installPackages(projectDir, pm, deps, true, dryRun)
|
|
37
|
+
|
|
38
|
+
await writeFile(
|
|
39
|
+
projectDir,
|
|
40
|
+
"jest.config.js",
|
|
41
|
+
getJestConfig(framework.testEnvironment),
|
|
42
|
+
force,
|
|
43
|
+
dryRun
|
|
44
|
+
)
|
|
45
|
+
await writeFile(
|
|
46
|
+
projectDir,
|
|
47
|
+
"jest.setup.js",
|
|
48
|
+
getSetupFile(framework.id),
|
|
49
|
+
force,
|
|
50
|
+
dryRun
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
withProjectCwd(projectDir, () => {
|
|
54
|
+
if (dryRun) {
|
|
55
|
+
console.log(
|
|
56
|
+
"[dry-run] Would set package scripts: test, test:watch, test:coverage"
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
packageJson()
|
|
61
|
+
.setScript(
|
|
62
|
+
"test",
|
|
63
|
+
"NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests"
|
|
64
|
+
)
|
|
65
|
+
.setScript(
|
|
66
|
+
"test:watch",
|
|
67
|
+
"NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
68
|
+
)
|
|
69
|
+
.setScript(
|
|
70
|
+
"test:coverage",
|
|
71
|
+
"NODE_OPTIONS=--experimental-vm-modules jest --coverage"
|
|
72
|
+
)
|
|
73
|
+
.save()
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { packageJson } from "../mrm-core.mjs"
|
|
2
|
+
import { installPackages, withProjectCwd, writeFile } from "./shared.mjs"
|
|
3
|
+
|
|
4
|
+
function getVitestConfig(framework) {
|
|
5
|
+
return `import { defineConfig } from "vitest/config"
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
test: {
|
|
9
|
+
environment: "${framework.testEnvironment}",
|
|
10
|
+
setupFiles: ["./vitest.setup.js"],
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getVitestSetup(framework) {
|
|
17
|
+
if (framework.id === "react") {
|
|
18
|
+
return `import "@testing-library/jest-dom/vitest"
|
|
19
|
+
`
|
|
20
|
+
}
|
|
21
|
+
return `// Vitest setup
|
|
22
|
+
`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runTestVitestRule(context) {
|
|
26
|
+
const { framework, projectDir, pm, force, dryRun } = context
|
|
27
|
+
const deps = ["vitest"]
|
|
28
|
+
if (framework.testEnvironment === "jsdom") {
|
|
29
|
+
deps.push("jsdom")
|
|
30
|
+
}
|
|
31
|
+
if (framework.id === "react") {
|
|
32
|
+
deps.push("@testing-library/jest-dom")
|
|
33
|
+
}
|
|
34
|
+
installPackages(projectDir, pm, deps, true, dryRun)
|
|
35
|
+
|
|
36
|
+
await writeFile(
|
|
37
|
+
projectDir,
|
|
38
|
+
"vitest.config.mjs",
|
|
39
|
+
getVitestConfig(framework),
|
|
40
|
+
force,
|
|
41
|
+
dryRun
|
|
42
|
+
)
|
|
43
|
+
await writeFile(
|
|
44
|
+
projectDir,
|
|
45
|
+
"vitest.setup.js",
|
|
46
|
+
getVitestSetup(framework),
|
|
47
|
+
force,
|
|
48
|
+
dryRun
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
withProjectCwd(projectDir, () => {
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
console.log(
|
|
54
|
+
"[dry-run] Would set package scripts: test, test:watch, test:coverage"
|
|
55
|
+
)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
packageJson()
|
|
59
|
+
.setScript("test", "vitest run")
|
|
60
|
+
.setScript("test:watch", "vitest")
|
|
61
|
+
.setScript("test:coverage", "vitest run --coverage")
|
|
62
|
+
.save()
|
|
63
|
+
})
|
|
64
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { json, packageJson } from "../mrm-core.mjs"
|
|
2
|
+
import { installPackages, withProjectCwd } from "./shared.mjs"
|
|
3
|
+
|
|
4
|
+
const TS_REQUIRED_OPTIONS = {
|
|
5
|
+
noImplicitAny: true,
|
|
6
|
+
noImplicitThis: true,
|
|
7
|
+
exactOptionalPropertyTypes: true,
|
|
8
|
+
noUncheckedIndexedAccess: true,
|
|
9
|
+
noUnusedLocals: true,
|
|
10
|
+
noUnusedParameters: true,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function runTypescriptRule(context) {
|
|
14
|
+
const { framework, projectDir, pm, dryRun } = context
|
|
15
|
+
installPackages(projectDir, pm, ["typescript"], true, dryRun)
|
|
16
|
+
|
|
17
|
+
withProjectCwd(projectDir, () => {
|
|
18
|
+
if (dryRun) {
|
|
19
|
+
console.log("[dry-run] Would update tsconfig.json")
|
|
20
|
+
console.log("[dry-run] Would set package script: type-check")
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tsconfig = json("tsconfig.json", {
|
|
25
|
+
compilerOptions: {},
|
|
26
|
+
exclude: [],
|
|
27
|
+
})
|
|
28
|
+
const mergedCompilerOptions = {
|
|
29
|
+
...(tsconfig.get("compilerOptions") ?? {}),
|
|
30
|
+
...TS_REQUIRED_OPTIONS,
|
|
31
|
+
}
|
|
32
|
+
const exclude = new Set(tsconfig.get("exclude", []))
|
|
33
|
+
for (const entry of framework.tsRequiredExcludes) {
|
|
34
|
+
exclude.add(entry)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
tsconfig
|
|
38
|
+
.set("compilerOptions", mergedCompilerOptions)
|
|
39
|
+
.set("exclude", Array.from(exclude))
|
|
40
|
+
.save()
|
|
41
|
+
|
|
42
|
+
packageJson().setScript("type-check", "tsc --noEmit").save()
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { execSync } from "node:child_process"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
export function detectPackageManager(projectDir) {
|
|
6
|
+
if (existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
|
|
7
|
+
return "pnpm"
|
|
8
|
+
}
|
|
9
|
+
if (existsSync(path.join(projectDir, "yarn.lock"))) {
|
|
10
|
+
return "yarn"
|
|
11
|
+
}
|
|
12
|
+
if (existsSync(path.join(projectDir, "package-lock.json"))) {
|
|
13
|
+
return "npm"
|
|
14
|
+
}
|
|
15
|
+
return "npm"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getRunCommand(pm) {
|
|
19
|
+
if (pm === "pnpm") return "pnpm"
|
|
20
|
+
if (pm === "yarn") return "yarn"
|
|
21
|
+
return "npm run"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runCommand(command, cwd, dryRun = false) {
|
|
25
|
+
if (dryRun) {
|
|
26
|
+
console.log(`[dry-run] Would run: ${command}`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
execSync(command, { cwd, stdio: "inherit" })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function runScript(pm, scriptName, cwd, dryRun = false) {
|
|
33
|
+
if (pm === "pnpm") {
|
|
34
|
+
runCommand(`pnpm ${scriptName}`, cwd, dryRun)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
if (pm === "yarn") {
|
|
38
|
+
runCommand(`yarn ${scriptName}`, cwd, dryRun)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
runCommand(`npm run ${scriptName}`, cwd, dryRun)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function installPackages(
|
|
45
|
+
pm,
|
|
46
|
+
projectDir,
|
|
47
|
+
packages,
|
|
48
|
+
isDev,
|
|
49
|
+
dryRun = false
|
|
50
|
+
) {
|
|
51
|
+
if (packages.length === 0) return
|
|
52
|
+
|
|
53
|
+
const list = packages.join(" ")
|
|
54
|
+
let command = ""
|
|
55
|
+
|
|
56
|
+
if (pm === "pnpm") {
|
|
57
|
+
command = `pnpm add ${isDev ? "-D " : ""}${list}`
|
|
58
|
+
} else if (pm === "yarn") {
|
|
59
|
+
command = `yarn add ${isDev ? "-D " : ""}${list}`
|
|
60
|
+
} else {
|
|
61
|
+
command = `npm install ${isDev ? "-D " : ""}${list}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(
|
|
65
|
+
`${dryRun ? "[dry-run] " : ""}Installing ${isDev ? "dev " : ""}dependencies: ${packages.join(", ")}`
|
|
66
|
+
)
|
|
67
|
+
runCommand(command, projectDir, dryRun)
|
|
68
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mkdtemp, writeFile } from "node:fs/promises"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { describe, expect, it } from "@jest/globals"
|
|
5
|
+
import { detectPackageManager } from "./package-manager.mjs"
|
|
6
|
+
|
|
7
|
+
describe("detectPackageManager", () => {
|
|
8
|
+
it("defaults to npm when no lockfile exists", async () => {
|
|
9
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "treg-pm-"))
|
|
10
|
+
expect(detectPackageManager(baseDir)).toBe("npm")
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("detects pnpm from lockfile", async () => {
|
|
14
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "treg-pm-"))
|
|
15
|
+
await writeFile(
|
|
16
|
+
path.join(baseDir, "pnpm-lock.yaml"),
|
|
17
|
+
"lockfileVersion: 9\n"
|
|
18
|
+
)
|
|
19
|
+
expect(detectPackageManager(baseDir)).toBe("pnpm")
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function hasPackage(pkg, name) {
|
|
2
|
+
return Boolean(
|
|
3
|
+
pkg.dependencies?.[name] ||
|
|
4
|
+
pkg.devDependencies?.[name] ||
|
|
5
|
+
pkg.peerDependencies?.[name]
|
|
6
|
+
)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatStep(step, total, message, dryRun) {
|
|
10
|
+
const suffix = dryRun ? " [dry-run]" : ""
|
|
11
|
+
return `[${step}/${total}] ${message}${suffix}`
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals"
|
|
2
|
+
import { formatStep, hasPackage } from "./utils.mjs"
|
|
3
|
+
|
|
4
|
+
describe("hasPackage", () => {
|
|
5
|
+
it("checks dependencies and devDependencies", () => {
|
|
6
|
+
const pkg = {
|
|
7
|
+
dependencies: { react: "^19.0.0" },
|
|
8
|
+
devDependencies: { eslint: "^9.0.0" },
|
|
9
|
+
}
|
|
10
|
+
expect(hasPackage(pkg, "react")).toBe(true)
|
|
11
|
+
expect(hasPackage(pkg, "eslint")).toBe(true)
|
|
12
|
+
expect(hasPackage(pkg, "typescript")).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe("formatStep", () => {
|
|
17
|
+
it("appends dry-run suffix when needed", () => {
|
|
18
|
+
expect(formatStep(2, 3, "Run mrm rules", true)).toBe(
|
|
19
|
+
"[2/3] Run mrm rules [dry-run]"
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
})
|