Metadata-Version: 2.1
Name: wexample-wex-addon-dev-javascript
Version: 8.1.0
Summary: Extends wex with JavaScript support: enforces JS/TS project layout and symlinks local npm packages into node_modules
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-wex-dev-python
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: wexample-api>=6.8.0
Requires-Dist: wexample-filestate-javascript>=6.6.0
Requires-Dist: wexample-wex-addon-ai>=13.0.0
Requires-Dist: wexample-wex-addon-app>=30.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# wex_addon_dev_javascript

Version: 8.1.0

`wex-addon-dev-javascript` extends wex for JavaScript and TypeScript developers: it enforces a standard project layout — requiring `package.json`, `tsconfig.json`, `.npmrc`, `src/`, `tests/`, and the corresponding `.gitignore` rules — and in local environments symlinks locally-developed npm packages directly into a running container's `node_modules/` so monorepo packages resolve without a registry publish cycle. It also contributes a `node` service and a `vite` service, each backed by Docker Compose, where the vite service patches `allowedHosts` in `vite.config.ts` for reverse-proxy use and the node service handles lockfile refresh, annotated-tag release, and registry polling for npm package suites managed through wex.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-wex-addon-dev-javascript
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-wex-addon-dev-javascript
```

The public entry point is `JavascriptAddonManager`, defined in src/wexample_wex_addon_dev_javascript/javascript_addon_manager.py:

```python
from wexample_wex_addon_dev_javascript.javascript_addon_manager import JavascriptAddonManager
```

Pass it to the kernel's `setup()` call to register the JavaScript addon slot:

```python
from wexample_wex_core.common.kernel import Kernel

kernel = Kernel()
kernel.setup(addons=[JavascriptAddonManager])
```

After `setup()` returns, the kernel recognises three workdir types: `javascript`, `javascript-package`, and `javascript-packages-suite`. Any workdir resolved against a directory holding a `package.json` will be matched to the appropriate class.

The addon also contributes four service commands to the `node` and `vite` namespaces. Wire locally-developed npm packages into a running container's `node_modules/` — replacing the registry copy with a live symlink — with:

```bash
wex node::service/install_local
```

Refresh the lockfile in the container after a `package.json` dependency change (the command detects whether the project uses npm, pnpm, or Yarn from whichever lockfile is present):

```bash
wex node::service/refresh_lock --npm_packages "@vendor/my-lib"
```

Patch `vite.config.ts` so the Vite dev server accepts requests from any host — required when the service runs behind a reverse proxy:

```bash
wex vite::service/install
```

Check whether the Vite dev server inside its container is responding:

```bash
wex vite::service/ready
```

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

The addon is a pure Python package that extends wex with JavaScript and TypeScript support. It has four layers: an addon manager that registers workdir types, a workdir hierarchy that enforces project layout and owns the release lifecycle, a file layer that controls what `package.json` and `tsconfig.json` contain when written to disk, and a service layer that contributes `node` and `vite` Docker service definitions with their commands.

### Entry point

src/wexample_wex_addon_dev_javascript/javascript_addon_manager.py is the only class the host kernel needs. It extends `AbstractAddonManager` and returns three workdir types from `get_workdir_types()`:

| key | class |
|---|---|
| `javascript` | `JavascriptWorkdir` |
| `javascript-package` | `JavascriptPackageWorkdir` |
| `javascript-packages-suite` | `JavascriptPackagesSuiteWorkdir` |

The kernel resolves a directory to the right class by matching that key against the workdir type declared in the project's wex config.

### Workdir layer

The three classes form a linear inheritance chain.

#### `JavascriptWorkdir`

src/wexample_wex_addon_dev_javascript/workdir/javascript_workdir.py is the base. It extends `CodeBaseWorkdir` (from `wexample-wex-addon-app`) and `WithAiWorkdirMixin`. Its `prepare_value()` declares the required filesystem layout that wex will enforce: `package.json`, `tsconfig.json`, `.npmrc`, a `src/` tree, a `tests/` tree, and `.gitignore` rules covering `node_modules/`, `dist/`, `build/`, `.npm`, and `.eslintcache`. JS/TS files under `src/` and `tests/` are captured by a `ChildrenFilterOption` that matches `*.js|jsx|ts|tsx` and applies the `BiomeOption` from `wexample-filestate-javascript`. The class also appends `JavascriptOptionsProvider` to the options chain and returns a `JavascriptPackageJsonFile` from `get_app_config_file()`.

#### `JavascriptPackageWorkdir`

src/wexample_wex_addon_dev_javascript/workdir/javascript_package_workdir.py extends `JavascriptWorkdir` and adds everything a publishable npm package needs.

**Naming.** `get_project_name()` returns `@{vendor}/{kebab-case-name}`, giving packages their scoped npm identity.

**Layout addition.** `prepare_value()` appends `.github/workflows/publish.yml` to the enforced file tree. The content is read from the bundled resource src/wexample_wex_addon_dev_javascript/resources/package_publish.yml — a GitHub Actions workflow that publishes to npm on any `v*` tag push.

**Release lifecycle.** `release()` runs three steps in order:
1. `_refresh_lockfile()` — detects which lockfile is present (`package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`) and runs the matching lock-only refresh command on the host machine.
2. `super().release()` — inherited bump, commit, and publish flow; `_publish()` overrides the publish step to create an annotated git tag (`v{version}`) and push it to the configured deployment remote, which triggers the CI workflow above.
3. `_wait_for_registry()` — polls `NpmRegistryGateway.has_version()` every 30 s for up to 20 minutes before the suite loop advances to the next dependent package.

**Version bump classification.** `_classify_version_bump()` inspects the git diff of `src/` since the last tag: no changes → minor bump; whitespace-only diff → minor; non-TypeScript files only → intermediate; any `.ts`/`.tsx` change → major.

#### `JavascriptPackagesSuiteWorkdir`

src/wexample_wex_addon_dev_javascript/workdir/javascript_packages_suite_workdir.py extends `FrameworkPackageSuiteWorkdir`. It discovers child packages by looking for directories inside `npm/` that contain a `package.json`, and resolves each child as a `JavascriptPackageWorkdir`.

### File layer

#### `NodePackageJsonFile`

src/wexample_wex_addon_dev_javascript/file/node_package_json_file.py composes `AppDependenciesConfigFileMixin` with `JsonFile`. It exposes the dependency-manifest API (`add_dependency`, `add_dependency_from_string`, `get_dependencies_versions`) and reads `dependencies` plus `peerDependencies` as the set of declared runtime constraints. Any workdir that ships a `package.json` — including polyglot app manifests — can attach this class.

#### `JavascriptPackageJsonFile`

src/wexample_wex_addon_dev_javascript/file/javascript_package_json_file.py extends `NodePackageJsonFile`. Its `dumps()` stamps `name`, `version`, and `repository` from the parent workdir before delegating to the JSON serialiser. It also calls `_apply_default_publish_config()`, which sets `type: "module"`, ensures `publishConfig.access` defaults to `"public"`, and — when neither `exports` nor a `dist`-targeting `files` array is already present — injects a default `exports` map (`"./*": { types: "./dist/*.d.ts", default: "./dist/*.js" }`), a `files: ["dist"]` list, and a `typesVersions` block so consumers can import without transpiling sources.

#### `JavascriptTsconfigJsonFile`

src/wexample_wex_addon_dev_javascript/file/javascript_tsconfig_json_file.py extends `JsonFile`. Its `dumps()` fills in `compilerOptions` defaults (`target: ES2020`, `module: NodeNext`, `moduleResolution: NodeNext`, `rootDir: src`, `outDir: dist`, `declaration: true`, `declarationMap: true`, `sourceMap: true`, `strict: false`, `esModuleInterop: true`, `skipLibCheck: true`, `forceConsistentCasingInFileNames: true`) and sets `include: ["src"]`, `exclude: ["dist", "node_modules", "tests"]`. All defaults use `setdefault`, so anything already in the file is left untouched.

### Service layer

Two service namespaces ship as self-contained directories, each containing a `service.yml`, a `docker/docker-compose.yml`, and a `commands/service/` package.

#### `vite` service

Defined in src/wexample_wex_addon_dev_javascript/services/vite/service.yml. The container (`${APP_PROJECT_NAME}_vite`) runs `oven/bun:latest`, mounts the app path to `/app`, persists `node_modules` in a named volume `vite_node_modules`, and starts with `bun install --no-cache && bun run dev --host`. The exposed port is controlled by the `VITE_PORT` var (default `8080`), which the compose file also passes to `nginx-proxy` via `VIRTUAL_PORT`.

src/wexample_wex_addon_dev_javascript/services/vite/app_service.py composes the base `AppService` with `AgentContributingServiceMixin`, which makes the AI agent declared in src/wexample_wex_addon_dev_javascript/services/vite/ai/agents/main/agent.yml discoverable by `wex talk`. The agent's system prompt lives in src/wexample_wex_addon_dev_javascript/services/vite/ai/agents/main/about-service.md.

**`vite::service/install`** — src/wexample_wex_addon_dev_javascript/services/vite/commands/service/install.py patches `vite.config.ts` or `vite.config.js` to add `server: { allowedHosts: true }` inside `defineConfig`. It handles two shapes: a bare `defineConfig()` call and a `defineConfig({ ... })` block. The patch is idempotent (skips if `allowedHosts` is already present).

**`vite::service/ready`** — src/wexample_wex_addon_dev_javascript/services/vite/commands/service/ready.py reads `app.project_name` from the runtime config, resolves the container name as `{project_name}_vite`, reads `VITE_PORT` from the service manifest vars, then runs `docker exec {container} bun -e "await fetch('http://localhost:{port}')"` and returns a `BooleanResponse`.

#### `node` service

Defined in src/wexample_wex_addon_dev_javascript/services/node/service.yml. The container (`${APP_PROJECT_NAME}_node`) runs `node:20-alpine`, mounts the app path to `/var/www/html`, and stays alive with `tail -f /dev/null` so subsequent `docker exec` calls can run inside it.

**`node::service/install_local`** — src/wexample_wex_addon_dev_javascript/services/node/commands/service/install_local.py reads the `local_packages.javascript` key from the runtime config (a map of vendor names to package directories). It builds a shell script that first installs the full `node_modules` tree (using whichever of yarn, pnpm, or npm the project's lockfile identifies), then iterates every directory under `/var/www/javascript-dev/{vendor}/`, reads its `package.json` to get the `name` field, removes the corresponding entry from `node_modules/`, and replaces it with a symlink to the local source. The script runs inside the service container via `docker exec`.

**`node::service/refresh_lock`** — src/wexample_wex_addon_dev_javascript/services/node/commands/service/refresh_lock.py accepts a `--npm_packages` option listing the changed packages (for logging), then runs a lockfile-only install inside the container: `yarn install` if `yarn.lock` is present, `pnpm install --lockfile-only` if `pnpm-lock.yaml`, or `npm install --package-lock-only` if `package-lock.json`.

### Common utilities

src/wexample_wex_addon_dev_javascript/common/npm_registry_gateway.py is a thin HTTP client over `AbstractGateway`. Its single public method `has_version(package, version)` fetches `/{encoded_package_name}` from the configured registry URL, parses the JSON manifest, and returns `True` if `version` appears in the `versions` map. It accepts an optional `token` for Bearer auth on private registries. `JavascriptPackageWorkdir._wait_for_registry()` calls it in a polling loop with a 30-second delay and a 40-attempt ceiling (20 minutes total).

### Constants

src/wexample_wex_addon_dev_javascript/const/tags.py declares `DomainTag` with three values — `domain:dev-server`, `domain:frontend`, `domain:service` — which the service commands attach via the `@command(tags=[...])` decorator. Tags govern how `wex talk` and automated agents filter and describe available commands.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- wexample-api: >=6.8.0
- wexample-filestate-javascript: >=6.6.0
- wexample-wex-addon-ai: >=13.0.0
- wexample-wex-addon-app: >=30.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-wex_addon_dev_javascript/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-wex-addon-dev-javascript
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-wex-addon-dev-javascript/issues
- **Discussions**: https://github.com/wexample/python-wex-addon-dev-javascript/discussions
- **PyPI**: [pypi.org/project/wexample-wex-addon-dev-javascript](https://pypi.org/project/wexample-wex-addon-dev-javascript/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
