@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,139 @@
|
|
|
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>OpenCQRS Quickstart</name>
|
|
8
|
+
<version>0.0.1-SNAPSHOT</version>
|
|
9
|
+
|
|
10
|
+
<properties>
|
|
11
|
+
<java.version>21</java.version>
|
|
12
|
+
<opencqrs.version>2.0.0</opencqrs.version>
|
|
13
|
+
<spring-boot.version>4.1.0</spring-boot.version>
|
|
14
|
+
</properties>
|
|
15
|
+
|
|
16
|
+
<dependencies>
|
|
17
|
+
<dependency>
|
|
18
|
+
<groupId>org.springframework.boot</groupId>
|
|
19
|
+
<artifactId>spring-boot-starter-webmvc</artifactId>
|
|
20
|
+
</dependency>
|
|
21
|
+
|
|
22
|
+
<!-- Read model persistence: Spring Data JPA + PostgreSQL, default for all projections -->
|
|
23
|
+
<dependency>
|
|
24
|
+
<groupId>org.springframework.boot</groupId>
|
|
25
|
+
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
|
26
|
+
</dependency>
|
|
27
|
+
<dependency>
|
|
28
|
+
<groupId>org.postgresql</groupId>
|
|
29
|
+
<artifactId>postgresql</artifactId>
|
|
30
|
+
<scope>runtime</scope>
|
|
31
|
+
</dependency>
|
|
32
|
+
|
|
33
|
+
<!-- Spring Integration JDBC: backs the distributed lock registry EventHandlingProcessor uses for
|
|
34
|
+
leader election (exactly one instance processes a given group+partition at a time) -->
|
|
35
|
+
<dependency>
|
|
36
|
+
<groupId>org.springframework.boot</groupId>
|
|
37
|
+
<artifactId>spring-boot-starter-integration</artifactId>
|
|
38
|
+
</dependency>
|
|
39
|
+
<dependency>
|
|
40
|
+
<groupId>org.springframework.integration</groupId>
|
|
41
|
+
<artifactId>spring-integration-jdbc</artifactId>
|
|
42
|
+
</dependency>
|
|
43
|
+
|
|
44
|
+
<dependency>
|
|
45
|
+
<groupId>org.springframework.boot</groupId>
|
|
46
|
+
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
47
|
+
</dependency>
|
|
48
|
+
|
|
49
|
+
<!-- OpenCQRS framework: command/event handling, EventSourcingDB client, Spring Boot auto-configuration -->
|
|
50
|
+
<dependency>
|
|
51
|
+
<groupId>com.opencqrs</groupId>
|
|
52
|
+
<artifactId>framework-spring-boot-starter</artifactId>
|
|
53
|
+
<version>${opencqrs.version}</version>
|
|
54
|
+
</dependency>
|
|
55
|
+
|
|
56
|
+
<!-- Auto-starts docker-compose.yml services (EventSourcingDB, Postgres) on app run -->
|
|
57
|
+
<dependency>
|
|
58
|
+
<groupId>org.springframework.boot</groupId>
|
|
59
|
+
<artifactId>spring-boot-docker-compose</artifactId>
|
|
60
|
+
<scope>runtime</scope>
|
|
61
|
+
<optional>true</optional>
|
|
62
|
+
</dependency>
|
|
63
|
+
|
|
64
|
+
<!-- Testing -->
|
|
65
|
+
<dependency>
|
|
66
|
+
<groupId>com.opencqrs</groupId>
|
|
67
|
+
<artifactId>framework-test</artifactId>
|
|
68
|
+
<version>${opencqrs.version}</version>
|
|
69
|
+
<scope>test</scope>
|
|
70
|
+
</dependency>
|
|
71
|
+
<dependency>
|
|
72
|
+
<groupId>org.springframework.boot</groupId>
|
|
73
|
+
<artifactId>spring-boot-starter-test</artifactId>
|
|
74
|
+
<scope>test</scope>
|
|
75
|
+
</dependency>
|
|
76
|
+
<!-- Spring Boot 4 test slices: @DataJpaTest (build-state-view/build-automation) and
|
|
77
|
+
@WebMvcTest (build-state-change's rest-api-patterns.md) each moved to their own artifact -->
|
|
78
|
+
<dependency>
|
|
79
|
+
<groupId>org.springframework.boot</groupId>
|
|
80
|
+
<artifactId>spring-boot-data-jpa-test</artifactId>
|
|
81
|
+
<scope>test</scope>
|
|
82
|
+
</dependency>
|
|
83
|
+
<dependency>
|
|
84
|
+
<groupId>org.springframework.boot</groupId>
|
|
85
|
+
<artifactId>spring-boot-webmvc-test</artifactId>
|
|
86
|
+
<scope>test</scope>
|
|
87
|
+
</dependency>
|
|
88
|
+
<dependency>
|
|
89
|
+
<groupId>org.junit.jupiter</groupId>
|
|
90
|
+
<artifactId>junit-jupiter</artifactId>
|
|
91
|
+
<scope>test</scope>
|
|
92
|
+
</dependency>
|
|
93
|
+
<dependency>
|
|
94
|
+
<groupId>org.springframework.boot</groupId>
|
|
95
|
+
<artifactId>spring-boot-testcontainers</artifactId>
|
|
96
|
+
<scope>test</scope>
|
|
97
|
+
</dependency>
|
|
98
|
+
<dependency>
|
|
99
|
+
<groupId>org.testcontainers</groupId>
|
|
100
|
+
<artifactId>testcontainers-postgresql</artifactId>
|
|
101
|
+
<scope>test</scope>
|
|
102
|
+
</dependency>
|
|
103
|
+
<dependency>
|
|
104
|
+
<groupId>org.testcontainers</groupId>
|
|
105
|
+
<artifactId>testcontainers-junit-jupiter</artifactId>
|
|
106
|
+
<scope>test</scope>
|
|
107
|
+
</dependency>
|
|
108
|
+
</dependencies>
|
|
109
|
+
|
|
110
|
+
<build>
|
|
111
|
+
<plugins>
|
|
112
|
+
<plugin>
|
|
113
|
+
<groupId>org.apache.maven.plugins</groupId>
|
|
114
|
+
<artifactId>maven-compiler-plugin</artifactId>
|
|
115
|
+
<configuration>
|
|
116
|
+
<source>${java.version}</source>
|
|
117
|
+
<target>${java.version}</target>
|
|
118
|
+
<parameters>true</parameters>
|
|
119
|
+
</configuration>
|
|
120
|
+
</plugin>
|
|
121
|
+
<plugin>
|
|
122
|
+
<groupId>org.springframework.boot</groupId>
|
|
123
|
+
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
124
|
+
</plugin>
|
|
125
|
+
</plugins>
|
|
126
|
+
</build>
|
|
127
|
+
|
|
128
|
+
<dependencyManagement>
|
|
129
|
+
<dependencies>
|
|
130
|
+
<dependency>
|
|
131
|
+
<groupId>org.springframework.boot</groupId>
|
|
132
|
+
<artifactId>spring-boot-dependencies</artifactId>
|
|
133
|
+
<version>${spring-boot.version}</version>
|
|
134
|
+
<type>pom</type>
|
|
135
|
+
<scope>import</scope>
|
|
136
|
+
</dependency>
|
|
137
|
+
</dependencies>
|
|
138
|
+
</dependencyManagement>
|
|
139
|
+
</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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.example.quickstart.config;
|
|
2
|
+
|
|
3
|
+
import com.opencqrs.framework.eventhandler.progress.JdbcProgressTracker;
|
|
4
|
+
import com.opencqrs.framework.persistence.EventSource;
|
|
5
|
+
import com.opencqrs.framework.types.PreconfiguredAssignableClassEventTypeResolver;
|
|
6
|
+
import java.util.Map;
|
|
7
|
+
import javax.sql.DataSource;
|
|
8
|
+
import org.springframework.context.annotation.Bean;
|
|
9
|
+
import org.springframework.context.annotation.Configuration;
|
|
10
|
+
import org.springframework.integration.jdbc.lock.DefaultLockRepository;
|
|
11
|
+
import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
|
|
12
|
+
import org.springframework.integration.jdbc.lock.LockRepository;
|
|
13
|
+
import org.springframework.transaction.PlatformTransactionManager;
|
|
14
|
+
|
|
15
|
+
@Configuration
|
|
16
|
+
public class CqrsConfiguration {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Identifies this service as the publisher of every event it appends (the CloudEvents {@code source}
|
|
20
|
+
* field). One fixed value for the whole service — not per-slice.
|
|
21
|
+
*/
|
|
22
|
+
@Bean
|
|
23
|
+
public EventSource eventSource() {
|
|
24
|
+
return new EventSource("tag://quickstart");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Maps every event's stored {@code type} string to its Java class explicitly. Required for production —
|
|
29
|
+
* the default {@code ClassNameEventTypeResolver} fallback uses the fully-qualified Java classname as the
|
|
30
|
+
* stored type, which breaks the moment a class is renamed or moved package, and can't interoperate with
|
|
31
|
+
* non-Java event producers.
|
|
32
|
+
*
|
|
33
|
+
* <p><strong>Every event type used by any slice must be registered here.</strong> build-state-change and
|
|
34
|
+
* build-automation both add a line when they introduce a new event — see their SKILL.md for the exact
|
|
35
|
+
* step. Naming convention: {@code "quickstart.{context}.{event-name}.v1"}, lowercase, dot-separated.
|
|
36
|
+
*/
|
|
37
|
+
@Bean
|
|
38
|
+
public PreconfiguredAssignableClassEventTypeResolver eventTypeResolver() {
|
|
39
|
+
return new PreconfiguredAssignableClassEventTypeResolver(Map.ofEntries(
|
|
40
|
+
// "quickstart.{context}.{eventname}.v1", {EventClass}.class
|
|
41
|
+
));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Distributed-lock leader election, backed by the {@code EVENTHANDLER_LOCK} table (see schema.sql) —
|
|
46
|
+
* ensures exactly one running instance processes events for a given processing group + partition at a
|
|
47
|
+
* time when this service is scaled to multiple instances.
|
|
48
|
+
*/
|
|
49
|
+
@Bean
|
|
50
|
+
public DefaultLockRepository defaultLockRepository(DataSource dataSource) {
|
|
51
|
+
var result = new DefaultLockRepository(dataSource);
|
|
52
|
+
result.setPrefix("EVENTHANDLER_");
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Bean
|
|
57
|
+
public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository) {
|
|
58
|
+
return new JdbcLockRegistry(lockRepository);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Durable checkpoint per processing group + partition (see {@code EVENTHANDLER_PROGRESS} in schema.sql),
|
|
63
|
+
* so a restart or fail-over resumes exactly where the previous instance left off instead of re-processing
|
|
64
|
+
* or skipping events. {@code setProceedTransactionally(true)} lets an {@code @EventHandling} method
|
|
65
|
+
* participate in the same transaction as the checkpoint update, making that handler's side effect and its
|
|
66
|
+
* checkpoint advance atomic together.
|
|
67
|
+
*/
|
|
68
|
+
@Bean
|
|
69
|
+
public JdbcProgressTracker jdbcProgressTracker(DataSource dataSource, PlatformTransactionManager transactionManager) {
|
|
70
|
+
var result = new JdbcProgressTracker(dataSource, transactionManager);
|
|
71
|
+
result.setProceedTransactionally(true);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
spring.application.name=quickstart
|
|
2
|
+
server.port=8080
|
|
3
|
+
|
|
4
|
+
esdb.server.uri=http://localhost:3000
|
|
5
|
+
esdb.server.api-token=${ESDB_API_TOKEN:secret}
|
|
6
|
+
|
|
7
|
+
spring.datasource.url=jdbc:postgresql://localhost:5432/quickstart
|
|
8
|
+
spring.datasource.username=quickstart
|
|
9
|
+
spring.datasource.password=quickstart
|
|
10
|
+
|
|
11
|
+
# Applies schema.sql (lock + progress tables) on every startup; JPA-mapped read model tables
|
|
12
|
+
# are handled by Hibernate below, not by schema.sql.
|
|
13
|
+
spring.sql.init.mode=always
|
|
14
|
+
spring.jpa.hibernate.ddl-auto=update
|
|
15
|
+
|
|
16
|
+
spring.docker.compose.enabled=true
|
|
17
|
+
spring.docker.compose.file=docker-compose.yml
|
|
18
|
+
|
|
19
|
+
management.endpoint.health.show-components=always
|
|
20
|
+
|
|
21
|
+
# In-memory command-handling cache is fine for a single instance / local dev. Revisit for
|
|
22
|
+
# multi-instance production deployments (see docs.opencqrs.com's Event Handling Processor reference).
|
|
23
|
+
opencqrs.command-handling.cache.type=in_memory
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- Backing tables for the distributed-lock leader election (EVENTHANDLER_LOCK, used by JdbcLockRegistry)
|
|
2
|
+
-- and the durable event-handling checkpoint (EVENTHANDLER_PROGRESS, used by JdbcProgressTracker) —
|
|
3
|
+
-- see com.example.quickstart.config.CqrsConfiguration. Verified against OpenCQRS's own reference
|
|
4
|
+
-- application (framework-spring-boot-autoconfigure); portable as-is to PostgreSQL.
|
|
5
|
+
CREATE TABLE IF NOT EXISTS EVENTHANDLER_LOCK (
|
|
6
|
+
LOCK_KEY CHAR(36) NOT NULL,
|
|
7
|
+
REGION VARCHAR(100) NOT NULL,
|
|
8
|
+
CLIENT_ID CHAR(36),
|
|
9
|
+
CREATED_DATE TIMESTAMP NOT NULL,
|
|
10
|
+
EXPIRED_AFTER TIMESTAMP NOT NULL,
|
|
11
|
+
CONSTRAINT EVENTHANDLER_LOCK_PK PRIMARY KEY (LOCK_KEY, REGION)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS EVENTHANDLER_PROGRESS (
|
|
15
|
+
GROUP_KEY VARCHAR(100) NOT NULL,
|
|
16
|
+
PARTITION_ID BIGINT NOT NULL,
|
|
17
|
+
EVENT_ID VARCHAR(100) NOT NULL,
|
|
18
|
+
CONSTRAINT EVENTHANDLER_PROGRESS_PK PRIMARY KEY (GROUP_KEY, PARTITION_ID)
|
|
19
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>OpenCQRS Quickstart</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>OpenCQRS 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>
|
|
@@ -58,6 +58,31 @@ When asked to build a slice, always follow this flow:
|
|
|
58
58
|
|
|
59
59
|
After you are done, automatically run the tests for the slice that was edited.
|
|
60
60
|
|
|
61
|
+
## Commit Scope Guard
|
|
62
|
+
|
|
63
|
+
A pre-commit hook (`.githooks/pre-commit`, installed via `init --hooks` — see its own project's setup)
|
|
64
|
+
runs `.build-kit/lib/check-commit-scope.cjs` on every commit that touches `src/slices/{context}/{slice}/`
|
|
65
|
+
or a webhook's `supabase/functions/{fn}/index.ts`. It loads every check under `.build-kit/lib/checks/`
|
|
66
|
+
and rejects the commit if any of them find a problem:
|
|
67
|
+
|
|
68
|
+
- **blocked-paths** — `package.json`/lockfiles and `server.ts` are never touched by slice work
|
|
69
|
+
- **slice-scope** — everything staged must be inside the slice folder (or the webhook's own edge
|
|
70
|
+
function) or a documented exception: `src/slices/{context}/{Context}Events.ts`,
|
|
71
|
+
`src/common/loadPostgresEventstore.ts`, or `supabase/config.toml`
|
|
72
|
+
- **append-only-migrations** — `supabase/migrations/V{n}__*.sql` may only be **added**, never edited
|
|
73
|
+
- **test-file-present** — a changed Command/Projection/processor file needs a sibling `*.test.ts`
|
|
74
|
+
(webhook edge functions are tested via `supabase functions serve`, not a test file — exempt)
|
|
75
|
+
- **no-invented-fields** — heuristic: flags a field used in code that isn't declared anywhere in
|
|
76
|
+
`.build-kit/.slices/{context}/{slice}/slice.json`
|
|
77
|
+
- **spec-coverage** — heuristic: the test file needs at least as many `it(...)` blocks as slice.json
|
|
78
|
+
has `specifications[]` entries
|
|
79
|
+
- **tsc-build** — `npx tsc --noEmit` must still pass
|
|
80
|
+
|
|
81
|
+
If a commit is rejected, split it — commit the out-of-scope file separately from the slice work, or add
|
|
82
|
+
the missing test/fix the field — rather than passing `--no-verify`. Run `npm run check:scope` any time
|
|
83
|
+
you want to check staged files before committing. To add a new check, read
|
|
84
|
+
`.build-kit/lib/checks/README.md` and drop in a file following its interface — no other wiring needed.
|
|
85
|
+
|
|
61
86
|
## Example Slice Structure
|
|
62
87
|
|
|
63
88
|
```
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
// A webhook slice is a self-contained edge function with no src/slices/ folder
|
|
35
|
+
// of its own — still counts as "this is a slice commit" for the gate below.
|
|
36
|
+
const WEBHOOK_FUNCTION_PATTERN = /^supabase\/functions\/[^/]+\/index\.ts$/;
|
|
37
|
+
|
|
38
|
+
function stagedChanges() {
|
|
39
|
+
const out = execSync('git diff --cached --name-status --no-renames', { encoding: 'utf8' });
|
|
40
|
+
return out
|
|
41
|
+
.split('\n')
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.map((line) => {
|
|
44
|
+
const [status, ...rest] = line.split('\t');
|
|
45
|
+
return { status: status[0], path: rest.join('\t') };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function loadChecks() {
|
|
50
|
+
const checksDir = path.join(__dirname, 'checks');
|
|
51
|
+
if (!fs.existsSync(checksDir)) return [];
|
|
52
|
+
return fs
|
|
53
|
+
.readdirSync(checksDir)
|
|
54
|
+
.filter((f) => f.endsWith('.cjs'))
|
|
55
|
+
.sort() // numeric filename prefixes (00-, 10-, ...) control run order
|
|
56
|
+
.map((f) => {
|
|
57
|
+
let mod;
|
|
58
|
+
try {
|
|
59
|
+
mod = require(path.join(checksDir, f));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`check-commit-scope: failed to load checks/${f} — ${err.message}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (typeof mod?.run !== 'function') {
|
|
65
|
+
console.error(`check-commit-scope: skipping checks/${f} — does not export { name, run(ctx) }`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return { file: f, name: mod.name || f, run: mod.run, skipIfAlreadyFailing: !!mod.skipIfAlreadyFailing };
|
|
69
|
+
})
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
let changes;
|
|
75
|
+
try {
|
|
76
|
+
changes = stagedChanges();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error('check-commit-scope: could not read staged changes —', err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (changes.length === 0) process.exit(0);
|
|
83
|
+
|
|
84
|
+
const touchesSlice = changes.some((c) => SLICE_PATTERN.test(c.path) || WEBHOOK_FUNCTION_PATTERN.test(c.path));
|
|
85
|
+
if (!touchesSlice) process.exit(0); // not a slice commit — nothing to enforce
|
|
86
|
+
|
|
87
|
+
const ctx = {
|
|
88
|
+
changes,
|
|
89
|
+
touchesSlice,
|
|
90
|
+
repoRoot: execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim(),
|
|
91
|
+
SLICE_PATTERN,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const checks = loadChecks();
|
|
95
|
+
const violations = [];
|
|
96
|
+
const claimedPaths = new Set(); // first check to flag a path wins — avoids repeat noise
|
|
97
|
+
|
|
98
|
+
for (const check of checks) {
|
|
99
|
+
if (check.skipIfAlreadyFailing && violations.length > 0) continue;
|
|
100
|
+
|
|
101
|
+
let result;
|
|
102
|
+
try {
|
|
103
|
+
result = check.run(ctx) || [];
|
|
104
|
+
} catch (err) {
|
|
105
|
+
violations.push({ path: '(check error)', reason: `[${check.name}] threw: ${err.message}` });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const v of result) {
|
|
110
|
+
if (claimedPaths.has(v.path)) continue;
|
|
111
|
+
claimedPaths.add(v.path);
|
|
112
|
+
violations.push({ path: v.path, reason: `[${check.name}] ${v.reason}` });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (violations.length > 0) {
|
|
117
|
+
console.error('\n❌ commit blocked — slice commit-scope guard found issues:\n');
|
|
118
|
+
for (const v of violations) console.error(` - ${v.path} — ${v.reason}`);
|
|
119
|
+
console.error('\nSee .build-kit/lib/checks/ for what each check enforces.\n');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
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
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Everything staged in a slice commit must be inside the slice's own folder (or
|
|
4
|
+
// a webhook's self-contained edge function), or one of the documented
|
|
5
|
+
// shared-infra exceptions a slice legitimately registers *into* (never rewrites
|
|
6
|
+
// wholesale). See build-kit/lib/AGENT.md and the build-state-view/
|
|
7
|
+
// build-automation/build-webhook SKILL.md files for what a compliant edit to an
|
|
8
|
+
// exception file looks like. Migration files have their own dedicated check
|
|
9
|
+
// (20-append-only-migrations.cjs), so they're allowed through here.
|
|
10
|
+
|
|
11
|
+
const WEBHOOK_FUNCTION_PATTERN = /^supabase\/functions\/[^/]+\/index\.ts$/;
|
|
12
|
+
|
|
13
|
+
const ALLOWED_EXCEPTIONS = [
|
|
14
|
+
/^src\/slices\/[^/]+\/[A-Za-z0-9]+Events\.ts$/, // per-context event union (append-only)
|
|
15
|
+
/^src\/common\/loadPostgresEventstore\.ts$/, // projection registration / schema.migrate()
|
|
16
|
+
/^supabase\/config\.toml$/, // webhook function registration (verify_jwt entry)
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const MIGRATION_PATTERN = /^supabase\/migrations\/V\d+__.*\.sql$/;
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
name: 'slice-scope',
|
|
23
|
+
run(ctx) {
|
|
24
|
+
const violations = [];
|
|
25
|
+
for (const { path: p } of ctx.changes) {
|
|
26
|
+
if (ctx.SLICE_PATTERN.test(p)) continue;
|
|
27
|
+
if (WEBHOOK_FUNCTION_PATTERN.test(p)) continue;
|
|
28
|
+
if (MIGRATION_PATTERN.test(p)) continue;
|
|
29
|
+
if (ALLOWED_EXCEPTIONS.some((r) => r.test(p))) continue;
|
|
30
|
+
violations.push({ path: p, reason: 'outside src/slices/{context}/{slice}/ and not a documented exception' });
|
|
31
|
+
}
|
|
32
|
+
return violations;
|
|
33
|
+
},
|
|
34
|
+
};
|