javi-forge 0.1.0 → 1.1.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/.releaserc +2 -1
- package/README.md +143 -31
- package/ai-config/commands/workflows/diagnose.md +70 -0
- package/ai-config/commands/workflows/discover.md +86 -0
- package/ci-local/ci-local.sh +18 -0
- package/ci-local/docker/node.Dockerfile +7 -0
- package/ci-local/hooks/commit-msg +0 -0
- package/ci-local/hooks/pre-commit +0 -0
- package/ci-local/hooks/pre-push +0 -0
- package/ci-local/install.sh +0 -0
- package/dist/commands/doctor.js +24 -1
- package/dist/commands/init.js +48 -1
- package/dist/commands/llmstxt.d.ts +9 -0
- package/dist/commands/llmstxt.js +93 -0
- package/dist/commands/llmstxt.test.d.ts +2 -0
- package/dist/commands/plugin.d.ts +24 -0
- package/dist/commands/plugin.js +78 -0
- package/dist/commands/plugin.test.d.ts +2 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +8 -0
- package/dist/index.js +33 -4
- package/dist/lib/plugin.d.ts +39 -0
- package/dist/lib/plugin.js +228 -0
- package/dist/lib/plugin.test.d.ts +2 -0
- package/dist/types/index.d.ts +42 -0
- package/dist/ui/App.d.ts +2 -1
- package/dist/ui/App.js +2 -1
- package/dist/ui/LlmsTxt.d.ts +8 -0
- package/dist/ui/LlmsTxt.js +48 -0
- package/dist/ui/Plugin.d.ts +9 -0
- package/dist/ui/Plugin.js +96 -0
- package/lib/common.sh +183 -0
- package/modules/obsidian-brain/README.md +32 -0
- package/modules/obsidian-brain/core/templates/braindump.md +15 -0
- package/modules/obsidian-brain/core/templates/consolidation.md +42 -0
- package/modules/obsidian-brain/core/templates/daily-note.md +18 -0
- package/modules/obsidian-brain/core/templates/resource-capture.md +14 -0
- package/modules/obsidian-brain/developer/templates/adr.md +40 -0
- package/modules/obsidian-brain/developer/templates/coding-session.md +24 -0
- package/modules/obsidian-brain/developer/templates/debug-journal.md +22 -0
- package/modules/obsidian-brain/developer/templates/sdd-feedback.md +27 -0
- package/modules/obsidian-brain/developer/templates/tech-debt.md +20 -0
- package/modules/obsidian-brain/pm-lead/templates/daily-brief.md +25 -0
- package/modules/obsidian-brain/pm-lead/templates/meeting-notes.md +24 -0
- package/modules/obsidian-brain/pm-lead/templates/risk-registry.md +12 -0
- package/modules/obsidian-brain/pm-lead/templates/sprint-review.md +27 -0
- package/modules/obsidian-brain/pm-lead/templates/stakeholder-update.md +24 -0
- package/modules/obsidian-brain/pm-lead/templates/team-intelligence.md +19 -0
- package/modules/obsidian-brain/pm-lead/templates/weekly-brief.md +29 -0
- package/package.json +12 -12
- package/schemas/plugin.schema.json +62 -0
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/analyze.test.d.ts.map +0 -1
- package/dist/commands/analyze.test.js +0 -145
- package/dist/commands/analyze.test.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/doctor.test.d.ts.map +0 -1
- package/dist/commands/doctor.test.js +0 -200
- package/dist/commands/doctor.test.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/init.test.d.ts.map +0 -1
- package/dist/commands/init.test.js +0 -271
- package/dist/commands/init.test.js.map +0 -1
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/e2e/aggressive.e2e.test.d.ts.map +0 -1
- package/dist/e2e/aggressive.e2e.test.js +0 -350
- package/dist/e2e/aggressive.e2e.test.js.map +0 -1
- package/dist/e2e/commands.e2e.test.d.ts.map +0 -1
- package/dist/e2e/commands.e2e.test.js +0 -213
- package/dist/e2e/commands.e2e.test.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/common.test.d.ts.map +0 -1
- package/dist/lib/common.test.js +0 -316
- package/dist/lib/common.test.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/frontmatter.test.d.ts.map +0 -1
- package/dist/lib/frontmatter.test.js +0 -257
- package/dist/lib/frontmatter.test.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/lib/template.test.d.ts.map +0 -1
- package/dist/lib/template.test.js +0 -201
- package/dist/lib/template.test.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/SyncUI.d.ts.map +0 -1
- package/dist/ui/SyncUI.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
- package/src/commands/analyze.test.ts +0 -145
- package/src/commands/analyze.ts +0 -69
- package/src/commands/doctor.test.ts +0 -208
- package/src/commands/doctor.ts +0 -163
- package/src/commands/init.test.ts +0 -298
- package/src/commands/init.ts +0 -285
- package/src/constants.ts +0 -69
- package/src/e2e/aggressive.e2e.test.ts +0 -557
- package/src/e2e/commands.e2e.test.ts +0 -298
- package/src/index.tsx +0 -106
- package/src/lib/common.test.ts +0 -318
- package/src/lib/common.ts +0 -127
- package/src/lib/frontmatter.test.ts +0 -291
- package/src/lib/frontmatter.ts +0 -77
- package/src/lib/template.test.ts +0 -226
- package/src/lib/template.ts +0 -99
- package/src/types/index.ts +0 -53
- package/src/ui/AnalyzeUI.tsx +0 -133
- package/src/ui/App.tsx +0 -175
- package/src/ui/CIContext.tsx +0 -25
- package/src/ui/CISelector.tsx +0 -72
- package/src/ui/Doctor.tsx +0 -122
- package/src/ui/Header.tsx +0 -48
- package/src/ui/MemorySelector.tsx +0 -73
- package/src/ui/NameInput.tsx +0 -82
- package/src/ui/OptionSelector.tsx +0 -100
- package/src/ui/Progress.tsx +0 -88
- package/src/ui/StackSelector.tsx +0 -101
- package/src/ui/Summary.tsx +0 -134
- package/src/ui/Welcome.tsx +0 -54
- package/src/ui/theme.ts +0 -10
- package/stryker.config.json +0 -19
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -16
package/src/lib/common.test.ts
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
// ── Mock fs-extra ────────────────────────────────────────────────────────────
|
|
5
|
-
vi.mock('fs-extra', () => {
|
|
6
|
-
const mockFs = {
|
|
7
|
-
pathExists: vi.fn(),
|
|
8
|
-
readJson: vi.fn(),
|
|
9
|
-
readFile: vi.fn(),
|
|
10
|
-
copy: vi.fn(),
|
|
11
|
-
ensureDir: vi.fn(),
|
|
12
|
-
}
|
|
13
|
-
return { default: mockFs, ...mockFs }
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
import fs from 'fs-extra'
|
|
17
|
-
import { detectStack, backupIfExists, isGitRepo } from './common.js'
|
|
18
|
-
|
|
19
|
-
const mockedFs = vi.mocked(fs)
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
vi.resetAllMocks()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
-
// detectStack
|
|
27
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
-
describe('detectStack', () => {
|
|
29
|
-
function mockFileExists(existingFiles: string[]) {
|
|
30
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
31
|
-
const p = String(filePath)
|
|
32
|
-
return existingFiles.some(f => p.endsWith(f))
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
it('detects build.gradle → java-gradle', async () => {
|
|
37
|
-
mockFileExists(['build.gradle'])
|
|
38
|
-
mockedFs.readFile.mockResolvedValue('sourceCompatibility = 21' as never)
|
|
39
|
-
const result = await detectStack('/project')
|
|
40
|
-
expect(result).not.toBeNull()
|
|
41
|
-
expect(result!.stackType).toBe('java-gradle')
|
|
42
|
-
expect(result!.buildTool).toBe('gradle')
|
|
43
|
-
expect(result!.javaVersion).toBe('21')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('detects build.gradle.kts → java-gradle', async () => {
|
|
47
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
48
|
-
const p = String(filePath)
|
|
49
|
-
if (p.endsWith('build.gradle')) return false
|
|
50
|
-
if (p.endsWith('build.gradle.kts')) return true
|
|
51
|
-
return false
|
|
52
|
-
})
|
|
53
|
-
mockedFs.readFile.mockResolvedValue('jvmTarget = "17"' as never)
|
|
54
|
-
const result = await detectStack('/project')
|
|
55
|
-
expect(result).not.toBeNull()
|
|
56
|
-
expect(result!.stackType).toBe('java-gradle')
|
|
57
|
-
expect(result!.javaVersion).toBe('17')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('detects pom.xml → java-maven', async () => {
|
|
61
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
62
|
-
const p = String(filePath)
|
|
63
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts')) return false
|
|
64
|
-
if (p.endsWith('pom.xml')) return true
|
|
65
|
-
return false
|
|
66
|
-
})
|
|
67
|
-
mockedFs.readFile.mockResolvedValue('<java.version>21</java.version>' as never)
|
|
68
|
-
const result = await detectStack('/project')
|
|
69
|
-
expect(result).not.toBeNull()
|
|
70
|
-
expect(result!.stackType).toBe('java-maven')
|
|
71
|
-
expect(result!.buildTool).toBe('maven')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('detects package.json + pnpm-lock.yaml → node/pnpm', async () => {
|
|
75
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
76
|
-
const p = String(filePath)
|
|
77
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml')) return false
|
|
78
|
-
if (p.endsWith('package.json') || p.endsWith('pnpm-lock.yaml')) return true
|
|
79
|
-
return false
|
|
80
|
-
})
|
|
81
|
-
mockedFs.readJson.mockResolvedValue({} as never)
|
|
82
|
-
const result = await detectStack('/project')
|
|
83
|
-
expect(result).not.toBeNull()
|
|
84
|
-
expect(result!.stackType).toBe('node')
|
|
85
|
-
expect(result!.buildTool).toBe('pnpm')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('detects package.json + yarn.lock → node/yarn', async () => {
|
|
89
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
90
|
-
const p = String(filePath)
|
|
91
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml')) return false
|
|
92
|
-
if (p.endsWith('package.json') || p.endsWith('yarn.lock')) return true
|
|
93
|
-
if (p.endsWith('pnpm-lock.yaml')) return false
|
|
94
|
-
return false
|
|
95
|
-
})
|
|
96
|
-
mockedFs.readJson.mockResolvedValue({} as never)
|
|
97
|
-
const result = await detectStack('/project')
|
|
98
|
-
expect(result).not.toBeNull()
|
|
99
|
-
expect(result!.stackType).toBe('node')
|
|
100
|
-
expect(result!.buildTool).toBe('yarn')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('detects package.json alone → node/npm', async () => {
|
|
104
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
105
|
-
const p = String(filePath)
|
|
106
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml')) return false
|
|
107
|
-
if (p.endsWith('package.json')) return true
|
|
108
|
-
return false
|
|
109
|
-
})
|
|
110
|
-
mockedFs.readJson.mockResolvedValue({} as never)
|
|
111
|
-
const result = await detectStack('/project')
|
|
112
|
-
expect(result).not.toBeNull()
|
|
113
|
-
expect(result!.stackType).toBe('node')
|
|
114
|
-
expect(result!.buildTool).toBe('npm')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('detects pyproject.toml → python/pyproject', async () => {
|
|
118
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
119
|
-
const p = String(filePath)
|
|
120
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml') || p.endsWith('package.json')) return false
|
|
121
|
-
if (p.endsWith('pyproject.toml')) return true
|
|
122
|
-
return false
|
|
123
|
-
})
|
|
124
|
-
const result = await detectStack('/project')
|
|
125
|
-
expect(result).not.toBeNull()
|
|
126
|
-
expect(result!.stackType).toBe('python')
|
|
127
|
-
expect(result!.buildTool).toBe('pyproject')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('detects requirements.txt → python/pip', async () => {
|
|
131
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
132
|
-
const p = String(filePath)
|
|
133
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml') || p.endsWith('package.json')) return false
|
|
134
|
-
if (p.endsWith('pyproject.toml')) return false
|
|
135
|
-
if (p.endsWith('requirements.txt')) return true
|
|
136
|
-
if (p.endsWith('Pipfile')) return false
|
|
137
|
-
return false
|
|
138
|
-
})
|
|
139
|
-
const result = await detectStack('/project')
|
|
140
|
-
expect(result).not.toBeNull()
|
|
141
|
-
expect(result!.stackType).toBe('python')
|
|
142
|
-
expect(result!.buildTool).toBe('pip')
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('detects Pipfile → python/pipenv', async () => {
|
|
146
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
147
|
-
const p = String(filePath)
|
|
148
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml') || p.endsWith('package.json')) return false
|
|
149
|
-
if (p.endsWith('pyproject.toml')) return false
|
|
150
|
-
if (p.endsWith('requirements.txt') || p.endsWith('setup.py')) return true
|
|
151
|
-
if (p.endsWith('Pipfile')) return true
|
|
152
|
-
return false
|
|
153
|
-
})
|
|
154
|
-
const result = await detectStack('/project')
|
|
155
|
-
expect(result).not.toBeNull()
|
|
156
|
-
expect(result!.stackType).toBe('python')
|
|
157
|
-
expect(result!.buildTool).toBe('pipenv')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('detects go.mod → go', async () => {
|
|
161
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
162
|
-
const p = String(filePath)
|
|
163
|
-
if (p.endsWith('go.mod')) return true
|
|
164
|
-
return false
|
|
165
|
-
})
|
|
166
|
-
const result = await detectStack('/project')
|
|
167
|
-
expect(result).not.toBeNull()
|
|
168
|
-
expect(result!.stackType).toBe('go')
|
|
169
|
-
expect(result!.buildTool).toBe('go')
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('detects Cargo.toml → rust', async () => {
|
|
173
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
174
|
-
const p = String(filePath)
|
|
175
|
-
if (p.endsWith('Cargo.toml')) return true
|
|
176
|
-
return false
|
|
177
|
-
})
|
|
178
|
-
const result = await detectStack('/project')
|
|
179
|
-
expect(result).not.toBeNull()
|
|
180
|
-
expect(result!.stackType).toBe('rust')
|
|
181
|
-
expect(result!.buildTool).toBe('cargo')
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('detects mix.exs → elixir', async () => {
|
|
185
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
186
|
-
const p = String(filePath)
|
|
187
|
-
if (p.endsWith('mix.exs')) return true
|
|
188
|
-
return false
|
|
189
|
-
})
|
|
190
|
-
const result = await detectStack('/project')
|
|
191
|
-
expect(result).not.toBeNull()
|
|
192
|
-
expect(result!.stackType).toBe('elixir')
|
|
193
|
-
expect(result!.buildTool).toBe('mix')
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('returns null for empty directory', async () => {
|
|
197
|
-
mockedFs.pathExists.mockResolvedValue(false as never)
|
|
198
|
-
const result = await detectStack('/empty-project')
|
|
199
|
-
expect(result).toBeNull()
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it('gives java-gradle precedence when both gradle + package.json exist', async () => {
|
|
203
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
204
|
-
const p = String(filePath)
|
|
205
|
-
if (p.endsWith('build.gradle') || p.endsWith('package.json')) return true
|
|
206
|
-
return false
|
|
207
|
-
})
|
|
208
|
-
mockedFs.readFile.mockResolvedValue('sourceCompatibility = 21' as never)
|
|
209
|
-
const result = await detectStack('/project')
|
|
210
|
-
expect(result).not.toBeNull()
|
|
211
|
-
expect(result!.stackType).toBe('java-gradle')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('detects JavaVersion.VERSION_21 in build.gradle', async () => {
|
|
215
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
216
|
-
const p = String(filePath)
|
|
217
|
-
if (p.endsWith('build.gradle')) return true
|
|
218
|
-
return false
|
|
219
|
-
})
|
|
220
|
-
mockedFs.readFile.mockResolvedValue('JavaVersion.VERSION_21' as never)
|
|
221
|
-
const result = await detectStack('/project')
|
|
222
|
-
expect(result!.javaVersion).toBe('21')
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('detects JavaVersion.VERSION_17 in build.gradle.kts', async () => {
|
|
226
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
227
|
-
const p = String(filePath)
|
|
228
|
-
if (p.endsWith('build.gradle')) return false
|
|
229
|
-
if (p.endsWith('build.gradle.kts')) return true
|
|
230
|
-
return false
|
|
231
|
-
})
|
|
232
|
-
mockedFs.readFile.mockResolvedValue('JavaVersion.VERSION_17' as never)
|
|
233
|
-
const result = await detectStack('/project')
|
|
234
|
-
expect(result!.javaVersion).toBe('17')
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('detects maven.compiler.source in pom.xml', async () => {
|
|
238
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
239
|
-
const p = String(filePath)
|
|
240
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts')) return false
|
|
241
|
-
if (p.endsWith('pom.xml')) return true
|
|
242
|
-
return false
|
|
243
|
-
})
|
|
244
|
-
mockedFs.readFile.mockResolvedValue('<maven.compiler.source>17</maven.compiler.source>' as never)
|
|
245
|
-
const result = await detectStack('/project')
|
|
246
|
-
expect(result!.javaVersion).toBe('17')
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('returns undefined javaVersion when no version pattern matches', async () => {
|
|
250
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
251
|
-
const p = String(filePath)
|
|
252
|
-
if (p.endsWith('build.gradle')) return true
|
|
253
|
-
return false
|
|
254
|
-
})
|
|
255
|
-
mockedFs.readFile.mockResolvedValue('apply plugin: java' as never)
|
|
256
|
-
const result = await detectStack('/project')
|
|
257
|
-
expect(result!.javaVersion).toBeUndefined()
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('handles readJson error for package.json gracefully', async () => {
|
|
261
|
-
mockedFs.pathExists.mockImplementation(async (filePath: unknown) => {
|
|
262
|
-
const p = String(filePath)
|
|
263
|
-
if (p.endsWith('build.gradle') || p.endsWith('build.gradle.kts') || p.endsWith('pom.xml')) return false
|
|
264
|
-
if (p.endsWith('package.json')) return true
|
|
265
|
-
return false
|
|
266
|
-
})
|
|
267
|
-
mockedFs.readJson.mockRejectedValue(new Error('invalid json') as never)
|
|
268
|
-
const result = await detectStack('/project')
|
|
269
|
-
expect(result).not.toBeNull()
|
|
270
|
-
expect(result!.stackType).toBe('node')
|
|
271
|
-
})
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
275
|
-
// backupIfExists
|
|
276
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
277
|
-
describe('backupIfExists', () => {
|
|
278
|
-
it('creates .bak and returns true when file exists', async () => {
|
|
279
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
280
|
-
mockedFs.copy.mockResolvedValue(undefined as never)
|
|
281
|
-
|
|
282
|
-
const result = await backupIfExists('/project/ci.yml')
|
|
283
|
-
expect(result).toBe(true)
|
|
284
|
-
expect(mockedFs.copy).toHaveBeenCalledWith(
|
|
285
|
-
'/project/ci.yml',
|
|
286
|
-
'/project/ci.yml.bak',
|
|
287
|
-
{ overwrite: true }
|
|
288
|
-
)
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
it('returns false when file does not exist', async () => {
|
|
292
|
-
mockedFs.pathExists.mockResolvedValue(false as never)
|
|
293
|
-
|
|
294
|
-
const result = await backupIfExists('/project/nonexistent.yml')
|
|
295
|
-
expect(result).toBe(false)
|
|
296
|
-
expect(mockedFs.copy).not.toHaveBeenCalled()
|
|
297
|
-
})
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
-
// isGitRepo
|
|
302
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
303
|
-
describe('isGitRepo', () => {
|
|
304
|
-
it('returns true when .git exists', async () => {
|
|
305
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
306
|
-
const result = await isGitRepo('/project')
|
|
307
|
-
expect(result).toBe(true)
|
|
308
|
-
expect(mockedFs.pathExists).toHaveBeenCalledWith(
|
|
309
|
-
path.join('/project', '.git')
|
|
310
|
-
)
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it('returns false when .git does not exist', async () => {
|
|
314
|
-
mockedFs.pathExists.mockResolvedValue(false as never)
|
|
315
|
-
const result = await isGitRepo('/project')
|
|
316
|
-
expect(result).toBe(false)
|
|
317
|
-
})
|
|
318
|
-
})
|
package/src/lib/common.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import type { Stack, StackDetection } from '../types/index.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect the project stack by looking for well-known build/config files.
|
|
7
|
-
* Returns the first match (precedence order matters).
|
|
8
|
-
*/
|
|
9
|
-
export async function detectStack(projectDir: string): Promise<StackDetection | null> {
|
|
10
|
-
const exists = async (file: string) =>
|
|
11
|
-
fs.pathExists(path.join(projectDir, file))
|
|
12
|
-
|
|
13
|
-
// Java — Gradle
|
|
14
|
-
if (await exists('build.gradle') || await exists('build.gradle.kts')) {
|
|
15
|
-
const javaVersion = await detectJavaVersion(projectDir)
|
|
16
|
-
return { stackType: 'java-gradle', buildTool: 'gradle', javaVersion }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Java — Maven
|
|
20
|
-
if (await exists('pom.xml')) {
|
|
21
|
-
const javaVersion = await detectJavaVersion(projectDir)
|
|
22
|
-
return { stackType: 'java-maven', buildTool: 'maven', javaVersion }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Node.js
|
|
26
|
-
if (await exists('package.json')) {
|
|
27
|
-
const pkg = await fs.readJson(path.join(projectDir, 'package.json')).catch(() => ({}))
|
|
28
|
-
const buildTool = await exists('pnpm-lock.yaml') ? 'pnpm'
|
|
29
|
-
: await exists('yarn.lock') ? 'yarn'
|
|
30
|
-
: 'npm'
|
|
31
|
-
return { stackType: 'node', buildTool }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Python
|
|
35
|
-
if (await exists('pyproject.toml') || await exists('requirements.txt') || await exists('setup.py')) {
|
|
36
|
-
const buildTool = await exists('pyproject.toml') ? 'pyproject'
|
|
37
|
-
: await exists('Pipfile') ? 'pipenv'
|
|
38
|
-
: 'pip'
|
|
39
|
-
return { stackType: 'python', buildTool }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Go
|
|
43
|
-
if (await exists('go.mod')) {
|
|
44
|
-
return { stackType: 'go', buildTool: 'go' }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Rust
|
|
48
|
-
if (await exists('Cargo.toml')) {
|
|
49
|
-
return { stackType: 'rust', buildTool: 'cargo' }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Elixir
|
|
53
|
-
if (await exists('mix.exs')) {
|
|
54
|
-
return { stackType: 'elixir', buildTool: 'mix' }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Try to detect Java version from gradle or maven config */
|
|
61
|
-
async function detectJavaVersion(projectDir: string): Promise<string | undefined> {
|
|
62
|
-
// Try build.gradle
|
|
63
|
-
const gradleFile = path.join(projectDir, 'build.gradle')
|
|
64
|
-
if (await fs.pathExists(gradleFile)) {
|
|
65
|
-
const content = await fs.readFile(gradleFile, 'utf-8')
|
|
66
|
-
const match = content.match(/sourceCompatibility\s*=\s*['"]?(\d+)/)
|
|
67
|
-
|| content.match(/JavaVersion\.VERSION_(\d+)/)
|
|
68
|
-
if (match) return match[1]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Try build.gradle.kts
|
|
72
|
-
const ktsFile = path.join(projectDir, 'build.gradle.kts')
|
|
73
|
-
if (await fs.pathExists(ktsFile)) {
|
|
74
|
-
const content = await fs.readFile(ktsFile, 'utf-8')
|
|
75
|
-
const match = content.match(/jvmTarget\s*=\s*['"](\d+)['"]/)
|
|
76
|
-
|| content.match(/JavaVersion\.VERSION_(\d+)/)
|
|
77
|
-
if (match) return match[1]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Try pom.xml
|
|
81
|
-
const pomFile = path.join(projectDir, 'pom.xml')
|
|
82
|
-
if (await fs.pathExists(pomFile)) {
|
|
83
|
-
const content = await fs.readFile(pomFile, 'utf-8')
|
|
84
|
-
const match = content.match(/<java\.version>(\d+)</)
|
|
85
|
-
|| content.match(/<maven\.compiler\.source>(\d+)</)
|
|
86
|
-
if (match) return match[1]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return undefined
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Back up a file by copying to filePath.bak (only if it exists) */
|
|
93
|
-
export async function backupIfExists(filePath: string): Promise<boolean> {
|
|
94
|
-
if (await fs.pathExists(filePath)) {
|
|
95
|
-
await fs.copy(filePath, `${filePath}.bak`, { overwrite: true })
|
|
96
|
-
return true
|
|
97
|
-
}
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Create a directory recursively if it doesn't exist */
|
|
102
|
-
export async function ensureDirExists(dirPath: string): Promise<void> {
|
|
103
|
-
await fs.ensureDir(dirPath)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Check if a directory is a git repository */
|
|
107
|
-
export async function isGitRepo(dir: string): Promise<boolean> {
|
|
108
|
-
return fs.pathExists(path.join(dir, '.git'))
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Resolve the forge assets root (the package root directory) */
|
|
112
|
-
export function getForgeRoot(): string {
|
|
113
|
-
// When running from dist/, go up one level
|
|
114
|
-
const thisDir = path.dirname(new URL(import.meta.url).pathname)
|
|
115
|
-
return path.resolve(thisDir, '..')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Stack display names */
|
|
119
|
-
export const STACK_LABELS: Record<Stack, string> = {
|
|
120
|
-
'node': 'Node.js / TypeScript',
|
|
121
|
-
'python': 'Python',
|
|
122
|
-
'go': 'Go',
|
|
123
|
-
'rust': 'Rust',
|
|
124
|
-
'java-gradle': 'Java (Gradle)',
|
|
125
|
-
'java-maven': 'Java (Maven)',
|
|
126
|
-
'elixir': 'Elixir',
|
|
127
|
-
}
|