javi-forge 1.37.1 → 1.38.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 +5 -5
- package/assets/claude-hooks/javi-forge-skillguard-pre-tool-use.mjs +13 -11
- package/assets/claude-hooks/manifest.json +35 -1
- package/ci-local/README.md +3 -6
- package/ci-local/ci-local.ps1 +36 -10
- package/ci-local/ci-local.sh +26 -9
- package/ci-local/install.ps1 +36 -14
- package/ci-local/install.sh +27 -10
- package/ci-local/lib/common.sh +3 -7
- package/ci-local/uninstall.ps1 +50 -0
- package/ci-local/uninstall.sh +38 -3
- package/dist/cli/bootstrap.d.ts +12 -0
- package/dist/cli/bootstrap.js +12 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +8 -1
- package/dist/cli/dispatch/simple-renderers.js +11 -2
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +85 -0
- package/dist/commands/claude-hooks.d.ts +1 -0
- package/dist/commands/claude-hooks.js +12 -3
- package/dist/commands/codex-hooks.d.ts +1 -0
- package/dist/commands/codex-hooks.js +12 -3
- package/dist/commands/doctor.d.ts +16 -1
- package/dist/commands/doctor.js +54 -35
- package/dist/commands/init.d.ts +4 -1
- package/dist/commands/init.js +2 -2
- package/dist/index.js +4 -81
- package/dist/lib/claude-hook-manager.d.ts +11 -6
- package/dist/lib/claude-hook-manager.js +28 -21
- package/dist/lib/codex-hook-manager.d.ts +10 -4
- package/dist/lib/codex-hook-manager.js +44 -13
- package/dist/lib/platform-support.d.ts +18 -14
- package/dist/lib/platform-support.js +19 -17
- package/dist/lib/secure-fs-posix.d.ts +8 -13
- package/dist/lib/secure-fs-posix.js +11 -52
- package/dist/lib/secure-fs-transaction.d.ts +2 -3
- package/dist/types/index.d.ts +7 -2
- package/dist/ui/Doctor.d.ts +2 -0
- package/dist/ui/Doctor.js +13 -0
- package/lib/common.sh +3 -7
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -13,6 +13,10 @@ npx javi-forge init
|
|
|
13
13
|
|
|
14
14
|
An interactive TUI guides you through project setup: pick a stack, CI provider, memory module, and get a production-ready project structure in seconds.
|
|
15
15
|
|
|
16
|
+
## Platform Support
|
|
17
|
+
|
|
18
|
+
javi-forge 2.0 supports Linux and Windows only. npm package eligibility enforces this boundary. Source or manual execution on every other host returns generic `unsupported-platform` guidance before probes or mutation; there is no legacy-host migration or compatibility mode.
|
|
19
|
+
|
|
16
20
|
## What It Creates
|
|
17
21
|
|
|
18
22
|
`javi-forge init` bootstraps a complete project with 13 sequential steps:
|
|
@@ -373,14 +377,10 @@ npx javi-forge doctor
|
|
|
373
377
|
- **Installed Modules** — engram, obsidian-brain, memory-simple, ghagga
|
|
374
378
|
|
|
375
379
|
|
|
376
|
-
## macOS CI-Local support
|
|
377
|
-
|
|
378
|
-
macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate your workflow. Existing installed guards are not removed in 1.x; Darwin code removal is planned separately for 2.0.
|
|
379
|
-
|
|
380
380
|
## Requirements
|
|
381
381
|
|
|
382
382
|
- **Node.js** >= 22 (required by ink 7; previous versions ran on >= 18)
|
|
383
|
-
- **Linux
|
|
383
|
+
- **Linux guard installation/repair** — the `acl` package (provides `getfacl`) is required to install or repair the Claude PreToolUse guard: the transactional installer proves every controlling directory carries no endangering ACL, and refuses fail-closed when `getfacl` is unresolvable. Install with `apt install acl`, `apk add acl`, or `dnf install acl` (slim container images usually omit it). An already-installed guard keeps working without it — `javi-forge hooks doctor claude` reports the capability as its own row.
|
|
384
384
|
- **`node` on `PATH`** — Claude Code spawns the guard in exec form, so `node` must resolve on the `PATH` Claude Code itself uses, not only inside javi-forge.
|
|
385
385
|
|
|
386
386
|
## Ecosystem
|
|
@@ -8,7 +8,8 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
export const MANAGED_MARKER = "// javi-forge-managed: claude-pretooluse v1";
|
|
9
9
|
export const INPUT_LIMIT_BYTES = 1_048_576;
|
|
10
10
|
export const SUPPORTED_TOOLS = Object.freeze(["Bash", "PowerShell", "Read", "Write", "Edit"]);
|
|
11
|
-
export const POLICY_REGISTRY = Object.freeze({ schemaVersion: 1, policyVersion:
|
|
11
|
+
export const POLICY_REGISTRY = Object.freeze({ schemaVersion: 1, policyVersion: 2, diagnosticsMaxBytes: 240 });
|
|
12
|
+
export function resolvePlatformSupport(platform = process.platform) { return platform === "linux" || platform === "win32" ? { supported: true } : { supported: false, reason: "unsupported-platform" }; }
|
|
12
13
|
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
13
14
|
// Per-agent adapter config (S0 core-extraction): every agent-specific input the guard needs
|
|
14
15
|
// (the isManaged protected-path set, the project-dir source, the managed marker) is resolved by
|
|
@@ -57,7 +58,7 @@ function lexicalNormalize(input, platform) {
|
|
|
57
58
|
else parts.push(part);
|
|
58
59
|
}
|
|
59
60
|
value = `${root}${root.endsWith("/") || parts.length === 0 ? "" : "/"}${parts.join("/")}`;
|
|
60
|
-
if (windows
|
|
61
|
+
if (windows) value = value.normalize("NFC").toLowerCase();
|
|
61
62
|
return value;
|
|
62
63
|
}
|
|
63
64
|
function nativeRealpath(input) {
|
|
@@ -109,7 +110,7 @@ function policyPathKeys(input, options = {}) {
|
|
|
109
110
|
function isAbsolutePolicyPath(value) { return POSIX_ABSOLUTE.test(value) || WINDOWS_DRIVE.test(value) || WINDOWS_UNC.test(value) || /^\\(?:\\\?|\?\?|\\\.)\\/i.test(value); }
|
|
110
111
|
// Home-relative credential files, matched as a path SUFFIX (never an expanded
|
|
111
112
|
// literal home) so one rule holds for every user, container and HOME. Literals
|
|
112
|
-
// stay lowercase because lexicalNormalize folds keys on
|
|
113
|
+
// stay lowercase because lexicalNormalize folds keys on win32.
|
|
113
114
|
const SENSITIVE_PATH_SUFFIXES = Object.freeze(["/.aws/credentials", "/.kube/config", "/.config/gcloud/application_default_credentials.json", "/.docker/config.json", "/.config/gh/hosts.yml"]);
|
|
114
115
|
// Absolute system secrets: the shadow suite holds every local account's password
|
|
115
116
|
// hash, and passwd/vipw leave the "-" backup beside it holding the same secret.
|
|
@@ -132,17 +133,16 @@ export function isSensitivePolicyKey(key, platform = process.platform) {
|
|
|
132
133
|
if (SENSITIVE_ABSOLUTE_KEYS.includes(key) || PROC_ENVIRON_KEY.test(key)) return true;
|
|
133
134
|
if (SENSITIVE_PATH_SUFFIXES.some((suffix) => key.endsWith(suffix))) return true;
|
|
134
135
|
if (SENSITIVE_DIRECTORY_SUFFIXES.some((directory) => key.endsWith(directory) || key.includes(`${directory}/`))) return true;
|
|
135
|
-
return platform === "win32"
|
|
136
|
+
return platform === "win32" ? basename.toLowerCase() === "serviceaccountkey.json" : basename === "serviceAccountKey.json";
|
|
136
137
|
}
|
|
137
138
|
function isManaged(key, managedSet = CLAUDE_MANAGED_SET, projectRoot = PROJECT_ROOT) {
|
|
138
139
|
const project = canonicalizePolicyPath(projectRoot);
|
|
139
140
|
if (!key.startsWith(`${project}/`) && key !== project) return false;
|
|
140
141
|
const relative = key.slice(project.length + 1);
|
|
141
|
-
// On
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
const foldedClaudeMd = (process.platform === "win32" || process.platform === "darwin") && managedSet.caseFoldExact.includes(relative);
|
|
142
|
+
// On win32 lexicalNormalize folds the key to lowercase, so the mixed-case
|
|
143
|
+
// CLAUDE.md literals must be matched case-insensitively too. Otherwise
|
|
144
|
+
// CLAUDE.md and .claude/CLAUDE.md lose managed-config protection.
|
|
145
|
+
const foldedClaudeMd = process.platform === "win32" && managedSet.caseFoldExact.includes(relative);
|
|
146
146
|
return foldedClaudeMd || managedSet.exact.includes(relative) || managedSet.prefixes.some((prefix) => relative.startsWith(prefix));
|
|
147
147
|
}
|
|
148
148
|
function evaluateFile(toolName, filePath, config = AGENT_CONFIGS.claude, projectRoot = PROJECT_ROOT) {
|
|
@@ -259,7 +259,7 @@ export function normalizeLiteralUtilityIdentity(rawToken) {
|
|
|
259
259
|
// Lexical-only: split on "/" without path/realpath/PATH/alias resolution.
|
|
260
260
|
// Basename compare is case-insensitive and host-independent so inherited
|
|
261
261
|
// deny families still fire for CHMOD/ENV/BASE64 on case-insensitive
|
|
262
|
-
// filesystems, mirroring the
|
|
262
|
+
// filesystems, mirroring the win32 path folding lexicalNormalize
|
|
263
263
|
// already applies. The canonical lowercase utility feeds profile lookup.
|
|
264
264
|
const token = typeof rawToken === "string" ? rawToken : "";
|
|
265
265
|
const literal = token.length > 0 && !NON_LITERAL_IDENTITY_MARKERS.test(token);
|
|
@@ -837,6 +837,7 @@ function diagnostic(error) {
|
|
|
837
837
|
"invalid-event": "input does not match the supported event schema",
|
|
838
838
|
"invalid-config": "missing or unknown --agent selector (expected --agent=<id>)",
|
|
839
839
|
"oversized-input": "stdin exceeds 1048576 bytes",
|
|
840
|
+
"unsupported-platform": "Linux and Windows are supported hosts",
|
|
840
841
|
"missing-policy": "embedded policy registry is unavailable",
|
|
841
842
|
"internal-error": "policy evaluation could not complete",
|
|
842
843
|
};
|
|
@@ -897,10 +898,11 @@ export function readBoundedStdin(stream = process.stdin) {
|
|
|
897
898
|
}
|
|
898
899
|
export async function main() {
|
|
899
900
|
try {
|
|
901
|
+
if (!resolvePlatformSupport().supported) throw new Error("unsupported-platform");
|
|
900
902
|
const config = resolveAgentConfig(process.argv);
|
|
901
903
|
const fault = process.argv.find((arg) => arg.startsWith("--javi-forge-test-fault="))?.split("=")[1];
|
|
902
904
|
if (fault === "missing-policy") throw new Error("missing-policy");
|
|
903
|
-
if (POLICY_REGISTRY.schemaVersion !== 1 || POLICY_REGISTRY.policyVersion !==
|
|
905
|
+
if (POLICY_REGISTRY.schemaVersion !== 1 || POLICY_REGISTRY.policyVersion !== 2) throw new Error("missing-policy");
|
|
904
906
|
const input = await readBoundedStdin();
|
|
905
907
|
let parsed;
|
|
906
908
|
try {
|
|
@@ -1 +1,35 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"asset": {
|
|
4
|
+
"name": "javi-forge-skillguard-pre-tool-use.mjs",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"policyVersion": 2,
|
|
7
|
+
"sha256": "59fc4224975ad64cfc85bab50ec60d9bd4948070e9d41f42ab43e6d8231c19a1",
|
|
8
|
+
"historical": [
|
|
9
|
+
"78be7e6613c012280b7ad17886462ba166b63ebd031e34565d757b3a0796d7cc",
|
|
10
|
+
"5dc2a5c31131f4ac7d8657c78b950de52776aad6eaefe78ea0d764a9963c4425",
|
|
11
|
+
"0c9aa8fa26b389f4892782f83104f0792c2b71ebca39c18c02706e2185e22b40",
|
|
12
|
+
"3581862f0567cce75a58b693c9ade80d39ee7d58add11537a34a8461c47c1ed4",
|
|
13
|
+
"54a270f28b068450b79547a88ec6f2d4854514392fd5f38ed1d6174ea093d7aa",
|
|
14
|
+
"9a565cec31d9e091e3fb9420b86685f824733bc1ebe479f086b2b955aba6ef3e"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"settingsEntries": {
|
|
18
|
+
"current": {
|
|
19
|
+
"version": 1,
|
|
20
|
+
"canonicalSha256": "b1341803cd076091edfcb473494514df1a23bd33372374ca1de6b75258e52e06"
|
|
21
|
+
},
|
|
22
|
+
"historical": [
|
|
23
|
+
{
|
|
24
|
+
"version": 1,
|
|
25
|
+
"canonicalSha256": "038c59a91bf8967f6908afed74c465f1e7030254e11e4f8738975d6d708424d4"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"installerHelpers": {
|
|
30
|
+
"windowsSecureObject": {
|
|
31
|
+
"name": "javi-forge-windows-secure-object.ps1",
|
|
32
|
+
"sha256": "2289ef6ac6b039ec74dc3ea0894413e243ff9bea963f04008a356b3838f9b8dd"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/ci-local/README.md
CHANGED
|
@@ -39,21 +39,18 @@ cd /path/to/new-project
|
|
|
39
39
|
.\.ci-local\install.ps1 # Windows (PowerShell 7+)
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
> **Importante:** CI-Local depende de `lib/common.sh` (Linux/WSL) y/o `lib/common.psm1` (Windows). Copiá ambos junto con el directorio `ci-local/`.
|
|
42
|
+
> **Importante:** CI-Local depende de `lib/common.sh` (Linux/WSL) y/o `lib/common.psm1` (Windows). Copiá ambos junto con el directorio `ci-local/`.
|
|
43
43
|
|
|
44
44
|
El installer (ambas variantes) falla con mensaje claro si `javi-forge` no está en el PATH.
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## Plataformas compatibles
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
### Soporte cross-platform
|
|
49
|
+
CI-Local soporta Linux/WSL y Windows nativo. Cualquier otro host recibe `unsupported-platform` antes de ejecutar comandos, probes, instalación o mutaciones. No existe un modo de compatibilidad ni un flujo de migración para hosts no soportados.
|
|
52
50
|
|
|
53
51
|
| Plataforma | Installer | Runner | Hooks | Requisitos |
|
|
54
52
|
|---|---|---|---|---|
|
|
55
53
|
| Linux | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
|
|
56
|
-
| macOS | Unsupported: pin a supported release or migrate | Refused before startup | Existing installed guards retained | No new install or startup |
|
|
57
54
|
| WSL | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
|
|
58
55
|
| Windows nativo | `install.ps1` | `ci-local.ps1` | bash | PowerShell 7+, Git for Windows (MSYS2 bash), docker (opcional) |
|
|
59
56
|
|
package/ci-local/ci-local.ps1
CHANGED
|
@@ -27,8 +27,8 @@ function Invoke-CiLocalMain {
|
|
|
27
27
|
[ref]$ExitCode
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
if ($Platform -
|
|
31
|
-
Write-Host '
|
|
30
|
+
if ($Platform -notin @('Linux', 'Windows')) {
|
|
31
|
+
Write-Host 'unsupported-platform: javi-forge supports Linux and Windows only.'
|
|
32
32
|
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
33
33
|
$ExitCode.Value = 1
|
|
34
34
|
return
|
|
@@ -36,15 +36,22 @@ function Invoke-CiLocalMain {
|
|
|
36
36
|
return 1
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
$bodyExitCode = 0
|
|
40
|
+
& ${function:Invoke-CiLocalStartupBody} -Platform $Platform -ExitCode ([ref]$bodyExitCode)
|
|
40
41
|
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
41
|
-
$ExitCode.Value =
|
|
42
|
+
$ExitCode.Value = $bodyExitCode
|
|
43
|
+
} elseif ($bodyExitCode -ne 0) {
|
|
44
|
+
return $bodyExitCode
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
function Invoke-CiLocalStartupBody {
|
|
46
|
-
param(
|
|
49
|
+
param(
|
|
50
|
+
[string]$Platform,
|
|
51
|
+
[ref]$ExitCode
|
|
52
|
+
)
|
|
47
53
|
|
|
54
|
+
$ExitCode.Value = 0
|
|
48
55
|
# See install.ps1 for the rationale behind 7.2 minimum (ResolveLinkTarget).
|
|
49
56
|
if ($PSVersionTable.PSEdition -ne 'Core' -or $PSVersionTable.PSVersion -lt [Version]'7.2') {
|
|
50
57
|
Write-Host 'ERROR: ci-local.ps1 requires PowerShell 7.2+ (pwsh).' -ForegroundColor Red
|
|
@@ -291,12 +298,15 @@ function Confirm-DockerImage {
|
|
|
291
298
|
$buildArgs += @('--build-arg', "JAVA_VERSION=$($Cfg.JavaVersion)")
|
|
292
299
|
}
|
|
293
300
|
& docker build @buildArgs -f $dockerfile -t $imageName (Join-Path $ScriptDir 'docker')
|
|
294
|
-
if ($LASTEXITCODE -ne 0) {
|
|
301
|
+
if ($LASTEXITCODE -ne 0) {
|
|
302
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
303
|
+
return
|
|
304
|
+
}
|
|
295
305
|
}
|
|
296
306
|
}
|
|
297
307
|
|
|
298
308
|
# Convert a host path to the form expected by docker run -v.
|
|
299
|
-
# Linux/
|
|
309
|
+
# Linux/WSL: /path/to/x -> /path/to/x (pass through)
|
|
300
310
|
# Windows: C:\Users\foo\proj -> /c/Users/foo/proj (Docker Desktop format)
|
|
301
311
|
# WSL inside Windows is the bash side and never reaches this code.
|
|
302
312
|
function ConvertTo-DockerHostPath {
|
|
@@ -348,7 +358,10 @@ function Invoke-InCi {
|
|
|
348
358
|
-v "${hostMount}:/home/runner/work" `
|
|
349
359
|
-e CI=true `
|
|
350
360
|
$imageName timeout $timeout bash -c $Cmd
|
|
351
|
-
if ($LASTEXITCODE -ne 0) {
|
|
361
|
+
if ($LASTEXITCODE -ne 0) {
|
|
362
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
363
|
+
return
|
|
364
|
+
}
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
# ─── Main ─────────────────────────────────────────────────────────────
|
|
@@ -385,21 +398,25 @@ switch ($Mode) {
|
|
|
385
398
|
|
|
386
399
|
'quick' {
|
|
387
400
|
Confirm-DockerImage -Cfg $cfg
|
|
401
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
388
402
|
Write-Host ''
|
|
389
403
|
Write-Host 'Running quick check...' -ForegroundColor Yellow
|
|
390
404
|
|
|
391
405
|
if ($cfg.LintCmd) {
|
|
392
406
|
Write-Host "Lint: $($cfg.LintCmd)" -ForegroundColor Cyan
|
|
393
407
|
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.LintCmd)"
|
|
408
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
394
409
|
}
|
|
395
410
|
if ($cfg.CompileCmd) {
|
|
396
411
|
Write-Host "Compile: $($cfg.CompileCmd)" -ForegroundColor Cyan
|
|
397
412
|
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.CompileCmd)" -RunUser 'root'
|
|
413
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
398
414
|
}
|
|
399
415
|
}
|
|
400
416
|
|
|
401
417
|
'shell' {
|
|
402
418
|
Confirm-DockerImage -Cfg $cfg
|
|
419
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
403
420
|
Write-Host ''
|
|
404
421
|
Write-Host 'Opening shell in CI environment...' -ForegroundColor Yellow
|
|
405
422
|
$imageName = Get-ImageName -StackType $cfg.StackType
|
|
@@ -408,11 +425,16 @@ switch ($Mode) {
|
|
|
408
425
|
-v "${hostMount}:/home/runner/work" `
|
|
409
426
|
-e CI=true `
|
|
410
427
|
$imageName 'cd /home/runner/work && bash'
|
|
428
|
+
if ($LASTEXITCODE -ne 0) {
|
|
429
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
430
|
+
return
|
|
431
|
+
}
|
|
411
432
|
}
|
|
412
433
|
|
|
413
434
|
Default {
|
|
414
435
|
# 'full'
|
|
415
436
|
Confirm-DockerImage -Cfg $cfg
|
|
437
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
416
438
|
Write-Host ''
|
|
417
439
|
Write-Host 'Running full CI simulation...' -ForegroundColor Yellow
|
|
418
440
|
|
|
@@ -429,6 +451,7 @@ switch ($Mode) {
|
|
|
429
451
|
Write-Host "Step $step/$total`: Lint" -ForegroundColor Yellow
|
|
430
452
|
Write-Host " $($cfg.LintCmd)" -ForegroundColor Cyan
|
|
431
453
|
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.LintCmd)"
|
|
454
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
432
455
|
$step++
|
|
433
456
|
}
|
|
434
457
|
|
|
@@ -437,6 +460,7 @@ switch ($Mode) {
|
|
|
437
460
|
Write-Host "Step $step/$total`: Compile" -ForegroundColor Yellow
|
|
438
461
|
Write-Host " $($cfg.CompileCmd)" -ForegroundColor Cyan
|
|
439
462
|
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.CompileCmd)" -RunUser 'root'
|
|
463
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
440
464
|
$step++
|
|
441
465
|
}
|
|
442
466
|
|
|
@@ -445,6 +469,7 @@ switch ($Mode) {
|
|
|
445
469
|
Write-Host "Step $step/$total`: Test" -ForegroundColor Yellow
|
|
446
470
|
Write-Host " $($cfg.TestCmd)" -ForegroundColor Cyan
|
|
447
471
|
Invoke-InCi -Cfg $cfg -Cmd "cd /home/runner/work && $($cfg.TestCmd)"
|
|
472
|
+
if ($ExitCode.Value -ne 0) { return }
|
|
448
473
|
$step++
|
|
449
474
|
}
|
|
450
475
|
|
|
@@ -455,7 +480,8 @@ switch ($Mode) {
|
|
|
455
480
|
& ghagga review --plain --exit-on-issues
|
|
456
481
|
if ($LASTEXITCODE -ne 0) {
|
|
457
482
|
Write-Host 'GHAGGA review found issues!' -ForegroundColor Red
|
|
458
|
-
|
|
483
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
484
|
+
return
|
|
459
485
|
}
|
|
460
486
|
}
|
|
461
487
|
}
|
|
@@ -468,7 +494,7 @@ Write-Host ''
|
|
|
468
494
|
}
|
|
469
495
|
|
|
470
496
|
if ($MyInvocation.InvocationName -ne '.') {
|
|
471
|
-
$platform = if ($
|
|
497
|
+
$platform = if ($IsWindows) { 'Windows' } elseif ($IsLinux) { 'Linux' } else { 'unsupported' }
|
|
472
498
|
$exitCode = 0
|
|
473
499
|
& ${function:Invoke-CiLocalMain} -Platform $platform -ExitCode ([ref]$exitCode)
|
|
474
500
|
if ($exitCode -ne 0) {
|
package/ci-local/ci-local.sh
CHANGED
|
@@ -11,23 +11,40 @@
|
|
|
11
11
|
# ./ci-local.sh detect # Mostrar stack detectado
|
|
12
12
|
# =============================================================================
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
CI_LOCAL_ENTRYPOINT_SOURCE="${BASH_SOURCE[0]}"
|
|
16
|
+
if [[ "$CI_LOCAL_ENTRYPOINT_SOURCE" == /* ]]; then
|
|
17
|
+
CI_LOCAL_ENTRYPOINT_PATH="$CI_LOCAL_ENTRYPOINT_SOURCE"
|
|
18
|
+
else
|
|
19
|
+
CI_LOCAL_ENTRYPOINT_PATH="$PWD/$CI_LOCAL_ENTRYPOINT_SOURCE"
|
|
20
|
+
fi
|
|
21
|
+
CI_LOCAL_ENTRYPOINT_DIR="${CI_LOCAL_ENTRYPOINT_PATH%/*}"
|
|
22
|
+
|
|
23
|
+
function ci_local_normalize_platform() {
|
|
15
24
|
local platform="${1:?platform required}"
|
|
16
|
-
shift
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
printf '%s\n' '
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
case "$platform" in
|
|
27
|
+
MINGW*_NT-*|MSYS*|CYGWIN*) printf '%s\n' 'Windows' ;;
|
|
28
|
+
*) printf '%s\n' "$platform" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ci_local_main() {
|
|
33
|
+
local platform
|
|
34
|
+
platform="$(ci_local_normalize_platform "${1:?platform required}")"
|
|
35
|
+
shift
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
case "$platform" in
|
|
38
|
+
Linux|Windows) ci_local_startup_body "$@" ;;
|
|
39
|
+
*) printf '%s\n' 'unsupported-platform: javi-forge supports Linux and Windows only.'; return 1 ;;
|
|
40
|
+
esac
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
function ci_local_startup_body() {
|
|
27
44
|
set -e
|
|
28
45
|
|
|
29
|
-
SCRIPT_DIR="$
|
|
30
|
-
PROJECT_DIR="$
|
|
46
|
+
SCRIPT_DIR="$CI_LOCAL_ENTRYPOINT_DIR"
|
|
47
|
+
PROJECT_DIR="${SCRIPT_DIR%/*}"
|
|
31
48
|
|
|
32
49
|
# Require realpath: load-bearing for the lib symlink check below.
|
|
33
50
|
if ! command -v realpath >/dev/null 2>&1; then
|
package/ci-local/install.ps1
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# .\install.ps1
|
|
8
8
|
#
|
|
9
9
|
# Behaviour MUST match install.sh. Diff against the bash version on every
|
|
10
|
-
# change. Test on
|
|
10
|
+
# change. Test on supported Windows and WSL hosts before tagging a release.
|
|
11
11
|
# =============================================================================
|
|
12
12
|
|
|
13
13
|
#Requires -Version 7.2
|
|
@@ -15,19 +15,36 @@
|
|
|
15
15
|
param()
|
|
16
16
|
|
|
17
17
|
function Invoke-CiLocalMain {
|
|
18
|
-
param(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
param(
|
|
19
|
+
[Parameter(Mandatory)][string]$Platform,
|
|
20
|
+
[ref]$ExitCode
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if ($Platform -notin @('Linux', 'Windows')) {
|
|
24
|
+
Write-Host 'unsupported-platform: javi-forge supports Linux and Windows only.'
|
|
25
|
+
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
26
|
+
$ExitCode.Value = 1
|
|
27
|
+
return
|
|
28
|
+
}
|
|
22
29
|
return 1
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
$bodyExitCode = 0
|
|
33
|
+
& ${function:Invoke-CiLocalStartupBody} -Platform $Platform -ExitCode ([ref]$bodyExitCode)
|
|
34
|
+
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
35
|
+
$ExitCode.Value = $bodyExitCode
|
|
36
|
+
} elseif ($bodyExitCode -ne 0) {
|
|
37
|
+
return $bodyExitCode
|
|
38
|
+
}
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
function Invoke-CiLocalStartupBody {
|
|
29
|
-
param(
|
|
42
|
+
param(
|
|
43
|
+
[string]$Platform,
|
|
44
|
+
[ref]$ExitCode
|
|
45
|
+
)
|
|
30
46
|
|
|
47
|
+
$ExitCode.Value = 0
|
|
31
48
|
# Defense-in-depth: #Requires is parsed by some hosts AFTER the script body.
|
|
32
49
|
# 7.2 is the minimum because FileSystemInfo.ResolveLinkTarget($true) is .NET 6+,
|
|
33
50
|
# which shipped with pwsh 7.2. Earlier pwsh 7.x runs on .NET 5 and the method
|
|
@@ -159,9 +176,11 @@ try {
|
|
|
159
176
|
|
|
160
177
|
$jfVersionOk = $true
|
|
161
178
|
$jfVersionOutput = ''
|
|
179
|
+
$jfVersionExitCode = 1
|
|
162
180
|
try {
|
|
163
181
|
$jfVersionOutput = (& javi-forge --version 2>&1) -join "`n"
|
|
164
|
-
|
|
182
|
+
$jfVersionExitCode = $LASTEXITCODE
|
|
183
|
+
if ($jfVersionExitCode -ne 0) { $jfVersionOk = $false }
|
|
165
184
|
} catch {
|
|
166
185
|
$jfVersionOk = $false
|
|
167
186
|
$jfVersionOutput = $_.Exception.Message
|
|
@@ -177,7 +196,8 @@ try {
|
|
|
177
196
|
Write-Host "ERROR: javi-forge found at $jfPath but '--version' failed" -ForegroundColor Red
|
|
178
197
|
Write-Host "Output: $jfVersionOutput" -ForegroundColor Yellow
|
|
179
198
|
Write-Host 'Reinstall: npm install -g javi-forge' -ForegroundColor Cyan
|
|
180
|
-
|
|
199
|
+
$ExitCode.Value = $jfVersionExitCode
|
|
200
|
+
return
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
# Reject UNC / remote paths outright — a javi-forge symlinked to
|
|
@@ -297,12 +317,13 @@ try {
|
|
|
297
317
|
& git config core.hooksPath $hooksRel
|
|
298
318
|
if ($LASTEXITCODE -ne 0) {
|
|
299
319
|
Write-Host 'ERROR: failed to set core.hooksPath. Are you inside a git repo?' -ForegroundColor Red
|
|
300
|
-
|
|
320
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
321
|
+
return
|
|
301
322
|
}
|
|
302
323
|
Write-Host "hooksPath = $hooksRel" -ForegroundColor Green
|
|
303
324
|
|
|
304
325
|
# Make hooks executable. Different OS, different model:
|
|
305
|
-
# -
|
|
326
|
+
# - Linux / WSL: chmod 0755 — POSIX bits, what git expects.
|
|
306
327
|
# - Windows native NTFS: chmod is a no-op; lock the ACL via icacls so
|
|
307
328
|
# only the current user can rewrite the hooks between commits.
|
|
308
329
|
if (Test-CommandExists -Name 'chmod') {
|
|
@@ -382,9 +403,10 @@ try {
|
|
|
382
403
|
}
|
|
383
404
|
|
|
384
405
|
if ($MyInvocation.InvocationName -ne '.') {
|
|
385
|
-
$platform = if ($
|
|
386
|
-
$exitCode =
|
|
387
|
-
|
|
406
|
+
$platform = if ($IsWindows) { 'Windows' } elseif ($IsLinux) { 'Linux' } else { 'unsupported' }
|
|
407
|
+
$exitCode = 0
|
|
408
|
+
& ${function:Invoke-CiLocalMain} -Platform $platform -ExitCode ([ref]$exitCode)
|
|
409
|
+
if ($exitCode -ne 0) {
|
|
388
410
|
exit $exitCode
|
|
389
411
|
}
|
|
390
412
|
}
|
package/ci-local/install.sh
CHANGED
|
@@ -3,22 +3,40 @@
|
|
|
3
3
|
# CI-LOCAL: Installation Script
|
|
4
4
|
# =============================================================================
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
CI_LOCAL_ENTRYPOINT_SOURCE="${BASH_SOURCE[0]}"
|
|
8
|
+
if [[ "$CI_LOCAL_ENTRYPOINT_SOURCE" == /* ]]; then
|
|
9
|
+
CI_LOCAL_ENTRYPOINT_PATH="$CI_LOCAL_ENTRYPOINT_SOURCE"
|
|
10
|
+
else
|
|
11
|
+
CI_LOCAL_ENTRYPOINT_PATH="$PWD/$CI_LOCAL_ENTRYPOINT_SOURCE"
|
|
12
|
+
fi
|
|
13
|
+
CI_LOCAL_ENTRYPOINT_DIR="${CI_LOCAL_ENTRYPOINT_PATH%/*}"
|
|
14
|
+
|
|
15
|
+
function ci_local_normalize_platform() {
|
|
7
16
|
local platform="${1:?platform required}"
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
printf '%s\n' '
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
case "$platform" in
|
|
19
|
+
MINGW*_NT-*|MSYS*|CYGWIN*) printf '%s\n' 'Windows' ;;
|
|
20
|
+
*) printf '%s\n' "$platform" ;;
|
|
21
|
+
esac
|
|
22
|
+
}
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
function ci_local_main() {
|
|
25
|
+
local platform
|
|
26
|
+
platform="$(ci_local_normalize_platform "${1:?platform required}")"
|
|
27
|
+
shift
|
|
28
|
+
|
|
29
|
+
case "$platform" in
|
|
30
|
+
Linux|Windows) ci_local_startup_body "$@" ;;
|
|
31
|
+
*) printf '%s\n' 'unsupported-platform: javi-forge supports Linux and Windows only.'; return 1 ;;
|
|
32
|
+
esac
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
function ci_local_startup_body() {
|
|
18
36
|
set -e
|
|
19
37
|
|
|
20
|
-
SCRIPT_DIR="$
|
|
21
|
-
PROJECT_DIR="$
|
|
38
|
+
SCRIPT_DIR="$CI_LOCAL_ENTRYPOINT_DIR"
|
|
39
|
+
PROJECT_DIR="${SCRIPT_DIR%/*}"
|
|
22
40
|
|
|
23
41
|
# ─── Required tools (fail-closed) ──────────────────────────────────
|
|
24
42
|
# realpath is load-bearing for the symlink-traversal checks below. If it's
|
|
@@ -27,7 +45,6 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
27
45
|
if ! command -v realpath >/dev/null 2>&1; then
|
|
28
46
|
printf 'ERROR: realpath is required for ci-local install (symlink checks).\n' >&2
|
|
29
47
|
printf ' Linux/WSL: install coreutils\n' >&2
|
|
30
|
-
printf ' macOS: brew install coreutils, then add gnubin to PATH\n' >&2
|
|
31
48
|
exit 1
|
|
32
49
|
fi
|
|
33
50
|
|
|
@@ -208,5 +225,5 @@ echo -e ""
|
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
211
|
-
ci_local_main "$(/usr/bin/uname -s)"
|
|
228
|
+
ci_local_main "$(/usr/bin/uname -s)" "$@"
|
|
212
229
|
fi
|
package/ci-local/lib/common.sh
CHANGED
|
@@ -39,16 +39,12 @@ log_info() { echo -e " ${CYAN}[INFO]${NC} $1"; }
|
|
|
39
39
|
log_step() { echo -e "${YELLOW}$1${NC}"; }
|
|
40
40
|
|
|
41
41
|
# =============================================================================
|
|
42
|
-
# sed_inplace -
|
|
42
|
+
# sed_inplace - GNU sed -i for supported Linux/WSL hosts
|
|
43
43
|
# =============================================================================
|
|
44
44
|
# Usage: sed_inplace "s/foo/bar/" file.txt
|
|
45
45
|
# =============================================================================
|
|
46
46
|
sed_inplace() {
|
|
47
|
-
|
|
48
|
-
sed -i "$@"
|
|
49
|
-
else
|
|
50
|
-
sed -i '' "$@"
|
|
51
|
-
fi
|
|
47
|
+
sed -i "$@"
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
# =============================================================================
|
|
@@ -100,7 +96,7 @@ detect_stack() {
|
|
|
100
96
|
STACK_TYPE="java-gradle"
|
|
101
97
|
BUILD_TOOL="gradle"
|
|
102
98
|
|
|
103
|
-
# Detect Java version from build files
|
|
99
|
+
# Detect Java version from build files
|
|
104
100
|
if [[ -f "$project_dir/build.gradle.kts" ]]; then
|
|
105
101
|
JAVA_VERSION=$(grep -E 'languageVersion\s*=\s*JavaLanguageVersion\.of\(' "$project_dir/build.gradle.kts" 2>/dev/null | grep -o '[0-9]\+' | head -1 || echo "21")
|
|
106
102
|
elif [[ -f "$project_dir/build.gradle" ]]; then
|
package/ci-local/uninstall.ps1
CHANGED
|
@@ -16,6 +16,42 @@ param(
|
|
|
16
16
|
[switch]$Purge
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
function Invoke-CiLocalMain {
|
|
21
|
+
param(
|
|
22
|
+
[Parameter(Mandatory)][string]$Platform,
|
|
23
|
+
[switch]$RestoreBackups,
|
|
24
|
+
[switch]$Purge,
|
|
25
|
+
[ref]$ExitCode
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if ($Platform -notin @('Linux', 'Windows')) {
|
|
29
|
+
Write-Host 'unsupported-platform: javi-forge supports Linux and Windows only.'
|
|
30
|
+
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
31
|
+
$ExitCode.Value = 1
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$bodyExitCode = 0
|
|
38
|
+
& ${function:Invoke-CiLocalStartupBody} -Platform $Platform -RestoreBackups:$RestoreBackups -Purge:$Purge -ExitCode ([ref]$bodyExitCode)
|
|
39
|
+
if ($PSBoundParameters.ContainsKey('ExitCode')) {
|
|
40
|
+
$ExitCode.Value = $bodyExitCode
|
|
41
|
+
} elseif ($bodyExitCode -ne 0) {
|
|
42
|
+
return $bodyExitCode
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Invoke-CiLocalStartupBody {
|
|
47
|
+
param(
|
|
48
|
+
[Parameter(Mandatory)][string]$Platform,
|
|
49
|
+
[switch]$RestoreBackups,
|
|
50
|
+
[switch]$Purge,
|
|
51
|
+
[ref]$ExitCode
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
$ExitCode.Value = 0
|
|
19
55
|
Set-StrictMode -Version Latest
|
|
20
56
|
$ErrorActionPreference = 'Stop'
|
|
21
57
|
|
|
@@ -32,6 +68,10 @@ try {
|
|
|
32
68
|
$current = (& git config --get core.hooksPath 2>$null) -join ''
|
|
33
69
|
if ($current) {
|
|
34
70
|
& git config --unset core.hooksPath
|
|
71
|
+
if ($LASTEXITCODE -ne 0) {
|
|
72
|
+
$ExitCode.Value = $LASTEXITCODE
|
|
73
|
+
return
|
|
74
|
+
}
|
|
35
75
|
Write-Host "Removed hooksPath = $current" -ForegroundColor Green
|
|
36
76
|
} else {
|
|
37
77
|
Write-Host 'No core.hooksPath was set; nothing to remove' -ForegroundColor Yellow
|
|
@@ -83,3 +123,13 @@ try {
|
|
|
83
123
|
} finally {
|
|
84
124
|
Pop-Location
|
|
85
125
|
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if ($MyInvocation.InvocationName -ne '.') {
|
|
129
|
+
$platform = if ($IsWindows) { 'Windows' } elseif ($IsLinux) { 'Linux' } else { 'unsupported' }
|
|
130
|
+
$exitCode = 0
|
|
131
|
+
& ${function:Invoke-CiLocalMain} -Platform $platform -RestoreBackups:$RestoreBackups -Purge:$Purge -ExitCode ([ref]$exitCode)
|
|
132
|
+
if ($exitCode -ne 0) {
|
|
133
|
+
exit $exitCode
|
|
134
|
+
}
|
|
135
|
+
}
|