Skip to content

Commit 3284206

Browse files
committed
KTL-4149 feat: add TeamCity project for building landing pages
1 parent d28b04e commit 3284206

File tree

7 files changed

+192
-0
lines changed

7 files changed

+192
-0
lines changed

.teamcity/common/utils.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package common
2+
3+
/**
4+
* Sanitizes a string to be used as a TeamCity ID.
5+
*
6+
* @param id The string to sanitize
7+
*/
8+
fun sanitizeId(id: String): String = id.replace("[^a-zA-Z0-9_]".toRegex(), "_")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package landings
2+
3+
/**
4+
* Configuration for a landing page instance.
5+
* Each landing page is a separate client app.
6+
*
7+
* @param name The landing page name, used as base path
8+
* @param repositoryUrl The GitHub repository URL
9+
* @param branch The branch to build from
10+
*/
11+
data class LandingConfiguration(
12+
val name: String,
13+
val repositoryUrl: String,
14+
val branch: String = "main"
15+
)
16+
17+
/**
18+
* List of all landing pages to be built.
19+
* Add new landing pages here.
20+
*/
21+
val landingConfigurations = listOf(
22+
LandingConfiguration(
23+
name = "kotlin-spring-ai-tutorial",
24+
repositoryUrl = "git@github.com:jetbrains-lovable/kotlin-ai-tutorial.git"
25+
)
26+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package landings
2+
3+
import jetbrains.buildServer.configs.kotlin.Project
4+
import landings.builds.BuildLandingPage
5+
6+
object LandingPagesProject : Project({
7+
name = "Landing Pages"
8+
description = "Builds landing pages from repositories"
9+
10+
landingConfigurations.forEach { config ->
11+
vcsRoot(createVcsRootForLanding(config))
12+
13+
buildType(BuildLandingPage(config))
14+
}
15+
})
16+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package landings.builds
2+
3+
import BuildParams.KLANG_NODE_CONTAINER
4+
import jetbrains.buildServer.configs.kotlin.BuildType
5+
import jetbrains.buildServer.configs.kotlin.buildSteps.script
6+
import jetbrains.buildServer.configs.kotlin.triggers.vcs
7+
import landings.LandingConfiguration
8+
import common.sanitizeId
9+
import landings.createVcsRootForLanding
10+
11+
/**
12+
* Build type for building a Vite landing page.
13+
* This build:
14+
* 1. Checks out the landing page repository
15+
* 2. Patches the Vite config to set the correct base path
16+
* 3. Installs npm dependencies
17+
* 4. Builds the static page
18+
* 5. Publishes the dist folder as an artifact
19+
*/
20+
class BuildLandingPage(private val config: LandingConfiguration) : BuildType({
21+
id("build_landing_${sanitizeId(config.name)}")
22+
name = "Build ${config.name} langing page"
23+
24+
vcs {
25+
root(createVcsRootForLanding(config))
26+
root(vcsRoots.KotlinLangOrg, "+:scripts => kotlin-web-site-scripts")
27+
cleanCheckout = true
28+
}
29+
30+
triggers {
31+
vcs {
32+
branchFilter = "+:${config.branch}"
33+
}
34+
}
35+
36+
artifactRules = """
37+
dist/** => ${config.name}.zip
38+
""".trimIndent()
39+
40+
requirements {
41+
contains("docker.server.osType", "linux")
42+
}
43+
44+
steps {
45+
script {
46+
name = "Patch Vite config and build"
47+
scriptContent = """
48+
#!/bin/sh
49+
set -e -x -u
50+
51+
# Patch Vite config
52+
node kotlin-web-site-scripts/patch-vite-base.mjs ${config.name}
53+
54+
# Install dependencies
55+
npm ci
56+
57+
# Build
58+
npm run build
59+
60+
# Verify dist folder exists
61+
if [ ! -d "dist" ]; then
62+
echo "Error: dist folder not found after build"
63+
exit 1
64+
fi
65+
""".trimIndent()
66+
dockerImage = KLANG_NODE_CONTAINER
67+
dockerPull = true
68+
}
69+
}
70+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package landings
2+
3+
import common.sanitizeId
4+
import jetbrains.buildServer.configs.kotlin.vcs.GitVcsRoot
5+
6+
fun createVcsRootForLanding(config: LandingConfiguration): GitVcsRoot {
7+
return GitVcsRoot {
8+
id("landing_vcs_${sanitizeId(config.name)}")
9+
name = "Landing: ${config.name}"
10+
url = config.repositoryUrl
11+
authMethod = uploadedKey {
12+
uploadedKey = "default teamcity key"
13+
}
14+
branch = "refs/heads/${config.branch}"
15+
}
16+
}

.teamcity/settings.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ project {
3131
references.BuildApiReferencesProject,
3232
tests.TestsProject,
3333
documentation.DocumentationProject,
34+
landings.LandingPagesProject,
3435
).also {
3536
it.forEach { subProject(it) }
3637
}

scripts/patch-vite-base.mjs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Patches Vite config to add the correct base path for deployment.
5+
*/
6+
7+
import { readFileSync, writeFileSync, existsSync } from 'fs';
8+
import { resolve } from 'path';
9+
10+
const landingName = process.argv[2];
11+
12+
if (!landingName) {
13+
console.error('Error: Landing name is required');
14+
process.exit(1);
15+
}
16+
17+
const configFiles = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'];
18+
let viteConfigPath = null;
19+
20+
for (const configFile of configFiles) {
21+
if (existsSync(configFile)) {
22+
viteConfigPath = resolve(configFile);
23+
break;
24+
}
25+
}
26+
27+
if (!viteConfigPath) {
28+
console.error('Error: No vite config file found (checked: vite.config.ts, vite.config.js, vite.config.mjs)');
29+
process.exit(1);
30+
}
31+
32+
console.log(`Found Vite config: ${viteConfigPath}`);
33+
34+
let content = readFileSync(viteConfigPath, 'utf-8');
35+
36+
const basePath = `/lp/${landingName}/`;
37+
38+
if (content.includes('base:')) {
39+
console.log('Base path already exists, replacing...');
40+
content = content.replace(
41+
/base\s*:\s*['"`][^'"`]*['"`]/g,
42+
`base: '${basePath}'`
43+
);
44+
} else {
45+
console.log('Adding base path to config...');
46+
content = content.replace(
47+
/defineConfig\s*\(\s*\{/,
48+
`defineConfig({\n base: '${basePath}',`
49+
);
50+
}
51+
52+
writeFileSync(viteConfigPath, content, 'utf-8');
53+
54+
console.log(`Successfully patched ${viteConfigPath} with base: '${basePath}'`);
55+

0 commit comments

Comments
 (0)