Created
October 9, 2024 05:00
-
-
Save kakashy/34a8c522f96ee0102baa3acc8f809330 to your computer and use it in GitHub Desktop.
This Go script streamlines the process of Dockerizing SvelteKit projects by automating the creation of necessary Docker configuration files. By detecting environment variables and incorporating them as build arguments, it ensures that the Docker build process has access to all required configurations without compromising security.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"encoding/json" | |
"fmt" | |
"os" | |
"path/filepath" | |
"regexp" | |
"strconv" | |
"strings" | |
) | |
// PackageJSON represents the structure of package.json | |
type PackageJSON struct { | |
Scripts map[string]string `json:"scripts"` | |
} | |
// SvelteKitProject holds information about each SvelteKit project | |
type SvelteKitProject struct { | |
Path string | |
PreviewPort string | |
EnvVars map[string]string | |
} | |
// DefaultPort is used if no port is specified in the preview script | |
const DefaultPort = "3000" | |
func main() { | |
currentDir, err := os.Getwd() | |
if err != nil { | |
fmt.Printf("Error getting current directory: %v\n", err) | |
return | |
} | |
// Find all SvelteKit projects (current dir and one level deep) | |
projects, err := findSvelteKitProjects(currentDir) | |
if err != nil { | |
fmt.Printf("Error finding SvelteKit projects: %v\n", err) | |
return | |
} | |
if len(projects) == 0 { | |
fmt.Println("No SvelteKit repositories found.") | |
return | |
} | |
for _, project := range projects { | |
fmt.Printf("SvelteKit repository detected: %s\n", project.Path) | |
// Create .dockerignore | |
err := createDockerignore(project.Path) | |
if err != nil { | |
fmt.Printf("Error creating .dockerignore in %s: %v\n", project.Path, err) | |
continue | |
} | |
fmt.Printf(".dockerignore created successfully in %s.\n", project.Path) | |
// Create Dockerfile | |
err = createDockerfile(project) | |
if err != nil { | |
fmt.Printf("Error creating Dockerfile in %s: %v\n", project.Path, err) | |
continue | |
} | |
fmt.Printf("Dockerfile created successfully in %s.\n", project.Path) | |
} | |
// Optionally, create a docker-compose.yml if multiple projects are found | |
if len(projects) > 1 { | |
err = createDockerCompose(projects, currentDir) | |
if err != nil { | |
fmt.Printf("Error creating docker-compose.yml: %v\n", err) | |
return | |
} | |
fmt.Println("docker-compose.yml created successfully.") | |
} | |
// Generate build.sh script | |
if len(projects) > 0 { | |
err = generateBuildScript(projects, currentDir) | |
if err != nil { | |
fmt.Printf("Error creating build.sh: %v\n", err) | |
return | |
} | |
fmt.Println("build.sh created successfully. Remember to make it executable with `chmod +x build.sh`.") | |
} | |
} | |
// findSvelteKitProjects searches for SvelteKit projects in the current directory and its immediate subdirectories | |
func findSvelteKitProjects(root string) ([]SvelteKitProject, error) { | |
var projects []SvelteKitProject | |
entries, err := os.ReadDir(root) | |
if err != nil { | |
return nil, err | |
} | |
// Include the root directory itself | |
candidateDirs := []string{root} | |
// Add immediate subdirectories, excluding node_modules and .git | |
for _, entry := range entries { | |
if entry.IsDir() && entry.Name() != "node_modules" && entry.Name() != ".git" { | |
candidateDirs = append(candidateDirs, filepath.Join(root, entry.Name())) | |
} | |
} | |
for _, dir := range candidateDirs { | |
isSvelteKit, previewPort, err := checkSvelteKitRepo(dir) | |
if err != nil { | |
fmt.Printf("Error checking directory %s: %v\n", dir, err) | |
continue | |
} | |
if isSvelteKit { | |
envVars, err := parseEnvFile(dir) | |
if err != nil { | |
fmt.Printf("Error parsing .env in %s: %v\n", dir, err) | |
// Continue without envVars | |
} | |
projects = append(projects, SvelteKitProject{ | |
Path: dir, | |
PreviewPort: previewPort, | |
EnvVars: envVars, | |
}) | |
} | |
} | |
return projects, nil | |
} | |
// checkSvelteKitRepo checks if a directory is a SvelteKit project and extracts the preview port | |
func checkSvelteKitRepo(dir string) (bool, string, error) { | |
svelteConfigPath := filepath.Join(dir, "svelte.config.js") | |
if _, err := os.Stat(svelteConfigPath); os.IsNotExist(err) { | |
return false, DefaultPort, nil | |
} | |
packageJSONPath := filepath.Join(dir, "package.json") | |
data, err := os.ReadFile(packageJSONPath) | |
if err != nil { | |
if os.IsNotExist(err) { | |
return false, DefaultPort, nil | |
} | |
return false, DefaultPort, err | |
} | |
var pkg PackageJSON | |
err = json.Unmarshal(data, &pkg) | |
if err != nil { | |
return false, DefaultPort, err | |
} | |
// Extract port from the "preview" script | |
previewPort := extractPort(pkg.Scripts["preview"]) | |
return true, previewPort, nil | |
} | |
// extractPort parses the preview script to find the port number | |
func extractPort(script string) string { | |
// Default port if not found | |
port := DefaultPort | |
if script == "" { | |
return port | |
} | |
// Regex to match --port=NUMBER or --port NUMBER | |
re := regexp.MustCompile(`--port\s*=?\s*(\d+)`) | |
matches := re.FindStringSubmatch(script) | |
if len(matches) >= 2 { | |
fmt.Printf("Found custom preview port: %s\n", matches[1]) | |
if _, err := strconv.Atoi(matches[1]); err == nil { | |
port = matches[1] | |
} else { | |
fmt.Printf("Invalid port number '%s' in preview script. Using default port %s.\n", matches[1], DefaultPort) | |
} | |
} | |
return port | |
} | |
// parseEnvFile checks for a .env file and parses its contents into a map | |
func parseEnvFile(dir string) (map[string]string, error) { | |
envPath := filepath.Join(dir, ".env") | |
if _, err := os.Stat(envPath); os.IsNotExist(err) { | |
// No .env file found | |
return nil, nil | |
} | |
file, err := os.Open(envPath) | |
if err != nil { | |
return nil, err | |
} | |
defer file.Close() | |
envVars := make(map[string]string) | |
scanner := bufio.NewScanner(file) | |
re := regexp.MustCompile(`^\s*([^#\s][A-Za-z0-9_]+)\s*=\s*(.*)\s*$`) | |
for scanner.Scan() { | |
line := scanner.Text() | |
matches := re.FindStringSubmatch(line) | |
if len(matches) == 3 { | |
key := matches[1] | |
value := matches[2] | |
// Remove surrounding quotes if present | |
value = strings.Trim(value, `"'`) | |
envVars[key] = value | |
} | |
} | |
if err := scanner.Err(); err != nil { | |
return nil, err | |
} | |
if len(envVars) > 0 { | |
fmt.Printf("Found %d environment variables in .env\n", len(envVars)) | |
} | |
return envVars, nil | |
} | |
// createDockerignore generates a .dockerignore file in the specified directory | |
func createDockerignore(dir string) error { | |
dockerignorePath := filepath.Join(dir, ".dockerignore") | |
file, err := os.Create(dockerignorePath) | |
if err != nil { | |
return err | |
} | |
defer file.Close() | |
dockerignoreContent := `node_modules | |
.git | |
*.log | |
*.env | |
.DS_Store | |
` | |
_, err = file.WriteString(dockerignoreContent) | |
return err | |
} | |
// createDockerfile generates a Dockerfile for a SvelteKit project using Bun's official image | |
func createDockerfile(project SvelteKitProject) error { | |
dockerfilePath := filepath.Join(project.Path, "Dockerfile") | |
file, err := os.Create(dockerfilePath) | |
if err != nil { | |
return err | |
} | |
defer file.Close() | |
var buildArgs string | |
var envInstructions string | |
// If there are environment variables, declare them as build args and set as env | |
if len(project.EnvVars) > 0 { | |
for key := range project.EnvVars { | |
// Declare build arg | |
buildArgs += fmt.Sprintf("ARG %s\n", key) | |
// Set env variable | |
envInstructions += fmt.Sprintf("ENV %s=${%s}\n", key, key) | |
} | |
} | |
dockerfileContent := fmt.Sprintf(`# Use Bun's official lightweight image | |
FROM oven/bun:latest | |
# Set working directory | |
WORKDIR /app | |
# Declare build arguments if any | |
%s | |
# Set environment variables from build arguments | |
%s | |
# Copy package files | |
COPY package*.json ./ | |
# Install dependencies using Bun | |
RUN bun install | |
# Copy the rest of the application | |
COPY . . | |
# Build the application | |
RUN bun run build | |
# Expose the port (from package.json or default 3000) | |
EXPOSE %s | |
# Start the application | |
CMD ["bun", "run", "preview"] | |
`, buildArgs, envInstructions, project.PreviewPort) | |
_, err = file.WriteString(dockerfileContent) | |
return err | |
} | |
// createDockerCompose generates a docker-compose.yml for multiple SvelteKit projects | |
func createDockerCompose(projects []SvelteKitProject, root string) error { | |
var services []string | |
for i, project := range projects { | |
serviceName := fmt.Sprintf("service%d", i+1) | |
port := project.PreviewPort | |
relativePath, err := filepath.Rel(root, project.Path) | |
if err != nil { | |
return err | |
} | |
// Prepare environment variables section | |
var envSection string | |
if len(project.EnvVars) > 0 { | |
envSection = " environment:\n" | |
for key := range project.EnvVars { | |
envSection += fmt.Sprintf(" - %s=${%s}\n", key, key) | |
} | |
} | |
// Define the service | |
service := fmt.Sprintf(` | |
%s: | |
build: | |
context: ./%s | |
dockerfile: Dockerfile | |
ports: | |
- "%s:%s" | |
restart: unless-stopped | |
%s | |
`, serviceName, relativePath, port, port, envSection) | |
services = append(services, service) | |
} | |
dockerComposeContent := `version: '3' | |
services:` + strings.Join(services, "\n") | |
dockerComposePath := filepath.Join(root, "docker-compose.yml") | |
return os.WriteFile(dockerComposePath, []byte(dockerComposeContent), 0644) | |
} | |
// generateBuildScript creates a build.sh script that passes environment variables as build args | |
func generateBuildScript(projects []SvelteKitProject, root string) error { | |
var lines []string | |
lines = append(lines, "#!/bin/bash\n") | |
lines = append(lines, "set -e\n\n") | |
for _, project := range projects { | |
projectName := filepath.Base(project.Path) | |
lines = append(lines, fmt.Sprintf("echo 'Building %s...'\n", projectName)) | |
lines = append(lines, fmt.Sprintf("docker build \\\n")) | |
for key, value := range project.EnvVars { | |
lines = append(lines, fmt.Sprintf(" --build-arg %s=\"%s\" \\\n", key, escapeShellArg(value))) | |
} | |
lines = append(lines, fmt.Sprintf(" -t %s:latest %s\n\n", projectName, filepath.Base(project.Path))) | |
} | |
// Write to build.sh | |
buildScriptPath := filepath.Join(root, "build.sh") | |
file, err := os.Create(buildScriptPath) | |
if err != nil { | |
return err | |
} | |
defer file.Close() | |
writer := bufio.NewWriter(file) | |
for _, line := range lines { | |
_, err := writer.WriteString(line) | |
if err != nil { | |
return err | |
} | |
} | |
return writer.Flush() | |
} | |
// escapeShellArg escapes double quotes in shell arguments | |
func escapeShellArg(arg string) string { | |
return strings.ReplaceAll(arg, `"`, `\"`) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment