@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
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,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,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,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
|
+
})
|