@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.
Files changed (31) hide show
  1. package/README.md +92 -0
  2. package/package.json +42 -0
  3. package/scripts/init-project/cli.mjs +173 -0
  4. package/scripts/init-project/cli.test.mjs +116 -0
  5. package/scripts/init-project/frameworks/index.mjs +48 -0
  6. package/scripts/init-project/frameworks/next/index.mjs +10 -0
  7. package/scripts/init-project/frameworks/node/index.mjs +8 -0
  8. package/scripts/init-project/frameworks/nuxt/index.mjs +10 -0
  9. package/scripts/init-project/frameworks/react/index.mjs +35 -0
  10. package/scripts/init-project/frameworks/react/v18/index.mjs +6 -0
  11. package/scripts/init-project/frameworks/react/v19/index.mjs +6 -0
  12. package/scripts/init-project/frameworks/svelte/index.mjs +10 -0
  13. package/scripts/init-project/frameworks/vue/index.mjs +10 -0
  14. package/scripts/init-project/frameworks.test.mjs +63 -0
  15. package/scripts/init-project/index.mjs +89 -0
  16. package/scripts/init-project/mrm-core.mjs +5 -0
  17. package/scripts/init-project/mrm-rules/ai-skills.mjs +220 -0
  18. package/scripts/init-project/mrm-rules/ai-skills.test.mjs +91 -0
  19. package/scripts/init-project/mrm-rules/format.mjs +55 -0
  20. package/scripts/init-project/mrm-rules/husky.mjs +78 -0
  21. package/scripts/init-project/mrm-rules/index.mjs +35 -0
  22. package/scripts/init-project/mrm-rules/lint.mjs +18 -0
  23. package/scripts/init-project/mrm-rules/shared.mjs +61 -0
  24. package/scripts/init-project/mrm-rules/test-jest.mjs +75 -0
  25. package/scripts/init-project/mrm-rules/test-vitest.mjs +64 -0
  26. package/scripts/init-project/mrm-rules/typescript.mjs +44 -0
  27. package/scripts/init-project/package-manager.mjs +68 -0
  28. package/scripts/init-project/package-manager.test.mjs +21 -0
  29. package/scripts/init-project/utils.mjs +12 -0
  30. package/scripts/init-project/utils.test.mjs +22 -0
  31. 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
+ })
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "./init-project/index.mjs"
3
+
4
+ main().catch(error => {
5
+ console.error(error)
6
+ process.exitCode = 1
7
+ })