Wednesday, November 5, 2025

Complete guide with code samples showing JSONC in JavaScriptNode.js and Python

 Here’s a complete guide with code samples showing how to parse JSONC (JSON withComments) in JavaScript/Node.jsPythonand Go, along with notes on available tooling support in each ecosystem.

๐Ÿงฉ What is JSONC?

JSONC (JSON with Comments) is an extension of JSON (JavaScript Object Notation) that allows comments (using // or /* */), making it more human-readable — useful for configuration files.
However, standard JSON parsers do not accept comments, so special handling is needed.

๐Ÿš€ 1. JavaScript / Node.js

✅ Tooling Support:

  • jsonc-parser (from Microsoft) — widely used, supports parsing and formatting.
  • VS Code uses jsonc-parser internally for .jsonc files.

๐Ÿ’ป Install:

npm install jsonc-parser

๐Ÿง  Example Code:

// parse-jsonc.js
import { parse } from 'jsonc-parser';

const jsoncText = `
{
  // This is a comment
  "name": "ChatGPT",
  "version": 5,
  /* multi-line comment */
  "features": ["AI", "Chat", "Knowledge"]
}
`;

const data = parse(jsoncText);
console.log(data);

๐Ÿงพ Output:

{
  "name": "ChatGPT",
  "version": 5,
  "features": ["AI", "Chat", "Knowledge"]
}

✅ Note:
You can also strip comments manually using libraries like strip-json-comments:

import fs from 'fs';
import stripJsonComments from
 'strip-json-comments';

const fileData = fs.readFileSync
('config.jsonc', 'utf8');
const cleanJson = stripJsonComments(fileData);
const data = JSON.parse(cleanJson);

๐Ÿ 2. Python

✅ Tooling Support:

  • commentjson – a drop-in replacement for json that supports comments.
  • json5 – also parses JSON with comments and trailing commas.

๐Ÿ’ป Install:

pip install commentjson

๐Ÿง  Example Code:

# parse_jsonc.py
import commentjson

jsonc_text = """
{
    // Single-line comment
    "language": "Python",
    "supports": ["JSON", "JSONC"],
    /* Multi-line
       comment */
    "version": 3.11
}
"""

data = commentjson.loads(jsonc_text)
print(data)

๐Ÿงพ Output:

{'language': 'Python', 'supports':
 ['JSON', 'JSONC'], 'version': 3.11}

✅ Alternative using json5:

import json5
data = json5.loads(jsonc_text)

๐Ÿน 3. Go (Golang)

✅ Tooling Support:

  • Go’s standard library encoding/json does not support comments.
  • Use libraries like:
    • jsonc (github.com/tidwall/jsonc)
    • github.com/muhammadmuzzammil1998/jsonc

๐Ÿ’ป Install:

go get github.com/tidwall/jsonc

๐Ÿง  Example Code:

// parse_jsonc.go
package main

import (
    "fmt"
    "github.com/tidwall/jsonc"
    "encoding/json"
)

func main() {
    jsoncData := `
    {
        // JSONC example
        "language": "Go",
        "version": 1.23,
        /* Comment block */
        "features": ["Fast", "Concurrent"]
    }`

    // Remove comments
    clean := jsonc.ToJSON(jsoncData)

    var data map[string]interface{}
    json.Unmarshal([]byte(clean), &data)

    fmt.Println(data)
}

๐Ÿงพ Output:

map[language:Go version:1.23 features
:[Fast Concurrent]]

๐Ÿงฐ ๐Ÿง  Key Takeaways

  • JSONC is not officially part of the JSON standard but enhances readability.
  • Ideal for configuration files where comments help documentation.
  • Before parsing, JSONC text must have its comments removed or ignored by a compatible parser.
  • Native JSON parsers (like JSON.parsejson.loadsencoding/json) will fail if comments are present.

 Examples for writing JSONC back to files (serialization) in these languages too?


Perfect ✅ — here are examples for writing (serializing) JSONC data back to files in
JavaScript/Node.jsPython, and Go, along with notes on tooling that supports writing JSONC safely (preserving comments when possible).

๐Ÿš€ 1. JavaScript / Node.js — Write JSONC to File

✅ Tooling Support:

  • jsonc-parser can modify JSONC text while preserving comments.
  • strip-json-comments + JSON.stringify can be used to save plain JSON (comments lost).

๐Ÿ’ป Install:

npm install jsonc-parser

๐Ÿง  Example Code (Preserving Comments):

// write-jsonc.js
import fs from 'fs';
import { modify, applyEdits } from 
'jsonc-parser';

const jsoncText = `
{
  // Configuration for ChatGPT
  "model": "GPT-5",
  "temperature": 0.8
}
`;

// Add a new property to the existing 
JSONC while preserving comments
const edits = modify(jsoncText,
 ['max_tokens'], 2048, 
{ formattingOptions: { insertSpaces: 
true, tabSize: 2 } });
const updatedText = applyEdits
(jsoncText, edits);

// Write back to file
fs.writeFileSync('config.jsonc', 
updatedText, 'utf8');
console.log('✅ config.jsonc
 updated successfully!');

๐Ÿงพ Resulting config.jsonc:

{
  // Configuration for ChatGPT
  "model": "GPT-5",
  "temperature": 0.8,
  "max_tokens": 2048
}

๐Ÿงฉ If you don’t need to preserve comments:

const fs = require('fs');
const data = {
  name: 'ChatGPT',
  version: 5,
  features: ['AI', 'Chat', 'Knowledge']
};

fs.writeFileSync('data.jsonc', 
JSON.stringify(data, null, 2));

๐Ÿ 2. Python — 

Write JSONC to File

✅ Tooling Support:

  • commentjson can dump JSONC with comments, but you must include them manually as part of the string.
  • json5 can serialize JSON5/JSONC-like data (with optional comments manually added).

๐Ÿ’ป Example Code:

# write_jsonc.py
import commentjson

data = {
    "name": "ChatGPT",
    "version": 5,
    "languages": ["Python", "JavaScript", "Go"]
}

# Serialize to JSON (comments must 
be added manually)
json_text = commentjson.dumps(data, indent=2)

# Add a comment line manually
jsonc_output = f"// Example JSONC 
output\n{json_text}"

with open("data.jsonc", "w") as f:
    f.write(jsonc_output)

print("✅ data.jsonc written successfully!")

๐Ÿงพ Output (data.jsonc):

// Example JSONC output
{
  "name": "ChatGPT",
  "version": 5,
  "languages": [
    "Python",
    "JavaScript",
    "Go"
  ]
}

๐Ÿง  Note: There is no automated Python library that preserves comments round-trip, so comments are usually inserted manually.

๐Ÿน 3. Go (Golang) — Write JSONC to File

✅ Tooling Support:

  • Go libraries like tidwall/jsonc strip comments during parsing, so you’ll need to reinsert them manually.
  • You can write regular JSON and prepend comments.

๐Ÿ’ป Example Code:

// write_jsonc.go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

func main() {
    data := map[string]interface{}{
        "app":    "ChatGPT",
        "version": 5,
        "features": []string{"AI", 
"Chat", "Knowledge"},
    }

    jsonBytes, _ := json.MarshalIndent
(data, "", "  ")

    // Add JSONC-style comment manually
    jsoncOutput := "// Example JSONC 
configuration\n" + string(jsonBytes)

    os.WriteFile("config.jsonc",
 []byte(jsoncOutput), 0644)
    fmt.Println("✅ config.jsonc
 written successfully!")
}

๐Ÿงพ Output (config.jsonc):

// Example JSONC configuration
{
  "app": "ChatGPT",
  "version": 5,
  "features": [
    "AI",
    "Chat",
    "Knowledge"
  ]
}

๐Ÿงฐ 

๐Ÿง  Key Tips

  • Round-trip editing (preserve + modify + write) is easiest in Node.js via jsonc-parser.
  • Python and Go lack native comment-preserving JSONC writers; treat comments as plain text.
  • For config files, consider storing templates (.jsonc) and modifying them using string operations if preserving comments matters.

A practical real-world example (like editing a settings.jsonc config file for an app) using all three languages next?

Excellent ✅ — let’s build a real-world JSONC configuration editing example in
JavaScript (Node.js)Python, and Go, showing how to:

  1. Load a .jsonc configuration file with comments.
  2. Modify a setting (e.g., theme or autoSave).
  3. Write it back while keeping the original comments intact (where possible).

⚙️ Example Scenario — settings.jsonc

We’ll use this same JSONC file for all languages:

{
  // Application display settings
  "theme": "light",

  // Auto-save interval in minutes
  "autoSave": 10,

  /* Enable experimental features */
  "experimental": false
}

Our goal:
✅ Change "theme" to "dark"
✅ Change "experimental" to true
✅ Save back the updated file

๐Ÿš€ 1. JavaScript / Node.js — Best for True JSONC Preservation

✅ Tool: jsonc-parser

Maintains formatting + comments automatically.

๐Ÿ’ป Install:

npm install jsonc-parser

๐Ÿง  Code: editSettings.js

import fs from "fs";
import { modify, applyEdits } 
from "jsonc-parser";

const filePath = "settings.jsonc";

// Read original JSONC file
const jsoncText = fs.readFileSync
(filePath, "utf8");

// Change "theme" to "dark"
let edits = modify(jsoncText,
 ["theme"], "dark", 
{ formattingOptions: 
{ insertSpaces: true, tabSize: 2 } });
let updatedText = applyEdits
(jsoncText, edits);

// Update "experimental" to true
edits = modify(updatedText, 
["experimental"], true, 
{ formattingOptions:
 { insertSpaces: true, tabSize: 2 } });
updatedText = applyEdits(updatedText, edits);

// Save updated JSONC back
fs.writeFileSync(filePath, 
updatedText, "utf8");

console.log("✅ settings.jsonc 
updated successfully!");

๐Ÿงพ Result (settings.jsonc):

{
  // Application display settings
  "theme": "dark",

  // Auto-save interval in minutes
  "autoSave": 10,

  /* Enable experimental features */
  "experimental": true
}

๐Ÿง  Preserves all comments — the cleanest approach.

๐Ÿ 2. Python — Simple, but Manual Comment Handling

✅ Tool: commentjson

Reads JSONC but cannot auto-preserve comments; we’ll handle them manually.

๐Ÿ’ป Install:

pip install commentjson

๐Ÿง  Code: edit_settings.py

import commentjson

file_path = "settings.jsonc"

# Read JSONC text
with open(file_path, "r") as f:
    jsonc_text = f.read()

# Parse ignoring comments
data = commentjson.loads(jsonc_text)

# Modify values
data["theme"] = "dark"
data["experimental"] = True

# Serialize back (comments will 
be added manually)
json_output = commentjson.dumps
(data, indent=2)
final_output = """// Application 
configuration (Python updated)\n""" 
+ json_output

# Write back
with open(file_path, "w") as f:
    f.write(final_output)

print("✅ settings.jsonc updated 
successfully!")

๐Ÿงพ Result:

// Application configuration (Python updated)
{
  "theme": "dark",
  "autoSave": 10,
  "experimental": true
}

⚠️ Note: Original inline comments are lost — Python doesn’t preserve them automatically.

๐Ÿน 3. Go — Manual Comment Re-Insertion

✅ Tool: tidwall/jsonc (for parsing JSONC)

Go’s native encoding/json is used for writing, with comment reinsertion by string concatenation.

๐Ÿ’ป Install:

go get github.com/tidwall/jsonc

๐Ÿง  Code: edit_settings.go

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "github.com/tidwall/jsonc"
)

func main() {
    filePath := "settings.jsonc"

    // Read JSONC file
    jsoncData, err := os.ReadFile(filePath)
    if err != nil {
        panic(err)
    }

    // Remove comments and parse
    clean := jsonc.ToJSON(string(jsoncData))

    var data map[string]interface{}
    if err := json.Unmarshal([]byte(clean), 
&data); err != nil {
        panic(err)
    }

    // Modify values
    data["theme"] = "dark"
    data["experimental"] = true

    // Serialize back to JSON
    jsonBytes, _ := json.MarshalIndent
(data, "", "  ")

    // Re-add comments manually
    jsoncOutput := `{
  // Application display settings
  "theme": "dark",

  // Auto-save interval in minutes
  "autoSave": 10,

  /* Enable experimental features */
  "experimental": true
}`

    os.WriteFile(filePath, 
[]byte(jsoncOutput), 0644)
    fmt.Println("✅ settings.jsonc 
updated successfully!")
}

๐Ÿง  Manual reinsertion is required — Go’s parser doesn’t preserve comments.

๐Ÿงฐ  Best Practices for Real-World Use

  1. For JSONC-heavy configs (like VS Code, TypeScript) → Use Node.js with jsonc-parser.
  2. For Python scripts → Use commentjson for reading, then rewrite with a comment header.
  3. For Go CLI tools → Store original comments separately or use templates for regenerating configs.
  4. Never use plain JSON.parse() or encoding/json directly on JSONC — they will throw syntax errors.
  5. Keep a .jsonc template file and modify it programmatically for safe round-trip editing.

Add a cross-language CLI demo (where all three scripts modify the same JSONC file sequentially and produce versioned backups like settings.v1.jsoncsettings.v2.jsonc, etc.)?

Perfect ๐Ÿ”ฅ — Let’s now build a cross-language CLI demo where JavaScriptPython, and Go scripts all edit the same settings.jsonc file sequentially, creating versioned backups like:

settings.v1.jsonc  ← Node.js update  
settings.v2.jsonc  ← Python update  
settings.v3.jsonc  ← Go update  

This demo shows how to maintain a versioned, comment-preserving JSONC configuration workflow across multiple languages.

๐Ÿงฉ Base Configuration File: settings.jsonc

We’ll start with this file in the working directory:

{
  // Application display settings
  "theme": "light",

  // Auto-save interval in minutes
  "autoSave": 10,

  /* Enable experimental features */
  "experimental": false
}

๐Ÿš€ Step 1: Node.js Script — Create settings.v1.jsonc

✅ Tool: jsonc-parser

๐Ÿ’ป edit_node.js

import fs from "fs";
import { modify, applyEdits } from 
"jsonc-parser";

const input = "settings.jsonc";
const output = "settings.v1.jsonc";

let jsoncText = fs.readFileSync
(input, "utf8");

// Update theme to "dark"
let edits = modify(jsoncText, 
["theme"], "dark", 
{ formattingOptions: { insertSpaces:
 true, tabSize: 2 } });
let updated = applyEdits(jsoncText, edits);

// Save version 1
fs.writeFileSync(output, updated, "utf8");
console.log("✅ Node.js:
 Created settings.v1.jsonc");

๐Ÿงพ Result (settings.v1.jsonc):

{
  // Application display settings
  "theme": "dark",

  // Auto-save interval in minutes
  "autoSave": 10,

  /* Enable experimental features */
  "experimental": false
}

๐Ÿ Step 2: Python Script — Create settings.v2.jsonc

✅ Tool: commentjson

๐Ÿ’ป edit_python.py

import commentjson

input_file = "settings.v1.jsonc"
output_file = "settings.v2.jsonc"

# Read JSONC text
with open(input_file, "r") as f:
    jsonc_text = f.read()

# Parse ignoring comments
data = commentjson.loads(jsonc_text)

# Modify configuration
data["autoSave"] = 15
data["experimental"] = True

# Serialize back (adding version comment)
json_text = commentjson.dumps(data, indent=2)
final_output = f"// Updated 
by Python script\n{json_text}"

# Write new version
with open(output_file, "w") as f:
    f.write(final_output)

print("✅ Python: Created settings.v2.jsonc")

๐Ÿงพ Result (settings.v2.jsonc):

// Updated by Python script
{
  "theme": "dark",
  "autoSave": 15,
  "experimental": true
}

⚠️ Inline comments from v1 are lost, but Python adds a header comment.

๐Ÿน Step 3: Go Script — Create settings.v3.jsonc

✅ Tool: tidwall/jsonc

๐Ÿ’ป edit_go.go

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "github.com/tidwall/jsonc"
)

func main() {
    inputFile := "settings.v2.jsonc"
    outputFile := "settings.v3.jsonc"

    data, err := os.ReadFile(inputFile)
    if err != nil {
        panic(err)
    }

    // Remove comments before parsing
    clean := jsonc.ToJSON(string(data))

    var config map[string]interface{}
    if err := json.Unmarshal([]byte(clean),
 &config); err != nil {
        panic(err)
    }

    // Modify Go-specific property
    config["compiledBy"] = "GoLang v1.23"

    // Serialize and reinsert comments
 manually
    jsonBytes, _ := json.MarshalIndent
(config, "", "  ")
    jsoncOutput := fmt.Sprintf
("// Updated by Go script\n%s",
 string(jsonBytes))

    os.WriteFile(outputFile,
 []byte(jsoncOutput), 0644)
    fmt.Println("✅ Go: Created 
settings.v3.jsonc")
}

๐Ÿงพ Result (settings.v3.jsonc):

// Updated by Go script
{
  "theme": "dark",
  "autoSave": 15,
  "experimental": true,
  "compiledBy": "GoLang v1.23"
}

๐Ÿงฐ  How to Run Sequentially

From a terminal:

# 1️⃣ Run Node.js
node edit_node.js

# 2️⃣ Run Python
python edit_python.py

# 3️⃣ Run Go
go run edit_go.go

✅ You’ll end up with:

settings.jsonc   ← Original file
settings.v1.jsonc ← Node.js updated version
settings.v2.jsonc ← Python updated version
settings.v3.jsonc ← Go updated version

๐ŸŒ Real-World Applications

This approach can be used to:

  • Manage cross-language configuration pipelines.
  • Build CLI tools that process JSONC configs consistently.
  • Automate app configuration versioning (useful for dev environments).
  • Enable multi-language tooling (e.g., Node.js backend + Go microservice + Python utility).

Extended this example into a cross-language automation script (like a master shell or Node.js controller script that runs all three sequentially and logs version diffs)?

Perfect ๐Ÿ’ฅ — let’s now take this to the next level:
we’ll build a cross-language automation pipeline that:

  1. Runs Node.jsPython, and Go scripts sequentially.
  2. Creates versioned .jsonc files (v1v2v3).
  3. Logs what changed between versions (a small “diff” summary).

We’ll implement this with one master controller script (using Node.js) — because Node can easily call external processes and handle file I/O.

๐Ÿงฉ Folder Setup

jsonc-demo/
├── settings.jsonc            ← Base config
├── edit_node.js              ← Node.js step
├── edit_python.py            ← Python step
├── edit_go.go                ← Go step
├── run_pipeline.js           ← Master 
controller

Make sure the other three scripts (edit_node.jsedit_python.pyedit_go.go) are the same as before.

⚙️ Step 1: Create run_pipeline.js

✅ Purpose:

  • Runs each script one by one.
  • Creates versioned files automatically.
  • Compares file differences between steps.

๐Ÿ’ป Code: run_pipeline.js

import { execSync } from "child_process";
import fs from "fs";
import path from "path";

// Utility to log version differences
function logDiff(oldFile, newFile) {
  const oldText = fs.existsSync(oldFile)
 ? fs.readFileSync(oldFile, "utf8") : "";
  const newText = fs.readFileSync
(newFile, "utf8");

  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");

  console.log(`\n๐Ÿ” Diff between
 ${path.basename(oldFile)} and
 ${path.basename(newFile)}:`);

  newLines.forEach((line, i) => {
    if (oldLines[i] !== line) {
      console.log(`  ➕ ${line.trim()}`);
    }
  });
}

try {
  console.log("๐Ÿš€ Starting cross-language
 JSONC pipeline...\n");

  // Step 1: Run Node.js script
  execSync("node edit_node.js", 
{ stdio: "inherit" });

  // Step 2: Run Python script
  execSync("python edit_python.py", 
{ stdio: "inherit" });

  // Step 3: Run Go script
  execSync("go run edit_go.go", 
{ stdio: "inherit" });

  console.log("\n✅ All scripts 
executed successfully!");

  // Compare outputs
  logDiff("settings.jsonc",
 "settings.v1.jsonc");
  logDiff("settings.v1.jsonc",
 "settings.v2.jsonc");
  logDiff("settings.v2.jsonc",
 "settings.v3.jsonc");

  console.log("\n๐ŸŽ‰ Pipeline 
completed! Check versioned files 
for final configurations.");
} catch (err) {
  console.error("❌ Error 
running pipeline:", err.message);
}

๐Ÿง  How It Works

  1. The master Node.js script executes:

    • node edit_node.js
    • python edit_python.py
    • go run edit_go.go
  2. Each script:

    • Reads the previous JSONC version.
    • Updates specific fields.
    • Writes out a new version (settings.v1.jsonc, etc.).
  3. The controller then prints differences between each version:

    • Added/modified lines are prefixed with .

๐Ÿงพ Example Console Output

When you run:

node run_pipeline.js

You’ll see:

๐Ÿš€ Starting cross-language JSONC pipeline...

✅ Node.js: Created settings.v1.jsonc
✅ Python: Created settings.v2.jsonc
✅ Go: Created settings.v3.jsonc

✅ All scripts executed successfully!

๐Ÿ” Diff between settings.jsonc and 
settings.v1.jsonc:
  ➕ "theme": "dark",

๐Ÿ” Diff between settings.v1.jsonc 
and settings.v2.jsonc:
  ➕ "autoSave": 15,
  ➕ "experimental": true

๐Ÿ” Diff between settings.v2.jsonc
 and settings.v3.jsonc:
  ➕ "compiledBy": "GoLang v1.23"

๐ŸŽ‰ Pipeline completed! Check 
versioned files for final configurations.

๐Ÿงฐ  Real-World Benefits

✅ Demonstrates multi-language JSONC compatibility.
✅ Enables cross-team configuration pipelines (e.g., Node backend + Python analytics + Go service).
✅ Shows how comments survive or are re-added.
✅ Creates audit trail via versioned files and diffs.

๐Ÿ’ก Optional Extension Ideas

You can enhance this demo further by:

  • ✨ Auto-generating version numbers dynamically (using timestamps).
  • ๐Ÿ—ƒ️ Storing diffs in a changelog.txt file.
  • ๐Ÿงช Running all steps inside a Docker container for reproducibility.
  • ๐Ÿ”„ Using Node.js to automatically revert to a specific version.

Add the optional extension — for example, an automated changelog generator (changelog.txt) that logs each version change with timestamps and diffs?

Fantastic ⚡ — let’s now upgrade your cross-language JSONC automation pipeline with a
๐Ÿ—ƒ Changelog Generator that automatically logs every version change, timestamp, and diff summary into a changelog.txt file.

This creates a complete audit trail for how your JSONC configuration evolves across Node.js, Python, and Go.

๐Ÿงฉ Updated Folder Structure

jsonc-demo/
├── settings.jsonc
├── edit_node.js
├── edit_python.py
├── edit_go.go
├── run_pipeline.js          ← Master 
automation controller
├── changelog.txt            ← Auto-generated

⚙️ Step 1: Enhanced Master Script — run_pipeline.js

We’ll extend the controller to:

  1. Run all language scripts sequentially.
  2. Log changes between versions.
  3. Append readable diffs with timestamps into changelog.txt.

๐Ÿ’ป run_pipeline.js

import { execSync } from "child_process";
import fs from "fs";
import path from "path";

// === Utility: Compare two JSONC files
 line-by-line ===
function getDiff(oldFile, newFile) {
  const oldText = fs.existsSync(oldFile) ? 
fs.readFileSync(oldFile, "utf8") : "";
  const newText = fs.readFileSync(newFile,
 "utf8");

  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");

  const diffLines = [];
  newLines.forEach((line, i) => {
    if (oldLines[i] !== line) {
      diffLines.push(`  ➕ ${line.trim()}`);
    }
  });
  return diffLines.join("\n");
}

// === Utility: Append changelog entry ===
function logChange(oldFile, newFile) {
  const timestamp = new Date().
toISOString().replace("T", " ").split(".")[0];
  const diffText = getDiff(oldFile, newFile);

  const logEntry = `
๐Ÿ•’ ${timestamp}๐Ÿ“ From: $
{path.basename(oldFile)}
๐Ÿ“ To:   ${path.basename(newFile)}

๐Ÿ” Changes:
${diffText || "  (No visible changes)"}`;

  fs.appendFileSync("changelog.txt",
 logEntry, "utf8");
  console.log(`๐Ÿ“ Logged changes
 from ${oldFile} → ${newFile}`);
}

// === Master runner ===
try {
  console.log("๐Ÿš€ Starting 
Cross-Language JSONC Pipeline...\n");

  // Clean up old changelog
  if (fs.existsSync("changelog.txt"))
 fs.unlinkSync("changelog.txt");

  // 1️⃣ Run Node.js
  execSync("node edit_node.js", 
{ stdio: "inherit" });

  // 2️⃣ Run Python
  execSync("python edit_python.py",
 { stdio: "inherit" });

  // 3️⃣ Run Go
  execSync("go run edit_go.go", 
{ stdio: "inherit" });

  console.log("\n✅ All scripts 
executed successfully!\n");

  // === Create changelog entries ===
  logChange("settings.jsonc", 
"settings.v1.jsonc");
  logChange("settings.v1.jsonc", 
"settings.v2.jsonc");
  logChange("settings.v2.jsonc", 
"settings.v3.jsonc");

  console.log("\n๐ŸŽ‰ Pipeline
 completed! See 'changelog.txt'
 for version history.\n");
} catch (err) {
  console.error("❌ Pipeline
 failed:", err.message);
}

๐Ÿงพ Example Output (Terminal)

๐Ÿš€ Starting Cross-Language JSONC Pipeline...

✅ Node.js: Created settings.v1.jsonc
✅ Python: Created settings.v2.jsonc
✅ Go: Created settings.v3.jsonc

✅ All scripts executed successfully!

๐Ÿ“ Logged changes from settings.jsonc 
→ settings.v1.jsonc
๐Ÿ“ Logged changes from settings.v1.jsonc
 → settings.v2.jsonc
๐Ÿ“ Logged changes from settings.v2.jsonc
 → settings.v3.jsonc

๐ŸŽ‰ Pipeline completed! See
 'changelog.txt' for version history.

๐Ÿ“– Example Generated

 changelog.txt

๐Ÿ•’ 2025-11-04 10:52:31
๐Ÿ“ From: settings.jsonc
๐Ÿ“ To:   settings.v1.jsonc

๐Ÿ” Changes:
  ➕ "theme": "dark",
๐Ÿ•’ 2025-11-04 10:52:33
๐Ÿ“ From: settings.v1.jsonc
๐Ÿ“ To:   settings.v2.jsonc

๐Ÿ” Changes:
  ➕ "autoSave": 15,
  ➕ "experimental": true
๐Ÿ•’ 2025-11-04 10:52:35
๐Ÿ“ From: settings.v2.jsonc
๐Ÿ“ To:   settings.v3.jsonc

๐Ÿ” Changes:
  ➕ "compiledBy": "GoLang v1.23"
๐Ÿงฐ How to Run the Full Demo
# 1️⃣ Ensure dependencies installed
npm install jsonc-parser
pip install commentjson
go get github.com/tidwall/jsonc

# 2️⃣ Run the entire automation
node run_pipeline.js

After running, your directory will contain:

settings.jsonc
settings.v1.jsonc
settings.v2.jsonc
settings.v3.jsonc
changelog.txt

๐Ÿง  What You’ve Built

✅ A cross-language JSONC workflow integrating JavaScriptPython, and Go.
✅ A pipeline automation system with versioning and diffs.
✅ A timestamped changelog for auditability and tracking.

This is a real-world blueprint for:

  • Configuration version control (for cross-platform systems).
  • Automated environment syncing across services.
  • Testing JSONC compatibility pipelines.

✨ Optional Next Upgrade Ideas

  1. Add Git integration – auto-commit each new version.
  2. Generate HTML changelog reports (pretty formatted diffs).
  3. Add a rollback command to restore previous JSONC versions.
  4. Containerize (e.g., Dockerfile with all three environments pre-installed).

Add the Git auto-commit integration next — so each version and changelog entry is automatically committed with a descriptive message like “Update via Python step”?

Perfect ๐Ÿš€ — now we’ll extend the cross-language JSONC automation pipeline to include
๐Ÿ’พ automatic Git commits for every version and changelog entry.

This turns your workflow into a fully version-controlled multi-language config manager — perfect for real projects where each service (Node, Python, Go) updates config files and commits changes automatically.

๐Ÿงฉ Updated Folder Overview

jsonc-demo/
├── settings.jsonc
├── edit_node.js
├── edit_python.py
├── edit_go.go
├── run_pipeline.js         
 ← Master automation + changelog + git commit
├── changelog.txt
└── .git/                    
← Git repo initialized automatically

⚙️ Step 1: Git Auto-Commit–Enabled Master Script

We’ll enhance the Node.js controller to:

  1. Run Node, Python, and Go steps sequentially.
  2. Log differences to changelog.txt.
  3. Auto-initialize Git (if not already).
  4. Auto-commit each stage with a clear message.

๐Ÿ’ป run_pipeline.js

import { execSync } from "child_process";
import fs from "fs";
import path from "path";

// === Utility: Diff between two files ===
function getDiff(oldFile, newFile) {
  const oldText = fs.existsSync(oldFile)
 ? fs.readFileSync(oldFile, "utf8") : "";
  const newText = fs.readFileSync
(newFile, "utf8");
  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");

  const diffLines = [];
  newLines.forEach((line, i) => {
    if (oldLines[i] !== line)
 diffLines.push(`  ➕ ${line.trim()}`);
  });
  return diffLines.join("\n");
}

// === Utility: Log changelog entry ===
function logChange(oldFile, newFile) {
  const timestamp = new Date().
toISOString().replace("T", " ").split(".")[0];
  const diffText = getDiff(oldFile, newFile);
  ๐Ÿ•’ ${timestamp}
๐Ÿ“ From: ${path.basename(oldFile)}
๐Ÿ“ To:   ${path.basename(newFile)}

๐Ÿ” Changes:
${diffText || "  (No visible changes)"}
;
  fs.appendFileSync("changelog.txt",
 entry, "utf8");
  console.log(`๐Ÿ“ Logged ${oldFile}
 → ${newFile}`);
}

// === Utility: Run shell command ===
function runCommand(cmd) {
  execSync(cmd, { stdio: "inherit" });
}

// === Utility: Git commit helper ===
function gitCommit(file, message) {
  try {
    execSync(`git add ${file} changelog.txt
`, { stdio: "ignore" });
    execSync(`git commit -m "${message}"`,
 { stdio: "ignore" });
    console.log(`๐Ÿ’พ Git committed: ${message}`);
  } catch (err) {
    console.error("⚠️ Git commit 
failed:", err.message);
  }
}

// === Master controller ===
try {
  console.log("๐Ÿš€ Starting 
Cross-Language JSONC Pipeline with 
Git tracking...\n");

  // Initialize Git if missing
  if (!fs.existsSync(".git")) {
    runCommand("git init");
    console.log("๐Ÿ†• Initialized
 new Git repository.\n");
  }

  // Clean up previous changelog
  if (fs.existsSync
("changelog.txt")) fs.unlinkSync
("changelog.txt");

  // Run Node.js step
  runCommand("node edit_node.js");
  logChange("settings.jsonc",
 "settings.v1.jsonc");
  gitCommit("settings.v1.jsonc",
 "Node.js step → Updated theme to dark");

  // Run Python step
  runCommand("python edit_python.py");
  logChange("settings.v1.jsonc",
 "settings.v2.jsonc");
  gitCommit("settings.v2.jsonc",
 "Python step → 
Updated autoSave & experimental");

  // Run Go step
  runCommand("go run edit_go.go");
  logChange("settings.v2.jsonc", 
"settings.v3.jsonc");
  gitCommit("settings.v3.jsonc", 
"Go step → Added compiledBy field");

  console.log("\n✅ All steps 
completed and committed!");
  console.log("๐Ÿ“„ See 
changelog.txt and git log for 
version history.\n");
} catch (err) {
  console.error("❌ Pipeline 
failed:", err.message);
}

๐Ÿง  What Happens Automatically

When you run:

node run_pipeline.js

The controller will:

  1. Create/update .git repo if not already initialized.
  2. Run each language script sequentially.
  3. Log diffs to changelog.txt.
  4. Stage and commit each updated version and changelog entry with descriptive messages.

๐Ÿงพ Example Terminal Output

๐Ÿš€ Starting Cross-Language JSONC
 Pipeline with Git tracking...

๐Ÿ†• Initialized new Git repository.

✅ Node.js: Created settings.v1.jsonc
๐Ÿ“ Logged settings.jsonc → settings.v1.jsonc
๐Ÿ’พ Git committed: Node.js step → 
Updated theme to dark

✅ Python: Created settings.v2.jsonc
๐Ÿ“ Logged settings.v1.jsonc → 
settings.v2.jsonc
๐Ÿ’พ Git committed: Python step →
 Updated autoSave & experimental

✅ Go: Created settings.v3.jsonc
๐Ÿ“ Logged settings.v2.jsonc → 
settings.v3.jsonc
๐Ÿ’พ Git committed: Go step → 
Added compiledBy field

✅ All steps completed and committed!
๐Ÿ“„ See changelog.txt and git log
 for version history.

๐Ÿงฐ Check Git History

After running the script, try:

git log --oneline

You’ll see:

d8a7f32 Go step → Added compiledBy field
a24b0c1 Python step → 
Updated autoSave & experimental
3be1b9a Node.js step → 
Updated theme to dark

Each commit will include both the settings.vX.jsonc and the updated changelog.txt.

๐Ÿ“– Example Changelog O

๐Ÿ•’ 2025-11-04 11:02:15

๐Ÿ“ From: settings.jsonc
๐Ÿ“ To:   settings.v1.jsonc

๐Ÿ” Changes:
  ➕ "theme": "dark",
..

๐Ÿง  Why This Is Powerful

✅ Fully automated versioning and commit tracking across multiple languages.
✅ Maintains a human-readable changelog for quick audits.
✅ Builds a Git-backed configuration evolution history — ideal for collaborative projects.
✅ Can plug directly into CI/CD pipelines (e.g., GitHub Actions or Jenkins).

๐Ÿ’ก Optional Next Steps

You can easily extend this system to:

  1. Push automatically to GitHub after each pipeline run.
    git remote add origin <your_repo_url>
    git push origin main
    
  2. Tag releases (e.g., v1.0v2.0):
    execSync('git tag v1.0 && git push 
  3. --tags');
    
  4. Add rollback script to restore specific versions:
    git checkout HEAD~1 settings.v2.jsonc
    
  5. Generate HTML or Markdown changelog reports for easy review.

Add that automatic Git push + tag functionality next (so each stage also pushes to GitHub and tags versions like v1v2v3)?

๐Ÿ”ฅ Excellent — let’s now extend the automation to full GitOps-level control:
Every stage (Node, Python, Go) will not only commit changes but also automatically push to GitHub and create version tags (v1v2v3).

This turns your JSONC pipeline into a continuous integration (CI) mini system — complete with audit logs, changelogs, and remote synchronization.

๐Ÿงฉ Folder Layout (Final Version)

jsonc-demo/
├── settings.jsonc
├── edit_node.js
├── edit_python.py
├── edit_go.go
├── run_pipeline.js        
  ← master script (with auto Git push + tags)
├── changelog.txt
└── .git/

⚙️ Step 1: Updated run_pipeline.js

Now includes:

  • Automatic Git initialization if missing
  • Commits per stage
  • Auto-tagging (v1v2v3)
  • Auto-pushing to GitHub (if remote is set)

๐Ÿ’ป run_pipeline.js

import { execSync } from "child_process";
import fs from "fs";
import path from "path";

// === Utility: Run command safely ===
function run(cmd, options = {}) {
  try {
    execSync(cmd, { stdio:
 "inherit", ...options });
  } catch (err) {
    console.error(`⚠️ Command failed
: ${cmd}\n${err.message}`);
  }
}

// === Utility: Compare two files ===
function getDiff(oldFile, newFile) {
  const oldText = fs.existsSync
(oldFile) ? fs.readFileSync(oldFile, 
"utf8") : "";
  const newText = fs.readFileSync
(newFile, "utf8");

  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");

  const diffLines = [];
  newLines.forEach((line, i) => {
    if (oldLines[i] !== line) 
diffLines.push(`  ➕ ${line.trim()}`);
  });
  return diffLines.join("\n");
}

// === Utility: Log changelog entry ===
function logChange(oldFile, newFile) {
  const timestamp = new Date().
toISOString().replace("T", " ").split(".")[0];
  const diffText = getDiff(oldFile, newFile);
  const entry = `
๐Ÿ•’ ${timestamp}
๐Ÿ“ From: ${path.basename(oldFile)}
๐Ÿ“ To:   ${path.basename(newFile)}

๐Ÿ” Changes:
${diffText || "  (No visible changes)"}
;
  fs.appendFileSync("changelog.txt", 
entry, "utf8");
  console.log(`๐Ÿ“ Logged ${oldFile} 
→ ${newFile}`);
}

// === Git commit helper ===
function gitCommitAndTag(file, message,
 tag) {
  try {
    execSync(`git add ${file} 
changelog.txt`, { stdio: "ignore" });
    execSync(`git commit -m "
${message}"`, { stdio: "ignore" });
    console.log(`๐Ÿ’พ Commit 
created: ${message}`);

    // Create and push tag
    execSync(`git tag -f ${tag}`, 
{ stdio: "ignore" });
    console.log(`๐Ÿท️  Tagged version: ${tag}`);

    // Push to remote (if exists)
    try {
      execSync("git push -u origin 
main --tags", { stdio: "ignore" });
      console.log(`๐ŸŒ Changes 
pushed to GitHub with tag ${tag}\n`);
    } catch {
      console.log("⚠️ Git 
remote not set — skipping push.\n");
    }
  } catch (err) {
    console.error("⚠️ Git
 commit/tag failed:", err.message);
  }
}

// === MASTER CONTROLLER ===
try {
  console.log("๐Ÿš€ Starting
 JSONC Cross-Language Pipeline 
(with Git push & tags)...\n");

  // Initialize Git if not yet present
  if (!fs.existsSync(".git")) {
    run("git init");
    run('git branch -M main');
    console.log("๐Ÿ†• Initialized 
new Git repository.\n");
    console.log("๐Ÿ‘‰ To enable 
auto-push, run once:");
    console.log('   git remote
 add origin <your_repo_url>\n');
  }

  // Clean old changelog
  if (fs.existsSync("changelog.txt")) 
fs.unlinkSync("changelog.txt");

  // === Step 1: Node.js ===
  run("node edit_node.js");
  logChange("settings.jsonc", 
"settings.v1.jsonc");
  gitCommitAndTag("settings.v1.jsonc", 
"Node.js step → dark theme", "v1");

  // === Step 2: Python ===
  run("python edit_python.py");
  logChange("settings.v1.jsonc",
 "settings.v2.jsonc");
  gitCommitAndTag("settings.v2.jsonc", 
"Python step → autoSave & 
experimental", "v2");

  // === Step 3: Go ===
  run("go run edit_go.go");
  logChange("settings.v2.jsonc", 
"settings.v3.jsonc");
  gitCommitAndTag("settings.v3.jsonc", 
"Go step → added compiledBy field", "v3");

  console.log("\n✅ All steps
 completed, tagged, and (optionally) pushed!");
  console.log("๐Ÿ“œ Run `git log 
--oneline` or `git tag` to view versions.\n");
} catch (err) {
  console.error("❌ Pipeline 
failed:", err.message);
}

๐Ÿง  What Happens

When you run:

node run_pipeline.js

You’ll get this automated process:

  1. ✅ Node.js edits → creates settings.v1.jsonc

    • Commits to Git (v1 tag)
    • Pushes (if remote added)
  2. ✅ Python edits → creates settings.v2.jsonc

    • Commits to Git (v2 tag)
    • Pushes
  3. ✅ Go edits → creates settings.v3.jsonc

    • Commits to Git (v3 tag)
    • Pushes

๐Ÿงพ Example Terminal Output

๐Ÿš€ Starting JSONC Cross-Language
 Pipeline (with Git push & tags)...

๐Ÿ†• Initialized new Git repository.

✅ Node.js: Created settings.v1.jsonc
๐Ÿ“ Logged settings.jsonc → settings.v1.jsonc
๐Ÿ’พ Commit created: Node.js step → dark theme
๐Ÿท️  Tagged version: v1
⚠️ Git remote not set — skipping push.

✅ Python: Created settings.v2.jsonc
๐Ÿ“ Logged settings.v1.jsonc →
 settings.v2.jsonc
๐Ÿ’พ Commit created: Python step →
 autoSave & experimental
๐Ÿท️  Tagged version: v2

✅ Go: Created settings.v3.jsonc
๐Ÿ“ Logged settings.v2.jsonc → 
settings.v3.jsonc
๐Ÿ’พ Commit created: Go step → 
added compiledBy field
๐Ÿท️  Tagged version: v3

✅ All steps completed, 
tagged, and (optionally) pushed!
๐Ÿ“œ Run `git log --oneline` or
 `git tag` to view versions.

๐Ÿงฐ To Enable Auto Push to GitHub

After the first run, connect 

your local folder to a GitHub repo:

git remote add origin
 https://github.com/<your-username>
/<repo-name>.git
git push -u origin main --tags

Next time you run node run_pipeline.js, all commits and tags (v1v2v3) will automatically sync to GitHub.

๐Ÿ’ก Optional Enhancements

You can easily add:

  • Release Notes Generator (auto-create Markdown summaries per tag)
  • GitHub Action that runs the pipeline on every push
  • Slack or Discord webhook notifications when a new version is tagged
  • HTML changelog report from the text logs

Add a Release Notes generator next — so each new Git tag (v1, v2, v3) automatically creates a formatted Markdown release summary (e.g., RELEASE_NOTES.md)?

Awesome ๐Ÿ’ฅ — let’s now extend your cross-language JSONC GitOps pipeline with a
๐Ÿชถ Release Notes Generator that automatically builds a formatted RELEASE_NOTES.md file for every version tag (v1v2v3).

Every new tag will:

  1. Append a section to RELEASE_NOTES.md.
  2. Include timestamp, version, summary, and a diff preview from the changelog.
  3. Be committed and (optionally) pushed to GitHub.

๐Ÿงฉ Final Folder Layout

jsonc-demo/
├── settings.jsonc
├── edit_node.js
├── edit_python.py
├── edit_go.go
├── run_pipeline.js       
     ← master controller
 (now with changelog + git + release notes)
├── changelog.txt
├── RELEASE_NOTES.md     
      ← auto-generated release summaries
└── .git/

⚙️ Step 1: Updated Master Script — run_pipeline.js

Below is the final integrated version, including:

  • JSONC automation
  • Changelog
  • Git commits + tags + pushes
  • Markdown release notes generation

๐Ÿ’ป run_pipeline.js

import { execSync } from "child_process";
import fs from "fs";
import path from "path";

// === Helper: Safe command runner ===
function run(cmd) {
  try {
    execSync(cmd, { stdio: "inherit" });
  } catch (err) {
    console.error(`⚠️ 
Command failed: ${cmd}\n${err.message}`);
  }
}

// === Helper: Diff two files ===
function getDiff(oldFile, newFile) {
  const oldText = 
fs.existsSync(oldFile) ?
 fs.readFileSync(oldFile, "utf8") : "";
  const newText = 
fs.readFileSync(newFile, "utf8");
  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");
  const diffLines = [];
  newLines.forEach((line, i) => {
    if (oldLines[i] !==
 line) diffLines.push(`+ ${line.trim()}`);
  });
  return diffLines.join("\n");
}

// === Helper: Append changelog entry ===
function logChange(oldFile, newFile) {
  const timestamp = 
new Date().toISOString().
replace("T", " ").split(".")[0];
  const diffText = getDiff(oldFile, newFile);
  const entry = `
๐Ÿ•’ ${timestamp}
๐Ÿ“ From: ${path.basename(oldFile)}
๐Ÿ“ To:   ${path.basename(newFile)}

๐Ÿ” Changes:
${diffText || "  (No visible changes)"}
;
  fs.appendFileSync("changelog.txt", 
entry, "utf8");
  console.log(`๐Ÿ“ Logged ${oldFile}
 → ${newFile}`);
  return diffText;
}

// === Helper: Generate release 
note section ===
function generateReleaseNotes
(version, message, diffText) {
  const timestamp = new Date().
toISOString().replace("T", " ").split(".")[0];
  const section = `
---

## ๐Ÿท️ ${version} — ${timestamp}
**Change Summary:** ${message}

\`\`\`diff
${diffText || "(No diff found)"}
\`\`\`

`;
  fs.appendFileSync("RELEASE_NOTES.md",
 section, "utf8");
  console.log(`๐Ÿชถ Added release 
notes for ${version}`);
}

// === Helper: Commit, tag, and push ===
function gitCommitAndTag(file,
 message, tag, diffText) {
  try {
    execSync(`git add ${file} 
changelog.txt RELEASE_NOTES.md`,
 { stdio: "ignore" });
    execSync(`git commit -m 
"${message}"`, { stdio: "ignore" });
    console.log(`๐Ÿ’พ Commit
 created: ${message}`);

    execSync(`git tag -f ${tag}`, 
{ stdio: "ignore" });
    console.log(`๐Ÿท️  Tagged version: ${tag}`);

    // Push if remote exists
    try {
      execSync("git push -u origin
 main --tags", { stdio: "ignore" });
      console.log(`๐ŸŒ Pushed 
commit + tag ${tag}\n`);
    } catch {
      console.log("⚠️ Git remote
 not set — skipping push.\n");
    }

    // Write release notes
    generateReleaseNotes(tag,
 message, diffText);
  } catch (err) {
    console.error("⚠️ Git commit/tag 
failed:", err.message);
  }
}

// === MAIN PIPELINE ===
try {
  console.log("๐Ÿš€ Starting JSONC
 Cross-Language Pipeline
 (Git + Release Notes)...\n");

  // Initialize Git if needed
  if (!fs.existsSync(".git")) {
    run("git init");
    run("git branch -M main");
    console.log("๐Ÿ†• Initialized
 new Git repository.");
    console.log("๐Ÿ‘‰ Run `git 
remote add origin <your_repo_url>`
 to enable pushing.\n");
  }

  // Clean old logs
  if (fs.existsSync("changelog.txt"))
 fs.unlinkSync("changelog.txt");
  if (fs.existsSync("RELEASE_NOTES.md"))
 fs.unlinkSync("RELEASE_NOTES.md");

  // === Node.js Step ===
  run("node edit_node.js");
  const diff1 = logChange("
settings.jsonc", "settings.v1.jsonc");
  gitCommitAndTag("settings.v1.jsonc", 
"Node.js step → dark theme", "v1", diff1);

  // === Python Step ===
  run("python edit_python.py");
  const diff2 = logChange("settings.
v1.jsonc", "settings.v2.jsonc");
  gitCommitAndTag("settings.v2.jsonc",
 "Python step → autoSave &
 experimental", "v2", diff2);

  // === Go Step ===
  run("go run edit_go.go");
  const diff3 = logChange("settings
.v2.jsonc", "settings.v3.jsonc");
  gitCommitAndTag("settings.
v3.jsonc", "Go step → added
 compiledBy field", "v3", diff3);

  console.log("\n✅ All steps 
completed, tagged, and release 
notes updated!");
  console.log("๐Ÿ“˜ See RELEASE_NOTES.md
 for summaries.\n");
} catch (err) {
  console.error("❌ Pipeline failed:",
 err.message);
}

๐Ÿงพ Example Output (Terminal)

๐Ÿš€ Starting JSONC Cross-Language Pipeline
 (Git + Release Notes)...

๐Ÿ†• Initialized new Git repository.

✅ Node.js: Created settings.v1.jsonc
๐Ÿ“ Logged settings.jsonc → settings.v1.jsonc
๐Ÿ’พ Commit created: Node.js step → dark theme
๐Ÿท️  Tagged version: v1
๐Ÿชถ Added release notes for v1

✅ Python: Created settings.v2.jsonc
๐Ÿ“ Logged settings.v1.jsonc →
 settings.v2.jsonc
๐Ÿ’พ Commit created: Python step →
 autoSave & experimental
๐Ÿท️  Tagged version: v2
๐Ÿชถ Added release notes for v2

✅ Go: Created settings.v3.jsonc
๐Ÿ“ Logged settings.v2.jsonc →
 settings.v3.jsonc
๐Ÿ’พ Commit created: Go step →
 added compiledBy field
๐Ÿท️  Tagged version: v3
๐Ÿชถ Added release notes for v3

✅ All steps completed, tagged,
 and release notes updated!
๐Ÿ“˜ See RELEASE_NOTES.md for summaries.

๐Ÿ“˜ Example Generated RELEASE_NOTES.md

---

## ๐Ÿท️ v1 — 2025-11-04 12:15:05
**Change Summary:** Node.js step → dark theme

```diff
+ "theme": "dark",

๐Ÿท️ v2 — 2025-11-04 12:15:09

Change Summary: Python step → autoSave & experimental

+ "autoSave": 15,
+ "experimental": true

๐Ÿท️ v3 — 2025-11-04 12:15:13

Change Summary: Go step → added compiledBy field

+ "compiledBy": "GoLang v1.23"

Each version automatically includes:
- Timestamp  
- Tag (`v1`, `v2`, `v3`)  
- Change summary  
- Diff preview  

---

## ✅ How to Run It

```bash
# Install dependencies
npm install jsonc-parser
pip install commentjson
go get github.com/tidwall/jsonc

# Initialize once and add your repo
git init
git branch -M main
git remote add origin
 https://github.com/<username>/<repo-name>.git

# Run the automation
node run_pipeline.js

๐Ÿง  What You Now Have

✅ Multi-language JSONC editing (Node, Python, Go)
✅ Automated changelog tracking
✅ Git commits, tagging, and optional pushes
✅ Markdown RELEASE_NOTES.md with diff previews
✅ A portable GitOps-ready configuration management pipeline

⚙️ Optional Next Steps

Would you like me to add:

  • ๐Ÿ“ค Automatic GitHub Release publishing (creates GitHub releases from tags and uploads the RELEASE_NOTES.md content automatically)?
    This would make your pipeline publish formal releases to GitHub each time v1v2v3 are created.

Perfect ๐Ÿ‘

So here’s what your setup can now do, all automated in one command (node run_pipeline.js):

✅ Parse and update JSONC files using Node.jsPython, and Go
✅ Automatically generate:

  • changelog.txt (human-readable diff log)
  • RELEASE_NOTES.md (Markdown summaries per version)
    ✅ Create Git commits + version tags (v1v2v3)
    ✅ Optionally push to GitHub if you’ve set your remote

✅ Quick Recap – How to Use

  1. Initialize & Connect Repo

    git init
    git branch -M main
    git remote add origin
  2.  https://github.com/<your-username>
  3. /<repo-name>.git
  4. Install Dependencies

    npm install jsonc-parser
    pip install commentjson
    go get github.com/tidwall/jsonc
    
  5. Run the Complete Pipeline

    node run_pipeline.js
    
  6. Check Outputs

    cat changelog.txt
    cat RELEASE_NOTES.md
    git log --oneline
    git tag
    

Add automatic GitHub Release publishing
(using your repo’s API token — so each tag (v1v2v3) is turned into a GitHub Release
with RELEASE_NOTES.md as the description)?

Good ๐Ÿ‘ before we proceed — to clarify:
automatically creating GitHub Releases from tags means your pipeline would talk to GitHub’s API using a personal access token (PAT).

That token gives permission for your local script to create releases on your GitHub repository (e.g., when you tag v1, it would make a new release page on GitHub with the release notes).

For security reasons, I can’t handle or store your token myself — but I can show you exactly how to set it up safely on your computer.

Here’s what you’ll need to do next ๐Ÿ‘‡

๐Ÿชถ Step 1: Create a GitHub Personal Access Token (PAT)

  1. Go to ๐Ÿ‘‰ https://github.com/settings/tokens
  2. Click “Generate new token (classic)”
  3. Give it a name like jsonc-pipeline
  4. Check repo scope (for creating releases)
  5. Copy the generated token (keep it private)

⚙️ Step 2: Set Token as Environment Variable

On your terminal (Linux/macOS):

export GITHUB_TOKEN=ghp_yourgeneratedtoken

On Windows PowerShell:

setx GITHUB_TOKEN "ghp_yourgeneratedtoken"

๐Ÿงฉ Step 3: Add Auto-Release Feature to Your Script

Here’s a small Node.js helper you can drop into your run_pipeline.js (at the bottom, after tagging):

import https from "https";

function createGitHubRelease
(tag, message, notes) {
  const token = process.env.GITHUB_TOKEN;
  const repoUrl = execSync
("git remote get-url origin").
toString().trim();

  // Extract owner/repo from the Git remote
  const match = repoUrl.match
(/github\.com[:/](.+?)\/(.+?)(\.git)?$/);
  if (!match) {
    console.log("⚠️
 Could not parse GitHub repo URL. 
Skipping release.");
    return;
  }

  const [_, owner, repo] = match;

  const data = JSON.stringify({
    tag_name: tag,
    name: `${tag} — ${message}`,
    body: notes,
    draft: false,
    prerelease: false,
  });

  const options = {
    hostname: "api.github.com",
    path: `/repos/${owner}/${repo}/releases`,
    method: "POST",
    headers: {
      "User-Agent": "jsonc-pipeline",
      "Authorization": `token ${token}`,
      "Content-Type": "application/json",
      "Content-Length":
 Buffer.byteLength(data),
    },
  };

  const req = https.request
(options, (res) => {
    console.log(`๐Ÿ“ค GitHub 
release created for ${tag}
 (status: ${res.statusCode})`);
  });

  req.on("error", (err) =>
 console.error("❌ Release failed:",
 err.message));
  req.write(data);
  req.end();
}

Then, right after each gitCommitAndTag(...) call, add:

createGitHubRelease(tag, message, diffText);

This will automatically publish a

 release to your GitHub repository with:

  • The tag (e.g., v3)
  • The release title (from your commit message)
  • The content (diff summary from the changelog)

✅ Result

When you run:

node run_pipeline.js

You’ll get:

  • Local commits + tags
  • RELEASE_NOTES.md updated
  • GitHub Releases automatically published ๐ŸŽ‰

  • final full integrated script (with changelog, tagging, release notes, and GitHub Release publishing all in one file)?

Perfect ๐Ÿ˜Ž — here’s your complete, production-ready Node.js pipeline script:
It handles JSONC parsingchangelog creationsemantic version taggingrelease note generation, and automatic GitHub Releases — all in one place.

๐Ÿ“„ run_pipeline.js

/**
 * JSONC Release Pipeline Script
 * ------------------------------------
---------
 * ✅ Parses and updates JSONC files
 * ✅ Generates changelog.txt and 
RELEASE_NOTES.md
 * ✅ Creates git commits + tags 
(v1, v2, v3, ...)
 * ✅ Publishes GitHub Releases automatically
 *
 * Requirements:
 *   npm install jsonc-parser
 *   export GITHUB_TOKEN=ghp_yourtoken
 */

import fs from "fs";
import { execSync } from "child_process";
import { parse, modify, applyEdits, 
printParseErrorCode } from "jsonc-parser";
import https from "https";

// -------------------- Configuration -
-------------------
const jsoncFile = "config.jsonc"; // 
your JSONC file
const changelogFile = "changelog.txt";
const releaseNotesFile = "RELEASE_NOTES.md";
let versionCounter = 1;

// -------------------- Helpers ------
--------------
function log(msg) {
  console.log(`\x1b[36m[INFO]\x1b[0m ${msg}`);
}

function run(cmd) {
  return execSync(cmd, { 
stdio: "pipe" }).toString().trim();
}

function readJSONC(file) {
  const content = fs.readFileSync
(file, "utf-8");
  const errors = [];
  const data = parse(content, 
errors, { allowTrailingComma: true });
  if (errors.length) {
    errors.forEach((e) =>
      console.error(`❌ JSONC 
Error: ${printParseErrorCode(e.error)} 
at ${e.offset}`)
    );
    throw new Error("Invalid JSONC");
  }
  return data;
}

function writeJSONC(file, json) {
  const content = JSON.stringify
(json, null, 2);
  fs.writeFileSync(file, content);
}

// -------------------- Core Logic -----
---------------
function updateJSONCFile() {
  log("Parsing JSONC file...");
  const data = readJSONC(jsoncFile);

  // Example modification:
 add or update version
  versionCounter++;
  data.version = `v${versionCounter}`;

  log("Updating JSONC file...");
  writeJSONC(jsoncFile, data);

  return `Updated version to 
v${versionCounter}`;
}

function appendChangelog(tag, message) {
  const entry = `[${new Date().
toISOString()}] ${tag} — ${message}\n`;
  fs.appendFileSync(changelogFile, entry);
}

function appendReleaseNotes(tag, message) {
  const entry = `\n## ${tag}\n${message}\n`;
  fs.appendFileSync(releaseNotesFile, entry);
}

// -------------------- Git Automation -
-------------------
function gitCommitAndTag(message) {
  log("Committing changes...");
  run("git add .");
  run(`git commit -m "${message}"`);

  const tag = `v${versionCounter}`;
  run(`git tag ${tag}`);

  log(`✅ Created git tag ${tag}`);
  return tag;
}

// -------------------- GitHub 
Release Publisher --------------------
function createGitHubRelease(tag,
 message, notes) {
  const token = process.env.GITHUB_TOKEN;
  if (!token) {
    console.log("⚠️ No GITHUB_TOKEN found. 
Skipping GitHub release.");
    return;
  }

  const repoUrl = run("git remote 
get-url origin");
  const match = repoUrl.match
(/github\.com[:/](.+?)\/(.+?)(\.git)?$/);
  if (!match) {
    console.log("⚠️ Could not 
parse GitHub repo URL. Skipping release.");
    return;
  }

  const [_, owner, repo] = match;

  const data = JSON.stringify({
    tag_name: tag,
    name: `${tag} — ${message}`,
    body: notes,
    draft: false,
    prerelease: false,
  });

  const options = {
    hostname: "api.github.com",
    path: `/repos/${owner}/${repo}/releases`,
    method: "POST",
    headers: {
      "User-Agent": "jsonc-pipeline",
      "Authorization": `token ${token}`,
      "Content-Type": "application/json",
      "Content-Length":
 Buffer.byteLength(data),
    },
  };

  const req = https.request(options, 
(res) => {
    console.log(`๐Ÿ“ค GitHub 
release created for ${tag} 
(status: ${res.statusCode})`);
  });

  req.on("error", (err) =>
 console.error("❌ Release failed:", 
err.message));
  req.write(data);
  req.end();
}

// -------------------- Pipeline 
Execution --------------------
log("๐Ÿš€ Starting JSONC release pipeline...");

const message = updateJSONCFile();
const tag = gitCommitAndTag(message);

appendChangelog(tag, message);
appendReleaseNotes(tag, message);
createGitHubRelease(tag, message, message);

log("๐ŸŽ‰ Pipeline complete!");

⚙️ How to Use

  1. Create a JSONC file (e.g., config.jsonc):

    {
      // App configuration
      "name": "MyApp",
      "version": "v1"
    }
    
  2. Initialize Git & Connect Repo

    git init
    git add .
    git commit -m "initial"
    git branch -M main
    git remote add origin 
  3. https://github.com/<your-
  4. username>/<repo>.git
    
  5. Install Dependencies

    npm install jsonc-parser
    
  6. Set Your GitHub Token

    export GITHUB_TOKEN=ghp_yourtoken
    
  7. Run the Pipeline

    node run_pipeline.js
    

๐Ÿงพ What You Get Automatically

Output FileDescription
config.jsoncUpdated with incremented version
changelog.txtAppend-only human-readable log
RELEASE_NOTES.mdMarkdown release notes
Git tags (v1v2, …)Created for each release
GitHub ReleasePublished automatically via API

Here include Python and Go equivalents of this GitHub-release pipeline (so you can trigger it in any language)?

Excellent ๐Ÿ‘ — below are Python ๐Ÿ and Go ๐Ÿน equivalents of your JSONC release pipeline.
Both versions replicate the Node.js pipeline logic:

  • Parse and update a .jsonc file
  • Commit and tag in Git
  • Generate changelog + release notes
  • Publish a GitHub Release automatically

๐Ÿ Python Version — run_pipeline.py

"""
JSONC Release Pipeline (Python Version)
------------------------------------------
✅ Parses JSONC using commentjson
✅ Updates version number
✅ Commits + tags in Git
✅ Generates changelog.txt & RELEASE_NOTES.md
✅ Publishes GitHub Release 
(requires GITHUB_TOKEN)
"""

import os
import json
import subprocess
import requests
import commentjson
from datetime import datetime

JSONC_FILE = "config.jsonc"
CHANGELOG = "changelog.txt"
RELEASE_NOTES = "RELEASE_NOTES.md"
version_counter = 1


def log(msg):
    print(f"\033[96m[INFO]\033[0m {msg}")


def run(cmd):
    return subprocess.check_output
(cmd, shell=True).decode().strip()


def read_jsonc(file):
    with open(file, "r") as f:
        return commentjson.load(f)


def write_jsonc(file, data):
    with open(file, "w") as f:
        json.dump(data, f, indent=2)


def update_jsonc():
    global version_counter
    data = read_jsonc(JSONC_FILE)
    version_counter += 1
    data["version"] = f"v{version_counter}"
    write_jsonc(JSONC_FILE, data)
    return f"Updated version to v
{version_counter}"


def append_changelog(tag, message):
    with open(CHANGELOG, "a") as f:
        f.write
(f"[{datetime.utcnow().isoformat()}] 
{tag} — {message}\n")


def append_release_notes(tag, message):
    with open(RELEASE_NOTES, "a") as f:
        f.write(f"\n## {tag}\n{message}\n")


def git_commit_and_tag(message):
    log("Committing changes...")
    run("git add .")
    run(f'git commit -m "{message}"')
    tag = f"v{version_counter}"
    run(f"git tag {tag}")
    log(f"✅ Created git tag {tag}")
    return tag


def create_github_release
(tag, message, notes):
    token = os.getenv("GITHUB_TOKEN")
    if not token:
        log("⚠️ No GITHUB_TOKEN found. 
Skipping GitHub release.")
        return

    repo_url = run("git remote get-url origin")
    import re
    m = re.search(r"github\.com[:/]
(.+?)/(.+?)(\.git)?$", repo_url)
    if not m:
        log("⚠️ Could not parse
 GitHub repo URL.")
        return

    owner, repo = m.group(1), m.group(2)
    url = f"https://api.github.com
/repos/{owner}/{repo}/releases"
    payload = {
        "tag_name": tag,
        "name": f"{tag} — {message}",
        "body": notes,
        "draft": False,
        "prerelease": False,
    }

    headers = {
        "Authorization": f"token {token}",
        "Accept": "application/
vnd.github+json",
        "User-Agent": "jsonc-pipeline",
    }

    res = requests.post(url, 
json=payload, headers=headers)
    log(f"๐Ÿ“ค GitHub release 
created for {tag} (status:
 {res.status_code})")


if __name__ == "__main__":
    log("๐Ÿš€ Starting Python 
JSONC pipeline...")

    message = update_jsonc()
    tag = git_commit_and_tag(message)

    append_changelog(tag, message)
    append_release_notes(tag, message)
    create_github_release(tag,
 message, message)

    log("๐ŸŽ‰ Pipeline complete!")

๐Ÿน Go Version — run_pipeline.go

// JSONC Release Pipeline (Go Version)
// ------------------------------------
// ✅ Parses JSONC using tidwall/jsonc
// ✅ Updates version
// ✅ Git commit + tag
// ✅ Changelog + release notes
// ✅ GitHub Release publishing

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"time"

	"github.com/tidwall/jsonc"
)

var (
	jsoncFile       = "config.jsonc"
	changelogFile   = "changelog.txt"
	releaseNotes    = "RELEASE_NOTES.md"
	versionCounter  = 1
)

func run(cmd string) string {
	out, _ := exec.Command("bash",
 "-c", cmd).Output()
	return string(bytes.TrimSpace(out))
}

func updateJSONC() string {
	data, _ := ioutil.ReadFile(jsoncFile)
	clean := jsonc.ToJSON(string(data))
	var obj map[string]interface{}
	json.Unmarshal([]byte(clean), &obj)

	versionCounter++
	obj["version"] = 
fmt.Sprintf("v%d", versionCounter)
	out, _ := json.MarshalIndent
(obj, "", "  ")
	ioutil.WriteFile(jsoncFile, out, 0644)
	return fmt.Sprintf("Updated 
version to v%d", versionCounter)
}

func appendFile(file, content string) {
	f, _ := os.OpenFile(file,
 os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	defer f.Close()
	f.WriteString(content)
}

func gitCommitAndTag(message string)
 string {
	run("git add .")
	run(fmt.Sprintf("git commit
 -m \"%s\"", message))
	tag := fmt.Sprintf("v%d", 
versionCounter)
	run(fmt.Sprintf("git tag %s", tag))
	fmt.Println("✅ Created git 
tag", tag)
	return tag
}

func createGitHubRelease(tag, message,
 notes string) {
	token := os.Getenv("GITHUB_TOKEN")
	if token == "" {
		fmt.Println("⚠️ No
 GITHUB_TOKEN found, skipping GitHub
 release.")
		return
	}

	repoURL := run("git remote 
get-url origin")
	re := regexp.MustCompile
(`github\.com[:/](.+?)/(.+?)(\.git)?$`)
	match := re.FindStringSubmatch
(repoURL)
	if match == nil {
		fmt.Println("⚠️ 
Could not parse GitHub repo URL.")
		return
	}

	owner, repo := match[1], match[2]
	url := fmt.Sprintf(
"https://api.github.com/repos/%s/%s/releases",
 owner, repo)

	payload := map[string]interface{}{
		"tag_name":   tag,
		"name":      
 fmt.Sprintf("%s — %s", tag, message),
		"body":       notes,
		"draft":      false,
		"prerelease": false,
	}
	body, _ := json.Marshal(payload)

	req, _ := http.NewRequest
("POST", url, bytes.NewBuffer(body))
	req.Header.Add("Authorization", 
"token "+token)
	req.Header.Add("User-Agent", 
"jsonc-pipeline")
	req.Header.Add("Content-Type",
 "application/json")

	client := &http.Client{}
	res, err := client.Do(req)
	if err == nil {
		fmt.Printf("๐Ÿ“ค GitHub 
release created for %s (status: %s)\n", 
tag, res.Status)
	}
}

func main() {
	fmt.Println("๐Ÿš€ Starting 
Go JSONC pipeline...")

	message := updateJSONC()
	tag := gitCommitAndTag(message)

	appendFile(changelogFile,
 fmt.Sprintf("[%s] %s — %s\n", 
time.Now().UTC().Format(time.RFC3339), 
tag, message))
	appendFile(releaseNotes, 
fmt.Sprintf("\n## %s\n%s\n", tag, message))
	createGitHubRelease(tag, 
message, message)

	fmt.Println("๐ŸŽ‰ Pipeline complete!")
}

⚙️ Setup (Same for All Three)

  1. Initialize Git Repo & JSONC File

    git init
    echo '{ "name": "MyApp", "version": 
  2. "v1" }' > config.jsonc
    git add .
    git commit -m "initial"
    git branch -M main
    git remote add origin https
  3. ://github.com/<user>/<repo>.git
    
  4. Set GitHub Token

    export GITHUB_TOKEN=ghp_yourtoken
    
  5. Run

    • Node.js → node run_pipeline.js
    • Python → python run_pipeline.py
    • Go → go run run_pipeline.go

Here include a CI/CD workflow (GitHub Actions) so this runs automatically whenever you push to main or merge a PR?

Perfect ๐Ÿ˜Ž — let’s make your pipeline fully automated in CI/CD using GitHub Actions.

This workflow will:
✅ Run your Node.js / Python / Go JSONC release pipeline
✅ Auto-commit, tag, and push updates
✅ Automatically publish GitHub Releases when a new tag is created
✅ Work securely with your GITHUB_TOKEN (no need for extra setup)

๐Ÿš€ .github/workflows/jsonc-release.yml

name: JSONC Auto Release Pipeline

on:
  push:
    branches:
      - main
  workflow_dispatch: # allows manual 
run from GitHub UI

permissions:
  contents: write  # required for
 creating commits/tags/releases

jobs:
  release:
    runs-on: ubuntu-latest

    steps:
      # ------------------- 1. 
Checkout Repository -------------------
      - name: Checkout code
        uses: actions/checkout@v4

      # ------------------- 2. 
Setup Node (for JSONC + Git 
release) -------------------
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      # ------------------- 3.
 Setup Python -------------------
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      # ------------------- 4. 
Setup Go -------------------
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      # ------------------- 5.
 Install Dependencies -------------------
      - name: Install dependencies
        run: |
          npm install jsonc-parser
          pip install commentjson requests
          go get github.com/tidwall/jsonc

      # ------------------- 6. 
Run JSONC Pipeline -------------------
      # Pick ONE script
 depending on your preferred language
      # (Uncomment the one you use)

      - name: Run Node.js pipeline
        run: node run_pipeline.js

      # - name: Run Python pipeline
      #   run: python run_pipeline.py

      # - name: Run Go pipeline
      #   run: go run run_pipeline.go

      # ------------------- 7. 
Push new tags and commits -------------------
      - name: Push changes back to repo
        env:
          GITHUB_TOKEN: ${{ 
secrets.GITHUB_TOKEN }}
        run: |
          git config user.name 
"github-actions"
          git config user.email
 "actions@github.com"
          git push origin main --tags

๐Ÿงฐ How It Works

๐Ÿ” Trigger

  • Runs automatically when you push or merge to main
  • You can also trigger it manually from the GitHub Actions tab (Run workflow)

⚙️ Actions

  1. Checks out your repository
  2. Sets up Node.js, Python, and Go (so you can run any pipeline)
  3. Installs dependencies
  4. Runs your JSONC pipeline (run_pipeline.js / .py / .go)
  5. Pushes back changes, commits, and tags
  6. Automatically publishes a GitHub Release (since your script uses GITHUB_TOKEN)

✅ Security Notes

  • You don’t need to manually add a personal token — GitHub Actions provides a secure GITHUB_TOKEN automatically.
  • It has permission to create commits, tags, and releases.

๐Ÿงพ Example Workflow Output

After merging or pushing to main:

  1. config.jsonc gets updated (version → next value)
  2. changelog.txt and RELEASE_NOTES.md updated
  3. Git tag created (e.g., v3)
  4. GitHub Release appears under your repo’s Releases tab ๐ŸŽ‰

 builds and attaches binaries or ZIPs (e.g., compiled .exe.tar.gz, or .zip files) to each GitHub Release automatically?

Excellent ๐Ÿ˜Ž — now let’s extend the workflow to not only tag and publish releases automatically,
but also build and attach binaries or ZIP archives (for Node.js, Python, or Go projects).

This is the kind of setup you’d see in a production CI/CD pipeline used for open-source releases ๐Ÿš€

⚙️ Full Workflow — .github/workflows/jsonc-release.yml

name: JSONC Auto Release with Artifacts

on:
  push:
    branches: [main]
  workflow_dispatch: # allows manual run

permissions:
  contents: write  # needed to
 create commits, tags, and releases

jobs:
  release:
    runs-on: ubuntu-latest

    steps:
      # ------------------- 1. 
Checkout -------------------
      - name: Checkout repository
        uses: actions/checkout@v4

      # ------------------- 2.
 Setup Languages -------------------
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      # ------------------- 3. 
Install Dependencies -------------------
      - name: Install dependencies
        run: |
          npm install jsonc-parser
          pip install commentjson requests
          go get github.com/tidwall/jsonc

      # ------------------- 4. 
Run Pipeline Script -------------------
      # Choose the one that
 matches your project:
      - name: Run JSONC pipeline (Node)
        run: node run_pipeline.js

      # - name: Run JSONC pipeline (Python)
      #   run: python run_pipeline.py

      # - name: Run JSONC pipeline (Go)
      #   run: go run run_pipeline.go

      # ------------------- 5. 
Build Release Artifacts -------------------
      # Examples — adjust for
 your language/project type
      - name: Build artifacts
        run: |
          mkdir dist

          # Node.js build (if applicable)
          if [ -f package.json ]; then
            npm pack
            mv *.tgz dist/
          fi

          # Python build (wheel + source)
          if [ -f setup.py ]; then
            python setup.py sdist bdist_wheel
            mv dist/* dist/ 2>
/dev/null || true
          fi

          # Go binary build
          if [ -f go.mod ]; then
            go build -o dist/jsonc-pipeline .
          fi

          # ZIP all outputs for convenience
          cd dist
          zip -r release_files.zip .
          cd ..

      # ------------------- 6.
 Create Release and Upload Artifacts ---
----------------
      - name: Create GitHub Release
        id: create_release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/release_files.zip
          name: Auto Release $
{{ github.run_number }}
          tag_name: v${{ github.run_number }}
          body_path: RELEASE_NOTES.md
        env:
          GITHUB_TOKEN: ${{ 
secrets.GITHUB_TOKEN }}

      # ------------------- 7. 
Push Updated Changes -------------------
      - name: Push commits and tags
        env:
          GITHUB_TOKEN: ${{ 
secrets.GITHUB_TOKEN }}
        run: |
          git config user.name "github-actions"
          git config user.email 
"actions@github.com"
          git push origin main --tags

๐Ÿงพ What This Workflow Does

StepDescription
1–2Checks out code and sets up Node, Python, and Go
3Installs dependencies (for JSONC parsing)
4Runs your JSONC version-bump pipeline
5Builds project artifacts (Node .tgz, Python .whl, Go binary, ZIP)
6Creates a GitHub Release with attached build ZIP
7Pushes updated changelog + tags to main

๐Ÿ“ฆ Example Result on GitHub

Each release (e.g., v23) will show:

  • ✅ Updated version in config.jsonc
  • ๐Ÿ—’️ New entry in changelog.txt
  • ๐Ÿ“ RELEASE_NOTES.md as the release description
  • ๐Ÿ“ฆ release_files.zip containing:
    • Node.js .tgz
    • Python .whl / .tar.gz
    • Go binary

๐Ÿงฐ Tips

  • If your project is only one language, you can delete the other build sections.
  • For multi-platform Go builds, you can use:
    GOOS=windows GOARCH=amd64 go
  •  build -o dist/jsonc-pipeline.exe .
    GOOS=linux GOARCH=amd64 go
  •  build -o dist/jsonc-pipeline-linux .
    GOOS=darwin GOARCH=arm64 go
  •  build -o dist/jsonc-pipeline-mac .
    
  • For Node.js, you can replace the npm pack step with a real build command (like npm run build).

this CI setup to also auto-increment the semantic version (v1.0.0 → v1.1.0) based on commit type (featfix, etc.) — similar to how semantic-release works?

Perfect ๐Ÿ‘Œ — this next upgrade turns your GitHub Actions pipeline into a semantic versioning system
(similar to professional CI setups like semantic-release).

It will automatically:
✅ Detect commit types (featfixperfdocs, etc.)
✅ Increment version numbers (v1.0.0 → v1.1.0 → v2.0.0)
✅ Tag + publish GitHub Releases
✅ Attach built artifacts
✅ Update your JSONC version and changelogs automatically

๐Ÿš€ .github/workflows/jsonc-semantic-release.yml

name: JSONC Semantic Release Pipeline

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: write
  issues: read
  pull-requests: read

jobs:
  semantic-release:
    runs-on: ubuntu-latest

    steps:
      # ------------------- 1. 
Checkout -------------------
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 
required for semantic versioning

      # ------------------- 2.
 Setup Node.js -------------------
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      # ------------------- 3.
 Install Dependencies -------------------
      - name: Install release tools
        run: |
          npm install -g 
semantic-release 
@semantic-release/changelog
 @semantic-release/git 
@semantic-release/github
          npm install jsonc-parser

      # ------------------- 4.
 Run Semantic Release -------------------
      - name: Run Semantic Release
        env:
          GITHUB_TOKEN: 
${{ secrets.GITHUB_TOKEN }}
        run: |
          npx semantic-release

      # ------------------- 5.
 Build and Attach Artifacts -----
--------------
      - name: Build release artifacts
        run: |
          mkdir -p dist

          # Optional build logic —
 adjust to your tech stack
          if [ -f package.json ]; then
            npm pack
            mv *.tgz dist/
          fi

          if [ -f setup.py ]; then
            pip install wheel
            python setup.py sdist bdist_wheel
            mv dist/* dist/ 2>/dev/null || true
          fi

          if [ -f go.mod ]; then
            go build -o dist/jsonc-pipeline
          fi

          zip -r dist/release_files.zip dist/*

      # ------------------- 6. 
Upload Artifacts to Release ---
----------------
      - name: Upload build artifacts
        uses: softprops/action-gh-release@v2
        with:
          files: dist/release_files.zip
        env:
          GITHUB_TOKEN: ${{ 
secrets.GITHUB_TOKEN }}

๐Ÿงฉ Add Semantic Release Configuration

Create a file named .releaserc.json in the root of your repo:

{
  "branches": ["main"],
  "plugins": [
    [
      "@semantic-release/commit-analyzer",
      {
        "preset": "conventionalcommits"
      }
    ],
    [
      "@semantic-release/release-
notes-generator",
      {
        "preset": "conventionalcommits"
      }
    ],
    [
      "@semantic-release/changelog",
      {
        "changelogFile": "changelog.txt"
      }
    ],
    [
      "@semantic-release/github",
      {
        "assets": [
          {
            "path": "dist/release_files.zip",
            "label": "Release Build"
          }
        ]
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["config.jsonc",
 "changelog.txt", "RELEASE_NOTES.md"],
        "message": "chore(release):
 ${nextRelease.version} 
[skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ]
}

๐Ÿงพ How It Works

  1. Semantic Commit Detection

    When you push commits like:

    feat: add new login feature
    fix: correct user API bug
    docs: update README
    

    → Semantic Release detects the highest impact and bumps:

    • fix: → patch (v1.0.1)
    • feat: → minor (v1.1.0)
    • BREAKING CHANGE: → major (v2.0.0)
  2. Automatic Version + Release Notes

    • Updates config.jsonc version (if included in your script)
    • Generates changelog entries in changelog.txt
    • Publishes a GitHub Release with attached ZIP artifacts
  3. Artifacts Attached

    • Node .tgz, Python .whl / .tar.gz, or Go binaries
    • All compressed in release_files.zip

✅ Example Output

After merging a PR:

  • config.jsonc → "version": "v1.2.0"
  • changelog.txt updated
  • Git tag v1.2.0 created
  • GitHub Release with notes
  •  + build artifacts attached
  • No manual tagging or pushing needed ๐ŸŽ‰

An auto-update step for config.jsonc’s version field
so Semantic Release writes the new version back into that file automatically (and commits it)?

Awesome ๐Ÿ˜Ž — let’s finish the setup so your config.jsonc version also updates automatically each time a new semantic release is created.

This makes your JSONC version file the single source of truth, just like package.json or pyproject.toml.

๐Ÿงฉ Step 1 — Add a Custom Version Updater Script

Create a file named update-jsonc-version.js in your repository root:

// update-jsonc-version.js
import fs from "fs";
import { parse, printParseErrorCode,
 modify } from "jsonc-parser";

const filePath = "./config.jsonc";
const version = process.env.RELEASE_VERSION;

if (!version) {
  console.error("❌ RELEASE_VERSION 
environment variable not set");
  process.exit(1);
}

const text = fs.readFileSync(filePath, "utf8");
let errors = [];
let json = parse(text, errors);

if (errors.length > 0) {
  console.error("❌ Failed to parse
 JSONC:");
  errors.forEach((e) => console.error
(printParseErrorCode(e.error)));
  process.exit(1);
}

// Apply modification to version field
const edits = modify(text, ["version"],
 version, { formattingOptions: 
{ insertSpaces: true, tabSize: 2 } });
const updated = applyEdits(text, edits);

fs.writeFileSync(filePath, updated, "utf8");
console.log(`✅ Updated config.jsonc
 version to ${version}`);

function applyEdits(text, edits) {
  let result = text;
  for (let i = edits.length - 1;
 i >= 0; i--) {
    const edit = edits[i];
    result = result.substring
(0, edit.offset) + edit.content 
+ result.substring(edit.offset + edit.length);
  }
  return result;
}

This uses the jsonc-parser library to safely update the version field without breaking comments or formatting.

⚙️ Step 2 — Update .releaserc.json Configuration

Add a custom step after Semantic Release runs to invoke this updater:

{
  "branches": ["main"],
  "plugins": [
    [
      "@semantic-release/commit-analyzer",
      { "preset": "conventionalcommits" }
    ],
    [
      "@semantic-release/
release-notes-generator",
      { "preset": "conventionalcommits" }
    ],
    [
      "@semantic-release/changelog",
      { "changelogFile": "changelog.txt" }
    ],
    [
      "@semantic-release/github",
      {
        "assets": [
          { "path": 
"dist/release_files.zip", "label": 
"Release Build" }
        ]
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["config.jsonc", 
"changelog.txt", "RELEASE_NOTES.md"],
        "message": "chore(release):
 ${nextRelease.version}
 [skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ],
  "prepare": [
    {
      "path": "@semantic-release/exec",
      "cmd": "RELEASE_VERSION=
${nextRelease.version} node 
update-jsonc-version.js"
    }
  ]
}

This tells Semantic Release to run your script and update config.jsonc just before committing.

๐Ÿงฑ Step 3 — Add Dependency to package.json

If your repo has a Node.js environment, add this dependency:

npm install jsonc-parser

๐Ÿš€ Step 4 — Run the Workflow

Each time you push commits to main that follow Conventional Commit rules:

feat: add support for JSONC comments
fix: correct missing comma issue

The pipeline will:

  1. Determine new version (e.g. 1.1.0)
  2. Update:
    • config.jsonc → "version": "1.1.0"
    • changelog.txt with new section
  3. Commit those updates back to the repo
  4. Create Git tag + GitHub Release
  5. Attach your build ZIP

๐Ÿงพ Example config.jsonc Before and After

Before:

{
  // Application configuration
  "name": "jsonc-pipeline",
  "version": "1.0.0"
}

After:

{
  // Application configuration
  "name": "jsonc-pipeline",
  "version": "1.1.0"
}

An example of Conventional Commit messages and how they map to automatic version bumps (major/minor/patch)?

Perfect ๐Ÿ‘ — here’s the Conventional Commit guide for your JSONC Semantic Release setup.

These are the commit message formats that trigger automatic version bumps in your GitHub Actions pipeline.

๐Ÿงญ What Are Conventional Commits?

Conventional Commits follow this pattern:

<type>(optional scope): <description>

[optional body]

[optional footer]

Example:

feat(parser): add JSONC comment 
stripping support

๐Ÿš€ Version Bump Rules (How Semantic Release Decides the Next Version)

Commit TypeExampleVersion BumpDescription
fix:fix: handle trailing commas in JSONCPatch (x.y.z → x.y.z+1)For bug
 fixes or small corrections
feat:feat(parser): add comment parsing featureMinor (x.y.z → x.y+1.0)For new features that don’t break existing code
perf:perf: improve JSONC parse speed by 20%MinorPerformance improvements
docs:docs: update README with JSONC examplesNo bumpDocumentation-only changes
style:style: format code with PrettierNo bumpCode style, linting, etc.
refactor:refactor: simplify parse logicNo bumpInternal code changes, no user impact
test:test: add new parser unit testsNo bumpTesting-only changes
chore:chore: update dependenciesNo bumpMaintenance
 or tooling changes
BREAKING CHANGE:Footer: BREAKING CHANGE: parser API updatedMajor (x.y.z → x+1.0.0)When backward compatibility
 is broken

๐Ÿ’ก Example Scenarios

๐Ÿงฑ 1. Small Fix

Commit:

fix(parser): handle null values correctly

→ Version bumps from 1.0.0 → 1.0.1

๐ŸŒŸ 2. New Feature

Commit:

feat(cli): add --strip-comments option

→ Version bumps from 1.0.1 → 1.1.0

๐Ÿ’ฅ 3. Breaking Change

Commit:

feat(parser): switch to async API

BREAKING CHANGE: old parse() method removed

→ Version bumps from 1.1.0 → 2.0.0

๐Ÿ“œ 4. Non-Impactful Change

Commit:

docs: update usage examples in README

→ No version bump, no release.

๐Ÿงฉ Combining Multiple Commits

If your merge includes several commits:

  • One feat: and one fix: → bumps minor
  • One BREAKING CHANGE: → bumps major
  • Only fix: commits → bumps patch

Semantic Release always chooses the highest-impact bump in a batch.

✅ Recommended Workflow for Your Project

  1. Use clear Conventional Commit messages.
  2. Push or merge into main.
  3. GitHub Actions runs the semantic release pipeline:
    • Detects commit types
    • Updates config.jsonc version
    • Updates changelog.txt
    • Publishes GitHub Release + assets

ready-made Git commit template (so developers automatically follow this format when committing)?

Perfect ๐Ÿ˜Ž — let’s add a Git commit message template so that everyone on your project automatically follows the correct Conventional Commit format (making Semantic Release work smoothly every time).

๐Ÿงฉ Step 1 — Create a Commit Template File

Create a file at the root of your repo called:
.gitmessage.txt

Paste this inside:

# ๐Ÿงฑ Conventional Commit Template
#
# Format: <type>(optional scope)
: <short summary>
# Example: feat(parser): 
add JSONC comment support
#
# Types: feat | fix | perf |
 docs | style | refactor | test | chore
#
# BREAKING CHANGES:
# Add a footer starting
 with BREAKING CHANGE: to 
indicate breaking updates

# Commit title (keep under 100 characters)
feat(parser): 

# Commit body (optional) – 
explain why the change was made
# Use full sentences and
 mention issue numbers if any.

# Footer (optional)
# BREAKING CHANGE: 
description of what changed
# Closes: #123

⚙️ Step 2 — Configure Git to Use This Template

Run this command in your repository:

git config commit.template .gitmessage.txt

Now, every time you run git commit, the editor will open with this prefilled structure.
That ensures all developers stick to the same format automatically.

๐Ÿง  Optional — Enforce Format with a Commit Linter

You can use commitlint to reject commits that don’t follow the rules.

Install:

npm install --save-dev
 @commitlint/{config-conventional,cli} husky

Add config files:

commitlint.config.js

export default {
  extends: ['@commitlint/config-conventional']
};

.husky/commit-msg (make it executable with chmod +x .husky/commit-msg)

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit "$1"

Initialize Husky hooks:

npx husky install
npm pkg set scripts.prepare="husky install"

Now any bad commit message (e.g. “update stuff”) will be rejected automatically.

✅ Developer Workflow Example

# Create a feature branch
git checkout -b feature/jsonc-support

# Make changes, then commit
git commit

# The editor opens with the 
Conventional Commit template.
# Fill it out, for example:
feat(parser): add JSONC 
comment stripping support

# Push to main
git push origin feature/jsonc-support

When merged into main, your GitHub Action pipeline will:

  • Detect feat → bump minor version
  • Update config.jsonc and changelog.txt
  • Create a GitHub release
  • Attach build artifacts

ready-to-copy Husky + Commitlint setup inside your existing GitHub workflow (so all contributors are automatically linted in CI)?

Excellent ๐Ÿ”ฅ — let’s now fully integrate Commitlint + Husky into your existing GitHub Actions and local developer setup.

This ensures that every commit, whether local or from a pull request, follows the Conventional Commits format your Semantic Release system depends on.

๐Ÿงฑ Step 1 — Install the Tools

Run these once in your project:

npm install --save-dev husky
 @commitlint/{config-conventional,cli}

This adds both Husky (for Git hooks) and Commitlint (for validation).

⚙️ Step 2 — Configure Commitlint

Create a file at the root of your repo:

commitlint.config.js

export default {
  extends: ['@commitlint/config-conventional'],
};

This tells Commitlint to enforce the official Conventional Commit rules.

⚙️ Step 3 — Set Up Husky Hooks

Initialize Husky:

npx husky install

Then tell npm to reinstall Husky automatically whenever someone clones your repo:

npm pkg set scripts.prepare="husky install"

Now, add a commit hook:

npx husky add .husky/commit-msg
 'npx --no-install commitlint --edit "$1"'

Make sure it’s executable:

chmod +x .husky/commit-msg

✅ From now on, every time someone runs git commit, Husky triggers Commitlint — and blocks invalid messages like “updated stuff” or “bug fix”.

๐Ÿงฉ Step 4 — Update Your .gitmessage.txt Template (Optional)

You can combine this with the commit template we added earlier for guidance.

Add this to your .gitmessage.txt:

# ๐Ÿงฑ Conventional Commit Template
# feat, fix, docs, style,
 refactor, perf, test, chore
# Example: feat(parser): add 
JSONC comment stripping
feat(scope): short message

# Body (optional): explain why
 and what changed

# Footer (optional)
# BREAKING CHANGE: details
# Closes: #123

Then set it globally:

git config commit.template .gitmessage.txt

⚙️ Step 5 — Enforce Commitlint in GitHub Actions (CI Check)

To make sure all contributors (even those not using Husky locally) follow the rules,
add this extra job to your existing GitHub workflow (for example, inside .github/workflows/jsonc-semantic-release.yml):

  lint-commits:
    name: Validate Conventional Commits
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Commitlint
        run: npm install --save-dev 
@commitlint/{config-conventional,cli}

      - name: Lint commit messages
        run: |
          npx commitlint --from=$(git rev-list --max-parents=0 HEAD) --to=HEAD

This job:

  • Checks every commit in the push/PR.
  • Fails the workflow if a bad commit message is detected.

✅ Example CI + Local Workflow Summary

ActionToolResult
Developer commits locallyHusky + CommitlintStops invalid commit message
Commit message template.gitmessage.txtHelps users follow the format
GitHub Actions checklint-commits jobRejects bad PRs or pushes
Semantic ReleaseGitHub ActionsPublishes new versions automatically

๐Ÿง  Example in Practice

✅ Good:

feat(parser): add support for
 trailing commas in JSONC

✅ Good:

fix(config): handle missing
 version field gracefully

❌ Bad:

update parser

❌ Bad:

bug fix

Now merge all this (semantic release + commitlint + husky + artifact build) into a single unified GitHub Actions workflow file so you can copy-paste it directly into .github/workflows/ci.yml?

Perfect ๐Ÿ’ช — here’s your complete, production-ready GitHub Actions workflow
that unifies everything:

✅ Semantic Release (auto versioning)
✅ JSONC version updater
✅ Build + attach artifacts (Node.js / Python / Go)
✅ Commitlint validation (Conventional Commits)
✅ Fully automated changelog and GitHub release

๐Ÿš€ .github/workflows/ci.yml

name: JSONC CI & Semantic Release Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: write
  issues: read
  pull-requests: read

jobs:
  # ------------------- ๐Ÿงฉ 1.
 Commit Message Validation --------
-----------
  lint-commits:
    name: Validate Conventional Commits
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Commitlint
        run: npm install --
save-dev @commitlint/{config-conventional,cli}

      - name: Lint commit messages
        run: npx commitlint
 --from=$(git rev-list -
-max-parents=0 HEAD) --to=HEAD

  # ------------------- ๐Ÿš€
 2. Build, Version, and Release ---
----------------
  release:
    name: Semantic Release & Build
    runs-on: ubuntu-latest
    needs: lint-commits

    steps:
      # Checkout full history 
for semantic-release
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Setup Node, Python, and Go
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      # Install Dependencies
      - name: Install dependencies
        run: |
          npm install -g 
semantic-release @semantic-release/
changelog @semantic-release/git 
@semantic-release/github @semantic-release/exec
          npm install jsonc-parser
          pip install wheel
          go mod tidy || true

      # ------------------- 
๐Ÿง  JSONC Version Updater -------------------
      - name: Create version update script
        run: |
          cat <<'EOF' > 
update-jsonc-version.js
          import fs from "fs";
          import { parse, modify, 
printParseErrorCode } from "jsonc-parser";

          const filePath =
 "./config.jsonc";
          const version = 
process.env.RELEASE_VERSION;
          if (!version) {
            console.error
("❌ RELEASE_VERSION not set");
            process.exit(1);
          }

          const text =
 fs.readFileSync(filePath, "utf8");
          let errors = [];
          const json = parse(text, errors);

          if (errors.length) {
            console.error("❌ Parse error:", errors.map(e => printParseErrorCode(e.error)).join(", "));
            process.exit(1);
          }

          const edits =
 modify(text, ["version"], version, { formattingOptions: { insertSpaces: true, tabSize: 2 } });
          let result = text;
          for (let i = edits.length - 1; i >= 0; i--) {
            const e = edits[i];
            result = result.slice(0, e.offset) + e.content + result.slice(e.offset + e.length);
          }

          fs.writeFileSync(filePath, result, "utf8");
          console.log("✅ Updated config.jsonc version to", version);
          EOF

      # ------------------- ๐Ÿ“ฆ Build Artifacts -------------------
      - name: Build artifacts
        run: |
          mkdir -p dist

          # Node.js
          if [ -f package.json ]; then
            npm pack
            mv *.tgz dist/
          fi

          # Python
          if [ -f setup.py ]; then
            python setup.py sdist bdist_wheel
            mv dist/* dist/ 2>/dev/null || true
          fi

          # Go
          if [ -f go.mod ]; then
            go build -o dist/jsonc-pipeline
          fi

          zip -r dist/release_files.zip dist/*

      # ------------------- ๐Ÿš€ Run
 Semantic Release -------------------
      - name: Run Semantic Release
        env:
          GITHUB_TOKEN: ${{ 
secrets.GITHUB_TOKEN }}
        run: |
          echo '{
            "branches": ["main"],
            "plugins": [
              ["@semantic-release/
commit-analyzer", {"preset": "conventionalcommits"}],
              ["@semantic-release/
release-notes-generator", {"preset": 
"conventionalcommits"}],
              ["@semantic-release/
changelog", {"changelogFile": "changelog.txt"}],
              ["@semantic-release/
exec", {"prepareCmd": "RELEASE_VERSION=${nextRelease.version} node update-jsonc-version.js"}],
              ["@semantic-release/git", {
                "assets": ["
config.jsonc", "changelog.txt", 
"RELEASE_NOTES.md"],
                "message": "
chore(release): ${nextRelease.version} 
[skip ci]\\n\\n${nextRelease.notes}"
              }],
              ["@semantic-
release/github", {
                "assets": [{
"path": "dist/release_files.zip",
 "label": "Release Build"}]
              }]
            ]
          }' > .releaserc.json

          npx semantic-release

๐Ÿงฉ Folder Setup Overview

.
├── config.jsonc
├── changelog.txt
├── RELEASE_NOTES.md
├── package.json
├── setup.py        (optional)
├── go.mod          (optional)
├── .gitmessage.txt
├── commitlint.config.js
└── .github/
    └── workflows/
        └── ci.yml

✅ What Happens When You Push

StepAction
1️⃣GitHub validates commit messages with Commitlint
2️⃣Semantic Release runs, analyzes commits
3️⃣Updates config.jsonc version
4️⃣Updates changelog.txt & RELEASE_NOTES.md
5️⃣Builds artifacts (Node, Python, Go)
6️⃣Publishes a GitHub Release with ZIP attachment
7️⃣Commits version + changelog back to main

๐Ÿง  Example Commit & Result

Commit:

feat(parser): add JSONC comment stripping support

Result:

  • Version bump: 1.0.0 → 1.1.0
  • config.jsonc updated automatically
  • changelog.txt entry added
  • GitHub Release created with artifact ZIP

add an optional step that automatically publishes the built package
to npm (for Node.js) or PyPI (for Python) 

when a release is made?

Excellent ๐Ÿ”ฅ — let’s finish your CI/CD pipeline by adding the final publishing step,
so that every successful release automatically uploads your built packages to:

  • ๐Ÿ“ฆ npm (for Node.js modules)
  • ๐Ÿ PyPI (for Python packages)
  • ๐Ÿ’พ (optional) Go release binaries are already uploaded to GitHub

This makes your JSONC project a fully automated, versioned, and deployable package.

๐Ÿš€ Final Version — .github/workflows/ci.yml

Here’s the complete end-to-end workflow ready to copy into your repo:

name: JSONC CI, Release & Publish Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: write
  issues: read
  pull-requests: read
  packages: write

jobs:
  # ------------------- ๐Ÿงฉ 1. 
Validate Commit Messages -------------------
  lint-commits:
    name: Validate Conventional Commits
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Commitlint
        run: npm install --save-dev 
@commitlint/{config-conventional,cli}

      - name: Lint commits
        run: npx commitlint --
from=$(git rev-list --
max-parents=0 HEAD) --to=HEAD

  # ------------------- ๐Ÿš€ 2. 
Build, Release, and Publish -------
------------
  release:
    name: Build, Semantic Release & Publish
    runs-on: ubuntu-latest
    needs: lint-commits

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Setup environments
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      # Install required dependencies
      - name: Install dependencies
        run: |
          npm install -g semantic-
release @semantic-release/changelog
 @semantic-release/git @semantic
-release/github @semantic-release/exec
          npm install jsonc-parser
          pip install wheel twine
          go mod tidy || true

      # ------------------- ๐Ÿง  
JSONC Version Updater -------------------
      - name: Create JSONC version
 update script
        run: |
          cat <<'EOF' > 
update-jsonc-version.js
          import fs from "fs";
          import { parse,
 modify, printParseErrorCode } 
from "jsonc-parser";
          const filePath = "./config.jsonc";
          const version = 
process.env.RELEASE_VERSION;
          if (!version) 
{ console.error("❌ RELEASE_VERSION not set"); process.exit(1); }
          const text = 
fs.readFileSync(filePath, "utf8");
          let errors = 
[];
          const json = parse(text, errors);
          if (errors.length) {
            console.
error("❌ Parse error:", 
errors.map(e => 
printParseErrorCode(e.error)).join(", "));
            process.exit(1);
          }
          const edits =
 modify(text, ["version"], 
version, { formattingOptions: 
{ insertSpaces: true, tabSize: 2 } });
          let result = text;
          for (let i = 
edits.length - 1; i >= 0; i--) {
            const e = edits[i];
            result = 
result.slice(0, e.offset) + 
e.content + result.slice(e.offset + e.length);
          }
          fs.writeFileSync
(filePath, result, "utf8");
          console.log("✅ 
Updated config.jsonc version to", version);
          EOF

      # -------------------
 ๐Ÿ“ฆ Build Artifacts -------------------
      - name: Build artifacts
        run: |
          mkdir -p dist
          # Node.js package
          if [ -f package.json ]; then
            npm pack
            mv *.tgz dist/
          fi
          # Python package
          if [ -f setup.py ]; then
            python setup.py sdist bdist_wheel
            mv dist/* dist/
 2>/dev/null || true
          fi
          # Go binary
          if [ -f go.mod ]; then
            go build -o dist/jsonc-pipeline
          fi
          zip -r dist/release_files.zip dist/*

      # ------------------- 
๐Ÿš€ Semantic Release -------------------
      - name: Run Semantic Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo '{
            "branches": ["main"],
            "plugins": [
              ["@semantic-
release/commit-analyzer", {"preset":
 "conventionalcommits"}],
              ["@semantic-
release/release-notes-generator
", {"preset": "conventionalcommits"}],
              ["@semantic-
release/changelog", {"changelogFile
": "changelog.txt"}],
              ["@semantic-
release/exec", {"prepareCmd":
 "RELEASE_VERSION=$
{nextRelease.version} node
 update-jsonc-version.js"}],
              ["@semantic-release/git", {
                "assets": 
["config.jsonc", "changelog.txt", 
"RELEASE_NOTES.md"],
                "message":
 "chore(release): $
{nextRelease.version}
 [skip ci]\\n\\n${nextRelease.notes}"
              }],
              ["@semantic-release/github", {
                "assets":
 [{"path": "dist/release_files.zip", 
"label": "Release Build"}]
              }]
            ]
          }' > .releaserc.json

          npx semantic-release

      # ------------------- 
๐Ÿ“ค Publish to npm -------------------
      - name: Publish to npm
        if: ${{ env.NPM_TOKEN !=
 '' && hashFiles('package.json') != '' }}
        env:
          NODE_AUTH_TOKEN:
 ${{ secrets.NPM_TOKEN }}
        run: |
          echo "//registry.
npmjs.org/:_authToken=$
{NODE_AUTH_TOKEN}" > ~/.npmrc
          npm publish --
access public || echo "⚠️
 npm publish skipped"

      # ------------------- 
๐Ÿ Publish to PyPI -------------------
      - name: Publish to PyPI
        if: ${{ 
secrets.PYPI_USERNAME
 != '' && secrets.PYPI_PASSWORD !=
 '' && hashFiles('setup.py') != '' }}
        run: |
          twine upload dist/*
 -u ${{ secrets.PYPI_USERNAME }} 
-p ${{ secrets.PYPI_PASSWORD }}
 || echo "⚠️ PyPI publish skipped"

๐Ÿงพ Required Repository Secrets

SecretPurpose
GITHUB_TOKENAuto-generated by GitHub for release commits
NPM_TOKENYour npm auth token (for npm publish)
PYPI_USERNAMEPyPI username (for Python publish)
PYPI_PASSWORDPyPI password or API token

✅ Summary of What Happens

StepAction
๐Ÿงฉ CommitlintEnsures Conventional Commit messages
๐Ÿง  JSONC Version UpdaterUpdates version in config.jsonc
๐Ÿ—️ Build StageBuilds Node, Python, and Go artifacts
๐Ÿš€ Semantic ReleaseBumps version, updates changelog, tags release
๐Ÿ“ฆ PublishPushes to npm and/or PyPI
๐Ÿ’พ GitHub ReleaseUploads artifacts ZIP automatically

๐Ÿง  Example

Commit:

feat(parser): add JSONC multiline
 comment support

Result:

  • Version: 1.0.0 → 1.1.0
  • config.jsonc updated
  • changelog.txt updated
  • GitHub release created with ZIP
  • npm and/or PyPI publish triggered ๐ŸŽฏ

✅ You now have a complete, professional-grade CI/CD setup
for JSONC-based projects — ready to auto-version, release, and deploy
across Node.js, Python, and Go environments.


GPAI refers to General-Purpose AI next level of artificial intelligence

"GPAI" refers to "General-Purpose AI" in the sense of broadly capable, task-agnostic systems spanning modalities, tools,...