@eventmodelers/cli 1.0.45 → 1.0.46
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 +4 -1
- package/cli.js +53 -1
- package/package.json +2 -2
- package/shared/build-kit/lib/checks/README.md +59 -0
- package/shared/build-kit/lib/ralph.js +110 -28
- package/shared/build-kit/lib/util/find-slice.cjs +59 -0
- package/shared/build-kit/ralph-claude.js +6 -2
- package/stacks/blank/templates/build-kit/lib/backend-prompt.md +106 -102
- package/stacks/blank/templates/build-kit/lib/prompt.md +102 -106
- package/stacks/kurrent/templates/.claude/skills/build-automation/SKILL.md +422 -0
- package/stacks/kurrent/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
- package/stacks/kurrent/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +65 -0
- package/stacks/kurrent/templates/.claude/skills/build-state-change/SKILL.md +418 -0
- package/stacks/kurrent/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +41 -0
- package/stacks/kurrent/templates/.claude/skills/build-state-change/references/integration-test-patterns.md +64 -0
- package/stacks/kurrent/templates/.claude/skills/build-state-view/SKILL.md +391 -0
- package/stacks/kurrent/templates/build-kit/CLAUDE.md +122 -0
- package/stacks/kurrent/templates/build-kit/lib/AGENT.md +73 -0
- package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +169 -0
- package/stacks/kurrent/templates/build-kit/lib/prompt.md +128 -0
- package/stacks/kurrent/templates/root/README.md +46 -0
- package/stacks/kurrent/templates/root/docker-compose.yml +45 -0
- package/stacks/kurrent/templates/root/mvnw +259 -0
- package/stacks/kurrent/templates/root/mvnw.cmd +149 -0
- package/stacks/kurrent/templates/root/pom.xml +152 -0
- package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
- package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/common/EventStore.java +91 -0
- package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/config/KurrentConfiguration.java +42 -0
- package/stacks/kurrent/templates/root/src/main/resources/application.properties +14 -0
- package/stacks/kurrent/templates/root/src/main/resources/static/index.html +11 -0
- package/stacks/node/templates/build-kit/CLAUDE.md +22 -0
- package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +123 -0
- package/stacks/node/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
- package/stacks/node/templates/build-kit/lib/checks/10-slice-scope.cjs +29 -0
- package/stacks/node/templates/build-kit/lib/checks/20-append-only-migrations.cjs +20 -0
- package/stacks/node/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
- package/stacks/node/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
- package/stacks/node/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
- package/stacks/node/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
- package/stacks/node/templates/root/.githooks/pre-commit +11 -0
- package/stacks/node/templates/root/package.json +2 -1
- package/stacks/node/templates/root/setup-env.sh +7 -1
- package/stacks/opencqrs/templates/.claude/skills/build-automation/SKILL.md +434 -0
- package/stacks/opencqrs/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
- package/stacks/opencqrs/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +62 -0
- package/stacks/opencqrs/templates/.claude/skills/build-state-change/SKILL.md +413 -0
- package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +46 -0
- package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +145 -0
- package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/test-fixture-patterns.md +85 -0
- package/stacks/opencqrs/templates/.claude/skills/build-state-view/SKILL.md +354 -0
- package/stacks/opencqrs/templates/build-kit/CLAUDE.md +101 -0
- package/stacks/opencqrs/templates/build-kit/lib/AGENT.md +58 -0
- package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +169 -0
- package/stacks/opencqrs/templates/build-kit/lib/prompt.md +128 -0
- package/stacks/opencqrs/templates/root/README.md +42 -0
- package/stacks/opencqrs/templates/root/docker-compose.yml +37 -0
- package/stacks/opencqrs/templates/root/mvnw +259 -0
- package/stacks/opencqrs/templates/root/mvnw.cmd +149 -0
- package/stacks/opencqrs/templates/root/pom.xml +139 -0
- package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
- package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/config/CqrsConfiguration.java +74 -0
- package/stacks/opencqrs/templates/root/src/main/resources/application.properties +23 -0
- package/stacks/opencqrs/templates/root/src/main/resources/schema.sql +19 -0
- package/stacks/opencqrs/templates/root/src/main/resources/static/index.html +11 -0
- package/stacks/supabase/templates/build-kit/CLAUDE.md +25 -0
- package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +126 -0
- package/stacks/supabase/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
- package/stacks/supabase/templates/build-kit/lib/checks/10-slice-scope.cjs +34 -0
- package/stacks/supabase/templates/build-kit/lib/checks/20-append-only-migrations.cjs +21 -0
- package/stacks/supabase/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
- package/stacks/supabase/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
- package/stacks/supabase/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
- package/stacks/supabase/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
- package/stacks/supabase/templates/root/.githooks/pre-commit +11 -0
- package/stacks/supabase/templates/root/package.json +2 -1
- package/stacks/supabase/templates/root/setup-env.sh +7 -1
- package/stacks/umadb/templates/.claude/skills/build-automation/SKILL.md +313 -0
- package/stacks/umadb/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +42 -0
- package/stacks/umadb/templates/.claude/skills/build-state-change/SKILL.md +376 -0
- package/stacks/umadb/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +42 -0
- package/stacks/umadb/templates/.claude/skills/build-state-change/references/umadb-query-patterns.md +78 -0
- package/stacks/umadb/templates/.claude/skills/build-state-view/SKILL.md +338 -0
- package/stacks/umadb/templates/build-kit/CLAUDE.md +94 -0
- package/stacks/umadb/templates/build-kit/lib/AGENT.md +47 -0
- package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +169 -0
- package/stacks/umadb/templates/build-kit/lib/prompt.md +128 -0
- package/stacks/umadb/templates/root/.mvn/wrapper/maven-wrapper.properties +19 -0
- package/stacks/umadb/templates/root/README.md +48 -0
- package/stacks/umadb/templates/root/docker-compose.yml +29 -0
- package/stacks/umadb/templates/root/mvnw +259 -0
- package/stacks/umadb/templates/root/mvnw.cmd +149 -0
- package/stacks/umadb/templates/root/pom.xml +151 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/QuickstartApplication.java +12 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/config/UmaDbConfig.java +39 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/DecisionModelLoader.java +76 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventCodec.java +40 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventDispatcher.java +100 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/OptimisticConcurrencyException.java +14 -0
- package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/SliceEventListener.java +23 -0
- package/stacks/umadb/templates/root/src/main/resources/application.properties +17 -0
- package/stacks/umadb/templates/root/src/test/java/io/umadb/quickstart/testsupport/InMemoryUmaDbClient.java +135 -0
- package/stacks/umadb/templates/root/src/test/resources/application.properties +8 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<# : batch portion
|
|
2
|
+
@REM ----------------------------------------------------------------------------
|
|
3
|
+
@REM Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
@REM or more contributor license agreements. See the NOTICE file
|
|
5
|
+
@REM distributed with this work for additional information
|
|
6
|
+
@REM regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
@REM to you under the Apache License, Version 2.0 (the
|
|
8
|
+
@REM "License"); you may not use this file except in compliance
|
|
9
|
+
@REM with the License. You may obtain a copy of the License at
|
|
10
|
+
@REM
|
|
11
|
+
@REM http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
@REM
|
|
13
|
+
@REM Unless required by applicable law or agreed to in writing,
|
|
14
|
+
@REM software distributed under the License is distributed on an
|
|
15
|
+
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
@REM KIND, either express or implied. See the License for the
|
|
17
|
+
@REM specific language governing permissions and limitations
|
|
18
|
+
@REM under the License.
|
|
19
|
+
@REM ----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
@REM ----------------------------------------------------------------------------
|
|
22
|
+
@REM Apache Maven Wrapper startup batch script, version 3.3.2
|
|
23
|
+
@REM
|
|
24
|
+
@REM Optional ENV vars
|
|
25
|
+
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
|
26
|
+
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
|
27
|
+
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
|
28
|
+
@REM ----------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
|
31
|
+
@SET __MVNW_CMD__=
|
|
32
|
+
@SET __MVNW_ERROR__=
|
|
33
|
+
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
|
34
|
+
@SET PSModulePath=
|
|
35
|
+
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
|
36
|
+
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
|
37
|
+
)
|
|
38
|
+
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
|
39
|
+
@SET __MVNW_PSMODULEP_SAVE=
|
|
40
|
+
@SET __MVNW_ARG0_NAME__=
|
|
41
|
+
@SET MVNW_USERNAME=
|
|
42
|
+
@SET MVNW_PASSWORD=
|
|
43
|
+
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
|
|
44
|
+
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
|
45
|
+
@GOTO :EOF
|
|
46
|
+
: end batch / begin powershell #>
|
|
47
|
+
|
|
48
|
+
$ErrorActionPreference = "Stop"
|
|
49
|
+
if ($env:MVNW_VERBOSE -eq "true") {
|
|
50
|
+
$VerbosePreference = "Continue"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
|
54
|
+
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
|
55
|
+
if (!$distributionUrl) {
|
|
56
|
+
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
|
60
|
+
"maven-mvnd-*" {
|
|
61
|
+
$USE_MVND = $true
|
|
62
|
+
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
|
63
|
+
$MVN_CMD = "mvnd.cmd"
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
default {
|
|
67
|
+
$USE_MVND = $false
|
|
68
|
+
$MVN_CMD = $script -replace '^mvnw','mvn'
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
|
74
|
+
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
|
75
|
+
if ($env:MVNW_REPOURL) {
|
|
76
|
+
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
|
77
|
+
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
|
|
78
|
+
}
|
|
79
|
+
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
|
80
|
+
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
|
81
|
+
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
|
82
|
+
if ($env:MAVEN_USER_HOME) {
|
|
83
|
+
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
|
84
|
+
}
|
|
85
|
+
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
|
86
|
+
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
|
87
|
+
|
|
88
|
+
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
|
89
|
+
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
|
90
|
+
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
|
91
|
+
exit $?
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
|
95
|
+
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# prepare tmp dir
|
|
99
|
+
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
|
100
|
+
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
|
101
|
+
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
|
102
|
+
trap {
|
|
103
|
+
if ($TMP_DOWNLOAD_DIR.Exists) {
|
|
104
|
+
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
|
105
|
+
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
|
110
|
+
|
|
111
|
+
# Download and Install Apache Maven
|
|
112
|
+
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
|
113
|
+
Write-Verbose "Downloading from: $distributionUrl"
|
|
114
|
+
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
|
115
|
+
|
|
116
|
+
$webclient = New-Object System.Net.WebClient
|
|
117
|
+
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
|
118
|
+
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
|
119
|
+
}
|
|
120
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
121
|
+
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
|
122
|
+
|
|
123
|
+
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
|
124
|
+
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
|
125
|
+
if ($distributionSha256Sum) {
|
|
126
|
+
if ($USE_MVND) {
|
|
127
|
+
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
|
128
|
+
}
|
|
129
|
+
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
|
130
|
+
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
|
131
|
+
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# unzip and move
|
|
136
|
+
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
|
137
|
+
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
|
|
138
|
+
try {
|
|
139
|
+
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
|
140
|
+
} catch {
|
|
141
|
+
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
|
142
|
+
Write-Error "fail to move MAVEN_HOME"
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
|
146
|
+
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
3
|
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
4
|
+
<modelVersion>4.0.0</modelVersion>
|
|
5
|
+
<groupId>com.example.quickstart</groupId>
|
|
6
|
+
<artifactId>quickstart</artifactId>
|
|
7
|
+
<name>Kurrent Quickstart</name>
|
|
8
|
+
<version>0.0.1-SNAPSHOT</version>
|
|
9
|
+
|
|
10
|
+
<properties>
|
|
11
|
+
<java.version>21</java.version>
|
|
12
|
+
<kurrentdb.version>1.2.1</kurrentdb.version>
|
|
13
|
+
<spring-boot.version>4.1.0</spring-boot.version>
|
|
14
|
+
<!-- Must track whatever version kurrentdb-client itself pulls in transitively (grpc-api is
|
|
15
|
+
runtime-scope only there) — check `mvn dependency:tree | grep grpc` after bumping
|
|
16
|
+
kurrentdb.version, and bump this to match. -->
|
|
17
|
+
<grpc.version>1.80.0</grpc.version>
|
|
18
|
+
</properties>
|
|
19
|
+
|
|
20
|
+
<dependencies>
|
|
21
|
+
<dependency>
|
|
22
|
+
<groupId>org.springframework.boot</groupId>
|
|
23
|
+
<artifactId>spring-boot-starter-webmvc</artifactId>
|
|
24
|
+
</dependency>
|
|
25
|
+
|
|
26
|
+
<!-- Classic Jackson 2 (com.fasterxml.jackson.core), for hand-serializing event payloads
|
|
27
|
+
(LibraryEventTypes and friends) with the same ObjectMapper the kurrentdb-client's own
|
|
28
|
+
samples use. Spring Boot 4 moved its own internal JSON handling to Jackson 3
|
|
29
|
+
(tools.jackson.core) and only pulls classic Jackson 2 in at *runtime* scope, transitively
|
|
30
|
+
via kurrentdb-client itself — not on the compile classpath by default. Declared explicitly,
|
|
31
|
+
version still resolved from Spring Boot's dependency-management BOM. -->
|
|
32
|
+
<dependency>
|
|
33
|
+
<groupId>com.fasterxml.jackson.core</groupId>
|
|
34
|
+
<artifactId>jackson-databind</artifactId>
|
|
35
|
+
</dependency>
|
|
36
|
+
|
|
37
|
+
<!-- Read model persistence: Spring Data JPA + PostgreSQL, default for all projections -->
|
|
38
|
+
<dependency>
|
|
39
|
+
<groupId>org.springframework.boot</groupId>
|
|
40
|
+
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
|
41
|
+
</dependency>
|
|
42
|
+
<dependency>
|
|
43
|
+
<groupId>org.postgresql</groupId>
|
|
44
|
+
<artifactId>postgresql</artifactId>
|
|
45
|
+
<scope>runtime</scope>
|
|
46
|
+
</dependency>
|
|
47
|
+
|
|
48
|
+
<dependency>
|
|
49
|
+
<groupId>org.springframework.boot</groupId>
|
|
50
|
+
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
51
|
+
</dependency>
|
|
52
|
+
|
|
53
|
+
<!-- KurrentDB Java client (plain SDK — no Spring Boot auto-configuration of its own,
|
|
54
|
+
see config.KurrentConfiguration for the beans this project wires by hand) -->
|
|
55
|
+
<dependency>
|
|
56
|
+
<groupId>io.kurrent</groupId>
|
|
57
|
+
<artifactId>kurrentdb-client</artifactId>
|
|
58
|
+
<version>${kurrentdb.version}</version>
|
|
59
|
+
</dependency>
|
|
60
|
+
|
|
61
|
+
<!-- io.grpc.Status/StatusRuntimeException — needed at compile scope to detect an
|
|
62
|
+
ALREADY_EXISTS conflict when idempotently creating a persistent-subscription group
|
|
63
|
+
(see build-state-view/build-automation). Only pulled in transitively by
|
|
64
|
+
kurrentdb-client at *runtime* scope otherwise. -->
|
|
65
|
+
<dependency>
|
|
66
|
+
<groupId>io.grpc</groupId>
|
|
67
|
+
<artifactId>grpc-api</artifactId>
|
|
68
|
+
<version>${grpc.version}</version>
|
|
69
|
+
</dependency>
|
|
70
|
+
|
|
71
|
+
<!-- Auto-starts docker-compose.yml services (KurrentDB, Postgres) on app run -->
|
|
72
|
+
<dependency>
|
|
73
|
+
<groupId>org.springframework.boot</groupId>
|
|
74
|
+
<artifactId>spring-boot-docker-compose</artifactId>
|
|
75
|
+
<scope>runtime</scope>
|
|
76
|
+
<optional>true</optional>
|
|
77
|
+
</dependency>
|
|
78
|
+
|
|
79
|
+
<!-- Testing -->
|
|
80
|
+
<dependency>
|
|
81
|
+
<groupId>org.springframework.boot</groupId>
|
|
82
|
+
<artifactId>spring-boot-starter-test</artifactId>
|
|
83
|
+
<scope>test</scope>
|
|
84
|
+
</dependency>
|
|
85
|
+
<!-- Spring Boot 4 test slices: @DataJpaTest and @WebMvcTest each moved to their own artifact -->
|
|
86
|
+
<dependency>
|
|
87
|
+
<groupId>org.springframework.boot</groupId>
|
|
88
|
+
<artifactId>spring-boot-data-jpa-test</artifactId>
|
|
89
|
+
<scope>test</scope>
|
|
90
|
+
</dependency>
|
|
91
|
+
<dependency>
|
|
92
|
+
<groupId>org.springframework.boot</groupId>
|
|
93
|
+
<artifactId>spring-boot-webmvc-test</artifactId>
|
|
94
|
+
<scope>test</scope>
|
|
95
|
+
</dependency>
|
|
96
|
+
<dependency>
|
|
97
|
+
<groupId>org.junit.jupiter</groupId>
|
|
98
|
+
<artifactId>junit-jupiter</artifactId>
|
|
99
|
+
<scope>test</scope>
|
|
100
|
+
</dependency>
|
|
101
|
+
<dependency>
|
|
102
|
+
<groupId>org.springframework.boot</groupId>
|
|
103
|
+
<artifactId>spring-boot-testcontainers</artifactId>
|
|
104
|
+
<scope>test</scope>
|
|
105
|
+
</dependency>
|
|
106
|
+
<dependency>
|
|
107
|
+
<groupId>org.testcontainers</groupId>
|
|
108
|
+
<artifactId>testcontainers</artifactId>
|
|
109
|
+
<scope>test</scope>
|
|
110
|
+
</dependency>
|
|
111
|
+
<dependency>
|
|
112
|
+
<groupId>org.testcontainers</groupId>
|
|
113
|
+
<artifactId>testcontainers-postgresql</artifactId>
|
|
114
|
+
<scope>test</scope>
|
|
115
|
+
</dependency>
|
|
116
|
+
<dependency>
|
|
117
|
+
<groupId>org.testcontainers</groupId>
|
|
118
|
+
<artifactId>testcontainers-junit-jupiter</artifactId>
|
|
119
|
+
<scope>test</scope>
|
|
120
|
+
</dependency>
|
|
121
|
+
</dependencies>
|
|
122
|
+
|
|
123
|
+
<build>
|
|
124
|
+
<plugins>
|
|
125
|
+
<plugin>
|
|
126
|
+
<groupId>org.apache.maven.plugins</groupId>
|
|
127
|
+
<artifactId>maven-compiler-plugin</artifactId>
|
|
128
|
+
<configuration>
|
|
129
|
+
<source>${java.version}</source>
|
|
130
|
+
<target>${java.version}</target>
|
|
131
|
+
<parameters>true</parameters>
|
|
132
|
+
</configuration>
|
|
133
|
+
</plugin>
|
|
134
|
+
<plugin>
|
|
135
|
+
<groupId>org.springframework.boot</groupId>
|
|
136
|
+
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
137
|
+
</plugin>
|
|
138
|
+
</plugins>
|
|
139
|
+
</build>
|
|
140
|
+
|
|
141
|
+
<dependencyManagement>
|
|
142
|
+
<dependencies>
|
|
143
|
+
<dependency>
|
|
144
|
+
<groupId>org.springframework.boot</groupId>
|
|
145
|
+
<artifactId>spring-boot-dependencies</artifactId>
|
|
146
|
+
<version>${spring-boot.version}</version>
|
|
147
|
+
<type>pom</type>
|
|
148
|
+
<scope>import</scope>
|
|
149
|
+
</dependency>
|
|
150
|
+
</dependencies>
|
|
151
|
+
</dependencyManagement>
|
|
152
|
+
</project>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package com.example.quickstart;
|
|
2
|
+
|
|
3
|
+
import org.springframework.boot.SpringApplication;
|
|
4
|
+
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
5
|
+
|
|
6
|
+
@SpringBootApplication
|
|
7
|
+
public class QuickstartApplication {
|
|
8
|
+
|
|
9
|
+
public static void main(String[] args) {
|
|
10
|
+
SpringApplication.run(QuickstartApplication.class, args);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/common/EventStore.java
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package com.example.quickstart.common;
|
|
2
|
+
|
|
3
|
+
import io.kurrent.dbclient.*;
|
|
4
|
+
import org.springframework.stereotype.Component;
|
|
5
|
+
|
|
6
|
+
import java.util.List;
|
|
7
|
+
import java.util.concurrent.ExecutionException;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Thin wrapper around {@link KurrentDBClient} shared by every write slice — reads a stream into a
|
|
11
|
+
* plain list (or empty, if the stream doesn't exist yet) and appends events, translating the client's
|
|
12
|
+
* checked/wrapped exceptions into unchecked ones so slice code doesn't repeat this boilerplate.
|
|
13
|
+
*
|
|
14
|
+
* <p>This project has no CQRS framework layer (unlike the OpenCQRS/Axon kits) — this class, plus each
|
|
15
|
+
* slice's own {@code decide}/{@code evolve} functions, is the entire "framework". See
|
|
16
|
+
* {@code .build-kit/CLAUDE.md} for the full convention.
|
|
17
|
+
*/
|
|
18
|
+
@Component
|
|
19
|
+
public class EventStore {
|
|
20
|
+
|
|
21
|
+
private final KurrentDBClient client;
|
|
22
|
+
|
|
23
|
+
public EventStore(KurrentDBClient client) {
|
|
24
|
+
this.client = client;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reads every event in {@code streamId}, forwards from the start. Returns
|
|
29
|
+
* {@link StreamEvents#empty()} if the stream doesn't exist yet — this is the normal, expected case
|
|
30
|
+
* for a creation command, not an error.
|
|
31
|
+
*/
|
|
32
|
+
public StreamEvents read(String streamId) {
|
|
33
|
+
try {
|
|
34
|
+
ReadResult result = client.readStream(streamId, ReadStreamOptions.get().forwards().fromStart()).get();
|
|
35
|
+
return new StreamEvents(result.getEvents(), result.getLastStreamPosition());
|
|
36
|
+
} catch (ExecutionException e) {
|
|
37
|
+
if (e.getCause() instanceof StreamNotFoundException) {
|
|
38
|
+
return StreamEvents.empty();
|
|
39
|
+
}
|
|
40
|
+
throw new EventStoreException(e.getCause());
|
|
41
|
+
} catch (InterruptedException e) {
|
|
42
|
+
Thread.currentThread().interrupt();
|
|
43
|
+
throw new EventStoreException(e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Appends {@code events} to {@code streamId} under the given {@code expectedState} (see
|
|
49
|
+
* {@link StreamState#noStream()}/{@link StreamState#streamRevision(long)}/etc). Throws
|
|
50
|
+
* {@link WrongExpectedVersionException} directly (unwrapped) on an optimistic-concurrency
|
|
51
|
+
* conflict — callers decide for themselves whether that's a real conflict (map it to HTTP 409) or
|
|
52
|
+
* an expected redelivery to swallow (see build-automation's SKILL.md).
|
|
53
|
+
*/
|
|
54
|
+
public WriteResult append(String streamId, StreamState expectedState, List<EventData> events) {
|
|
55
|
+
try {
|
|
56
|
+
AppendToStreamOptions options = AppendToStreamOptions.get().streamState(expectedState);
|
|
57
|
+
return client.appendToStream(streamId, options, events.toArray(new EventData[0])).get();
|
|
58
|
+
} catch (ExecutionException e) {
|
|
59
|
+
if (e.getCause() instanceof WrongExpectedVersionException wrongExpectedVersion) {
|
|
60
|
+
throw wrongExpectedVersion;
|
|
61
|
+
}
|
|
62
|
+
throw new EventStoreException(e.getCause());
|
|
63
|
+
} catch (InterruptedException e) {
|
|
64
|
+
Thread.currentThread().interrupt();
|
|
65
|
+
throw new EventStoreException(e);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public record StreamEvents(List<ResolvedEvent> events, long lastStreamPosition) {
|
|
70
|
+
private static final long NO_STREAM_POSITION = -1;
|
|
71
|
+
|
|
72
|
+
public static StreamEvents empty() {
|
|
73
|
+
return new StreamEvents(List.of(), NO_STREAM_POSITION);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public boolean isEmpty() {
|
|
77
|
+
return events.isEmpty();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The {@link StreamState} to append under, given this stream's current position. */
|
|
81
|
+
public StreamState expectedState() {
|
|
82
|
+
return isEmpty() ? StreamState.noStream() : StreamState.streamRevision(lastStreamPosition);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public static class EventStoreException extends RuntimeException {
|
|
87
|
+
public EventStoreException(Throwable cause) {
|
|
88
|
+
super(cause);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package com.example.quickstart.config;
|
|
2
|
+
|
|
3
|
+
import io.kurrent.dbclient.ConnectionStringParsingException;
|
|
4
|
+
import io.kurrent.dbclient.KurrentDBClient;
|
|
5
|
+
import io.kurrent.dbclient.KurrentDBClientSettings;
|
|
6
|
+
import io.kurrent.dbclient.KurrentDBConnectionString;
|
|
7
|
+
import io.kurrent.dbclient.KurrentDBPersistentSubscriptionsClient;
|
|
8
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
9
|
+
import org.springframework.context.annotation.Bean;
|
|
10
|
+
import org.springframework.context.annotation.Configuration;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The KurrentDB Java client ({@code kurrentdb-client}) ships no Spring Boot auto-configuration of its
|
|
14
|
+
* own (unlike OpenCQRS's {@code framework-spring-boot-starter}) — it is a plain SDK. These beans are
|
|
15
|
+
* this project's own wiring, not a vendor-provided starter.
|
|
16
|
+
*/
|
|
17
|
+
@Configuration
|
|
18
|
+
public class KurrentConfiguration {
|
|
19
|
+
|
|
20
|
+
@Value("${kurrentdb.connection-string}")
|
|
21
|
+
private String connectionString;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A single client instance is safe to share as a singleton across the whole application — it does
|
|
25
|
+
* not need to be opened/closed per request (see the client's own getting-started guide).
|
|
26
|
+
*/
|
|
27
|
+
@Bean(destroyMethod = "shutdown")
|
|
28
|
+
public KurrentDBClient kurrentDBClient() throws ConnectionStringParsingException {
|
|
29
|
+
KurrentDBClientSettings settings = KurrentDBConnectionString.parseOrThrow(connectionString);
|
|
30
|
+
return KurrentDBClient.create(settings);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Separate client for managing/consuming persistent subscriptions — used by build-state-view and
|
|
35
|
+
* build-automation slices, not by build-state-change (which only appends/reads streams directly).
|
|
36
|
+
*/
|
|
37
|
+
@Bean(destroyMethod = "shutdown")
|
|
38
|
+
public KurrentDBPersistentSubscriptionsClient kurrentDBPersistentSubscriptionsClient() throws ConnectionStringParsingException {
|
|
39
|
+
KurrentDBClientSettings settings = KurrentDBConnectionString.parseOrThrow(connectionString);
|
|
40
|
+
return KurrentDBPersistentSubscriptionsClient.create(settings);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
spring.application.name=quickstart
|
|
2
|
+
server.port=8080
|
|
3
|
+
|
|
4
|
+
kurrentdb.connection-string=${KURRENTDB_CONNECTION_STRING:kurrentdb://localhost:2113?tls=false}
|
|
5
|
+
|
|
6
|
+
spring.datasource.url=jdbc:postgresql://localhost:5432/quickstart
|
|
7
|
+
spring.datasource.username=quickstart
|
|
8
|
+
spring.datasource.password=quickstart
|
|
9
|
+
spring.jpa.hibernate.ddl-auto=update
|
|
10
|
+
|
|
11
|
+
spring.docker.compose.enabled=true
|
|
12
|
+
spring.docker.compose.file=docker-compose.yml
|
|
13
|
+
|
|
14
|
+
management.endpoint.health.show-components=always
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Kurrent Quickstart</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Kurrent Quickstart</h1>
|
|
9
|
+
<p>Backend is running. Slices built from the event model board are wired up under their own REST paths.</p>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -57,6 +57,28 @@ When asked to build a slice, always follow this flow:
|
|
|
57
57
|
|
|
58
58
|
After you are done, automatically run the tests for the slice that was edited.
|
|
59
59
|
|
|
60
|
+
## Commit Scope Guard
|
|
61
|
+
|
|
62
|
+
A pre-commit hook (`.githooks/pre-commit`, installed via `init --hooks` — see its own project's setup)
|
|
63
|
+
runs `.build-kit/lib/check-commit-scope.cjs` on every commit that touches `src/slices/{context}/{slice}/`.
|
|
64
|
+
It loads every check under `.build-kit/lib/checks/` and rejects the commit if any of them find a problem:
|
|
65
|
+
|
|
66
|
+
- **blocked-paths** — `package.json`/lockfiles and `server.ts` are never touched by slice work
|
|
67
|
+
- **slice-scope** — everything staged must be inside the slice folder or a documented exception:
|
|
68
|
+
`src/slices/{context}/{Context}Events.ts` or `src/common/loadPostgresEventstore.ts`
|
|
69
|
+
- **append-only-migrations** — `migrations/V{n}__*.sql` may only be **added**, never edited
|
|
70
|
+
- **test-file-present** — a changed Command/Projection/processor file needs a sibling `*.test.ts`
|
|
71
|
+
- **no-invented-fields** — heuristic: flags a field used in code that isn't declared anywhere in
|
|
72
|
+
`.build-kit/.slices/{context}/{slice}/slice.json`
|
|
73
|
+
- **spec-coverage** — heuristic: the test file needs at least as many `it(...)` blocks as slice.json
|
|
74
|
+
has `specifications[]` entries
|
|
75
|
+
- **tsc-build** — `npx tsc --noEmit` must still pass
|
|
76
|
+
|
|
77
|
+
If a commit is rejected, split it — commit the out-of-scope file separately from the slice work, or add
|
|
78
|
+
the missing test/fix the field — rather than passing `--no-verify`. Run `npm run check:scope` any time
|
|
79
|
+
you want to check staged files before committing. To add a new check, read
|
|
80
|
+
`.build-kit/lib/checks/README.md` and drop in a file following its interface — no other wiring needed.
|
|
81
|
+
|
|
60
82
|
## Example Slice Structure
|
|
61
83
|
|
|
62
84
|
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Runner for the slice commit-scope guard. Loads every check module from
|
|
5
|
+
// ./checks/*.cjs and runs it against the currently staged changeset.
|
|
6
|
+
//
|
|
7
|
+
// Check interface (see ./checks/README.md for the full contract + a template):
|
|
8
|
+
// module.exports = {
|
|
9
|
+
// name: 'my-check', // short id, shown in violation output
|
|
10
|
+
// skipIfAlreadyFailing: false, // optional — skip this check once an earlier
|
|
11
|
+
// // one has already failed (use for slow checks)
|
|
12
|
+
// run(ctx) {
|
|
13
|
+
// return [{ path: 'some/file.ts', reason: 'why this is a problem' }];
|
|
14
|
+
// },
|
|
15
|
+
// };
|
|
16
|
+
// `run()` returns an array of violations (empty array/undefined/null = pass).
|
|
17
|
+
//
|
|
18
|
+
// `ctx` passed to every check:
|
|
19
|
+
// changes [{status, path}] — staged files (git status letter + path)
|
|
20
|
+
// touchesSlice true — this commit touches src/slices/{context}/{slice}/**
|
|
21
|
+
// (the runner already gates on this before loading checks)
|
|
22
|
+
// repoRoot absolute path to the repo root
|
|
23
|
+
// SLICE_PATTERN RegExp matching a path inside a slice's own folder
|
|
24
|
+
//
|
|
25
|
+
// Zero dependencies — plain Node, so it works from git's pre-commit hook
|
|
26
|
+
// (see ../../.githooks/pre-commit), from `npm run check:scope`, or from CI.
|
|
27
|
+
// Invoked as: node .build-kit/lib/check-commit-scope.cjs
|
|
28
|
+
|
|
29
|
+
const { execSync } = require('child_process');
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const SLICE_PATTERN = /^src\/slices\/[^/]+\/[^/]+\//;
|
|
34
|
+
|
|
35
|
+
function stagedChanges() {
|
|
36
|
+
const out = execSync('git diff --cached --name-status --no-renames', { encoding: 'utf8' });
|
|
37
|
+
return out
|
|
38
|
+
.split('\n')
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((line) => {
|
|
41
|
+
const [status, ...rest] = line.split('\t');
|
|
42
|
+
return { status: status[0], path: rest.join('\t') };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadChecks() {
|
|
47
|
+
const checksDir = path.join(__dirname, 'checks');
|
|
48
|
+
if (!fs.existsSync(checksDir)) return [];
|
|
49
|
+
return fs
|
|
50
|
+
.readdirSync(checksDir)
|
|
51
|
+
.filter((f) => f.endsWith('.cjs'))
|
|
52
|
+
.sort() // numeric filename prefixes (00-, 10-, ...) control run order
|
|
53
|
+
.map((f) => {
|
|
54
|
+
let mod;
|
|
55
|
+
try {
|
|
56
|
+
mod = require(path.join(checksDir, f));
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`check-commit-scope: failed to load checks/${f} — ${err.message}`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (typeof mod?.run !== 'function') {
|
|
62
|
+
console.error(`check-commit-scope: skipping checks/${f} — does not export { name, run(ctx) }`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return { file: f, name: mod.name || f, run: mod.run, skipIfAlreadyFailing: !!mod.skipIfAlreadyFailing };
|
|
66
|
+
})
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function main() {
|
|
71
|
+
let changes;
|
|
72
|
+
try {
|
|
73
|
+
changes = stagedChanges();
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('check-commit-scope: could not read staged changes —', err.message);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (changes.length === 0) process.exit(0);
|
|
80
|
+
|
|
81
|
+
const touchesSlice = changes.some((c) => SLICE_PATTERN.test(c.path));
|
|
82
|
+
if (!touchesSlice) process.exit(0); // not a slice commit — nothing to enforce
|
|
83
|
+
|
|
84
|
+
const ctx = {
|
|
85
|
+
changes,
|
|
86
|
+
touchesSlice,
|
|
87
|
+
repoRoot: execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(),
|
|
88
|
+
SLICE_PATTERN,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const checks = loadChecks();
|
|
92
|
+
const violations = [];
|
|
93
|
+
const claimedPaths = new Set(); // first check to flag a path wins — avoids repeat noise
|
|
94
|
+
|
|
95
|
+
for (const check of checks) {
|
|
96
|
+
if (check.skipIfAlreadyFailing && violations.length > 0) continue;
|
|
97
|
+
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = check.run(ctx) || [];
|
|
101
|
+
} catch (err) {
|
|
102
|
+
violations.push({ path: '(check error)', reason: `[${check.name}] threw: ${err.message}` });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const v of result) {
|
|
107
|
+
if (claimedPaths.has(v.path)) continue;
|
|
108
|
+
claimedPaths.add(v.path);
|
|
109
|
+
violations.push({ path: v.path, reason: `[${check.name}] ${v.reason}` });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (violations.length > 0) {
|
|
114
|
+
console.error('\n❌ commit blocked — slice commit-scope guard found issues:\n');
|
|
115
|
+
for (const v of violations) console.error(` - ${v.path} — ${v.reason}`);
|
|
116
|
+
console.error('\nSee .build-kit/lib/checks/ for what each check enforces.\n');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Rejects a slice commit that touches shared infra which must never change from
|
|
4
|
+
// slice work: the package manifest/lockfiles (no new/changed dependencies) and
|
|
5
|
+
// server.ts (routes/processors are auto-discovered — never wired there by hand).
|
|
6
|
+
|
|
7
|
+
const BLOCKED = [
|
|
8
|
+
{
|
|
9
|
+
pattern: /^(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|npm-shrinkwrap\.json)$/,
|
|
10
|
+
reason: 'dependency/package manifest changes are not allowed from a slice commit',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
pattern: /^server\.ts$/,
|
|
14
|
+
reason: 'server.ts is shared infra (routes/processors are auto-discovered) — never touched by slice work',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
name: 'blocked-paths',
|
|
20
|
+
run(ctx) {
|
|
21
|
+
const violations = [];
|
|
22
|
+
for (const { path: p } of ctx.changes) {
|
|
23
|
+
const hit = BLOCKED.find((b) => b.pattern.test(p));
|
|
24
|
+
if (hit) violations.push({ path: p, reason: hit.reason });
|
|
25
|
+
}
|
|
26
|
+
return violations;
|
|
27
|
+
},
|
|
28
|
+
};
|