npm package: @ni-kismet/sl-webapp-nipkg
Description: Build tool for packaging web applications into SystemLink WebApp .nipkg format. Supports Node.js, Python, .NET Blazor, and static sites.
Author: National Instruments
Latest version: 2.0.0

# SL WebApp NIPKG

[![JFrog Registry](https://img.shields.io/badge/registry-JFrog-green)](https://pull.artifacts.ni.com/ui/repos/tree/General/npm-remote-cache/.npm/@ni-kismet/sl-webapp-nipkg)
[![Tests](https://github.com/ni/sl-webapp-nipkg/actions/workflows/ci.yml/badge.svg)](https://github.com/ni/sl-webapp-nipkg/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Page](https://img.shields.io/badge/Page-github.io-darkgreen.svg)](https://ni.github.io/sl-webapp-nipkg/)

A flexible tool for packaging web applications into SystemLink WebApp `.nipkg` format for National Instruments Package Manager. Works with any web application framework including Node.js (React, Angular, Vue), Python (Pyodide), .NET (Blazor), and static HTML sites.

## Features

- 🚀 **Easy Integration**: Works seamlessly with any web application
- 📦 **Automated Packaging**: Builds and packages your app in one command
- 🏪 **Plugin Manager Submission Ready**: Generate Plugin Manager metadata for display name, icon, category, license, and screenshot submission files without embedding `screenshotFiles` in the control file
- 🚢 **SystemLink Deployment**: Create/update WebApps and upload `.nipkg` content from CLI
- 🔐 **Secure Authentication**: `login`/`logout` commands store credentials per-host — no secrets in config files
- ⚙️ **Flexible Configuration**: Optional config files - use CLI flags or JSON config
- 🎯 **TypeScript Support**: Written in TypeScript with full type definitions
- 🌈 **Beautiful CLI**: Colorful, informative command-line interface
- 🔧 **CI/CD Ready**: Easy integration with build pipelines
- 🌍 **Cross-Platform**: Works on Windows, macOS, and Linux without external dependencies
- 🔌 **Framework Agnostic**: Works with React, Angular, Vue, Blazor, Pyodide, static HTML, and more
- 🎨 **Zero Config**: No config files required - just point to your build directory

## Prerequisites

- Node.js 20 or higher (only to run the packaging tool)

## Installation

### Global Installation (Recommended)

```bash
npm install -g @ni-kismet/sl-webapp-nipkg
```

### Project-specific Installation

```bash
# As a dev dependency in your project
npm install --save-dev @ni-kismet/sl-webapp-nipkg
```

## Quick Start

### Option 1: Minimal Usage (No Config Files)

```bash
# Package any web application - just point to the build directory
sl-webapp-nipkg build --build-dir ./dist --name my-app

# With full metadata
sl-webapp-nipkg build \
  --build-dir ./dist \
  --name my-webapp \
  --version "1.0.0" \
  --maintainer "Your Name <name@example.com>"
```

### Option 2: With Configuration File (Recommended for Node.js projects)

1. **Navigate to your project**:

   ```bash
   cd my-webapp-project
   ```

2. **Initialize configuration** (optional):

   ```bash
   sl-webapp-nipkg init
   ```

3. **Edit the generated `nipkg.config.json`** (all fields optional):

   ```json
   {
     "package": "my-webapp",
     "maintainer": "John Doe <john.doe@company.com>",
     "displayName": "My WebApp",
     "section": "Dashboard",
     "license": "MIT",
     "xbPlugin": "webapp",
     "slPluginManagerTags": "dashboard,monitoring",
     "iconFile": "icon.svg",
     "buildDir": "dist",
     "buildCommand": "npm run build"
   }
   ```

   **Note:** The `package`, `version`, `description`, `homepage`, and `license` are auto-populated from your `package.json` when using `init`. Other fields must be specified explicitly in `nipkg.config.json` if needed.

4. **Build and package**:

   ```bash
   sl-webapp-nipkg build --build

    # Build, package, and also emit a schema v2 manifest.json
    sl-webapp-nipkg build --build --manifest
   ```

## CLI Commands

### `sl-webapp-nipkg build`

Build and package your web application.

#### Options

**Core Options:**

- `--build-dir <path>` - Build output directory to package (required if not in config)
- `--name <name>` - Package identifier in `^[a-z0-9][a-z0-9._-]*$` format (auto-detected from package.json or directory name)
- `--version <version>` - Package version (auto-detected from package.json or defaults to 1.0.0)
- `--description <description>` - Package description (optional)
- `--maintainer <maintainer>` - Maintainer info (defaults to 'Unknown')
- `--output-dir <path>` - Output directory for nipkg files (default: dist/nipkg)

**Build Options:**

- `-b, --build [command]` - Run build command before packaging (optionally specify custom command to override config)
- `-v, --verbose` - Enable verbose output
- `--skip-cleanup` - Skip cleanup of existing packages
- `--build-suffix <suffix>` - Add a suffix to the package name (e.g., build ID for CI/CD)
- `--config <path>` - Custom config file path (default: 'nipkg.config.json')
- `--manifest` - Generate `manifest.json` from the built `.nipkg`
- `--manifest-output <path>` - Output path for the generated manifest file (default: `manifest.json`)
- `--source-repo <owner/name>` - Optional source repository for generated manifest provenance
- `--release-tag <tag>` - Optional release tag for generated manifest provenance; must be used together with `--source-repo`
- `--source-commit <sha>` - Optional source commit SHA for generated manifest provenance

**Note:** CLI options override config file values, which override package.json values.

#### Examples

**Node.js Projects:**

```bash
# Build and package (runs buildCommand from config)
sl-webapp-nipkg build --build

# Build with custom command (overrides config)
sl-webapp-nipkg build --build "npm run build:production"

# Use existing build output (no build step)
sl-webapp-nipkg build

# Build with build ID suffix for CI/CD
sl-webapp-nipkg build --build --build-suffix "${BUILD_ID}"

# Build, package, and generate manifest.json in one step
sl-webapp-nipkg build --build --manifest

# Build, package, and write manifest to a custom path
sl-webapp-nipkg build --build --manifest --manifest-output submissions/manifest.json

# Verbose output with custom config
sl-webapp-nipkg build --build --verbose --config my-nipkg.config.json
```

**Non-Node.js Projects (Python, Blazor, Static Sites):**

```bash
# Python Pyodide webapp
sl-webapp-nipkg build --build-dir ./public --name my-pyodide-app

# .NET Blazor webapp
sl-webapp-nipkg build \
  --build-dir ./bin/Release/net8.0/publish \
  --name my-blazor-app \
  --version "2.0.0"

# Static HTML site
sl-webapp-nipkg build --build-dir ./dist --name my-static-site --version 1.0.0

# With full metadata
sl-webapp-nipkg build \
  --build-dir ./output \
  --name my-webapp \
  --version "1.5.0" \
  --description "My awesome webapp" \
  --maintainer "Team <team@company.com>"
```

### `sl-webapp-nipkg init`

Initialize a `nipkg.config.json` file in the current directory. Auto-populates `package`, `maintainer`, `homepage`, and `license` from `package.json`, and sets `section: "Dashboard"` and `xbPlugin: "webapp"` as starter defaults.

### `sl-webapp-nipkg generate-manifest`

Generate a schema v2 `manifest.json` for a reviewed `.nipkg` artifact. The command hashes the artifact bytes and writes the thin submission-manifest shape defined by `app-manifest.schema.json`.

#### Options

- `--output <path>` - Output path for manifest file (default: `manifest.json`)
- `--nipkg-file <path>` - Path to the `.nipkg` artifact to reference and hash
- `--source-repo <owner/name>` - Optional source repository in `owner/name` format
- `--release-tag <tag>` - Optional release tag for the reviewed artifact; must be used together with `--source-repo`
- `--source-commit <sha>` - Optional 40-character lowercase commit SHA for the build that produced the artifact

The generated manifest always contains `schemaVersion: 2`, the `.nipkg` basename as `nipkgFile`, and a `sha256` hash of the exact `.nipkg` bytes.

#### Example

```bash
# Generate manifest after build
sl-webapp-nipkg build
sl-webapp-nipkg generate-manifest \
  --nipkg-file dist/nipkg/my-app_1.0.0_all.nipkg \
  --source-repo ni/sl-webapp-nipkg \
  --release-tag v1.0.0
```

### `sl-webapp-nipkg login`

Authenticate with a SystemLink host and store credentials locally so you don't need to pass `--api-key` on every command.

```bash
# Interactive — prompts for host and API key
sl-webapp-nipkg login

# Non-interactive — prompts only for API key
sl-webapp-nipkg login --host https://systemlink.example.com
```

Credentials are verified against the SystemLink API before being stored. A `401` response means the key was wrong and nothing is saved.

- Credentials are stored in `~/.sl-webapp-nipkg/credentials.json` (permissions set to `0600` on Unix)
- Multiple hosts can be stored side by side
- The `deploy` and `delete` commands automatically use the stored credential when no explicit key is provided

#### Login Options

- `--host <url>` - SystemLink host URL (prompted interactively if omitted)
- `--timeout <ms>` - Verification request timeout in milliseconds (default `30000`)

### `sl-webapp-nipkg logout`

Remove the stored credential for a host.

```bash
# Interactive — prompts for host
sl-webapp-nipkg logout

# Non-interactive
sl-webapp-nipkg logout --host https://systemlink.example.com
```

#### Logout Options

- `--host <url>` - SystemLink host URL (prompted interactively if omitted)

### `sl-webapp-nipkg deploy`

Deploy a `.nipkg` to SystemLink WebApps.

Create mode performs two API calls:

1. `POST {host}/niapp/v1/webapps` (creates WebApp and returns `id`)
2. `PUT {host}/niapp/v1/webapps/{id}/content` (uploads `.nipkg`)

Update mode performs three API calls:

1. `GET {host}/niapp/v1/webapps/{id}` (fetches current data to preserve unchanged fields)
2. `PUT {host}/niapp/v1/webapps/{id}` (updates only provided metadata fields)
3. `PUT {host}/niapp/v1/webapps/{id}/content` (uploads `.nipkg`)

The tool can automatically discover and select `.nipkg` files from your build directory. For updates, you only need to provide the `--app-id` and any fields you want to change; current values are fetched from SystemLink.

#### Deploy Options

- `--nipkg <path>` - Path to a `.nipkg` file **or** a directory to search for `.nipkg` files (optional - see auto-discovery below)
- `-b, --build [command]` - Run build command before deploying (optionally specify custom command)
- `--latest` - Skip interactive selection and auto-pick the newest `.nipkg` (useful for CI/CD)
- `--build-suffix <suffix>` - Suffix appended to nipkg filename when `--build` is used (e.g., CI build ID)
- `--skip-cleanup` - Skip cleanup of existing nipkg files when `--build` is used
- `--host <url>` - SystemLink host URL
- `--api-key <key>` - API key used as `x-ni-api-key`
- `--name <name>` - WebApp name (optional for update; falls back to top-level `displayName` from config)
- `--workspace-id <id>` - Workspace UUID (required for create, optional for update)
- `--policy-ids <ids>` - Comma-separated policy IDs (optional)
- `--properties <json>` - JSON properties object (optional, include `version` field if desired)
- `--update` - Update existing WebApp instead of creating new
- `--app-id <id>` - Existing WebApp ID (required with `--update`)
- `--timeout <ms>` - Request timeout in milliseconds (default `30000`)
- `--config <path>` - Config path (default `nipkg.config.json`)
- `-v, --verbose` - Verbose output

#### Auto-discovery of nipkg Files

If `--nipkg` is not provided, the tool will search for `.nipkg` files in:

1. The output directory from the `--build` step (if `--build` was used)
2. The `outputDir` from `nipkg.config.json` (defaults to `dist/nipkg`)

When `--nipkg` points to a **directory**, the newest `.nipkg` inside it is selected automatically.

**Default behavior (interactive selection):**

```bash
sl-webapp-nipkg deploy --update --app-id <id>
# If multiple .nipkg files are found, prompts you to pick one
```

**Auto-select latest (non-interactive, for CI/CD):**

```bash
sl-webapp-nipkg deploy --update --app-id <id> --latest
# Always picks the newest .nipkg without prompting
```

#### Build Before Deploy

Build and deploy in a single command:

```bash
# Build with default build command from config, then deploy
sl-webapp-nipkg deploy \
  --update --app-id "35075240-68a1-431b-afe1-9ce76da46de3" \
  --build \
  --latest

# Build with custom command, then deploy
sl-webapp-nipkg deploy \
  --update --app-id "35075240-68a1-431b-afe1-9ce76da46de3" \
  --build "npm run build:prod" \
  --latest
```

#### Smart Updates (Fetch Current Data)

For update operations, the tool fetches current WebApp data from SystemLink. You only need to provide the fields you want to change:

```bash
# Update only the properties, keep name/workspace/policyIds unchanged
sl-webapp-nipkg deploy \
  --update --app-id "35075240-68a1-431b-afe1-9ce76da46de3" \
  --nipkg ./dist/nipkg/my-app_1.0.1_all.nipkg \
  --host "$SYSTEMLINK_HOST" \
  --api-key "$SYSTEMLINK_API_KEY" \
  --properties '{"version":"1.0.1","description":"Updated version"}'

# Update only the policy IDs, keep other metadata
sl-webapp-nipkg deploy \
  --update --app-id "35075240-68a1-431b-afe1-9ce76da46de3" \
  --nipkg ./dist/nipkg/my-app_1.0.1_all.nipkg \
  --host "$SYSTEMLINK_HOST" \
  --api-key "$SYSTEMLINK_API_KEY" \
  --policy-ids "policy-1,policy-2"
```

#### Deploy Examples

**Create new WebApp:**

```bash
sl-webapp-nipkg deploy \
  --nipkg ./dist/nipkg/my-app_1.0.0_all.nipkg \
  --host https://test.lifecyclesolutions.ni.com \
  --api-key "$SYSTEMLINK_API_KEY" \
  --name "My WebApp" \
  --workspace-id "45bc27cf-85c6-485c-945d-a51e664f511d" \
  --policy-ids "policy-1,policy-2" \
  --properties '{"description":"my app","version":"1.0.0"}'
```

**Update existing WebApp (minimal - only app-id required):**

```bash
sl-webapp-nipkg deploy \
  --nipkg ./dist/nipkg/my-app_1.0.1_all.nipkg \
  --host https://test-api.lifecyclesolutions.ni.com \
  --api-key "$SYSTEMLINK_API_KEY" \
  --update \
  --app-id "35075240-68a1-431b-afe1-9ce76da46de3"
```

**Build and deploy in single command:**

```bash
sl-webapp-nipkg deploy \
  --build \
  --update --app-id "35075240-68a1-431b-afe1-9ce76da46de3" \
  --latest \
  --host https://test-api.lifecyclesolutions.ni.com \
  --api-key "$SYSTEMLINK_API_KEY" \
  --properties '{"version":"1.0.1"}'
```

Environment variable fallback:

- `SYSTEMLINK_HOST`
- `SYSTEMLINK_API_KEY`

Precedence: CLI flags > environment variables > stored login credential (`sl-webapp-nipkg login`) > defaults (where applicable).

> **Security note:** The API key is intentionally **not** a `nipkg.config.json` field. The recommended workflow is to run `sl-webapp-nipkg login` once per host — credentials are stored in `~/.sl-webapp-nipkg/credentials.json` with restricted permissions. For CI/CD pipelines, use the `SYSTEMLINK_API_KEY` environment variable or the `--api-key` flag.

## Configuration

### Configuration File (`nipkg.config.json`)

**All fields are optional.** CLI options override config file values. Config file values override package.json auto-detection.

Legacy `appStore*` fields are still accepted as input during the transition, but generated manifests and control files always use the new Plugin Manager field names.

| Property                          | Type                         | Required | Description                                                                                                                         |
| --------------------------------- | ---------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `package`                         | string                       | ❌       | Unique package identifier — lowercase with hyphens/dots/underscores (auto-detected from package.json)                               |
| `name`                            | string                       | ❌       | **Deprecated.** Use `package` instead                                                                                               |
| `version`                         | string                       | ❌       | Package version (auto-detected from package.json, defaults to 1.0.0)                                                                |
| `displayName`                     | string                       | ❌       | Human-readable display name shown in the catalog card                                                                               |
| `description`                     | string                       | ❌       | Short description (auto-detected from package.json)                                                                                 |
| `section`                         | string                       | ❌       | Fine-grained catalog section (e.g. `'Dashboard'`, `'Administration'`, `'Monitoring'`)                                               |
| `maintainer`                      | string                       | ❌       | Maintainer information (defaults to 'Unknown')                                                                                      |
| `homepage`                        | string                       | ❌       | Homepage / repository URL (auto-detected from package.json)                                                                         |
| `license`                         | string                       | ❌       | SPDX license identifier — **required for Plugin Manager submissions** (auto-detected from package.json)                             |
| `xbPlugin`                        | string                       | ❌       | Top-level plugin type: `'webapp'`, `'notebook'`, `'dashboard'`, `'routine'`, or `'bundle'`                                          |
| `slPluginManagerTags`             | string                       | ❌       | Comma-separated Plugin Manager tags (e.g. `'assets,calibration'`)                                                                   |
| `slPluginManagerMinServerVersion` | string                       | ❌       | Minimum SystemLink server version (e.g. `'2024 Q4'`)                                                                                |
| `iconFile`                        | string                       | ❌       | Path to the primary app icon (SVG or PNG, max 128×128 px); included in generated manifests and embedded as `XB-SlPluginManagerIcon` |
| `screenshotFiles`                 | string[]                     | ❌       | Paths to up to 3 screenshot files (PNG, max 800×600 px) preserved in config for higher-level tooling                                |
| `tags`                            | string                       | ❌       | Standard nipkg `Tags:` field (falls back to `slPluginManagerTags`, then `appStoreTags` if omitted)                                  |
| `extraControlFields`              | object                       | ❌       | Arbitrary extra control file fields written verbatim as `Key: value` lines                                                          |
| `architecture`                    | string                       | ❌       | Target architecture (default: `'windows_all'` for Plugin Manager submissions)                                                       |
| `userVisible`                     | boolean \| `'yes'` \| `'no'` | ❌       | Whether the package appears in the Plugin Manager catalog (default: `'yes'`)                                                        |
| `buildDir`                        | string                       | ❌\*     | Build output directory (e.g., `'dist'`, `'build'`) — \*Required if not provided via CLI                                             |
| `buildCommand`                    | string                       | ❌       | Custom build command (default: `'npm run build'`)                                                                                   |
| `outputDir`                       | string                       | ❌       | Custom nipkg output directory (default: `'dist/nipkg'`)                                                                             |
| `buildSuffix`                     | string                       | ❌       | Optional suffix for package filename (e.g., build ID for CI/CD)                                                                     |
| `depends`                         | string[]                     | ❌       | Package dependencies                                                                                                                |
| `deployment`                      | object                       | ❌       | SystemLink deployment defaults used by `deploy` command                                                                             |

### Example Configuration

```json
{
  "package": "my-webapp",
  "version": "1.2.3",
  "description": "A SystemLink WebApp for National Instruments",
  "displayName": "My WebApp",
  "section": "Dashboard",
  "maintainer": "John Doe <john.doe@company.com>",
  "homepage": "https://github.com/org/my-webapp",
  "license": "MIT",
  "xbPlugin": "webapp",
  "slPluginManagerTags": "assets,calibration",
  "slPluginManagerMinServerVersion": "2024 Q4",
  "iconFile": "assets/icon.svg",
  "architecture": "all",
  "userVisible": "yes",
  "buildDir": "dist",
  "buildCommand": "npm run build",
  "depends": ["ni-systemlink-server >= 2023.1"],
  "outputDir": "packages",
  "deployment": {
    "host": "https://test.lifecyclesolutions.ni.com",
    "name": "my-webapp",
    "workspaceId": "45bc27cf-85c6-485c-945d-a51e664f511d",
    "policyIds": [],
    "properties": {
      "description": "my webapp",
      "version": "1.2.3"
    },
    "timeout": 30000
  }
}
```

**Note:** The `outputDir` is used by the `deploy` command for automatic `.nipkg` file discovery.

## Framework Examples

### Non-Node.js Projects

**Python Pyodide:**

```bash
# No config file needed
sl-webapp-nipkg build \
  --build-dir ./public \
  --name my-pyodide-app \
  --version 1.0.0 \
  --maintainer "Python Team <team@example.com>"
```

**.NET Blazor:**

```bash
# Package Blazor WebAssembly output
sl-webapp-nipkg build \
  --build-dir ./bin/Release/net8.0/publish/wwwroot \
  --name my-blazor-app \
  --version 2.0.0
```

**Static HTML:**

```bash
# Package any static web content
sl-webapp-nipkg build --build-dir ./dist --name my-static-site
```

### Node.js Projects

**React:**

```json
{
  "maintainer": "Your Name <your.email@company.com>",
  "buildDir": "build",
  "buildCommand": "npm run build"
}
```

### Vue

```json
{
  "maintainer": "Your Name <your.email@company.com>",
  "buildDir": "dist",
  "buildCommand": "npm run build"
}
```

### Angular

```json
{
  "maintainer": "Your Name <your.email@company.com>",
  "buildDir": "dist/my-app/browser",
  "buildCommand": "npm run build"
}
```

**Note:** For Angular projects, specify the build configuration in `buildCommand`: `"buildCommand": "ng build --configuration production"` or use an npm script like `"build": "ng build --configuration production"` and let the default `npm run build` work.

### Next.js

```json
{
  "maintainer": "Your Name <your.email@company.com>",
  "buildDir": "out",
  "buildCommand": "npm run build && npm run export"
}
```

## Integration with Node.js Projects

### Add to package.json Scripts

```json
{
  "scripts": {
    "start": "npm run dev",
    "dev": "vite",
    "build": "vite build",
    "build:nipkg": "sl-webapp-nipkg build --build",
    "package:nipkg": "sl-webapp-nipkg build"
  }
}
```

### Use in npm Scripts

```bash
# Build and package for production
npm run build:nipkg

# Package existing build
npm run package:nipkg
```

## Programmatic Usage

```typescript
import {
  SystemLinkNipkgBuilder,
  NipkgConfig,
  BuildOptions,
} from "@ni-kismet/sl-webapp-nipkg";

const config: NipkgConfig = {
  package: "my-app",
  version: "1.0.0",
  description: "My SystemLink WebApp",
  displayName: "My App",
  section: "Dashboard",
  maintainer: "John Doe <john@example.com>",
  license: "MIT",
  xbPlugin: "webapp",
  buildDir: "dist",
};

const options: BuildOptions = {
  build: true,
  verbose: true,
};

const builder = new SystemLinkNipkgBuilder(config, options);
await builder.build();
```

## CI/CD Integration

The `--build-suffix` option allows you to create unique package names for PR/branch builds while keeping clean names for production releases.

**Output examples:**

- PR builds: `my-app_1.0.0.12345_all.nipkg` (includes build ID)
- Main/production: `my-app_1.0.0_all.nipkg` (standard naming)

### GitHub Actions

```yaml
name: Build and Package

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

jobs:
  build:
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build and Package (PR)
        if: github.event_name == 'pull_request'
        run: sl-webapp-nipkg build --build --build-suffix "${{ github.run_number }}"

      - name: Build and Package (Main)
        if: github.event_name == 'push' && github.ref == 'refs/heads/main'
        run: sl-webapp-nipkg build --build

      - name: Upload Package
        uses: actions/upload-artifact@v3
        with:
          name: nipkg-package
          path: dist/nipkg/*.nipkg
```

### Azure DevOps

```yaml
trigger:
  - main

pool:
  vmImage: "windows-latest"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "20.x"

  - script: npm ci
    displayName: "Install dependencies"

  - script: |
      if [ "$(Build.SourceBranch)" = "refs/heads/main" ]; then
        npm run build
        sl-webapp-nipkg build
      else
        npm run build
        sl-webapp-nipkg build --build-suffix "$(Build.BuildId)"
      fi
    displayName: "Build and package"

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: "dist/nipkg"
      ArtifactName: "nipkg-package"
```

## Package Structure

After packaging, your project will have this structure:

```text
your-webapp-project/
├── dist/                            # Your build output
│   ├── index.html
│   ├── assets/
│   └── ...
├── dist/nipkg/                      # NIPKG packaging
│   ├── your-app_1.0.0_all.nipkg    # Final package
│   └── temp-source/                # Temporary (auto-cleaned)
├── nipkg.config.json                # Package configuration
└── package.json                     # NPM scripts
```

## Requirements

- Node.js 20+

## Development

### Building from Source

```bash
git clone https://github.com/ni/sl-webapp-nipkg.git
cd sl-webapp-nipkg
npm install
npm run build
```

### Local Development

```bash
# Link globally for testing
npm link

# Use in any Node.js project
cd /path/to/your/project
sl-webapp-nipkg --help
```

## Troubleshooting

### Common Issues

#### "API key is required"

- Run `sl-webapp-nipkg login --host <url>` to store credentials for this host, **or**
- Pass `--api-key "$SYSTEMLINK_API_KEY"` on the command line, **or**
- Set the `SYSTEMLINK_API_KEY` environment variable

#### "Build directory not found"

- Provide build directory via CLI: `--build-dir ./dist`
- Or run with `--build` flag to build before packaging
- Check that your build command runs successfully
- Verify `buildDir` in nipkg.config.json points to the correct directory

#### "buildDir is required"

- Provide via CLI: `sl-webapp-nipkg build --build-dir ./dist`
- Or add to nipkg.config.json: `"buildDir": "dist"`

#### Package dependencies missing

- Add required NI runtime dependencies to `depends` array in config
- Example: `"depends": ["ni-systemlink-server >= 2023.1"]`

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Make your changes
4. Commit your changes: `git commit -m 'Add amazing feature'`
5. Push to the branch: `git push origin feature/amazing-feature`
6. Submit a pull request

## License

MIT © National Instruments

## Support

- 📚 [Documentation](https://github.com/ni/sl-webapp-nipkg/wiki)
- 🐛 [Report Issues](https://github.com/ni/sl-webapp-nipkg/issues)
- 💬 [Discussions](https://github.com/ni/sl-webapp-nipkg/discussions)

---

_Made with ❤️ for the National Instruments community_