Compare commits

..

10 Commits

Author SHA1 Message Date
Muhammad Atif Ali
b88d50a7c9 Merge branch 'main' into atif/multi-gateway 2024-11-27 13:21:55 +05:00
Muhammad Atif Ali
cd6aa274f1 fix tests 2024-11-26 14:08:24 +05:00
Muhammad Atif Ali
2f51d70fb7 always use latest and update default versions to 2024.3 2024-11-26 13:50:55 +05:00
Muhammad Atif Ali
dbf3c47f45 Merge branch 'main' into atif/multi-gateway 2024-11-17 00:00:33 +05:00
Muhammad Atif Ali
d45f2e6ad1 Update JetBrains Gateway module to v1.0.24 2024-11-14 20:06:39 +05:00
Muhammad Atif Ali
70020d8b8c Support multiple default IDEs in JetBrains Gateway 2024-11-14 19:54:58 +05:00
Muhammad Atif Ali
937ffcd47b Update slug format for JetBrains Gateway apps
This change improves URL uniqueness by appending a lowercase IDE
identifier to the slug, ensuring distinct slugs for each default IDE.
2024-11-14 19:50:09 +05:00
Muhammad Atif Ali
5bc2aa4aa0 Fix JetBrains Gateway tests for multiple IDEs
- Allow creation of links with multiple IDEs.
- Ensure outputs handle arrays for identifying multiple IDEs.
- Update runTerraformApply to handle array values as JSON strings.
2024-11-14 19:36:36 +05:00
Muhammad Atif Ali
4452630a7e Support multiple default IDEs in JetBrains Gateway 2024-11-14 18:41:40 +05:00
Muhammad Atif Ali
27e3faf31c feat: enable multiple IDE buttons in JetBrains
Add support for specifying a list of default IDEs to be displayed on
the Workspace page. This allows users to see multiple IDE options
simultaneously. Ensure no duplicates are included and validate
provided IDE codes against allowed set. Adjust logic to dynamically
render IDE buttons based on specified defaults, improving flexibility
in user interface setup.
2024-11-14 11:31:27 +05:00
8 changed files with 136 additions and 292 deletions

View File

@@ -48,7 +48,7 @@ update_component_status() {
# Function to create an incident # Function to create an incident
create_incident() { create_incident() {
local incident_name="Degraded Service" local incident_name="Testing Instatus"
local message="The following modules are experiencing issues:\n" local message="The following modules are experiencing issues:\n"
for i in "${!failures[@]}"; do for i in "${!failures[@]}"; do
message+="$((i + 1)). ${failures[$i]}\n" message+="$((i + 1)). ${failures[$i]}\n"
@@ -59,7 +59,7 @@ create_incident() {
component_status="MAJOROUTAGE" component_status="MAJOROUTAGE"
fi fi
# see https://instatus.com/help/api/incidents # see https://instatus.com/help/api/incidents
incident_id=$(curl -s -X POST "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \ response=$(curl -s -X POST "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
-H "Authorization: Bearer $INSTATUS_API_KEY" \ -H "Authorization: Bearer $INSTATUS_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{ -d "{
@@ -74,25 +74,10 @@ create_incident() {
\"status\": \"PARTIALOUTAGE\" \"status\": \"PARTIALOUTAGE\"
} }
] ]
}" | jq -r '.id') }")
echo "Created incident with ID: $incident_id" incident_id=$(echo "$response" | jq -r '.id')
} echo "$incident_id"
# Function to check for existing unresolved incidents
check_existing_incident() {
# Fetch the latest incidents with status not equal to "RESOLVED"
local unresolved_incidents=$(curl -s -X GET "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
-H "Authorization: Bearer $INSTATUS_API_KEY" \
-H "Content-Type: application/json" | jq -r '.incidents[] | select(.status != "RESOLVED") | .id')
if [[ -n "$unresolved_incidents" ]]; then
echo "Unresolved incidents found: $unresolved_incidents"
return 0 # Indicate that there are unresolved incidents
else
echo "No unresolved incidents found."
return 1 # Indicate that no unresolved incidents exist
fi
} }
force_redeploy_registry () { force_redeploy_registry () {
@@ -189,10 +174,9 @@ else
update_component_status "PARTIALOUTAGE" update_component_status "PARTIALOUTAGE"
fi fi
# Check if there is an existing incident before creating a new one # Create a new incident
if ! check_existing_incident; then incident_id=$(create_incident)
create_incident echo "Created incident with ID: $incident_id"
fi
# If a module is down, force a reployment to try getting things back online # If a module is down, force a reployment to try getting things back online
# ASAP # ASAP

View File

@@ -14,12 +14,12 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"] jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
default = "GO" default = ["GO"]
} }
``` ```
@@ -32,52 +32,27 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
default = "GO" default = ["GO"]
} }
``` ```
### Use the latest version of each IDE ### Use the fixed version
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["IU", "PY"] jetbrains_ides = ["GO", "WS"]
default = "IU" default = ["GO"]
latest = true latest = false # current version is 2024.3
}
```
### Use fixed versions set by `jetbrains_ide_versions`
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["IU", "PY"]
default = "IU"
latest = false
jetbrains_ide_versions = {
"IU" = {
build_number = "243.21565.193"
version = "2024.3"
}
"PY" = {
build_number = "243.21565.199"
version = "2024.3"
}
}
} }
``` ```
@@ -86,12 +61,12 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
default = "GO" default = ["GO"]
latest = true latest = true
channel = "eap" channel = "eap"
} }
@@ -104,14 +79,29 @@ Due to the highest priority of the `ide_download_link` parameter in the `(jetbra
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.25" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
releases_base_link = "https://releases.internal.site/" releases_base_link = "https://releases.internal.site/"
download_base_link = "https://download.internal.site/" download_base_link = "https://download.internal.site/"
default = "GO" default = ["GO"]
}
```
### Add multiple IDEs
**Note:** This removes the choice of IDE from the user.
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
default = ["GO", "WS"]
} }
``` ```

View File

@@ -16,14 +16,13 @@ describe("jetbrains-gateway", async () => {
it("should create a link with the default values", async () => { it("should create a link with the default values", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
// These are all required.
agent_id: "foo", agent_id: "foo",
agent_name: "foo", agent_name: "foo",
folder: "/home/coder", folder: "/home/coder",
}); });
expect(state.outputs.url.value).toBe( expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz", "jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
); ]);
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "gateway", (res) => res.type === "coder_app" && res.name === "gateway",
@@ -34,13 +33,31 @@ describe("jetbrains-gateway", async () => {
expect(coder_app?.instances[0].attributes.order).toBeNull(); expect(coder_app?.instances[0].attributes.order).toBeNull();
}); });
it("default to first ide", async () => { it("default to first IDE", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "foo", agent_name: "foo",
folder: "/home/foo", folder: "/home/foo",
jetbrains_ides: '["IU", "GO", "PY"]', jetbrains_ides: ["IU", "PY"],
}); });
expect(state.outputs.identifier.value).toBe("IU"); expect(state.outputs.identifier.value).toEqual(["IU"]);
expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
]);
});
it("should create multiple IDEs", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
agent_name: "foo",
folder: "/home/foo",
default: ["GO", "IU", "PY"],
});
expect(state.outputs.identifier.value).toEqual(["GO", "IU", "PY"]);
expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=GO&ide_build_number=243.21565.208&ide_download_link=https://download.jetbrains.com/go/goland-2024.3.tar.gz",
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=PY&ide_build_number=243.21565.199&ide_download_link=https://download.jetbrains.com/python/pycharm-professional-2024.3.tar.gz",
]);
}); });
}); });

View File

@@ -39,9 +39,23 @@ variable "folder" {
} }
variable "default" { variable "default" {
default = "" default = []
type = string type = list(string)
description = "Default IDE" description = "List of default IDEs to be added to the Workspace page."
# check if the list is unique
validation {
condition = length(var.default) == length(toset(var.default))
error_message = "The default must not contain duplicates."
}
# check if default are valid jetbrains_ides
validation {
condition = (
alltrue([
for code in var.default : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], code)
])
)
error_message = "The default must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
}
} }
variable "order" { variable "order" {
@@ -59,7 +73,7 @@ variable "coder_parameter_order" {
variable "latest" { variable "latest" {
type = bool type = bool
description = "Whether to fetch the latest version of the IDE." description = "Whether to fetch the latest version of the IDE."
default = false default = true
} }
variable "channel" { variable "channel" {
@@ -124,7 +138,7 @@ variable "jetbrains_ide_versions" {
variable "jetbrains_ides" { variable "jetbrains_ides" {
type = list(string) type = list(string)
description = "The list of IDE product codes." description = "The list of IDE product codes to be shown to the user. Does not apply when there are multiple defaults."
default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"] default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"]
validation { validation {
condition = ( condition = (
@@ -239,23 +253,42 @@ locals {
} }
} }
icon = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].icon identifier = try([data.coder_parameter.jetbrains_ide[0].value], var.default)
json_data = var.latest ? jsondecode(data.http.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].response_body) : {} list_json_data = var.latest ? [
key = var.latest ? keys(local.json_data)[0] : "" for ide in local.identifier : jsondecode(data.http.jetbrains_ide_versions[ide].response_body)
display_name = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].name ] : []
identifier = data.coder_parameter.jetbrains_ide.value list_key = var.latest ? [
download_link = var.latest ? local.json_data[local.key][0].downloads.linux.link : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link for j in local.list_json_data : keys(j)[0]
build_number = var.latest ? local.json_data[local.key][0].build : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number ] : []
version = var.latest ? local.json_data[local.key][0].version : var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version download_links = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].downloads.linux.link
] : [
for ide in local.identifier : local.jetbrains_ides[ide].download_link
]
build_numbers = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].build
] : [
for ide in local.identifier : local.jetbrains_ides[ide].build_number
]
versions = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].version
] : [
for ide in local.identifier : local.jetbrains_ides[ide].version
]
display_names = [for key in keys(coder_app.gateway) : coder_app.gateway[key].display_name]
icons = [for key in keys(coder_app.gateway) : coder_app.gateway[key].icon]
urls = [for key in keys(coder_app.gateway) : coder_app.gateway[key].url]
} }
data "coder_parameter" "jetbrains_ide" { data "coder_parameter" "jetbrains_ide" {
# remove the coder_parameter if there are multiple default
count = length(var.default) > 1 ? 0 : 1
type = "string" type = "string"
name = "jetbrains_ide" name = "jetbrains_ide"
display_name = "JetBrains IDE" display_name = "JetBrains IDE"
icon = "/icon/gateway.svg" icon = "/icon/gateway.svg"
mutable = true mutable = true
default = var.default == "" ? var.jetbrains_ides[0] : var.default default = length(var.default) > 0 ? var.default[0] : var.jetbrains_ides[0]
order = var.coder_parameter_order order = var.coder_parameter_order
dynamic "option" { dynamic "option" {
@@ -272,10 +305,11 @@ data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {} data "coder_workspace_owner" "me" {}
resource "coder_app" "gateway" { resource "coder_app" "gateway" {
for_each = length(var.default) > 1 ? toset(var.default) : toset([data.coder_parameter.jetbrains_ide[0].value])
agent_id = var.agent_id agent_id = var.agent_id
slug = var.slug slug = "${var.slug}-${lower(each.value)}"
display_name = local.display_name display_name = local.jetbrains_ides[each.value].name
icon = local.icon icon = local.jetbrains_ides[each.value].icon
external = true external = true
order = var.order order = var.order
url = join("", [ url = join("", [
@@ -292,38 +326,45 @@ resource "coder_app" "gateway" {
"&token=", "&token=",
"$SESSION_TOKEN", "$SESSION_TOKEN",
"&ide_product_code=", "&ide_product_code=",
data.coder_parameter.jetbrains_ide.value, each.value,
"&ide_build_number=", "&ide_build_number=",
local.build_number, local.jetbrains_ides[each.value].build_number,
"&ide_download_link=", "&ide_download_link=",
local.download_link, local.jetbrains_ides[each.value].download_link,
]) ])
} }
output "identifier" { output "identifier" {
value = local.identifier value = local.identifier
description = "The product code of the JetBrains IDE."
} }
output "display_name" { output "display_name" {
value = local.display_name value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].display_name]
description = "The display name of the JetBrains IDE."
} }
output "icon" { output "icon" {
value = local.icon value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].icon]
description = "The icon of the JetBrains IDE."
} }
output "download_link" { output "download_link" {
value = local.download_link value = local.download_links
description = "The download link of the JetBrains IDE."
} }
output "build_number" { output "build_number" {
value = local.build_number value = local.build_numbers
description = "The build number of the JetBrains IDE."
} }
output "version" { output "version" {
value = local.version value = local.versions
description = "The version of the JetBrains IDE."
} }
output "url" { output "url" {
value = coder_app.gateway.url value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].url]
description = "The URL to connect to the JetBrains IDE."
} }

View File

@@ -1,72 +0,0 @@
---
display_name: Monitoring
description: Monitoring of workspace resources
maintainer_github: coder
verified: true
tags: [monitoring]
---
# Monitoring
This module adds monitoring of workspace resources.
```tf
module "monitoring" {
source = "registry.coder.com/modules/monitoring/coder"
version = "1.0.0"
agent_id = coder_agent.dev.id
}
```
## Examples
```tf
module "monitoring" {
source = "registry.coder.com/modules/monitoring/coder"
version = "1.0.0"
agent_id = coder_agent.dev.id
}
```
### Enable/Disable
You can customize the monitoring by setting the `enabled`, `memory_enabled`, and `disk_enabled` variables.
```tf
module "monitoring" {
source = "registry.coder.com/modules/monitoring/coder"
version = "1.0.0"
agent_id = coder_agent.dev.id
enabled = false
memory_enabled = true
disk_enabled = false
}
```
### Customize Thresholds
You can customize the thresholds by setting the `threshold`, `memory_threshold`, and `disk_threshold` variables.
```tf
module "monitoring" {
source = "registry.coder.com/modules/monitoring/coder"
version = "1.0.0"
agent_id = coder_agent.dev.id
threshold = 90
memory_threshold = 95
disk_threshold = 90
}
```
### Customize Disks
You can customize the disks by setting the `disks` variable.
```tf
module "monitoring" {
source = "registry.coder.com/modules/monitoring/coder"
version = "1.0.0"
agent_id = coder_agent.dev.id
disks = ["/"]
}
```

View File

@@ -1,92 +0,0 @@
terraform {
required_version = ">= 1.0.25"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.0.2"
}
}
}
variable "threshold" {
type = number
description = "The threshold for the monitoring, used for all resources unless overridden by *_threshold - expressed as a percentage."
default = 90
validation {
condition = var.threshold >= 0 && var.threshold <= 100
error_message = "The threshold must be between 0 and 100."
}
}
variable "memory_threshold" {
type = number
description = "The threshold for the memory monitoring - expressed as a percentage."
default = 90
validation {
condition = var.memory_threshold >= 0 && var.memory_threshold <= 100
error_message = "The memory_threshold must be between 0 and 100."
}
}
variable "disk_threshold" {
type = number
description = "The threshold for the disk monitoring - expressed as a percentage."
default = 90
validation {
condition = var.disk_threshold >= 0 && var.disk_threshold <= 100
error_message = "The disk_threshold must be between 0 and 100."
}
}
variable "disks" {
type = list(string)
description = "The disks to monitor. e.g. ['/', '/home']"
default = ["/"]
}
variable "enabled" {
type = bool
description = "Whether the monitoring is enabled."
default = true
validation {
condition = var.enabled == true || var.enabled == false
error_message = "The enabled must be true or false."
}
}
variable "memory_enabled" {
type = bool
description = "Whether the memory monitoring is enabled."
default = true
validation {
condition = var.memory_enabled == true || var.memory_enabled == false
error_message = "The memory_enabled must be true or false."
}
}
variable "disk_enabled" {
type = bool
description = "Whether the disk monitoring is enabled."
default = true
validation {
condition = var.disk_enabled == true || var.disk_enabled == false
error_message = "The disk_enabled must be true or false."
}
}
variable "agent_id" {
type = string
description = "The ID of the agent to monitor."
}
data "coder_monitoring" "monitoring" {
threshold = var.threshold
memory_threshold = var.memory_threshold
disk_threshold = var.disk_threshold
disks = var.disks
enabled = var.enabled
memory_enabled = var.memory_enabled
disk_enabled = var.disk_enabled
agent_id = var.agent_id
}

View File

@@ -200,7 +200,8 @@ export const runTerraformApply = async <TVars extends TerraformVariables>(
const combinedEnv = env === undefined ? {} : { ...env }; const combinedEnv = env === undefined ? {} : { ...env };
for (const [key, value] of Object.entries(vars)) { for (const [key, value] of Object.entries(vars)) {
combinedEnv[`TF_VAR_${key}`] = String(value); // Convert arrays to JSON strings
combinedEnv[`TF_VAR_${key}`] = Array.isArray(value) ? JSON.stringify(value) : String(value);
} }
const proc = spawn( const proc = spawn(

View File

@@ -21,39 +21,14 @@ for dir in "${changed_dirs[@]}"; do
if [[ -f "$dir/README.md" ]]; then if [[ -f "$dir/README.md" ]]; then
file="$dir/README.md" file="$dir/README.md"
tmpfile=$(mktemp /tmp/tempfile.XXXXXX) tmpfile=$(mktemp /tmp/tempfile.XXXXXX)
awk -v tag="$LATEST_TAG" ' awk -v tag="$LATEST_TAG" '{
BEGIN { in_code_block = 0; in_nested_block = 0 } if ($1 == "version" && $2 == "=") {
{ sub(/"[^"]*"/, "\"" tag "\"")
# Detect the start and end of Markdown code blocks. print
if ($0 ~ /^```/) { } else {
in_code_block = !in_code_block
# Reset nested block tracking when exiting a code block.
if (!in_code_block) {
in_nested_block = 0
}
}
# Handle nested blocks within a code block.
if (in_code_block) {
# Detect the start of a nested block (skipping "module" blocks).
if ($0 ~ /{/ && !($1 == "module" || $1 ~ /^[a-zA-Z0-9_]+$/)) {
in_nested_block++
}
# Detect the end of a nested block.
if ($0 ~ /}/ && in_nested_block > 0) {
in_nested_block--
}
# Update "version" only if not in a nested block.
if (!in_nested_block && $1 == "version" && $2 == "=") {
sub(/"[^"]*"/, "\"" tag "\"")
}
}
print print
} }
' "$file" > "$tmpfile" && mv "$tmpfile" "$file" }' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
# Check if the README.md file has changed # Check if the README.md file has changed
if ! git diff --quiet -- "$dir/README.md"; then if ! git diff --quiet -- "$dir/README.md"; then