javi-forge 1.6.1 → 1.7.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/README.md +45 -1
- package/ci-local/README.md +56 -11
- package/ci-local/ci-local.ps1 +445 -0
- package/ci-local/ci-local.sh +52 -9
- package/ci-local/docker/python.Dockerfile +7 -0
- package/ci-local/hooks/commit-msg +120 -44
- package/ci-local/hooks/commit-msg.test.sh +286 -0
- package/ci-local/hooks/pre-commit +9 -1
- package/ci-local/hooks/pre-push +13 -2
- package/ci-local/install.ps1 +367 -0
- package/ci-local/install.sh +151 -5
- package/ci-local/uninstall.ps1 +85 -0
- package/ci-local/uninstall.sh +95 -0
- package/dist/cli/dispatch/ci.d.ts +12 -0
- package/dist/cli/dispatch/ci.js +38 -0
- package/dist/cli/dispatch/security.d.ts +11 -0
- package/dist/cli/dispatch/security.js +68 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
- package/dist/cli/dispatch/simple-renderers.js +65 -0
- package/dist/cli/dispatch/skill-publish.d.ts +15 -0
- package/dist/cli/dispatch/skill-publish.js +44 -0
- package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
- package/dist/cli/dispatch/skills-cmd.js +93 -0
- package/dist/cli/dispatch/tdd.d.ts +11 -0
- package/dist/cli/dispatch/tdd.js +45 -0
- package/dist/cli/dispatch/types.d.ts +29 -0
- package/dist/cli/dispatch/types.js +2 -0
- package/dist/cli/dispatch/workflow.d.ts +11 -0
- package/dist/cli/dispatch/workflow.js +67 -0
- package/dist/cli/help.d.ts +127 -0
- package/dist/cli/help.js +138 -0
- package/dist/cli/runtime.d.ts +16 -0
- package/dist/cli/runtime.js +38 -0
- package/dist/cli/validators.d.ts +17 -0
- package/dist/cli/validators.js +35 -0
- package/dist/commands/analyze.js +1 -3
- package/dist/commands/ci.d.ts +47 -0
- package/dist/commands/ci.js +380 -63
- package/dist/commands/doctor.js +2 -4
- package/dist/commands/init/report.d.ts +13 -0
- package/dist/commands/init/report.js +13 -0
- package/dist/commands/init/steps/agent-skills.d.ts +13 -0
- package/dist/commands/init/steps/agent-skills.js +45 -0
- package/dist/commands/init/steps/ai-sync.d.ts +15 -0
- package/dist/commands/init/steps/ai-sync.js +62 -0
- package/dist/commands/init/steps/ci.d.ts +24 -0
- package/dist/commands/init/steps/ci.js +74 -0
- package/dist/commands/init/steps/claude-md.d.ts +14 -0
- package/dist/commands/init/steps/claude-md.js +54 -0
- package/dist/commands/init/steps/code-graph.d.ts +16 -0
- package/dist/commands/init/steps/code-graph.js +68 -0
- package/dist/commands/init/steps/context-dir.d.ts +13 -0
- package/dist/commands/init/steps/context-dir.js +47 -0
- package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
- package/dist/commands/init/steps/docker-deploy.js +60 -0
- package/dist/commands/init/steps/ghagga.d.ts +15 -0
- package/dist/commands/init/steps/ghagga.js +58 -0
- package/dist/commands/init/steps/git.d.ts +25 -0
- package/dist/commands/init/steps/git.js +81 -0
- package/dist/commands/init/steps/gitignore.d.ts +13 -0
- package/dist/commands/init/steps/gitignore.js +39 -0
- package/dist/commands/init/steps/local-ai.d.ts +14 -0
- package/dist/commands/init/steps/local-ai.js +53 -0
- package/dist/commands/init/steps/manifest.d.ts +13 -0
- package/dist/commands/init/steps/manifest.js +58 -0
- package/dist/commands/init/steps/memory.d.ts +15 -0
- package/dist/commands/init/steps/memory.js +60 -0
- package/dist/commands/init/steps/mock.d.ts +13 -0
- package/dist/commands/init/steps/mock.js +64 -0
- package/dist/commands/init/steps/sdd.d.ts +12 -0
- package/dist/commands/init/steps/sdd.js +40 -0
- package/dist/commands/init/steps/security.d.ts +28 -0
- package/dist/commands/init/steps/security.js +103 -0
- package/dist/commands/init/types.d.ts +30 -0
- package/dist/commands/init/types.js +2 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +42 -630
- package/dist/commands/llmstxt.js +1 -1
- package/dist/commands/plugin.js +1 -1
- package/dist/commands/security.js +23 -4
- package/dist/commands/skills/analysis.d.ts +19 -0
- package/dist/commands/skills/analysis.js +211 -0
- package/dist/commands/skills/benchmark.d.ts +6 -0
- package/dist/commands/skills/benchmark.js +82 -0
- package/dist/commands/skills/constants.d.ts +10 -0
- package/dist/commands/skills/constants.js +27 -0
- package/dist/commands/skills/directives.d.ts +21 -0
- package/dist/commands/skills/directives.js +101 -0
- package/dist/commands/skills/parsing.d.ts +16 -0
- package/dist/commands/skills/parsing.js +108 -0
- package/dist/commands/skills/rules.d.ts +5 -0
- package/dist/commands/skills/rules.js +32 -0
- package/dist/commands/skills/scoring.d.ts +47 -0
- package/dist/commands/skills/scoring.js +306 -0
- package/dist/commands/skills.d.ts +7 -125
- package/dist/commands/skills.js +8 -874
- package/dist/commands/tdd-pipeline.js +1 -1
- package/dist/commands/tdd.js +1 -1
- package/dist/commands/workflow.js +2 -0
- package/dist/constants.js +2 -2
- package/dist/index.js +26 -473
- package/dist/lib/agent-skills.js +1 -1
- package/dist/{commands → lib}/atlassian-mcp.js +2 -2
- package/dist/lib/auto-skill-install.js +2 -3
- package/dist/lib/auto-wire.js +7 -7
- package/dist/lib/ci-config.d.ts +65 -0
- package/dist/lib/ci-config.js +297 -0
- package/dist/lib/claudemd.js +1 -1
- package/dist/lib/codex-export.js +4 -8
- package/dist/lib/common.js +2 -2
- package/dist/lib/context.js +2 -2
- package/dist/{commands → lib}/crash-recovery.js +4 -4
- package/dist/lib/docker.d.ts +22 -1
- package/dist/lib/docker.js +57 -49
- package/dist/lib/exec.d.ts +10 -0
- package/dist/lib/exec.js +11 -0
- package/dist/lib/frontmatter.js +2 -2
- package/dist/{commands → lib}/harness-audit.js +1 -1
- package/dist/{commands → lib}/parallel-batch.js +1 -3
- package/dist/lib/plugin.js +2 -4
- package/dist/{commands → lib}/secret-scanner.js +3 -1
- package/dist/{commands → lib}/security-analysis.js +4 -2
- package/dist/lib/skill-publish.js +23 -5
- package/dist/{commands → lib}/skill-scanner.d.ts +1 -1
- package/dist/{commands → lib}/skill-scanner.js +3 -3
- package/dist/lib/stack-detector.js +3 -3
- package/dist/{commands → lib}/supply-chain.js +1 -1
- package/dist/lib/template.js +2 -2
- package/dist/lib/workflow/discovery.d.ts +8 -0
- package/dist/lib/workflow/discovery.js +18 -1
- package/dist/lib/workflow/parser.js +15 -15
- package/dist/lib/workflow/validator.js +4 -4
- package/dist/tasks/index.d.ts +3 -3
- package/dist/tasks/index.js +3 -3
- package/dist/tasks/scaffold-tasks.d.ts +1 -1
- package/dist/tasks/scaffold-tasks.js +10 -10
- package/dist/tasks/task-id.d.ts +1 -1
- package/dist/tasks/task-id.js +9 -5
- package/dist/tasks/task-tracker.d.ts +1 -1
- package/dist/tasks/task-tracker.js +7 -7
- package/dist/ui/App.js +1 -1
- package/dist/ui/CIContext.js +1 -2
- package/dist/ui/Header.js +4 -4
- package/dist/ui/NameInput.js +1 -1
- package/dist/ui/Progress.d.ts +1 -1
- package/dist/ui/Progress.js +1 -1
- package/dist/ui/StackSelector.js +1 -1
- package/lib/common.psm1 +167 -0
- package/lib/common.sh +30 -0
- package/package.json +38 -19
- /package/dist/{commands → lib}/atlassian-mcp.d.ts +0 -0
- /package/dist/{commands → lib}/crash-recovery.d.ts +0 -0
- /package/dist/{commands → lib}/harness-audit.d.ts +0 -0
- /package/dist/{commands → lib}/parallel-batch.d.ts +0 -0
- /package/dist/{commands → lib}/secret-scanner.d.ts +0 -0
- /package/dist/{commands → lib}/security-analysis.d.ts +0 -0
- /package/dist/{commands → lib}/supply-chain.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.js +0 -0
package/README.md
CHANGED
|
@@ -88,8 +88,12 @@ flowchart LR
|
|
|
88
88
|
| `ci` | Run CI simulation (lint + compile + test + security + ghagga) |
|
|
89
89
|
| `ci init` | Install git hooks in `.git/hooks/` (recommended for existing repos) |
|
|
90
90
|
| `tdd init` | Install TDD-enforcing pre-commit hook (auto-detects stack) |
|
|
91
|
+
| `tdd pipeline` | Install full TDD pipeline (red → green → refactor enforcement) |
|
|
91
92
|
| `analyze` | Run repoforge skills analysis on current project |
|
|
92
93
|
| `doctor` | Show comprehensive health report |
|
|
94
|
+
| `workflow show` | Print the active workflow definition |
|
|
95
|
+
| `workflow validate` | Lint the workflow YAML against the schema |
|
|
96
|
+
| `workflow list` | List bundled workflow templates |
|
|
93
97
|
| `plugin add` | Install a plugin from GitHub (`org/repo`) |
|
|
94
98
|
| `plugin remove` | Remove an installed plugin |
|
|
95
99
|
| `plugin list` | List installed plugins |
|
|
@@ -98,8 +102,11 @@ flowchart LR
|
|
|
98
102
|
| `plugin sync` | Auto-detect and wire installed plugins into manifest |
|
|
99
103
|
| `plugin export` | Export plugin to Agent Skills spec (`skills.json`) |
|
|
100
104
|
| `plugin export --codex` | Export plugin to Codex-compatible TOML subagent files |
|
|
105
|
+
| `plugin export-skills` | Aggregate all installed plugin skills into a single bundle |
|
|
101
106
|
| `plugin import` | Import an Agent Skills spec package as a javi-forge plugin |
|
|
102
107
|
| `skills doctor` | Show skills health report (conflict + duplicate detection) |
|
|
108
|
+
| `skills auto` | Scan project lockfiles and auto-install matching skills from the registry |
|
|
109
|
+
| `skill publish` | Publish a local skill to the registry (validates manifest, signs the artifact) |
|
|
103
110
|
| `skills budget` | Show token cost of loaded skills |
|
|
104
111
|
| `skills score` | Score a skill on quality dimensions (0-100) |
|
|
105
112
|
| `skills benchmark` | Benchmark a skill with structural quality checks |
|
|
@@ -126,6 +133,43 @@ npx javi-forge security check
|
|
|
126
133
|
npx javi-forge llms-txt
|
|
127
134
|
```
|
|
128
135
|
|
|
136
|
+
### Mixed-stack CI (hybrid repositories)
|
|
137
|
+
|
|
138
|
+
By default `ci` auto-detects a **single** stack from marker files — zero
|
|
139
|
+
config, unchanged for single-stack repos. Hybrid repositories (e.g. Node
|
|
140
|
+
frontend + Python backend) declare every toolchain in a versioned config,
|
|
141
|
+
`.javi-forge/ci.yaml`:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
version: 1
|
|
145
|
+
runners:
|
|
146
|
+
- name: backend
|
|
147
|
+
stack: python
|
|
148
|
+
directory: backend
|
|
149
|
+
setup: pip install -r requirements.txt
|
|
150
|
+
lint: ruff check .
|
|
151
|
+
test: pytest
|
|
152
|
+
requires: [python3, ruff, pytest]
|
|
153
|
+
- name: frontend
|
|
154
|
+
stack: node
|
|
155
|
+
lint: pnpm run lint
|
|
156
|
+
test: pnpm run test
|
|
157
|
+
requires: [node, pnpm]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Runners execute in order, each in its own directory and image, and every
|
|
161
|
+
`requires` tool is verified fail-closed before any phase runs — a missing
|
|
162
|
+
tool aborts with an explicit error instead of a skipped check. Custom
|
|
163
|
+
images, digest pinning (`image: name@sha256:…`) and custom
|
|
164
|
+
`build-context` Dockerfiles are supported.
|
|
165
|
+
|
|
166
|
+
- `javi-forge ci --config <path>` — load explicit runners
|
|
167
|
+
- `javi-forge ci --stack python` — force a single stack (single-stack
|
|
168
|
+
repos only; insufficient for hybrid repos)
|
|
169
|
+
|
|
170
|
+
Full guide: [docs/ci-runners.md](docs/ci-runners.md) (config reference,
|
|
171
|
+
migration from auto-detection, troubleshooting, trust boundary).
|
|
172
|
+
|
|
129
173
|
### CLI Flags
|
|
130
174
|
|
|
131
175
|
| Flag | Type | Default | Description |
|
|
@@ -330,7 +374,7 @@ npx javi-forge doctor
|
|
|
330
374
|
|
|
331
375
|
## Requirements
|
|
332
376
|
|
|
333
|
-
- **Node.js** >= 18
|
|
377
|
+
- **Node.js** >= 22 (required by ink 7; previous versions ran on >= 18)
|
|
334
378
|
|
|
335
379
|
## Ecosystem
|
|
336
380
|
|
package/ci-local/README.md
CHANGED
|
@@ -15,6 +15,16 @@ Reproduce tu CI/CD localmente antes de push. Si pasa local → pasa en GitHub Ac
|
|
|
15
15
|
|
|
16
16
|
## Instalación (30 segundos)
|
|
17
17
|
|
|
18
|
+
### Prerequisito obligatorio
|
|
19
|
+
|
|
20
|
+
Los hooks invocan la CLI `javi-forge`. Instalala global ANTES de correr `install.sh`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g javi-forge
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Si estás desarrollando javi-forge mismo, usá `pnpm link --global` desde la raíz del workspace.
|
|
27
|
+
|
|
18
28
|
### Copiar a un proyecto nuevo
|
|
19
29
|
|
|
20
30
|
```bash
|
|
@@ -24,12 +34,25 @@ cp -r lib /path/to/new-project/
|
|
|
24
34
|
|
|
25
35
|
# En el nuevo proyecto
|
|
26
36
|
cd /path/to/new-project
|
|
27
|
-
./.ci-local/install.sh # Linux/Mac
|
|
37
|
+
./.ci-local/install.sh # Linux/Mac/WSL
|
|
28
38
|
# o
|
|
29
|
-
.\.ci-local\install.ps1 # Windows
|
|
39
|
+
.\.ci-local\install.ps1 # Windows (PowerShell 7+)
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
> **Importante:** CI-Local depende de `lib/common.sh` (
|
|
42
|
+
> **Importante:** CI-Local depende de `lib/common.sh` (Linux/Mac) y/o `lib/common.psm1` (Windows). Copiá ambos junto con el directorio `ci-local/`.
|
|
43
|
+
|
|
44
|
+
El installer (ambas variantes) falla con mensaje claro si `javi-forge` no está en el PATH.
|
|
45
|
+
|
|
46
|
+
### Soporte cross-platform
|
|
47
|
+
|
|
48
|
+
| Plataforma | Installer | Runner | Hooks | Requisitos |
|
|
49
|
+
|---|---|---|---|---|
|
|
50
|
+
| Linux | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
|
|
51
|
+
| macOS | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
|
|
52
|
+
| WSL | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
|
|
53
|
+
| Windows nativo | `install.ps1` | `ci-local.ps1` | bash | PowerShell 7+, Git for Windows (MSYS2 bash), docker (opcional) |
|
|
54
|
+
|
|
55
|
+
Los git hooks (`pre-commit`, `commit-msg`, `pre-push`) son bash en todas las plataformas. En Windows nativo corren via el MSYS2 bash que viene con Git for Windows — no se necesita una versión PowerShell de los hooks.
|
|
33
56
|
|
|
34
57
|
### Dependencias opcionales
|
|
35
58
|
|
|
@@ -53,15 +76,37 @@ git commit -m "..." # → pre-commit: AI check + lint + security (~30s)
|
|
|
53
76
|
git push # → pre-push: CI completo en Docker (~3min)
|
|
54
77
|
```
|
|
55
78
|
|
|
56
|
-
### AI Attribution Blocker
|
|
79
|
+
### AI Attribution Blocker (best-effort)
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- Nombres de modelos: `claude opus`, `gpt-4`, `chatgpt`, etc.
|
|
81
|
+
El hook `commit-msg` bloquea atribución accidental a herramientas de IA.
|
|
82
|
+
**Es best-effort, no es un control de seguridad adversarial.**
|
|
83
|
+
|
|
84
|
+
#### Qué bloquea
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
- `Co-authored-by: Claude`, `Co-authored-by: GPT`, etc.
|
|
87
|
+
- `Made/generated/written/built/produced by/with X` (con proveedores conocidos)
|
|
88
|
+
- `X-assisted` (anthropic-assisted, openai-assisted, etc.)
|
|
89
|
+
- Referencias a `@anthropic.com`, `@openai.com`, etc.
|
|
90
|
+
- Nombres de modelos: `claude opus`, `gpt-4`, `chatgpt`, `claude-sonnet`, `Claude3.5`
|
|
91
|
+
- AI IDEs: `Cursor`, `Windsurf`, `Codeium`, `Cody`, `Aider`
|
|
92
|
+
- Variantes con whitespace doble, markdown (`**Claude**`), puntuación entre tokens,
|
|
93
|
+
emoji separadores, full-width latin (`Claude`), combining diacritics (`Ćlaude`),
|
|
94
|
+
y Unicode invisibles (ZWSP/RLM/NBSP).
|
|
95
|
+
|
|
96
|
+
#### Qué NO bloquea (limitaciones documentadas)
|
|
97
|
+
|
|
98
|
+
- **Homoglifos cross-script**: `Сlaude` (Cyrillic), `Ϲlaude` (Greek), `Ꮯlaude` (Cherokee).
|
|
99
|
+
Requieren transliteración via módulos perl no-core.
|
|
100
|
+
- **Tag characters / variation selectors**: `Made by\u{E0063}laude`. Rangos Unicode
|
|
101
|
+
específicos no estripeados.
|
|
102
|
+
- **Bare provider name sin verbo prepositional**: "asked Claude to refactor",
|
|
103
|
+
"I used Claude for this", "claude helped me debug". El riesgo de false positive
|
|
104
|
+
en nombres personales o conversaciones legítimas es alto.
|
|
105
|
+
|
|
106
|
+
Si tu modelo de amenaza requiere defensa contra adversarios motivados, usá
|
|
107
|
+
**signed commits** + revisor humano. Un regex no es cripto.
|
|
108
|
+
|
|
109
|
+
**Tú sos el único autor de tu código.**
|
|
65
110
|
|
|
66
111
|
### Manual
|
|
67
112
|
|
|
@@ -133,7 +178,7 @@ Developer workflow:
|
|
|
133
178
|
|
|
134
179
|
lib/ # Dependencia requerida (copiar junto con .ci-local/)
|
|
135
180
|
├── common.sh # Funciones compartidas (Bash)
|
|
136
|
-
└──
|
|
181
|
+
└── common.psm1 # Funciones compartidas (PowerShell)
|
|
137
182
|
```
|
|
138
183
|
|
|
139
184
|
## Personalización
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# CI-LOCAL: Universal CI Simulation (PowerShell)
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Mirror of ci-local/ci-local.sh for Windows / PowerShell users.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# .\ci-local.ps1 # full CI
|
|
8
|
+
# .\ci-local.ps1 quick # lint + compile only
|
|
9
|
+
# .\ci-local.ps1 shell # interactive shell inside CI container
|
|
10
|
+
# .\ci-local.ps1 detect # print detected stack and exit
|
|
11
|
+
#
|
|
12
|
+
# Behaviour MUST match ci-local.sh. Diff against the bash version on every
|
|
13
|
+
# change.
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
#Requires -Version 7.2
|
|
17
|
+
[CmdletBinding()]
|
|
18
|
+
param(
|
|
19
|
+
[Parameter(Position = 0)]
|
|
20
|
+
[ValidateSet('full', 'quick', 'shell', 'detect')]
|
|
21
|
+
[string]$Mode = 'full'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# See install.ps1 for the rationale behind 7.2 minimum (ResolveLinkTarget).
|
|
25
|
+
if ($PSVersionTable.PSEdition -ne 'Core' -or $PSVersionTable.PSVersion -lt [Version]'7.2') {
|
|
26
|
+
Write-Host 'ERROR: ci-local.ps1 requires PowerShell 7.2+ (pwsh).' -ForegroundColor Red
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Set-StrictMode -Version Latest
|
|
31
|
+
$ErrorActionPreference = 'Stop'
|
|
32
|
+
|
|
33
|
+
$ScriptDir = Split-Path -Parent $PSCommandPath
|
|
34
|
+
$ProjectDir = Split-Path -Parent $ScriptDir
|
|
35
|
+
|
|
36
|
+
# ─── Symlink-safe path resolver (inline, runs BEFORE Import-Module) ───
|
|
37
|
+
# Same defense as install.ps1: remove any pre-existing alias/function with
|
|
38
|
+
# this name (Alias > Function in PS lookup order would let a $PROFILE
|
|
39
|
+
# alias shadow our resolver), then verify the binding is the local
|
|
40
|
+
# function defined in THIS script file.
|
|
41
|
+
Remove-Item Alias:Resolve-RealPath -Force -ErrorAction SilentlyContinue
|
|
42
|
+
Remove-Item Function:Resolve-RealPath -Force -ErrorAction SilentlyContinue
|
|
43
|
+
function Resolve-RealPath([string]$P) {
|
|
44
|
+
if ([string]::IsNullOrEmpty($P)) { throw "Resolve-RealPath: empty path" }
|
|
45
|
+
$item = Get-Item -LiteralPath $P -Force -ErrorAction Stop
|
|
46
|
+
$target = $item.ResolveLinkTarget($true)
|
|
47
|
+
if ($target) {
|
|
48
|
+
if (-not $target.Exists) {
|
|
49
|
+
throw "Resolve-RealPath: dangling symlink target $($target.FullName)"
|
|
50
|
+
}
|
|
51
|
+
return $target.FullName
|
|
52
|
+
}
|
|
53
|
+
return $item.FullName
|
|
54
|
+
}
|
|
55
|
+
$_rr = Get-Command Resolve-RealPath -ErrorAction SilentlyContinue
|
|
56
|
+
if (-not $_rr -or $_rr.CommandType -ne 'Function' -or $_rr.ScriptBlock.File -ne $PSCommandPath) {
|
|
57
|
+
Write-Host 'ERROR: Resolve-RealPath is shadowed by an external command' -ForegroundColor Red
|
|
58
|
+
exit 1
|
|
59
|
+
}
|
|
60
|
+
Remove-Variable _rr
|
|
61
|
+
|
|
62
|
+
# ─── Load shared lib ──────────────────────────────────────────────────
|
|
63
|
+
$libCandidates = @(
|
|
64
|
+
(Join-Path $ScriptDir 'lib' 'common.psm1')
|
|
65
|
+
(Join-Path $ScriptDir '..' 'lib' 'common.psm1')
|
|
66
|
+
)
|
|
67
|
+
$libPath = $libCandidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
|
68
|
+
if (-not $libPath) {
|
|
69
|
+
Write-Host 'ERROR: lib/common.psm1 not found' -ForegroundColor Red
|
|
70
|
+
exit 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# SECURITY: validate the module BEFORE importing. Mirror of install.ps1.
|
|
74
|
+
try {
|
|
75
|
+
$libReal = Resolve-RealPath $libPath
|
|
76
|
+
$projectReal = Resolve-RealPath $ProjectDir
|
|
77
|
+
} catch {
|
|
78
|
+
Write-Host "ERROR: could not resolve lib/common.psm1 or project root: $($_.Exception.Message)" -ForegroundColor Red
|
|
79
|
+
exit 1
|
|
80
|
+
}
|
|
81
|
+
$sep = [System.IO.Path]::DirectorySeparatorChar
|
|
82
|
+
if (-not ($libReal.Equals($projectReal, [StringComparison]::OrdinalIgnoreCase) -or
|
|
83
|
+
$libReal.StartsWith($projectReal + $sep, [StringComparison]::OrdinalIgnoreCase))) {
|
|
84
|
+
Write-Host 'ERROR: lib/common.psm1 resolves outside the project root' -ForegroundColor Red
|
|
85
|
+
Write-Host " LIB resolved: $libReal" -ForegroundColor Yellow
|
|
86
|
+
Write-Host " PROJECT resolved: $projectReal" -ForegroundColor Yellow
|
|
87
|
+
exit 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Import-Module $libPath -Force
|
|
91
|
+
|
|
92
|
+
# ─── Stack detection + per-stack CI commands ──────────────────────────
|
|
93
|
+
function Get-CiConfig {
|
|
94
|
+
param([string]$ProjectDir)
|
|
95
|
+
|
|
96
|
+
$stack = Get-Stack -ProjectDir $ProjectDir
|
|
97
|
+
|
|
98
|
+
$cfg = @{
|
|
99
|
+
StackType = $stack.StackType
|
|
100
|
+
BuildTool = $stack.BuildTool
|
|
101
|
+
JavaVersion = $stack.JavaVersion
|
|
102
|
+
Dockerfile = ''
|
|
103
|
+
LintCmd = ''
|
|
104
|
+
CompileCmd = ''
|
|
105
|
+
TestCmd = ''
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
switch ($stack.StackType) {
|
|
109
|
+
'java-gradle' {
|
|
110
|
+
$cfg.Dockerfile = 'java.Dockerfile'
|
|
111
|
+
$cfg.LintCmd = './gradlew spotlessCheck --no-daemon'
|
|
112
|
+
$cfg.CompileCmd = './gradlew clean classes testClasses --no-daemon && chown -R runner:runner build .gradle 2>/dev/null || true'
|
|
113
|
+
$cfg.TestCmd = './gradlew test --no-daemon'
|
|
114
|
+
}
|
|
115
|
+
'java-maven' {
|
|
116
|
+
$cfg.Dockerfile = 'java.Dockerfile'
|
|
117
|
+
$cfg.LintCmd = './mvnw spotless:check'
|
|
118
|
+
$cfg.CompileCmd = './mvnw clean compile test-compile && chown -R runner:runner target .mvn 2>/dev/null || true'
|
|
119
|
+
$cfg.TestCmd = './mvnw test'
|
|
120
|
+
}
|
|
121
|
+
'node' {
|
|
122
|
+
$cfg.Dockerfile = 'node.Dockerfile'
|
|
123
|
+
$pkg = Join-Path $ProjectDir 'package.json'
|
|
124
|
+
$pkgRaw = Get-Content -LiteralPath $pkg -Raw -ErrorAction SilentlyContinue
|
|
125
|
+
if ($pkgRaw -and ($pkgRaw -match '"lint"')) { $cfg.LintCmd = "$($stack.BuildTool) run lint" }
|
|
126
|
+
if ($pkgRaw -and ($pkgRaw -match '"build"')) { $cfg.CompileCmd = "rm -rf dist build && $($stack.BuildTool) run build && chown -R runner:runner dist build 2>/dev/null || true" }
|
|
127
|
+
if ($pkgRaw -and ($pkgRaw -match '"test"')) {
|
|
128
|
+
$cfg.TestCmd = "$($stack.BuildTool) test"
|
|
129
|
+
if ($pkgRaw -match '"test:hooks"') {
|
|
130
|
+
$cfg.TestCmd = "$($cfg.TestCmd) && $($stack.BuildTool) run test:hooks"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
'python' {
|
|
135
|
+
$cfg.Dockerfile = 'python.Dockerfile'
|
|
136
|
+
$cfg.LintCmd = 'ruff check . && { pylint **/*.py 2>/dev/null || true; }'
|
|
137
|
+
$cfg.TestCmd = 'pytest'
|
|
138
|
+
}
|
|
139
|
+
'go' {
|
|
140
|
+
$cfg.Dockerfile = 'go.Dockerfile'
|
|
141
|
+
$cfg.LintCmd = 'golangci-lint run'
|
|
142
|
+
$cfg.CompileCmd = 'go clean -cache && go build ./... && chown -R runner:runner . 2>/dev/null || true'
|
|
143
|
+
$cfg.TestCmd = 'go test ./...'
|
|
144
|
+
}
|
|
145
|
+
'rust' {
|
|
146
|
+
$cfg.Dockerfile = 'rust.Dockerfile'
|
|
147
|
+
$cfg.LintCmd = 'cargo clippy -- -D warnings'
|
|
148
|
+
$cfg.CompileCmd = 'cargo clean && cargo build && chown -R runner:runner target 2>/dev/null || true'
|
|
149
|
+
$cfg.TestCmd = 'cargo test'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return $cfg
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ─── Docker helpers ───────────────────────────────────────────────────
|
|
157
|
+
function Get-ImageName {
|
|
158
|
+
param([string]$StackType)
|
|
159
|
+
"ci-local-$StackType"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function New-Dockerfile {
|
|
163
|
+
param([Parameter(Mandatory)][hashtable]$Cfg)
|
|
164
|
+
|
|
165
|
+
$dockerDir = Join-Path $ScriptDir 'docker'
|
|
166
|
+
New-Item -ItemType Directory -Force -Path $dockerDir | Out-Null
|
|
167
|
+
$target = Join-Path $dockerDir $Cfg.Dockerfile
|
|
168
|
+
|
|
169
|
+
# Heredoc bodies kept byte-identical to ci-local.sh so the image-hash
|
|
170
|
+
# cache aligns across platforms when the same Dockerfile is generated.
|
|
171
|
+
$content = switch ($Cfg.StackType) {
|
|
172
|
+
{ $_ -in 'java-gradle','java-maven' } {
|
|
173
|
+
@'
|
|
174
|
+
ARG JAVA_VERSION=21
|
|
175
|
+
FROM eclipse-temurin:${JAVA_VERSION}-jdk-noble
|
|
176
|
+
RUN apt-get update && apt-get install -y git curl unzip && rm -rf /var/lib/apt/lists/*
|
|
177
|
+
RUN useradd -m -s /bin/bash runner
|
|
178
|
+
USER runner
|
|
179
|
+
WORKDIR /home/runner/work
|
|
180
|
+
ENV GRADLE_USER_HOME=/home/runner/.gradle
|
|
181
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
182
|
+
'@
|
|
183
|
+
}
|
|
184
|
+
'node' {
|
|
185
|
+
@'
|
|
186
|
+
FROM node:22-slim@sha256:689c11043dad91472750cd824c97dd5e2318e9dd6f954e492fe7af0135d33ceb
|
|
187
|
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
188
|
+
RUN npm install -g pnpm
|
|
189
|
+
RUN useradd -m -s /bin/bash runner
|
|
190
|
+
USER runner
|
|
191
|
+
WORKDIR /home/runner/work
|
|
192
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
193
|
+
'@
|
|
194
|
+
}
|
|
195
|
+
'python' {
|
|
196
|
+
@'
|
|
197
|
+
FROM python:3.12-slim@sha256:401f6e1a67dad31a1bd78e9ad22d0ee0a3b52154e6bd30e90be696bb6a3d7461
|
|
198
|
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
199
|
+
RUN pip install --no-cache-dir pytest ruff pylint poetry
|
|
200
|
+
RUN useradd -m -s /bin/bash runner
|
|
201
|
+
USER runner
|
|
202
|
+
WORKDIR /home/runner/work
|
|
203
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
204
|
+
'@
|
|
205
|
+
}
|
|
206
|
+
'go' {
|
|
207
|
+
@'
|
|
208
|
+
FROM golang:1.23-bookworm@sha256:167053a2bb901972bf2c1611f8f52c44d5fe7e762e5cab213708d82c421614db
|
|
209
|
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
210
|
+
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 && \
|
|
211
|
+
mv /root/go/bin/golangci-lint /usr/local/bin/
|
|
212
|
+
RUN useradd -m -s /bin/bash runner
|
|
213
|
+
USER runner
|
|
214
|
+
WORKDIR /home/runner/work
|
|
215
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
216
|
+
'@
|
|
217
|
+
}
|
|
218
|
+
'rust' {
|
|
219
|
+
@'
|
|
220
|
+
FROM rust:1.83-slim@sha256:540c902e99c384163b688bbd8b5b8520e94e7731b27f7bd0eaa56ae1960627ab
|
|
221
|
+
RUN apt-get update && apt-get install -y git pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
|
|
222
|
+
RUN rustup component add clippy rustfmt
|
|
223
|
+
RUN useradd -m -s /bin/bash runner
|
|
224
|
+
USER runner
|
|
225
|
+
WORKDIR /home/runner/work
|
|
226
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
227
|
+
'@
|
|
228
|
+
}
|
|
229
|
+
default {
|
|
230
|
+
@'
|
|
231
|
+
FROM ubuntu:24.04@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b
|
|
232
|
+
RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
|
|
233
|
+
RUN useradd -m -s /bin/bash runner
|
|
234
|
+
USER runner
|
|
235
|
+
WORKDIR /home/runner/work
|
|
236
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
237
|
+
'@
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Write with LF line endings to match the bash output, so the
|
|
242
|
+
# dockerfile-hash label stays consistent between platforms.
|
|
243
|
+
[System.IO.File]::WriteAllText($target, ($content + "`n"), [System.Text.UTF8Encoding]::new($false))
|
|
244
|
+
Write-Host "Created $($Cfg.Dockerfile)" -ForegroundColor Green
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function Confirm-DockerImage {
|
|
248
|
+
param([Parameter(Mandatory)][hashtable]$Cfg)
|
|
249
|
+
|
|
250
|
+
$imageName = Get-ImageName -StackType $Cfg.StackType
|
|
251
|
+
$dockerfile = Join-Path $ScriptDir 'docker' $Cfg.Dockerfile
|
|
252
|
+
|
|
253
|
+
if (-not (Test-Path -LiteralPath $dockerfile -PathType Leaf)) {
|
|
254
|
+
Write-Host "Creating Dockerfile for $($Cfg.StackType)..." -ForegroundColor Yellow
|
|
255
|
+
New-Dockerfile -Cfg $Cfg
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# SHA256 of the Dockerfile content — matches `sha256sum` from the bash version.
|
|
259
|
+
$hash = (Get-FileHash -LiteralPath $dockerfile -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
260
|
+
$imageHash = ''
|
|
261
|
+
try {
|
|
262
|
+
$imageHash = & docker inspect --format='{{index .Config.Labels "dockerfile-hash"}}' $imageName 2>$null
|
|
263
|
+
} catch { }
|
|
264
|
+
|
|
265
|
+
if ($hash -ne $imageHash) {
|
|
266
|
+
Write-Host 'Building CI Docker image...' -ForegroundColor Yellow
|
|
267
|
+
$buildArgs = @('--label', "dockerfile-hash=$hash")
|
|
268
|
+
if ($Cfg.JavaVersion -and $Cfg.StackType -like 'java-*') {
|
|
269
|
+
$buildArgs += @('--build-arg', "JAVA_VERSION=$($Cfg.JavaVersion)")
|
|
270
|
+
}
|
|
271
|
+
& docker build @buildArgs -f $dockerfile -t $imageName (Join-Path $ScriptDir 'docker')
|
|
272
|
+
if ($LASTEXITCODE -ne 0) { throw 'docker build failed' }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Convert a host path to the form expected by docker run -v.
|
|
277
|
+
# Linux/macOS: /path/to/x -> /path/to/x (pass through)
|
|
278
|
+
# Windows: C:\Users\foo\proj -> /c/Users/foo/proj (Docker Desktop format)
|
|
279
|
+
# WSL inside Windows is the bash side and never reaches this code.
|
|
280
|
+
function ConvertTo-DockerHostPath {
|
|
281
|
+
param([Parameter(Mandatory)][string]$Path)
|
|
282
|
+
if (-not $IsWindows) { return $Path }
|
|
283
|
+
# Drive letter + colon. Two forms exist:
|
|
284
|
+
# C:\Users\foo → absolute, valid for docker bind
|
|
285
|
+
# C:Users\foo → drive-relative, NOT a real absolute path
|
|
286
|
+
# Refuse the drive-relative form — it produces /cUsers/foo which is a
|
|
287
|
+
# broken bind mount and worth surfacing immediately.
|
|
288
|
+
if ($Path -match '^([A-Za-z]):(.*)$') {
|
|
289
|
+
$drive = $Matches[1].ToLowerInvariant()
|
|
290
|
+
$rest = $Matches[2]
|
|
291
|
+
if (-not ($rest.StartsWith('/') -or $rest.StartsWith('\'))) {
|
|
292
|
+
throw "ConvertTo-DockerHostPath: drive-relative path not supported ($Path). Use an absolute path."
|
|
293
|
+
}
|
|
294
|
+
return "/$drive" + ($rest -replace '\\', '/')
|
|
295
|
+
}
|
|
296
|
+
return ($Path -replace '\\', '/')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function Invoke-InCi {
|
|
300
|
+
param(
|
|
301
|
+
[Parameter(Mandatory)][hashtable]$Cfg,
|
|
302
|
+
[Parameter(Mandatory)][string]$Cmd,
|
|
303
|
+
[string]$RunUser = 'runner'
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
$imageName = Get-ImageName -StackType $Cfg.StackType
|
|
307
|
+
|
|
308
|
+
$timeout = if ($env:CI_LOCAL_TIMEOUT) { $env:CI_LOCAL_TIMEOUT } else { '600' }
|
|
309
|
+
if ($timeout -notmatch '^\d+$') {
|
|
310
|
+
Write-Host "Error: CI_LOCAL_TIMEOUT must be a positive integer, got: $timeout" -ForegroundColor Red
|
|
311
|
+
exit 1
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
$dockerFlags = @('--rm')
|
|
315
|
+
# Allocate a TTY only when stdin is a real terminal. The bash version
|
|
316
|
+
# uses `[ -t 0 ]`; the PowerShell equivalent is IsInputRedirected from
|
|
317
|
+
# System.Console — older UserInteractive + RawUI checks are unreliable
|
|
318
|
+
# in pwsh 7 (flagged by audit round 3).
|
|
319
|
+
if (-not [Console]::IsInputRedirected) { $dockerFlags += '-it' }
|
|
320
|
+
|
|
321
|
+
$hostMount = ConvertTo-DockerHostPath -Path $ProjectDir
|
|
322
|
+
& docker run @dockerFlags `
|
|
323
|
+
--stop-timeout 30 `
|
|
324
|
+
--entrypoint '' `
|
|
325
|
+
--user $RunUser `
|
|
326
|
+
-v "${hostMount}:/home/runner/work" `
|
|
327
|
+
-e CI=true `
|
|
328
|
+
$imageName timeout $timeout bash -c $Cmd
|
|
329
|
+
if ($LASTEXITCODE -ne 0) { throw "step failed with exit $LASTEXITCODE" }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
# ─── Main ─────────────────────────────────────────────────────────────
|
|
333
|
+
Write-Host ''
|
|
334
|
+
Write-Host '=== CI-LOCAL ===' -ForegroundColor Yellow
|
|
335
|
+
|
|
336
|
+
$cfg = Get-CiConfig -ProjectDir $ProjectDir
|
|
337
|
+
|
|
338
|
+
if ($cfg.StackType -eq 'unknown') {
|
|
339
|
+
Write-Host 'Could not detect project type!' -ForegroundColor Red
|
|
340
|
+
Write-Host 'Supported: Java/Gradle, Java/Maven, Node, Python, Go, Rust' -ForegroundColor Yellow
|
|
341
|
+
exit 1
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
Write-Host "Detected: $($cfg.StackType) ($($cfg.BuildTool))" -ForegroundColor Green
|
|
345
|
+
if ($cfg.StackType -like 'java-*') {
|
|
346
|
+
Write-Host "Java version: $($cfg.JavaVersion)" -ForegroundColor Green
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
$ghaggaAvailable = Test-CommandExists -Name 'ghagga'
|
|
350
|
+
|
|
351
|
+
switch ($Mode) {
|
|
352
|
+
'detect' {
|
|
353
|
+
Write-Host ''
|
|
354
|
+
Write-Host 'Stack details:' -ForegroundColor Cyan
|
|
355
|
+
Write-Host " Type: $($cfg.StackType)"
|
|
356
|
+
Write-Host " Build tool: $($cfg.BuildTool)"
|
|
357
|
+
Write-Host " Dockerfile: $($cfg.Dockerfile)"
|
|
358
|
+
Write-Host " Lint: $($cfg.LintCmd)"
|
|
359
|
+
Write-Host " Compile: $($cfg.CompileCmd)"
|
|
360
|
+
Write-Host " Test: $($cfg.TestCmd)"
|
|
361
|
+
exit 0
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
'quick' {
|
|
365
|
+
Confirm-DockerImage -Cfg $cfg
|
|
366
|
+
Write-Host ''
|
|
367
|
+
Write-Host 'Running quick check...' -ForegroundColor Yellow
|
|
368
|
+
|
|
369
|
+
if ($cfg.LintCmd) {
|
|
370
|
+
Write-Host "Lint: $($cfg.LintCmd)" -ForegroundColor Cyan
|
|
371
|
+
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.LintCmd)"
|
|
372
|
+
}
|
|
373
|
+
if ($cfg.CompileCmd) {
|
|
374
|
+
Write-Host "Compile: $($cfg.CompileCmd)" -ForegroundColor Cyan
|
|
375
|
+
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.CompileCmd)" -RunUser 'root'
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
'shell' {
|
|
380
|
+
Confirm-DockerImage -Cfg $cfg
|
|
381
|
+
Write-Host ''
|
|
382
|
+
Write-Host 'Opening shell in CI environment...' -ForegroundColor Yellow
|
|
383
|
+
$imageName = Get-ImageName -StackType $cfg.StackType
|
|
384
|
+
$hostMount = ConvertTo-DockerHostPath -Path $ProjectDir
|
|
385
|
+
& docker run --rm -it `
|
|
386
|
+
-v "${hostMount}:/home/runner/work" `
|
|
387
|
+
-e CI=true `
|
|
388
|
+
$imageName 'cd /home/runner/work && bash'
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
Default {
|
|
392
|
+
# 'full'
|
|
393
|
+
Confirm-DockerImage -Cfg $cfg
|
|
394
|
+
Write-Host ''
|
|
395
|
+
Write-Host 'Running full CI simulation...' -ForegroundColor Yellow
|
|
396
|
+
|
|
397
|
+
$total = 0
|
|
398
|
+
if ($cfg.LintCmd) { $total++ }
|
|
399
|
+
if ($cfg.CompileCmd) { $total++ }
|
|
400
|
+
if ($cfg.TestCmd) { $total++ }
|
|
401
|
+
if ($ghaggaAvailable) { $total++ }
|
|
402
|
+
|
|
403
|
+
$step = 1
|
|
404
|
+
|
|
405
|
+
if ($cfg.LintCmd) {
|
|
406
|
+
Write-Host ''
|
|
407
|
+
Write-Host "Step $step/$total`: Lint" -ForegroundColor Yellow
|
|
408
|
+
Write-Host " $($cfg.LintCmd)" -ForegroundColor Cyan
|
|
409
|
+
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.LintCmd)"
|
|
410
|
+
$step++
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if ($cfg.CompileCmd) {
|
|
414
|
+
Write-Host ''
|
|
415
|
+
Write-Host "Step $step/$total`: Compile" -ForegroundColor Yellow
|
|
416
|
+
Write-Host " $($cfg.CompileCmd)" -ForegroundColor Cyan
|
|
417
|
+
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.CompileCmd)" -RunUser 'root'
|
|
418
|
+
$step++
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if ($cfg.TestCmd) {
|
|
422
|
+
Write-Host ''
|
|
423
|
+
Write-Host "Step $step/$total`: Test" -ForegroundColor Yellow
|
|
424
|
+
Write-Host " $($cfg.TestCmd)" -ForegroundColor Cyan
|
|
425
|
+
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.TestCmd)"
|
|
426
|
+
$step++
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if ($ghaggaAvailable) {
|
|
430
|
+
Write-Host ''
|
|
431
|
+
Write-Host "Step $step/$total`: GHAGGA Review" -ForegroundColor Yellow
|
|
432
|
+
Write-Host ' ghagga review --plain --exit-on-issues' -ForegroundColor Cyan
|
|
433
|
+
& ghagga review --plain --exit-on-issues
|
|
434
|
+
if ($LASTEXITCODE -ne 0) {
|
|
435
|
+
Write-Host 'GHAGGA review found issues!' -ForegroundColor Red
|
|
436
|
+
exit 1
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
Write-Host ''
|
|
443
|
+
Write-Host 'CI Local completed successfully!' -ForegroundColor Green
|
|
444
|
+
Write-Host ' Safe to push - CI should pass.' -ForegroundColor Green
|
|
445
|
+
Write-Host ''
|