3 Commits

9 changed files with 195 additions and 27 deletions

View File

@@ -8,6 +8,9 @@ ARG IS_RELEASE="false"
# Set working directory
WORKDIR /app
# Install git for version generation
RUN apk add --no-cache git
# Copy package files
COPY package*.json ./
@@ -53,4 +56,4 @@ ENV LISTEN_ADDR=0.0.0.0
ENV LISTEN_PORT=3000
# Start the integrated server
ENTRYPOINT ["./entrypoint.sh"]
ENTRYPOINT ["./entrypoint.sh"]

View File

@@ -1,6 +1,6 @@
{
"name": "jmespath-playground",
"version": "1.4.1",
"version": "1.4.3",
"description": "A React-based web application for testing JMESPath expressions against JSON data",
"main": "index.js",
"scripts": {

View File

@@ -36,6 +36,15 @@ function getContainerTool() {
}
}
function isGitRepo() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}
async function generateVersionFile() {
const versionModuleUrl = pathToFileURL(path.join(__dirname, 'version.mjs')).href;
const { generateVersionFile: generate } = await import(versionModuleUrl);
@@ -44,6 +53,26 @@ async function generateVersionFile() {
return versionFilePath;
}
function writeVersionFile(version, isRelease) {
const versionFilePath = path.join(__dirname, '..', 'src', 'version.js');
const contents = [
`export const VERSION = '${version}';`,
`export const IS_RELEASE = ${isRelease};`,
''
].join('\n');
fs.writeFileSync(versionFilePath, contents, 'utf8');
return versionFilePath;
}
function readPackageJsonVersion() {
const packagePath = path.join(__dirname, '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
if (!pkg.version) {
throw new Error('package.json does not contain a version');
}
return pkg.version;
}
function readVersionFile(versionFilePath) {
const contents = fs.readFileSync(versionFilePath, 'utf8');
const versionMatch = contents.match(/export const VERSION = '([^']+)';/);
@@ -79,6 +108,7 @@ Usage:
Options:
--all-arch Build for both arm64 and amd64 (default: build for host architecture only)
--arch <arch> Target architecture (arm64 or amd64). Can be specified multiple times.
--registry <id> Image registry (default: docker.io). Can also set JMESPATH_REGISTRY.
--help, -h Show this help message and exit
Examples:
@@ -86,6 +116,7 @@ Examples:
build-image.mjs --all-arch # Builds for both arm64 and amd64
build-image.mjs --arch arm64 # Builds for arm64 only
build-image.mjs --arch arm64 --arch amd64 # Explicitly specify both
build-image.mjs --registry docker.io # Use Docker Hub registry explicitly
build-image.mjs -h # Show help`);
}
@@ -105,6 +136,10 @@ async function main() {
type: 'string',
multiple: true,
description: 'Target architecture (arm64 or amd64)'
},
registry: {
type: 'string',
description: 'Image registry (default: docker.io)'
}
},
strict: true,
@@ -117,8 +152,19 @@ async function main() {
}
const containerTool = getContainerTool();
const versionFilePath = await generateVersionFile();
const { version, isRelease } = readVersionFile(versionFilePath);
let version;
let isRelease;
if (isGitRepo()) {
const versionFilePath = await generateVersionFile();
const versionInfo = readVersionFile(versionFilePath);
version = versionInfo.version;
isRelease = versionInfo.isRelease;
} else {
version = readPackageJsonVersion();
isRelease = true;
writeVersionFile(version, isRelease);
}
let architectures;
if (values['all-arch']) {
@@ -133,14 +179,16 @@ async function main() {
console.log(`Target architectures: ${architectures.join(', ')}`);
// Build container image
const registry = values.registry || process.env.JMESPATH_REGISTRY || 'docker.io';
const imageName = `${registry.replace(/\/$/, '')}/skoszewski/jmespath-playground`;
const tags = isRelease
? [
`-t skoszewski/jmespath-playground:${version}`,
`-t skoszewski/jmespath-playground:latest`
`-t ${imageName}:${version}`,
`-t ${imageName}:latest`
].join(' ')
: [
`-t skoszewski/jmespath-playground:dev`,
`-t skoszewski/jmespath-playground:latest`
`-t ${imageName}:dev`,
`-t ${imageName}:latest`
].join(' ');
const archFlags = architectures.map(arch => `--arch ${arch}`).join(' ');
@@ -160,15 +208,15 @@ async function main() {
if (isRelease) {
console.log(`\nTo run the container:`);
console.log(` ${containerTool} run --arch arm64 --name jmespathpg -p 3000:3000 skoszewski/jmespath-playground:${version}`);
console.log(` ${containerTool} run --arch arm64 --name jmespathpg -p 3000:3000 ${imageName}:${version}`);
if (containerTool === 'docker') {
console.log(`\nTo push to Docker Hub:`);
console.log(` docker push skoszewski/jmespath-playground:${version}`);
console.log(` docker push skoszewski/jmespath-playground:latest`);
console.log(` docker push ${imageName}:${version}`);
console.log(` docker push ${imageName}:latest`);
}
} else {
console.log(`\nTo run the container:`);
console.log(` ${containerTool} run --arch arm64 --name jmespathpg -p 3000:3000 skoszewski/jmespath-playground:dev`);
console.log(` ${containerTool} run --arch arm64 --name jmespathpg -p 3000:3000 ${imageName}:dev`);
}
}

View File

@@ -2,6 +2,24 @@ import { readFileSync, write, writeFileSync } from "fs";
import { execSync } from "child_process";
import semver from "semver";
export function isGitAvailable() {
try {
execSync("git --version", { stdio: "ignore" });
return true;
} catch (e) {
return false;
}
}
export function isGitRepo() {
try {
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
return true;
} catch (e) {
return false;
}
}
export function getGitVersion() {
let rawGitVersion;
let gitVersion;
@@ -28,26 +46,38 @@ export function getGitVersion() {
}
export function generateVersionFile(versionFilePath) {
if (!isGitAvailable()) {
throw new Error("Git is required to generate version info.");
}
// Read package.json version
const packageVersion = JSON.parse(
readFileSync("package.json", { encoding: "utf-8" }),
).version;
// Get version from git repository
const gitVersion = getGitVersion();
const gitBaseVersion = semver.coerce(gitVersion)?.version;
let gitVersion = packageVersion;
let gitBaseVersion = packageVersion;
let isRelease = true;
// if git returned malformed version, throw error
if (!gitBaseVersion || gitBaseVersion === "0.0.0") {
throw new Error(
"Cannot determine git version. Make sure the script is run in a git repository with tags.",
);
}
if (isGitRepo()) {
// Get version from git repository
gitVersion = getGitVersion();
gitBaseVersion = semver.coerce(gitVersion)?.version;
// Compare git version with package.json version
if (semver.neq(gitBaseVersion, packageVersion)) {
throw new Error(
`Version mismatch: package.json version is ${packageVersion}, but git version is ${gitBaseVersion}`,
);
// if git returned malformed version, throw error
if (!gitBaseVersion || gitBaseVersion === "0.0.0") {
throw new Error(
"Cannot determine git version. Make sure the script is run in a git repository with tags.",
);
}
// Compare git version with package.json version
if (semver.neq(gitBaseVersion, packageVersion)) {
throw new Error(
`Version mismatch: package.json version is ${packageVersion}, but git version is ${gitBaseVersion}`,
);
}
isRelease = gitVersion === packageVersion;
}
// Generate version file
@@ -58,7 +88,7 @@ export function generateVersionFile(versionFilePath) {
// Generated at: ${buildDate}
export const VERSION = '${packageVersion}';
export const IS_RELEASE = ${gitVersion === packageVersion};
export const IS_RELEASE = ${isRelease};
export const BUILD_TIME = '${buildDate}';
`,
);

7
terraform/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
.terraform.lock.hcl.backup
*tfplan

41
terraform/main.tf Normal file
View File

@@ -0,0 +1,41 @@
# Create Cloud Run service
resource "google_cloud_run_v2_service" "jppg" {
name = "jmespath-playground"
location = var.default_region
invoker_iam_disabled = true
template {
containers {
image = "skoszewski/jmespath-playground:${var.image_version}"
name = "jmespath-playground-1"
ports {
container_port = 3000
}
}
}
scaling {
max_instance_count = 1
}
deletion_protection = var.deletion_protection
lifecycle {
ignore_changes = [
client
]
}
}
resource "google_cloud_run_domain_mapping" "jppg" {
name = "jmespath-playground.gcp-lab.koszewscy.waw.pl"
location = var.default_region
spec {
route_name = google_cloud_run_v2_service.jppg.name
}
metadata {
namespace = var.project_id
}
}

13
terraform/providers.tf Normal file
View File

@@ -0,0 +1,13 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~>7.17.0"
}
}
}
provider "google" {
project = var.project_id
region = var.default_region
}

View File

@@ -0,0 +1,5 @@
{
"project_id": "dom-lab",
"image_version": "1.4.3",
"deletion_protection": true
}

21
terraform/variables.tf Normal file
View File

@@ -0,0 +1,21 @@
variable "project_id" {
description = "Google project id"
type = string
}
variable "default_region" {
description = "Default Google Cloud region"
type = string
default = "europe-west1" # Belgium
}
variable "image_version" {
description = "Version of the Docker image"
type = string
}
variable "deletion_protection" {
type = bool
description = "Protect resources from deletion using terraform destroy and apply."
default = true
}