@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
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @tyyyho/treg
2
+
3
+ Treg is a CLI tool for initializing development conventions in existing projects.
4
+
5
+ It installs and configures tools and records clear usage guidelines as skills.
6
+
7
+ Treg helps both human developers and AI agents work within the same set of expectations, reducing configuration drift and long-term maintenance overhead.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ pnpm dlx @tyyyho/treg init <project-dir> --framework react
13
+ # or
14
+ npx @tyyyho/treg init <project-dir> --framework react
15
+ ```
16
+
17
+ By default, all features are applied:
18
+
19
+ - `husky`
20
+ - `typescript`
21
+ - `lint`
22
+ - `format`
23
+ - `test`
24
+
25
+ ## Options
26
+
27
+ ```bash
28
+ npx @tyyyho/treg <command> [projectDir] [options]
29
+
30
+ init Initialize infra rules (requires --framework)
31
+ add Add selected infra features
32
+ list List supported targets
33
+
34
+ --framework <node|react|next|vue|svelte|nuxt>
35
+ Target framework
36
+ --framework-version <major> Optional major version hint (currently react only)
37
+ --pm <pnpm|npm|yarn|auto> Package manager (auto-detected by default)
38
+ --features <lint,format,typescript,test,husky>
39
+ Features to install (all selected by default)
40
+ --test-runner <jest|vitest> Test runner when test feature is enabled
41
+ --force Overwrite existing config files
42
+ --dry-run Show planned changes without writing files
43
+ --skip-husky-install Do not run husky install
44
+ --skills Update AGENTS.md/CLAUDE.md with feature skill guidance
45
+ --help Show help
46
+ ```
47
+
48
+ ## Examples
49
+
50
+ Initialize a React project:
51
+
52
+ ```bash
53
+ npx @tyyyho/treg init . --framework react
54
+ ```
55
+
56
+ Add only lint and format:
57
+
58
+ ```bash
59
+ npx @tyyyho/treg add . --features lint,format
60
+ ```
61
+
62
+ Use Vitest:
63
+
64
+ ```bash
65
+ npx @tyyyho/treg init . --framework node --features test --test-runner vitest
66
+ ```
67
+
68
+ Set framework major version explicitly (for variant rules):
69
+
70
+ ```bash
71
+ npx @tyyyho/treg init . --framework react --framework-version 18
72
+ ```
73
+
74
+ Preview changes only:
75
+
76
+ ```bash
77
+ npx @tyyyho/treg init . --framework react --dry-run
78
+ ```
79
+
80
+ Enable AI skill guidance update:
81
+
82
+ ```bash
83
+ npx @tyyyho/treg add . --features lint,format,husky --skills
84
+ ```
85
+
86
+ ## Publish
87
+
88
+ ```bash
89
+ pnpm install
90
+ pnpm run prepublishOnly
91
+ npm publish --access public
92
+ ```
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@tyyyho/treg",
3
+ "version": "0.1.2",
4
+ "description": "CLI tool for initializing development conventions in existing projects.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "treg": "./scripts/init-project.mjs"
8
+ },
9
+ "files": [
10
+ "scripts/init-project.mjs",
11
+ "scripts/init-project"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "engines": {
17
+ "node": ">=18.0.0"
18
+ },
19
+ "scripts": {
20
+ "lint": "eslint .",
21
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
22
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
23
+ "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
24
+ "lint:check": "eslint . --max-warnings 0",
25
+ "format": "prettier --write .",
26
+ "format:check": "prettier --check .",
27
+ "type-check": "tsc --noEmit",
28
+ "prepare": "husky",
29
+ "prepublishOnly": "pnpm format:check && pnpm lint:check && pnpm type-check && pnpm test"
30
+ },
31
+ "dependencies": {
32
+ "mrm-core": "^7.1.22"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20",
36
+ "eslint": "^9",
37
+ "husky": "^9.1.7",
38
+ "jest": "^30.2.0",
39
+ "prettier": "^3.8.0",
40
+ "typescript": "^5"
41
+ }
42
+ }
@@ -0,0 +1,173 @@
1
+ const ALLOWED_COMMANDS = ["init", "add", "list"]
2
+ const ALLOWED_PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "auto"]
3
+ const ALLOWED_FRAMEWORKS = ["node", "react", "next", "vue", "svelte", "nuxt"]
4
+ const ALLOWED_FEATURES = ["lint", "format", "typescript", "test", "husky"]
5
+ const ALLOWED_TEST_RUNNERS = ["jest", "vitest"]
6
+
7
+ export const USAGE = `Usage: treg <command> [projectDir] [options]
8
+
9
+ Commands:
10
+ init Initialize infra rules in a project (requires --framework)
11
+ add Add selected infra features to an existing project
12
+ list List supported frameworks, features, and test runners
13
+
14
+ Options:
15
+ --framework <node|react|next|vue|svelte|nuxt>
16
+ Target framework
17
+ --framework-version <major> Optional framework major version hint
18
+ --features <lint,format,typescript,test,husky>
19
+ Features to install (all selected by default)
20
+ --test-runner <jest|vitest> Test runner when test feature is enabled
21
+ --pm <pnpm|npm|yarn|auto> Package manager (auto-detected if omitted)
22
+ --force Overwrite existing config files
23
+ --dry-run Print planned changes without writing files
24
+ --skip-husky-install Do not run husky install
25
+ --skills Update AGENTS.md/CLAUDE.md with feature skill guidance
26
+ -h, --help Show help
27
+ `
28
+
29
+ export function parseArgs(argv) {
30
+ const options = {
31
+ command: "init",
32
+ projectDir: null,
33
+ framework: null,
34
+ frameworkVersion: null,
35
+ features: [],
36
+ testRunner: "jest",
37
+ pm: null,
38
+ force: false,
39
+ dryRun: false,
40
+ skipHuskyInstall: false,
41
+ skills: false,
42
+ help: false,
43
+ }
44
+
45
+ let cursor = 0
46
+ const firstArg = argv[0]
47
+ if (firstArg && ALLOWED_COMMANDS.includes(firstArg)) {
48
+ options.command = firstArg
49
+ cursor = 1
50
+ }
51
+
52
+ for (let i = cursor; i < argv.length; i += 1) {
53
+ const arg = argv[i]
54
+ if (arg === "-h" || arg === "--help") {
55
+ options.help = true
56
+ } else if (arg === "--framework") {
57
+ options.framework = argv[i + 1]
58
+ i += 1
59
+ } else if (arg.startsWith("--framework=")) {
60
+ options.framework = arg.split("=")[1]
61
+ } else if (arg === "--framework-version") {
62
+ options.frameworkVersion = argv[i + 1]
63
+ i += 1
64
+ } else if (arg.startsWith("--framework-version=")) {
65
+ options.frameworkVersion = arg.split("=")[1]
66
+ } else if (arg === "--features") {
67
+ options.features.push(...parseCsvValue(argv[i + 1], "--features"))
68
+ i += 1
69
+ } else if (arg.startsWith("--features=")) {
70
+ options.features.push(...parseCsvValue(arg.split("=")[1], "--features"))
71
+ } else if (arg === "--test-runner") {
72
+ options.testRunner = argv[i + 1]
73
+ i += 1
74
+ } else if (arg.startsWith("--test-runner=")) {
75
+ options.testRunner = arg.split("=")[1]
76
+ } else if (arg === "--pm") {
77
+ options.pm = argv[i + 1]
78
+ i += 1
79
+ } else if (arg.startsWith("--pm=")) {
80
+ options.pm = arg.split("=")[1]
81
+ } else if (arg === "--force") {
82
+ options.force = true
83
+ } else if (arg === "--dry-run") {
84
+ options.dryRun = true
85
+ } else if (arg === "--skip-husky-install") {
86
+ options.skipHuskyInstall = true
87
+ } else if (arg === "--skills") {
88
+ options.skills = true
89
+ } else if (!arg.startsWith("-") && !options.projectDir) {
90
+ options.projectDir = arg
91
+ } else {
92
+ throw new Error(`Unknown argument: ${arg}`)
93
+ }
94
+ }
95
+
96
+ validateParsedOptions(options)
97
+ return options
98
+ }
99
+
100
+ function parseCsvValue(rawValue, flagName) {
101
+ if (!rawValue) {
102
+ throw new Error(`Missing value for ${flagName}`)
103
+ }
104
+
105
+ return rawValue
106
+ .split(",")
107
+ .map(item => item.trim())
108
+ .filter(Boolean)
109
+ }
110
+
111
+ function validateParsedOptions(options) {
112
+ if (!ALLOWED_COMMANDS.includes(options.command)) {
113
+ throw new Error(`Unsupported command: ${options.command}`)
114
+ }
115
+
116
+ if (options.pm && !ALLOWED_PACKAGE_MANAGERS.includes(options.pm)) {
117
+ throw new Error(`Unsupported package manager: ${options.pm}`)
118
+ }
119
+
120
+ if (options.framework && !ALLOWED_FRAMEWORKS.includes(options.framework)) {
121
+ throw new Error(`Unsupported framework: ${options.framework}`)
122
+ }
123
+
124
+ if (options.frameworkVersion && !/^\d+$/.test(options.frameworkVersion)) {
125
+ throw new Error(
126
+ "Invalid --framework-version: major version must be numeric"
127
+ )
128
+ }
129
+
130
+ if (
131
+ options.frameworkVersion &&
132
+ options.framework &&
133
+ options.framework !== "react"
134
+ ) {
135
+ throw new Error(
136
+ `Unsupported --framework-version for framework: ${options.framework}`
137
+ )
138
+ }
139
+
140
+ if (!ALLOWED_TEST_RUNNERS.includes(options.testRunner)) {
141
+ throw new Error(`Unsupported test runner: ${options.testRunner}`)
142
+ }
143
+
144
+ for (const feature of options.features) {
145
+ if (!ALLOWED_FEATURES.includes(feature)) {
146
+ throw new Error(`Unsupported feature in --features: ${feature}`)
147
+ }
148
+ }
149
+
150
+ if (options.command === "init" && !options.help && !options.framework) {
151
+ throw new Error("Missing required option: --framework")
152
+ }
153
+ }
154
+
155
+ export function resolveFeatures(options) {
156
+ const selected = new Set(
157
+ options.features.length > 0 ? options.features : ALLOWED_FEATURES
158
+ )
159
+
160
+ return {
161
+ lint: selected.has("lint"),
162
+ format: selected.has("format"),
163
+ typescript: selected.has("typescript"),
164
+ test: selected.has("test"),
165
+ husky: selected.has("husky"),
166
+ }
167
+ }
168
+
169
+ export function printSupportedTargets() {
170
+ console.log("Frameworks: node, react, next, vue, svelte, nuxt")
171
+ console.log("Features: lint, format, typescript, test, husky")
172
+ console.log("Test runners: jest, vitest")
173
+ }
@@ -0,0 +1,116 @@
1
+ import { describe, expect, it } from "@jest/globals"
2
+ import { parseArgs, resolveFeatures } from "./cli.mjs"
3
+
4
+ describe("parseArgs", () => {
5
+ it("parses init command options", () => {
6
+ const parsed = parseArgs([
7
+ "init",
8
+ "demo-app",
9
+ "--framework",
10
+ "react",
11
+ "--features",
12
+ "lint,test",
13
+ "--test-runner",
14
+ "vitest",
15
+ "--pm=npm",
16
+ "--force",
17
+ "--dry-run",
18
+ "--skip-husky-install",
19
+ ])
20
+
21
+ expect(parsed).toEqual({
22
+ command: "init",
23
+ projectDir: "demo-app",
24
+ framework: "react",
25
+ frameworkVersion: null,
26
+ features: ["lint", "test"],
27
+ testRunner: "vitest",
28
+ pm: "npm",
29
+ force: true,
30
+ dryRun: true,
31
+ skipHuskyInstall: true,
32
+ skills: false,
33
+ help: false,
34
+ })
35
+ })
36
+
37
+ it("parses list command", () => {
38
+ const parsed = parseArgs(["list"])
39
+ expect(parsed.command).toBe("list")
40
+ })
41
+
42
+ it("accepts additional frameworks", () => {
43
+ const parsed = parseArgs(["init", "--framework", "nuxt"])
44
+ expect(parsed.framework).toBe("nuxt")
45
+ })
46
+
47
+ it("parses skills flag", () => {
48
+ const parsed = parseArgs(["add", "--skills"])
49
+ expect(parsed.skills).toBe(true)
50
+ })
51
+
52
+ it("accepts svelte framework", () => {
53
+ const parsed = parseArgs(["init", "--framework", "svelte"])
54
+ expect(parsed.framework).toBe("svelte")
55
+ })
56
+
57
+ it("throws when init is missing framework", () => {
58
+ expect(() => parseArgs(["init"])).toThrow(
59
+ "Missing required option: --framework"
60
+ )
61
+ })
62
+
63
+ it("throws for unsupported framework", () => {
64
+ expect(() => parseArgs(["init", "--framework", "angular"])).toThrow(
65
+ "Unsupported framework: angular"
66
+ )
67
+ })
68
+
69
+ it("throws for unsupported feature", () => {
70
+ expect(() =>
71
+ parseArgs(["init", "--framework", "node", "--features", "husky,ai"])
72
+ ).toThrow("Unsupported feature in --features: ai")
73
+ })
74
+
75
+ it("throws for non-numeric framework version", () => {
76
+ expect(() =>
77
+ parseArgs([
78
+ "init",
79
+ "--framework",
80
+ "react",
81
+ "--framework-version",
82
+ "latest",
83
+ ])
84
+ ).toThrow("Invalid --framework-version: major version must be numeric")
85
+ })
86
+
87
+ it("throws when non-react uses framework version", () => {
88
+ expect(() =>
89
+ parseArgs(["init", "--framework", "vue", "--framework-version", "3"])
90
+ ).toThrow("Unsupported --framework-version for framework: vue")
91
+ })
92
+ })
93
+
94
+ describe("resolveFeatures", () => {
95
+ it("enables all features by default", () => {
96
+ expect(resolveFeatures(parseArgs(["add"]))).toEqual({
97
+ lint: true,
98
+ format: true,
99
+ typescript: true,
100
+ test: true,
101
+ husky: true,
102
+ })
103
+ })
104
+
105
+ it("uses selected features", () => {
106
+ expect(
107
+ resolveFeatures(parseArgs(["add", "--features", "lint,format,husky"]))
108
+ ).toEqual({
109
+ lint: true,
110
+ format: true,
111
+ typescript: false,
112
+ test: false,
113
+ husky: true,
114
+ })
115
+ })
116
+ })
@@ -0,0 +1,48 @@
1
+ import { nextFramework } from "./next/index.mjs"
2
+ import { nodeFramework } from "./node/index.mjs"
3
+ import { nuxtFramework } from "./nuxt/index.mjs"
4
+ import { reactFramework, resolveReactFramework } from "./react/index.mjs"
5
+ import { svelteFramework } from "./svelte/index.mjs"
6
+ import { vueFramework } from "./vue/index.mjs"
7
+
8
+ const FRAMEWORK_REGISTRY = {
9
+ next: nextFramework,
10
+ node: nodeFramework,
11
+ nuxt: nuxtFramework,
12
+ react: reactFramework,
13
+ svelte: svelteFramework,
14
+ vue: vueFramework,
15
+ }
16
+
17
+ const FRAMEWORK_DETECT_ORDER = [
18
+ nuxtFramework,
19
+ nextFramework,
20
+ reactFramework,
21
+ vueFramework,
22
+ svelteFramework,
23
+ nodeFramework,
24
+ ]
25
+
26
+ export function resolveFramework(frameworkArg, frameworkVersion, packageJson) {
27
+ if (frameworkArg === "react") {
28
+ return resolveReactFramework(packageJson, frameworkVersion)
29
+ }
30
+
31
+ if (frameworkArg) {
32
+ return FRAMEWORK_REGISTRY[frameworkArg]
33
+ }
34
+
35
+ const detected = detectFramework(packageJson)
36
+ if (detected.id === "react") {
37
+ return resolveReactFramework(packageJson, frameworkVersion)
38
+ }
39
+
40
+ return detected
41
+ }
42
+
43
+ export function detectFramework(packageJson) {
44
+ const matched = FRAMEWORK_DETECT_ORDER.find(framework =>
45
+ framework.matches(packageJson)
46
+ )
47
+ return matched ?? nodeFramework
48
+ }
@@ -0,0 +1,10 @@
1
+ import { hasPackage } from "../../utils.mjs"
2
+
3
+ export const nextFramework = {
4
+ id: "next",
5
+ testEnvironment: "jsdom",
6
+ tsRequiredExcludes: [".next", "dist", "coverage", "jest.config.js", "public"],
7
+ matches(packageJson) {
8
+ return hasPackage(packageJson, "next")
9
+ },
10
+ }
@@ -0,0 +1,8 @@
1
+ export const nodeFramework = {
2
+ id: "node",
3
+ testEnvironment: "node",
4
+ tsRequiredExcludes: ["dist", "coverage"],
5
+ matches() {
6
+ return true
7
+ },
8
+ }
@@ -0,0 +1,10 @@
1
+ import { hasPackage } from "../../utils.mjs"
2
+
3
+ export const nuxtFramework = {
4
+ id: "nuxt",
5
+ testEnvironment: "jsdom",
6
+ tsRequiredExcludes: [".nuxt", ".output", "dist", "coverage", "public"],
7
+ matches(packageJson) {
8
+ return hasPackage(packageJson, "nuxt")
9
+ },
10
+ }
@@ -0,0 +1,35 @@
1
+ import { hasPackage } from "../../utils.mjs"
2
+ import { reactV18Framework } from "./v18/index.mjs"
3
+ import { reactV19Framework } from "./v19/index.mjs"
4
+
5
+ export const reactFramework = {
6
+ id: "react",
7
+ variant: "v19",
8
+ testEnvironment: "jsdom",
9
+ tsRequiredExcludes: ["dist", "coverage", "jest.config.js", "public"],
10
+ matches(packageJson) {
11
+ return (
12
+ hasPackage(packageJson, "react") || hasPackage(packageJson, "react-dom")
13
+ )
14
+ },
15
+ }
16
+
17
+ const REACT_VARIANTS = {
18
+ 18: reactV18Framework,
19
+ 19: reactV19Framework,
20
+ }
21
+
22
+ export function resolveReactFramework(packageJson, frameworkVersion) {
23
+ if (frameworkVersion && REACT_VARIANTS[frameworkVersion]) {
24
+ return REACT_VARIANTS[frameworkVersion]
25
+ }
26
+
27
+ const detected =
28
+ packageJson?.dependencies?.react ?? packageJson?.devDependencies?.react
29
+ const major = typeof detected === "string" ? detected.match(/\d+/)?.[0] : null
30
+ if (major && REACT_VARIANTS[major]) {
31
+ return REACT_VARIANTS[major]
32
+ }
33
+
34
+ return reactFramework
35
+ }
@@ -0,0 +1,6 @@
1
+ export const reactV18Framework = {
2
+ id: "react",
3
+ variant: "v18",
4
+ testEnvironment: "jsdom",
5
+ tsRequiredExcludes: ["dist", "coverage", "jest.config.js", "public"],
6
+ }
@@ -0,0 +1,6 @@
1
+ export const reactV19Framework = {
2
+ id: "react",
3
+ variant: "v19",
4
+ testEnvironment: "jsdom",
5
+ tsRequiredExcludes: ["dist", "coverage", "jest.config.js", "public"],
6
+ }
@@ -0,0 +1,10 @@
1
+ import { hasPackage } from "../../utils.mjs"
2
+
3
+ export const svelteFramework = {
4
+ id: "svelte",
5
+ testEnvironment: "jsdom",
6
+ tsRequiredExcludes: ["dist", "coverage", ".svelte-kit"],
7
+ matches(packageJson) {
8
+ return hasPackage(packageJson, "svelte")
9
+ },
10
+ }
@@ -0,0 +1,10 @@
1
+ import { hasPackage } from "../../utils.mjs"
2
+
3
+ export const vueFramework = {
4
+ id: "vue",
5
+ testEnvironment: "jsdom",
6
+ tsRequiredExcludes: ["dist", "coverage"],
7
+ matches(packageJson) {
8
+ return hasPackage(packageJson, "vue")
9
+ },
10
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "@jest/globals"
2
+ import { detectFramework, resolveFramework } from "./frameworks/index.mjs"
3
+
4
+ describe("frameworks", () => {
5
+ it("detects nuxt before next", () => {
6
+ const framework = detectFramework({
7
+ dependencies: { nuxt: "4.0.0", next: "15.0.0", react: "19.0.0" },
8
+ })
9
+ expect(framework.id).toBe("nuxt")
10
+ })
11
+
12
+ it("detects next before react", () => {
13
+ const framework = detectFramework({
14
+ dependencies: { next: "15.0.0", react: "19.0.0" },
15
+ })
16
+ expect(framework.id).toBe("next")
17
+ })
18
+
19
+ it("detects react from dependencies", () => {
20
+ const framework = detectFramework({ dependencies: { react: "19.0.0" } })
21
+ expect(framework.id).toBe("react")
22
+ })
23
+
24
+ it("detects vue from dependencies", () => {
25
+ expect(detectFramework({ dependencies: { vue: "3.5.0" } }).id).toBe("vue")
26
+ })
27
+
28
+ it("detects svelte from dependencies", () => {
29
+ expect(detectFramework({ dependencies: { svelte: "5.0.0" } }).id).toBe(
30
+ "svelte"
31
+ )
32
+ })
33
+
34
+ it("falls back to node", () => {
35
+ expect(detectFramework({ dependencies: {} }).id).toBe("node")
36
+ })
37
+
38
+ it("resolves explicit framework", () => {
39
+ expect(
40
+ resolveFramework("node", null, { dependencies: { react: "19.0.0" } }).id
41
+ ).toBe("node")
42
+ })
43
+
44
+ it("resolves explicit nuxt framework", () => {
45
+ expect(resolveFramework("nuxt", null, { dependencies: {} }).id).toBe("nuxt")
46
+ })
47
+
48
+ it("resolves react v18 from --framework-version", () => {
49
+ const framework = resolveFramework("react", "18", {
50
+ dependencies: { react: "^19.0.0" },
51
+ })
52
+ expect(framework.id).toBe("react")
53
+ expect(framework.variant).toBe("v18")
54
+ })
55
+
56
+ it("resolves detected react v19 from package version", () => {
57
+ const framework = resolveFramework(null, null, {
58
+ dependencies: { react: "^19.2.0" },
59
+ })
60
+ expect(framework.id).toBe("react")
61
+ expect(framework.variant).toBe("v19")
62
+ })
63
+ })