diff --git a/.github/workflows/cc.yml b/.github/workflows/cc.yml
index 32f61aadc0..ab985f345d 100644
--- a/.github/workflows/cc.yml
+++ b/.github/workflows/cc.yml
@@ -12,7 +12,7 @@ jobs:
- uses: cashapp/activate-hermit@v1
- name: Update PR title
run: |
- orig="${{ github.event.pull_request.title }}"
+ orig=${{ toJSON(github.event.pull_request.title) }}
modified="$(gptcc "$orig")"
diff -u <(echo "$orig") <(echo "$modified") | tail +4 || true
gh pr edit --title "$modified"
diff --git a/cmd/ftl/cmd_init.go b/cmd/ftl/cmd_init.go
index 2ce75f2c08..a97aecd8ea 100644
--- a/cmd/ftl/cmd_init.go
+++ b/cmd/ftl/cmd_init.go
@@ -8,11 +8,12 @@ import (
"reflect"
"strings"
- "github.com/TBD54566975/scaffolder"
"github.com/alecthomas/errors"
+ "github.com/beevik/etree"
"github.com/iancoleman/strcase"
- goruntime "github.com/TBD54566975/ftl/go-runtime"
+ "github.com/TBD54566975/scaffolder"
+
"github.com/TBD54566975/ftl/internal"
kotlinruntime "github.com/TBD54566975/ftl/kotlin-runtime"
)
@@ -32,7 +33,13 @@ func (i initGoCmd) Run(parent *initCmd) error {
if i.Name == "" {
i.Name = filepath.Base(i.Dir)
}
- return errors.WithStack(scaffold(goruntime.Files, parent.Hermit, i.Dir, i))
+ tmpDir, err := unzipToTmpDir(kotlinruntime.Files)
+ if err != nil {
+ return errors.Wrap(err, "failed to unzip kotlin runtime")
+ }
+ defer os.RemoveAll(tmpDir)
+
+ return errors.WithStack(scaffold(parent.Hermit, tmpDir, i.Dir, i))
}
type initKotlinCmd struct {
@@ -42,28 +49,87 @@ type initKotlinCmd struct {
Name string `arg:"" help:"Name of the FTL module to create underneath the base directory."`
}
-func (i *initKotlinCmd) Run(parent *initCmd) error {
+func (i initKotlinCmd) Run(parent *initCmd) error {
if i.Name == "" {
i.Name = filepath.Base(i.Dir)
}
- return errors.WithStack(scaffold(kotlinruntime.Files, parent.Hermit, i.Dir, i))
+
+ if _, err := os.Stat(filepath.Join(i.Dir, i.Name)); err == nil {
+ return errors.Errorf("module directory %s already exists", filepath.Join(i.Dir, i.Name))
+ }
+
+ options := []scaffolder.Option{}
+
+ // Update root POM if it already exists.
+ pomFile := filepath.Join(i.Dir, "pom.xml")
+ if _, err := os.Stat(pomFile); err == nil {
+ options = append(options, scaffolder.Exclude("pom.xml"))
+ if err := updatePom(pomFile, i.Name); err != nil {
+ return errors.WithStack(err)
+ }
+ }
+
+ tmpDir, err := unzipToTmpDir(kotlinruntime.Files)
+ if err != nil {
+ return errors.Wrap(err, "failed to unzip kotlin runtime")
+ }
+ defer os.RemoveAll(tmpDir)
+
+ return errors.WithStack(scaffold(parent.Hermit, tmpDir, i.Dir, initKotlinContext{initKotlinCmd: i}, options...))
}
-func scaffold(reader *zip.Reader, hermit bool, dir string, ctx any) error {
- tmpDir, err := os.MkdirTemp("", "ftl-init-*")
+func updatePom(pomFile, name string) error {
+ tree := etree.NewDocument()
+ err := tree.ReadFromFile(pomFile)
if err != nil {
return errors.WithStack(err)
}
- defer os.RemoveAll(tmpDir)
- err = internal.UnzipDir(reader, tmpDir)
+
+ // Add new module entry to root of XML file
+ root := tree.Root()
+ modules := root.SelectElement("modules")
+ if modules == nil {
+ modules = root.CreateElement("modules")
+ }
+ modules.CreateText(" ")
+ module := modules.CreateElement("module")
+ module.SetText("ftl-module-" + name)
+ modules.CreateText("\n ")
+
+ // Write updated XML file back to disk
+ err = tree.WriteToFile(pomFile)
if err != nil {
return errors.WithStack(err)
}
+ return nil
+}
+
+type initKotlinContext struct {
+ initKotlinCmd
+ Modules []string
+ Prelude string
+ Postlude string
+}
+
+func unzipToTmpDir(reader *zip.Reader) (string, error) {
+ tmpDir, err := os.MkdirTemp("", "ftl-init-*")
+ if err != nil {
+ return "", errors.WithStack(err)
+ }
+ err = internal.UnzipDir(reader, tmpDir)
+ if err != nil {
+ return "", errors.WithStack(err)
+ }
+ return tmpDir, nil
+}
+
+func scaffold(hermit bool, source string, destination string, ctx any, options ...scaffolder.Option) error {
opts := []scaffolder.Option{scaffolder.Functions(scaffoldFuncs), scaffolder.Exclude("go.mod")}
if !hermit {
opts = append(opts, scaffolder.Exclude("bin"))
}
- if err := scaffolder.Scaffold(tmpDir, dir, ctx, opts...); err != nil {
+ opts = append(opts, options...)
+ if err := scaffolder.Scaffold(source, destination, ctx, opts...); err != nil {
return errors.Wrap(err, "failed to scaffold")
}
return nil
diff --git a/go.mod b/go.mod
index 59ced25aee..86fa90f425 100644
--- a/go.mod
+++ b/go.mod
@@ -68,6 +68,7 @@ require (
github.com/alecthomas/errors v0.4.0
github.com/alecthomas/participle/v2 v2.0.0
github.com/alecthomas/types v0.7.1
+ github.com/beevik/etree v1.2.0
github.com/deckarep/golang-set/v2 v2.3.0
github.com/gofrs/flock v0.8.1
github.com/iancoleman/strcase v0.2.0
diff --git a/go.sum b/go.sum
index ccf692f908..838a981a51 100644
--- a/go.sum
+++ b/go.sum
@@ -30,6 +30,8 @@ github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVK
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/amacneil/dbmate/v2 v2.7.0 h1:aaTyfVPxf01KyIx5E0i/OATygrit2hjivyKSX0YSJxI=
github.com/amacneil/dbmate/v2 v2.7.0/go.mod h1:I13evRylGros6OXuJij+oskvr+YMaPvSD9Z7PNucXWA=
+github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw=
+github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
github.com/bool64/dev v0.2.31 h1:OS57EqYaYe2M/2bw9uhDCIFiZZwywKFS/4qMLN6JUmQ=
github.com/bool64/dev v0.2.31/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
diff --git a/kotlin-runtime/scaffolding/ftl-module-{{ .Name | lower }}/pom.xml b/kotlin-runtime/scaffolding/ftl-module-{{ .Name | lower }}/pom.xml
index 76bbd57d2c..5b6c7cafee 100644
--- a/kotlin-runtime/scaffolding/ftl-module-{{ .Name | lower }}/pom.xml
+++ b/kotlin-runtime/scaffolding/ftl-module-{{ .Name | lower }}/pom.xml
@@ -12,6 +12,10 @@
1.0-SNAPSHOT
+
+ {{ .Name | lower }}
+
+
diff --git a/kotlin-runtime/scaffolding/pom.xml b/kotlin-runtime/scaffolding/pom.xml
index f7dfe6ddf9..48c7f36176 100644
--- a/kotlin-runtime/scaffolding/pom.xml
+++ b/kotlin-runtime/scaffolding/pom.xml
@@ -136,7 +136,7 @@
target/dependency/ftl-generator.jar
--endpoint=http://127.0.0.1:8892
--dest=${project.build.directory}
- --module={{ .Name | camel | lower }}
+ --module=${ftlModuleName}
--module-client-suffix=ModuleClient
diff --git a/scripts/integration-tests b/scripts/integration-tests
index 1aa2c3a683..1727b02303 100755
--- a/scripts/integration-tests
+++ b/scripts/integration-tests
@@ -13,19 +13,12 @@ error() {
build_release() {
info "Building release"
- bit build/release/ftl-controller \
- build/release/ftl-runner \
- build/release/ftl \
+ bit build/release/ftl \
kotlin-runtime/ftl-runtime/target/ftl-runtime-1.0-SNAPSHOT.jar \
kotlin-runtime/ftl-generator/target/ftl-generator-1.0-SNAPSHOT-jar-with-dependencies.jar \
build/template/ftl/jars/ftl-runtime.jar
}
-wipe_database() {
- info "Initialising database"
- ftl-initdb --recreate
-}
-
wait_for() {
info "Waiting for $1"
for _ in {1..240}; do
@@ -45,7 +38,7 @@ stop_cluster() {
start_cluster() {
info "Starting cluster"
- goreman -logtime=false -f Procfile.integration start &
+ ftl serve --recreate &
wait_for "cluster to become ready" "ftl status"
trap stop_cluster EXIT INT TERM
}
@@ -59,27 +52,29 @@ deploy_echo_kotlin() (
deploy_fresh_kotlin() (
info "Deploying newly initialised Kotlin module"
- rm -rf build/echo2
- ftl init kotlin build/modules echo2
- cd build/modules/ftl-module-echo2
+ IT_MODULES=build/modules
+ rm -rf "${IT_MODULES}"
+ ftl init kotlin "${IT_MODULES}" echo2
+ ftl init kotlin "${IT_MODULES}" echo3
+ cd "${IT_MODULES}"
mvn compile
- ftl deploy target
+ ftl deploy ftl-module-echo2/target
+ ftl deploy ftl-module-echo3/target
)
deploy_time_go() (
info "Deploying time"
cd examples
# Pull a supported platforms from the cluster.
- platform="$(ftl status | jq -r '.runners[].labels | "\(.os)-\(.arch)"' | sort | uniq | head -1)"
+ platform="$(ftl status | jq -r '(.runners // [])[].labels | "\(.os)-\(.arch)"' | sort | uniq | head -1)"
ftl-go --os "${platform%-*}" --arch "${platform#*-}" deploy time
)
wait_for_deploys() {
- wait_for "deployments to come up" 'ftl status | jq -r ".routes[].module" | sort | paste -sd " " - | grep -q "echo echo2 time"'
+ wait_for "deployments to come up" 'ftl status | jq -r "(.routes // [])[].module" | sort | paste -sd " " - | grep -q "echo echo2 echo3 time"'
}
build_release
-wipe_database
start_cluster
# Cluster is up, start interacting with it.
@@ -95,4 +90,8 @@ message="$(ftl call echo.echo '{"name": "Alice"}' | jq -r .message)"
message="$(ftl call echo2.echo '{"name": "Alice"}' | jq -r .message)"
[[ "$message" =~ "Hello, Alice!" ]] || error "Unexpected response from echo2: $message"
+
+message="$(ftl call echo3.echo '{"name": "Alice"}' | jq -r .message)"
+[[ "$message" =~ "Hello, Alice!" ]] || error "Unexpected response from echo2: $message"
+
info "Success!"