From 79cef2ecfce6c53c7c71ca21616c406847a11d46 Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:07:52 +0000 Subject: [PATCH] feat(nodejs): add pre and post install scripts with coder exp sync support Add pre_install_script and post_install_script variables to the nodejs module following the pattern used by other registry modules (agent-helper, claude-code, aider, etc.). Scripts use coder exp sync for reliable execution ordering, enabling dependency coordination between modules. Changes: - Add pre_install_script and post_install_script optional variables - Wrap install script with coder exp sync want/start/complete - Add conditional pre/post install coder_script resources - Export sync script names as outputs for cross-module coordination - Add nodejs.tftest.hcl with 5 test cases - Update README with pre/post install documentation and examples - Bump version references to 1.0.14 --- registry/thezoker/modules/nodejs/README.md | 34 ++++- registry/thezoker/modules/nodejs/main.test.ts | 20 ++- registry/thezoker/modules/nodejs/main.tf | 115 +++++++++++++++-- .../thezoker/modules/nodejs/nodejs.tftest.hcl | 122 ++++++++++++++++++ 4 files changed, 273 insertions(+), 18 deletions(-) create mode 100644 registry/thezoker/modules/nodejs/nodejs.tftest.hcl diff --git a/registry/thezoker/modules/nodejs/README.md b/registry/thezoker/modules/nodejs/README.md index d7293aac..477b1334 100644 --- a/registry/thezoker/modules/nodejs/README.md +++ b/registry/thezoker/modules/nodejs/README.md @@ -15,7 +15,7 @@ Automatically installs [Node.js](https://github.com/nodejs/node) via [`nvm`](htt module "nodejs" { count = data.coder_workspace.me.start_count source = "registry.coder.com/thezoker/nodejs/coder" - version = "1.0.13" + version = "1.0.14" agent_id = coder_agent.example.id } ``` @@ -28,17 +28,35 @@ This installs multiple versions of Node.js: module "nodejs" { count = data.coder_workspace.me.start_count source = "registry.coder.com/thezoker/nodejs/coder" - version = "1.0.13" + version = "1.0.14" agent_id = coder_agent.example.id node_versions = [ "18", "20", "node" ] - default_node_version = "1.0.13" + default_node_version = "20" } ``` +## Pre and Post Install Scripts + +Use `pre_install_script` and `post_install_script` to run custom scripts before and after Node.js installation. These use `coder exp sync` for reliable script ordering, making them useful for dependency coordination between modules. + +```tf +module "nodejs" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/thezoker/nodejs/coder" + version = "1.0.14" + agent_id = coder_agent.example.id + + pre_install_script = "echo 'Setting up prerequisites...'" + post_install_script = "npm install -g yarn pnpm" +} +``` + +The module exports sync script names (`pre_install_script_name`, `install_script_name`, `post_install_script_name`) that other modules can use with `coder exp sync want` to coordinate execution order. + ## Full example A example with all available options: @@ -47,15 +65,17 @@ A example with all available options: module "nodejs" { count = data.coder_workspace.me.start_count source = "registry.coder.com/thezoker/nodejs/coder" - version = "1.0.13" + version = "1.0.14" agent_id = coder_agent.example.id - nvm_version = "1.0.13" + nvm_version = "v0.39.7" nvm_install_prefix = "/opt/nvm" node_versions = [ - "16", "18", + "20", "node" ] - default_node_version = "1.0.13" + default_node_version = "20" + pre_install_script = "echo 'Pre-install setup'" + post_install_script = "npm install -g typescript" } ``` diff --git a/registry/thezoker/modules/nodejs/main.test.ts b/registry/thezoker/modules/nodejs/main.test.ts index 14f8a066..c0cb4032 100644 --- a/registry/thezoker/modules/nodejs/main.test.ts +++ b/registry/thezoker/modules/nodejs/main.test.ts @@ -1,5 +1,5 @@ -import { describe } from "bun:test"; -import { runTerraformInit, testRequiredVariables } from "~test"; +import { describe, expect, it } from "bun:test"; +import { runTerraformInit, testRequiredVariables, runTerraformApply } from "~test"; describe("nodejs", async () => { await runTerraformInit(import.meta.dir); @@ -8,5 +8,19 @@ describe("nodejs", async () => { agent_id: "foo", }); - // More tests depend on shebang refactors + it("accepts pre_install_script and post_install_script", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + pre_install_script: "echo pre", + post_install_script: "echo post", + }); + expect(state).toBeDefined(); + }); + + it("works without pre/post install scripts", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + expect(state).toBeDefined(); + }); }); diff --git a/registry/thezoker/modules/nodejs/main.tf b/registry/thezoker/modules/nodejs/main.tf index 9c9c5c76..b4b00ab5 100644 --- a/registry/thezoker/modules/nodejs/main.tf +++ b/registry/thezoker/modules/nodejs/main.tf @@ -38,15 +38,114 @@ variable "default_node_version" { default = "node" } +variable "pre_install_script" { + type = string + description = "Custom script to run before installing Node.js. Can be used for dependency ordering between modules." + default = null +} + +variable "post_install_script" { + type = string + description = "Custom script to run after installing Node.js." + default = null +} + +locals { + encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" + encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : "" + + module_dir_path = "$HOME/.nodejs-module" + + pre_install_script_name = "nodejs-pre_install_script" + install_script_name = "nodejs-install_script" + post_install_script_name = "nodejs-post_install_script" +} + +resource "coder_script" "nodejs_pre_install" { + count = var.pre_install_script != null ? 1 : 0 + agent_id = var.agent_id + display_name = "Node.js: Pre-Install" + run_on_start = true + script = <<-EOT + #!/bin/bash + set -o errexit + set -o pipefail + + mkdir -p ${local.module_dir_path} + + trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT + coder exp sync start ${local.pre_install_script_name} + + echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.module_dir_path}/pre_install.sh + chmod +x ${local.module_dir_path}/pre_install.sh + + ${local.module_dir_path}/pre_install.sh 2>&1 + EOT +} + resource "coder_script" "nodejs" { agent_id = var.agent_id - display_name = "Node.js:" - script = templatefile("${path.module}/run.sh", { - NVM_VERSION : var.nvm_version, - INSTALL_PREFIX : var.nvm_install_prefix, - NODE_VERSIONS : join(",", var.node_versions), - DEFAULT : var.default_node_version, - }) - run_on_start = true + display_name = "Node.js: Install" + run_on_start = true + script = <<-EOT + #!/bin/bash + set -o errexit + set -o pipefail + + mkdir -p ${local.module_dir_path} + + trap 'coder exp sync complete ${local.install_script_name}' EXIT + %{if var.pre_install_script != null~} + coder exp sync want ${local.install_script_name} ${local.pre_install_script_name} + %{endif~} + coder exp sync start ${local.install_script_name} + + echo -n '${base64encode(templatefile("${path.module}/run.sh", { + NVM_VERSION = var.nvm_version, + INSTALL_PREFIX = var.nvm_install_prefix, + NODE_VERSIONS = join(",", var.node_versions), + DEFAULT = var.default_node_version, +}))}' | base64 -d > ${local.module_dir_path}/install.sh + chmod +x ${local.module_dir_path}/install.sh + + ${local.module_dir_path}/install.sh 2>&1 + EOT + start_blocks_login = true } + +resource "coder_script" "nodejs_post_install" { + count = var.post_install_script != null ? 1 : 0 + agent_id = var.agent_id + display_name = "Node.js: Post-Install" + run_on_start = true + script = <<-EOT + #!/bin/bash + set -o errexit + set -o pipefail + + trap 'coder exp sync complete ${local.post_install_script_name}' EXIT + coder exp sync want ${local.post_install_script_name} ${local.install_script_name} + coder exp sync start ${local.post_install_script_name} + + echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.module_dir_path}/post_install.sh + chmod +x ${local.module_dir_path}/post_install.sh + + ${local.module_dir_path}/post_install.sh 2>&1 + EOT +} + +output "pre_install_script_name" { + description = "The name of the pre-install script for sync." + value = local.pre_install_script_name +} + +output "install_script_name" { + description = "The name of the install script for sync." + value = local.install_script_name +} + +output "post_install_script_name" { + description = "The name of the post-install script for sync." + value = local.post_install_script_name +} diff --git a/registry/thezoker/modules/nodejs/nodejs.tftest.hcl b/registry/thezoker/modules/nodejs/nodejs.tftest.hcl new file mode 100644 index 00000000..e8024e56 --- /dev/null +++ b/registry/thezoker/modules/nodejs/nodejs.tftest.hcl @@ -0,0 +1,122 @@ +run "test_nodejs_basic" { + command = plan + + variables { + agent_id = "test-agent-123" + } + + assert { + condition = var.agent_id == "test-agent-123" + error_message = "Agent ID variable should be set correctly" + } + + assert { + condition = var.nvm_version == "master" + error_message = "nvm_version should default to master" + } + + assert { + condition = var.default_node_version == "node" + error_message = "default_node_version should default to node" + } + + assert { + condition = var.pre_install_script == null + error_message = "pre_install_script should default to null" + } + + assert { + condition = var.post_install_script == null + error_message = "post_install_script should default to null" + } +} + +run "test_with_scripts" { + command = plan + + variables { + agent_id = "test-agent-scripts" + pre_install_script = "echo 'Pre-install script'" + post_install_script = "echo 'Post-install script'" + } + + assert { + condition = var.pre_install_script == "echo 'Pre-install script'" + error_message = "Pre-install script should be set correctly" + } + + assert { + condition = var.post_install_script == "echo 'Post-install script'" + error_message = "Post-install script should be set correctly" + } +} + +run "test_custom_options" { + command = plan + + variables { + agent_id = "test-agent-custom" + nvm_version = "v0.39.7" + nvm_install_prefix = ".custom-nvm" + node_versions = ["18", "20", "node"] + default_node_version = "20" + } + + assert { + condition = var.nvm_version == "v0.39.7" + error_message = "nvm_version should be set to v0.39.7" + } + + assert { + condition = var.nvm_install_prefix == ".custom-nvm" + error_message = "nvm_install_prefix should be set correctly" + } + + assert { + condition = length(var.node_versions) == 3 + error_message = "node_versions should have 3 entries" + } + + assert { + condition = var.default_node_version == "20" + error_message = "default_node_version should be set to 20" + } +} + +run "test_with_pre_install_only" { + command = plan + + variables { + agent_id = "test-agent-pre" + pre_install_script = "echo 'pre-install'" + } + + assert { + condition = var.pre_install_script != null + error_message = "Pre-install script should be set" + } + + assert { + condition = var.post_install_script == null + error_message = "Post-install script should default to null" + } +} + +run "test_with_post_install_only" { + command = plan + + variables { + agent_id = "test-agent-post" + post_install_script = "echo 'post-install'" + } + + assert { + condition = var.pre_install_script == null + error_message = "Pre-install script should default to null" + } + + assert { + condition = var.post_install_script != null + error_message = "Post-install script should be set" + } +}