npm package: @hiscojs/jsonnet-updater
Description: Type-safe, immutable Jsonnet updates with local variable management, function definitions, comment preservation, and advanced array merging strategies
Latest version: 1.4.0

# @hiscojs/jsonnet-updater

Type-safe, immutable Jsonnet updates with local variable management, function definitions, comment preservation, and advanced array merging strategies.

## Features

- 🔒 **Type-safe updates** using TypeScript proxies for automatic path detection
- 🎯 **Immutable operations** - Original content never modified
- 📝 **Comment preservation** - Keep your documentation intact
- 📄 **Document headers** - Extract and preserve file headers with multiple formatting styles
- 🔧 **Local variables** - Manage `local` declarations easily (similar to YAML anchors)
- ⚡ **Function definitions** - Create and manage reusable Jsonnet functions
- 🔄 **Advanced array merging** - Multiple strategies (by name, by property, by content)
- 📐 **Formatting preservation** - Maintains indentation and style
- 🎨 **Clean API** - Intuitive, developer-friendly interface

## Installation

```bash
npm install @hiscojs/jsonnet-updater
```

## Quick Start

### Basic Value Update

```typescript
import { updateJsonnet } from '@hiscojs/jsonnet-updater';

const jsonnetString = `
{
  environment: 'dev',
  replicas: 3,
  image: 'myapp:1.0.0'
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj,
      merge: (orig) => ({
        ...orig,
        replicas: 5,
        image: 'myapp:2.0.0'
      })
    });
  }
});

console.log(result);
// Output:
// {
//   environment: 'dev',
//   replicas: 5,
//   image: 'myapp:2.0.0'
// }
```

## Core Concepts

### The `annotate` Pattern

The library uses a `change` function within `annotate` to specify updates. This provides type-safety and immutable updates:

```typescript
annotate: ({ change }) => {
  change({
    findKey: (obj) => obj.parentObject,       // Find the parent object to update
    merge: (original) => ({                    // Return updated parent object
      ...original,                             // Spread original properties
      propertyToUpdate: newValue               // Override specific properties
    })
  });
}
```

**Important**: Always find the parent object containing the property you want to update, then return the complete updated parent object using the spread operator.

### Return Type

All operations return a `JsonnetEdit<T>` object:

```typescript
interface JsonnetEdit<T> {
  result: string;           // Updated Jsonnet string
  resultParsed: T;          // Parsed object (evaluated Jsonnet)
  originalParsed: T;        // Original parsed object
  locals: LocalVariable[];  // Defined local variables
  functions: LocalFunction[]; // Defined local functions
}
```

## Working with Local Variables

Local variables in Jsonnet are like YAML anchors - they allow you to define reusable values.

### Adding Local Variables

```typescript
import { updateJsonnet } from '@hiscojs/jsonnet-updater';

const jsonnetString = `
{
  name: 'myapp',
  version: '1.0.0'
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  locals: [
    {
      name: 'namespace',
      value: 'production'
    },
    {
      name: 'imageTag',
      value: 'v2.0.0'
    }
  ],
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.namespace,
      merge: () => '$.namespace'  // Reference the local variable
    });
  }
});

console.log(result);
// Output:
// local namespace = 'production';
// local imageTag = 'v2.0.0';
// {
//   name: 'myapp',
//   version: '1.0.0',
//   namespace: $.namespace
// }
```

### Using Existing Local Variables

```typescript
const jsonnetString = `
local environment = 'dev';
local replicas = 3;

{
  env: environment,
  count: replicas
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  locals: [
    { name: 'replicas', value: 5 }  // Override the local variable
  ]
});

console.log(result);
// Output:
// local environment = 'dev';
// local replicas = 5;  // <-- Updated
//
// {
//   env: environment,
//   count: replicas
// }
```

## Working with Local Functions

Local functions provide reusable logic, similar to how you might use YAML anchors for complex structures.

### Defining Local Functions

```typescript
import { updateJsonnet } from '@hiscojs/jsonnet-updater';

const jsonnetString = `
{
  services: []
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  functions: [
    {
      name: 'createService',
      params: ['name', 'port'],
      body: `{
  name: name,
  port: port,
  protocol: 'TCP'
}`
    }
  ],
  annotate: ({ change, functions }) => {
    change({
      findKey: (obj) => obj.services,
      merge: () => [
        '$.createService("api", 8080)',
        '$.createService("web", 3000)'
      ]
    });
  }
});

console.log(result);
// Output:
// local createService(name, port) = {
//   name: name,
//   port: port,
//   protocol: 'TCP'
// };
//
// {
//   services: [
//     $.createService("api", 8080),
//     $.createService("web", 3000)
//   ]
// }
```

### Complex Function Example

```typescript
const { result } = updateJsonnet({
  jsonnetString: '{}',
  functions: [
    {
      name: 'createDeployment',
      params: ['name', 'image', 'replicas'],
      body: `{
  apiVersion: 'apps/v1',
  kind: 'Deployment',
  metadata: {
    name: name
  },
  spec: {
    replicas: replicas,
    template: {
      spec: {
        containers: [{
          name: name,
          image: image
        }]
      }
    }
  }
}`
    }
  ],
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.deployment,
      merge: () => '$.createDeployment("myapp", "myapp:2.0", 3)'
    });
  }
});
```

## Advanced Array Merging

Use `addInstructions` for sophisticated array handling:

```typescript
import { updateJsonnet, addInstructions } from '@hiscojs/jsonnet-updater';

const jsonnetString = `
{
  services: [
    { name: 'api', port: 8080 },
    { name: 'web', port: 3000 }
  ]
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.services,
      merge: (current) => [
        ...current,
        ...addInstructions({
          prop: 'services',
          mergeByName: true  // Merge by 'name' property
        }),
        { name: 'api', port: 8081, replicas: 3 },  // Updates existing
        { name: 'cache', port: 6379 }              // Adds new
      ]
    });
  }
});

console.log(result);
// Output:
// {
//   services: [
//     { name: 'api', port: 8081, replicas: 3 },  // Merged by name
//     { name: 'web', port: 3000 },
//     { name: 'cache', port: 6379 }              // Added
//   ]
// }
```

### Merge Strategies

```typescript
...addInstructions({
  prop: 'arrayName',
  mergeByName: true,        // Merge by 'name' property
  // OR
  mergeByProp: 'id',        // Merge by specific property
  // OR
  mergeByContents: true,    // Merge by full content comparison

  deepMerge: true           // Deep merge objects (default: false)
})
```

## Property Deletion

Delete properties using the `exclude` helper function:

```typescript
import { updateJsonnet, exclude } from '@hiscojs/jsonnet-updater';

const jsonnetString = `
{
  name: 'myapp',
  version: '1.0.0',
  deprecated: true,
  legacy: 'old-value'
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj,
      merge: (orig) => exclude(orig, 'deprecated', 'legacy')
    });
  }
});

console.log(result);
// Output:
// {
//   name: 'myapp',
//   version: '1.0.0'
// }
```

### Deleting Properties with Updates

Combine `exclude` with the spread operator for partial updates:

```typescript
const { result } = updateJsonnet({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj,
      merge: (orig) => ({
        ...exclude(orig, 'deprecated'),
        version: '2.0.0',           // Update existing
        environment: 'production'    // Add new
      })
    });
  }
});
```

### Deleting Nested Properties

```typescript
const { result } = updateJsonnet({
  jsonnetString: `{
  server: {
    host: 'localhost',
    port: 8080,
    oldTimeout: 30
  }
}`,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.server,
      merge: (orig) => ({
        ...exclude(orig, 'oldTimeout'),
        timeout: 60  // Replace with new property
      })
    });
  }
});
```

**Note**: For deleting array elements, use standard JavaScript array methods like `filter()`:

```typescript
change({
  findKey: (obj) => obj.items,
  merge: (orig) => orig.filter(item => item.active)
});
```

## Nested Object Updates

```typescript
const jsonnetString = `
{
  server: {
    host: 'localhost',
    port: 8080,
    ssl: {
      enabled: false
    }
  }
}
`;

const { result } = updateJsonnet({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.server.ssl,
      merge: (orig) => ({
        ...orig,
        enabled: true,
        cert: '/path/to/cert.pem'
      })
    });
  }
});

console.log(result);
// Output:
// {
//   server: {
//     host: 'localhost',
//     port: 8080,
//     ssl: {
//       enabled: true,
//       cert: '/path/to/cert.pem'
//     }
//   }
// }
```

## Document Headers

Preserve and manage document headers (comments at the top of the file):

### Simple Headers

```typescript
const jsonnetString = `# Application Configuration
# Version: 1.0
# Author: DevOps Team
{
  name: 'myapp',
  version: '1.0.0'
}`;

const { result, extractedHeader } = updateJsonnet({
  jsonnetString,
  documentHeader: {
    type: 'simple',
    content: ['Application Configuration', 'Version: 1.0', 'Author: DevOps Team']
  },
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj,
      merge: (orig) => ({
        ...orig,
        version: '2.0.0'
      })
    });
  }
});

// Output:
// # Application Configuration
// # Version: 1.0
// # Author: DevOps Team
// {
//   name: 'myapp',
//   version: '2.0.0'
// }
```

### Multi-line Bordered Headers

```typescript
const { result } = updateJsonnet({
  jsonnetString: '{ service: "api" }',
  documentHeader: {
    type: 'multi-line',
    content: ['Service Configuration', 'Owner: Platform Team'],
    border: '#',
    width: 50
  }
});

// Output:
// ##################################################
// # Service Configuration
// # Owner: Platform Team
// ##################################################
// {
//   service: 'api'
// }
```

### Raw Headers

```typescript
const { result } = updateJsonnet({
  jsonnetString: '{ generated: true }',
  documentHeader: {
    type: 'raw',
    content: '// Custom header format\n// DO NOT EDIT - Generated file'
  }
});

// Output:
// // Custom header format
// // DO NOT EDIT - Generated file
// {
//   generated: true
// }
```

### Header Extraction

When a `documentHeader` is provided, the library automatically extracts existing headers:

```typescript
const { extractedHeader } = updateJsonnet({
  jsonnetString: withHeader,
  documentHeader: { type: 'simple', content: [] }
});

console.log(extractedHeader);
// {
//   type: 'simple',
//   content: ['Application Configuration', 'Version: 1.0'],
//   raw: '# Application Configuration\n# Version: 1.0'
// }
```

## Format Options

Control how the output is formatted:

```typescript
const { result } = updateJsonnet({
  jsonnetString,
  formatOptions: {
    indent: 2,                    // Number of spaces (or '\t')
    preserveIndentation: true,    // Auto-detect from source (default)
    trailingNewline: true         // Add newline at end (default)
  },
  annotate: ({ change }) => {
    // Your updates...
  }
});
```

## Real-World Examples

### Kubernetes Manifest Generation

```typescript
import { updateJsonnet } from '@hiscojs/jsonnet-updater';

const kubernetesTemplate = `
{
  apiVersion: 'v1',
  kind: 'ConfigMap',
  metadata: {
    name: 'app-config'
  },
  data: {}
}
`;

const { result } = updateJsonnet({
  jsonnetString: kubernetesTemplate,
  locals: [
    { name: 'environment', value: 'production' },
    { name: 'region', value: 'us-west-2' }
  ],
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.metadata,
      merge: (orig) => ({
        ...orig,
        namespace: '$.environment'
      })
    });

    change({
      findKey: (obj) => obj,
      merge: (orig) => ({
        ...orig,
        data: {
          DATABASE_URL: 'postgres://prod-db:5432',
          REGION: '$.region',
          LOG_LEVEL: 'info'
        }
      })
    });
  }
});
```

### Multi-Service Configuration

```typescript
const { result } = updateJsonnet({
  jsonnetString: '{}',
  functions: [
    {
      name: 'createService',
      params: ['name', 'image', 'port', 'env'],
      body: `{
  name: name,
  image: image,
  ports: [{ containerPort: port }],
  env: env
}`
    }
  ],
  locals: [
    { name: 'dbHost', value: 'postgres.example.com' },
    { name: 'cacheHost', value: 'redis.example.com' }
  ],
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.services,
      merge: () => [
        `$.createService(
          "api",
          "api:2.0",
          8080,
          [{ name: "DB_HOST", value: $.dbHost }]
        )`,
        `$.createService(
          "worker",
          "worker:1.5",
          8081,
          [
            { name: "DB_HOST", value: $.dbHost },
            { name: "CACHE_HOST", value: $.cacheHost }
          ]
        )`
      ]
    });
  }
});
```

## API Reference

### `updateJsonnet<T>(options): JsonnetEdit<T>`

Main function for updating Jsonnet content.

**Options:**

```typescript
{
  jsonnetString: string;              // Input Jsonnet content
  annotate?: (ctx: AnnotateContext) => void;  // Update callback
  formatOptions?: FormatOptions;      // Formatting preferences
  locals?: LocalVariable[];           // Local variable definitions
  functions?: LocalFunction[];        // Local function definitions
  documentHeader?: DocumentHeader;    // Document header configuration
}
```

**Return Type:**

```typescript
interface JsonnetEdit<T> {
  result: string;                     // Updated Jsonnet string
  resultParsed: T;                    // Parsed updated object
  originalParsed: T;                  // Original parsed object
  locals: LocalVariable[];            // Defined local variables
  functions: LocalFunction[];         // Defined local functions
  extractedHeader?: ExtractedHeader;  // Extracted document header (if present)
}
```

**AnnotateContext:**

```typescript
{
  change: (instruction: ChangeInstruction) => void;
  locals: Record<string, any>;        // Access to local variables
  functions: Record<string, Function>; // Access to local functions
}
```

**ChangeInstruction:**

```typescript
{
  findKey: (proxy: T) => any;         // Path selector with type-safety
  merge: (current: any) => any;       // Update function
}
```

**DocumentHeader:**

```typescript
{
  type: 'simple' | 'multi-line' | 'raw';  // Header formatting style
  content: string | string[];              // Header content
  border?: string;                         // Border char for multi-line (default: '#')
  width?: number;                          // Width for multi-line (default: auto)
}
```

**ExtractedHeader:**

```typescript
{
  type: 'simple' | 'multi-line' | 'raw';  // Detected header type
  content: string[];                       // Parsed content lines
  raw: string;                             // Raw header string
}
```

### `addInstructions(options): Instruction[]`

Generate instructions for advanced array merging (re-exported from `@hiscojs/object-updater`).

**Options:**

```typescript
{
  prop: string;              // Array property name
  mergeByName?: boolean;     // Merge by 'name' property
  mergeByProp?: string;      // Merge by specific property
  mergeByContents?: boolean; // Merge by content comparison
  deepMerge?: boolean;       // Deep merge objects
}
```

### `exclude<T>(obj: T, ...keys: (keyof T)[]): Partial<T>`

Helper function for deleting properties from objects (re-exported from `@hiscojs/object-updater`).

**Parameters:**
- `obj`: The source object
- `keys`: Property names to exclude/delete (variable number of arguments)

**Returns**: A new object with specified properties set to `undefined`, signaling deletion

**Example:**

```typescript
import { updateJsonnet, exclude } from '@hiscojs/jsonnet-updater';

// Delete single property
merge: (orig) => exclude(orig, 'deprecated')

// Delete multiple properties
merge: (orig) => exclude(orig, 'deprecated', 'legacy', 'old')

// Combine with spread for updates
merge: (orig) => ({
  ...exclude(orig, 'deprecated'),
  version: '2.0.0',
  newProp: 'value'
})
```

## TypeScript Support

Full TypeScript support with generic types:

```typescript
interface MyConfig {
  server: {
    host: string;
    port: number;
  };
  features: string[];
}

const { result, resultParsed } = updateJsonnet<MyConfig>({
  jsonnetString,
  annotate: ({ change }) => {
    change({
      findKey: (obj) => obj.server.port,  // Type-safe!
      merge: () => 9000
    });
  }
});

// resultParsed is typed as MyConfig
console.log(resultParsed.server.host);
```

## Known Limitations

While `jsonnet-updater` is powerful for many use cases, it has some limitations:

### Parsing Limitations
- **Cannot parse Jsonnet files with function calls in data values** (e.g., `person1: Person()`)
- Complex Jsonnet expressions may not parse correctly
- Best used with static JSON-like Jsonnet structures

### Function Evaluation
- Functions are not evaluated (treated as templates)
- Function calls are represented as string references (`$.functionName()`)

### Comment Preservation
- Comments are tracked but not fully re-inserted
- Basic comment preservation implementation

## Recommended Use Cases

### ✅ Best For

- **Creating Jsonnet files from scratch** with functions and local variables
- **Updating static JSON-like Jsonnet structures** with predictable schemas
- **Managing local variables and functions** as reusable templates
- **Kubernetes manifest generation** with templating
- **Configuration file templating** for multi-environment setups

### ⚠️ Not Ideal For

- **Complex Jsonnet** with heavy use of function calls in data
- **Files with computed values and conditionals** that require evaluation
- **Interactive Jsonnet evaluation** or runtime value computation

## Comparison with Other Updaters

| Feature | jsonnet-updater | yaml-updater | json-updater |
|---------|----------------|--------------|--------------|
| Type-safe updates | ✅ | ✅ | ✅ |
| Comment preservation | ✅ | ✅ | ✅ |
| Local variables | ✅ (native) | ✅ (anchors) | ❌ |
| Functions | ✅ (native) | ❌ | ❌ |
| Array merging | ✅ | ✅ | ✅ |
| Multi-document | ❌ | ✅ | ❌ |
| Format detection | ✅ | ✅ | ✅ |

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT

## Programmatic Generation

For cases where you need to **generate Jsonnet from scratch** rather than update existing files, the library provides two approaches:

### 1. JavaScript-Native API (Recommended ⭐)

Write plain JavaScript objects and use helpers only when needed - similar to `yaml-updater`'s `addInstructions`.

```typescript
import {
  objectToJsonnet,
  identifier,
  extVar,
  concat,
  addInstructions,
} from '@hiscojs/jsonnet-updater';

const template = objectToJsonnet(
  {
    apiVersion: 'argoproj.io/v1alpha1',
    kind: 'AppProject',
    metadata: {
      name: identifier('projectName'), // Variable reference
      namespace: 'argocd',
    },
    spec: {
      description: concat('Project: ', identifier('projectName')), // String concatenation
      sourceRepos: ['*'],

      // Explicit array formatting control (like yaml-updater)
      ...addInstructions({ prop: 'destinations', multiline: true }),
      destinations: [
        {
          server: identifier('clusterAddress'),
          namespace: '*',
        },
      ],
    },
  },
  {
    locals: [
      { name: 'projectName', value: extVar('projectName') },
      { name: 'clusterAddress', value: extVar('clusterAddress') },
    ],
  }
);

// Output:
// {
//   local projectName = std.extVar("projectName");
//   local clusterAddress = std.extVar("clusterAddress");
//
//   apiVersion: "argoproj.io/v1alpha1",
//   kind: "AppProject",
//   metadata: {
//     name: projectName,
//     namespace: "argocd",
//   },
//   spec: {
//     description: "Project: " + projectName,
//     sourceRepos: ["*"],
//     destinations: [
//       {
//         server: clusterAddress,
//         namespace: "*",
//       },
//     ],
//   },
// }
```

**Helpers:**
- `identifier(name)` - Variable reference: `identifier('var')` → `var`
- `extVar(name)` - External variable: `extVar('name')` → `std.extVar("name")`
- `concat(left, right)` - String concatenation: `concat('a', 'b')` → `"a" + "b"`
- `addInstructions({ prop, multiline })` - Array formatting control (like yaml-updater)

See [OBJECT_API.md](OBJECT_API.md) for complete documentation.

### 2. AST Builders (Low-Level)

For cases where you need to **generate Jsonnet from scratch** rather than update existing files, the library provides AST builder helpers.

### Quick Example

```typescript
import { generate, Document } from '@hiscojs/jsonnet-updater';
import * as b from '@hiscojs/jsonnet-updater/builders';

// Build the AST
const ast: Document = {
  type: 'Document',
  body: b.object(
    [
      b.field('apiVersion', b.string('v1')),
      b.field('kind', b.string('ConfigMap')),
      b.field('metadata', b.object([
        b.field('name', b.identifier('appName')),
        b.field('namespace', b.string('default')),
      ])),
      b.field('data', b.object([
        b.field('config.json', b.string('{"key": "value"}')),
      ])),
    ],
    // Local variables (second parameter)
    [
      b.local('appName', b.extVar('appName')),
    ]
  ),
};

// Generate Jsonnet code
const result = generate(ast, {
  indent: '  ',
  trailingCommas: true,
});

console.log(result);
// Output:
// {
//   local appName = std.extVar("appName");
//
//   apiVersion: "v1",
//   kind: "ConfigMap",
//   metadata: {
//     name: appName,
//     namespace: "default",
//   },
//   data: {
//     "config.json": "{\"key\": \"value\"}",
//   },
// }
```

### Builder API

| Builder | Output | Description |
|---------|--------|-------------|
| `b.string('text')` | `"text"` | String literal |
| `b.number(42)` | `42` | Number literal |
| `b.boolean(true)` | `true` | Boolean literal |
| `b.identifier('name')` | `name` | Variable reference |
| `b.extVar('var')` | `std.extVar("var")` | External variable |
| `b.object([...])` | `{ ... }` | Object |
| `b.array([...])` | `[...]` | Array |
| `b.field('key', value)` | `key: value` | Object field |
| `b.local('name', value)` | `local name = value` | Local variable |
| `b.binary('+', a, b)` | `a + b` | Binary expression |
| `b.call(func, [args])` | `func(args)` | Function call |
| `b.member(obj, 'prop')` | `obj.prop` | Member access |

### Array Formatting Control

Control whether arrays are formatted inline or multi-line:

```typescript
// Auto-detect (default): inline for simple values, multi-line for objects
b.array([b.string('a'), b.string('b')])  // ["a", "b"]
b.array([b.object([...])])                // [{\n  ...\n}]

// Force multi-line (explicit control)
b.array(
  [b.string('dev'), b.string('staging'), b.string('prod')],
  { multiline: true }
)
// Output:
// [
//   "dev",
//   "staging",
//   "prod",
// ]

// Force inline
b.array([...], { multiline: false })  // [...]
```

**Array Formatting Options:**
- `multiline: true` - Force multi-line format
- `multiline: false` - Force inline format
- `undefined` - Auto-detect based on element types (default)

### Complete Example: ArgoCD AppProject

See [appproject-template.jsonnet](appproject-template.jsonnet) for a complete example of generating a complex Jsonnet template programmatically.

```typescript
import { generate, Document } from '@hiscojs/jsonnet-updater';
import * as b from '@hiscojs/jsonnet-updater/builders';

const ast: Document = {
  type: 'Document',
  body: b.object(
    [
      b.field('apiVersion', b.string('argoproj.io/v1alpha1')),
      b.field('kind', b.string('AppProject')),
      b.field('metadata', b.object([
        b.field('name', b.identifier('projectName')),
        b.field('namespace', b.string('argocd')),
      ])),
      b.field('spec', b.object([
        b.field(
          'description',
          b.binary('+', b.string('Project: '), b.identifier('projectName'))
        ),
        b.field('sourceRepos', b.array([b.string('*')])),
        b.field(
          'destinations',
          b.array(
            [
              b.object([
                b.field('server', b.identifier('clusterAddress')),
                b.field('namespace', b.string('*')),
              ]),
            ],
            { multiline: true }  // Explicit multi-line formatting
          )
        ),
      ])),
    ],
    [
      b.local('projectName', b.extVar('projectName')),
      b.local('clusterAddress', b.extVar('clusterAddress')),
    ]
  ),
};

const result = generate(ast, { indent: '  ', trailingCommas: true });
```

### When to Use Programmatic Generation

**✅ Use AST builders when:**
- Creating Jsonnet files from scratch
- Building templates dynamically from data
- Need type-safe construction with validation
- Generating consistent structures programmatically

**✅ Use `updateJsonnet()` when:**
- Updating existing Jsonnet files
- Preserving comments and formatting
- Making incremental changes to existing content

## Related Libraries

- [@hiscojs/yaml-updater](https://www.npmjs.com/package/@hiscojs/yaml-updater) - YAML updates with comment preservation
- [@hiscojs/json-updater](https://www.npmjs.com/package/@hiscojs/json-updater) - JSON/JSONC updates with formatting
- [@hiscojs/dotenv-updater](https://www.npmjs.com/package/@hiscojs/dotenv-updater) - .env file updates
- [@hiscojs/object-updater](https://www.npmjs.com/package/@hiscojs/object-updater) - Core update logic engine