javi-forge 0.1.0 → 1.0.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/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/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 +1 -1
- package/schemas/plugin.schema.json +62 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
- package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
- package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
- package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
- 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/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +0 -25
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +0 -29
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +0 -18
- 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
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import type { InitOptions, InitStep } from '../types/index.js'
|
|
3
|
-
|
|
4
|
-
// ── Mock fs-extra ────────────────────────────────────────────────────────────
|
|
5
|
-
vi.mock('fs-extra', () => {
|
|
6
|
-
const mockFs = {
|
|
7
|
-
pathExists: vi.fn(),
|
|
8
|
-
readFile: vi.fn(),
|
|
9
|
-
readJson: vi.fn(),
|
|
10
|
-
writeFile: vi.fn(),
|
|
11
|
-
writeJson: vi.fn(),
|
|
12
|
-
copy: vi.fn(),
|
|
13
|
-
ensureDir: vi.fn(),
|
|
14
|
-
}
|
|
15
|
-
return { default: mockFs, ...mockFs }
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
// ── Mock child_process ───────────────────────────────────────────────────────
|
|
19
|
-
vi.mock('child_process', () => ({
|
|
20
|
-
execFile: vi.fn((_cmd: string, _args: string[], _opts: unknown, cb: Function) => {
|
|
21
|
-
cb(null, { stdout: '', stderr: '' })
|
|
22
|
-
}),
|
|
23
|
-
}))
|
|
24
|
-
|
|
25
|
-
// ── Mock template module ─────────────────────────────────────────────────────
|
|
26
|
-
vi.mock('../lib/template.js', () => ({
|
|
27
|
-
generateDependabotYml: vi.fn().mockResolvedValue('dependabot-content'),
|
|
28
|
-
generateCIWorkflow: vi.fn().mockResolvedValue('ci-workflow-content'),
|
|
29
|
-
getCIDestination: vi.fn().mockReturnValue('.github/workflows/ci.yml'),
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
|
-
// ── Mock common module ───────────────────────────────────────────────────────
|
|
33
|
-
vi.mock('../lib/common.js', () => ({
|
|
34
|
-
backupIfExists: vi.fn().mockResolvedValue(false),
|
|
35
|
-
ensureDirExists: vi.fn().mockResolvedValue(undefined),
|
|
36
|
-
}))
|
|
37
|
-
|
|
38
|
-
import fs from 'fs-extra'
|
|
39
|
-
import { execFile } from 'child_process'
|
|
40
|
-
import { initProject } from './init.js'
|
|
41
|
-
import { generateCIWorkflow, getCIDestination } from '../lib/template.js'
|
|
42
|
-
|
|
43
|
-
const mockedFs = vi.mocked(fs)
|
|
44
|
-
const mockedExecFile = vi.mocked(execFile)
|
|
45
|
-
const mockedGenerateCIWorkflow = vi.mocked(generateCIWorkflow)
|
|
46
|
-
const mockedGetCIDestination = vi.mocked(getCIDestination)
|
|
47
|
-
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
vi.resetAllMocks()
|
|
50
|
-
|
|
51
|
-
// Default: most things exist
|
|
52
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
53
|
-
mockedFs.writeFile.mockResolvedValue(undefined as never)
|
|
54
|
-
mockedFs.writeJson.mockResolvedValue(undefined as never)
|
|
55
|
-
mockedFs.copy.mockResolvedValue(undefined as never)
|
|
56
|
-
mockedFs.ensureDir.mockResolvedValue(undefined as never)
|
|
57
|
-
|
|
58
|
-
// Default: execFile succeeds (promisified version)
|
|
59
|
-
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
|
|
60
|
-
if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
|
|
61
|
-
return undefined as any
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Default: CI workflow available
|
|
65
|
-
mockedGenerateCIWorkflow.mockResolvedValue('ci-workflow-content')
|
|
66
|
-
mockedGetCIDestination.mockReturnValue('.github/workflows/ci.yml')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
function makeOptions(overrides: Partial<InitOptions> = {}): InitOptions {
|
|
70
|
-
return {
|
|
71
|
-
projectName: 'test-project',
|
|
72
|
-
projectDir: '/test/project',
|
|
73
|
-
stack: 'node',
|
|
74
|
-
ciProvider: 'github',
|
|
75
|
-
memory: 'engram',
|
|
76
|
-
aiSync: true,
|
|
77
|
-
sdd: true,
|
|
78
|
-
ghagga: true,
|
|
79
|
-
dryRun: false,
|
|
80
|
-
...overrides,
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function collectSteps(options: InitOptions): Promise<InitStep[]> {
|
|
85
|
-
const steps: InitStep[] = []
|
|
86
|
-
return initProject(options, (step) => steps.push(step)).then(() => steps)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
90
|
-
// initProject
|
|
91
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
92
|
-
describe('initProject', () => {
|
|
93
|
-
it('completes full happy path — all steps report done', async () => {
|
|
94
|
-
// .git doesn't exist yet so it initializes
|
|
95
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
96
|
-
const s = String(p)
|
|
97
|
-
if (s.endsWith('.git')) return false as never
|
|
98
|
-
return true as never
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const steps = await collectSteps(makeOptions())
|
|
102
|
-
const doneSteps = steps.filter(s => s.status === 'done')
|
|
103
|
-
// Should have multiple 'done' status steps
|
|
104
|
-
expect(doneSteps.length).toBeGreaterThanOrEqual(8)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('dry-run: no filesystem writes are made', async () => {
|
|
108
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
109
|
-
const s = String(p)
|
|
110
|
-
if (s.endsWith('.git')) return false as never
|
|
111
|
-
return true as never
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
const steps = await collectSteps(makeOptions({ dryRun: true }))
|
|
115
|
-
// In dry-run, fs.writeFile and fs.writeJson should not be called
|
|
116
|
-
expect(mockedFs.writeFile).not.toHaveBeenCalled()
|
|
117
|
-
expect(mockedFs.writeJson).not.toHaveBeenCalled()
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('continues other steps when one step errors', async () => {
|
|
121
|
-
// Make CI generation throw
|
|
122
|
-
mockedGenerateCIWorkflow.mockRejectedValue(new Error('CI template error'))
|
|
123
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
124
|
-
const s = String(p)
|
|
125
|
-
if (s.endsWith('.git')) return false as never
|
|
126
|
-
return true as never
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const steps = await collectSteps(makeOptions())
|
|
130
|
-
// Should have both error and done steps
|
|
131
|
-
const errorSteps = steps.filter(s => s.status === 'error')
|
|
132
|
-
const doneSteps = steps.filter(s => s.status === 'done')
|
|
133
|
-
expect(errorSteps.length).toBeGreaterThanOrEqual(1)
|
|
134
|
-
expect(doneSteps.length).toBeGreaterThanOrEqual(5)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('skips memory when memory is none', async () => {
|
|
138
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
139
|
-
const steps = await collectSteps(makeOptions({ memory: 'none' }))
|
|
140
|
-
const memStep = steps.find(s => s.id === 'memory' && s.status === 'skipped')
|
|
141
|
-
expect(memStep).toBeDefined()
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('skips ghagga when ghagga is false', async () => {
|
|
145
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
146
|
-
const steps = await collectSteps(makeOptions({ ghagga: false }))
|
|
147
|
-
const ghStep = steps.find(s => s.id === 'ghagga' && s.status === 'skipped')
|
|
148
|
-
expect(ghStep).toBeDefined()
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('skips SDD when sdd is false', async () => {
|
|
152
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
153
|
-
const steps = await collectSteps(makeOptions({ sdd: false }))
|
|
154
|
-
const sddStep = steps.find(s => s.id === 'sdd' && s.status === 'skipped')
|
|
155
|
-
expect(sddStep).toBeDefined()
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('skips AI sync when aiSync is false', async () => {
|
|
159
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
160
|
-
const steps = await collectSteps(makeOptions({ aiSync: false }))
|
|
161
|
-
const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'skipped')
|
|
162
|
-
expect(aiStep).toBeDefined()
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('reports already exists when .git directory is present', async () => {
|
|
166
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
167
|
-
const steps = await collectSteps(makeOptions())
|
|
168
|
-
const gitStep = steps.find(s => s.id === 'git-init' && s.status === 'done' && s.detail === 'already exists')
|
|
169
|
-
expect(gitStep).toBeDefined()
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('skips CI step when no template found', async () => {
|
|
173
|
-
mockedGenerateCIWorkflow.mockResolvedValue(null)
|
|
174
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
175
|
-
|
|
176
|
-
const steps = await collectSteps(makeOptions())
|
|
177
|
-
const ciStep = steps.find(s => s.id === 'ci-template' && s.status === 'skipped')
|
|
178
|
-
expect(ciStep).toBeDefined()
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('writes manifest with correct structure', async () => {
|
|
182
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
183
|
-
const s = String(p)
|
|
184
|
-
if (s.endsWith('.git')) return false as never
|
|
185
|
-
return true as never
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
await collectSteps(makeOptions({
|
|
189
|
-
projectName: 'test-manifest',
|
|
190
|
-
stack: 'node',
|
|
191
|
-
ciProvider: 'github',
|
|
192
|
-
memory: 'engram',
|
|
193
|
-
ghagga: true,
|
|
194
|
-
sdd: true,
|
|
195
|
-
aiSync: true,
|
|
196
|
-
}))
|
|
197
|
-
|
|
198
|
-
expect(mockedFs.writeJson).toHaveBeenCalled()
|
|
199
|
-
const [manifestPath, manifestData] = mockedFs.writeJson.mock.calls[0]
|
|
200
|
-
expect(String(manifestPath)).toContain('manifest.json')
|
|
201
|
-
expect(manifestData).toMatchObject({
|
|
202
|
-
version: '0.1.0',
|
|
203
|
-
projectName: 'test-manifest',
|
|
204
|
-
stack: 'node',
|
|
205
|
-
ciProvider: 'github',
|
|
206
|
-
memory: 'engram',
|
|
207
|
-
})
|
|
208
|
-
expect((manifestData as any).modules).toContain('engram')
|
|
209
|
-
expect((manifestData as any).modules).toContain('ghagga')
|
|
210
|
-
expect((manifestData as any).modules).toContain('sdd')
|
|
211
|
-
expect((manifestData as any).modules).toContain('ai-config')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('reports error with helpful message when javi-ai not found', async () => {
|
|
215
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
216
|
-
const s = String(p)
|
|
217
|
-
if (s.endsWith('.git')) return true as never
|
|
218
|
-
return true as never
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
// Make javi-ai sync fail with ENOENT
|
|
222
|
-
mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
|
|
223
|
-
const cmdStr = String(_cmd)
|
|
224
|
-
const argsArr = _args as string[]
|
|
225
|
-
if (cmdStr === 'npx' && argsArr?.includes('javi-ai')) {
|
|
226
|
-
if (typeof cb === 'function') cb(new Error('ENOENT: command not found'), { stdout: '', stderr: '' })
|
|
227
|
-
} else {
|
|
228
|
-
if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
|
|
229
|
-
}
|
|
230
|
-
return undefined as any
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
const steps = await collectSteps(makeOptions({ aiSync: true }))
|
|
234
|
-
const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'error')
|
|
235
|
-
expect(aiStep).toBeDefined()
|
|
236
|
-
expect(aiStep!.detail).toContain('javi-ai not found')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('reports steps in order via callback', async () => {
|
|
240
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
241
|
-
const s = String(p)
|
|
242
|
-
if (s.endsWith('.git')) return false as never
|
|
243
|
-
return true as never
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
const steps = await collectSteps(makeOptions())
|
|
247
|
-
const stepIds = steps.map(s => s.id)
|
|
248
|
-
|
|
249
|
-
// First step should be git-init
|
|
250
|
-
expect(stepIds[0]).toBe('git-init')
|
|
251
|
-
|
|
252
|
-
// Manifest should be among the last
|
|
253
|
-
const manifestIdx = stepIds.lastIndexOf('manifest')
|
|
254
|
-
expect(manifestIdx).toBeGreaterThan(stepIds.indexOf('git-init'))
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('skips dependabot for non-github providers', async () => {
|
|
258
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
259
|
-
const steps = await collectSteps(makeOptions({ ciProvider: 'gitlab' }))
|
|
260
|
-
const depStep = steps.find(s => s.id === 'dependabot' && s.status === 'skipped')
|
|
261
|
-
expect(depStep).toBeDefined()
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
it('skips gitignore when .gitignore already exists', async () => {
|
|
265
|
-
mockedFs.pathExists.mockResolvedValue(true as never)
|
|
266
|
-
const steps = await collectSteps(makeOptions())
|
|
267
|
-
const giStep = steps.find(s => s.id === 'gitignore' && s.detail === 'already exists')
|
|
268
|
-
expect(giStep).toBeDefined()
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('skips hooks when ci-local dir is missing', async () => {
|
|
272
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
273
|
-
const s = String(p)
|
|
274
|
-
if (s.includes('ci-local')) return false as never
|
|
275
|
-
if (s.endsWith('.git')) return false as never
|
|
276
|
-
return true as never
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
const steps = await collectSteps(makeOptions())
|
|
280
|
-
const hookStep = steps.find(s => s.id === 'git-hooks' && s.status === 'skipped')
|
|
281
|
-
expect(hookStep).toBeDefined()
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('reports error when memory module not found', async () => {
|
|
285
|
-
mockedFs.pathExists.mockImplementation(async (p: unknown) => {
|
|
286
|
-
const s = String(p)
|
|
287
|
-
// Module source directory doesn't exist
|
|
288
|
-
if (s.includes('modules/engram') && !s.includes('.javi-forge')) return false as never
|
|
289
|
-
if (s.endsWith('.git')) return false as never
|
|
290
|
-
return true as never
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
const steps = await collectSteps(makeOptions({ memory: 'engram' }))
|
|
294
|
-
const memStep = steps.find(s => s.id === 'memory' && s.status === 'error')
|
|
295
|
-
expect(memStep).toBeDefined()
|
|
296
|
-
expect(memStep!.detail).toContain('module not found')
|
|
297
|
-
})
|
|
298
|
-
})
|
package/src/commands/init.ts
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { execFile } from 'child_process'
|
|
4
|
-
import { promisify } from 'util'
|
|
5
|
-
import type { InitOptions, InitStep, ForgeManifest } from '../types/index.js'
|
|
6
|
-
import { backupIfExists, ensureDirExists } from '../lib/common.js'
|
|
7
|
-
import { generateDependabotYml, generateCIWorkflow, getCIDestination } from '../lib/template.js'
|
|
8
|
-
import {
|
|
9
|
-
FORGE_ROOT,
|
|
10
|
-
TEMPLATES_DIR,
|
|
11
|
-
MODULES_DIR,
|
|
12
|
-
CI_LOCAL_DIR,
|
|
13
|
-
} from '../constants.js'
|
|
14
|
-
|
|
15
|
-
const execFileAsync = promisify(execFile)
|
|
16
|
-
|
|
17
|
-
type StepCallback = (step: InitStep) => void
|
|
18
|
-
|
|
19
|
-
function report(onStep: StepCallback, id: string, label: string, status: InitStep['status'], detail?: string) {
|
|
20
|
-
onStep({ id, label, status, detail })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Main init orchestrator: bootstraps a project with CI, git hooks,
|
|
25
|
-
* memory module, AI config sync, SDD, and ghagga.
|
|
26
|
-
*/
|
|
27
|
-
export async function initProject(
|
|
28
|
-
options: InitOptions,
|
|
29
|
-
onStep: StepCallback
|
|
30
|
-
): Promise<void> {
|
|
31
|
-
const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, dryRun } = options
|
|
32
|
-
|
|
33
|
-
// Ensure project directory exists before any steps
|
|
34
|
-
if (!dryRun && projectDir) {
|
|
35
|
-
await ensureDirExists(projectDir)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ── Step 1: Initialize git ────────────────────────────────────────────────
|
|
39
|
-
const stepGit = 'git-init'
|
|
40
|
-
report(onStep, stepGit, 'Initialize git repository', 'running')
|
|
41
|
-
try {
|
|
42
|
-
const gitDir = path.join(projectDir, '.git')
|
|
43
|
-
if (!await fs.pathExists(gitDir)) {
|
|
44
|
-
if (!dryRun) {
|
|
45
|
-
await execFileAsync('git', ['init'], { cwd: projectDir })
|
|
46
|
-
}
|
|
47
|
-
report(onStep, stepGit, 'Initialize git repository', 'done', 'initialized')
|
|
48
|
-
} else {
|
|
49
|
-
report(onStep, stepGit, 'Initialize git repository', 'done', 'already exists')
|
|
50
|
-
}
|
|
51
|
-
} catch (e) {
|
|
52
|
-
report(onStep, stepGit, 'Initialize git repository', 'error', String(e))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ── Step 2: Configure git hooks path ──────────────────────────────────────
|
|
56
|
-
const stepHooks = 'git-hooks'
|
|
57
|
-
report(onStep, stepHooks, 'Configure git hooks path', 'running')
|
|
58
|
-
try {
|
|
59
|
-
const ciLocalSrc = CI_LOCAL_DIR
|
|
60
|
-
const ciLocalDest = path.join(projectDir, 'ci-local')
|
|
61
|
-
if (await fs.pathExists(ciLocalSrc)) {
|
|
62
|
-
if (!dryRun) {
|
|
63
|
-
await fs.copy(ciLocalSrc, ciLocalDest, { overwrite: false, errorOnExist: false })
|
|
64
|
-
// Set core.hooksPath to ci-local/hooks
|
|
65
|
-
const hooksDir = path.join(ciLocalDest, 'hooks')
|
|
66
|
-
if (await fs.pathExists(hooksDir)) {
|
|
67
|
-
await execFileAsync('git', ['config', 'core.hooksPath', 'ci-local/hooks'], { cwd: projectDir })
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
report(onStep, stepHooks, 'Configure git hooks path', 'done', 'ci-local/hooks')
|
|
71
|
-
} else {
|
|
72
|
-
report(onStep, stepHooks, 'Configure git hooks path', 'skipped', 'no ci-local dir')
|
|
73
|
-
}
|
|
74
|
-
} catch (e) {
|
|
75
|
-
report(onStep, stepHooks, 'Configure git hooks path', 'error', String(e))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ── Step 3: Copy CI template ──────────────────────────────────────────────
|
|
79
|
-
const stepCI = 'ci-template'
|
|
80
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'running')
|
|
81
|
-
try {
|
|
82
|
-
const ciContent = await generateCIWorkflow(stack, ciProvider)
|
|
83
|
-
if (ciContent) {
|
|
84
|
-
const dest = path.join(projectDir, getCIDestination(ciProvider))
|
|
85
|
-
if (!dryRun) {
|
|
86
|
-
await backupIfExists(dest)
|
|
87
|
-
await ensureDirExists(path.dirname(dest))
|
|
88
|
-
await fs.writeFile(dest, ciContent, 'utf-8')
|
|
89
|
-
}
|
|
90
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'done', getCIDestination(ciProvider))
|
|
91
|
-
} else {
|
|
92
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'skipped', `no template for ${stack}`)
|
|
93
|
-
}
|
|
94
|
-
} catch (e) {
|
|
95
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'error', String(e))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ── Step 4: Generate .gitignore ───────────────────────────────────────────
|
|
99
|
-
const stepGitignore = 'gitignore'
|
|
100
|
-
report(onStep, stepGitignore, 'Generate .gitignore', 'running')
|
|
101
|
-
try {
|
|
102
|
-
const templatePath = path.join(FORGE_ROOT, '.gitignore.template')
|
|
103
|
-
const dest = path.join(projectDir, '.gitignore')
|
|
104
|
-
if (await fs.pathExists(templatePath) && !await fs.pathExists(dest)) {
|
|
105
|
-
if (!dryRun) {
|
|
106
|
-
await fs.copy(templatePath, dest)
|
|
107
|
-
}
|
|
108
|
-
report(onStep, stepGitignore, 'Generate .gitignore', 'done', 'from template')
|
|
109
|
-
} else if (await fs.pathExists(dest)) {
|
|
110
|
-
report(onStep, stepGitignore, 'Generate .gitignore', 'done', 'already exists')
|
|
111
|
-
} else {
|
|
112
|
-
report(onStep, stepGitignore, 'Generate .gitignore', 'skipped', 'no template')
|
|
113
|
-
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
report(onStep, stepGitignore, 'Generate .gitignore', 'error', String(e))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ── Step 5: Generate dependabot.yml ───────────────────────────────────────
|
|
119
|
-
const stepDeps = 'dependabot'
|
|
120
|
-
report(onStep, stepDeps, 'Generate dependabot.yml', 'running')
|
|
121
|
-
try {
|
|
122
|
-
if (ciProvider === 'github') {
|
|
123
|
-
const content = await generateDependabotYml([stack], true)
|
|
124
|
-
const dest = path.join(projectDir, '.github', 'dependabot.yml')
|
|
125
|
-
if (!dryRun) {
|
|
126
|
-
await backupIfExists(dest)
|
|
127
|
-
await ensureDirExists(path.dirname(dest))
|
|
128
|
-
await fs.writeFile(dest, content, 'utf-8')
|
|
129
|
-
}
|
|
130
|
-
report(onStep, stepDeps, 'Generate dependabot.yml', 'done')
|
|
131
|
-
} else {
|
|
132
|
-
report(onStep, stepDeps, 'Generate dependabot.yml', 'skipped', `not needed for ${ciProvider}`)
|
|
133
|
-
}
|
|
134
|
-
} catch (e) {
|
|
135
|
-
report(onStep, stepDeps, 'Generate dependabot.yml', 'error', String(e))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Step 6: Install memory module ─────────────────────────────────────────
|
|
139
|
-
const stepMem = 'memory'
|
|
140
|
-
report(onStep, stepMem, `Install memory module: ${memory}`, 'running')
|
|
141
|
-
try {
|
|
142
|
-
if (memory !== 'none') {
|
|
143
|
-
const moduleSrc = path.join(MODULES_DIR, memory)
|
|
144
|
-
if (await fs.pathExists(moduleSrc)) {
|
|
145
|
-
if (!dryRun) {
|
|
146
|
-
// Copy module files to project
|
|
147
|
-
const moduleDest = path.join(projectDir, '.javi-forge', 'modules', memory)
|
|
148
|
-
await ensureDirExists(moduleDest)
|
|
149
|
-
await fs.copy(moduleSrc, moduleDest, { overwrite: false, errorOnExist: false })
|
|
150
|
-
|
|
151
|
-
// If engram, copy .mcp-config-snippet.json to project
|
|
152
|
-
if (memory === 'engram') {
|
|
153
|
-
const snippetSrc = path.join(moduleSrc, '.mcp-config-snippet.json')
|
|
154
|
-
if (await fs.pathExists(snippetSrc)) {
|
|
155
|
-
const snippetDest = path.join(projectDir, '.mcp-config-snippet.json')
|
|
156
|
-
await fs.copy(snippetSrc, snippetDest, { overwrite: false })
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
report(onStep, stepMem, `Install memory module: ${memory}`, 'done')
|
|
161
|
-
} else {
|
|
162
|
-
report(onStep, stepMem, `Install memory module: ${memory}`, 'error', 'module not found')
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
report(onStep, stepMem, `Install memory module: ${memory}`, 'skipped', 'none selected')
|
|
166
|
-
}
|
|
167
|
-
} catch (e) {
|
|
168
|
-
report(onStep, stepMem, `Install memory module: ${memory}`, 'error', String(e))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── Step 7: AI config sync (delegated to javi-ai) ──────────────────────────
|
|
172
|
-
const stepAI = 'ai-sync'
|
|
173
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'running')
|
|
174
|
-
try {
|
|
175
|
-
if (aiSync) {
|
|
176
|
-
if (!dryRun) {
|
|
177
|
-
try {
|
|
178
|
-
await execFileAsync('npx', ['javi-ai', 'sync', '--project-dir', projectDir, '--target', 'all'], {
|
|
179
|
-
cwd: projectDir,
|
|
180
|
-
timeout: 120_000,
|
|
181
|
-
})
|
|
182
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'done', 'javi-ai sync --target all')
|
|
183
|
-
} catch (syncErr: unknown) {
|
|
184
|
-
const msg = syncErr instanceof Error ? syncErr.message : String(syncErr)
|
|
185
|
-
if (msg.includes('ENOENT') || msg.includes('not found') || msg.includes('ERR_MODULE_NOT_FOUND')) {
|
|
186
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'error',
|
|
187
|
-
'javi-ai not found. Install with: npm install -g javi-ai (or run npx javi-ai sync manually)')
|
|
188
|
-
} else {
|
|
189
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'error', msg)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'done', 'dry-run: would run javi-ai sync --target all')
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'skipped', 'not selected')
|
|
197
|
-
}
|
|
198
|
-
} catch (e) {
|
|
199
|
-
report(onStep, stepAI, 'Sync AI config via javi-ai', 'error', String(e))
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ── Step 8: SDD (Spec-Driven Development) ─────────────────────────────────
|
|
203
|
-
const stepSDD = 'sdd'
|
|
204
|
-
report(onStep, stepSDD, 'Set up SDD (openspec/)', 'running')
|
|
205
|
-
try {
|
|
206
|
-
if (sdd) {
|
|
207
|
-
if (!dryRun) {
|
|
208
|
-
const openspecDir = path.join(projectDir, 'openspec')
|
|
209
|
-
await ensureDirExists(openspecDir)
|
|
210
|
-
// Create a README if none exists
|
|
211
|
-
const readmePath = path.join(openspecDir, 'README.md')
|
|
212
|
-
if (!await fs.pathExists(readmePath)) {
|
|
213
|
-
await fs.writeFile(readmePath, `# openspec/\n\nSpec-Driven Development artifacts for ${projectName}.\n\nSee: /sdd:new <name> to start a new change.\n`, 'utf-8')
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
report(onStep, stepSDD, 'Set up SDD (openspec/)', 'done')
|
|
217
|
-
} else {
|
|
218
|
-
report(onStep, stepSDD, 'Set up SDD (openspec/)', 'skipped', 'not selected')
|
|
219
|
-
}
|
|
220
|
-
} catch (e) {
|
|
221
|
-
report(onStep, stepSDD, 'Set up SDD (openspec/)', 'error', String(e))
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ── Step 9: GHAGGA ────────────────────────────────────────────────────────
|
|
225
|
-
const stepGhagga = 'ghagga'
|
|
226
|
-
report(onStep, stepGhagga, 'Install GHAGGA review system', 'running')
|
|
227
|
-
try {
|
|
228
|
-
if (ghagga) {
|
|
229
|
-
const ghaggaSrc = path.join(MODULES_DIR, 'ghagga')
|
|
230
|
-
if (await fs.pathExists(ghaggaSrc)) {
|
|
231
|
-
if (!dryRun) {
|
|
232
|
-
const ghaggaDest = path.join(projectDir, '.javi-forge', 'modules', 'ghagga')
|
|
233
|
-
await ensureDirExists(ghaggaDest)
|
|
234
|
-
await fs.copy(ghaggaSrc, ghaggaDest, { overwrite: false, errorOnExist: false })
|
|
235
|
-
|
|
236
|
-
// Copy ghagga workflow to CI provider location
|
|
237
|
-
if (ciProvider === 'github') {
|
|
238
|
-
const workflowSrc = path.join(FORGE_ROOT, 'workflows', 'reusable-ghagga-review.yml')
|
|
239
|
-
if (await fs.pathExists(workflowSrc)) {
|
|
240
|
-
const workflowDest = path.join(projectDir, '.github', 'workflows', 'ghagga-review.yml')
|
|
241
|
-
await ensureDirExists(path.dirname(workflowDest))
|
|
242
|
-
await fs.copy(workflowSrc, workflowDest, { overwrite: false })
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
report(onStep, stepGhagga, 'Install GHAGGA review system', 'done')
|
|
247
|
-
} else {
|
|
248
|
-
report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', 'module not found')
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
report(onStep, stepGhagga, 'Install GHAGGA review system', 'skipped', 'not selected')
|
|
252
|
-
}
|
|
253
|
-
} catch (e) {
|
|
254
|
-
report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', String(e))
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ── Step 10: Write manifest ───────────────────────────────────────────────
|
|
258
|
-
const stepManifest = 'manifest'
|
|
259
|
-
report(onStep, stepManifest, 'Write forge manifest', 'running')
|
|
260
|
-
try {
|
|
261
|
-
if (!dryRun) {
|
|
262
|
-
const manifestDir = path.join(projectDir, '.javi-forge')
|
|
263
|
-
await ensureDirExists(manifestDir)
|
|
264
|
-
const manifest: ForgeManifest = {
|
|
265
|
-
version: '0.1.0',
|
|
266
|
-
projectName,
|
|
267
|
-
stack,
|
|
268
|
-
ciProvider,
|
|
269
|
-
memory,
|
|
270
|
-
createdAt: new Date().toISOString(),
|
|
271
|
-
updatedAt: new Date().toISOString(),
|
|
272
|
-
modules: [
|
|
273
|
-
...(memory !== 'none' ? [memory] : []),
|
|
274
|
-
...(ghagga ? ['ghagga'] : []),
|
|
275
|
-
...(sdd ? ['sdd'] : []),
|
|
276
|
-
...(aiSync ? ['ai-config'] : []),
|
|
277
|
-
],
|
|
278
|
-
}
|
|
279
|
-
await fs.writeJson(path.join(manifestDir, 'manifest.json'), manifest, { spaces: 2 })
|
|
280
|
-
}
|
|
281
|
-
report(onStep, stepManifest, 'Write forge manifest', 'done', '.javi-forge/manifest.json')
|
|
282
|
-
} catch (e) {
|
|
283
|
-
report(onStep, stepManifest, 'Write forge manifest', 'error', String(e))
|
|
284
|
-
}
|
|
285
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { fileURLToPath } from 'url'
|
|
3
|
-
|
|
4
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
5
|
-
|
|
6
|
-
/** Root of the javi-forge package (one level up from dist/) */
|
|
7
|
-
export const FORGE_ROOT = path.resolve(__dirname, '..')
|
|
8
|
-
|
|
9
|
-
/** Templates directory */
|
|
10
|
-
export const TEMPLATES_DIR = path.join(FORGE_ROOT, 'templates')
|
|
11
|
-
|
|
12
|
-
/** Modules directory */
|
|
13
|
-
export const MODULES_DIR = path.join(FORGE_ROOT, 'modules')
|
|
14
|
-
|
|
15
|
-
/** Workflows directory */
|
|
16
|
-
export const WORKFLOWS_DIR = path.join(FORGE_ROOT, 'workflows')
|
|
17
|
-
|
|
18
|
-
/** AI config directory */
|
|
19
|
-
export const AI_CONFIG_DIR = path.join(FORGE_ROOT, 'ai-config')
|
|
20
|
-
|
|
21
|
-
/** Schemas directory */
|
|
22
|
-
export const SCHEMAS_DIR = path.join(FORGE_ROOT, 'schemas')
|
|
23
|
-
|
|
24
|
-
/** CI-local directory */
|
|
25
|
-
export const CI_LOCAL_DIR = path.join(FORGE_ROOT, 'ci-local')
|
|
26
|
-
|
|
27
|
-
/** Dependabot fragment directory */
|
|
28
|
-
export const DEPENDABOT_FRAGMENTS_DIR = path.join(TEMPLATES_DIR, 'common', 'dependabot')
|
|
29
|
-
|
|
30
|
-
/** Stack-to-dependabot fragment mapping */
|
|
31
|
-
export const STACK_DEPENDABOT_MAP: Record<string, string[]> = {
|
|
32
|
-
'node': ['npm'],
|
|
33
|
-
'python': ['pip'],
|
|
34
|
-
'go': ['gomod'],
|
|
35
|
-
'rust': ['cargo'],
|
|
36
|
-
'java-gradle': ['gradle'],
|
|
37
|
-
'java-maven': ['maven'],
|
|
38
|
-
'elixir': [],
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Stack-to-CI template filename mapping */
|
|
42
|
-
export const STACK_CI_MAP: Record<string, Record<string, string>> = {
|
|
43
|
-
github: {
|
|
44
|
-
'node': 'ci-node.yml',
|
|
45
|
-
'python': 'ci-python.yml',
|
|
46
|
-
'go': 'ci-go.yml',
|
|
47
|
-
'rust': 'ci-rust.yml',
|
|
48
|
-
'java-gradle': 'ci-java.yml',
|
|
49
|
-
'java-maven': 'ci-java.yml',
|
|
50
|
-
},
|
|
51
|
-
gitlab: {
|
|
52
|
-
'node': 'gitlab-ci-node.yml',
|
|
53
|
-
'python': 'gitlab-ci-python.yml',
|
|
54
|
-
'go': 'gitlab-ci-go.yml',
|
|
55
|
-
'rust': 'gitlab-ci-rust.yml',
|
|
56
|
-
'java-gradle': 'gitlab-ci-java.yml',
|
|
57
|
-
'java-maven': 'gitlab-ci-java.yml',
|
|
58
|
-
},
|
|
59
|
-
woodpecker: {
|
|
60
|
-
'node': 'woodpecker-node.yml',
|
|
61
|
-
'python': 'woodpecker-python.yml',
|
|
62
|
-
'go': 'woodpecker-go.yml',
|
|
63
|
-
'rust': 'woodpecker-rust.yml',
|
|
64
|
-
'java-gradle': 'woodpecker-java.yml',
|
|
65
|
-
'java-maven': 'woodpecker-java.yml',
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|