diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4775d01..ce8939f 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -2,12 +2,64 @@
If you wish to contribute to the NanoForge project, fork the repository and submit a pull request. Please mind following the pre-commit hooks to keep the codebase as clean as possible.
+## Repository structure
+
+This repository is a [pnpm workspace](https://pnpm.io/workspaces) monorepo orchestrated with [Turborepo](https://turborepo.com). It contains the following packages:
+
+| Path | Package | Description |
+| ----------------- | --------------------------- | ----------------------------------------------------- |
+| `.` | `@nanoforge-dev/cli` | The `nf` command line interface |
+| `libs/config` | `@nanoforge-dev/config` | Config types and helpers for `nanoforge.config.ts` |
+| `libs/schematics` | `@nanoforge-dev/schematics` | Schematics used by the CLI to generate projects files |
+
+The CLI depends on the libraries through `workspace:*`, so the libraries must be built before the CLI. The `repo:*` scripts take care of this for you.
+
## Setup
To get ready to work on the codebase, please do the following:
1. Fork & clone the repository, and make sure you're on the **main** branch
-2. Run `pnpm install --frozen-lockfile` ([install](https://pnpm.io/installation))
-3. Make your changes
-4. Run `pnpm format && pnpm build && pnpm test:unit` to run ESLint/Prettier, build and tests
-5. [Submit a pull request](https://github.com/NanoForge-dev/CLI/compare) (Make sure you follow the [conventional commit format](https://github.com/NanoForge-dev/CLI/blob/main/.github/COMMIT_CONVENTION.md))
+2. Use the Node.js version defined in [`.nvmrc`](../.nvmrc)
+3. Run `pnpm install --frozen-lockfile` ([install](https://pnpm.io/installation))
+4. Run `pnpm repo:build` to build every package
+5. Make your changes
+6. Run `pnpm repo:format && pnpm repo:build && pnpm repo:test` to run ESLint/Prettier, build and tests on every package
+7. [Submit a pull request](https://github.com/NanoForge-dev/CLI/compare) (Make sure you follow the [conventional commit format](https://github.com/NanoForge-dev/CLI/blob/main/.github/COMMIT_CONVENTION.md))
+
+## Useful commands
+
+Scripts prefixed with `repo:` run on every package of the workspace through Turborepo:
+
+| Command | Description |
+| --------------------- | --------------------------------------------- |
+| `pnpm repo:build` | Build all packages |
+| `pnpm repo:lint` | Check formatting and lint all packages |
+| `pnpm repo:format` | Format and fix lint errors in all packages |
+| `pnpm repo:test` | Run unit and end-to-end tests of all packages |
+| `pnpm repo:test:unit` | Run unit tests of all packages |
+| `pnpm repo:test:e2e` | Run end-to-end tests of all packages |
+
+Scripts without prefix (`build`, `lint`, `test`, ...) only run on the CLI package. To run a script on a single library, use the pnpm `--filter` flag:
+
+```sh
+pnpm --filter @nanoforge-dev/config test:unit
+pnpm --filter @nanoforge-dev/schematics build
+```
+
+## Commits
+
+Commits and pull request titles must follow the [conventional commit format](https://github.com/NanoForge-dev/CLI/blob/main/.github/COMMIT_CONVENTION.md). Use the package as scope when your change targets a single one: `cli`, `config` or `schematics` (e.g. `feat(config): add server port validation`).
+
+Git hooks are installed automatically by `pnpm install`:
+
+- `commit-msg` checks the commit message with commitlint
+- `pre-commit` formats staged files with lint-staged
+- `pre-push` runs `repo:build`, `repo:lint` and `repo:test`
+
+## Releases
+
+All the packages of this repository share the same version and are released together. Releases are handled by maintainers through GitHub Actions:
+
+1. The **Pre-Release** workflow opens a `releases/cli@` pull request that bumps every package to `` and updates their changelogs. The version must be higher than the latest published version of **every** package (e.g. `@nanoforge-dev/schematics` is already at `2.2.1` on npm)
+2. Merging this pull request triggers the **Release** workflow, which publishes the packages to npm (libraries first), creates the `` tag and GitHub release, and synchronizes the documentation
+3. The **Alpha Release** workflow can be triggered manually to publish an `alpha` pre-release of a single package from any branch
diff --git a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml
index 30d738b..cf6c03b 100644
--- a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml
@@ -7,7 +7,17 @@ body:
value: |
Thank you for filing an issue! If you are here to ask a question, don't hesitate to open a discussion instead: https://github.com/NanoForge-dev/CLI/discussions.
- This issue form is for Nanoforge CLI.
+ This issue form is for the Nanoforge CLI and its libraries (`@nanoforge-dev/config`, `@nanoforge-dev/schematics`). If the issue is not related to a CLI command, pick `other` as the command.
+ - type: dropdown
+ id: package
+ attributes:
+ label: Which package is this bug report for?
+ options:
+ - "@nanoforge-dev/cli"
+ - "@nanoforge-dev/config"
+ - "@nanoforge-dev/schematics"
+ validations:
+ required: true
- type: dropdown
id: command
attributes:
@@ -16,7 +26,7 @@ body:
- build
- create
- dev
- - generate
+ - editor
- help
- install
- login
diff --git a/.github/ISSUE_TEMPLATE/02-feature_request.yml b/.github/ISSUE_TEMPLATE/02-feature_request.yml
index 9012528..a4f7706 100644
--- a/.github/ISSUE_TEMPLATE/02-feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/02-feature_request.yml
@@ -6,6 +6,16 @@ body:
attributes:
value: |
Thank you for submitting an idea !
+ - type: dropdown
+ id: package
+ attributes:
+ label: Which package is this feature request for?
+ options:
+ - "@nanoforge-dev/cli"
+ - "@nanoforge-dev/config"
+ - "@nanoforge-dev/schematics"
+ validations:
+ required: true
- type: dropdown
id: command
attributes:
@@ -14,7 +24,7 @@ body:
- build
- create
- dev
- - generate
+ - editor
- help
- install
- login
diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml
index 61fe548..facd38a 100644
--- a/.github/actions/prepare/action.yml
+++ b/.github/actions/prepare/action.yml
@@ -8,7 +8,7 @@ inputs:
description: Install pnpm depedencies
default: "true"
build:
- description: Build packages
+ description: Build all workspace packages
default: "true"
runs:
using: composite
@@ -17,7 +17,7 @@ runs:
name: Install Node.js
uses: actions/setup-node@v6
with:
- node-version: 25
+ node-version-file: .nvmrc
package-manager-cache: false
registry-url: https://registry.npmjs.org
@@ -28,4 +28,4 @@ runs:
- if: inputs.build == 'true'
name: Build packages
shell: bash
- run: pnpm run build
+ run: pnpm run repo:build
diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml
index af0aa6e..397fb14 100644
--- a/.github/issue-labeler.yml
+++ b/.github/issue-labeler.yml
@@ -10,9 +10,9 @@ command:dev:
- "### Which command is this (bug
report|feature request) for\\?\\n\\ndev\\n"
-command:generate:
+command:editor:
- "### Which command is this (bug
- report|feature request) for\\?\\n\\ngenerate\\n"
+ report|feature request) for\\?\\n\\neditor\\n"
command:help:
- "### Which command is this (bug
@@ -45,3 +45,11 @@ command:start:
command:unpublish:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nunpublish\\n"
+
+lib:config:
+ - "### Which package is this (bug
+ report|feature request) for\\?\\n\\n@nanoforge-dev/config\\n"
+
+lib:schematics:
+ - "### Which package is this (bug
+ report|feature request) for\\?\\n\\n@nanoforge-dev/schematics\\n"
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 19c2157..89e7091 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -16,11 +16,11 @@ command:dev:
- src/command/commands/dev.command.ts
- src/action/actions/dev.action.ts
-command:generate:
+command:editor:
- changed-files:
- any-glob-to-any-file:
- - src/command/commands/generate.command.ts
- - src/action/actions/generate.action.ts
+ - src/command/commands/editor.command.ts
+ - src/action/actions/editor.action.ts
command:install:
- changed-files:
@@ -69,3 +69,15 @@ documentation:
- any-glob-to-any-file:
- docs/*
- docs/**/*
+
+lib:config:
+ - changed-files:
+ - any-glob-to-any-file:
+ - libs/config/*
+ - libs/config/**/*
+
+lib:schematics:
+ - changed-files:
+ - any-glob-to-any-file:
+ - libs/schematics/*
+ - libs/schematics/**/*
diff --git a/.github/labels.yml b/.github/labels.yml
index 3b1e5c3..30ae617 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -54,8 +54,8 @@
description: "Related to dev command"
color: "aaa3dc"
-- name: "command:generate"
- description: "Related to generate command"
+- name: "command:editor"
+ description: "Related to editor command"
color: "aaa3dc"
- name: "command:help"
@@ -90,6 +90,14 @@
description: "Related to unpublish command"
color: "aaa3dc"
+- name: "lib:config"
+ description: "Related to the @nanoforge-dev/config library"
+ color: "c5def5"
+
+- name: "lib:schematics"
+ description: "Related to the @nanoforge-dev/schematics library"
+ color: "c5def5"
+
- name: "question"
description: "Further information is requested"
color: "d876e3"
diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml
index 587dcfb..2e2de91 100644
--- a/.github/workflows/alpha-release.yml
+++ b/.github/workflows/alpha-release.yml
@@ -3,6 +3,14 @@ name: Alpha Release
on:
workflow_dispatch:
inputs:
+ package:
+ description: "The published name of a single package to release"
+ type: choice
+ required: true
+ options:
+ - "@nanoforge-dev/cli"
+ - "@nanoforge-dev/config"
+ - "@nanoforge-dev/schematics"
dry_run:
description: Perform a dry run?
type: boolean
@@ -16,7 +24,7 @@ jobs:
alpha-release:
name: Alpha release
runs-on: ubuntu-latest
- if: github.repository_owner == 'NanoForge-dev' && github.ref_name != 'main' && github.ref_name != 'master'
+ if: github.repository_owner == 'NanoForge-dev'
steps:
- name: Checkout repository
uses: actions/checkout@v7
@@ -27,7 +35,7 @@ jobs:
- name: Release alpha
uses: ./node_modules/@nanoforge-dev/actions/actions/release-dev
with:
- package: "@nanoforge-dev/cli"
+ package: ${{ inputs.package }}
tag: alpha
dry: ${{ inputs.dry_run }}
env:
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
index ce4d8e4..3977f21 100644
--- a/.github/workflows/pre-release.yml
+++ b/.github/workflows/pre-release.yml
@@ -34,8 +34,10 @@ jobs:
- name: Create release PR
uses: ./node_modules/@nanoforge-dev/actions/actions/create-packages-release-pr
with:
- packages: "@nanoforge-dev/cli"
+ packages: "@nanoforge-dev/config,@nanoforge-dev/schematics,@nanoforge-dev/cli"
version: ${{ inputs.version }}
+ branch-format: "releases/cli@{version}"
+ commit-format: "chore: release @nanoforge-dev/cli@{version}"
dry: ${{ inputs.dry_run }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c4a89b7..bee025f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,7 +15,7 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
- if: github.repository_owner == 'NanoForge-dev' && github.event.pull_request.merged == true && startsWith(github.head_ref, 'releases/')
+ if: github.repository_owner == 'NanoForge-dev' && github.event.pull_request.merged == true && startsWith(github.head_ref, 'releases/cli@')
steps:
- name: Checkout repository
uses: actions/checkout@v7
@@ -26,33 +26,13 @@ jobs:
- name: Release packages
uses: ./node_modules/@nanoforge-dev/actions/actions/release-packages
with:
- packages: "@nanoforge-dev/cli"
+ packages: "@nanoforge-dev/config,@nanoforge-dev/schematics,@nanoforge-dev/cli"
tag-format: "{version}"
latest: true
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- beta-release:
- name: Beta release
- runs-on: ubuntu-latest
- if: github.repository_owner == 'NanoForge-dev' && github.event.pull_request.merged == true && !startsWith(github.head_ref, 'releases/')
- steps:
- - name: Checkout repository
- uses: actions/checkout@v7
-
- - name: Prepare
- uses: ./.github/actions/prepare
-
- - name: Release beta
- uses: ./node_modules/@nanoforge-dev/actions/actions/release-dev
- with:
- package: "@nanoforge-dev/cli"
- tag: beta
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
publish-docs:
name: Publish docs
runs-on: ubuntu-latest
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a7cdc53..cf20107 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,7 +20,7 @@ jobs:
uses: ./.github/actions/prepare
- name: Run linter
- run: pnpm run lint
+ run: pnpm run repo:lint
- name: Run tests
- run: pnpm run test
+ run: pnpm run repo:test
diff --git a/.gitignore b/.gitignore
index d1eb980..af461f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -229,4 +229,6 @@ src/**/*.d.ts
.tmp-e2e-*/
-.direnv
\ No newline at end of file
+.direnv
+
+.turbo/
\ No newline at end of file
diff --git a/.husky/pre-push b/.husky/pre-push
index 842469b..79c9dc2 100644
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,3 +1,3 @@
-pnpm run build
-pnpm run lint
-pnpm run test
+pnpm run repo:build
+pnpm run repo:lint
+pnpm run repo:test
diff --git a/.nvmrc b/.nvmrc
index 7273c0f..4aa0e0a 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-25
+v26
diff --git a/.prettierignore b/.prettierignore
index 921482e..d416fc3 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,3 +12,5 @@ CHANGELOG.md
coverage/
.tmp-e2e-*/
+
+libs/
diff --git a/README.md b/README.md
index 8bf0143..6343fec 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+
@@ -78,7 +78,6 @@ Used to run your nanoforge project in dev mode.
- `-d, --directory ` specify the working directory of the command.
- `-c, --config ` path to the config file.
-- `--generate` generate app files from config, like generate command in dev mode. (default: `false`)
### `editor`
@@ -89,15 +88,6 @@ Used to start the nanoforge editor.
- `--open` open the editor in the default web browser (default: `true` if path is specified, `false` otherwise).
- `--no-open` do not open the editor in the default web browser.
-### `generate`
-
-Used to generate nanoforge project files from config.
-
-- `-d, --directory ` specify the working directory of the command.
-- `-c, --config ` path to the config file.
-- `--editor` generate the editor main file.
-- `--watch` generate app in watching mode. (default: `false`)
-
### `install` or `add`
Used to add nanoforge components, systems or libraries to your project.
@@ -164,8 +154,8 @@ Used to start your nanoforge project.
- `--client-dir ` specify the directory of the client.
- `--server-dir ` specify the directory of the server.
- `--watch` run app in watching mode. (default: `false`)
-- `--cert ` path to the SSL certificate for HTTPS.
-- `--key ` path to the SSL key for HTTPS.
+- `--cert ` path to the TLS certificate for HTTPS.
+- `--key ` path to the TLS key for HTTPS.
### `unpublish`
@@ -193,7 +183,7 @@ Variables are scoped by prefix:
> **Note:** Prefixes are stripped before the variable is exposed to libraries.
-For full documentation on how libraries consume these variables, see [@nanoforge-dev/config](https://github.com/NanoForge-dev/Engine/tree/main/packages/config).
+For full documentation on how libraries consume these variables, see [@nanoforge-dev/config](https://github.com/NanoForge-dev/CLI/tree/main/libs/config).
## Contributing
diff --git a/cliff.toml b/cliff.toml
index 9151523..831f4e2 100644
--- a/cliff.toml
+++ b/cliff.toml
@@ -76,4 +76,4 @@ sort_commits = "newest"
[remote.github]
owner = "NanoForge-dev"
-repo = "cli"
+repo = "CLI"
diff --git a/docs/docs/commands/1-overview.mdx b/docs/docs/commands/1-overview.mdx
index feff2b4..4d3915f 100644
--- a/docs/docs/commands/1-overview.mdx
+++ b/docs/docs/commands/1-overview.mdx
@@ -36,19 +36,16 @@ nf --help
Serve a built project, optionally over HTTPS.
-
- Generate project files from your configuration.
-
-
+
Create a single component or system.
-
+
Add components, systems, or libraries from the registry.
-
+
Launch the NanoForge visual editor.
-
+
Authenticate, publish, and unpublish packages.
diff --git a/docs/docs/commands/2-commands/2-dev.mdx b/docs/docs/commands/2-commands/2-dev.mdx
index 022d1a6..af69747 100644
--- a/docs/docs/commands/2-commands/2-dev.mdx
+++ b/docs/docs/commands/2-commands/2-dev.mdx
@@ -16,11 +16,10 @@ nf dev
## Options
-| Option | Description |
-| ----------------------------- | ------------------------------------------------------------------------------------------ |
-| `-d, --directory ` | Working directory of the command. |
-| `-c, --config ` | Path to the configuration file. |
-| `--generate` | Generate app files from config in dev mode, like the `generate` command. Default: `false`. |
+| Option | Description |
+| ----------------------------- | --------------------------------- |
+| `-d, --directory ` | Working directory of the command. |
+| `-c, --config ` | Path to the configuration file. |
## Examples
@@ -29,9 +28,3 @@ nf dev
```bash
nf dev
```
-
-**Regenerate files from config while developing:**
-
-```bash
-nf dev --generate
-```
diff --git a/docs/docs/commands/2-commands/4-start.mdx b/docs/docs/commands/2-commands/4-start.mdx
index 1b9e8b2..cf1f49e 100644
--- a/docs/docs/commands/2-commands/4-start.mdx
+++ b/docs/docs/commands/2-commands/4-start.mdx
@@ -27,8 +27,8 @@ nf start
| `--client-dir ` | Directory of the built client. |
| `--server-dir ` | Directory of the built server. |
| `--watch` | Run in watch mode. Default: `false`. |
-| `--cert ` | Path to the SSL certificate for HTTPS. |
-| `--key ` | Path to the SSL key for HTTPS. |
+| `--cert ` | Path to the TLS certificate for HTTPS. |
+| `--key ` | Path to the TLS key for HTTPS. |
## Examples
diff --git a/docs/docs/commands/2-commands/6-create.mdx b/docs/docs/commands/2-commands/5-create.mdx
similarity index 100%
rename from docs/docs/commands/2-commands/6-create.mdx
rename to docs/docs/commands/2-commands/5-create.mdx
diff --git a/docs/docs/commands/2-commands/5-generate.mdx b/docs/docs/commands/2-commands/5-generate.mdx
deleted file mode 100644
index 831fc97..0000000
--- a/docs/docs/commands/2-commands/5-generate.mdx
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: generate
-description: Generate NanoForge project files from configuration
----
-
-## Overview
-
-`nf generate` generates project files from your [configuration](../../configuration/1-configuration-file).
-It reads the config and produces the entry files and wiring your project needs, including the editor
-main file when requested.
-
-## Usage
-
-```bash
-nf generate
-```
-
-## Options
-
-| Option | Description |
-| ----------------------------- | ----------------------------------------- |
-| `-d, --directory ` | Working directory of the command. |
-| `-c, --config ` | Path to the configuration file. |
-| `--editor` | Generate the editor main file. |
-| `--watch` | Generate in watch mode. Default: `false`. |
-
-## Examples
-
-**Generate once:**
-
-```bash
-nf generate
-```
-
-**Regenerate on change, including the editor entry:**
-
-```bash
-nf generate --editor --watch
-```
diff --git a/docs/docs/commands/2-commands/7-install.mdx b/docs/docs/commands/2-commands/6-install.mdx
similarity index 100%
rename from docs/docs/commands/2-commands/7-install.mdx
rename to docs/docs/commands/2-commands/6-install.mdx
diff --git a/docs/docs/commands/2-commands/8-editor.mdx b/docs/docs/commands/2-commands/7-editor.mdx
similarity index 100%
rename from docs/docs/commands/2-commands/8-editor.mdx
rename to docs/docs/commands/2-commands/7-editor.mdx
diff --git a/docs/docs/commands/2-commands/9-registry.mdx b/docs/docs/commands/2-commands/8-registry.mdx
similarity index 100%
rename from docs/docs/commands/2-commands/9-registry.mdx
rename to docs/docs/commands/2-commands/8-registry.mdx
diff --git a/docs/docs/configuration/1-configuration-file.mdx b/docs/docs/configuration/1-configuration-file.mdx
index 4063f2d..51a89b9 100644
--- a/docs/docs/configuration/1-configuration-file.mdx
+++ b/docs/docs/configuration/1-configuration-file.mdx
@@ -1,96 +1,140 @@
---
title: Configuration file
-description: The nanoforge.config.json file consumed by the CLI
+description: The nanoforge.config.ts (or .js) file consumed by the CLI
---
import { Tooltip } from "mintlify/components";
## Overview
-Every NanoForge project has a `nanoforge.config.json` at its root. The CLI reads this file to know
-how to build, generate, and start your game. Commands accept `-c, --config ` to point at a
-different file, and most fields can be overridden per run with command flags.
-
-## Top-level fields
-
-| Field | Type | Default | Description |
-| --------------- | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
-| `name` | `string` | `nanoforge-app` | The name of the application. |
-| `language` | `"ts" \| "js"` | `ts` | Language used in the project. |
-| `initFunctions` | `boolean` | `true` | Whether init function lifecycle hooks are wired up. |
-| `client` | `object` | - | Client configuration. See below. |
-| `server` | `object` | - | Server configuration. See below. |
-| `ssl` | `object` | - | Ssl configuration for the game. See below. |
-
-## `client`
-
-| Field | Type | Default | Description |
-| ----------------- | --------- | ---------------------------------- | -------------------------------------- |
-| `enable` | `boolean` | `true` | Whether the client is enabled. |
-| `port` | `string` | `3000` | Port the client listens on. |
-| `outDir` | `string` | `.nanoforge/client` | Build output directory for the client. |
-| `build.entry` | `string` | `client/main.ts` | Client entry file. |
-| `build.staticDir` | `string` | `client/static` | Client static assets directory. |
-| `editor.entry` | `string` | `.nanoforge/editor/client/main.ts` | Editor entry file for the client. |
-| `editor.save` | `string` | `.nanoforge/client.save.json` | Editor save file for the client. |
-| `dirs.components` | `string` | `client/components` | Where client components live. |
-| `dirs.systems` | `string` | `client/systems` | Where client systems live. |
-
-## `server`
-
-The `server` block mirrors `client`, minus `port`. It is disabled by default.
-
-| Field | Type | Default | Description |
-| ----------------- | --------- | ---------------------------------- | -------------------------------------- |
-| `enable` | `boolean` | `false` | Whether the server is enabled. |
-| `outDir` | `string` | `.nanoforge/server` | Build output directory for the server. |
-| `build.entry` | `string` | `server/main.ts` | Server entry file. |
-| `build.staticDir` | `string` | `server/static` | Server static assets directory. |
-| `editor.entry` | `string` | `.nanoforge/editor/server/main.ts` | Editor entry file for the server. |
-| `editor.save` | `string` | `.nanoforge/server.save.json` | Editor save file for the server. |
-| `dirs.components` | `string` | `server/components` | Where server components live. |
-| `dirs.systems` | `string` | `server/systems` | Where server systems live. |
-
-## `ssl`
-
-| Field | Type | Default | Description |
-| -------- | -------- | ------- | --------------------------- |
-| `enable` | `boolean | `false` | Whether the ssl is enabled. |
-| `cert` | `string` | - | Certificate file. |
-| `key` | `string` | - | Key file. |
-
-## Example
-
-```json
-{
- "name": "my-game",
- "language": "ts",
- "initFunctions": true,
- "client": {
- "enable": true,
- "port": "3000",
- "outDir": ".nanoforge/client",
- "build": { "entry": "client/main.ts", "staticDir": "client/static" },
- "editor": {
- "entry": ".nanoforge/editor/client/main.ts",
- "save": ".nanoforge/client.save.json"
- },
- "dirs": { "components": "client/components", "systems": "client/systems" }
- },
- "server": {
- "enable": false,
- "outDir": ".nanoforge/server",
- "build": { "entry": "server/main.ts", "staticDir": "server/static" },
- "editor": {
- "entry": ".nanoforge/editor/server/main.ts",
- "save": ".nanoforge/server.save.json"
- },
- "dirs": { "components": "server/components", "systems": "server/systems" }
- },
- "ssl": {
- "enable": true,
- "cert": "path/to/cert",
- "key": "path/to/key"
- }
-}
+Every NanoForge workspace, project, or lib has a `nanoforge.config.ts` (or `.js`) at its root,
+exporting its configuration wrapped in `defineConfig` from `@nanoforge-dev/config`. The CLI reads
+this file to know how to build and start your game, and — for a workspace root — to discover the
+projects and libs it contains.
+
+Commands accept `-c, --config ` to point at a different directory to read the config
+from (default: the current directory). The CLI looks for `nanoforge.config.ts` in that directory
+first, falls back to `nanoforge.config.js` when no `.ts` file exists, and fails if neither is
+present.
+
+## `type`
+
+A config's `type` field discriminates between four shapes:
+
+- `workspace` — a monorepo root listing its `packages`.
+- `lib` — a shared library consumed by clients/servers.
+- `client` — a browser-side NanoForge app.
+- `server` — a server-side NanoForge app.
+
+The config at the root directory the CLI is pointed at must be a `workspace` or a `client`/`server`
+project — a bare `lib` config is not valid there. Inside a `workspace`, none of the packages it
+resolves may themselves be of type `workspace`; nesting workspaces isn't supported.
+
+## Defining a config
+
+Wrap your config in `defineConfig` to get type-checking and editor autocompletion on its fields:
+
+```ts
+import { defineConfig } from "@nanoforge-dev/config";
+
+export default defineConfig({
+ type: "client",
+ entryFile: "src/main.ts",
+ libs: ["../../libs/my-lib"],
+});
+```
+
+`defineConfig` is an identity function — it returns its argument unchanged.
+
+## `workspace`
+
+| Field | Type | Default | Description |
+| ---------- | ---------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `packages` | `string[]` | `[]` | Glob patterns matched against directories relative to the workspace root, each expected to hold its own `client`, `server`, or `lib` config. |
+
+A directory matched by a pattern but missing a `nanoforge.config.ts`/`.js` is skipped without
+error. A pattern that matches no project or lib at all only logs a warning, rather than failing
+the whole resolution. A matched directory whose config is itself of type `workspace` throws.
+
+### Example
+
+```ts
+import { defineConfig } from "@nanoforge-dev/config";
+
+export default defineConfig({
+ type: "workspace",
+ packages: ["apps/*", "libs/*"],
+});
+```
+
+## `lib`
+
+| Field | Type | Default | Description |
+| ---------------- | -------- | ------------------- | ------------------------ |
+| `dir.assets` | `string` | `assets` | Where assets live. |
+| `dir.shared` | `string` | `shared` | Where shared code lives. |
+| `dir.components` | `string` | `shared/components` | Used by the editor only. |
+| `dir.systems` | `string` | `shared/systems` | Used by the editor only. |
+| `dir.scenes` | `string` | `shared/scenes` | Used by the editor only. |
+
+### Example
+
+```ts
+import { defineConfig } from "@nanoforge-dev/config";
+
+export default defineConfig({ type: "lib" });
```
+
+## `client` / `server`
+
+`client` and `server` share the exact same fields and defaults — `type` is the only thing that
+tells them apart.
+
+| Field | Type | Default | Description |
+| ---------------- | ---------- | ---------------- | ------------------------------------------------------ |
+| `entryFile` | `string` | `src/main.ts` | Entry file. |
+| `out.dir` | `string` | `dist` | Build output directory. |
+| `out.mainFile` | `string` | `main.js` | Build output entry file name. |
+| `dir.assets` | `string` | `assets` | Where assets live. |
+| `dir.packages` | `string` | `nf_modules` | Where downloaded package dependencies are installed. |
+| `dir.components` | `string` | `src/components` | Used by the editor only. |
+| `dir.systems` | `string` | `src/systems` | Used by the editor only. |
+| `dir.scenes` | `string` | `src/scenes` | Used by the editor only. |
+| `libs` | `string[]` | `[]` | Relative paths to shared libs this project depends on. |
+
+### Example
+
+```ts
+import { defineConfig } from "@nanoforge-dev/config";
+
+export default defineConfig({
+ type: "server",
+ entryFile: "src/main.ts",
+ dir: { assets: "static" },
+ libs: ["../../libs/shared"],
+});
+```
+
+## Resolving defaults
+
+Missing fields are filled in with the defaults for the config's `type`, deep-merging nested
+objects like `dir` and `out`:
+
+```ts
+import { resolveConfig } from "@nanoforge-dev/config";
+
+const resolved = resolveConfig({ type: "client", dir: { assets: "static" } });
+// resolved.dir.assets === "static"
+// resolved.dir.packages === "nf_modules" (default, not overridden)
+```
+
+Array fields (`packages`, `libs`) are replaced wholesale by an override rather than concatenated
+with the defaults.
+
+## Errors
+
+- `ConfigNotFoundError` — thrown by the CLI when neither `nanoforge.config.ts` nor
+ `nanoforge.config.js` exists in the directory it was pointed at.
+- `ConfigParseError` (from `@nanoforge-dev/config`) — thrown while loading or validating a config
+ file's default export. Its `code` identifies the failure: `not-found`, `load-failed`,
+ `no-default-export`, or `invalid-type`.
diff --git a/docs/docs/getting-started/1-introduction.mdx b/docs/docs/getting-started/1-introduction.mdx
index 57c4ac0..6616b17 100644
--- a/docs/docs/getting-started/1-introduction.mdx
+++ b/docs/docs/getting-started/1-introduction.mdx
@@ -24,10 +24,7 @@ production, and publishing reusable packages to the NanoForge registry.
Run your game locally with hot reloading, then start it as you would in production.
-
- Generate components, systems, and entry files from your configuration.
-
-
+
Publish and consume components, systems, and libraries through the NanoForge registry.
diff --git a/docs/docs/getting-started/3-quickstart.mdx b/docs/docs/getting-started/3-quickstart.mdx
index a6b52ac..be335af 100644
--- a/docs/docs/getting-started/3-quickstart.mdx
+++ b/docs/docs/getting-started/3-quickstart.mdx
@@ -60,4 +60,4 @@ This quickstart takes you from an empty folder to a running game in a few minute
## Where to go next
- Learn each command in detail in the [Commands reference](../commands/1-overview).
-- Follow task-oriented [Guides](../../guides/cli/1-overview) for common workflows.
+- Follow task-oriented [Guides](/guides/cli/1-overview) for common workflows.
diff --git a/docs/docs/schematics/1-overview.mdx b/docs/docs/schematics/1-overview.mdx
new file mode 100644
index 0000000..1b14655
--- /dev/null
+++ b/docs/docs/schematics/1-overview.mdx
@@ -0,0 +1,52 @@
+---
+title: Schematics
+description: Overview of the @nanoforge-dev/schematics package
+---
+
+import { Card, CardGroup } from "mintlify/components";
+
+## What are schematics?
+
+NanoForge schematics are code generators built on [Angular DevKit](https://github.com/angular/angular-cli/tree/main/packages/angular_devkit/schematics). They produce the boilerplate files that every NanoForge project needs — from the initial application scaffold down to individual ECS components and systems.
+
+The NanoForge CLI (`nf`) calls these schematics internally whenever you run commands like `nf new` or `nf create`. You can also invoke them directly via the `schematics` CLI for advanced use cases.
+
+## Installation
+
+```bash
+npm install -g @nanoforge-dev/cli
+```
+
+The schematics package is a dependency of the CLI and does not need to be installed separately. If you want to run schematics without the CLI:
+
+```bash
+npm install -g @angular-devkit/schematics-cli
+npm install @nanoforge-dev/schematics
+```
+
+Then invoke directly:
+
+```bash
+schematics @nanoforge-dev/schematics: [options]
+```
+
+## Available schematics
+
+
+
+ Scaffold the root of a NanoForge monorepo: `nanoforge.config.ts`, `package.json`, TypeScript or
+ JavaScript config, and linting.
+
+
+ Generate a client or server project under `apps/` inside a workspace, including its
+ `main.ts` entry point, example component/system, and optional Dockerfile.
+
+
+ Scaffold an ECS component class with an editor manifest for use in the NanoForge editor.
+
+
+ Scaffold an ECS system function with an editor manifest for use in the NanoForge editor.
+
+
+
+see the [changelog](https://github.com/NanoForge-dev/schematics/blob/main/CHANGELOG.md) for the full release history.
diff --git a/docs/docs/schematics/2-schematics/1-workspace.mdx b/docs/docs/schematics/2-schematics/1-workspace.mdx
new file mode 100644
index 0000000..ce94053
--- /dev/null
+++ b/docs/docs/schematics/2-schematics/1-workspace.mdx
@@ -0,0 +1,69 @@
+---
+title: Workspace
+description: Generate a NanoForge workspace (monorepo root)
+---
+
+import { Tooltip } from "mintlify/components";
+
+## Overview
+
+The `workspace` schematic creates the root of a NanoForge monorepo: the `nanoforge.config.ts` workspace declaration, `package.json`, and TypeScript or JavaScript base config. A workspace holds one or more projects generated with the [`project`](./2-project) schematic under `apps/`.
+
+`@nanoforge-dev/cli`, `nanoforge` and (for TypeScript) `typescript` are pinned to the newest registry version published at least 48h ago at generation time, mirroring pnpm's own `minimumReleaseAge` supply-chain protection so a freshly (and potentially compromised) published release isn't picked up immediately. Non-nanoforge packages like `typescript` are pinned to a specific version rather than a range; `typescript` is also kept on its `6.x` line rather than jumping to the `7.x` native rewrite.
+
+## Usage
+
+```bash
+schematics @nanoforge-dev/schematics:workspace --name=my-game
+```
+
+## Options
+
+| Option | Type | Default | Description |
+| ---------------- | ------------------------------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | `string` | `nanoforge-workspace` | The name of the workspace |
+| `directory` | `string` | `name` | Destination directory for the generated files |
+| `language` | `"ts" \| "js"` | `ts` | Language to use for generated source files |
+| `strict` | `boolean` | `true` | Enable strict mode |
+| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager to target |
+| `allowBuilds` | `string[]` | `[]` | Packages allowed to run install/build scripts, in addition to `bun` |
+
+## Generated structure
+
+```
+my-game/
+├── package.json
+├── tsconfig.json # TypeScript only
+├── tsconfig.spec.json # TypeScript only
+├── jsconfig.json # JavaScript only
+├── pnpm-workspace.yaml # if packageManager: pnpm — allowBuilds map
+├── nanoforge.config.ts # (or .js)
+├── .gitignore
+├── README.md
+└── apps/ # generated with the `project` schematic
+```
+
+## Examples
+
+**Minimal TypeScript workspace:**
+
+```bash
+schematics @nanoforge-dev/schematics:workspace --name=my-game
+```
+
+**pnpm workspace:**
+
+```bash
+schematics @nanoforge-dev/schematics:workspace \
+ --name=my-game \
+ --packageManager=pnpm
+```
+
+**pnpm workspace allowing an extra dependency's build scripts (bun is always allowed):**
+
+```bash
+schematics @nanoforge-dev/schematics:workspace \
+ --name=my-game \
+ --packageManager=pnpm \
+ --allowBuilds=esbuild
+```
diff --git a/docs/docs/schematics/2-schematics/2-project.mdx b/docs/docs/schematics/2-schematics/2-project.mdx
new file mode 100644
index 0000000..dcd7c79
--- /dev/null
+++ b/docs/docs/schematics/2-schematics/2-project.mdx
@@ -0,0 +1,100 @@
+---
+title: Project
+description: Generate a NanoForge client or server project inside a workspace
+---
+
+import { Tooltip } from "mintlify/components";
+
+## Overview
+
+The `project` schematic generates a complete client or server project under `directory` (e.g. `apps/client`) inside a [`workspace`](./1-workspace): its own `package.json`, `nanoforge.config.ts`, `tsconfig.json`, example ECS component and system, `src/main.ts` entry point, and optionally a `Dockerfile` and lifecycle init functions. `directory` is the full destination path — it isn't appended with `part` automatically, so generating a client and a server side by side means passing e.g. `--directory=apps/client` and `--directory=apps/server` in two separate invocations.
+
+For a standalone project (`workspace: false`), engine dependency versions are pinned to the newest release published at least 48h ago, mirroring pnpm's own `minimumReleaseAge` supply-chain protection. `@nanoforge-dev/ecs` and `@nanoforge-dev/network` are resolved directly under those same names — no aliasing to a different registry package. A standalone project also generates its own `allowBuilds` config (`pnpm-workspace.yaml`'s `allowBuilds` map, `package.json`'s `allowScripts` or `trustedDependencies`, depending on `packageManager`) since it has no parent workspace root to provide one — see `allowBuilds` below.
+
+Engine packages are declared as `devDependencies`, except `@nanoforge-dev/network` on a server project, which is declared in `dependencies` because the server needs it at runtime. A client project gets `@nanoforge-dev/graphics-2d`, `@nanoforge-dev/input` and `@nanoforge-dev/network` in addition to `@nanoforge-dev/ecs` and `nanoforge`.
+
+## Usage
+
+```bash
+schematics @nanoforge-dev/schematics:project --part=client --workspaceName=my-game --directory=apps/client
+```
+
+## Options
+
+| Option | Type | Default | Description |
+| ---------------- | ------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `part` | `"client" \| "server"` | _(required)_ | The type of project to generate |
+| `workspaceName` | `string` | _(required)_ | Name of the parent workspace |
+| `directory` | `string` | `apps` | Full destination path for this project |
+| `language` | `"ts" \| "js"` | `ts` | Language to use for generated source files |
+| `strict` | `boolean` | `true` | Enable strict mode |
+| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager to target |
+| `workspace` | `boolean` | `true` | Generated inside an existing NanoForge workspace |
+| `initFunctions` | `boolean` | `false` | Add lifecycle init function hooks |
+| `hasServer` | `boolean` | `false` | Sync the client's position with a companion server |
+| `docker` | `boolean` | `false` | Generate a Dockerfile and .dockerignore |
+| `editor` | `boolean` | `false` | Reserved for future editor build support |
+| `libs` | `string[]` | `[]` | Relative paths to shared libs this project depends on, written into `nanoforge.config.ts` |
+| `allowBuilds` | `string[]` | `[]` | Packages allowed to run install/build scripts (standalone projects only) |
+
+## Generated structure
+
+```
+/
+├── package.json
+├── tsconfig.json # TypeScript only
+├── jsconfig.json # JavaScript only
+├── nanoforge.config.ts # (or .js)
+├── pnpm-workspace.yaml # if !workspace && packageManager: pnpm && allowBuilds.length > 0
+├── Dockerfile # if docker: true
+├── .dockerignore # if docker: true
+├── src/
+│ ├── main.ts
+│ ├── components/
+│ │ ├── position-2d.component.ts # both parts
+│ │ └── drawable-circle-2d.component.ts # client only
+│ └── systems/
+│ ├── draw-2d.system.ts # client only
+│ ├── position-sync.system.ts # client only, if hasServer: true
+│ └── move-2d.system.ts # server only
+└── assets/
+```
+
+## Examples
+
+**Generate a client project:**
+
+```bash
+schematics @nanoforge-dev/schematics:project --part=client --workspaceName=my-game --directory=apps/client
+```
+
+**Generate a server project with Docker, syncing position with the client:**
+
+```bash
+schematics @nanoforge-dev/schematics:project \
+ --part=server \
+ --workspaceName=my-game \
+ --directory=apps/server \
+ --docker=true
+```
+
+**Generate a client that syncs its position from that server:**
+
+```bash
+schematics @nanoforge-dev/schematics:project \
+ --part=client \
+ --workspaceName=my-game \
+ --directory=apps/client \
+ --hasServer=true
+```
+
+**Generate a JavaScript client depending on a shared lib:**
+
+```bash
+schematics @nanoforge-dev/schematics:project \
+ --part=client \
+ --workspaceName=my-game \
+ --directory=apps/client \
+ --language=js \
+ --libs=../../libs/shared
+```
diff --git a/docs/docs/schematics/2-schematics/3-component.mdx b/docs/docs/schematics/2-schematics/3-component.mdx
new file mode 100644
index 0000000..9b14080
--- /dev/null
+++ b/docs/docs/schematics/2-schematics/3-component.mdx
@@ -0,0 +1,120 @@
+---
+title: Component
+description: Generate a NanoForge ECS component
+---
+
+import Tooltip from "mint/Tooltip";
+
+## Overview
+
+The `component` schematic scaffolds a new ECS component class for a NanoForge client or server part. The generated file includes the component class, a default export required for code generation, and an `EDITOR_COMPONENT_MANIFEST` required for the NanoForge editor to display and configure the component.
+
+## Usage
+
+```bash
+schematics @nanoforge-dev/schematics:component --part=client --name=health
+```
+
+## Options
+
+| Option | Type | Default | Description |
+| ----------- | ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | `string` | `example` | Component name |
+| `part` | `"client" \| "server"` | _(required)_ | The part of the application this component belongs to |
+| `directory` | `string` | `.` | Destination directory for the generated file |
+| `language` | `"ts" \| "js"` | `ts` | Language to use for the generated file |
+
+## Generated file
+
+`{directory}/{name}.component.ts`
+
+```ts
+import { type EditorComponentManifest } from "@nanoforge-dev/ecs-client";
+
+export class HealthComponent {
+ name = this.constructor.name;
+
+ constructor(
+ public paramA: string,
+ public paramB: number,
+ public paramC: boolean = false,
+ ) {}
+
+ get foo() {
+ return "bar";
+ }
+
+ get paramAOrDefault() {
+ return this.paramC ? this.paramA : "default";
+ }
+
+ addOne() {
+ this.paramB += 1;
+ }
+}
+
+// Required to generate code
+export default HealthComponent.name;
+
+// Required for the editor to display the component and generate code
+export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = {
+ name: "Health",
+ description: "Health component description",
+ params: [
+ {
+ type: "string",
+ name: "paramA",
+ description: "Param A description",
+ example: "Example value",
+ },
+ {
+ type: "number",
+ name: "paramB",
+ description: "Param B description",
+ example: 3,
+ },
+ {
+ type: "boolean",
+ name: "paramC",
+ description: "Param C description",
+ example: true,
+ default: false,
+ optional: true,
+ },
+ ],
+};
+```
+
+## Editor manifest
+
+The `EDITOR_COMPONENT_MANIFEST` export is required for the NanoForge editor to discover the component, render its configuration panel, and generate code. Each `params` entry describes one constructor parameter:
+
+| Field | Required | Description |
+| ------------- | -------- | ------------------------------------------------------------ |
+| `type` | Yes | Parameter type: `"string"`, `"number"`, or `"boolean"` |
+| `name` | Yes | Constructor parameter name |
+| `description` | Yes | Human-readable description shown in the editor |
+| `example` | Yes | Example value used in code generation |
+| `default` | No | Default value when the parameter is optional |
+| `optional` | No | When `true`, the editor treats the parameter as non-required |
+
+## Examples
+
+**Generate a `PlayerComponent` for the client:**
+
+```bash
+schematics @nanoforge-dev/schematics:component \
+ --part=client \
+ --name=player \
+ --directory=client/components
+```
+
+**Generate a JavaScript server component:**
+
+```bash
+schematics @nanoforge-dev/schematics:component \
+ --part=server \
+ --name=network-player \
+ --language=js \
+ --directory=server/components
+```
diff --git a/docs/docs/schematics/2-schematics/4-system.mdx b/docs/docs/schematics/2-schematics/4-system.mdx
new file mode 100644
index 0000000..51ecf8d
--- /dev/null
+++ b/docs/docs/schematics/2-schematics/4-system.mdx
@@ -0,0 +1,105 @@
+---
+title: System
+description: Generate a NanoForge ECS system
+---
+
+import { Tooltip } from "mintlify/components";
+
+## Overview
+
+The `system` schematic scaffolds a new ECS system function for a NanoForge client or server part. The generated file includes the system function, a default export required for code generation, and an `EDITOR_SYSTEM_MANIFEST` required for the NanoForge editor to display and configure the system.
+
+## Usage
+
+```bash
+schematics @nanoforge-dev/schematics:system --part=client --name=movement
+```
+
+## Options
+
+| Option | Type | Default | Description |
+| ----------- | ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `name` | `string` | `example` | System name |
+| `part` | `"client" \| "server"` | _(required)_ | The part of the application this system belongs to |
+| `directory` | `string` | `.` | Destination directory for the generated file |
+| `language` | `"ts" \| "js"` | `ts` | Language to use for the generated file |
+
+## Generated file
+
+`{directory}/{name}.system.ts`
+
+```ts
+import { type Context } from "@nanoforge-dev/common";
+import { type EditorSystemManifest, type Registry } from "@nanoforge-dev/ecs-client";
+
+import { ExampleComponent } from "../components/example.component";
+
+export const movementSystem = (registry: Registry, ctx: Context) => {
+ const entities = registry.getZipper([ExampleComponent]);
+
+ entities.forEach((entity) => {
+ if (entity.ExampleComponent.paramA === "end") {
+ ctx.app.setIsRunning(false);
+ return;
+ }
+
+ if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end";
+
+ if (entity.ExampleComponent.paramB >= 0)
+ entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1;
+ });
+};
+
+// Required to generate code
+export default movementSystem.name;
+
+// Required for the editor to display the system and generate code
+export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
+ name: "movement",
+ description:
+ "This system end the game when paramB reaches 0 for any entity with ExampleComponent",
+ dependencies: ["ExampleComponent"],
+};
+```
+
+## Editor manifest
+
+The `EDITOR_SYSTEM_MANIFEST` export is required for the NanoForge editor to discover the system and wire it into the game loop.
+
+| Field | Required | Description |
+| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | Yes | System name as displayed in the editor |
+| `description` | Yes | Human-readable description of what the system does |
+| `dependencies` | Yes | Component names this system depends on |
+
+## System signature
+
+Every NanoForge system follows the same function signature:
+
+```ts
+(registry: Registry, ctx: Context) => void
+```
+
+- **`registry`** — the ECS registry; use `registry.getZipper([...])` to query entities with specific components.
+- **`ctx`** — the application context; provides access to `ctx.app`, `ctx.assets`, and other engine services.
+
+## Examples
+
+**Generate a `PhysicsSystem` for the client:**
+
+```bash
+schematics @nanoforge-dev/schematics:system \
+ --part=client \
+ --name=physics \
+ --directory=client/systems
+```
+
+**Generate a JavaScript server system:**
+
+```bash
+schematics @nanoforge-dev/schematics:system \
+ --part=server \
+ --name=sync-state \
+ --language=js \
+ --directory=server/systems
+```
diff --git a/docs/guides/cli/1-overview.mdx b/docs/guides/cli/1-overview.mdx
index eec3f3a..0f987de 100644
--- a/docs/guides/cli/1-overview.mdx
+++ b/docs/guides/cli/1-overview.mdx
@@ -5,7 +5,7 @@ description: Task-oriented walkthroughs for common NanoForge CLI workflows
import { Card, CardGroup } from "mintlify/components";
-The [Commands reference](../../docs/commands/1-overview) documents what each command does. These guides
+The [Commands reference](/docs/commands/1-overview) documents what each command does. These guides
take the opposite angle: they follow the **paths a user actually walks** when building a game with
the CLI, stitching commands together end to end.
diff --git a/docs/guides/cli/3-develop-locally.mdx b/docs/guides/cli/3-develop-locally.mdx
index e474f61..3dcda03 100644
--- a/docs/guides/cli/3-develop-locally.mdx
+++ b/docs/guides/cli/3-develop-locally.mdx
@@ -18,22 +18,6 @@ Once a project exists, most of your time is spent in the develop-and-iterate loo
-
- When you change `nanoforge.config.json` - for example adding new component or system directories
- - regenerate the project files. You can do this in a one-off run:
-
- ```bash
- nf generate
- ```
-
- Or have dev mode regenerate as it runs:
-
- ```bash
- nf dev --generate
- ```
-
-
-
Working in a monorepo or with multiple configs? Every command accepts `-d` and `-c`:
diff --git a/e2e/cli-build.test.ts b/e2e/cli-build.test.ts
index 82da5d5..7a917ab 100644
--- a/e2e/cli-build.test.ts
+++ b/e2e/cli-build.test.ts
@@ -2,10 +2,29 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { stripDefineConfig, writeStandaloneEntry } from "./helpers/project-fixtures";
import { runCli } from "./helpers/run-cli";
const tmpDir = resolve(__dirname, "../.tmp-e2e-build");
+const newProject = (name: string, directory: string, server: boolean) =>
+ runCli([
+ "new",
+ "--name",
+ name,
+ "--language",
+ "ts",
+ "--package-manager",
+ "npm",
+ "--strict",
+ server ? "--server" : "--no-server",
+ "--skip-install",
+ "--no-docker",
+ "--no-git",
+ "-d",
+ directory,
+ ]);
+
beforeAll(async () => {
mkdirSync(tmpDir, { recursive: true });
});
@@ -17,65 +36,48 @@ afterAll(() => {
describe("nf build (TypeScript, no server)", () => {
const projectDir = resolve(tmpDir, "build-ts-no-server");
const appDir = resolve(projectDir, "build-app");
+ const outDir = resolve(appDir, "dist");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "build-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--no-init-functions",
- "--no-skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ await newProject("build-app", projectDir, false);
+ stripDefineConfig(appDir);
+ writeStandaloneEntry(appDir);
});
it("should run the build command", async () => {
const { exitCode } = await runCli(["build", "-d", appDir]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client"))).toBe(true);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "main.js"))).toBe(true);
+ expect(existsSync(resolve(outDir, "main.js"))).toBe(true);
});
- it("should accept --config option", async () => {
- const { exitCode } = await runCli(["build", "-d", appDir, "--config", "nanoforge.config.json"]);
+ it("should accept --client-out-dir option", async () => {
+ const { exitCode } = await runCli(["build", "-d", appDir, "--client-out-dir", "custom-out"]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "main.js"))).toBe(true);
+ expect(existsSync(resolve(appDir, "custom-out", "main.js"))).toBe(true);
});
- it("should accept --client-out-dir option", async () => {
- const { exitCode } = await runCli(["build", "-d", appDir, "--client-out-dir", "custom-out"]);
+ it("should accept --client-entry option", async () => {
+ writeFileSync(resolve(appDir, "src", "other.ts"), 'console.log("other");\n');
+
+ const { exitCode } = await runCli(["build", "-d", appDir, "--client-entry", "src/other.ts"]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, "custom-out"))).toBe(true);
- expect(existsSync(resolve(appDir, "custom-out", "main.js"))).toBe(true);
+ expect(existsSync(resolve(outDir, "other.js"))).toBe(true);
});
- it("should copy static files to output dir", async () => {
- const staticDir = resolve(appDir, "client", "static");
- mkdirSync(staticDir, { recursive: true });
- writeFileSync(resolve(staticDir, "asset.txt"), "hello");
+ it("should copy assets to output dir", async () => {
+ writeFileSync(resolve(appDir, "assets", "asset.txt"), "hello");
const { exitCode } = await runCli(["build", "-d", appDir]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "asset.txt"))).toBe(true);
+ expect(existsSync(resolve(outDir, "asset.txt"))).toBe(true);
});
it("should reset output dir before rebuild", async () => {
- const outDir = resolve(appDir, ".nanoforge", "client");
mkdirSync(outDir, { recursive: true });
writeFileSync(resolve(outDir, "stale.js"), "// stale");
@@ -100,97 +102,89 @@ describe("nf build (TypeScript, no server)", () => {
]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "custom-asset.txt"))).toBe(true);
+ expect(existsSync(resolve(outDir, "custom-asset.txt"))).toBe(true);
});
it("should keep process alive with --watch flag", async () => {
const { killed } = await runCli(["build", "-d", appDir, "--watch"], { timeout: 3000 });
-
expect(killed).toBe(true);
});
it("should accept --editor flag and use editor entry", async () => {
- const editorEntryDir = resolve(appDir, ".nanoforge", "editor", "client");
- mkdirSync(editorEntryDir, { recursive: true });
- writeFileSync(resolve(editorEntryDir, "main.ts"), 'console.log("editor");');
-
const { exitCode } = await runCli(["build", "-d", appDir, "--editor"]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "main.js"))).toBe(true);
+ expect(existsSync(resolve(outDir, "main.js"))).toBe(true);
});
});
describe("nf build (TypeScript, with server)", () => {
const projectDir = resolve(tmpDir, "build-ts-with-server");
- const appDir = resolve(projectDir, "build-server-app");
+ const workspaceDir = resolve(projectDir, "build-server-app");
+ const clientDir = resolve(workspaceDir, "apps/client");
+ const serverDir = resolve(workspaceDir, "apps/server");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
+ await newProject("build-server-app", projectDir, true);
+ for (const dir of [workspaceDir, clientDir, serverDir]) stripDefineConfig(dir);
+ writeStandaloneEntry(clientDir);
+ writeStandaloneEntry(serverDir);
+ });
- await runCli([
- "new",
- "--name",
- "build-server-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--no-strict",
- "--server",
- "--no-init-functions",
- "--no-skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ it("should build every project of the workspace", async () => {
+ const { exitCode } = await runCli(["build", "-d", workspaceDir]);
+
+ expect(exitCode).toBe(0);
+ expect(existsSync(resolve(clientDir, "dist", "main.js"))).toBe(true);
+ expect(existsSync(resolve(serverDir, "dist", "main.js"))).toBe(true);
});
- it("should run the build command with server enabled", async () => {
- const { exitCode } = await runCli(["build", "-d", appDir]);
+ it("should build a single project of the workspace", async () => {
+ rmSync(resolve(clientDir, "dist"), { recursive: true, force: true });
+
+ const { exitCode } = await runCli(["build", "-d", serverDir]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "client", "main.js"))).toBe(true);
- expect(existsSync(resolve(appDir, ".nanoforge", "server", "main.js"))).toBe(true);
+ expect(existsSync(resolve(serverDir, "dist", "main.js"))).toBe(true);
+ expect(existsSync(resolve(clientDir, "dist"))).toBe(false);
});
it("should accept --server-out-dir option", async () => {
const { exitCode } = await runCli([
"build",
"-d",
- appDir,
+ workspaceDir,
"--server-out-dir",
"custom-server-out",
]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, "custom-server-out"))).toBe(true);
- expect(existsSync(resolve(appDir, "custom-server-out", "main.js"))).toBe(true);
+ expect(existsSync(resolve(serverDir, "custom-server-out", "main.js"))).toBe(true);
+ expect(existsSync(resolve(clientDir, "dist", "main.js"))).toBe(true);
});
it("should accept --server-static-dir option and copy files from it", async () => {
- const customStaticDir = resolve(appDir, "custom-server-static");
+ const customStaticDir = resolve(serverDir, "custom-server-static");
mkdirSync(customStaticDir, { recursive: true });
writeFileSync(resolve(customStaticDir, "server-asset.txt"), "server");
const { exitCode } = await runCli([
"build",
"-d",
- appDir,
+ workspaceDir,
"--server-static-dir",
"custom-server-static",
]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, ".nanoforge", "server", "server-asset.txt"))).toBe(true);
+ expect(existsSync(resolve(serverDir, "dist", "server-asset.txt"))).toBe(true);
});
});
describe("nf build (with invalid directory)", () => {
it("should fail when directory does not exist", async () => {
const { exitCode } = await runCli(["build", "-d", resolve(tmpDir, "nonexistent")]);
-
expect(exitCode).not.toBe(0);
});
@@ -199,7 +193,6 @@ describe("nf build (with invalid directory)", () => {
mkdirSync(emptyDir, { recursive: true });
const { exitCode } = await runCli(["build", "-d", emptyDir]);
-
expect(exitCode).not.toBe(0);
});
});
diff --git a/e2e/cli-create.test.ts b/e2e/cli-create.test.ts
index 4e09265..cb88640 100644
--- a/e2e/cli-create.test.ts
+++ b/e2e/cli-create.test.ts
@@ -2,10 +2,29 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
import { resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { stripDefineConfig } from "./helpers/project-fixtures";
import { runCli } from "./helpers/run-cli";
const tmpDir = resolve(__dirname, "../.tmp-e2e-create");
+const newProject = (name: string, directory: string, language: "ts" | "js", server: boolean) =>
+ runCli([
+ "new",
+ "--name",
+ name,
+ "--language",
+ language,
+ "--package-manager",
+ "npm",
+ "--no-strict",
+ server ? "--server" : "--no-server",
+ "--skip-install",
+ "--no-docker",
+ "--no-git",
+ "-d",
+ directory,
+ ]);
+
beforeAll(() => {
mkdirSync(tmpDir, { recursive: true });
});
@@ -14,30 +33,14 @@ afterAll(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
-describe("nf create (TypeScript, client component)", () => {
+describe("nf create (TypeScript, client project)", () => {
const projectDir = resolve(tmpDir, "create-ts-client");
const appDir = resolve(projectDir, "create-app");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "create-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ await newProject("create-app", projectDir, "ts", false);
+ stripDefineConfig(appDir);
});
it("should create a component successfully", async () => {
@@ -55,8 +58,8 @@ describe("nf create (TypeScript, client component)", () => {
expect(stdout).toContain("Element successfully created");
});
- it("should generate a component file in client/components", () => {
- expect(existsSync(resolve(appDir, "client/components/player.component.ts"))).toBe(true);
+ it("should generate a component file in src/components", () => {
+ expect(existsSync(resolve(appDir, "src/components/player.component.ts"))).toBe(true);
});
it("should create a system successfully", async () => {
@@ -73,23 +76,8 @@ describe("nf create (TypeScript, client component)", () => {
expect(stdout).toContain("Element successfully created");
});
- it("should generate a system file in client/systems", () => {
- expect(existsSync(resolve(appDir, "client/systems/movement.system.ts"))).toBe(true);
- });
-
- it("should accept --config option", async () => {
- const { exitCode } = await runCli([
- "create",
- "component",
- "--name",
- "health",
- "--config",
- "nanoforge.config.json",
- "-d",
- appDir,
- ]);
-
- expect(exitCode).toBe(0);
+ it("should generate a system file in src/systems", () => {
+ expect(existsSync(resolve(appDir, "src/systems/movement.system.ts"))).toBe(true);
});
it("should create a component with a custom path via --path", async () => {
@@ -99,40 +87,26 @@ describe("nf create (TypeScript, client component)", () => {
"--name",
"custom",
"--path",
- "client/custom-components",
+ "src/custom-components",
"-d",
appDir,
]);
expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, "client/custom-components/custom.component.ts"))).toBe(true);
+ expect(existsSync(resolve(appDir, "src/custom-components/custom.component.ts"))).toBe(true);
});
});
-describe("nf create (TypeScript, server component)", () => {
+describe("nf create (TypeScript, server project in a workspace)", () => {
const projectDir = resolve(tmpDir, "create-ts-server");
- const appDir = resolve(projectDir, "create-server-app");
+ const workspaceDir = resolve(projectDir, "create-server-app");
+ const serverDir = resolve(workspaceDir, "apps/server");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "create-server-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--no-strict",
- "--server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ await newProject("create-server-app", projectDir, "ts", true);
+ stripDefineConfig(workspaceDir);
+ stripDefineConfig(serverDir);
});
it("should create a server component successfully", async () => {
@@ -140,52 +114,34 @@ describe("nf create (TypeScript, server component)", () => {
"create",
"component",
"--name",
- "enemy",
- "--server",
+ "network",
"-d",
- appDir,
+ serverDir,
]);
expect(exitCode).toBe(0);
- });
-
- it("should generate a component file in server/components", () => {
- expect(existsSync(resolve(appDir, "server/components/enemy.component.ts"))).toBe(true);
+ expect(existsSync(resolve(serverDir, "src/components/network.component.ts"))).toBe(true);
});
it("should create a server system successfully", async () => {
- const { exitCode } = await runCli([
- "create",
- "system",
- "--name",
- "physics",
- "--server",
- "-d",
- appDir,
- ]);
+ const { exitCode } = await runCli(["create", "system", "--name", "sync", "-d", serverDir]);
expect(exitCode).toBe(0);
+ expect(existsSync(resolve(serverDir, "src/systems/sync.system.ts"))).toBe(true);
});
- it("should generate a system file in server/systems", () => {
- expect(existsSync(resolve(appDir, "server/systems/physics.system.ts"))).toBe(true);
- });
-
- it("should create a server component with a custom path via --path", async () => {
+ it("should fail when run at the workspace root", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
- "network",
- "--server",
- "--path",
- "server/custom-components",
+ "root",
"-d",
- appDir,
+ workspaceDir,
]);
- expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, "server/custom-components/network.component.ts"))).toBe(true);
+ expect(exitCode).not.toBe(0);
+ expect(existsSync(resolve(workspaceDir, "src/components/root.component.ts"))).toBe(false);
});
});
@@ -195,44 +151,26 @@ describe("nf create (JavaScript)", () => {
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "create-js-app",
- "--language",
- "js",
- "--package-manager",
- "npm",
- "--no-strict",
- "--no-server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ await newProject("create-js-app", projectDir, "js", false);
+ stripDefineConfig(appDir);
});
it("should create a JavaScript component successfully", async () => {
const { exitCode } = await runCli(["create", "component", "--name", "sprite", "-d", appDir]);
-
expect(exitCode).toBe(0);
});
it("should generate a .js component file", () => {
- expect(existsSync(resolve(appDir, "client/components/sprite.component.js"))).toBe(true);
+ expect(existsSync(resolve(appDir, "src/components/sprite.component.js"))).toBe(true);
});
it("should create a JavaScript system successfully", async () => {
const { exitCode } = await runCli(["create", "system", "--name", "render", "-d", appDir]);
-
expect(exitCode).toBe(0);
});
it("should generate a .js system file", () => {
- expect(existsSync(resolve(appDir, "client/systems/render.system.js"))).toBe(true);
+ expect(existsSync(resolve(appDir, "src/systems/render.system.js"))).toBe(true);
});
});
@@ -246,16 +184,12 @@ describe("nf create (error cases)", () => {
"-d",
resolve(tmpDir, "nonexistent"),
]);
-
expect(exitCode).not.toBe(0);
});
it("should fail with an invalid type", async () => {
- const projectDir = resolve(tmpDir, "create-ts-client");
- const appDir = resolve(projectDir, "create-app");
-
+ const appDir = resolve(tmpDir, "create-ts-client/create-app");
const { exitCode } = await runCli(["create", "invalid-type", "--name", "test", "-d", appDir]);
-
expect(exitCode).not.toBe(0);
});
});
diff --git a/e2e/cli-generate.test.ts b/e2e/cli-generate.test.ts
deleted file mode 100644
index 05d74a9..0000000
--- a/e2e/cli-generate.test.ts
+++ /dev/null
@@ -1,355 +0,0 @@
-import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
-import { resolve } from "node:path";
-import { afterAll, beforeAll, describe, expect, it } from "vitest";
-
-import { runCli } from "./helpers/run-cli";
-
-const tmpDir = resolve(__dirname, "../.tmp-e2e-generate");
-
-beforeAll(async () => {
- mkdirSync(tmpDir, { recursive: true });
-});
-
-afterAll(() => {
- rmSync(tmpDir, { recursive: true, force: true });
-});
-
-describe("nf generate (TypeScript, no server)", () => {
- const projectDir = resolve(tmpDir, "gen-ts-no-server");
- const appDir = resolve(projectDir, "gen-app");
-
- beforeAll(async () => {
- mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "gen-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
- });
-
- it("should run the generate command", async () => {
- const { exitCode } = await runCli(["generate", "-d", appDir]);
-
- expect(exitCode).toBe(0);
- });
-
- it("should accept --config option", async () => {
- const { exitCode } = await runCli([
- "generate",
- "-d",
- appDir,
- "--config",
- "nanoforge.config.json",
- ]);
-
- expect(exitCode).toBe(0);
- });
-
- it("should generate client main.ts file", async () => {
- const { exitCode } = await runCli(["generate", "-d", appDir]);
-
- expect(exitCode).toBe(0);
- expect(existsSync(resolve(appDir, "client/main.ts"))).toBe(true);
- });
-
- it("should generate valid TypeScript client main content", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- // Core imports
- expect(content).toContain('import { type IRunOptions } from "@nanoforge-dev/common"');
- expect(content).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core"');
-
- // Library imports
- expect(content).toContain('import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager"');
- expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"');
- expect(content).toContain('import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d"');
- expect(content).toContain('import { InputLibrary } from "@nanoforge-dev/input"');
- expect(content).toContain('import { MusicLibrary } from "@nanoforge-dev/music"');
- expect(content).toContain('import { SoundLibrary } from "@nanoforge-dev/sound"');
-
- // Main function signature with TypeScript type
- expect(content).toContain("export async function main(options: IRunOptions)");
-
- // Factory creates client
- expect(content).toContain("NanoforgeFactory.createClient()");
-
- // App lifecycle
- expect(content).toContain("await app.init(options)");
- expect(content).toContain("await app.run()");
- });
-
- it("should generate init function imports and calls", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- // Init function imports
- expect(content).toContain('import { afterInit } from "./init/after-init"');
- expect(content).toContain('import { beforeInit } from "./init/before-init"');
- expect(content).toContain('import { afterRegistryInit } from "./init/after-registry-init"');
- expect(content).toContain('import { beforeRegistryInit } from "./init/before-registry-init"');
- expect(content).toContain('import { afterRun } from "./init/after-run"');
- expect(content).toContain('import { beforeRun } from "./init/before-run"');
-
- // Init function calls
- expect(content).toContain("await beforeInit(app)");
- expect(content).toContain("await afterInit(app)");
- expect(content).toContain("await beforeRun(app)");
- expect(content).toContain("await afterRun(app)");
- expect(content).toContain("await beforeRegistryInit(app, registry)");
- expect(content).toContain("await afterRegistryInit(app, registry)");
- });
-
- it("should generate library instantiation and registration", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- // Library instantiation
- expect(content).toContain("const assetManagerLibrary = new AssetManagerLibrary()");
- expect(content).toContain("const ecsLibrary = new ECSClientLibrary()");
- expect(content).toContain("const graphicsLibrary = new Graphics2DLibrary()");
- expect(content).toContain("const inputLibrary = new InputLibrary()");
- expect(content).toContain("const musicLibrary = new MusicLibrary()");
- expect(content).toContain("const soundLibrary = new SoundLibrary()");
-
- // Library registration
- expect(content).toContain("app.useAssetManager(assetManagerLibrary)");
- expect(content).toContain("app.useComponentSystem(ecsLibrary)");
- expect(content).toContain("app.useGraphics(graphicsLibrary)");
- expect(content).toContain("app.useInput(inputLibrary)");
- expect(content).toContain("app.useSound(soundLibrary)");
- });
-
- it("should generate ECS components, systems, and entities", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- // Component and system imports
- expect(content).toContain('import { ExampleComponent } from "./components/example.component"');
- expect(content).toContain('import { exampleSystem } from "./systems/example.system"');
-
- // Registry access
- expect(content).toContain("const registry = ecsLibrary.registry");
-
- // Entity creation and component attachment
- expect(content).toContain("const exampleEntity = registry.spawnEntity()");
- expect(content).toContain(
- 'registry.addComponent(exampleEntity, new ExampleComponent("example", 10, undefined))',
- );
-
- // System registration
- expect(content).toContain("registry.addSystem(exampleSystem)");
- });
-
- it("should regenerate main.ts with modified save file (add component and entity)", async () => {
- const savePath = resolve(appDir, ".nanoforge/client.save.json");
- const save = JSON.parse(readFileSync(savePath, "utf-8"));
-
- save.components.push({
- name: "HealthComponent",
- path: "./components/health.component.ts",
- paramsNames: ["health"],
- });
-
- save.entities[0].components["HealthComponent"] = { health: 100 };
-
- save.entities.push({
- id: "exampleEntity2",
- components: {
- ExampleComponent: {
- paramA: "example2",
- paramB: 15,
- },
- },
- });
-
- writeFileSync(savePath, JSON.stringify(save, null, 2));
-
- const { exitCode } = await runCli(["generate", "-d", appDir]);
- expect(exitCode).toBe(0);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- expect(content).toContain('import { HealthComponent } from "./components/health.component"');
- expect(content).toContain("new HealthComponent(100)");
-
- expect(content).toContain("const exampleEntity2 = registry.spawnEntity()");
- expect(content).toContain(
- 'registry.addComponent(exampleEntity2, new ExampleComponent("example2", 15, undefined))',
- );
-
- // Original component should still be present
-
- expect(content).toContain('import { ExampleComponent } from "./components/example.component"');
- expect(content).toContain("const exampleEntity = registry.spawnEntity()");
- expect(content).toContain(
- 'registry.addComponent(exampleEntity, new ExampleComponent("example", 10, undefined))',
- );
- });
-
- it("should not generate server directory", async () => {
- expect(existsSync(resolve(appDir, "server"))).toBe(false);
- });
-
- it("should generate client editor main.ts", async () => {
- await runCli(["generate", "-d", appDir, "--editor"]);
-
- const content = readFileSync(resolve(appDir, ".nanoforge/editor/client/main.ts"), "utf-8");
-
- expect(content).toContain("NanoforgeFactory.createClient()");
- expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"');
- expect(content).toContain('from "@nanoforge-dev/core-editor"');
- });
-});
-
-describe("nf generate (TypeScript, with server)", () => {
- const projectDir = resolve(tmpDir, "gen-ts-with-server");
- const appDir = resolve(projectDir, "gen-server-app");
-
- beforeAll(async () => {
- mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "gen-server-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--no-strict",
- "--server",
- "--init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
- });
-
- it("should run the generate command with server enabled", async () => {
- const { exitCode } = await runCli(["generate", "-d", appDir]);
-
- expect(exitCode).toBe(0);
- });
-
- it("should generate server main.ts file", async () => {
- await runCli(["generate", "-d", appDir]);
-
- expect(existsSync(resolve(appDir, "server/main.ts"))).toBe(true);
- });
-
- it("should generate valid TypeScript server main content", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "server/main.ts"), "utf-8");
-
- // Core imports
- expect(content).toContain('import { type IRunOptions } from "@nanoforge-dev/common"');
- expect(content).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core"');
-
- // Server-specific library
- expect(content).toContain('import { ECSServerLibrary } from "@nanoforge-dev/ecs-server"');
-
- // Factory creates server (not client)
- expect(content).toContain("NanoforgeFactory.createServer()");
- expect(content).not.toContain("NanoforgeFactory.createClient()");
-
- // Main function signature
- expect(content).toContain("export async function main(options: IRunOptions)");
-
- // App lifecycle
- expect(content).toContain("await app.init(options)");
- expect(content).toContain("await app.run()");
- });
-
- it("should generate server init function imports and calls", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "server/main.ts"), "utf-8");
-
- expect(content).toContain('import { beforeInit } from "./init/before-init"');
- expect(content).toContain('import { afterInit } from "./init/after-init"');
- expect(content).toContain("await beforeInit(app)");
- expect(content).toContain("await afterInit(app)");
- });
-
- it("should generate server ECS setup", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "server/main.ts"), "utf-8");
-
- expect(content).toContain("const ecsLibrary = new ECSServerLibrary()");
- expect(content).toContain("app.useComponentSystem(ecsLibrary)");
- expect(content).toContain("const registry = ecsLibrary.registry");
- expect(content).toContain("const exampleEntity = registry.spawnEntity()");
- expect(content).toContain("registry.addSystem(exampleSystem)");
- });
-
- it("should regenerate server main.ts with modified save file (add system)", async () => {
- const savePath = resolve(appDir, ".nanoforge/server.save.json");
- const save = JSON.parse(readFileSync(savePath, "utf-8"));
-
- save.systems.push({
- name: "movementSystem",
- path: "./systems/movement.system.ts",
- });
-
- writeFileSync(savePath, JSON.stringify(save, null, 2));
-
- const { exitCode } = await runCli(["generate", "-d", appDir]);
- expect(exitCode).toBe(0);
-
- const content = readFileSync(resolve(appDir, "server/main.ts"), "utf-8");
-
- expect(content).toContain('import { movementSystem } from "./systems/movement.system"');
- expect(content).toContain("registry.addSystem(movementSystem)");
- // Original system should still be present
- expect(content).toContain('import { exampleSystem } from "./systems/example.system"');
- expect(content).toContain("registry.addSystem(exampleSystem)");
- });
-
- it("should also regenerate client main.ts", async () => {
- await runCli(["generate", "-d", appDir]);
-
- const content = readFileSync(resolve(appDir, "client/main.ts"), "utf-8");
-
- expect(content).toContain("NanoforgeFactory.createClient()");
- expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"');
- });
-});
-
-describe("nf generate (with invalid directory)", () => {
- it("should fail when directory does not exist", async () => {
- const { exitCode } = await runCli(["generate", "-d", resolve(tmpDir, "nonexistent")]);
-
- expect(exitCode).not.toBe(0);
- });
-
- it("should fail when no config file is found", async () => {
- const emptyDir = resolve(tmpDir, "empty-dir");
- mkdirSync(emptyDir, { recursive: true });
-
- const { exitCode } = await runCli(["generate", "-d", emptyDir]);
-
- expect(exitCode).not.toBe(0);
- });
-});
diff --git a/e2e/cli-help.test.ts b/e2e/cli-help.test.ts
index d5a1311..662025d 100644
--- a/e2e/cli-help.test.ts
+++ b/e2e/cli-help.test.ts
@@ -10,7 +10,6 @@ describe("nf --help", () => {
expect(stdout).toContain("Usage: nf [options]");
expect(stdout).toContain("build");
expect(stdout).toContain("dev");
- expect(stdout).toContain("generate");
expect(stdout).toContain("install|add");
expect(stdout).toContain("new");
expect(stdout).toContain("start");
@@ -57,7 +56,6 @@ describe("nf build --help", () => {
expect(exitCode).toBe(0);
expect(stdout).toContain("build your game");
expect(stdout).toContain("--directory");
- expect(stdout).toContain("--config");
expect(stdout).toContain("--client-entry");
expect(stdout).toContain("--server-entry");
expect(stdout).toContain("--client-static-dir");
@@ -69,18 +67,6 @@ describe("nf build --help", () => {
});
});
-describe("nf generate --help", () => {
- it("should display generate command options", async () => {
- const { stdout, exitCode } = await runCli(["generate", "--help"]);
-
- expect(exitCode).toBe(0);
- expect(stdout).toContain("generate nanoforge files from config");
- expect(stdout).toContain("--directory");
- expect(stdout).toContain("--config");
- expect(stdout).toContain("--watch");
- });
-});
-
describe("nf install --help", () => {
it("should display install command options", async () => {
const { stdout, exitCode } = await runCli(["install", "--help"]);
@@ -100,7 +86,6 @@ describe("nf start --help", () => {
expect(exitCode).toBe(0);
expect(stdout).toContain("start your game");
expect(stdout).toContain("--directory");
- expect(stdout).toContain("--config");
expect(stdout).toContain("--port");
expect(stdout).toContain("--watch");
});
@@ -113,7 +98,6 @@ describe("nf dev --help", () => {
expect(exitCode).toBe(0);
expect(stdout).toContain("run your game in dev mode");
expect(stdout).toContain("--directory");
- expect(stdout).toContain("--generate");
expect(stdout).toContain("--editor");
});
});
diff --git a/e2e/cli-install.test.ts b/e2e/cli-install.test.ts
index 3106204..9bcdeed 100644
--- a/e2e/cli-install.test.ts
+++ b/e2e/cli-install.test.ts
@@ -1,4 +1,4 @@
-import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
+import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
@@ -18,26 +18,14 @@ describe("nf install (with existing project)", () => {
const projectDir = resolve(tmpDir, "install-project");
const appDir = resolve(projectDir, "install-app");
- beforeAll(async () => {
- mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "install-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--no-init-functions",
- "--no-skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
+ beforeAll(() => {
+ // `install -l` only needs a package.json: a generated project cannot be used until the
+ // engine v2 packages it depends on are published on npm.
+ mkdirSync(appDir, { recursive: true });
+ writeFileSync(
+ resolve(appDir, "package.json"),
+ JSON.stringify({ name: "install-app", private: true }, null, 2),
+ );
});
it("should run the install command with a library name", async () => {
@@ -124,8 +112,7 @@ describe("nf install (without library name)", () => {
"npm",
"--strict",
"--no-server",
- "--no-init-functions",
- "--no-skip-install",
+ "--skip-install",
"--no-docker",
"--no-git",
"-d",
diff --git a/e2e/cli-new-config.test.ts b/e2e/cli-new-config.test.ts
index 80ef8eb..72f61e4 100644
--- a/e2e/cli-new-config.test.ts
+++ b/e2e/cli-new-config.test.ts
@@ -6,6 +6,24 @@ import { runCli } from "./helpers/run-cli";
const tmpDir = resolve(__dirname, "../.tmp-e2e-new-config");
+const newProject = (name: string, directory: string, extraArgs: string[]) =>
+ runCli([
+ "new",
+ "--name",
+ name,
+ "--language",
+ "ts",
+ "--package-manager",
+ "npm",
+ "--strict",
+ "--skip-install",
+ "--no-docker",
+ "--no-git",
+ ...extraArgs,
+ "-d",
+ directory,
+ ]);
+
beforeAll(() => {
mkdirSync(tmpDir, { recursive: true });
});
@@ -16,79 +34,46 @@ afterAll(() => {
describe("nf new config output (no server)", () => {
const projectDir = resolve(tmpDir, "config-no-server");
- let config: any;
+ let config: string;
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "config-test",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
-
- config = JSON.parse(
- readFileSync(resolve(projectDir, "config-test/nanoforge.config.json"), "utf-8"),
- );
+ await newProject("config-test", projectDir, ["--no-server"]);
+ config = readFileSync(resolve(projectDir, "config-test/nanoforge.config.ts"), "utf-8");
});
- it("should have client config", () => {
- expect(config.client.enable).toBe(true);
+ it("should generate a client project config", () => {
+ expect(config).toContain('type: "client"');
});
- it("should not have server enabled", () => {
- expect(config.server?.enable).not.toBe(true);
+ it("should use defineConfig from nanoforge", () => {
+ expect(config).toContain('import { defineConfig } from "nanoforge/config";');
});
});
describe("nf new config output (with server)", () => {
const projectDir = resolve(tmpDir, "config-with-server");
- let config: any;
+ const workspaceDir = resolve(projectDir, "server-test");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
+ await newProject("server-test", projectDir, ["--server"]);
+ });
- await runCli([
- "new",
- "--name",
- "server-test",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--no-strict",
- "--server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--no-git",
- "-d",
- projectDir,
- ]);
-
- config = JSON.parse(
- readFileSync(resolve(projectDir, "server-test/nanoforge.config.json"), "utf-8"),
- );
+ it("should generate a workspace config including the apps", () => {
+ const config = readFileSync(resolve(workspaceDir, "nanoforge.config.ts"), "utf-8");
+ expect(config).toContain('type: "workspace"');
+ expect(config).toContain('packages: ["apps/*"]');
});
- it("should have server enabled", () => {
- expect(config.server.enable).toBe(true);
+ it("should generate a client project config", () => {
+ const config = readFileSync(resolve(workspaceDir, "apps/client/nanoforge.config.ts"), "utf-8");
+ expect(config).toContain('type: "client"');
});
- it("should have client build config", () => {
- expect(config.client.enable).toBe(true);
+ it("should generate a server project config", () => {
+ const config = readFileSync(resolve(workspaceDir, "apps/server/nanoforge.config.ts"), "utf-8");
+ expect(config).toContain('type: "server"');
});
});
@@ -98,26 +83,7 @@ describe("nf new package.json output", () => {
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
-
- await runCli([
- "new",
- "--name",
- "pkg-app",
- "--language",
- "ts",
- "--package-manager",
- "npm",
- "--strict",
- "--no-server",
- "--no-init-functions",
- "--skip-install",
- "--no-docker",
- "--editor",
- "--no-git",
- "-d",
- projectDir,
- ]);
-
+ await newProject("pkg-app", projectDir, ["--no-server", "--editor"]);
pkg = JSON.parse(readFileSync(resolve(projectDir, "pkg-app/package.json"), "utf-8"));
});
@@ -127,7 +93,11 @@ describe("nf new package.json output", () => {
it("should have nanoforge dependencies", () => {
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
- expect(allDeps).toHaveProperty("@nanoforge-dev/core");
- expect(allDeps).toHaveProperty("@nanoforge-dev/core-editor");
+ expect(allDeps).toHaveProperty("nanoforge");
+ expect(allDeps).toHaveProperty("@nanoforge-dev/ecs");
+ expect(allDeps).toHaveProperty("@nanoforge-dev/graphics-2d");
});
+
+ // TODO: `--editor` does not add any editor dependency to the generated project yet.
+ it.todo("should have editor dependencies with --editor");
});
diff --git a/e2e/cli-new.test.ts b/e2e/cli-new.test.ts
index 931e601..2eacf09 100644
--- a/e2e/cli-new.test.ts
+++ b/e2e/cli-new.test.ts
@@ -16,6 +16,7 @@ afterAll(() => {
describe("nf new (TypeScript, no server)", () => {
const projectDir = resolve(tmpDir, "ts-no-server");
+ const appDir = resolve(projectDir, "ts-app");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
@@ -32,8 +33,7 @@ describe("nf new (TypeScript, no server)", () => {
"npm",
"--strict",
"--no-server",
- "--no-init-functions",
- "--no-skip-install",
+ "--skip-install",
"--no-docker",
"--no-git",
"-d",
@@ -46,47 +46,74 @@ describe("nf new (TypeScript, no server)", () => {
});
it("should create the project directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app"))).toBe(true);
+ expect(existsSync(appDir)).toBe(true);
});
- it("should generate nanoforge.config.json", () => {
- const configPath = resolve(projectDir, "ts-app/nanoforge.config.json");
+ it("should generate a client nanoforge.config.ts", () => {
+ const configPath = resolve(appDir, "nanoforge.config.ts");
expect(existsSync(configPath)).toBe(true);
-
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
- expect(config.client).toBeDefined();
- expect(config.client.enable).toBe(true);
+ expect(readFileSync(configPath, "utf-8")).toContain('type: "client"');
});
it("should generate package.json", () => {
- expect(existsSync(resolve(projectDir, "ts-app/package.json"))).toBe(true);
+ const pkg = JSON.parse(readFileSync(resolve(appDir, "package.json"), "utf-8"));
+ expect(pkg.name).toBe("ts-app");
});
it("should generate tsconfig.json for TypeScript", () => {
- expect(existsSync(resolve(projectDir, "ts-app/tsconfig.json"))).toBe(true);
+ expect(existsSync(resolve(appDir, "tsconfig.json"))).toBe(true);
});
- it("should generate client directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app/client"))).toBe(true);
+ it("should generate the client entry file", () => {
+ expect(existsSync(resolve(appDir, "src/main.ts"))).toBe(true);
});
- it("should generate client main file", () => {
- expect(existsSync(resolve(projectDir, "ts-app/client/main.ts"))).toBe(true);
+ it("should generate components and systems", () => {
+ expect(existsSync(resolve(appDir, "src/components"))).toBe(true);
+ expect(existsSync(resolve(appDir, "src/systems"))).toBe(true);
});
- it("should not generate server directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app/server"))).toBe(false);
+ it("should not generate a workspace", () => {
+ expect(existsSync(resolve(appDir, "apps"))).toBe(false);
});
- it("should not have server enabled in config", () => {
- const configPath = resolve(projectDir, "ts-app/nanoforge.config.json");
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
- expect(config.server?.enable).not.toBe(true);
+ it("should not generate docker files", () => {
+ expect(existsSync(resolve(appDir, "Dockerfile"))).toBe(false);
+ });
+});
+
+describe("nf new (with dependencies installation)", () => {
+ // TODO: enable once the engine v2 packages (`@nanoforge-dev/ecs`, `@nanoforge-dev/network`
+ // and `nanoforge`) are published on npm, installing a generated project fails until then.
+ it.skip("should install the project dependencies", async () => {
+ const projectDir = resolve(tmpDir, "with-install");
+ mkdirSync(projectDir, { recursive: true });
+
+ const { exitCode } = await runCli([
+ "new",
+ "--name",
+ "install-app",
+ "--language",
+ "ts",
+ "--package-manager",
+ "npm",
+ "--strict",
+ "--no-server",
+ "--no-skip-install",
+ "--no-docker",
+ "--no-git",
+ "-d",
+ projectDir,
+ ]);
+
+ expect(exitCode).toBe(0);
+ expect(existsSync(resolve(projectDir, "install-app/node_modules"))).toBe(true);
});
});
describe("nf new (JavaScript, with server)", () => {
const projectDir = resolve(tmpDir, "js-with-server");
+ const workspaceDir = resolve(projectDir, "js-game");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
@@ -103,8 +130,7 @@ describe("nf new (JavaScript, with server)", () => {
"pnpm",
"--no-strict",
"--server",
- "--init-functions",
- "--no-skip-install",
+ "--skip-install",
"--no-docker",
"--no-git",
"-d",
@@ -115,42 +141,33 @@ describe("nf new (JavaScript, with server)", () => {
expect(stdout).toContain("Project successfully created");
});
- it("should generate server directory", () => {
- expect(existsSync(resolve(projectDir, "js-game/server"))).toBe(true);
+ it("should generate a workspace nanoforge.config.js", () => {
+ const content = readFileSync(resolve(workspaceDir, "nanoforge.config.js"), "utf-8");
+ expect(content).toContain('type: "workspace"');
+ expect(content).toContain('packages: ["apps/*"]');
});
- it("should generate server main file as .js", () => {
- expect(existsSync(resolve(projectDir, "js-game/server/main.js"))).toBe(true);
+ it("should generate a pnpm workspace", () => {
+ expect(existsSync(resolve(workspaceDir, "pnpm-workspace.yaml"))).toBe(true);
});
- it("should generate client main file as .js", () => {
- expect(existsSync(resolve(projectDir, "js-game/client/main.js"))).toBe(true);
+ it("should generate the client project", () => {
+ const content = readFileSync(resolve(workspaceDir, "apps/client/nanoforge.config.js"), "utf-8");
+ expect(content).toContain('type: "client"');
+ expect(existsSync(resolve(workspaceDir, "apps/client/src/main.js"))).toBe(true);
});
- it("should have server enabled in config", () => {
- const configPath = resolve(projectDir, "js-game/nanoforge.config.json");
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
- expect(config.server.enable).toBe(true);
- });
-
- it("should generate init function files for client", () => {
- expect(existsSync(resolve(projectDir, "js-game/client/init/before-init.js"))).toBe(true);
- expect(existsSync(resolve(projectDir, "js-game/client/init/after-init.js"))).toBe(true);
- });
-
- it("should generate init function files for server", () => {
- expect(existsSync(resolve(projectDir, "js-game/server/init/before-init.js"))).toBe(true);
- expect(existsSync(resolve(projectDir, "js-game/server/init/after-init.js"))).toBe(true);
+ it("should generate the server project", () => {
+ const content = readFileSync(resolve(workspaceDir, "apps/server/nanoforge.config.js"), "utf-8");
+ expect(content).toContain('type: "server"');
+ expect(existsSync(resolve(workspaceDir, "apps/server/src/main.js"))).toBe(true);
});
it("should generate jsconfig.json instead of tsconfig.json", () => {
- expect(existsSync(resolve(projectDir, "js-game/jsconfig.json"))).toBe(true);
- expect(existsSync(resolve(projectDir, "js-game/tsconfig.json"))).toBe(false);
- });
-
- it("should generate save files in .nanoforge", () => {
- expect(existsSync(resolve(projectDir, "js-game/.nanoforge/client.save.json"))).toBe(true);
- expect(existsSync(resolve(projectDir, "js-game/.nanoforge/server.save.json"))).toBe(true);
+ expect(existsSync(resolve(workspaceDir, "jsconfig.json"))).toBe(true);
+ expect(existsSync(resolve(workspaceDir, "tsconfig.json"))).toBe(false);
+ expect(existsSync(resolve(workspaceDir, "apps/client/jsconfig.json"))).toBe(true);
+ expect(existsSync(resolve(workspaceDir, "apps/server/jsconfig.json"))).toBe(true);
});
});
@@ -174,7 +191,6 @@ describe("nf new (with --path option)", () => {
"npm",
"--strict",
"--no-server",
- "--no-init-functions",
"--skip-install",
"--no-docker",
"--no-git",
@@ -187,12 +203,14 @@ describe("nf new (with --path option)", () => {
});
it("should create the project in the custom path", () => {
- expect(existsSync(resolve(projectDir, "custom/subdir"))).toBe(true);
+ expect(existsSync(resolve(projectDir, "custom/subdir/nanoforge.config.ts"))).toBe(true);
+ expect(existsSync(resolve(projectDir, "path-app"))).toBe(false);
});
});
describe("nf new (with typescript with docker option)", () => {
const projectDir = resolve(tmpDir, "ts-with-docker");
+ const appDir = resolve(projectDir, "ts-app");
beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });
@@ -209,8 +227,7 @@ describe("nf new (with typescript with docker option)", () => {
"npm",
"--strict",
"--no-server",
- "--no-init-functions",
- "--no-skip-install",
+ "--skip-install",
"--docker",
"--no-git",
"-d",
@@ -222,50 +239,16 @@ describe("nf new (with typescript with docker option)", () => {
expect(stdout).toContain("Project successfully created");
});
- it("should create the project directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app"))).toBe(true);
- });
-
- it("should generate nanoforge.config.json", () => {
- const configPath = resolve(projectDir, "ts-app/nanoforge.config.json");
- expect(existsSync(configPath)).toBe(true);
-
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
- expect(config.client).toBeDefined();
- expect(config.client.enable).toBe(true);
- });
-
- it("should generate package.json", () => {
- expect(existsSync(resolve(projectDir, "ts-app/package.json"))).toBe(true);
- });
-
- it("should generate tsconfig.json for TypeScript", () => {
- expect(existsSync(resolve(projectDir, "ts-app/tsconfig.json"))).toBe(true);
- });
-
- it("should generate client directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app/client"))).toBe(true);
- });
-
- it("should generate client main file", () => {
- expect(existsSync(resolve(projectDir, "ts-app/client/main.ts"))).toBe(true);
- });
-
- it("should not generate server directory", () => {
- expect(existsSync(resolve(projectDir, "ts-app/server"))).toBe(false);
- });
-
- it("should not have server enabled in config", () => {
- const configPath = resolve(projectDir, "ts-app/nanoforge.config.json");
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
- expect(config.server?.enable).not.toBe(true);
+ it("should generate the project", () => {
+ expect(existsSync(resolve(appDir, "nanoforge.config.ts"))).toBe(true);
+ expect(existsSync(resolve(appDir, "src/main.ts"))).toBe(true);
});
it("should generate Dockerfile", () => {
- expect(existsSync(resolve(projectDir, "ts-app/Dockerfile"))).toBe(true);
+ expect(existsSync(resolve(appDir, "Dockerfile"))).toBe(true);
});
it("should generate .dockerignore", () => {
- expect(existsSync(resolve(projectDir, "ts-app/.dockerignore"))).toBe(true);
+ expect(existsSync(resolve(appDir, ".dockerignore"))).toBe(true);
});
});
diff --git a/e2e/helpers/project-fixtures.ts b/e2e/helpers/project-fixtures.ts
new file mode 100644
index 0000000..d44656d
--- /dev/null
+++ b/e2e/helpers/project-fixtures.ts
@@ -0,0 +1,36 @@
+import { existsSync, readFileSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+
+const CONFIG_IMPORT = 'import { defineConfig } from "nanoforge/config";\n';
+const DEFINE_CONFIG_CALL = /defineConfig\(([\s\S]*)\);/;
+
+/**
+ * Configs generated by `nf new` import `defineConfig` from `nanoforge/config`, which is not
+ * published yet, so the CLI cannot load them in a project scaffolded with `--skip-install`.
+ * `defineConfig` is an identity function: stripping the import and the call keeps the exact
+ * generated config while making it loadable without dependencies.
+ *
+ * TODO: remove once `nanoforge` is published with its `./config` export.
+ */
+export const stripDefineConfig = (projectDir: string): void => {
+ const path = ["nanoforge.config.ts", "nanoforge.config.js"]
+ .map((file) => join(projectDir, file))
+ .find((file) => existsSync(file));
+ if (!path) throw new Error(`No nanoforge config found in '${projectDir}'`);
+
+ const content = readFileSync(path, "utf-8");
+ if (!content.includes(CONFIG_IMPORT) || !DEFINE_CONFIG_CALL.test(content))
+ throw new Error(`Unexpected config format in '${path}', update stripDefineConfig`);
+
+ writeFileSync(path, content.replace(CONFIG_IMPORT, "").replace(DEFINE_CONFIG_CALL, "$1;"));
+};
+
+/**
+ * Engine packages imported by the generated entry file are not published yet, so builds run
+ * against an entry file without any dependency.
+ *
+ * TODO: remove once the engine v2 packages are published.
+ */
+export const writeStandaloneEntry = (projectDir: string, entry = "src/main.ts"): void => {
+ writeFileSync(join(projectDir, entry), 'console.log("nanoforge");\n');
+};
diff --git a/eslint.config.js b/eslint.config.js
index b0da256..0f0f63c 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,3 +1,8 @@
import eslintConfig from "@nanoforge-dev/utils-eslint-config";
-export default eslintConfig;
+export default [
+ ...eslintConfig,
+ {
+ ignores: ["libs/**"],
+ },
+];
diff --git a/libs/config/.cliff-jumperrc.json b/libs/config/.cliff-jumperrc.json
new file mode 100644
index 0000000..ae950d4
--- /dev/null
+++ b/libs/config/.cliff-jumperrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json",
+ "name": "config",
+ "org": "nanoforge-dev",
+ "packagePath": "libs/config",
+ "tagTemplate": "{{new-version}}",
+ "identifierBase": false
+}
diff --git a/libs/config/.gitignore b/libs/config/.gitignore
new file mode 100644
index 0000000..88ef62e
--- /dev/null
+++ b/libs/config/.gitignore
@@ -0,0 +1,234 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# Turbo
+.turbo/
+
+# Compiled files
+src/**/*.js
+src/**/*.d.ts
+
+# mint-tsdocs cache
+docs/.tsdocs/
diff --git a/libs/config/.prettierignore b/libs/config/.prettierignore
new file mode 100644
index 0000000..64b127c
--- /dev/null
+++ b/libs/config/.prettierignore
@@ -0,0 +1,11 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+bun.lock
+
+.turbo/
+node_modules/
+dist/
+coverage/
+CHANGELOG.md
diff --git a/libs/config/CHANGELOG.md b/libs/config/CHANGELOG.md
new file mode 100644
index 0000000..6361e43
--- /dev/null
+++ b/libs/config/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
diff --git a/libs/config/LICENSE b/libs/config/LICENSE
new file mode 100644
index 0000000..4672486
--- /dev/null
+++ b/libs/config/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright © 2026 NanoForge
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libs/config/README.md b/libs/config/README.md
new file mode 100644
index 0000000..05cc5c3
--- /dev/null
+++ b/libs/config/README.md
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## About
+
+`@nanoforge-dev/config` is a library with the config types and helpers used to define and resolve `nanoforge.config.ts` files across Nanoforge CLI.
+
+## Installation
+
+**Node.js 26 or newer is required.**
+
+```sh
+npm install @nanoforge-dev/config
+yarn add @nanoforge-dev/config
+pnpm add @nanoforge-dev/config
+bun add @nanoforge-dev/config
+```
+
+## Usage
+
+Wrap a `nanoforge.config.ts` file's default export in `defineConfig` to get type-checking and editor autocompletion:
+
+```ts
+import { defineConfig } from "@nanoforge-dev/config";
+
+export default defineConfig({
+ type: "client",
+ entryFile: "src/main.ts",
+ libs: ["../../libs/my-lib"],
+});
+```
+
+The `type` field discriminates between the four config shapes:
+
+- `workspace` — a monorepo root listing its `packages`.
+- `lib` — a shared library consumed by clients/servers.
+- `client` — a browser-side NanoForge app.
+- `server` — a server-side NanoForge app.
+
+Each config type has partial, optional fields. `resolveConfig` (and its per-type variants `resolveWorkspaceConfig`, `resolveLibConfig`, `resolveClientConfig`, `resolveServerConfig`) fill in missing fields with defaults, deep-merging nested objects like `dir` and `out`:
+
+```ts
+import { resolveConfig } from "@nanoforge-dev/config";
+
+const resolved = resolveConfig({ type: "client", dir: { assets: "static" } });
+// resolved.dir.assets === "static"
+// resolved.dir.packages === "nf_modules" (default, not overridden)
+```
+
+Array fields (`packages`, `libs`) are replaced wholesale by an override rather than concatenated with the defaults.
+
+### Loading a config file
+
+`parseConfig` loads a `nanoforge.config.ts`/`.js` file from a given path, validates its default export, and resolves it against the defaults in one step:
+
+```ts
+import { parseConfig } from "@nanoforge-dev/config";
+
+const config = await parseConfig("./nanoforge.config.ts");
+```
+
+If the file is missing, fails to load, has no default export, or its default export isn't a recognizable `NanoforgeConfig`, `parseConfig` rejects with a `ConfigParseError` whose `code` (`"not-found" | "load-failed" | "no-default-export" | "invalid-type"`) identifies the failure:
+
+```ts
+import { ConfigParseError, parseConfig } from "@nanoforge-dev/config";
+
+try {
+ await parseConfig("./nanoforge.config.ts");
+} catch (error) {
+ if (error instanceof ConfigParseError && error.code === "not-found") {
+ // ...
+ }
+}
+```
+
+## Links
+
+- [GitHub][source]
+- [npm][npm]
+
+## Contributing
+
+Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
+[documentation][documentation].
+See [the contribution guide][contributing] if you'd like to submit a PR.
+
+## Help
+
+If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please don't hesitate to ask questions in [discussions][discussions].
+
+[documentation]: https://github.com/NanoForge-dev/CLI
+[discussions]: https://github.com/NanoForge-dev/CLI/discussions
+[source]: https://github.com/NanoForge-dev/CLI/tree/main/libs/config
+[npm]: https://www.npmjs.com/package/@nanoforge-dev/config
+[contributing]: https://github.com/NanoForge-dev/CLI/blob/main/.github/CONTRIBUTING.md
diff --git a/libs/config/cliff.toml b/libs/config/cliff.toml
new file mode 100644
index 0000000..4bb6a65
--- /dev/null
+++ b/libs/config/cliff.toml
@@ -0,0 +1,79 @@
+[changelog]
+header = """
+# Changelog
+
+All notable changes to this project will be documented in this file.\n
+"""
+body = """
+{%- macro remote_url() -%}
+ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
+{%- endmacro -%}
+{% if version %}\
+ # [{{ version | trim_start_matches(pat="v") }}]\
+ {% if previous %}\
+ {% if previous.version %}\
+ ({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
+ {% else %}\
+ ({{ self::remote_url() }}/tree/{{ version }})\
+ {% endif %}\
+ {% endif %} \
+ - ({{ timestamp | date(format="%Y-%m-%d") }})
+{% else %}\
+ # [unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+ ## {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.scope %}\
+ **{{commit.scope}}:** \
+ {% endif %}\
+ {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
+ {% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
+ {% if commit.breaking %}\
+ {% for footer in commit.footers %}\
+ {% if footer.breaking %}\
+ \n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
+ {% endif %}\
+ {% endfor %}\
+ {% endif %}\
+ {% endfor %}
+{% endfor %}\
+{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\
+ \n### New Contributors\n
+ {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\
+ - @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
+ {% endfor %}\
+{% endif %}\n
+"""
+trim = true
+footer = ""
+
+[git]
+conventional_commits = true
+filter_unconventional = true
+commit_parsers = [
+ { message = "^feat", group = "Features"},
+ { message = "^fix", group = "Bug Fixes"},
+ { message = "^docs", group = "Documentation"},
+ { message = "^perf", group = "Performance"},
+ { message = "^refactor", group = "Refactor"},
+ { message = "^types", group = "Typings"},
+ { message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
+ { message = "^revert", skip = true},
+ { message = "^style", group = "Styling"},
+ { message = "^test", group = "Testing"},
+ { message = "^chore", skip = true},
+ { message = "^ci", skip = true},
+ { message = "^build", skip = true},
+ { body = ".*security", group = "Security"},
+]
+filter_commits = true
+protect_breaking_commits = true
+tag_pattern = "^[0-9]+"
+ignore_tags = ""
+topo_order = false
+sort_commits = "newest"
+
+[remote.github]
+owner = "NanoForge-dev"
+repo = "CLI"
diff --git a/libs/config/eslint.config.js b/libs/config/eslint.config.js
new file mode 100644
index 0000000..62ec06d
--- /dev/null
+++ b/libs/config/eslint.config.js
@@ -0,0 +1,3 @@
+import config from "@nanoforge-dev/utils-eslint-config";
+
+export default config;
diff --git a/libs/config/package.json b/libs/config/package.json
new file mode 100644
index 0000000..66a0346
--- /dev/null
+++ b/libs/config/package.json
@@ -0,0 +1,90 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@nanoforge-dev/config",
+ "version": "1.4.2",
+ "description": "NanoForge CLI - Config",
+ "keywords": [
+ "nanoforge",
+ "game",
+ "engine"
+ ],
+ "homepage": "https://github.com/NanoForge-dev/CLI#readme",
+ "bugs": "https://github.com/NanoForge-dev/CLI/issues",
+ "license": "MIT",
+ "contributors": [
+ "Exelo ",
+ "Fexkoser ",
+ "Harper ",
+ "Josephine ",
+ "Leo "
+ ],
+ "files": [
+ "dist"
+ ],
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.cts",
+ "exports": {
+ ".": {
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ },
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "./package.json": "./package.json"
+ },
+ "type": "module",
+ "directories": {
+ "lib": "src"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/NanoForge-dev/CLI.git",
+ "directory": "libs/config"
+ },
+ "funding": "https://github.com/NanoForge-dev/CLI?sponsor",
+ "scripts": {
+ "build": "tsc --noEmit && tsdown --config-loader unrun",
+ "lint": "prettier --check . && eslint --format=pretty src",
+ "format": "prettier --write . && eslint --fix --format=pretty src",
+ "test:unit": "vitest run --config vitest.config.ts",
+ "prepack": "pnpm run build && pnpm run lint",
+ "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'libs/config/*'",
+ "release": "cliff-jumper",
+ "docs": "mint-tsdocs generate"
+ },
+ "dependencies": {
+ "unrun": "catalog:libs",
+ "zod": "catalog:libs"
+ },
+ "devDependencies": {
+ "@favware/cliff-jumper": "catalog:ci",
+ "@nanoforge-dev/utils-eslint-config": "catalog:lint",
+ "@nanoforge-dev/utils-prettier-config": "catalog:lint",
+ "@trivago/prettier-plugin-sort-imports": "catalog:lint",
+ "eslint": "catalog:lint",
+ "prettier": "catalog:lint",
+ "tsdown": "catalog:build",
+ "typescript": "catalog:build",
+ "vitest": "catalog:test"
+ },
+ "packageManager": "pnpm@12.0.0",
+ "engines": {
+ "node": "26"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "lint-staged": {
+ "**": [
+ "prettier --ignore-unknown --write"
+ ],
+ "src/**/*.ts": [
+ "eslint --fix"
+ ]
+ }
+}
diff --git a/libs/config/prettier.config.js b/libs/config/prettier.config.js
new file mode 100644
index 0000000..27d0e26
--- /dev/null
+++ b/libs/config/prettier.config.js
@@ -0,0 +1,3 @@
+import config from "@nanoforge-dev/utils-prettier-config";
+
+export default config;
diff --git a/libs/config/src/defaults/default-configs.ts b/libs/config/src/defaults/default-configs.ts
new file mode 100644
index 0000000..2b06faf
--- /dev/null
+++ b/libs/config/src/defaults/default-configs.ts
@@ -0,0 +1,72 @@
+import type { ClientConfig, LibConfig, ServerConfig, WorkspaceConfig } from "../types";
+import type { DeepRequired } from "../types/utils.type";
+
+export const defaultWorkspaceConfig: DeepRequired> = {
+ packages: [],
+};
+
+export const defaultLibConfig: DeepRequired> = {
+ dir: {
+ assets: "assets",
+ shared: "shared",
+
+ components: "shared/components",
+ systems: "shared/systems",
+ scenes: "shared/scenes",
+ },
+};
+
+export const defaultClientConfig: DeepRequired> = {
+ entryFile: "src/main.ts",
+ out: {
+ dir: "dist",
+ mainFile: "main.js",
+ },
+
+ dir: {
+ assets: "assets",
+ packages: "nf_modules",
+
+ components: "src/components",
+ systems: "src/systems",
+ scenes: "src/scenes",
+ },
+
+ editor: {
+ entryFile: "src/main.ts",
+ },
+
+ language: "ts",
+
+ port: "3000",
+ tls: {
+ enable: false,
+ },
+
+ libs: [],
+};
+
+export const defaultServerConfig: DeepRequired> = {
+ entryFile: "src/main.ts",
+ out: {
+ dir: "dist",
+ mainFile: "main.js",
+ },
+
+ dir: {
+ assets: "assets",
+ packages: "nf_modules",
+
+ components: "src/components",
+ systems: "src/systems",
+ scenes: "src/scenes",
+ },
+
+ editor: {
+ entryFile: "src/main.ts",
+ },
+
+ language: "ts",
+
+ libs: [],
+};
diff --git a/libs/config/src/define-config.ts b/libs/config/src/define-config.ts
new file mode 100644
index 0000000..788e025
--- /dev/null
+++ b/libs/config/src/define-config.ts
@@ -0,0 +1,7 @@
+import type { NanoforgeConfig } from "./types";
+
+/**
+ * Identity function used to get type-checking and editor autocompletion on
+ * a `nanoforge.config.ts` file's default export.
+ */
+export const defineConfig = (config: T): T => config;
diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts
new file mode 100644
index 0000000..efce8b3
--- /dev/null
+++ b/libs/config/src/index.ts
@@ -0,0 +1,31 @@
+export {
+ defaultClientConfig,
+ defaultLibConfig,
+ defaultServerConfig,
+ defaultWorkspaceConfig,
+} from "./defaults/default-configs";
+export { defineConfig } from "./define-config";
+export { ConfigParseError } from "./parse/config-error";
+export type { ConfigParseErrorCode } from "./parse/config-error";
+export { parseConfig } from "./parse/parse-config";
+export {
+ resolveClientConfig,
+ resolveConfig,
+ resolveLibConfig,
+ resolveServerConfig,
+ resolveWorkspaceConfig,
+} from "./merge/resolve-config";
+export type {
+ BaseConfig,
+ BuildableConfig,
+ ClientConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig,
+ LibConfig,
+ NanoforgeConfig,
+ ServerConfig,
+ SourceableConfig,
+ TlsConfig,
+ WorkspaceConfig,
+} from "./types";
diff --git a/libs/config/src/merge/deep-merge.ts b/libs/config/src/merge/deep-merge.ts
new file mode 100644
index 0000000..0d8d912
--- /dev/null
+++ b/libs/config/src/merge/deep-merge.ts
@@ -0,0 +1,37 @@
+import type { DeepPartial } from "../types/utils.type";
+
+const isPlainObject = (value: unknown): value is Record =>
+ typeof value === "object" && value !== null && !Array.isArray(value);
+
+const mergeUnknown = (
+ defaults: Record,
+ override: Record,
+): Record => {
+ const result: Record = { ...defaults };
+
+ for (const key of Object.keys(override)) {
+ const overrideValue = override[key];
+ if (overrideValue === undefined) continue;
+
+ const defaultValue = defaults[key];
+ result[key] =
+ isPlainObject(defaultValue) && isPlainObject(overrideValue)
+ ? mergeUnknown(defaultValue, overrideValue)
+ : overrideValue;
+ }
+
+ return result;
+};
+
+/**
+ * Recursively merges `override` onto `defaults`.
+ *
+ * @remarks
+ * Nested plain objects (e.g. `dir`, `out`) are merged key by key. Array
+ * values (e.g. `packages`, `libs`) are replaced wholesale by `override`,
+ * not concatenated.
+ */
+export const deepMerge = >(
+ defaults: T,
+ override: DeepPartial,
+): T => mergeUnknown(defaults, override as Record) as T;
diff --git a/libs/config/src/merge/resolve-config.ts b/libs/config/src/merge/resolve-config.ts
new file mode 100644
index 0000000..6440b3b
--- /dev/null
+++ b/libs/config/src/merge/resolve-config.ts
@@ -0,0 +1,52 @@
+import {
+ defaultClientConfig,
+ defaultLibConfig,
+ defaultServerConfig,
+ defaultWorkspaceConfig,
+} from "../defaults/default-configs";
+import type {
+ ClientConfig,
+ LibConfig,
+ NanoforgeConfig,
+ ServerConfig,
+ WorkspaceConfig,
+} from "../types";
+import type { DeepRequired } from "../types/utils.type";
+import { deepMerge } from "./deep-merge";
+
+export const resolveWorkspaceConfig = (config: WorkspaceConfig): DeepRequired => ({
+ ...deepMerge(defaultWorkspaceConfig, config),
+ type: "workspace",
+});
+
+export const resolveLibConfig = (config: LibConfig): DeepRequired => ({
+ ...deepMerge(defaultLibConfig, config),
+ type: "lib",
+});
+
+export const resolveClientConfig = (config: ClientConfig): DeepRequired => ({
+ ...deepMerge(defaultClientConfig, config),
+ type: "client",
+});
+
+export const resolveServerConfig = (config: ServerConfig): DeepRequired => ({
+ ...deepMerge(defaultServerConfig, config),
+ type: "server",
+});
+
+/**
+ * Merges a `nanoforge.config.ts` config against the defaults matching its
+ * `type`.
+ */
+export const resolveConfig = (config: NanoforgeConfig): DeepRequired => {
+ switch (config.type) {
+ case "workspace":
+ return resolveWorkspaceConfig(config);
+ case "lib":
+ return resolveLibConfig(config);
+ case "client":
+ return resolveClientConfig(config);
+ case "server":
+ return resolveServerConfig(config);
+ }
+};
diff --git a/libs/config/src/parse/config-error.ts b/libs/config/src/parse/config-error.ts
new file mode 100644
index 0000000..54736ca
--- /dev/null
+++ b/libs/config/src/parse/config-error.ts
@@ -0,0 +1,14 @@
+export type ConfigParseErrorCode =
+ "not-found" | "load-failed" | "no-default-export" | "invalid-type";
+
+export class ConfigParseError extends Error {
+ constructor(
+ public readonly code: ConfigParseErrorCode,
+ message: string,
+ public readonly path: string,
+ options?: { cause?: unknown },
+ ) {
+ super(`${message}${options?.cause ? `\n${options?.cause}` : ""}`);
+ this.name = "ConfigParseError";
+ }
+}
diff --git a/libs/config/src/parse/load-config-module.ts b/libs/config/src/parse/load-config-module.ts
new file mode 100644
index 0000000..ae1264d
--- /dev/null
+++ b/libs/config/src/parse/load-config-module.ts
@@ -0,0 +1,38 @@
+import { access } from "node:fs/promises";
+
+import { ConfigParseError } from "./config-error";
+
+export const loadConfigModule = async (path: string | URL): Promise => {
+ const pathLabel = String(path);
+
+ try {
+ await access(path);
+ } catch (cause) {
+ throw new ConfigParseError("not-found", `Config file not found: ${pathLabel}`, pathLabel, {
+ cause,
+ });
+ }
+
+ let module: { default?: unknown };
+ try {
+ const { unrun } = await import("unrun");
+ ({ module } = await unrun<{ default?: unknown }>({ path, preset: "bundle-require" }));
+ } catch (cause) {
+ throw new ConfigParseError(
+ "load-failed",
+ `Failed to load config file: ${pathLabel}`,
+ pathLabel,
+ { cause },
+ );
+ }
+
+ if (module.default === undefined) {
+ throw new ConfigParseError(
+ "no-default-export",
+ `Config file has no default export: ${pathLabel}`,
+ pathLabel,
+ );
+ }
+
+ return module.default;
+};
diff --git a/libs/config/src/parse/parse-config.ts b/libs/config/src/parse/parse-config.ts
new file mode 100644
index 0000000..466490e
--- /dev/null
+++ b/libs/config/src/parse/parse-config.ts
@@ -0,0 +1,11 @@
+import { resolveConfig } from "../merge/resolve-config";
+import type { NanoforgeConfig } from "../types";
+import type { DeepRequired } from "../types/utils.type";
+import { loadConfigModule } from "./load-config-module";
+import { assertNanoforgeConfig } from "./validate-config";
+
+export const parseConfig = async (path: string | URL): Promise> => {
+ const raw = await loadConfigModule(path);
+ assertNanoforgeConfig(raw, String(path));
+ return resolveConfig(raw);
+};
diff --git a/libs/config/src/parse/validate-config.ts b/libs/config/src/parse/validate-config.ts
new file mode 100644
index 0000000..8a07e97
--- /dev/null
+++ b/libs/config/src/parse/validate-config.ts
@@ -0,0 +1,112 @@
+import { z } from "zod";
+
+import type { NanoforgeConfig } from "../types";
+import { ConfigParseError } from "./config-error";
+
+const buildableConfigShape = {
+ entryFile: z.string().optional(),
+ out: z
+ .object({
+ dir: z.string().optional(),
+ mainFile: z.string().optional(),
+ })
+ .optional(),
+};
+
+const containLibConfigShape = {
+ libs: z.array(z.string()).optional(),
+};
+
+const editorConfigShape = {
+ editor: z
+ .object({
+ entryFile: z.string().optional(),
+ })
+ .optional(),
+};
+
+const languageConfigShape = {
+ language: z.enum(["ts", "js"]).optional(),
+};
+
+const tlsConfigShape = {
+ tls: z
+ .union([
+ z.object({ enable: z.literal(false).optional() }),
+ z.object({ enable: z.literal(true), cert: z.string(), key: z.string() }),
+ ])
+ .optional(),
+};
+
+const sourceableConfigShape = {
+ dir: z
+ .object({
+ assets: z.string().optional(),
+ packages: z.string().optional(),
+ components: z.string().optional(),
+ systems: z.string().optional(),
+ scenes: z.string().optional(),
+ })
+ .optional(),
+};
+
+const workspaceConfigSchema = z.object({
+ type: z.literal("workspace"),
+ packages: z.array(z.string()).optional(),
+});
+
+const libConfigSchema = z.object({
+ type: z.literal("lib"),
+ dir: z
+ .object({
+ assets: z.string().optional(),
+ shared: z.string().optional(),
+ components: z.string().optional(),
+ systems: z.string().optional(),
+ scenes: z.string().optional(),
+ })
+ .optional(),
+});
+
+const clientConfigSchema = z.object({
+ type: z.literal("client"),
+ ...sourceableConfigShape,
+ ...buildableConfigShape,
+ ...containLibConfigShape,
+ ...editorConfigShape,
+ ...languageConfigShape,
+ ...tlsConfigShape,
+ port: z.string().optional(),
+});
+
+const serverConfigSchema = z.object({
+ type: z.literal("server"),
+ ...sourceableConfigShape,
+ ...buildableConfigShape,
+ ...containLibConfigShape,
+ ...editorConfigShape,
+ ...languageConfigShape,
+});
+
+const nanoforgeConfigSchema = z.discriminatedUnion("type", [
+ workspaceConfigSchema,
+ libConfigSchema,
+ clientConfigSchema,
+ serverConfigSchema,
+]);
+
+export function assertNanoforgeConfig(
+ value: unknown,
+ path: string,
+): asserts value is NanoforgeConfig {
+ const result = nanoforgeConfigSchema.safeParse(value);
+
+ if (!result.success) {
+ throw new ConfigParseError(
+ "invalid-type",
+ `Config file's default export is not a valid NanoforgeConfig: ${path}\n${z.prettifyError(result.error)}`,
+ path,
+ { cause: result.error },
+ );
+ }
+}
diff --git a/libs/config/src/types/base-config.type.ts b/libs/config/src/types/base-config.type.ts
new file mode 100644
index 0000000..2cbabf6
--- /dev/null
+++ b/libs/config/src/types/base-config.type.ts
@@ -0,0 +1,6 @@
+/**
+ * Common shape every `nanoforge.config.ts` file's default export satisfies.
+ */
+export interface BaseConfig {
+ type: T;
+}
diff --git a/libs/config/src/types/client-config.type.ts b/libs/config/src/types/client-config.type.ts
new file mode 100644
index 0000000..b5b1fca
--- /dev/null
+++ b/libs/config/src/types/client-config.type.ts
@@ -0,0 +1,22 @@
+import type { BaseConfig } from "./base-config.type";
+import type {
+ BuildableConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig,
+ SourceableConfig,
+ TlsConfig,
+} from "./mixins.type";
+
+export interface ClientConfig
+ extends
+ BaseConfig<"client">,
+ SourceableConfig,
+ BuildableConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig,
+ TlsConfig {
+ /** Port the client dev-server loader listens on. */
+ port?: string;
+}
diff --git a/libs/config/src/types/index.ts b/libs/config/src/types/index.ts
new file mode 100644
index 0000000..3a0523a
--- /dev/null
+++ b/libs/config/src/types/index.ts
@@ -0,0 +1,21 @@
+import type { ClientConfig } from "./client-config.type";
+import type { LibConfig } from "./lib-config.type";
+import type { ServerConfig } from "./server-config.type";
+import type { WorkspaceConfig } from "./workspace-config.type";
+
+export type { BaseConfig } from "./base-config.type";
+export type { ClientConfig } from "./client-config.type";
+export type { LibConfig } from "./lib-config.type";
+export type {
+ BuildableConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig,
+ SourceableConfig,
+ TlsConfig,
+} from "./mixins.type";
+export type { ServerConfig } from "./server-config.type";
+export type { WorkspaceConfig } from "./workspace-config.type";
+
+/** Discriminated union of every `nanoforge.config.ts` shape, keyed by `type`. */
+export type NanoforgeConfig = WorkspaceConfig | LibConfig | ClientConfig | ServerConfig;
diff --git a/libs/config/src/types/lib-config.type.ts b/libs/config/src/types/lib-config.type.ts
new file mode 100644
index 0000000..3b2c3cb
--- /dev/null
+++ b/libs/config/src/types/lib-config.type.ts
@@ -0,0 +1,15 @@
+import type { BaseConfig } from "./base-config.type";
+
+export interface LibConfig extends BaseConfig<"lib"> {
+ dir?: {
+ assets?: string;
+ shared?: string;
+
+ /** Used by the editor only. Should be inside `shared`. */
+ components?: string;
+ /** Used by the editor only. Should be inside `shared`. */
+ systems?: string;
+ /** Used by the editor only. Should be inside `shared`. */
+ scenes?: string;
+ };
+}
diff --git a/libs/config/src/types/mixins.type.ts b/libs/config/src/types/mixins.type.ts
new file mode 100644
index 0000000..50785f9
--- /dev/null
+++ b/libs/config/src/types/mixins.type.ts
@@ -0,0 +1,49 @@
+export interface ContainLibConfig {
+ libs?: string[];
+}
+
+export interface BuildableConfig {
+ entryFile?: string;
+ out?: {
+ dir?: string;
+ mainFile?: string;
+ };
+}
+
+export interface EditorConfig {
+ editor?: {
+ /** Entry point used to build/run this project in editor mode (`--editor`). */
+ entryFile?: string;
+ };
+}
+
+export interface LanguageConfig {
+ /** Source language, used to pick `.ts`/`.js` component and system schematic templates. */
+ language?: "ts" | "js";
+}
+
+export interface TlsConfig {
+ tls?:
+ | {
+ enable?: false;
+ }
+ | {
+ enable: true;
+ cert: string;
+ key: string;
+ };
+}
+
+export interface SourceableConfig {
+ dir?: {
+ assets?: string;
+ packages?: string;
+
+ /** Used by the editor only. */
+ components?: string;
+ /** Used by the editor only. */
+ systems?: string;
+ /** Used by the editor only. */
+ scenes?: string;
+ };
+}
diff --git a/libs/config/src/types/server-config.type.ts b/libs/config/src/types/server-config.type.ts
new file mode 100644
index 0000000..c7f3abc
--- /dev/null
+++ b/libs/config/src/types/server-config.type.ts
@@ -0,0 +1,17 @@
+import type { BaseConfig } from "./base-config.type";
+import type {
+ BuildableConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig,
+ SourceableConfig,
+} from "./mixins.type";
+
+export interface ServerConfig
+ extends
+ BaseConfig<"server">,
+ SourceableConfig,
+ BuildableConfig,
+ ContainLibConfig,
+ EditorConfig,
+ LanguageConfig {}
diff --git a/libs/config/src/types/utils.type.ts b/libs/config/src/types/utils.type.ts
new file mode 100644
index 0000000..bc71828
--- /dev/null
+++ b/libs/config/src/types/utils.type.ts
@@ -0,0 +1,3 @@
+export type DeepRequired = T extends object ? { [P in keyof T]-?: DeepRequired } : T;
+
+export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;
diff --git a/libs/config/src/types/workspace-config.type.ts b/libs/config/src/types/workspace-config.type.ts
new file mode 100644
index 0000000..8d45c7d
--- /dev/null
+++ b/libs/config/src/types/workspace-config.type.ts
@@ -0,0 +1,5 @@
+import type { BaseConfig } from "./base-config.type";
+
+export interface WorkspaceConfig extends BaseConfig<"workspace"> {
+ packages?: string[];
+}
diff --git a/libs/config/test/define-config.spec.ts b/libs/config/test/define-config.spec.ts
new file mode 100644
index 0000000..0bd4071
--- /dev/null
+++ b/libs/config/test/define-config.spec.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from "vitest";
+
+import { defineConfig } from "../src";
+import type { NanoforgeConfig } from "../src";
+
+describe("defineConfig", () => {
+ it("returns the given config unchanged", () => {
+ const config = defineConfig({ type: "client", libs: ["../libs/test-lib"] });
+ expect(config).toEqual({ type: "client", libs: ["../libs/test-lib"] });
+ });
+
+ it("narrows to the matching config shape via the type discriminant", () => {
+ const config: NanoforgeConfig = defineConfig({ type: "server" });
+ if (config.type === "server") {
+ expect(config.libs).toBeUndefined();
+ } else {
+ throw new Error("expected type to be 'server'");
+ }
+ });
+});
diff --git a/libs/config/test/parse-config.spec.ts b/libs/config/test/parse-config.spec.ts
new file mode 100644
index 0000000..2d98c72
--- /dev/null
+++ b/libs/config/test/parse-config.spec.ts
@@ -0,0 +1,49 @@
+import { describe, expect, it } from "vitest";
+
+import { ConfigParseError, parseConfig } from "../src";
+
+const fixture = (name: string) => new URL(`./parse/fixtures/${name}`, import.meta.url);
+
+describe("parseConfig", () => {
+ it("resolves a .ts config file into a fully-defaulted config", async () => {
+ const resolved = await parseConfig(fixture("valid-config.ts"));
+ expect(resolved.type).toBe("client");
+ expect(resolved).toMatchObject({
+ entryFile: "custom.ts",
+ out: { dir: "dist", mainFile: "main.js" },
+ libs: [],
+ });
+ });
+
+ it("resolves a .js config file the same way as .ts", async () => {
+ const resolved = await parseConfig(fixture("valid-config.js"));
+ expect(resolved).toEqual({ type: "workspace", packages: ["apps/*"] });
+ });
+
+ it("rejects with code 'not-found' when the file doesn't exist", async () => {
+ await expect(parseConfig(fixture("missing-config.ts"))).rejects.toMatchObject({
+ constructor: ConfigParseError,
+ code: "not-found",
+ });
+ });
+
+ it("rejects with code 'load-failed' when the file throws while loading", async () => {
+ const error = await parseConfig(fixture("throwing-config.ts")).catch((e) => e);
+ expect(error).toMatchObject({ constructor: ConfigParseError, code: "load-failed" });
+ expect(error.message).toContain("boom");
+ });
+
+ it("rejects with code 'no-default-export' when there's no default export", async () => {
+ await expect(parseConfig(fixture("no-default-export.ts"))).rejects.toMatchObject({
+ constructor: ConfigParseError,
+ code: "no-default-export",
+ });
+ });
+
+ it("rejects with code 'invalid-type' when the type is unrecognized", async () => {
+ await expect(parseConfig(fixture("invalid-type-config.ts"))).rejects.toMatchObject({
+ constructor: ConfigParseError,
+ code: "invalid-type",
+ });
+ });
+});
diff --git a/libs/config/test/parse/fixtures/invalid-type-config.ts b/libs/config/test/parse/fixtures/invalid-type-config.ts
new file mode 100644
index 0000000..850f8cd
--- /dev/null
+++ b/libs/config/test/parse/fixtures/invalid-type-config.ts
@@ -0,0 +1,3 @@
+export default {
+ type: "nope",
+};
diff --git a/libs/config/test/parse/fixtures/no-default-export.ts b/libs/config/test/parse/fixtures/no-default-export.ts
new file mode 100644
index 0000000..f85a7a9
--- /dev/null
+++ b/libs/config/test/parse/fixtures/no-default-export.ts
@@ -0,0 +1,3 @@
+export const notDefault = {
+ type: "client",
+};
diff --git a/libs/config/test/parse/fixtures/throwing-config.ts b/libs/config/test/parse/fixtures/throwing-config.ts
new file mode 100644
index 0000000..c3fc99d
--- /dev/null
+++ b/libs/config/test/parse/fixtures/throwing-config.ts
@@ -0,0 +1 @@
+throw new Error("boom");
diff --git a/libs/config/test/parse/fixtures/valid-config.js b/libs/config/test/parse/fixtures/valid-config.js
new file mode 100644
index 0000000..e9727de
--- /dev/null
+++ b/libs/config/test/parse/fixtures/valid-config.js
@@ -0,0 +1,4 @@
+export default {
+ type: "workspace",
+ packages: ["apps/*"],
+};
diff --git a/libs/config/test/parse/fixtures/valid-config.ts b/libs/config/test/parse/fixtures/valid-config.ts
new file mode 100644
index 0000000..ae4e5c1
--- /dev/null
+++ b/libs/config/test/parse/fixtures/valid-config.ts
@@ -0,0 +1,4 @@
+export default {
+ type: "client",
+ entryFile: "custom.ts",
+};
diff --git a/libs/config/test/resolve-config.spec.ts b/libs/config/test/resolve-config.spec.ts
new file mode 100644
index 0000000..5f60e27
--- /dev/null
+++ b/libs/config/test/resolve-config.spec.ts
@@ -0,0 +1,76 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ resolveClientConfig,
+ resolveConfig,
+ resolveLibConfig,
+ resolveServerConfig,
+ resolveWorkspaceConfig,
+} from "../src";
+
+describe("resolveWorkspaceConfig", () => {
+ it("fills in defaults for an empty config", () => {
+ expect(resolveWorkspaceConfig({ type: "workspace" })).toEqual({
+ type: "workspace",
+ packages: [],
+ });
+ });
+
+ it("lets an explicit override win", () => {
+ expect(resolveWorkspaceConfig({ type: "workspace", packages: ["apps/*"] })).toEqual({
+ type: "workspace",
+ packages: ["apps/*"],
+ });
+ });
+});
+
+describe("resolveLibConfig", () => {
+ it("fills in nested dir defaults", () => {
+ expect(resolveLibConfig({ type: "lib" }).dir).toEqual({
+ assets: "assets",
+ shared: "shared",
+ components: "shared/components",
+ systems: "shared/systems",
+ scenes: "shared/scenes",
+ });
+ });
+
+ it("merges a partial dir override with defaults", () => {
+ expect(resolveLibConfig({ type: "lib", dir: { assets: "static" } }).dir).toEqual({
+ assets: "static",
+ shared: "shared",
+ components: "shared/components",
+ systems: "shared/systems",
+ scenes: "shared/scenes",
+ });
+ });
+});
+
+describe("resolveClientConfig", () => {
+ it("fills in defaults for an empty config", () => {
+ const resolved = resolveClientConfig({ type: "client" });
+ expect(resolved.entryFile).toBe("src/main.ts");
+ expect(resolved.out).toEqual({ dir: "dist", mainFile: "main.js" });
+ expect(resolved.libs).toEqual([]);
+ });
+
+ it("replaces array fields wholesale instead of concatenating", () => {
+ const resolved = resolveClientConfig({ type: "client", libs: ["../../libs/test-lib"] });
+ expect(resolved.libs).toEqual(["../../libs/test-lib"]);
+ });
+});
+
+describe("resolveServerConfig", () => {
+ it("fills in defaults for an empty config", () => {
+ const resolved = resolveServerConfig({ type: "server" });
+ expect(resolved.entryFile).toBe("src/main.ts");
+ expect(resolved.dir?.assets).toBe("assets");
+ });
+});
+
+describe("resolveConfig", () => {
+ it("dispatches to the resolver matching the config's type", () => {
+ expect(resolveConfig({ type: "workspace" })).toEqual({ type: "workspace", packages: [] });
+ expect(resolveConfig({ type: "server" }).entryFile).toBe("src/main.ts");
+ });
+});
diff --git a/libs/config/tsconfig.json b/libs/config/tsconfig.json
new file mode 100644
index 0000000..1ada333
--- /dev/null
+++ b/libs/config/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist"],
+ "compilerOptions": {
+ "declaration": true,
+ "paths": {
+ "/snippets/*": ["./docs/snippets/*"]
+ }
+ },
+ "mdx": {
+ "checkMdx": true
+ }
+}
diff --git a/libs/config/tsconfig.spec.json b/libs/config/tsconfig.spec.json
new file mode 100644
index 0000000..8270cab
--- /dev/null
+++ b/libs/config/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true,
+ "skipLibCheck": true
+ },
+ "include": ["test/**/*.spec.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/libs/config/tsdown.config.ts b/libs/config/tsdown.config.ts
new file mode 100644
index 0000000..609a944
--- /dev/null
+++ b/libs/config/tsdown.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "tsdown";
+
+export default [
+ defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ format: ["esm", "cjs"],
+ shims: true,
+ dts: true,
+ sourcemap: true,
+ clean: true,
+ minify: true,
+ fixedExtension: false,
+ platform: "node",
+ target: "esnext",
+ treeshake: false,
+ deps: {
+ neverBundle: true,
+ },
+ outputOptions: {
+ assetFileNames: "[name][extname]",
+ },
+ }),
+];
diff --git a/libs/config/vitest.config.ts b/libs/config/vitest.config.ts
new file mode 100644
index 0000000..3ff3a4a
--- /dev/null
+++ b/libs/config/vitest.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["test/**/*.spec.ts"],
+ passWithNoTests: true,
+ coverage: {
+ provider: "v8",
+ include: ["src/**/*.ts"],
+ exclude: [
+ "**/*.{interface,type,d}.ts",
+ "**/{interfaces,types}/*.ts",
+ "**/{interfaces,types}.ts",
+ "**/index.{js,ts}",
+ ],
+ },
+ },
+});
diff --git a/libs/schematics/.cliff-jumperrc.json b/libs/schematics/.cliff-jumperrc.json
new file mode 100644
index 0000000..39ace0a
--- /dev/null
+++ b/libs/schematics/.cliff-jumperrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json",
+ "name": "schematics",
+ "org": "nanoforge-dev",
+ "packagePath": "libs/schematics",
+ "tagTemplate": "{{new-version}}",
+ "identifierBase": false
+}
diff --git a/libs/schematics/.gitignore b/libs/schematics/.gitignore
new file mode 100644
index 0000000..bca0f13
--- /dev/null
+++ b/libs/schematics/.gitignore
@@ -0,0 +1,226 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+.direnv
diff --git a/libs/schematics/.prettierignore b/libs/schematics/.prettierignore
new file mode 100644
index 0000000..91942fb
--- /dev/null
+++ b/libs/schematics/.prettierignore
@@ -0,0 +1,14 @@
+.idea/
+.vscode/
+
+dist/
+node_modules/
+
+pnpm-lock.yaml
+pnpm-workspace.yaml
+
+CHANGELOG.md
+
+coverage/
+
+/src/libs/*/files/**
diff --git a/libs/schematics/CHANGELOG.md b/libs/schematics/CHANGELOG.md
new file mode 100644
index 0000000..911dfd8
--- /dev/null
+++ b/libs/schematics/CHANGELOG.md
@@ -0,0 +1,245 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+# [2.2.0](https://github.com/NanoForge-dev/schematics/compare/2.1.4...2.2.0) - (2026-06-30)
+
+## Features
+
+- Add pnpm workspace in app schematic (#155) ([5e0924d](https://github.com/NanoForge-dev/schematics/commit/5e0924d217c6ca62e322627f4adee79470c7bf2f)) by @Exeloo
+- Add assets to entity generation (#153) ([a008fc6](https://github.com/NanoForge-dev/schematics/commit/a008fc6784cc4d15f3f09fc41efd9a0ea658c3a5)) by @Exeloo
+- Graphics 2d editor generation (#154) ([89059f7](https://github.com/NanoForge-dev/schematics/commit/89059f7fbb5a505f3669bc691acbf377abd51fa6)) by @Exeloo
+
+# [2.1.4](https://github.com/NanoForge-dev/schematics/compare/1.0.2...2.1.4) - (2026-06-29)
+
+## Bug Fixes
+
+- Issues (#139) ([131e794](https://github.com/NanoForge-dev/schematics/commit/131e794601b9fdcb583ac52eab9ca44a1cf8531f)) by @Exeloo
+- **synchronize-docs:** Use actions key to have the rights (#127) ([f73464d](https://github.com/NanoForge-dev/schematics/commit/f73464d09f08f14f2c82960fe9cef2dfde7c00dc)) by @MartinFillon
+- Give package json path (#124) ([a31e265](https://github.com/NanoForge-dev/schematics/commit/a31e26522078c9b60a63d3d91f1eccda96dadac7)) by @MartinFillon
+- **template:** New format (#113) ([1e89de7](https://github.com/NanoForge-dev/schematics/commit/1e89de7a536f75783c260cf1a0f0b05f99ffcf97)) by @Tchips46
+- Remove name formatting on app (#110) ([9478843](https://github.com/NanoForge-dev/schematics/commit/9478843b45a97714070babc6a7d41b8a9a6f2f65)) by @Exeloo
+- Change save format in part base schematic (#105) ([6eaffe8](https://github.com/NanoForge-dev/schematics/commit/6eaffe8d94ce99727a8c27e1f7d5e3fa39a787ca)) by @Exeloo
+- **docker:** Change base image as alpine doesnt have the right libs (#102) ([3b8c2b2](https://github.com/NanoForge-dev/schematics/commit/3b8c2b2476210b3df6a08415bb5f18b7cbdaf970)) by @MartinFillon
+- Remove ts types in js example system in part base (#93) ([89555b4](https://github.com/NanoForge-dev/schematics/commit/89555b46ffa8f3a4e2f157ecd9fabb2202de1cd7)) by @Exeloo
+- Fields does not replace on application (#86) ([d252953](https://github.com/NanoForge-dev/schematics/commit/d252953d4aef98a3ea505bb7bd45701f139d5599)) by @Exeloo
+- Use of directory variable and js issues (#84) ([9d98d1a](https://github.com/NanoForge-dev/schematics/commit/9d98d1ae977e69cfc07b4e9131bde2ea2b34964f)) by @Exeloo
+
+## Documentation
+
+- Fix docs path (#140) ([fead8fd](https://github.com/NanoForge-dev/schematics/commit/fead8fd1e12ff872a5056f791dcaa7013bd135db)) by @Exeloo
+- Add schematics docs (#61) ([7735324](https://github.com/NanoForge-dev/schematics/commit/7735324bcb5438929a24481e157677cc25f7457c)) by @Exeloo
+- Fix contribution (#47) ([b6ad53e](https://github.com/NanoForge-dev/schematics/commit/b6ad53e9f77b6f6619b5090f08e9d3fb94e90a74)) by @Exeloo
+- Add contributing docs and actions (#41) ([91eea94](https://github.com/NanoForge-dev/schematics/commit/91eea949ea785a03ca66d3a6e07d30d8e24df815)) by @Exeloo
+
+## Features
+
+- Add new release workflow (#137) ([af6c1e0](https://github.com/NanoForge-dev/schematics/commit/af6c1e0a9d49cb0cf46bbe6edde02ccfde04bc89)) by @Exeloo
+- Use new docs actions to synchronize config (#123) ([fa3f4ec](https://github.com/NanoForge-dev/schematics/commit/fa3f4ece55b281bb37771b2f1fe5580ff5f707fc)) by @MartinFillon
+- Add dom to application schema tsconfig (#111) ([368fa7c](https://github.com/NanoForge-dev/schematics/commit/368fa7cdd94027fa9b8cc23516654bd6bb3a0beb)) by @Exeloo
+- Add component and system schematics (#103) ([38c90df](https://github.com/NanoForge-dev/schematics/commit/38c90df9bbe971da83aaa0338f39f875695083cf)) by @Exeloo
+- **editor:** Add component holding entity id (#100) ([433c648](https://github.com/NanoForge-dev/schematics/commit/433c64851faafaf4198ac56801d09b47cce150c4)) by @Tchips46
+- **save:** Allow partial saves component values as record string any (#98) ([9280349](https://github.com/NanoForge-dev/schematics/commit/9280349d9028d99028ab529e3c477cfd11d14606)) by @Tchips46
+- Set save params types to any (#96) ([7a29579](https://github.com/NanoForge-dev/schematics/commit/7a29579e03fc1c4ca2ebc7794b4b3dd60167f741)) by @Exeloo
+- Add core editor to dependencies (#90) ([29cfa08](https://github.com/NanoForge-dev/schematics/commit/29cfa0864ee4a84fdc5aed5e790da454334dd2d5)) by @Exeloo
+- Add better handling of path option (#89) ([1e0c469](https://github.com/NanoForge-dev/schematics/commit/1e0c469f98aac91d81e21d20e36a7d9e7a94df8b)) by @Exeloo
+ - **BREAKING CHANGE:** Old schemas doesn't work anymore and config is changed
+- Add editor option to part main schematic (#88) ([c59ed27](https://github.com/NanoForge-dev/schematics/commit/c59ed2710e2db2bcd3e5c553be9c80f5ad54b732)) by @Exeloo
+- Add lint param handling on application (#79) ([96c2128](https://github.com/NanoForge-dev/schematics/commit/96c212817d763fc2677d28cd57f3d196c6a45f6d)) by @Exeloo
+- Setup docker schema (#75) ([cf7ead5](https://github.com/NanoForge-dev/schematics/commit/cf7ead5c49164a85e3beb65be3c73c842cd14e58)) by @MartinFillon
+- Add network and fix versions in app base (#70) ([6d5963a](https://github.com/NanoForge-dev/schematics/commit/6d5963a954e7a27c442377ecb70716a43e479717)) by @Exeloo
+- Update and add pre-release (#48) ([49422f7](https://github.com/NanoForge-dev/schematics/commit/49422f7ad9e0595378cf6e8b44dc7711d09b8cfa)) by @Exeloo
+
+## Testing
+
+- Add unit tests and e2e tests (#67) ([8eef6b5](https://github.com/NanoForge-dev/schematics/commit/8eef6b51984d1187d3f0b936d670e362ad0224c6)) by @Exeloo
+
+### New Contributors
+
+- @MartinFillon made their first contribution in #151
+- @github-actions[bot] made their first contribution in #150
+- @dependabot[bot] made their first contribution in #143
+- @Tchips46 made their first contribution in #113
+
+# [2.1.4](https://github.com/NanoForge-dev/schematics/compare/1.0.2...2.1.4) - (2026-06-29)
+
+## Bug Fixes
+
+- Issues (#139) ([131e794](https://github.com/NanoForge-dev/schematics/commit/131e794601b9fdcb583ac52eab9ca44a1cf8531f)) by @Exeloo
+- **synchronize-docs:** Use actions key to have the rights (#127) ([f73464d](https://github.com/NanoForge-dev/schematics/commit/f73464d09f08f14f2c82960fe9cef2dfde7c00dc)) by @MartinFillon
+- Give package json path (#124) ([a31e265](https://github.com/NanoForge-dev/schematics/commit/a31e26522078c9b60a63d3d91f1eccda96dadac7)) by @MartinFillon
+- **template:** New format (#113) ([1e89de7](https://github.com/NanoForge-dev/schematics/commit/1e89de7a536f75783c260cf1a0f0b05f99ffcf97)) by @Tchips46
+- Remove name formatting on app (#110) ([9478843](https://github.com/NanoForge-dev/schematics/commit/9478843b45a97714070babc6a7d41b8a9a6f2f65)) by @Exeloo
+- Change save format in part base schematic (#105) ([6eaffe8](https://github.com/NanoForge-dev/schematics/commit/6eaffe8d94ce99727a8c27e1f7d5e3fa39a787ca)) by @Exeloo
+- **docker:** Change base image as alpine doesnt have the right libs (#102) ([3b8c2b2](https://github.com/NanoForge-dev/schematics/commit/3b8c2b2476210b3df6a08415bb5f18b7cbdaf970)) by @MartinFillon
+- Remove ts types in js example system in part base (#93) ([89555b4](https://github.com/NanoForge-dev/schematics/commit/89555b46ffa8f3a4e2f157ecd9fabb2202de1cd7)) by @Exeloo
+- Fields does not replace on application (#86) ([d252953](https://github.com/NanoForge-dev/schematics/commit/d252953d4aef98a3ea505bb7bd45701f139d5599)) by @Exeloo
+- Use of directory variable and js issues (#84) ([9d98d1a](https://github.com/NanoForge-dev/schematics/commit/9d98d1ae977e69cfc07b4e9131bde2ea2b34964f)) by @Exeloo
+
+## Documentation
+
+- Fix docs path (#140) ([fead8fd](https://github.com/NanoForge-dev/schematics/commit/fead8fd1e12ff872a5056f791dcaa7013bd135db)) by @Exeloo
+- Add schematics docs (#61) ([7735324](https://github.com/NanoForge-dev/schematics/commit/7735324bcb5438929a24481e157677cc25f7457c)) by @Exeloo
+- Fix contribution (#47) ([b6ad53e](https://github.com/NanoForge-dev/schematics/commit/b6ad53e9f77b6f6619b5090f08e9d3fb94e90a74)) by @Exeloo
+- Add contributing docs and actions (#41) ([91eea94](https://github.com/NanoForge-dev/schematics/commit/91eea949ea785a03ca66d3a6e07d30d8e24df815)) by @Exeloo
+
+## Features
+
+- Add new release workflow (#137) ([af6c1e0](https://github.com/NanoForge-dev/schematics/commit/af6c1e0a9d49cb0cf46bbe6edde02ccfde04bc89)) by @Exeloo
+- Use new docs actions to synchronize config (#123) ([fa3f4ec](https://github.com/NanoForge-dev/schematics/commit/fa3f4ece55b281bb37771b2f1fe5580ff5f707fc)) by @MartinFillon
+- Add dom to application schema tsconfig (#111) ([368fa7c](https://github.com/NanoForge-dev/schematics/commit/368fa7cdd94027fa9b8cc23516654bd6bb3a0beb)) by @Exeloo
+- Add component and system schematics (#103) ([38c90df](https://github.com/NanoForge-dev/schematics/commit/38c90df9bbe971da83aaa0338f39f875695083cf)) by @Exeloo
+- **editor:** Add component holding entity id (#100) ([433c648](https://github.com/NanoForge-dev/schematics/commit/433c64851faafaf4198ac56801d09b47cce150c4)) by @Tchips46
+- **save:** Allow partial saves component values as record string any (#98) ([9280349](https://github.com/NanoForge-dev/schematics/commit/9280349d9028d99028ab529e3c477cfd11d14606)) by @Tchips46
+- Set save params types to any (#96) ([7a29579](https://github.com/NanoForge-dev/schematics/commit/7a29579e03fc1c4ca2ebc7794b4b3dd60167f741)) by @Exeloo
+- Add core editor to dependencies (#90) ([29cfa08](https://github.com/NanoForge-dev/schematics/commit/29cfa0864ee4a84fdc5aed5e790da454334dd2d5)) by @Exeloo
+- Add better handling of path option (#89) ([1e0c469](https://github.com/NanoForge-dev/schematics/commit/1e0c469f98aac91d81e21d20e36a7d9e7a94df8b)) by @Exeloo
+ - **BREAKING CHANGE:** Old schemas doesn't work anymore and config is changed
+- Add editor option to part main schematic (#88) ([c59ed27](https://github.com/NanoForge-dev/schematics/commit/c59ed2710e2db2bcd3e5c553be9c80f5ad54b732)) by @Exeloo
+- Add lint param handling on application (#79) ([96c2128](https://github.com/NanoForge-dev/schematics/commit/96c212817d763fc2677d28cd57f3d196c6a45f6d)) by @Exeloo
+- Setup docker schema (#75) ([cf7ead5](https://github.com/NanoForge-dev/schematics/commit/cf7ead5c49164a85e3beb65be3c73c842cd14e58)) by @MartinFillon
+- Add network and fix versions in app base (#70) ([6d5963a](https://github.com/NanoForge-dev/schematics/commit/6d5963a954e7a27c442377ecb70716a43e479717)) by @Exeloo
+- Update and add pre-release (#48) ([49422f7](https://github.com/NanoForge-dev/schematics/commit/49422f7ad9e0595378cf6e8b44dc7711d09b8cfa)) by @Exeloo
+
+## Testing
+
+- Add unit tests and e2e tests (#67) ([8eef6b5](https://github.com/NanoForge-dev/schematics/commit/8eef6b51984d1187d3f0b936d670e362ad0224c6)) by @Exeloo
+
+### New Contributors
+
+- @MartinFillon made their first contribution in #149
+- @dependabot[bot] made their first contribution in #143
+- @github-actions[bot] made their first contribution in #114
+- @Tchips46 made their first contribution in #113
+
+# [@nanoforge-dev/schematics@2.1.3](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@2.1.2...@nanoforge-dev/schematics@2.1.3) - (2026-04-19)
+
+## Bug Fixes
+
+- **template:** New format (#113) ([1e89de7](https://github.com/NanoForge-dev/schematics/commit/1e89de7a536f75783c260cf1a0f0b05f99ffcf97)) by @Tchips46
+
+# [@nanoforge-dev/schematics@2.1.2](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@2.1.1...@nanoforge-dev/schematics@2.1.2) - (2026-04-17)
+
+## Bug Fixes
+
+- Remove name formatting on app (#110) ([9478843](https://github.com/NanoForge-dev/schematics/commit/9478843b45a97714070babc6a7d41b8a9a6f2f65)) by @Exeloo
+
+## Features
+
+- Add dom to application schema tsconfig (#111) ([368fa7c](https://github.com/NanoForge-dev/schematics/commit/368fa7cdd94027fa9b8cc23516654bd6bb3a0beb)) by @Exeloo
+
+# [@nanoforge-dev/schematics@2.1.1](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@2.1.0...@nanoforge-dev/schematics@2.1.1) - (2026-04-02)
+
+## Bug Fixes
+
+- Change save format in part base schematic (#105) ([6eaffe8](https://github.com/NanoForge-dev/schematics/commit/6eaffe8d94ce99727a8c27e1f7d5e3fa39a787ca)) by @Exeloo
+
+# [@nanoforge-dev/schematics@2.1.0](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@2.0.0...@nanoforge-dev/schematics@2.1.0) - (2026-04-02)
+
+## Bug Fixes
+
+- **docker:** Change base image as alpine doesnt have the right libs (#102) ([3b8c2b2](https://github.com/NanoForge-dev/schematics/commit/3b8c2b2476210b3df6a08415bb5f18b7cbdaf970)) by @MartinFillon
+- Remove ts types in js example system in part base (#93) ([89555b4](https://github.com/NanoForge-dev/schematics/commit/89555b46ffa8f3a4e2f157ecd9fabb2202de1cd7)) by @Exeloo
+
+## Features
+
+- Add component and system schematics (#103) ([38c90df](https://github.com/NanoForge-dev/schematics/commit/38c90df9bbe971da83aaa0338f39f875695083cf)) by @Exeloo
+- **editor:** Add component holding entity id (#100) ([433c648](https://github.com/NanoForge-dev/schematics/commit/433c64851faafaf4198ac56801d09b47cce150c4)) by @Tchips46
+- **save:** Allow partial saves component values as record string any (#98) ([9280349](https://github.com/NanoForge-dev/schematics/commit/9280349d9028d99028ab529e3c477cfd11d14606)) by @Tchips46
+- Set save params types to any (#96) ([7a29579](https://github.com/NanoForge-dev/schematics/commit/7a29579e03fc1c4ca2ebc7794b4b3dd60167f741)) by @Exeloo
+
+### New Contributors
+
+- @Tchips46 made their first contribution in #100
+
+# [@nanoforge-dev/schematics@2.0.0](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@1.2.2...@nanoforge-dev/schematics@2.0.0) - (2026-03-17)
+
+## Features
+
+- Add core editor to dependencies (#90) ([29cfa08](https://github.com/NanoForge-dev/schematics/commit/29cfa0864ee4a84fdc5aed5e790da454334dd2d5)) by @Exeloo
+- Add better handling of path option (#89) ([1e0c469](https://github.com/NanoForge-dev/schematics/commit/1e0c469f98aac91d81e21d20e36a7d9e7a94df8b)) by @Exeloo
+ - **BREAKING CHANGE:** Old schemas doesn't work anymore and config is changed
+- Add editor option to part main schematic (#88) ([c59ed27](https://github.com/NanoForge-dev/schematics/commit/c59ed2710e2db2bcd3e5c553be9c80f5ad54b732)) by @Exeloo
+
+# [@nanoforge-dev/schematics@1.2.2](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@1.2.1...@nanoforge-dev/schematics@1.2.2) - (2026-03-15)
+
+## Bug Fixes
+
+- Fields does not replace on application (#86) ([d252953](https://github.com/NanoForge-dev/schematics/commit/d252953d4aef98a3ea505bb7bd45701f139d5599)) by @Exeloo
+
+# [@nanoforge-dev/schematics@1.2.1](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@1.2.0...@nanoforge-dev/schematics@1.2.1) - (2026-03-15)
+
+## Bug Fixes
+
+- Use of directory variable and js issues (#84) ([9d98d1a](https://github.com/NanoForge-dev/schematics/commit/9d98d1ae977e69cfc07b4e9131bde2ea2b34964f)) by @Exeloo
+
+## Features
+
+- Add lint param handling on application (#79) ([96c2128](https://github.com/NanoForge-dev/schematics/commit/96c212817d763fc2677d28cd57f3d196c6a45f6d)) by @Exeloo
+
+# [@nanoforge-dev/schematics@1.2.0](https://github.com/NanoForge-dev/schematics/compare/@nanoforge-dev/schematics@1.1.0...@nanoforge-dev/schematics@1.2.0) - (2026-02-27)
+
+## Features
+
+- Setup docker schema (#75) ([cf7ead5](https://github.com/NanoForge-dev/schematics/commit/cf7ead5c49164a85e3beb65be3c73c842cd14e58)) by @MartinFillon
+
+### New Contributors
+
+- @MartinFillon made their first contribution in #75
+- @dependabot[bot] made their first contribution in #76
+
+# [@nanoforge-dev/schematics@1.1.0](https://github.com/NanoForge-dev/schematics/tree/@nanoforge-dev/schematics@1.1.0) - (2026-02-19)
+
+## Documentation
+
+- Add schematics docs (#61) ([7735324](https://github.com/NanoForge-dev/schematics/commit/7735324bcb5438929a24481e157677cc25f7457c)) by @Exeloo
+- Fix contribution (#47) ([b6ad53e](https://github.com/NanoForge-dev/schematics/commit/b6ad53e9f77b6f6619b5090f08e9d3fb94e90a74)) by @Exeloo
+- Add contributing docs and actions (#41) ([91eea94](https://github.com/NanoForge-dev/schematics/commit/91eea949ea785a03ca66d3a6e07d30d8e24df815)) by @Exeloo
+
+## Features
+
+- Add network and fix versions in app base (#70) ([6d5963a](https://github.com/NanoForge-dev/schematics/commit/6d5963a954e7a27c442377ecb70716a43e479717)) by @Exeloo
+- Update and add pre-release (#48) ([49422f7](https://github.com/NanoForge-dev/schematics/commit/49422f7ad9e0595378cf6e8b44dc7711d09b8cfa)) by @Exeloo
+
+## Testing
+
+- Add unit tests and e2e tests (#67) ([8eef6b5](https://github.com/NanoForge-dev/schematics/commit/8eef6b51984d1187d3f0b936d670e362ad0224c6)) by @Exeloo
+
+# [1.0.2](https://github.com/NanoForge-dev/schematics/compare/1.0.1...1.0.2) - (2025-12-06)
+
+## Bug Fixes
+
+- Split ecs between client and server (#38) ([086d42f](https://github.com/NanoForge-dev/schematics/commit/086d42f1639cc764fb6f21c6ce8d9426ec2581e7)) by @Exeloo
+
+# [1.0.1](https://github.com/NanoForge-dev/schematics/compare/1.0.0...1.0.1) - (2025-12-04)
+
+## Bug Fixes
+
+- **configuration:** Simplify schema and change defaults (#34) ([af29b00](https://github.com/NanoForge-dev/schematics/commit/af29b0011ae078dc75c9625e8f470f8506a7c7cb)) by @Exeloo
+
+# [1.0.0](https://github.com/NanoForge-dev/schematics/compare/0.0.1...1.0.0) - (2025-11-30)
+
+## Bug Fixes
+
+- Change exports in package json and update schema paths (#17) ([b78b72a](https://github.com/NanoForge-dev/schematics/commit/b78b72a40b2287d841a3cac90adb0d37b4493d4e)) by @Exeloo
+
+## Features
+
+- **schematics:** Add part-main schematics for generating client/server main files (#29) ([6f34c1c](https://github.com/NanoForge-dev/schematics/commit/6f34c1c0f70d67eb78645108a665f7c5ebab8086)) by @Exeloo
+- **schematics:** Replace client by base-part schematics to handle server (#20) ([c5ad539](https://github.com/NanoForge-dev/schematics/commit/c5ad539d7ac524de724c49dfa6b0e7b310700a44)) by @Exeloo
+- **schematics:** Add client base schematics (#19) ([f02b26d](https://github.com/NanoForge-dev/schematics/commit/f02b26db6be9226857fbf3fe0ffe1abf6de31024)) by @Exeloo
+
+### New Contributors
+
+- @renovate[bot] made their first contribution in #27
+
+# [0.0.1](https://github.com/NanoForge-dev/schematics/tree/0.0.1) - (2025-11-28)
+
+## Features
+
+- Add base application and configuration schematics (#12) ([d881653](https://github.com/NanoForge-dev/schematics/commit/d881653dd57a886f12e18b9acf0d48d31d3686e6))
+- Init schematics project (#11) ([3fb0e61](https://github.com/NanoForge-dev/schematics/commit/3fb0e61075cf8094309765a0f685d2411c7f8aa8)) by @Exeloo
+
+### New Contributors
+
+- @Exeloo made their first contribution in #11
diff --git a/libs/schematics/LICENSE b/libs/schematics/LICENSE
new file mode 100644
index 0000000..4672486
--- /dev/null
+++ b/libs/schematics/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright © 2026 NanoForge
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libs/schematics/README.md b/libs/schematics/README.md
new file mode 100644
index 0000000..90ab999
--- /dev/null
+++ b/libs/schematics/README.md
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## About
+
+This package contains the Schematics of NanoForge, used by the [CLI][cli-source] to generate project files. Check [releases][github-releases] to see versions of the Schematics. Nanoforge is a powerful game engine for web browser.
+
+## Usage
+
+To use Nanoforge Schematics, please refer to the [CLI documentation][cli-source] !
+
+First, install the CLI :
+
+```bash
+npm install -g @nanoforge-dev/cli
+```
+
+And then create a new project :
+
+```bash
+nf new
+```
+
+## Schematics
+
+This repository provide multiples schematics, usable with Angular Devkit schematics.
+
+- `workspace` : Root of a NanoForge monorepo (`nanoforge.config.ts`, `package.json`, tooling)
+- `project` : Client or server project under `apps/`, including `main.ts`, example component/system, and an optional Dockerfile
+- `component` : ECS component class for a client or server part
+- `system` : ECS system function for a client or server part
+
+## Contributing
+
+Please read through our [contribution guidelines][contributing] before starting a pull request. We welcome contributions of all kinds, not just code! If you're stuck for ideas, look for the [good first issue][good-first-issue] label on issues in the repository. If you have any questions about the project, feel free to ask them on [Discussions][discussions]. Before creating your own issue or pull request, always check to see if one already exists! Don't rush contributions, take your time and ensure you're doing it correctly.
+
+## Help
+
+If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please ask on [Discussions][discussions].
+
+[contributing]: https://github.com/NanoForge-dev/CLI/blob/main/.github/CONTRIBUTING.md
+[discussions]: https://github.com/NanoForge-dev/CLI/discussions
+[cli-source]: https://github.com/NanoForge-dev/CLI
+[github-releases]: https://github.com/NanoForge-dev/CLI/releases
+[good-first-issue]: https://github.com/NanoForge-dev/CLI/contribute
diff --git a/libs/schematics/cliff.toml b/libs/schematics/cliff.toml
new file mode 100644
index 0000000..4bb6a65
--- /dev/null
+++ b/libs/schematics/cliff.toml
@@ -0,0 +1,79 @@
+[changelog]
+header = """
+# Changelog
+
+All notable changes to this project will be documented in this file.\n
+"""
+body = """
+{%- macro remote_url() -%}
+ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
+{%- endmacro -%}
+{% if version %}\
+ # [{{ version | trim_start_matches(pat="v") }}]\
+ {% if previous %}\
+ {% if previous.version %}\
+ ({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
+ {% else %}\
+ ({{ self::remote_url() }}/tree/{{ version }})\
+ {% endif %}\
+ {% endif %} \
+ - ({{ timestamp | date(format="%Y-%m-%d") }})
+{% else %}\
+ # [unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+ ## {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.scope %}\
+ **{{commit.scope}}:** \
+ {% endif %}\
+ {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
+ {% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
+ {% if commit.breaking %}\
+ {% for footer in commit.footers %}\
+ {% if footer.breaking %}\
+ \n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
+ {% endif %}\
+ {% endfor %}\
+ {% endif %}\
+ {% endfor %}
+{% endfor %}\
+{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\
+ \n### New Contributors\n
+ {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\
+ - @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
+ {% endfor %}\
+{% endif %}\n
+"""
+trim = true
+footer = ""
+
+[git]
+conventional_commits = true
+filter_unconventional = true
+commit_parsers = [
+ { message = "^feat", group = "Features"},
+ { message = "^fix", group = "Bug Fixes"},
+ { message = "^docs", group = "Documentation"},
+ { message = "^perf", group = "Performance"},
+ { message = "^refactor", group = "Refactor"},
+ { message = "^types", group = "Typings"},
+ { message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
+ { message = "^revert", skip = true},
+ { message = "^style", group = "Styling"},
+ { message = "^test", group = "Testing"},
+ { message = "^chore", skip = true},
+ { message = "^ci", skip = true},
+ { message = "^build", skip = true},
+ { body = ".*security", group = "Security"},
+]
+filter_commits = true
+protect_breaking_commits = true
+tag_pattern = "^[0-9]+"
+ignore_tags = ""
+topo_order = false
+sort_commits = "newest"
+
+[remote.github]
+owner = "NanoForge-dev"
+repo = "CLI"
diff --git a/libs/schematics/commitlint.config.cjs b/libs/schematics/commitlint.config.cjs
new file mode 100644
index 0000000..a6580b4
--- /dev/null
+++ b/libs/schematics/commitlint.config.cjs
@@ -0,0 +1,4 @@
+module.exports = {
+ extends: ["@commitlint/config-conventional"],
+ rules: {},
+};
diff --git a/libs/schematics/e2e/component.test.ts b/libs/schematics/e2e/component.test.ts
new file mode 100644
index 0000000..aaf3cf8
--- /dev/null
+++ b/libs/schematics/e2e/component.test.ts
@@ -0,0 +1,171 @@
+import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing";
+import { resolve } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+
+const collectionPath = resolve(__dirname, "../dist/collection.json");
+
+describe("component schematic", () => {
+ const runner = new SchematicTestRunner("schematics", collectionPath);
+
+ describe("TypeScript component for client part", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ });
+
+ it("should generate a .ts component file", () => {
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ });
+
+ it("should use PascalCase class name", () => {
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("class MyComponentComponent");
+ });
+
+ it("should use the correct part in the import", () => {
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("@nanoforge-dev/ecs-client");
+ });
+
+ it("should export the component name as default", () => {
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("export default MyComponentComponent.name");
+ });
+
+ it("should export the EDITOR_COMPONENT_MANIFEST", () => {
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("export const EDITOR_COMPONENT_MANIFEST");
+ expect(content).toContain('name: "MyComponent"');
+ });
+ });
+
+ describe("TypeScript component for server part", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ directory: "my-app",
+ part: "server",
+ language: "ts",
+ });
+ });
+
+ it("should generate a .ts component file", () => {
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ });
+
+ it("should use the correct part in the import", () => {
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("@nanoforge-dev/ecs-server");
+ });
+ });
+
+ describe("JavaScript component", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ directory: "my-app",
+ part: "client",
+ language: "js",
+ });
+ });
+
+ it("should generate a .js component file", () => {
+ expect(tree.files).toContain("/my-app/my-component.component.js");
+ });
+
+ it("should use PascalCase class name", () => {
+ const content = tree.readContent("/my-app/my-component.component.js");
+ expect(content).toContain("class MyComponentComponent");
+ });
+
+ it("should use the correct part in the JSDoc typedef", () => {
+ const content = tree.readContent("/my-app/my-component.component.js");
+ expect(content).toContain("@nanoforge-dev/ecs-client");
+ });
+
+ it("should export the EDITOR_COMPONENT_MANIFEST", () => {
+ const content = tree.readContent("/my-app/my-component.component.js");
+ expect(content).toContain("export const EDITOR_COMPONENT_MANIFEST");
+ expect(content).toContain('name: "MyComponent"');
+ });
+ });
+
+ describe("name formatting", () => {
+ it("should handle kebab-case name", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "my-component",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("class MyComponentComponent");
+ });
+
+ it("should handle PascalCase name", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "MyComponent",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("class MyComponentComponent");
+ });
+
+ it("should handle snake_case name", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "my_component",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ const content = tree.readContent("/my-app/my-component.component.ts");
+ expect(content).toContain("class MyComponentComponent");
+ });
+ });
+
+ describe("custom directory", () => {
+ it("should generate file in the specified directory", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ directory: "src/client/components",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/src/client/components/my-component.component.ts");
+ });
+ });
+
+ describe("default values", () => {
+ it("should default to TypeScript when language is not specified", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ directory: "my-app",
+ part: "client",
+ });
+ expect(tree.files).toContain("/my-app/my-component.component.ts");
+ });
+
+ it("should default to current directory when directory is not specified", async () => {
+ const tree = await runner.runSchematic("component", {
+ name: "myComponent",
+ part: "client",
+ });
+ expect(tree.files).toContain("/my-component.component.ts");
+ });
+ });
+});
diff --git a/libs/schematics/e2e/project.test.ts b/libs/schematics/e2e/project.test.ts
new file mode 100644
index 0000000..2f0637e
--- /dev/null
+++ b/libs/schematics/e2e/project.test.ts
@@ -0,0 +1,429 @@
+import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing";
+import { resolve } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+
+const collectionPath = resolve(__dirname, "../dist/collection.json");
+
+// The factory resolves real registry versions (respecting a 48h trust
+// window) for standalone projects, so we can't assert an exact pinned
+// number here — only that a registry-resolved value or a workspace-linking
+// protocol came back.
+// Includes semver-range fallbacks (e.g. "^2") so this still passes when the
+// registry is unreachable and the fetcher falls back to its default.
+const RESOLVED_VERSION = /^(\d+\.\d+\.\d+|workspace:\*|\*|\^\d+(\.\d+){0,2})$/;
+
+describe("project schematic", () => {
+ const runner = new SchematicTestRunner("schematics", collectionPath);
+
+ describe("client project with TypeScript", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "my-workspace",
+ directory: "apps/client",
+ });
+ });
+
+ it("should generate the project structure under apps/client", () => {
+ expect(tree.files).toContain("/apps/client/package.json");
+ expect(tree.files).toContain("/apps/client/tsconfig.json");
+ expect(tree.files).toContain("/apps/client/nanoforge.config.ts");
+ expect(tree.files).toContain("/apps/client/src/main.ts");
+ expect(tree.files).toContain("/apps/client/src/components/drawable-circle-2d.component.ts");
+ expect(tree.files).toContain("/apps/client/src/components/position-2d.component.ts");
+ expect(tree.files).toContain("/apps/client/src/systems/draw-2d.system.ts");
+ expect(tree.files).toContain("/apps/client/assets/.gitkeep");
+ });
+
+ it("should derive the package name from workspaceName and part", () => {
+ const packageJson = JSON.parse(tree.readContent("/apps/client/package.json"));
+ expect(packageJson.name).toBe("my-workspace-client");
+ });
+
+ it("should include client-only dependencies", () => {
+ const packageJson = JSON.parse(tree.readContent("/apps/client/package.json"));
+ expect(packageJson.devDependencies["@nanoforge-dev/graphics-2d"]).toBeDefined();
+ expect(packageJson.devDependencies["@nanoforge-dev/input"]).toBeDefined();
+ });
+
+ it("should declare network as a dev dependency", () => {
+ const packageJson = JSON.parse(tree.readContent("/apps/client/package.json"));
+ expect(packageJson.devDependencies["@nanoforge-dev/network"]).toBeDefined();
+ expect(packageJson).not.toHaveProperty("dependencies");
+ });
+
+ it("should declare a client-typed nanoforge.config.ts", () => {
+ const content = tree.readContent("/apps/client/nanoforge.config.ts");
+ expect(content).toContain('type: "client"');
+ });
+
+ it("should generate a client entry point", () => {
+ const content = tree.readContent("/apps/client/src/main.ts");
+ expect(content).toContain("NanoforgeFactory.createClient({ tickRate: 60 })");
+ expect(content).toContain('from "@nanoforge-dev/ecs/client"');
+ expect(content).toContain("Graphics2DLibrary");
+ expect(content).toContain("InputLibrary");
+ expect(content).toContain("NetworkClientLibrary");
+ });
+
+ it("should access the graphics library directly off ctx, not through ctx.libs", () => {
+ const content = tree.readContent("/apps/client/src/systems/draw-2d.system.ts");
+ expect(content).toContain("ctx.graphics");
+ expect(content).not.toContain("ctx.libs");
+ });
+
+ it("should not generate Dockerfile or init functions by default", () => {
+ expect(tree.files).not.toContain("/apps/client/Dockerfile");
+ expect(tree.files).not.toContain("/apps/client/.dockerignore");
+ const initFiles = tree.files.filter((f) => f.includes("/init/"));
+ expect(initFiles).toHaveLength(0);
+ });
+ });
+
+ describe("server project with TypeScript", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "my-workspace",
+ directory: "apps/server",
+ });
+ });
+
+ it("should generate the project structure under apps/server", () => {
+ expect(tree.files).toContain("/apps/server/package.json");
+ expect(tree.files).toContain("/apps/server/src/main.ts");
+ });
+
+ it("should not include client-only dependencies", () => {
+ const packageJson = JSON.parse(tree.readContent("/apps/server/package.json"));
+ expect(packageJson.devDependencies["@nanoforge-dev/graphics-2d"]).toBeUndefined();
+ expect(packageJson.devDependencies["@nanoforge-dev/input"]).toBeUndefined();
+ });
+
+ it("should declare network as a runtime dependency", () => {
+ const packageJson = JSON.parse(tree.readContent("/apps/server/package.json"));
+ expect(packageJson.dependencies["@nanoforge-dev/network"]).toBeDefined();
+ expect(packageJson.devDependencies["@nanoforge-dev/network"]).toBeUndefined();
+ });
+
+ it("should generate a server entry point", () => {
+ const content = tree.readContent("/apps/server/src/main.ts");
+ expect(content).toContain("NanoforgeFactory.createServer({ tickRate: 60 })");
+ expect(content).toContain('from "@nanoforge-dev/ecs/server"');
+ expect(content).not.toContain("Graphics2DLibrary");
+ expect(content).toContain("NetworkServerLibrary");
+ expect(content).toContain("move2D");
+ expect(content).toContain("new Position2D(500, 500)");
+ });
+
+ it("should generate a server-side position component and movement system", () => {
+ expect(tree.files).toContain("/apps/server/src/components/position-2d.component.ts");
+ expect(tree.files).toContain("/apps/server/src/systems/move-2d.system.ts");
+ expect(tree.files).not.toContain(
+ "/apps/server/src/components/drawable-circle-2d.component.ts",
+ );
+ expect(tree.files).not.toContain("/apps/server/src/systems/draw-2d.system.ts");
+ });
+ });
+
+ describe("with a custom name", () => {
+ it("replaces part in the package name when workspaceName is set", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "my-workspace",
+ name: "frontend",
+ directory: "custom-name-apps/client",
+ });
+ const packageJson = JSON.parse(tree.readContent("/custom-name-apps/client/package.json"));
+ expect(packageJson.name).toBe("my-workspace-frontend");
+ });
+
+ it("becomes the whole package name when workspaceName is absent", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ name: "standalone-app",
+ directory: "custom-name-standalone-apps/client",
+ });
+ const packageJson = JSON.parse(
+ tree.readContent("/custom-name-standalone-apps/client/package.json"),
+ );
+ expect(packageJson.name).toBe("standalone-app");
+ });
+
+ it("falls back to a default app name when neither workspaceName nor name is set", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ directory: "custom-name-default-apps/client",
+ });
+ const packageJson = JSON.parse(
+ tree.readContent("/custom-name-default-apps/client/package.json"),
+ );
+ expect(packageJson.name).toBe("nanoforge-app");
+ });
+ });
+
+ describe("with hasServer", () => {
+ it("keeps the client's position local by default (hasServer omitted)", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "no-server-workspace",
+ directory: "no-server-apps/client",
+ });
+ expect(tree.files).not.toContain(
+ "/no-server-apps/client/src/systems/position-sync.system.ts",
+ );
+ const content = tree.readContent("/no-server-apps/client/src/main.ts");
+ expect(content).not.toContain("positionSync");
+ });
+
+ it("syncs the client's position from the server when hasServer is true", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "has-server-workspace",
+ directory: "has-server-apps/client",
+ hasServer: true,
+ });
+ expect(tree.files).toContain("/has-server-apps/client/src/systems/position-sync.system.ts");
+ const content = tree.readContent("/has-server-apps/client/src/main.ts");
+ expect(content).toContain('import { positionSync } from "./systems/position-sync.system"');
+ expect(content).toContain("registry.addSystem(positionSync)");
+ });
+
+ it("is ignored for a server project - position handling is always active there", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "server-has-server-workspace",
+ directory: "server-has-server-apps/server",
+ hasServer: false,
+ });
+ expect(tree.files).toContain("/server-has-server-apps/server/src/systems/move-2d.system.ts");
+ });
+ });
+
+ describe("with docker enabled", () => {
+ it("should generate Dockerfile and .dockerignore for a standalone project", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "docker-workspace",
+ directory: "docker-apps/client",
+ docker: true,
+ workspace: false,
+ });
+ expect(tree.files).toContain("/docker-apps/client/Dockerfile");
+ expect(tree.files).toContain("/docker-apps/client/.dockerignore");
+ });
+
+ it("should not generate a Dockerfile when the project is inside a workspace", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "docker-in-workspace",
+ directory: "docker-in-workspace-apps/client",
+ docker: true,
+ workspace: true,
+ });
+ expect(tree.files).not.toContain("/docker-in-workspace-apps/client/Dockerfile");
+ expect(tree.files).not.toContain("/docker-in-workspace-apps/client/.dockerignore");
+ });
+
+ describe.each(["npm", "yarn", "pnpm", "bun"] as const)(
+ "with %s package manager",
+ (packageManager) => {
+ it("should branch Dockerfile content on the package manager", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: `${packageManager}-workspace`,
+ directory: `${packageManager}-apps/client`,
+ docker: true,
+ workspace: false,
+ packageManager,
+ });
+ const content = tree.readContent(`/${packageManager}-apps/client/Dockerfile`);
+ const installCommand = packageManager === "npm" ? "npm ci" : `${packageManager} install`;
+ expect(content).toContain(`RUN ${installCommand}`);
+ expect(content).toContain(`RUN ${packageManager} run build`);
+ expect(content).toContain(`CMD ["${packageManager}", "run", "start"]`);
+ });
+ },
+ );
+ });
+
+ describe("dependency versioning via the workspace field", () => {
+ describe.each(["npm", "yarn", "pnpm", "bun"] as const)(
+ "with %s package manager",
+ (packageManager) => {
+ it("resolves a real registry version by default", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: `${packageManager}-dep-workspace`,
+ directory: `${packageManager}-dep-apps/client`,
+ packageManager,
+ });
+ const packageJson = JSON.parse(
+ tree.readContent(`/${packageManager}-dep-apps/client/package.json`),
+ );
+ expect(packageJson.devDependencies["@nanoforge-dev/ecs"]).toMatch(RESOLVED_VERSION);
+ expect(packageJson.devDependencies["nanoforge"]).toMatch(RESOLVED_VERSION);
+ });
+
+ it("resolves a real registry version when workspace is false", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: `${packageManager}-standalone-workspace`,
+ directory: `${packageManager}-standalone-apps/client`,
+ packageManager,
+ workspace: false,
+ });
+ const packageJson = JSON.parse(
+ tree.readContent(`/${packageManager}-standalone-apps/client/package.json`),
+ );
+ expect(packageJson.devDependencies["@nanoforge-dev/ecs"]).toMatch(RESOLVED_VERSION);
+ expect(packageJson.devDependencies["nanoforge"]).toMatch(RESOLVED_VERSION);
+ });
+ },
+ );
+
+ it("produces valid JSON for a server project", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "valid-json-workspace",
+ directory: "valid-json-apps/server",
+ });
+ expect(() =>
+ JSON.parse(tree.readContent("/valid-json-apps/server/package.json")),
+ ).not.toThrow();
+ });
+ });
+
+ describe("with JavaScript", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "js-workspace",
+ directory: "js-apps/client",
+ language: "js",
+ });
+ });
+
+ it("should generate JS files", () => {
+ expect(tree.files).toContain("/js-apps/client/src/main.js");
+ expect(tree.files).toContain(
+ "/js-apps/client/src/components/drawable-circle-2d.component.js",
+ );
+ expect(tree.files).toContain("/js-apps/client/jsconfig.json");
+ expect(tree.files).not.toContain("/js-apps/client/tsconfig.json");
+ });
+ });
+
+ describe("@nanoforge-dev/ecs and @nanoforge-dev/network version resolution", () => {
+ it("still writes the ecs/network keys even though they're resolved via their real per-part packages", async () => {
+ // @nanoforge-dev/ecs and @nanoforge-dev/network aren't published under
+ // those names (see ecs-lib/ecs-client/ecs-server and
+ // network-client/network-server) — the factory aliases to the real
+ // package to resolve a trustworthy version, but keeps writing the
+ // existing (unchanged) `ecs`/`network` keys.
+ const client = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "ecs-alias-client-workspace",
+ directory: "ecs-alias-client-apps/client",
+ workspace: false,
+ });
+ const clientPackageJson = JSON.parse(
+ client.readContent("/ecs-alias-client-apps/client/package.json"),
+ );
+ expect(clientPackageJson.devDependencies["@nanoforge-dev/ecs"]).toMatch(RESOLVED_VERSION);
+ expect(clientPackageJson.devDependencies["@nanoforge-dev/network"]).toMatch(RESOLVED_VERSION);
+
+ const server = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "ecs-alias-server-workspace",
+ directory: "ecs-alias-server-apps/server",
+ workspace: false,
+ });
+ const serverPackageJson = JSON.parse(
+ server.readContent("/ecs-alias-server-apps/server/package.json"),
+ );
+ expect(serverPackageJson.devDependencies["@nanoforge-dev/ecs"]).toMatch(RESOLVED_VERSION);
+ expect(serverPackageJson.dependencies["@nanoforge-dev/network"]).toMatch(RESOLVED_VERSION);
+ });
+ });
+
+ describe("packageManager (Corepack) field", () => {
+ it("is never set on a generated project's package.json", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "no-corepack-workspace",
+ directory: "no-corepack-apps/client",
+ workspace: false,
+ packageManager: "pnpm",
+ });
+ const packageJson = JSON.parse(tree.readContent("/no-corepack-apps/client/package.json"));
+ expect(packageJson.packageManager).toBeUndefined();
+ });
+ });
+
+ describe("allowBuilds (standalone projects are their own root)", () => {
+ it("is empty when generated inside a workspace", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "in-workspace-allow-builds",
+ directory: "in-workspace-allow-builds-apps/server",
+ packageManager: "pnpm",
+ });
+ expect(tree.files).not.toContain(
+ "/in-workspace-allow-builds-apps/server/pnpm-workspace.yaml",
+ );
+ const packageJson = JSON.parse(
+ tree.readContent("/in-workspace-allow-builds-apps/server/package.json"),
+ );
+ expect(packageJson).not.toHaveProperty("allowScripts");
+ expect(packageJson).not.toHaveProperty("trustedDependencies");
+ });
+
+ it("always allows bun in a pnpm-workspace.yaml for a standalone pnpm project", async () => {
+ const tree = await runner.runSchematic("project", {
+ part: "client",
+ workspaceName: "standalone-allow-builds",
+ directory: "standalone-allow-builds-apps/client",
+ workspace: false,
+ packageManager: "pnpm",
+ });
+ const pnpmWorkspaceYaml = tree.readContent(
+ "/standalone-allow-builds-apps/client/pnpm-workspace.yaml",
+ );
+ expect(pnpmWorkspaceYaml).toContain("allowBuilds:");
+ expect(pnpmWorkspaceYaml).toContain("bun: true");
+ });
+
+ it("always allows bun in allowScripts/trustedDependencies for a standalone npm/bun project", async () => {
+ const npmTree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "standalone-npm-allow-builds",
+ directory: "standalone-npm-allow-builds-apps/server",
+ workspace: false,
+ packageManager: "npm",
+ });
+ const npmPackageJson = JSON.parse(
+ npmTree.readContent("/standalone-npm-allow-builds-apps/server/package.json"),
+ );
+ expect(npmPackageJson.allowScripts).toEqual({ bun: true });
+
+ const bunTree = await runner.runSchematic("project", {
+ part: "server",
+ workspaceName: "standalone-bun-allow-builds",
+ directory: "standalone-bun-allow-builds-apps/server",
+ workspace: false,
+ packageManager: "bun",
+ });
+ const bunPackageJson = JSON.parse(
+ bunTree.readContent("/standalone-bun-allow-builds-apps/server/package.json"),
+ );
+ expect(bunPackageJson.trustedDependencies).toEqual(["bun"]);
+ });
+ });
+});
diff --git a/libs/schematics/e2e/system.test.ts b/libs/schematics/e2e/system.test.ts
new file mode 100644
index 0000000..b392fc3
--- /dev/null
+++ b/libs/schematics/e2e/system.test.ts
@@ -0,0 +1,171 @@
+import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing";
+import { resolve } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+
+const collectionPath = resolve(__dirname, "../dist/collection.json");
+
+describe("system schematic", () => {
+ const runner = new SchematicTestRunner("schematics", collectionPath);
+
+ describe("TypeScript system for client part", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ });
+
+ it("should generate a .ts system file", () => {
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ });
+
+ it("should use camelCase function name", () => {
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export const mySystemSystem");
+ });
+
+ it("should use the correct part in the import", () => {
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("@nanoforge-dev/ecs-client");
+ });
+
+ it("should export the system name as default", () => {
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export default mySystemSystem.name");
+ });
+
+ it("should export the EDITOR_SYSTEM_MANIFEST", () => {
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export const EDITOR_SYSTEM_MANIFEST");
+ expect(content).toContain('name: "mySystem"');
+ });
+ });
+
+ describe("TypeScript system for server part", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ directory: "my-app",
+ part: "server",
+ language: "ts",
+ });
+ });
+
+ it("should generate a .ts system file", () => {
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ });
+
+ it("should use the correct part in the import", () => {
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("@nanoforge-dev/ecs-server");
+ });
+ });
+
+ describe("JavaScript system", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ directory: "my-app",
+ part: "client",
+ language: "js",
+ });
+ });
+
+ it("should generate a .js system file", () => {
+ expect(tree.files).toContain("/my-app/my-system.system.js");
+ });
+
+ it("should use camelCase function name", () => {
+ const content = tree.readContent("/my-app/my-system.system.js");
+ expect(content).toContain("export const mySystemSystem");
+ });
+
+ it("should use the correct part in the JSDoc typedef", () => {
+ const content = tree.readContent("/my-app/my-system.system.js");
+ expect(content).toContain("@nanoforge-dev/ecs-client");
+ });
+
+ it("should export the EDITOR_SYSTEM_MANIFEST", () => {
+ const content = tree.readContent("/my-app/my-system.system.js");
+ expect(content).toContain("export const EDITOR_SYSTEM_MANIFEST");
+ expect(content).toContain('name: "mySystem"');
+ });
+ });
+
+ describe("name formatting", () => {
+ it("should handle kebab-case name", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "my-system",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export const mySystemSystem");
+ });
+
+ it("should handle PascalCase name", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "MySystem",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export const mySystemSystem");
+ });
+
+ it("should handle snake_case name", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "my_system",
+ directory: "my-app",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ const content = tree.readContent("/my-app/my-system.system.ts");
+ expect(content).toContain("export const mySystemSystem");
+ });
+ });
+
+ describe("custom directory", () => {
+ it("should generate file in the specified directory", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ directory: "src/client/systems",
+ part: "client",
+ language: "ts",
+ });
+ expect(tree.files).toContain("/src/client/systems/my-system.system.ts");
+ });
+ });
+
+ describe("default values", () => {
+ it("should default to TypeScript when language is not specified", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ directory: "my-app",
+ part: "client",
+ });
+ expect(tree.files).toContain("/my-app/my-system.system.ts");
+ });
+
+ it("should default to current directory when directory is not specified", async () => {
+ const tree = await runner.runSchematic("system", {
+ name: "mySystem",
+ part: "client",
+ });
+ expect(tree.files).toContain("/my-system.system.ts");
+ });
+ });
+});
diff --git a/libs/schematics/e2e/tsconfig.json b/libs/schematics/e2e/tsconfig.json
new file mode 100644
index 0000000..a0601d8
--- /dev/null
+++ b/libs/schematics/e2e/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ "include": ["./**/*.test.ts"],
+ "exclude": []
+}
diff --git a/libs/schematics/e2e/workspace.test.ts b/libs/schematics/e2e/workspace.test.ts
new file mode 100644
index 0000000..10445b1
--- /dev/null
+++ b/libs/schematics/e2e/workspace.test.ts
@@ -0,0 +1,264 @@
+import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing";
+import { resolve } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+
+const collectionPath = resolve(__dirname, "../dist/collection.json");
+
+// The factory resolves real registry versions (respecting a 48h trust
+// window), so we can't assert exact pinned numbers here — only that a
+// registry-resolved value or a workspace-linking protocol came back.
+// Includes semver-range fallbacks (e.g. "latest") so this still passes when
+// the registry is unreachable and the fetcher falls back to its default.
+const RESOLVED_VERSION = /^(\d+\.\d+\.\d+|workspace:\*|\*|latest|\^\d+(\.\d+){0,2})$/;
+
+describe("workspace schematic", () => {
+ const runner = new SchematicTestRunner("schematics", collectionPath);
+
+ describe("with TypeScript (default)", () => {
+ let tree: UnitTestTree;
+ let packageJson: any;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("workspace", { name: "my-workspace" });
+ packageJson = JSON.parse(tree.readContent("/my-workspace/package.json"));
+ });
+
+ it("should generate workspace files", () => {
+ expect(tree.files).toContain("/my-workspace/package.json");
+ expect(tree.files).toContain("/my-workspace/tsconfig.json");
+ expect(tree.files).toContain("/my-workspace/.gitignore");
+ expect(tree.files).toContain("/my-workspace/nanoforge.config.ts");
+ expect(tree.files).toContain("/my-workspace/README.md");
+ expect(tree.files).not.toContain("/my-workspace/pnpm-workspace.yaml");
+ });
+
+ it("does not generate any lint tooling files or the prettier/eslint dependencies", () => {
+ expect(tree.files).not.toContain("/my-workspace/eslint.config.js");
+ expect(tree.files).not.toContain("/my-workspace/prettier.config.js");
+ expect(tree.files).not.toContain("/my-workspace/.prettierignore");
+ expect(packageJson.devDependencies["prettier"]).toBeUndefined();
+ expect(packageJson.devDependencies["eslint"]).toBeUndefined();
+ expect(packageJson.scripts.lint).toBeUndefined();
+ expect(packageJson.scripts.format).toBeUndefined();
+ });
+
+ it("should set the workspace name in package.json", () => {
+ expect(packageJson.name).toBe("my-workspace");
+ });
+
+ it("should declare npm workspaces in package.json (apps only, no libs)", () => {
+ expect(packageJson.workspaces).toEqual(["apps/*"]);
+ });
+
+ it("does not include @nanoforge-dev/core", () => {
+ expect(packageJson.devDependencies["@nanoforge-dev/core"]).toBeUndefined();
+ });
+
+ it("should not include pnpm config by default", () => {
+ expect(packageJson).not.toHaveProperty("pnpm");
+ });
+
+ it("should resolve real versions for cli/nanoforge/typescript", () => {
+ expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toMatch(RESOLVED_VERSION);
+ expect(packageJson.devDependencies["nanoforge"]).toMatch(RESOLVED_VERSION);
+ expect(packageJson.devDependencies["typescript"]).toMatch(RESOLVED_VERSION);
+ });
+
+ it("resolves typescript to a caret range (or the exact fallback version)", () => {
+ expect(packageJson.devDependencies["typescript"]).toMatch(/^\^?\d+\.\d+\.\d+$/);
+ });
+
+ it("does not jump typescript to its major-7 native rewrite", () => {
+ const version = packageJson.devDependencies["typescript"] as string;
+ expect(version.replace(/^\^/, "").split(".")[0]).toBe("6");
+ });
+
+ it("should declare a workspace-typed nanoforge.config.ts with apps only (no libs)", () => {
+ const content = tree.readContent("/my-workspace/nanoforge.config.ts");
+ expect(content).toContain('type: "workspace"');
+ expect(content).toContain('packages: ["apps/*"]');
+ });
+
+ it("does not set a packageManager (Corepack) field", () => {
+ expect(packageJson.packageManager).toBeUndefined();
+ });
+ });
+
+ describe("with JavaScript", () => {
+ let tree: UnitTestTree;
+
+ beforeAll(async () => {
+ tree = await runner.runSchematic("workspace", {
+ name: "js-workspace",
+ language: "js",
+ });
+ });
+
+ it("should generate JS workspace files", () => {
+ expect(tree.files).toContain("/js-workspace/package.json");
+ expect(tree.files).toContain("/js-workspace/jsconfig.json");
+ expect(tree.files).toContain("/js-workspace/nanoforge.config.js");
+ expect(tree.files).not.toContain("/js-workspace/tsconfig.json");
+ expect(tree.files).not.toContain("/js-workspace/nanoforge.config.ts");
+ });
+
+ it("has the same package.json shape as the TypeScript workspace, minus typescript itself", () => {
+ const packageJson = JSON.parse(tree.readContent("/js-workspace/package.json"));
+ expect(packageJson.devDependencies["typescript"]).toBeUndefined();
+ expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toMatch(RESOLVED_VERSION);
+ expect(packageJson.devDependencies["@nanoforge-dev/core"]).toBeUndefined();
+ expect(packageJson.devDependencies["prettier"]).toBeUndefined();
+ expect(packageJson.version).toBeUndefined();
+ expect(packageJson.author).toBeUndefined();
+ expect(packageJson.description).toBeUndefined();
+ expect(packageJson).not.toHaveProperty("lint-staged");
+ expect(packageJson.workspaces).toEqual(["apps/*"]);
+ });
+
+ it("does not generate any lint tooling files (same as TypeScript)", () => {
+ expect(tree.files).not.toContain("/js-workspace/eslint.config.js");
+ expect(tree.files).not.toContain("/js-workspace/prettier.config.js");
+ expect(tree.files).not.toContain("/js-workspace/.prettierignore");
+ });
+ });
+
+ describe("with custom directory", () => {
+ it("should generate files in the specified directory", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "my-workspace",
+ directory: "custom-dir",
+ });
+ expect(tree.files).toContain("/custom-dir/package.json");
+ });
+ });
+
+ describe("with pnpm", () => {
+ it("should generate pnpm-specific files and no npm workspaces field", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "pnpm-workspace",
+ packageManager: "pnpm",
+ });
+ expect(tree.files).toContain("/pnpm-workspace/pnpm-workspace.yaml");
+ const packageJson = JSON.parse(tree.readContent("/pnpm-workspace/package.json"));
+ // pnpm 10+ no longer reads the "pnpm" field from package.json (it warns and
+ // ignores it); build-script approval now lives in pnpm-workspace.yaml's
+ // `allowBuilds` map instead. See https://pnpm.io/settings.
+ expect(packageJson).not.toHaveProperty("pnpm");
+ expect(packageJson.workspaces).toBeUndefined();
+ expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toMatch(RESOLVED_VERSION);
+ });
+
+ it("should generate a pnpm-workspace.yaml with apps only (no libs)", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "pnpm-packages-workspace",
+ packageManager: "pnpm",
+ });
+ const pnpmWorkspaceYaml = tree.readContent("/pnpm-packages-workspace/pnpm-workspace.yaml");
+ expect(pnpmWorkspaceYaml).toContain('- "apps/*"');
+ expect(pnpmWorkspaceYaml).not.toContain("libs/*");
+ });
+
+ it("always allows bun's build script in pnpm-workspace.yaml's allowBuilds map", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "pnpm-default-allow-builds-workspace",
+ packageManager: "pnpm",
+ });
+ const pnpmWorkspaceYaml = tree.readContent(
+ "/pnpm-default-allow-builds-workspace/pnpm-workspace.yaml",
+ );
+ expect(pnpmWorkspaceYaml).toContain("allowBuilds:");
+ expect(pnpmWorkspaceYaml).toContain("bun: true");
+ });
+
+ it("adds user-requested packages to pnpm-workspace.yaml's allowBuilds map alongside bun", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "pnpm-allow-builds-workspace",
+ packageManager: "pnpm",
+ allowBuilds: ["esbuild"],
+ });
+ const pnpmWorkspaceYaml = tree.readContent(
+ "/pnpm-allow-builds-workspace/pnpm-workspace.yaml",
+ );
+ expect(pnpmWorkspaceYaml).toContain("bun: true");
+ expect(pnpmWorkspaceYaml).toContain("esbuild: true");
+ });
+ });
+
+ describe("with npm", () => {
+ it("always allows bun's build script via package.json's allowScripts", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "npm-allow-scripts-workspace",
+ packageManager: "npm",
+ allowBuilds: ["esbuild"],
+ });
+ expect(tree.files).not.toContain("/npm-allow-scripts-workspace/pnpm-workspace.yaml");
+ const packageJson = JSON.parse(tree.readContent("/npm-allow-scripts-workspace/package.json"));
+ expect(packageJson.allowScripts).toEqual({ bun: true, esbuild: true });
+ });
+ });
+
+ describe("with bun", () => {
+ it("always allows bun's build script via package.json's trustedDependencies", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "bun-trusted-deps-workspace",
+ packageManager: "bun",
+ allowBuilds: ["esbuild"],
+ });
+ expect(tree.files).not.toContain("/bun-trusted-deps-workspace/pnpm-workspace.yaml");
+ const packageJson = JSON.parse(tree.readContent("/bun-trusted-deps-workspace/package.json"));
+ expect(packageJson.trustedDependencies).toEqual(["bun", "esbuild"]);
+ });
+ });
+
+ describe("with yarn", () => {
+ it("should not generate pnpm-workspace.yaml, no script-allow field, and pinned versions", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "yarn-workspace",
+ packageManager: "yarn",
+ });
+ expect(tree.files).not.toContain("/yarn-workspace/pnpm-workspace.yaml");
+ const packageJson = JSON.parse(tree.readContent("/yarn-workspace/package.json"));
+ expect(packageJson).not.toHaveProperty("allowScripts");
+ expect(packageJson).not.toHaveProperty("trustedDependencies");
+ expect(packageJson.workspaces).toEqual(["apps/*"]);
+ expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toMatch(RESOLVED_VERSION);
+ });
+ });
+
+ describe("with docker disabled (default)", () => {
+ it("should not generate a Dockerfile or .dockerignore", async () => {
+ const tree = await runner.runSchematic("workspace", { name: "no-docker-workspace" });
+ expect(tree.files).not.toContain("/no-docker-workspace/Dockerfile");
+ expect(tree.files).not.toContain("/no-docker-workspace/.dockerignore");
+ });
+ });
+
+ describe("with docker enabled", () => {
+ it("should generate Dockerfile and .dockerignore", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: "docker-workspace",
+ docker: true,
+ });
+ expect(tree.files).toContain("/docker-workspace/Dockerfile");
+ expect(tree.files).toContain("/docker-workspace/.dockerignore");
+ });
+
+ describe.each(["npm", "yarn", "pnpm", "bun"] as const)(
+ "with %s package manager",
+ (packageManager) => {
+ it("should branch Dockerfile content on the package manager", async () => {
+ const tree = await runner.runSchematic("workspace", {
+ name: `${packageManager}-docker-workspace`,
+ docker: true,
+ packageManager,
+ });
+ const content = tree.readContent(`/${packageManager}-docker-workspace/Dockerfile`);
+ const installCommand = packageManager === "npm" ? "npm ci" : `${packageManager} install`;
+ expect(content).toContain(`RUN ${installCommand}`);
+ expect(content).toContain(`RUN ${packageManager} run build`);
+ expect(content).toContain(`CMD ["${packageManager}", "run", "start"]`);
+ });
+ },
+ );
+ });
+});
diff --git a/libs/schematics/eslint.config.js b/libs/schematics/eslint.config.js
new file mode 100644
index 0000000..5e9532d
--- /dev/null
+++ b/libs/schematics/eslint.config.js
@@ -0,0 +1,3 @@
+import eslintConfig from "@nanoforge-dev/utils-eslint-config";
+
+export default [...eslintConfig, { ignores: ["src/libs/**/files/**/*"] }];
diff --git a/libs/schematics/package.json b/libs/schematics/package.json
new file mode 100644
index 0000000..4be5c5f
--- /dev/null
+++ b/libs/schematics/package.json
@@ -0,0 +1,109 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@nanoforge-dev/schematics",
+ "version": "2.2.0",
+ "description": "NanoForge Schematics",
+ "keywords": [
+ "nanoforge",
+ "game",
+ "schematics"
+ ],
+ "homepage": "https://github.com/NanoForge-dev/CLI#readme",
+ "bugs": "https://github.com/NanoForge-dev/CLI/issues",
+ "license": "MIT",
+ "contributors": [
+ "Exelo ",
+ "Fexkoser ",
+ "Harper ",
+ "Josephine ",
+ "Leo "
+ ],
+ "files": [
+ "dist"
+ ],
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.cts",
+ "exports": {
+ ".": {
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ },
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "./package.json": "./package.json",
+ "./collection.json": "./dist/collection.json"
+ },
+ "type": "module",
+ "directories": {
+ "lib": "src"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/NanoForge-dev/CLI.git",
+ "directory": "libs/schematics"
+ },
+ "funding": "https://github.com/NanoForge-dev/CLI?sponsor",
+ "scripts": {
+ "build": "tsc --noEmit && tsdown",
+ "postbuild": "pnpm run copy:collection && pnpm run copy:lib",
+ "copy:collection": "cpx src/collection.json dist && cpx 'src/libs/**/schema.json' dist/libs",
+ "copy:lib": "cpx 'src/libs/**/files/**/{.,}*' dist/libs",
+ "lint": "prettier --check . && eslint --format=pretty src",
+ "format": "prettier --write . && eslint --fix --format=pretty src",
+ "prepack": "pnpm run build && pnpm run lint",
+ "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'libs/schematics/*'",
+ "release": "cliff-jumper",
+ "test": "pnpm run test:unit && pnpm run test:e2e",
+ "test:unit": "vitest run -c vitest.config.ts",
+ "test:unit:watch": "vitest -c vitest.config.ts",
+ "test:e2e": "vitest run -c vitest.config.e2e.ts",
+ "test:e2e:watch": "vitest -c vitest.config.e2e.ts",
+ "test:coverage": "pnpm run test:unit --coverage && pnpm run test:e2e --coverage"
+ },
+ "dependencies": {
+ "@angular-devkit/core": "catalog:schematics",
+ "@angular-devkit/schematics": "catalog:schematics",
+ "rxjs": "catalog:schematics"
+ },
+ "devDependencies": {
+ "@angular-devkit/schematics-cli": "catalog:schematics",
+ "@commitlint/cli": "catalog:ci",
+ "@commitlint/config-conventional": "catalog:ci",
+ "@favware/cliff-jumper": "catalog:ci",
+ "@nanoforge-dev/actions": "catalog:ci",
+ "@nanoforge-dev/utils-eslint-config": "catalog:lint",
+ "@nanoforge-dev/utils-prettier-config": "catalog:lint",
+ "@trivago/prettier-plugin-sort-imports": "catalog:lint",
+ "@types/node": "catalog:core",
+ "@vitest/coverage-v8": "catalog:test",
+ "cpx2": "catalog:build",
+ "eslint": "catalog:lint",
+ "husky": "catalog:ci",
+ "lint-staged": "catalog:ci",
+ "prettier": "catalog:lint",
+ "tsdown": "catalog:build",
+ "typescript": "catalog:build",
+ "vitest": "catalog:test"
+ },
+ "packageManager": "pnpm@12.0.0",
+ "engines": {
+ "node": "26"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "schematics": "./dist/collection.json",
+ "lint-staged": {
+ "**": [
+ "prettier --ignore-unknown --write"
+ ],
+ "src/**/*.ts": [
+ "eslint --fix"
+ ]
+ }
+}
diff --git a/libs/schematics/prettier.config.js b/libs/schematics/prettier.config.js
new file mode 100644
index 0000000..121c003
--- /dev/null
+++ b/libs/schematics/prettier.config.js
@@ -0,0 +1,6 @@
+import prettierConfig from "@nanoforge-dev/utils-prettier-config";
+
+export default {
+ ...prettierConfig,
+ importOrder: ["^@utils/(.*)$", "^~/(.*)$", "^[./]"],
+};
diff --git a/libs/schematics/src/collection.json b/libs/schematics/src/collection.json
new file mode 100644
index 0000000..3fdca2d
--- /dev/null
+++ b/libs/schematics/src/collection.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
+ "schematics": {
+ "workspace": {
+ "factory": "./libs/workspace/workspace.factory#main",
+ "description": "Create a NanoForge workspace (monorepo root: config, tooling, package manifest).",
+ "schema": "./libs/workspace/schema.json"
+ },
+ "project": {
+ "factory": "./libs/project/project.factory#main",
+ "description": "Create a NanoForge client or server project under apps/.",
+ "schema": "./libs/project/schema.json"
+ },
+ "component": {
+ "factory": "./libs/component/component.factory#main",
+ "description": "Create a NanoForge component.",
+ "schema": "./libs/component/schema.json"
+ },
+ "system": {
+ "factory": "./libs/system/system.factory#main",
+ "description": "Create a NanoForge system.",
+ "schema": "./libs/system/schema.json"
+ }
+ }
+}
diff --git a/libs/schematics/src/defaults.ts b/libs/schematics/src/defaults.ts
new file mode 100644
index 0000000..f4621c9
--- /dev/null
+++ b/libs/schematics/src/defaults.ts
@@ -0,0 +1,22 @@
+export const DEFAULT_APP_NAME = "nanoforge-app";
+export const DEFAULT_LANGUAGE = "ts";
+export const DEFAULT_PACKAGE_MANAGER = "npm";
+export const DEFAULT_NANOFORGE_DEPENDENCY_VERSION = "^2";
+export const DEFAULT_ENGINE_VERSION = "^2";
+
+/** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */
+export const DEFAULT_CLI_DEPENDENCY_VERSION = "latest";
+
+/**
+ * Fallback used when the registry lookup for `typescript` fails. A specific
+ * version, not a range — non-nanoforge packages are pinned exactly.
+ */
+export const DEFAULT_TYPESCRIPT_VERSION = "6.0.3";
+
+/**
+ * Mirrors pnpm's own `minimumReleaseAge` supply-chain protection: when
+ * resolving a pinned dependency version at generation time, only consider
+ * versions published at least this long ago, so a freshly (and potentially
+ * maliciously) published release isn't picked up immediately.
+ */
+export const MIN_RELEASE_AGE_HOURS = 48;
diff --git a/libs/schematics/src/index.ts b/libs/schematics/src/index.ts
new file mode 100644
index 0000000..8156f2d
--- /dev/null
+++ b/libs/schematics/src/index.ts
@@ -0,0 +1 @@
+export * from "./defaults";
diff --git a/libs/schematics/src/libs/component/component.factory.ts b/libs/schematics/src/libs/component/component.factory.ts
new file mode 100644
index 0000000..da8ba33
--- /dev/null
+++ b/libs/schematics/src/libs/component/component.factory.ts
@@ -0,0 +1,41 @@
+import { type Path, join, normalize, strings } from "@angular-devkit/core";
+import {
+ type Rule,
+ type Source,
+ apply,
+ mergeWith,
+ move,
+ template,
+ url,
+} from "@angular-devkit/schematics";
+
+import { toKebabCase, toPascalCase } from "@utils/formatting";
+
+import { type ComponentOptions } from "./component.options";
+import { type ComponentSchema } from "./component.schema";
+
+const transform = (schema: ComponentSchema): ComponentOptions => {
+ return {
+ part: schema.part,
+ className: toPascalCase(schema.name),
+ fileName: toKebabCase(schema.name),
+ };
+};
+
+const generate = (options: ComponentOptions, path: string, language: "ts" | "js"): Source => {
+ const rules = [
+ template({
+ ...strings,
+ ...options,
+ }),
+ move(normalize(path)),
+ ];
+
+ return apply(url(join("./files" as Path, language)), rules);
+};
+
+export const main = (schema: ComponentSchema): Rule => {
+ const options = transform(schema);
+
+ return mergeWith(generate(options, schema.directory, schema.language));
+};
diff --git a/libs/schematics/src/libs/component/component.options.d.ts b/libs/schematics/src/libs/component/component.options.d.ts
new file mode 100644
index 0000000..270628d
--- /dev/null
+++ b/libs/schematics/src/libs/component/component.options.d.ts
@@ -0,0 +1,16 @@
+export interface ComponentOptions {
+ /**
+ * The part of the application to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * Component class name
+ */
+ className: string;
+
+ /**
+ * Component filename
+ */
+ fileName: string;
+}
diff --git a/libs/schematics/src/libs/component/component.schema.d.ts b/libs/schematics/src/libs/component/component.schema.d.ts
new file mode 100644
index 0000000..4285128
--- /dev/null
+++ b/libs/schematics/src/libs/component/component.schema.d.ts
@@ -0,0 +1,21 @@
+export interface ComponentSchema {
+ /**
+ * Component destination directory
+ */
+ name: string;
+
+ /**
+ * Component destination directory
+ */
+ directory: string;
+
+ /**
+ * The part of the application to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * NanoForge Application language
+ */
+ language: "js" | "ts";
+}
diff --git a/libs/schematics/src/libs/component/files/js/__fileName__.component.js b/libs/schematics/src/libs/component/files/js/__fileName__.component.js
new file mode 100644
index 0000000..10ad6b3
--- /dev/null
+++ b/libs/schematics/src/libs/component/files/js/__fileName__.component.js
@@ -0,0 +1,72 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs-<%= part %>").EditorComponentManifest} EditorComponentManifest
+ */
+
+export class <%= className %>Component {
+ static name = "<%= className %>Component";
+ name = this.constructor.name;
+
+ paramA;
+ paramB;
+ paramC;
+
+ /**
+ * <%= className %> component constructor
+ * @param {string} paramA
+ * @param {number} paramB
+ * @param {boolean} [paramC = false]
+ */
+ constructor(paramA, paramB, paramC = false) {
+ this.paramA = paramA;
+ this.paramB = paramB;
+ this.paramC = paramC;
+ }
+
+ get foo() {
+ return "bar";
+ }
+
+ get paramAOrDefault() {
+ return this.paramC ? this.paramA : "default";
+ }
+
+ addOne() {
+ this.paramB += 1;
+ }
+}
+
+// * Required to generate code
+export default <%= className %>Component.name;
+
+/**
+ * Editor component manifest
+ * * Required for the editor to display the component and generate code
+ * @type {EditorComponentManifest}
+ */
+export const EDITOR_COMPONENT_MANIFEST = {
+ name: "<%= className %>",
+ description: "<%= className %> component description",
+ params: [
+ {
+ type: "string",
+ name: "paramA",
+ description: "Param A description",
+ example: "Example value",
+ },
+ {
+ type: "number",
+ name: "paramB",
+ description: "Param B description",
+ example: 3,
+ },
+ {
+ type: "boolean",
+ name: "paramC",
+ description: "Param C description",
+ example: true,
+ default: false,
+ // Not required because it has a default value
+ optional: true,
+ },
+ ],
+};
diff --git a/libs/schematics/src/libs/component/files/ts/__fileName__.component.ts b/libs/schematics/src/libs/component/files/ts/__fileName__.component.ts
new file mode 100644
index 0000000..2be836f
--- /dev/null
+++ b/libs/schematics/src/libs/component/files/ts/__fileName__.component.ts
@@ -0,0 +1,55 @@
+import { type EditorComponentManifest } from "@nanoforge-dev/ecs-<%= part %>";
+
+export class <%= className %>Component {
+ name = this.constructor.name;
+
+ constructor(
+ public paramA: string,
+ public paramB: number,
+ public paramC: boolean = false,
+ ) {}
+
+ get foo() {
+ return "bar";
+ }
+
+ get paramAOrDefault() {
+ return this.paramC ? this.paramA : "default";
+ }
+
+ addOne() {
+ this.paramB += 1;
+ }
+}
+
+// * Required to generate code
+export default <%= className %>Component.name;
+
+// * Required for the editor to display the component and generate code
+export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = {
+ name: "<%= className %>",
+ description: "<%= className %> component description",
+ params: [
+ {
+ type: "string",
+ name: "paramA",
+ description: "Param A description",
+ example: "Example value",
+ },
+ {
+ type: "number",
+ name: "paramB",
+ description: "Param B description",
+ example: 3,
+ },
+ {
+ type: "boolean",
+ name: "paramC",
+ description: "Param C description",
+ example: true,
+ default: false,
+ // Not required because it has a default value
+ optional: true,
+ },
+ ],
+};
diff --git a/libs/schematics/src/libs/component/schema.json b/libs/schematics/src/libs/component/schema.json
new file mode 100644
index 0000000..a5a6d7f
--- /dev/null
+++ b/libs/schematics/src/libs/component/schema.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "SchematicsNanoForgeComponent",
+ "title": "NanoForge Component Options Schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Component name",
+ "default": "example"
+ },
+ "directory": {
+ "type": "string",
+ "description": "Component destination directory",
+ "default": "."
+ },
+ "part": {
+ "type": "string",
+ "enum": ["client", "server"],
+ "description": "The part of the application to generate"
+ },
+ "language": {
+ "type": "string",
+ "enum": ["ts", "js"],
+ "description": "NanoForge application language",
+ "default": "ts"
+ }
+ },
+ "required": ["part"]
+}
diff --git a/libs/schematics/src/libs/project/files/js/.dockerignore b/libs/schematics/src/libs/project/files/js/.dockerignore
new file mode 100644
index 0000000..6769f3a
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/.dockerignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+.nanoforge/
+.git/
+.env
diff --git a/libs/schematics/src/libs/project/files/js/Dockerfile b/libs/schematics/src/libs/project/files/js/Dockerfile
new file mode 100644
index 0000000..7b95dad
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/Dockerfile
@@ -0,0 +1,36 @@
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
+<% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
+RUN pnpm runtime set node 26 -g
+<% } else { %>FROM node:26-slim AS base
+<% } %><% if (packageManager === "yarn") { %>
+RUN npm install -g yarn
+<% } %>
+WORKDIR /app
+
+FROM base AS deps
+
+<% if (packageManager === "npm") { %>COPY package.json package-lock.json ./
+RUN npm ci
+<% } else if (packageManager === "yarn") { %>COPY package.json yarn.lock ./
+RUN yarn install --frozen-lockfile
+<% } else if (packageManager === "pnpm") { %>COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile
+<% } else { %>COPY package.json bun.lock ./
+RUN bun install --frozen-lockfile
+<% } %>
+FROM deps AS build
+COPY . .
+RUN <%= packageManager %> run build
+
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
+WORKDIR /app
+
+COPY --from=build /app/node_modules ./node_modules
+COPY --from=build /app/dist ./dist
+COPY --from=build /app/package.json ./
+COPY --from=build /app/nanoforge.config.js ./
+<% if (part === "server") { %>
+<% } %><% if (packageManager !== "pnpm") { %>
+USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
+<% } %><% if (part === "client") { %>EXPOSE 3000
+<% } %>CMD ["<%= packageManager %>", "run", "start"]
diff --git a/libs/schematics/src/libs/project/files/js/README.md b/libs/schematics/src/libs/project/files/js/README.md
new file mode 100644
index 0000000..b068141
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/README.md
@@ -0,0 +1,53 @@
+# <%= packageName %>
+
+A [NanoForge](https://nanoforge.eu) <%= part %> app.
+<% if (!workspace) { %>
+## Prerequisites
+
+- Node.js and <%= packageManager %>
+- The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
+
+## Install
+
+```bash
+<%= packageManager %> install
+```
+<% } %>
+## Development
+
+```bash
+nf dev
+```
+
+`nf dev` serves the game and watches your files, so changes are picked up as you work.
+
+## Build & start
+
+```bash
+nf build
+nf start
+```
+
+`nf build` compiles this <%= part %> for production, and `nf start` serves the build.
+<% if (editor) { %>
+This app was generated with editor support. Launch it with:
+
+```bash
+nf editor .
+```
+<% } %><% if (docker) { %>
+A `Dockerfile` is also included to containerize the built app for deployment.
+<% } %>
+## CLI reference
+
+| Command | Description |
+| ------------- | ---------------------------------- |
+| `nf dev` | Develop with hot reload |
+| `nf build` | Build for production |
+| `nf start` | Run a built app |<% if (editor) { %>
+| `nf editor` | Launch the visual editor |<% } %>
+
+## More info
+
+- [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
+- [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge
diff --git a/libs/schematics/src/libs/project/files/js/__dot__gitignore b/libs/schematics/src/libs/project/files/js/__dot__gitignore
new file mode 100644
index 0000000..ba6da87
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/__dot__gitignore
@@ -0,0 +1,134 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# File-based project format
+*.iws
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### NanoForge template
+# Compilation directories
+.nanoforge/client
+.nanoforge/server
diff --git a/libs/schematics/src/libs/project/files/js/assets/__dot__gitkeep b/libs/schematics/src/libs/project/files/js/assets/__dot__gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/libs/schematics/src/libs/project/files/js/jsconfig.json b/libs/schematics/src/libs/project/files/js/jsconfig.json
new file mode 100644
index 0000000..5653881
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/jsconfig.json
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://json.schemastore.org/jsconfig.json",<% if (workspace) { %>
+ "extends": "../../jsconfig.json",<% } else { %>
+ "compilerOptions": {
+ "allowUnreachableCode": <%= !strict %>,
+ "allowUnusedLabels": <%= !strict %>,
+ "exactOptionalPropertyTypes": <%= strict %>,
+ "noFallthroughCasesInSwitch": <%= strict %>,
+ "noImplicitOverride": <%= strict %>,
+ "noImplicitReturns": <%= strict %>,
+ "noPropertyAccessFromIndexSignature": <%= !strict %>,
+ "noUncheckedIndexedAccess": <%= strict %>,
+ "noUnusedLocals": <%= strict %>,
+ "noUnusedParameters": <%= strict %>,
+
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": false,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ "experimentalDecorators": true,
+ "lib": ["ESNext", "esnext.disposable", "DOM"],
+ "target": "ESNext",
+ "useDefineForClassFields": true
+ },<% } %>
+ "include": ["src"],
+ "exclude": ["node_modules", ".nanoforge", "dist"]
+}
diff --git a/libs/schematics/src/libs/project/files/js/nanoforge.config.js b/libs/schematics/src/libs/project/files/js/nanoforge.config.js
new file mode 100644
index 0000000..b42a8a5
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from "nanoforge/config";
+
+export default defineConfig({
+ type: "<%= part %>",
+ entryFile: "src/main.js",
+ editor: {
+ entryFile: "src/main.js",
+ },
+ language: "js",
+});
diff --git a/libs/schematics/src/libs/project/files/js/package.json.template b/libs/schematics/src/libs/project/files/js/package.json.template
new file mode 100644
index 0000000..9124d89
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/package.json.template
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "<%= packageName %>",
+ "scripts": {
+ "dev": "nf dev",
+ "build": "nf build",
+ "start": "nf start"
+ },<% if (part === "server") { %>
+ "dependencies": {
+ "@nanoforge-dev/network": "<%= networkVersion %>"
+ },<% } %>
+ "devDependencies": {
+ "@nanoforge-dev/ecs": "<%= ecsVersion %>",<% if (part === "client") { %>
+ "@nanoforge-dev/graphics-2d": "<%= graphics2dVersion %>",
+ "@nanoforge-dev/input": "<%= inputVersion %>",
+ "@nanoforge-dev/network": "<%= networkVersion %>",<% } %>
+ "nanoforge": "<%= nanoforgeVersion %>"
+ },<% if (packageManager === "npm" && allowBuilds.length > 0) { %>
+ "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
+ "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
+ },<% } %><% if (packageManager === "bun" && allowBuilds.length > 0) { %>
+ "trustedDependencies": [<% allowBuilds.forEach(function (pkg, i) { %>"<%= pkg %>"<% if (i < allowBuilds.length - 1) { %>, <% } %><% }); %>],<% } %>
+ "private": true
+}
diff --git a/libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template b/libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template
new file mode 100644
index 0000000..1e7271b
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template
@@ -0,0 +1,3 @@
+<% if (allowBuilds.length > 0) { %>allowBuilds:<% allowBuilds.forEach(function (pkg) { %>
+ <%= pkg %>: true<% }); %>
+<% } %>
\ No newline at end of file
diff --git a/libs/schematics/src/libs/project/files/js/src/components/drawable-circle-2d.component.js b/libs/schematics/src/libs/project/files/js/src/components/drawable-circle-2d.component.js
new file mode 100644
index 0000000..2f3a3f5
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/components/drawable-circle-2d.component.js
@@ -0,0 +1,404 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs").EditorComponentManifest} EditorComponentManifest
+ * @typedef {import("@nanoforge-dev/graphics-2d").Layer} Layer
+ * @typedef {import("nanoforge").NfFile} NfFile
+ */
+
+import { Circle } from "@nanoforge-dev/graphics-2d";
+
+export class DrawableCircle2D {
+ name = this.constructor.name;
+ shape;
+
+ /** @type {NfFile | undefined} */
+ #fillPatternImage;
+
+ /**
+ * @param {number} [radius]
+ * @param {boolean} [visible]
+ * @param {string} [id]
+ * @param {number} [opacity]
+ * @param {number} [scaleX]
+ * @param {number} [scaleY]
+ * @param {number} [skewX]
+ * @param {number} [skewY]
+ * @param {number} [rotation]
+ * @param {number} [offsetX]
+ * @param {number} [offsetY]
+ * @param {string} [fillColor]
+ * @param {NfFile} [fillPatternImage]
+ * @param {number} [fillPatternX]
+ * @param {number} [fillPatternY]
+ * @param {number} [fillPatternOffsetX]
+ * @param {number} [fillPatternOffsetY]
+ * @param {number} [fillPatternScaleX]
+ * @param {number} [fillPatternScaleY]
+ * @param {number} [fillPatternRotation]
+ * @param {string} [fillPatternRepeat]
+ */
+ constructor(
+ radius,
+ visible,
+ id,
+ opacity,
+ scaleX,
+ scaleY,
+ skewX,
+ skewY,
+ rotation,
+ offsetX,
+ offsetY,
+ fillColor,
+ fillPatternImage,
+ fillPatternX,
+ fillPatternY,
+ fillPatternOffsetX,
+ fillPatternOffsetY,
+ fillPatternScaleX,
+ fillPatternScaleY,
+ fillPatternRotation,
+ fillPatternRepeat,
+ ) {
+ this.#fillPatternImage = fillPatternImage;
+
+ this.shape = new Circle({
+ radius,
+ visible,
+ id,
+ opacity,
+ scaleX,
+ scaleY,
+ skewX,
+ skewY,
+ rotation,
+ offsetX,
+ offsetY,
+ fill: fillColor,
+ fillPatternImage: fillPatternImage
+ ? Object.assign(new Image(), { src: fillPatternImage.path })
+ : undefined,
+ fillPatternX,
+ fillPatternY,
+ fillPatternOffsetX,
+ fillPatternOffsetY,
+ fillPatternScaleX,
+ fillPatternScaleY,
+ fillPatternRotation,
+ fillPatternRepeat,
+ });
+ }
+
+ get radius() {
+ return this.shape.radius();
+ }
+ set radius(v) {
+ this.shape.radius(v);
+ this.#redraw();
+ }
+ get visible() {
+ return this.shape.visible();
+ }
+ set visible(v) {
+ this.shape.visible(v);
+ this.#redraw();
+ }
+ get id() {
+ return this.shape.id();
+ }
+ set id(v) {
+ this.shape.id(v);
+ this.#redraw();
+ }
+ get opacity() {
+ return this.shape.opacity();
+ }
+ set opacity(v) {
+ this.shape.opacity(v);
+ this.#redraw();
+ }
+ get scaleX() {
+ return this.shape.scaleX();
+ }
+ set scaleX(v) {
+ this.shape.scaleX(v);
+ this.#redraw();
+ }
+ get scaleY() {
+ return this.shape.scaleY();
+ }
+ set scaleY(v) {
+ this.shape.scaleY(v);
+ this.#redraw();
+ }
+ get skewX() {
+ return this.shape.skewX();
+ }
+ set skewX(v) {
+ this.shape.skewX(v);
+ this.#redraw();
+ }
+ get skewY() {
+ return this.shape.skewY();
+ }
+ set skewY(v) {
+ this.shape.skewY(v);
+ this.#redraw();
+ }
+ get rotation() {
+ return this.shape.rotation();
+ }
+ set rotation(v) {
+ this.shape.rotation(v);
+ this.#redraw();
+ }
+ get offsetX() {
+ return this.shape.offsetX();
+ }
+ set offsetX(v) {
+ this.shape.offsetX(v);
+ this.#redraw();
+ }
+ get offsetY() {
+ return this.shape.offsetY();
+ }
+ set offsetY(v) {
+ this.shape.offsetY(v);
+ this.#redraw();
+ }
+ get fillColor() {
+ return this.shape.fill().toString();
+ }
+ set fillColor(v) {
+ this.shape.fill(v);
+ this.#redraw();
+ }
+ /** @returns {NfFile | undefined} */
+ get fillPatternImage() {
+ return this.#fillPatternImage;
+ }
+ /** @param {NfFile} v */
+ set fillPatternImage(v) {
+ this.#fillPatternImage = v;
+ this.shape.fillPatternImage(v ? Object.assign(new Image(), { src: v.path }) : undefined);
+ this.#redraw();
+ }
+ get fillPatternX() {
+ return this.shape.fillPatternX();
+ }
+ set fillPatternX(v) {
+ this.shape.fillPatternX(v);
+ this.#redraw();
+ }
+ get fillPatternY() {
+ return this.shape.fillPatternY();
+ }
+ set fillPatternY(v) {
+ this.shape.fillPatternY(v);
+ this.#redraw();
+ }
+ get fillPatternOffsetX() {
+ return this.shape.fillPatternOffsetX();
+ }
+ set fillPatternOffsetX(v) {
+ this.shape.fillPatternOffsetX(v);
+ this.#redraw();
+ }
+ get fillPatternOffsetY() {
+ return this.shape.fillPatternOffsetY();
+ }
+ set fillPatternOffsetY(v) {
+ this.shape.fillPatternOffsetY(v);
+ this.#redraw();
+ }
+ get fillPatternScaleX() {
+ return this.shape.fillPatternScaleX();
+ }
+ set fillPatternScaleX(v) {
+ this.shape.fillPatternScaleX(v);
+ this.#redraw();
+ }
+ get fillPatternScaleY() {
+ return this.shape.fillPatternScaleY();
+ }
+ set fillPatternScaleY(v) {
+ this.shape.fillPatternScaleY(v);
+ this.#redraw();
+ }
+ get fillPatternRotation() {
+ return this.shape.fillPatternRotation();
+ }
+ set fillPatternRotation(v) {
+ this.shape.fillPatternRotation(v);
+ this.#redraw();
+ }
+ get fillPatternRepeat() {
+ return this.shape.fillPatternRepeat();
+ }
+ set fillPatternRepeat(v) {
+ this.shape.fillPatternRepeat(v);
+ this.#redraw();
+ }
+
+ /** @param {Layer} layer */
+ addToLayer(layer) {
+ if (this.shape.getParent() !== layer) layer.add(this.shape);
+ }
+
+ #redraw() {
+ this.shape.getLayer()?.batchDraw();
+ }
+}
+// * Required to generate code
+export default DrawableCircle2D.name;
+
+// * Required for the editor to display the component and generate code
+/** @type {EditorComponentManifest} */
+export const EDITOR_COMPONENT_MANIFEST = {
+ name: "DrawableCircle2D",
+ description: "Graphically drawable circle 2D",
+ params: [
+ {
+ name: "radius",
+ type: "number",
+ description: "Radius of the circle in pixels",
+ default: 10,
+ optional: false,
+ },
+ {
+ name: "visible",
+ type: "boolean",
+ description: "Controls whether the node is rendered",
+ default: true,
+ optional: false,
+ },
+ {
+ name: "id",
+ type: "string",
+ description: "Unique identifier for the node, used with stage.findOne('#id')",
+ optional: true,
+ },
+ {
+ name: "opacity",
+ type: "number",
+ description: "Opacity from 0 (transparent) to 1 (fully opaque)",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "scaleX",
+ type: "number",
+ description: "Horizontal scale factor",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "scaleY",
+ type: "number",
+ description: "Vertical scale factor",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "skewX",
+ type: "number",
+ default: 0,
+ description: "Horizontal skew in radians, shears along the X axis",
+ optional: true,
+ },
+ {
+ name: "skewY",
+ type: "number",
+ default: 0,
+ description: "Vertical skew in radians, shears along the Y axis",
+ optional: true,
+ },
+ {
+ name: "rotation",
+ type: "number",
+ description:
+ "Rotation angle in degrees applied around the offset point (positive values rotate clockwise)",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "offsetX",
+ type: "number",
+ description: "Horizontal offset for the transform origin",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "offsetY",
+ type: "number",
+ description: "Vertical offset for the transform origin",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "fillColor",
+ type: "string",
+ description:
+ 'Solid fill color as a CSS color string (e.g. "red", "#ff0000", "rgba(255,0,0,0.5)"), or a pre-built CanvasGradient object',
+ default: "red",
+ example: "rgb(255,255,255)",
+ optional: true,
+ },
+ {
+ name: "fillPatternImage",
+ type: "asset",
+ description: "Image element to use as a tiling pattern fill",
+ optional: true,
+ },
+ {
+ name: "fillPatternX",
+ type: "number",
+ description:
+ "Horizontal offset of the pattern origin relative to the shape's top-left corner, in pixels",
+ optional: true,
+ },
+ {
+ name: "fillPatternY",
+ type: "number",
+ description:
+ "Vertical offset of the pattern origin relative to the shape's top-left corner, in pixels",
+ optional: true,
+ },
+ {
+ name: "fillPatternOffsetX",
+ type: "number",
+ description: "Horizontally shifts the pattern image within the tile before repeating",
+ optional: true,
+ },
+ {
+ name: "fillPatternOffsetY",
+ type: "number",
+ description: "Vertically shifts the pattern image within the tile before repeating",
+ optional: true,
+ },
+ {
+ name: "fillPatternScaleX",
+ type: "number",
+ description: "Horizontal scale factor for the pattern tile",
+ optional: true,
+ },
+ {
+ name: "fillPatternScaleY",
+ type: "number",
+ description: "Vertical scale factor for the pattern tile",
+ optional: true,
+ },
+ {
+ name: "fillPatternRotation",
+ type: "number",
+ description: "Rotation of the pattern tile in degrees",
+ optional: true,
+ },
+ {
+ name: "fillPatternRepeat",
+ type: "string",
+ description:
+ 'Repeat mode for the pattern. One of "repeat" (default), "repeat-x", "repeat-y", or "no-repeat"',
+ optional: true,
+ },
+ ],
+};
diff --git a/libs/schematics/src/libs/project/files/js/src/components/position-2d.component.js b/libs/schematics/src/libs/project/files/js/src/components/position-2d.component.js
new file mode 100644
index 0000000..82f4b4f
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/components/position-2d.component.js
@@ -0,0 +1,42 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs").EditorComponentManifest} EditorComponentManifest
+ */
+
+export class Position2D {
+ name = this.constructor.name;
+
+ /**
+ * @param {number} x
+ * @param {number} y
+ */
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+}
+
+// * Required to generate code
+export default Position2D.name;
+
+// * Required for the editor to display the component and generate code
+/** @type {EditorComponentManifest} */
+export const EDITOR_COMPONENT_MANIFEST = {
+ name: "Position2D",
+ description: "Position of an entity in a 2 dimensional space in pixel",
+ params: [
+ {
+ type: "number",
+ name: "x",
+ description: "Horizontal position in pixel",
+ example: 4.2,
+ default: 0,
+ },
+ {
+ type: "number",
+ name: "y",
+ description: "Vertical position in pixel",
+ example: 67,
+ default: 0,
+ },
+ ],
+};
diff --git a/libs/schematics/src/libs/project/files/js/src/main.js b/libs/schematics/src/libs/project/files/js/src/main.js
new file mode 100644
index 0000000..68ec50a
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/main.js
@@ -0,0 +1,43 @@
+import { NanoforgeFactory } from "nanoforge";
+import { EcsLibrary } from "@nanoforge-dev/ecs/<%= part %>";<% if (part === "client") { %>
+import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
+import { InputLibrary } from "@nanoforge-dev/input";<% } %>
+import { <%= part === "client" ? "NetworkClientLibrary" : "NetworkServerLibrary" %> } from "@nanoforge-dev/network/<%= part %>";
+<% if (part === "client") { %>
+import { DrawableCircle2D } from "./components/drawable-circle-2d.component";<% } %>
+import { Position2D } from "./components/position-2d.component";<% if (part === "client") { %>
+import { draw2D } from "./systems/draw-2d.system";<% if (hasServer) { %>
+import { positionSync } from "./systems/position-sync.system";<% } %><% } else { %>
+import { move2D } from "./systems/move-2d.system";<% } %>
+
+/**
+ * @param {import("nanoforge").<%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %>} options
+ * @returns {Promise}
+ */
+export const main = async (options) => {
+ const app = NanoforgeFactory.<%= part === "client" ? "createClient" : "createServer" %>({ tickRate: 60 });
+
+ const ecs = new EcsLibrary();
+ app.use(ecs);<% if (part === "client") { %>
+ app.use(new Graphics2DLibrary());
+ app.use(new InputLibrary());<% } %>
+ app.use(new <%= part === "client" ? "NetworkClientLibrary" : "NetworkServerLibrary" %>());
+
+ await app.init(options);
+
+ const registry = ecs.registry;
+<% if (part === "client") { %>
+ const circle = registry.spawnEntity();
+ registry.addComponent(circle, new Position2D(500, 500));
+ registry.addComponent(circle, new DrawableCircle2D(75, true, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, "red"));
+
+ registry.addSystem(draw2D);<% if (hasServer) { %>
+ registry.addSystem(positionSync);<% } %>
+<% } else { %>
+ const entity = registry.spawnEntity();
+ registry.addComponent(entity, new Position2D(500, 500));
+
+ registry.addSystem(move2D);
+<% } %>
+ await app.run();
+};
diff --git a/libs/schematics/src/libs/project/files/js/src/systems/draw-2d.system.js b/libs/schematics/src/libs/project/files/js/src/systems/draw-2d.system.js
new file mode 100644
index 0000000..2312b4c
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/systems/draw-2d.system.js
@@ -0,0 +1,33 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
+ * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
+ * @typedef {import("nanoforge").Context} Context
+ */
+
+import { DrawableCircle2D } from "../components/drawable-circle-2d.component";
+import { Position2D } from "../components/position-2d.component";
+
+/**
+ * @param {Registry} registry
+ * @param {Context} ctx
+ */
+export function draw2D(registry, ctx) {
+ const graphic2d = ctx.graphics;
+
+ const entities = registry.getZipper([Position2D, DrawableCircle2D]);
+
+ entities.forEach(({ Position2D, DrawableCircle2D }) => {
+ DrawableCircle2D.addToLayer(graphic2d.baseLayer);
+ DrawableCircle2D.shape.setPosition(Position2D);
+ });
+}
+// * Required to generate code
+export default draw2D.name;
+
+// * Required for the editor to display the system and generate code
+/** @type {EditorSystemManifest} */
+export const EDITOR_SYSTEM_MANIFEST = {
+ name: "draw2D",
+ description: "Draw every drawable component",
+ dependencies: ["Position2D", "DrawableCircle2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/js/src/systems/move-2d.system.js b/libs/schematics/src/libs/project/files/js/src/systems/move-2d.system.js
new file mode 100644
index 0000000..a619065
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/systems/move-2d.system.js
@@ -0,0 +1,34 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
+ * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
+ * @typedef {import("nanoforge").Context} Context
+ */
+
+import { Position2D } from "../components/position-2d.component";
+
+/**
+ * @param {Registry} registry
+ * @param {Context} ctx
+ */
+export function move2D(registry, ctx) {
+ const network = ctx.network;
+ const entities = registry.getZipper([Position2D]);
+
+ entities.forEach(({ Position2D }) => {
+ Position2D.x += 1;
+
+ network.tcp.sendToEverybody(
+ new TextEncoder().encode(JSON.stringify({ x: Position2D.x, y: Position2D.y })),
+ );
+ });
+}
+// * Required to generate code
+export default move2D.name;
+
+// * Required for the editor to display the system and generate code
+/** @type {EditorSystemManifest} */
+export const EDITOR_SYSTEM_MANIFEST = {
+ name: "move2D",
+ description: "Moves the entity and broadcasts its position to every connected client",
+ dependencies: ["Position2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/js/src/systems/position-sync.system.js b/libs/schematics/src/libs/project/files/js/src/systems/position-sync.system.js
new file mode 100644
index 0000000..d9339e0
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/js/src/systems/position-sync.system.js
@@ -0,0 +1,35 @@
+/**
+ * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
+ * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
+ * @typedef {import("nanoforge").Context} Context
+ */
+
+import { Position2D } from "../components/position-2d.component";
+
+/**
+ * @param {Registry} registry
+ * @param {Context} ctx
+ */
+export function positionSync(registry, ctx) {
+ const network = ctx.network;
+ const packets = network.tcp
+ .getReceivedPackets()
+ .map((packet) => JSON.parse(new TextDecoder().decode(packet)));
+
+ const [entity] = registry.getZipper([Position2D]);
+
+ packets.forEach((packet) => {
+ entity.Position2D.x = packet.x;
+ entity.Position2D.y = packet.y;
+ });
+}
+// * Required to generate code
+export default positionSync.name;
+
+// * Required for the editor to display the system and generate code
+/** @type {EditorSystemManifest} */
+export const EDITOR_SYSTEM_MANIFEST = {
+ name: "positionSync",
+ description: "Overwrites the local entity's position with the latest value received from the server",
+ dependencies: ["Position2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/ts/.dockerignore b/libs/schematics/src/libs/project/files/ts/.dockerignore
new file mode 100644
index 0000000..6769f3a
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/.dockerignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+.nanoforge/
+.git/
+.env
diff --git a/libs/schematics/src/libs/project/files/ts/Dockerfile b/libs/schematics/src/libs/project/files/ts/Dockerfile
new file mode 100644
index 0000000..3f3e585
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/Dockerfile
@@ -0,0 +1,36 @@
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
+<% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
+RUN pnpm runtime set node 26 -g
+<% } else { %>FROM node:26-slim AS base
+<% } %><% if (packageManager === "yarn") { %>
+RUN npm install -g yarn
+<% } %>
+WORKDIR /app
+
+FROM base AS deps
+
+<% if (packageManager === "npm") { %>COPY package.json package-lock.json ./
+RUN npm ci
+<% } else if (packageManager === "yarn") { %>COPY package.json yarn.lock ./
+RUN yarn install --frozen-lockfile
+<% } else if (packageManager === "pnpm") { %>COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile
+<% } else { %>COPY package.json bun.lock ./
+RUN bun install --frozen-lockfile
+<% } %>
+FROM deps AS build
+COPY . .
+RUN <%= packageManager %> run build
+
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
+WORKDIR /app
+
+COPY --from=build /app/node_modules ./node_modules
+COPY --from=build /app/dist ./dist
+COPY --from=build /app/package.json ./
+COPY --from=build /app/nanoforge.config.ts ./
+<% if (part === "server") { %>
+<% } %><% if (packageManager !== "pnpm") { %>
+USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
+<% } %><% if (part === "client") { %>EXPOSE 3000
+<% } %>CMD ["<%= packageManager %>", "run", "start"]
diff --git a/libs/schematics/src/libs/project/files/ts/README.md b/libs/schematics/src/libs/project/files/ts/README.md
new file mode 100644
index 0000000..b068141
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/README.md
@@ -0,0 +1,53 @@
+# <%= packageName %>
+
+A [NanoForge](https://nanoforge.eu) <%= part %> app.
+<% if (!workspace) { %>
+## Prerequisites
+
+- Node.js and <%= packageManager %>
+- The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
+
+## Install
+
+```bash
+<%= packageManager %> install
+```
+<% } %>
+## Development
+
+```bash
+nf dev
+```
+
+`nf dev` serves the game and watches your files, so changes are picked up as you work.
+
+## Build & start
+
+```bash
+nf build
+nf start
+```
+
+`nf build` compiles this <%= part %> for production, and `nf start` serves the build.
+<% if (editor) { %>
+This app was generated with editor support. Launch it with:
+
+```bash
+nf editor .
+```
+<% } %><% if (docker) { %>
+A `Dockerfile` is also included to containerize the built app for deployment.
+<% } %>
+## CLI reference
+
+| Command | Description |
+| ------------- | ---------------------------------- |
+| `nf dev` | Develop with hot reload |
+| `nf build` | Build for production |
+| `nf start` | Run a built app |<% if (editor) { %>
+| `nf editor` | Launch the visual editor |<% } %>
+
+## More info
+
+- [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
+- [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge
diff --git a/libs/schematics/src/libs/project/files/ts/__dot__gitignore b/libs/schematics/src/libs/project/files/ts/__dot__gitignore
new file mode 100644
index 0000000..ba6da87
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/__dot__gitignore
@@ -0,0 +1,134 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# File-based project format
+*.iws
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### NanoForge template
+# Compilation directories
+.nanoforge/client
+.nanoforge/server
diff --git a/libs/schematics/src/libs/project/files/ts/assets/__dot__gitkeep b/libs/schematics/src/libs/project/files/ts/assets/__dot__gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/libs/schematics/src/libs/project/files/ts/nanoforge.config.ts b/libs/schematics/src/libs/project/files/ts/nanoforge.config.ts
new file mode 100644
index 0000000..b05cbcb
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/nanoforge.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from "nanoforge/config";
+
+export default defineConfig({
+ type: "<%= part %>",
+});
diff --git a/libs/schematics/src/libs/project/files/ts/package.json.template b/libs/schematics/src/libs/project/files/ts/package.json.template
new file mode 100644
index 0000000..9124d89
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/package.json.template
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "<%= packageName %>",
+ "scripts": {
+ "dev": "nf dev",
+ "build": "nf build",
+ "start": "nf start"
+ },<% if (part === "server") { %>
+ "dependencies": {
+ "@nanoforge-dev/network": "<%= networkVersion %>"
+ },<% } %>
+ "devDependencies": {
+ "@nanoforge-dev/ecs": "<%= ecsVersion %>",<% if (part === "client") { %>
+ "@nanoforge-dev/graphics-2d": "<%= graphics2dVersion %>",
+ "@nanoforge-dev/input": "<%= inputVersion %>",
+ "@nanoforge-dev/network": "<%= networkVersion %>",<% } %>
+ "nanoforge": "<%= nanoforgeVersion %>"
+ },<% if (packageManager === "npm" && allowBuilds.length > 0) { %>
+ "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
+ "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
+ },<% } %><% if (packageManager === "bun" && allowBuilds.length > 0) { %>
+ "trustedDependencies": [<% allowBuilds.forEach(function (pkg, i) { %>"<%= pkg %>"<% if (i < allowBuilds.length - 1) { %>, <% } %><% }); %>],<% } %>
+ "private": true
+}
diff --git a/libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template b/libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template
new file mode 100644
index 0000000..1e7271b
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template
@@ -0,0 +1,3 @@
+<% if (allowBuilds.length > 0) { %>allowBuilds:<% allowBuilds.forEach(function (pkg) { %>
+ <%= pkg %>: true<% }); %>
+<% } %>
\ No newline at end of file
diff --git a/libs/schematics/src/libs/project/files/ts/src/components/drawable-circle-2d.component.ts b/libs/schematics/src/libs/project/files/ts/src/components/drawable-circle-2d.component.ts
new file mode 100644
index 0000000..30c4454
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/components/drawable-circle-2d.component.ts
@@ -0,0 +1,367 @@
+import type { EditorComponentManifest } from "@nanoforge-dev/ecs";
+import { Circle, type Layer } from "@nanoforge-dev/graphics-2d";
+import type { NfFile } from "nanoforge";
+
+export class DrawableCircle2D {
+ public readonly name: string = this.constructor.name;
+ shape: Circle;
+
+ constructor(
+ radius?: number,
+ visible?: boolean,
+ id?: string,
+ opacity?: number,
+ scaleX?: number,
+ scaleY?: number,
+ skewX?: number,
+ skewY?: number,
+ rotation?: number,
+ offsetX?: number,
+ offsetY?: number,
+ fillColor?: string,
+ private _fillPatternImage?: NfFile,
+ fillPatternX?: number,
+ fillPatternY?: number,
+ fillPatternOffsetX?: number,
+ fillPatternOffsetY?: number,
+ fillPatternScaleX?: number,
+ fillPatternScaleY?: number,
+ fillPatternRotation?: number,
+ fillPatternRepeat?: string,
+ ) {
+ this.shape = new Circle({
+ radius,
+ visible,
+ id,
+ opacity,
+ scaleX,
+ scaleY,
+ skewX,
+ skewY,
+ rotation,
+ offsetX,
+ offsetY,
+ fill: fillColor,
+ fillPatternImage: _fillPatternImage
+ ? Object.assign(new Image(), { src: _fillPatternImage.path })
+ : undefined,
+ fillPatternX,
+ fillPatternY,
+ fillPatternOffsetX,
+ fillPatternOffsetY,
+ fillPatternScaleX,
+ fillPatternScaleY,
+ fillPatternRotation,
+ fillPatternRepeat,
+ });
+ }
+
+ get radius() {
+ return this.shape.radius();
+ }
+ set radius(v: number) {
+ this.shape.radius(v);
+ this.redraw();
+ }
+ get visible() {
+ return this.shape.visible();
+ }
+ set visible(v: boolean) {
+ this.shape.visible(v);
+ this.redraw();
+ }
+ get id() {
+ return this.shape.id();
+ }
+ set id(v: string) {
+ this.shape.id(v);
+ this.redraw();
+ }
+ get opacity() {
+ return this.shape.opacity();
+ }
+ set opacity(v: number) {
+ this.shape.opacity(v);
+ this.redraw();
+ }
+ get scaleX() {
+ return this.shape.scaleX();
+ }
+ set scaleX(v: number) {
+ this.shape.scaleX(v);
+ this.redraw();
+ }
+ get scaleY() {
+ return this.shape.scaleY();
+ }
+ set scaleY(v: number) {
+ this.shape.scaleY(v);
+ this.redraw();
+ }
+ get skewX() {
+ return this.shape.skewX();
+ }
+ set skewX(v: number) {
+ this.shape.skewX(v);
+ this.redraw();
+ }
+ get skewY() {
+ return this.shape.skewY();
+ }
+ set skewY(v: number) {
+ this.shape.skewY(v);
+ this.redraw();
+ }
+ get rotation() {
+ return this.shape.rotation();
+ }
+ set rotation(v: number) {
+ this.shape.rotation(v);
+ this.redraw();
+ }
+ get offsetX() {
+ return this.shape.offsetX();
+ }
+ set offsetX(v: number) {
+ this.shape.offsetX(v);
+ this.redraw();
+ }
+ get offsetY() {
+ return this.shape.offsetY();
+ }
+ set offsetY(v: number) {
+ this.shape.offsetY(v);
+ this.redraw();
+ }
+ get fillColor() {
+ return this.shape.fill().toString();
+ }
+ set fillColor(v: string) {
+ this.shape.fill(v);
+ this.redraw();
+ }
+ get fillPatternImage(): NfFile | undefined {
+ return this._fillPatternImage;
+ }
+ set fillPatternImage(v: NfFile) {
+ this.shape.fillPatternImage(v ? Object.assign(new Image(), { src: v.path }) : undefined);
+ this.redraw();
+ }
+ get fillPatternX() {
+ return this.shape.fillPatternX();
+ }
+ set fillPatternX(v: number) {
+ this.shape.fillPatternX(v);
+ this.redraw();
+ }
+ get fillPatternY() {
+ return this.shape.fillPatternY();
+ }
+ set fillPatternY(v: number) {
+ this.shape.fillPatternY(v);
+ this.redraw();
+ }
+ get fillPatternOffsetX() {
+ return this.shape.fillPatternOffsetX();
+ }
+ set fillPatternOffsetX(v: number) {
+ this.shape.fillPatternOffsetX(v);
+ this.redraw();
+ }
+ get fillPatternOffsetY() {
+ return this.shape.fillPatternOffsetY();
+ }
+ set fillPatternOffsetY(v: number) {
+ this.shape.fillPatternOffsetY(v);
+ this.redraw();
+ }
+ get fillPatternScaleX() {
+ return this.shape.fillPatternScaleX();
+ }
+ set fillPatternScaleX(v: number) {
+ this.shape.fillPatternScaleX(v);
+ this.redraw();
+ }
+ get fillPatternScaleY() {
+ return this.shape.fillPatternScaleY();
+ }
+ set fillPatternScaleY(v: number) {
+ this.shape.fillPatternScaleY(v);
+ this.redraw();
+ }
+ get fillPatternRotation() {
+ return this.shape.fillPatternRotation();
+ }
+ set fillPatternRotation(v: number) {
+ this.shape.fillPatternRotation(v);
+ this.redraw();
+ }
+ get fillPatternRepeat() {
+ return this.shape.fillPatternRepeat();
+ }
+ set fillPatternRepeat(v: string) {
+ this.shape.fillPatternRepeat(v);
+ this.redraw();
+ }
+
+ public addToLayer(layer: Layer): void {
+ if (this.shape.getParent() !== layer) layer.add(this.shape);
+ }
+
+ private redraw() {
+ this.shape.getLayer()?.batchDraw();
+ }
+}
+// * Required to generate code
+export default DrawableCircle2D.name;
+
+// * Required for the editor to display the component and generate code
+export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = {
+ name: "DrawableCircle2D",
+ description: "Graphically drawable circle 2D",
+ params: [
+ {
+ name: "radius",
+ type: "number",
+ description: "Radius of the circle in pixels",
+ default: 10,
+ optional: false,
+ },
+ {
+ name: "visible",
+ type: "boolean",
+ description: "Controls whether the node is rendered",
+ default: true,
+ optional: false,
+ },
+ {
+ name: "id",
+ type: "string",
+ description: "Unique identifier for the node, used with stage.findOne('#id')",
+ optional: true,
+ },
+ {
+ name: "opacity",
+ type: "number",
+ description: "Opacity from 0 (transparent) to 1 (fully opaque)",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "scaleX",
+ type: "number",
+ description: "Horizontal scale factor",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "scaleY",
+ type: "number",
+ description: "Vertical scale factor",
+ default: 1,
+ optional: true,
+ },
+ {
+ name: "skewX",
+ type: "number",
+ default: 0,
+ description: "Horizontal skew in radians, shears along the X axis",
+ optional: true,
+ },
+ {
+ name: "skewY",
+ type: "number",
+ default: 0,
+ description: "Vertical skew in radians, shears along the Y axis",
+ optional: true,
+ },
+ {
+ name: "rotation",
+ type: "number",
+ description:
+ "Rotation angle in degrees applied around the offset point (positive values rotate clockwise)",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "offsetX",
+ type: "number",
+ description: "Horizontal offset for the transform origin",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "offsetY",
+ type: "number",
+ description: "Vertical offset for the transform origin",
+ default: 0,
+ optional: true,
+ },
+ {
+ name: "fillColor",
+ type: "string",
+ description:
+ 'Solid fill color as a CSS color string (e.g. "red", "#ff0000", "rgba(255,0,0,0.5)"), or a pre-built CanvasGradient object',
+ default: "red",
+ example: "rgb(255,255,255)",
+ optional: true,
+ },
+ {
+ name: "fillPatternImage",
+ type: "asset",
+ description: "Image element to use as a tiling pattern fill",
+ optional: true,
+ },
+ {
+ name: "fillPatternX",
+ type: "number",
+ description:
+ "Horizontal offset of the pattern origin relative to the shape's top-left corner, in pixels",
+ optional: true,
+ },
+ {
+ name: "fillPatternY",
+ type: "number",
+ description:
+ "Vertical offset of the pattern origin relative to the shape's top-left corner, in pixels",
+ optional: true,
+ },
+ {
+ name: "fillPatternOffsetX",
+ type: "number",
+ description: "Horizontally shifts the pattern image within the tile before repeating",
+ optional: true,
+ },
+ {
+ name: "fillPatternOffsetY",
+ type: "number",
+ description: "Vertically shifts the pattern image within the tile before repeating",
+ optional: true,
+ },
+ {
+ name: "fillPatternScaleX",
+ type: "number",
+ description: "Horizontal scale factor for the pattern tile",
+ optional: true,
+ },
+ {
+ name: "fillPatternScaleY",
+ type: "number",
+ description: "Vertical scale factor for the pattern tile",
+ optional: true,
+ },
+ {
+ name: "fillPatternRotation",
+ type: "number",
+ description: "Rotation of the pattern tile in degrees",
+ optional: true,
+ },
+ {
+ name: "fillPatternRepeat",
+ type: "string",
+ description:
+ 'Repeat mode for the pattern. One of "repeat" (default), "repeat-x", "repeat-y", or "no-repeat"',
+ optional: true,
+ },
+ ],
+};
\ No newline at end of file
diff --git a/libs/schematics/src/libs/project/files/ts/src/components/position-2d.component.ts b/libs/schematics/src/libs/project/files/ts/src/components/position-2d.component.ts
new file mode 100644
index 0000000..149cec5
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/components/position-2d.component.ts
@@ -0,0 +1,35 @@
+import type { EditorComponentManifest } from "@nanoforge-dev/ecs";
+
+export class Position2D {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Position2D.name;
+
+// * Required for the editor to display the component and generate code
+export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = {
+ name: "Position2D",
+ description: "Position of an entity in a 2 dimensional space in pixel",
+ params: [
+ {
+ type: "number",
+ name: "x",
+ description: "Horizontal position in pixel",
+ example: 4.2,
+ default: 0,
+ },
+ {
+ type: "number",
+ name: "y",
+ description: "Vertical position in pixel",
+ example: 67,
+ default: 0,
+ },
+ ],
+};
diff --git a/libs/schematics/src/libs/project/files/ts/src/main.ts b/libs/schematics/src/libs/project/files/ts/src/main.ts
new file mode 100644
index 0000000..2402c78
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/main.ts
@@ -0,0 +1,39 @@
+import { NanoforgeFactory, type <%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %> } from "nanoforge";
+import { EcsLibrary } from "@nanoforge-dev/ecs/<%= part %>";<% if (part === "client") { %>
+import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
+import { InputLibrary } from "@nanoforge-dev/input";<% } %>
+import { <%= part === "client" ? "NetworkClientLibrary" : "NetworkServerLibrary" %> } from "@nanoforge-dev/network/<%= part %>";
+<% if (part === "client") { %>
+import { DrawableCircle2D } from "./components/drawable-circle-2d.component";<% } %>
+import { Position2D } from "./components/position-2d.component";<% if (part === "client") { %>
+import { draw2D } from "./systems/draw-2d.system";<% if (hasServer) { %>
+import { positionSync } from "./systems/position-sync.system";<% } %><% } else { %>
+import { move2D } from "./systems/move-2d.system";<% } %>
+
+export const main = async (options: <%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %>): Promise => {
+ const app = NanoforgeFactory.<%= part === "client" ? "createClient" : "createServer" %>({ tickRate: 60 });
+
+ const ecs = new EcsLibrary();
+ app.use(ecs);<% if (part === "client") { %>
+ app.use(new Graphics2DLibrary());
+ app.use(new InputLibrary());<% } %>
+ app.use(new <%= part === "client" ? "NetworkClientLibrary" : "NetworkServerLibrary" %>());
+
+ await app.init(options);
+
+ const registry = ecs.registry;
+<% if (part === "client") { %>
+ const circle = registry.spawnEntity();
+ registry.addComponent(circle, new Position2D(500, 500));
+ registry.addComponent(circle, new DrawableCircle2D(75, true, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, "red"));
+
+ registry.addSystem(draw2D);<% if (hasServer) { %>
+ registry.addSystem(positionSync);<% } %>
+<% } else { %>
+ const entity = registry.spawnEntity();
+ registry.addComponent(entity, new Position2D(500, 500));
+
+ registry.addSystem(move2D);
+<% } %>
+ await app.run();
+};
diff --git a/libs/schematics/src/libs/project/files/ts/src/systems/draw-2d.system.ts b/libs/schematics/src/libs/project/files/ts/src/systems/draw-2d.system.ts
new file mode 100644
index 0000000..eb075b6
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/systems/draw-2d.system.ts
@@ -0,0 +1,25 @@
+import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
+import { type Context } from "nanoforge";
+
+import { DrawableCircle2D } from "../components/drawable-circle-2d.component";
+import { Position2D } from "../components/position-2d.component";
+
+export function draw2D(registry: Registry, ctx: Context) {
+ const graphic2d = ctx.graphics;
+
+ const entities = registry.getZipper([Position2D, DrawableCircle2D]);
+
+ entities.forEach(({ Position2D, DrawableCircle2D }) => {
+ DrawableCircle2D.addToLayer(graphic2d.baseLayer);
+ DrawableCircle2D.shape.setPosition(Position2D);
+ });
+}
+// * Required to generate code
+export default draw2D.name;
+
+// * Required for the editor to display the system and generate code
+export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
+ name: "draw2D",
+ description: "Draw every drawable component",
+ dependencies: ["Position2D", "DrawableCircle2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/ts/src/systems/move-2d.system.ts b/libs/schematics/src/libs/project/files/ts/src/systems/move-2d.system.ts
new file mode 100644
index 0000000..a980282
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/systems/move-2d.system.ts
@@ -0,0 +1,26 @@
+import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
+import { type Context } from "nanoforge";
+
+import { Position2D } from "../components/position-2d.component";
+
+export function move2D(registry: Registry, ctx: Context) {
+ const network = ctx.network;
+ const entities = registry.getZipper([Position2D]);
+
+ entities.forEach(({ Position2D }) => {
+ Position2D.x += 1;
+
+ network.tcp.sendToEverybody(
+ new TextEncoder().encode(JSON.stringify({ x: Position2D.x, y: Position2D.y })),
+ );
+ });
+}
+// * Required to generate code
+export default move2D.name;
+
+// * Required for the editor to display the system and generate code
+export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
+ name: "move2D",
+ description: "Moves the entity and broadcasts its position to every connected client",
+ dependencies: ["Position2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/ts/src/systems/position-sync.system.ts b/libs/schematics/src/libs/project/files/ts/src/systems/position-sync.system.ts
new file mode 100644
index 0000000..09fc85f
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/src/systems/position-sync.system.ts
@@ -0,0 +1,27 @@
+import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
+import { type Context } from "nanoforge";
+
+import { Position2D } from "../components/position-2d.component";
+
+export function positionSync(registry: Registry, ctx: Context) {
+ const network = ctx.network;
+ const packets = network.tcp
+ .getReceivedPackets()
+ .map((packet) => JSON.parse(new TextDecoder().decode(packet)) as { x: number; y: number });
+
+ const [entity] = registry.getZipper([Position2D]);
+
+ packets.forEach((packet) => {
+ entity.Position2D.x = packet.x;
+ entity.Position2D.y = packet.y;
+ });
+}
+// * Required to generate code
+export default positionSync.name;
+
+// * Required for the editor to display the system and generate code
+export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
+ name: "positionSync",
+ description: "Overwrites the local entity's position with the latest value received from the server",
+ dependencies: ["Position2D"],
+};
diff --git a/libs/schematics/src/libs/project/files/ts/tsconfig.json b/libs/schematics/src/libs/project/files/ts/tsconfig.json
new file mode 100644
index 0000000..70b5f64
--- /dev/null
+++ b/libs/schematics/src/libs/project/files/ts/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",<% if (workspace) { %>
+ "extends": "../../tsconfig.json",<% } else { %>
+ // Mapped from https://www.typescriptlang.org/tsconfig
+ "compilerOptions": {
+ // Type Checking
+ "allowUnreachableCode": <%= !strict %>,
+ "allowUnusedLabels": <%= !strict %>,
+ "exactOptionalPropertyTypes": <%= strict %>,
+ "noFallthroughCasesInSwitch": <%= strict %>,
+ "noImplicitOverride": <%= strict %>,
+ "noImplicitReturns": <%= strict %>,
+ "noPropertyAccessFromIndexSignature": <%= !strict %>,
+ "noUncheckedIndexedAccess": <%= strict %>,
+ "noUnusedLocals": <%= strict %>,
+ "noUnusedParameters": <%= strict %>,
+ "strict": <%= strict %>,
+
+ // Modules
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ // Interop Constraints
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": false,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ // Language and Environment
+ "experimentalDecorators": true,
+ "lib": ["ESNext", "esnext.disposable", "DOM"],
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ },<% } %>
+ "include": ["src"],
+ "exclude": ["node_modules", ".nanoforge", "dist"]
+}
diff --git a/libs/schematics/src/libs/project/project.factory.ts b/libs/schematics/src/libs/project/project.factory.ts
new file mode 100644
index 0000000..dff4730
--- /dev/null
+++ b/libs/schematics/src/libs/project/project.factory.ts
@@ -0,0 +1,133 @@
+import { type Path, join, normalize, strings } from "@angular-devkit/core";
+import {
+ type Rule,
+ type Source,
+ apply,
+ filter,
+ mergeWith,
+ move,
+ renameTemplateFiles,
+ template,
+ url,
+} from "@angular-devkit/schematics";
+
+import { toKebabCase } from "@utils/formatting";
+import { fetchTrustedVersion } from "@utils/registry";
+
+import {
+ DEFAULT_APP_NAME,
+ DEFAULT_LANGUAGE,
+ DEFAULT_NANOFORGE_DEPENDENCY_VERSION,
+ DEFAULT_PACKAGE_MANAGER,
+} from "~/defaults";
+
+import { type ProjectOptions } from "./project.options";
+import { type ProjectSchema } from "./project.schema";
+
+interface DepVersions {
+ ecs: string;
+ graphics2d: string;
+ input: string;
+ network: string;
+ nanoforge: string;
+}
+
+const resolveDepVersions = async (part: ProjectSchema["part"]): Promise => {
+ const isClient = part === "client";
+
+ const [nanoforge, ecs, network, graphics2d, input] = await Promise.all([
+ fetchTrustedVersion("nanoforge", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),
+ fetchTrustedVersion("@nanoforge-dev/ecs", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),
+ fetchTrustedVersion("@nanoforge-dev/network", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),
+ isClient
+ ? fetchTrustedVersion("@nanoforge-dev/graphics-2d", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)
+ : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,
+ isClient
+ ? fetchTrustedVersion("@nanoforge-dev/input", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)
+ : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,
+ ]);
+
+ return {
+ ecs,
+ graphics2d,
+ input,
+ network,
+ nanoforge,
+ };
+};
+
+const transform = async (schema: ProjectSchema): Promise => {
+ const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;
+ const workspace = schema.workspace ?? true;
+ const depVersions = await resolveDepVersions(schema.part);
+
+ const name = schema.name ?? schema.part;
+ const packageName = schema.workspaceName
+ ? `${toKebabCase(schema.workspaceName)}-${toKebabCase(name)}`
+ : toKebabCase(schema.name ?? DEFAULT_APP_NAME);
+
+ return {
+ part: schema.part,
+ appClass: schema.part === "client" ? "NanoforgeClient" : "NanoforgeServer",
+ packageName,
+ language: schema.language ?? DEFAULT_LANGUAGE,
+ strict: schema.strict ?? true,
+ packageManager,
+ workspace,
+ ecsVersion: depVersions.ecs,
+ graphics2dVersion: depVersions.graphics2d,
+ inputVersion: depVersions.input,
+ networkVersion: depVersions.network,
+ nanoforgeVersion: depVersions.nanoforge,
+ hasServer: schema.hasServer,
+ docker: schema.docker ?? false,
+ editor: schema.editor ?? false,
+ allowBuilds: workspace ? [] : ["bun"],
+ };
+};
+
+const generate = (options: ProjectOptions, path: string): Source => {
+ return apply(url(join("./files" as Path, options.language)), [
+ template({
+ dot: ".",
+ ...strings,
+ ...options,
+ }),
+ filter(
+ (path) =>
+ (options.docker && !options.workspace) ||
+ !["Dockerfile", ".dockerignore"].includes(path.split("/").at(-1) ?? ""),
+ ),
+ filter(
+ (path) =>
+ (options.packageManager === "pnpm" && options.allowBuilds.length > 0) ||
+ !(path.split("/").at(-1) ?? "").startsWith("pnpm-workspace.yaml"),
+ ),
+ filter((path) => {
+ const filename = path.split("/").at(-1) ?? "";
+ if (
+ filename.startsWith("drawable-circle-2d.component") ||
+ filename.startsWith("draw-2d.system")
+ ) {
+ return options.part === "client";
+ }
+ if (filename.startsWith("move-2d.system")) {
+ return options.part === "server";
+ }
+ if (filename.startsWith("position-sync.system")) {
+ return options.part === "client" && options.hasServer;
+ }
+ return true;
+ }),
+ renameTemplateFiles(),
+ move(normalize(path)),
+ ]);
+};
+
+export const main = (schema: ProjectSchema): Rule => {
+ return async () => {
+ const options = await transform(schema);
+
+ return mergeWith(generate(options, schema.directory));
+ };
+};
diff --git a/libs/schematics/src/libs/project/project.options.d.ts b/libs/schematics/src/libs/project/project.options.d.ts
new file mode 100644
index 0000000..ad4a4ab
--- /dev/null
+++ b/libs/schematics/src/libs/project/project.options.d.ts
@@ -0,0 +1,82 @@
+export interface ProjectOptions {
+ /**
+ * The type of project to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * Fully-qualified Nanoforge app class name for the generated part,
+ * used in JSDoc type annotations for JavaScript projects
+ */
+ appClass: string;
+
+ /**
+ * The derived package.json name (from workspaceName and part)
+ */
+ packageName: string;
+
+ /**
+ * NanoForge project language
+ */
+ language: "js" | "ts";
+
+ /**
+ * With strict mode (TypeScript or JavaScript but only on compatible editor)
+ */
+ strict: boolean;
+
+ /**
+ * The used package manager
+ */
+ packageManager: "npm" | "yarn" | "pnpm" | "bun";
+
+ /**
+ * Whether this project is generated inside an existing NanoForge workspace
+ */
+ workspace: boolean;
+
+ /**
+ * Resolved version/protocol for the @nanoforge-dev/ecs dependency (aliased
+ * to @nanoforge-dev/ecs-lib for registry lookups; see project.factory.ts)
+ */
+ ecsVersion: string;
+
+ /** Resolved version/protocol for @nanoforge-dev/graphics-2d (client only) */
+ graphics2dVersion: string;
+
+ /** Resolved version/protocol for @nanoforge-dev/input (client only) */
+ inputVersion: string;
+
+ /**
+ * Resolved version/protocol for the @nanoforge-dev/network dependency
+ * (aliased to @nanoforge-dev/network-client or -server for registry
+ * lookups depending on `part`; see project.factory.ts)
+ */
+ networkVersion: string;
+
+ /** Resolved version/protocol for the nanoforge engine package */
+ nanoforgeVersion: string;
+
+ /**
+ * Only meaningful when `part` is "client": whether the client syncs its
+ * entity position from a companion server instead of owning it locally.
+ */
+ hasServer: boolean;
+
+ /**
+ * Generate a Dockerfile for the project. Ignored when `workspace` is true - a project
+ * inside a workspace is built and run by the workspace's own Dockerfile instead.
+ */
+ docker: boolean;
+
+ /**
+ * Add editor dependencies
+ */
+ editor: boolean;
+
+ /**
+ * Package names allowed to run install/build scripts. Only rendered when
+ * `workspace` is false — see {@link ProjectSchema.allowBuilds}.
+ */
+ allowBuilds: string[];
+}
diff --git a/libs/schematics/src/libs/project/project.schema.d.ts b/libs/schematics/src/libs/project/project.schema.d.ts
new file mode 100644
index 0000000..5f00a9c
--- /dev/null
+++ b/libs/schematics/src/libs/project/project.schema.d.ts
@@ -0,0 +1,67 @@
+export interface ProjectSchema {
+ /**
+ * The type of project to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * Name of the parent workspace, used to derive the project package name.
+ * Optional - when omitted, `name` is used as the whole package name
+ * instead of being appended to the workspace name.
+ */
+ workspaceName?: string | null;
+
+ /**
+ * Name used to derive the project package name. When `workspaceName` is
+ * set, the package name is `${workspaceName}-${name}` (replacing the
+ * previous `${workspaceName}-${part}` suffix). When `workspaceName` is
+ * absent, `name` becomes the whole package name. Defaults to `part`.
+ */
+ name?: string | null;
+
+ /**
+ * Full destination path for this project (e.g. "apps/client",
+ * "packages/player-client"). The caller is responsible for including any
+ * part-specific segment - it is not appended automatically.
+ */
+ directory: string;
+
+ /**
+ * NanoForge project language
+ */
+ language: "js" | "ts";
+
+ /**
+ * With strict mode
+ */
+ strict: boolean;
+
+ /**
+ * The used package manager
+ */
+ packageManager: "npm" | "yarn" | "pnpm" | "bun";
+
+ /**
+ * Whether this project is generated inside an existing NanoForge workspace
+ */
+ workspace: boolean;
+
+ /**
+ * Generate a Dockerfile for the project. Ignored when `workspace` is true - a project
+ * inside a workspace is built and run by the workspace's own Dockerfile instead.
+ */
+ docker: boolean;
+
+ /**
+ * Only meaningful when `part` is "client": whether the client syncs its
+ * entity position from a companion server instead of owning it locally.
+ * A server project always simulates and broadcasts its own position,
+ * regardless of this flag.
+ */
+ hasServer: boolean;
+
+ /**
+ * Add editor dependencies
+ */
+ editor: boolean;
+}
diff --git a/libs/schematics/src/libs/project/schema.json b/libs/schematics/src/libs/project/schema.json
new file mode 100644
index 0000000..cc933df
--- /dev/null
+++ b/libs/schematics/src/libs/project/schema.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "SchematicsNanoForgeProject",
+ "title": "NanoForge Project Options Schema",
+ "type": "object",
+ "properties": {
+ "part": {
+ "type": "string",
+ "enum": ["client", "server"],
+ "description": "The type of project to generate"
+ },
+ "workspaceName": {
+ "type": ["string", "null"],
+ "description": "Name of the parent workspace, used to derive the project package name. Optional - when omitted, `name` is used as the whole package name instead of being appended to the workspace name.",
+ "default": null
+ },
+ "name": {
+ "type": ["string", "null"],
+ "description": "Name used to derive the project package name. When `workspaceName` is set, the package name is `${workspaceName}-${name}` (replacing the previous `${workspaceName}-${part}` suffix). When `workspaceName` is absent, `name` becomes the whole package name. Defaults to `part`.",
+ "default": null
+ },
+ "directory": {
+ "type": "string",
+ "description": "Full destination path for this project (e.g. \"apps/client\", \"packages/player-client\"). The caller is responsible for including any part-specific segment - it is not appended automatically.",
+ "default": "apps"
+ },
+ "language": {
+ "type": "string",
+ "enum": ["ts", "js"],
+ "description": "NanoForge project language",
+ "default": "ts"
+ },
+ "strict": {
+ "type": "boolean",
+ "description": "With strict mode",
+ "default": true
+ },
+ "packageManager": {
+ "type": "string",
+ "enum": ["npm", "yarn", "pnpm", "bun"],
+ "description": "The used package manager",
+ "default": "npm"
+ },
+ "workspace": {
+ "type": "boolean",
+ "description": "Whether this project is generated inside an existing NanoForge workspace",
+ "default": true
+ },
+ "docker": {
+ "type": "boolean",
+ "description": "Generate a Dockerfile for the project. Ignored when `workspace` is true - a project inside a workspace is built and run by the workspace's own Dockerfile instead.",
+ "default": false
+ },
+ "initFunctions": {
+ "type": "boolean",
+ "description": "Add init functions to the project",
+ "default": false
+ },
+ "hasServer": {
+ "type": "boolean",
+ "description": "Only meaningful when part is \"client\": whether the client syncs its entity position from a companion server instead of owning it locally. A server project always simulates and broadcasts its own position, regardless of this flag.",
+ "default": false
+ },
+ "editor": {
+ "type": "boolean",
+ "description": "Add editor dependencies",
+ "default": false
+ },
+ "libs": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Relative paths to shared libs this project depends on",
+ "default": []
+ },
+ "allowBuilds": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Package names allowed to run install/build scripts. Only applies to a standalone project (workspace: false); ignored otherwise since the workspace root already handles it.",
+ "default": []
+ }
+ },
+ "required": ["part"]
+}
diff --git a/libs/schematics/src/libs/system/files/js/__fileName__.system.js b/libs/schematics/src/libs/system/files/js/__fileName__.system.js
new file mode 100644
index 0000000..64240a7
--- /dev/null
+++ b/libs/schematics/src/libs/system/files/js/__fileName__.system.js
@@ -0,0 +1,44 @@
+/**
+ * @typedef {import("@nanoforge-dev/common").Context} Context
+ * @typedef {import("@nanoforge-dev/ecs-<%= part %>").EditorSystemManifest} EditorSystemManifest
+ * @typedef {import("@nanoforge-dev/ecs-<%= part %>").Registry} Registry
+ */
+
+import { ExampleComponent } from "../components/example.component";
+
+/**
+ * <%= functionName %> system
+ * This system end the game when paramB reaches 0 for any entity with ExampleComponent
+ * @param {Registry} registry - ECS registry instance
+ * @param {Context} ctx - Nanoforge <%= part %> instance
+ */
+export const <%= functionName %>System = (registry, ctx) => {
+ const entities = registry.getZipper([ExampleComponent]);
+
+ entities.forEach((entity) => {
+ if (entity.ExampleComponent.paramA === "end") {
+ ctx.app.setIsRunning(false);
+ return;
+ }
+
+ if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end";
+
+ if (entity.ExampleComponent.paramB >= 0)
+ entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1;
+ });
+};
+
+// * Required to generate code
+export default <%= functionName %>System.name;
+
+/**
+ * Editor component manifest
+ * * Required for the editor to display the system and generate code
+ * @type {EditorSystemManifest}
+ */
+export const EDITOR_SYSTEM_MANIFEST = {
+ name: "<%= functionName %>",
+ description:
+ "This system end the game when paramB reaches 0 for any entity with ExampleComponent",
+ dependencies: ["ExampleComponent"],
+};
diff --git a/libs/schematics/src/libs/system/files/ts/__fileName__.system.ts b/libs/schematics/src/libs/system/files/ts/__fileName__.system.ts
new file mode 100644
index 0000000..d1f9ce6
--- /dev/null
+++ b/libs/schematics/src/libs/system/files/ts/__fileName__.system.ts
@@ -0,0 +1,31 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type EditorSystemManifest, type Registry } from "@nanoforge-dev/ecs-<%= part %>";
+
+import { ExampleComponent } from "../components/example.component";
+
+export const <%= functionName %>System = (registry: Registry, ctx: Context) => {
+ const entities = registry.getZipper([ExampleComponent]);
+
+ entities.forEach((entity) => {
+ if (entity.ExampleComponent.paramA === "end") {
+ ctx.app.setIsRunning(false);
+ return;
+ }
+
+ if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end";
+
+ if (entity.ExampleComponent.paramB >= 0)
+ entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1;
+ });
+};
+
+// * Required to generate code
+export default <%= functionName %>System.name;
+
+// * Required for the editor to display the system and generate code
+export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
+ name: "<%= functionName %>",
+ description:
+ "This system end the game when paramB reaches 0 for any entity with ExampleComponent",
+ dependencies: ["ExampleComponent"],
+};
diff --git a/libs/schematics/src/libs/system/schema.json b/libs/schematics/src/libs/system/schema.json
new file mode 100644
index 0000000..0578ac2
--- /dev/null
+++ b/libs/schematics/src/libs/system/schema.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "SchematicsNanoForgeSystem",
+ "title": "NanoForge System Options Schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "System name",
+ "default": "example"
+ },
+ "directory": {
+ "type": "string",
+ "description": "System destination directory",
+ "default": "."
+ },
+ "part": {
+ "type": "string",
+ "enum": ["client", "server"],
+ "description": "The part of the application to generate"
+ },
+ "language": {
+ "type": "string",
+ "enum": ["ts", "js"],
+ "description": "NanoForge application language",
+ "default": "ts"
+ }
+ },
+ "required": ["part"]
+}
diff --git a/libs/schematics/src/libs/system/system.factory.ts b/libs/schematics/src/libs/system/system.factory.ts
new file mode 100644
index 0000000..93fabde
--- /dev/null
+++ b/libs/schematics/src/libs/system/system.factory.ts
@@ -0,0 +1,41 @@
+import { type Path, join, normalize, strings } from "@angular-devkit/core";
+import {
+ type Rule,
+ type Source,
+ apply,
+ mergeWith,
+ move,
+ template,
+ url,
+} from "@angular-devkit/schematics";
+
+import { toCamelCase, toKebabCase } from "@utils/formatting";
+
+import { type SystemOptions } from "./system.options";
+import { type SystemSchema } from "./system.schema";
+
+const transform = (schema: SystemSchema): SystemOptions => {
+ return {
+ part: schema.part,
+ functionName: toCamelCase(schema.name),
+ fileName: toKebabCase(schema.name),
+ };
+};
+
+const generate = (options: SystemOptions, path: string, language: "ts" | "js"): Source => {
+ const rules = [
+ template({
+ ...strings,
+ ...options,
+ }),
+ move(normalize(path)),
+ ];
+
+ return apply(url(join("./files" as Path, language)), rules);
+};
+
+export const main = (schema: SystemSchema): Rule => {
+ const options = transform(schema);
+
+ return mergeWith(generate(options, schema.directory, schema.language));
+};
diff --git a/libs/schematics/src/libs/system/system.options.d.ts b/libs/schematics/src/libs/system/system.options.d.ts
new file mode 100644
index 0000000..c16d2e6
--- /dev/null
+++ b/libs/schematics/src/libs/system/system.options.d.ts
@@ -0,0 +1,16 @@
+export interface SystemOptions {
+ /**
+ * The part of the application to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * System function name
+ */
+ functionName: string;
+
+ /**
+ * System filename
+ */
+ fileName: string;
+}
diff --git a/libs/schematics/src/libs/system/system.schema.d.ts b/libs/schematics/src/libs/system/system.schema.d.ts
new file mode 100644
index 0000000..19c7bfe
--- /dev/null
+++ b/libs/schematics/src/libs/system/system.schema.d.ts
@@ -0,0 +1,21 @@
+export interface SystemSchema {
+ /**
+ * System destination directory
+ */
+ name: string;
+
+ /**
+ * System destination directory
+ */
+ directory: string;
+
+ /**
+ * The part of the application to generate
+ */
+ part: "client" | "server";
+
+ /**
+ * NanoForge Application language
+ */
+ language: "js" | "ts";
+}
diff --git a/libs/schematics/src/libs/workspace/files/js/.dockerignore b/libs/schematics/src/libs/workspace/files/js/.dockerignore
new file mode 100644
index 0000000..1c13727
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/.dockerignore
@@ -0,0 +1,5 @@
+**/node_modules/
+**/dist/
+**/.nanoforge/
+**/.env
+.git/
diff --git a/libs/schematics/src/libs/workspace/files/js/Dockerfile b/libs/schematics/src/libs/workspace/files/js/Dockerfile
new file mode 100644
index 0000000..c28346e
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/Dockerfile
@@ -0,0 +1,36 @@
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
+<% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
+RUN pnpm runtime set node 26 -g
+<% } else { %>FROM node:26-slim AS base
+<% } %><% if (packageManager === "yarn") { %>
+RUN npm install -g yarn
+<% } %>
+WORKDIR /app
+
+FROM base AS deps
+<% if (packageManager === "pnpm") { %>
+COPY pnpm-lock.yaml ./
+RUN pnpm fetch
+
+FROM deps AS build
+COPY . .
+RUN pnpm install --frozen-lockfile --offline
+<% } else if (packageManager === "npm") { %>
+FROM deps AS build
+COPY . .
+RUN npm ci
+<% } else { %>
+FROM deps AS build
+COPY . .
+RUN <%= packageManager %> install --frozen-lockfile
+<% } %>
+RUN <%= packageManager %> run build
+
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
+WORKDIR /app
+
+COPY --from=build /app .
+<% if (packageManager !== "pnpm") { %>
+USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
+<% } %>EXPOSE 3000
+CMD ["<%= packageManager %>", "run", "start"]
diff --git a/libs/schematics/src/libs/workspace/files/js/README.md b/libs/schematics/src/libs/workspace/files/js/README.md
new file mode 100644
index 0000000..cf7bc11
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/README.md
@@ -0,0 +1,51 @@
+# <%= name %>
+
+A [NanoForge](https://nanoforge.eu) game workspace.
+
+## Prerequisites
+
+- Node.js and <%= packageManager %>
+- The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
+
+## Install
+
+```bash
+<%= packageManager %> install
+```
+
+## Structure
+
+This is the root of your NanoForge monorepo. `nanoforge.config.js` lists the app packages, and each
+client or server app lives under `apps/` (generated separately with the `project` schematic).
+
+## Development
+
+```bash
+nf dev -r
+```
+
+`-r` runs the command across every app in the workspace. `nf dev` serves the game and watches your
+files, so changes are picked up as you work. Target a single app instead with `-d apps/`.
+
+## Build & start
+
+```bash
+nf build -r
+nf start -r
+```
+
+`nf build` compiles every app for production, and `nf start` serves the build.
+
+## CLI reference
+
+| Command | Description |
+| ---------- | ------------------------------------- |
+| `nf dev` | Develop with hot reload |
+| `nf build` | Build for production |
+| `nf start` | Run a built app |
+| `-r` | Apply the command to every app |
+
+## More info
+
+- [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
+- [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge
diff --git a/libs/schematics/src/libs/workspace/files/js/__dot__env b/libs/schematics/src/libs/workspace/files/js/__dot__env
new file mode 100644
index 0000000..75b4595
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/__dot__env
@@ -0,0 +1,6 @@
+NANOFORGE_CLIENT_SERVER_TCP_PORT=4444
+NANOFORGE_CLIENT_SERVER_UDP_PORT=4445
+NANOFORGE_CLIENT_SERVER_ADDRESS=127.0.0.1
+
+NANOFORGE_SERVER_LISTENING_TCP_PORT=4444
+NANOFORGE_SERVER_LISTENING_UDP_PORT=4445
diff --git a/libs/schematics/src/libs/workspace/files/js/__dot__gitignore b/libs/schematics/src/libs/workspace/files/js/__dot__gitignore
new file mode 100644
index 0000000..6857e7d
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/__dot__gitignore
@@ -0,0 +1,137 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# File-based project format
+*.iws
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# Turborepo
+.turbo/
+
+### NanoForge template
+# Build/compilation output
+.nanoforge/
+dist/
diff --git a/libs/schematics/src/libs/workspace/files/js/jsconfig.json b/libs/schematics/src/libs/workspace/files/js/jsconfig.json
new file mode 100644
index 0000000..4b7a35e
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/jsconfig.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json.schemastore.org/jsconfig.json",
+ "compilerOptions": {
+ "allowUnreachableCode": <%= !strict %>,
+ "allowUnusedLabels": <%= !strict %>,
+ "exactOptionalPropertyTypes": <%= strict %>,
+ "noFallthroughCasesInSwitch": <%= strict %>,
+ "noImplicitOverride": <%= strict %>,
+ "noImplicitReturns": <%= strict %>,
+ "noPropertyAccessFromIndexSignature": <%= !strict %>,
+ "noUncheckedIndexedAccess": <%= strict %>,
+ "noUnusedLocals": <%= strict %>,
+ "noUnusedParameters": <%= strict %>,
+
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": false,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ "experimentalDecorators": true,
+ "lib": ["ESNext", "esnext.disposable", "DOM"],
+ "target": "ESNext",
+ "useDefineForClassFields": true
+ },
+ "exclude": ["node_modules", ".nanoforge", "dist"]
+}
diff --git a/libs/schematics/src/libs/workspace/files/js/nanoforge.config.js b/libs/schematics/src/libs/workspace/files/js/nanoforge.config.js
new file mode 100644
index 0000000..1bbf8d4
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/nanoforge.config.js
@@ -0,0 +1,6 @@
+import { defineConfig } from "nanoforge/config";
+
+export default defineConfig({
+ type: "workspace",
+ packages: ["apps/*"],
+});
diff --git a/libs/schematics/src/libs/workspace/files/js/package.json.template b/libs/schematics/src/libs/workspace/files/js/package.json.template
new file mode 100644
index 0000000..cfd2c9a
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/package.json.template
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "<%= name %>",
+ "scripts": {
+ "dev": "nf dev -r",
+ "build": "nf build -r",
+ "start": "nf start -r"
+ },
+ "devDependencies": {
+ "@nanoforge-dev/cli": "<%= cliVersion %>",
+ "nanoforge": "<%= engineVersion %>"
+ },<% if (packageManager !== "pnpm") { %>
+ "workspaces": ["apps/*"],<% } %><% if (packageManager === "npm" && allowBuilds.length > 0) { %>
+ "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
+ "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
+ },<% } %><% if (packageManager === "bun" && allowBuilds.length > 0) { %>
+ "trustedDependencies": [<% allowBuilds.forEach(function (pkg, i) { %>"<%= pkg %>"<% if (i < allowBuilds.length - 1) { %>, <% } %><% }); %>],<% } %>
+ "private": true
+}
diff --git a/libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template b/libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template
new file mode 100644
index 0000000..34f3b9c
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template
@@ -0,0 +1,6 @@
+packages:
+ - "apps/*"
+<% if (allowBuilds.length > 0) { %>
+allowBuilds:<% allowBuilds.forEach(function (pkg) { %>
+ <%= pkg %>: true<% }); %>
+<% } %>
\ No newline at end of file
diff --git a/libs/schematics/src/libs/workspace/files/ts/.dockerignore b/libs/schematics/src/libs/workspace/files/ts/.dockerignore
new file mode 100644
index 0000000..1c13727
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/.dockerignore
@@ -0,0 +1,5 @@
+**/node_modules/
+**/dist/
+**/.nanoforge/
+**/.env
+.git/
diff --git a/libs/schematics/src/libs/workspace/files/ts/Dockerfile b/libs/schematics/src/libs/workspace/files/ts/Dockerfile
new file mode 100644
index 0000000..c28346e
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/Dockerfile
@@ -0,0 +1,36 @@
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
+<% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
+RUN pnpm runtime set node 26 -g
+<% } else { %>FROM node:26-slim AS base
+<% } %><% if (packageManager === "yarn") { %>
+RUN npm install -g yarn
+<% } %>
+WORKDIR /app
+
+FROM base AS deps
+<% if (packageManager === "pnpm") { %>
+COPY pnpm-lock.yaml ./
+RUN pnpm fetch
+
+FROM deps AS build
+COPY . .
+RUN pnpm install --frozen-lockfile --offline
+<% } else if (packageManager === "npm") { %>
+FROM deps AS build
+COPY . .
+RUN npm ci
+<% } else { %>
+FROM deps AS build
+COPY . .
+RUN <%= packageManager %> install --frozen-lockfile
+<% } %>
+RUN <%= packageManager %> run build
+
+<% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
+WORKDIR /app
+
+COPY --from=build /app .
+<% if (packageManager !== "pnpm") { %>
+USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
+<% } %>EXPOSE 3000
+CMD ["<%= packageManager %>", "run", "start"]
diff --git a/libs/schematics/src/libs/workspace/files/ts/README.md b/libs/schematics/src/libs/workspace/files/ts/README.md
new file mode 100644
index 0000000..7a6c2c6
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/README.md
@@ -0,0 +1,51 @@
+# <%= name %>
+
+A [NanoForge](https://nanoforge.eu) game workspace.
+
+## Prerequisites
+
+- Node.js and <%= packageManager %>
+- The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
+
+## Install
+
+```bash
+<%= packageManager %> install
+```
+
+## Structure
+
+This is the root of your NanoForge monorepo. `nanoforge.config.ts` lists the app packages, and each
+client or server app lives under `apps/` (generated separately with the `project` schematic).
+
+## Development
+
+```bash
+nf dev -r
+```
+
+`-r` runs the command across every app in the workspace. `nf dev` serves the game and watches your
+files, so changes are picked up as you work. Target a single app instead with `-d apps/`.
+
+## Build & start
+
+```bash
+nf build -r
+nf start -r
+```
+
+`nf build` compiles every app for production, and `nf start` serves the build.
+
+## CLI reference
+
+| Command | Description |
+| ---------- | ------------------------------------- |
+| `nf dev` | Develop with hot reload |
+| `nf build` | Build for production |
+| `nf start` | Run a built app |
+| `-r` | Apply the command to every app |
+
+## More info
+
+- [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
+- [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge
diff --git a/libs/schematics/src/libs/workspace/files/ts/__dot__env b/libs/schematics/src/libs/workspace/files/ts/__dot__env
new file mode 100644
index 0000000..75b4595
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/__dot__env
@@ -0,0 +1,6 @@
+NANOFORGE_CLIENT_SERVER_TCP_PORT=4444
+NANOFORGE_CLIENT_SERVER_UDP_PORT=4445
+NANOFORGE_CLIENT_SERVER_ADDRESS=127.0.0.1
+
+NANOFORGE_SERVER_LISTENING_TCP_PORT=4444
+NANOFORGE_SERVER_LISTENING_UDP_PORT=4445
diff --git a/libs/schematics/src/libs/workspace/files/ts/__dot__gitignore b/libs/schematics/src/libs/workspace/files/ts/__dot__gitignore
new file mode 100644
index 0000000..fc0f862
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/__dot__gitignore
@@ -0,0 +1,134 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# File-based project format
+*.iws
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### NanoForge template
+# Build/compilation output
+.nanoforge/
+dist/
diff --git a/libs/schematics/src/libs/workspace/files/ts/nanoforge.config.ts b/libs/schematics/src/libs/workspace/files/ts/nanoforge.config.ts
new file mode 100644
index 0000000..1bbf8d4
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/nanoforge.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from "nanoforge/config";
+
+export default defineConfig({
+ type: "workspace",
+ packages: ["apps/*"],
+});
diff --git a/libs/schematics/src/libs/workspace/files/ts/package.json.template b/libs/schematics/src/libs/workspace/files/ts/package.json.template
new file mode 100644
index 0000000..29b7e4b
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/package.json.template
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "<%= name %>",
+ "scripts": {
+ "dev": "nf dev -r",
+ "build": "nf build -r",
+ "start": "nf start -r"
+ },
+ "devDependencies": {
+ "@nanoforge-dev/cli": "<%= cliVersion %>",
+ "nanoforge": "<%= engineVersion %>",
+ "typescript": "<%= typescriptVersion %>"
+ },<% if (packageManager !== "pnpm") { %>
+ "workspaces": ["apps/*"],<% } %><% if (packageManager === "npm" && allowBuilds.length > 0) { %>
+ "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
+ "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
+ },<% } %><% if (packageManager === "bun" && allowBuilds.length > 0) { %>
+ "trustedDependencies": [<% allowBuilds.forEach(function (pkg, i) { %>"<%= pkg %>"<% if (i < allowBuilds.length - 1) { %>, <% } %><% }); %>],<% } %>
+ "private": true
+}
diff --git a/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template b/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template
new file mode 100644
index 0000000..34f3b9c
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template
@@ -0,0 +1,6 @@
+packages:
+ - "apps/*"
+<% if (allowBuilds.length > 0) { %>
+allowBuilds:<% allowBuilds.forEach(function (pkg) { %>
+ <%= pkg %>: true<% }); %>
+<% } %>
\ No newline at end of file
diff --git a/libs/schematics/src/libs/workspace/files/ts/tsconfig.json b/libs/schematics/src/libs/workspace/files/ts/tsconfig.json
new file mode 100644
index 0000000..5156549
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/files/ts/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ // Mapped from https://www.typescriptlang.org/tsconfig
+ "compilerOptions": {
+ // Type Checking
+ "allowUnreachableCode": <%= !strict %>,
+ "allowUnusedLabels": <%= !strict %>,
+ "exactOptionalPropertyTypes": <%= strict %>,
+ "noFallthroughCasesInSwitch": <%= strict %>,
+ "noImplicitOverride": <%= strict %>,
+ "noImplicitReturns": <%= strict %>,
+ "noPropertyAccessFromIndexSignature": <%= !strict %>,
+ "noUncheckedIndexedAccess": <%= strict %>,
+ "noUnusedLocals": <%= strict %>,
+ "noUnusedParameters": <%= strict %>,
+ "strict": <%= strict %>,
+
+ // Modules
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ // Interop Constraints
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": false,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ // Language and Environment
+ "experimentalDecorators": true,
+ "lib": ["ESNext", "esnext.disposable", "DOM"],
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ },
+ "exclude": ["node_modules", ".nanoforge", "dist"]
+}
diff --git a/libs/schematics/src/libs/workspace/schema.json b/libs/schematics/src/libs/workspace/schema.json
new file mode 100644
index 0000000..9d843e6
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/schema.json
@@ -0,0 +1,45 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "SchematicsNanoForgeWorkspace",
+ "title": "NanoForge Workspace Options Schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the workspace",
+ "default": "nanoforge-app"
+ },
+ "directory": {
+ "type": "string",
+ "description": "NanoForge workspace destination directory (defaults to the workspace name when omitted)"
+ },
+ "language": {
+ "type": "string",
+ "enum": ["ts", "js"],
+ "description": "NanoForge workspace language",
+ "default": "ts"
+ },
+ "strict": {
+ "type": "boolean",
+ "description": "With strict mode (TypeScript or JavaScript but only on compatible editor)",
+ "default": true
+ },
+ "packageManager": {
+ "type": "string",
+ "enum": ["npm", "yarn", "pnpm", "bun"],
+ "description": "NanoForge workspace package manager",
+ "default": "npm"
+ },
+ "allowBuilds": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "Package names allowed to run install/build scripts, in addition to `bun` which is always allowed (@nanoforge-dev/cli depends on it and it has its own install script). Written to pnpm-workspace.yaml (pnpm), package.json's allowScripts (npm) or trustedDependencies (bun). Ignored for yarn.",
+ "default": []
+ },
+ "docker": {
+ "type": "boolean",
+ "description": "Generate a Dockerfile building every app in the workspace into one image",
+ "default": false
+ }
+ }
+}
diff --git a/libs/schematics/src/libs/workspace/workspace.factory.ts b/libs/schematics/src/libs/workspace/workspace.factory.ts
new file mode 100644
index 0000000..61cb091
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/workspace.factory.ts
@@ -0,0 +1,84 @@
+import { type Path, join, normalize, strings } from "@angular-devkit/core";
+import {
+ type Rule,
+ type Source,
+ apply,
+ filter,
+ mergeWith,
+ move,
+ renameTemplateFiles,
+ template,
+ url,
+} from "@angular-devkit/schematics";
+
+import { fetchTrustedVersions } from "@utils/registry";
+
+import {
+ DEFAULT_CLI_DEPENDENCY_VERSION,
+ DEFAULT_ENGINE_VERSION,
+ DEFAULT_PACKAGE_MANAGER,
+ DEFAULT_TYPESCRIPT_VERSION,
+} from "~/defaults";
+
+import { type WorkspaceOptions } from "./workspace.options";
+import { type WorkspaceSchema } from "./workspace.schema";
+
+/**
+ * bun ships as a postinstall-driven binary download (its own package.json
+ * declares `"postinstall": "node install.js"`), and @nanoforge-dev/cli
+ * depends on it unconditionally, so every generated workspace needs it
+ * approved to run its build script regardless of what the caller passes.
+ */
+const ALWAYS_ALLOWED_BUILDS = ["bun"];
+
+const transform = async (schema: WorkspaceSchema): Promise => {
+ const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;
+
+ const versions = await fetchTrustedVersions({
+ "@nanoforge-dev/cli": { fallback: DEFAULT_CLI_DEPENDENCY_VERSION },
+ nanoforge: { fallback: DEFAULT_ENGINE_VERSION },
+ typescript: { fallback: DEFAULT_TYPESCRIPT_VERSION, major: 6 },
+ });
+
+ return {
+ name: schema.name,
+ engineVersion: versions.nanoforge,
+ cliVersion: versions["@nanoforge-dev/cli"],
+ typescriptVersion: versions.typescript,
+ language: schema.language,
+ strict: schema.strict,
+ packageManager,
+ allowBuilds: Array.from(new Set([...ALWAYS_ALLOWED_BUILDS, ...(schema.allowBuilds ?? [])])),
+ docker: schema.docker ?? false,
+ };
+};
+
+const generate = (options: WorkspaceOptions, path: string): Source => {
+ return apply(url(join("./files" as Path, options.language)), [
+ template({
+ dot: ".",
+ ...strings,
+ ...options,
+ }),
+ filter(
+ (path) =>
+ options.packageManager === "pnpm" ||
+ !(path.split("/").at(-1) ?? "").startsWith("pnpm-workspace.yaml"),
+ ),
+ filter(
+ (path) =>
+ options.docker || !["Dockerfile", ".dockerignore"].includes(path.split("/").at(-1) ?? ""),
+ ),
+ renameTemplateFiles(),
+ move(normalize(path)),
+ ]);
+};
+
+export const main = (schema: WorkspaceSchema): Rule => {
+ return async () => {
+ const options = await transform(schema);
+ const path = schema.directory ?? options.name;
+
+ return mergeWith(generate(options, path));
+ };
+};
diff --git a/libs/schematics/src/libs/workspace/workspace.options.d.ts b/libs/schematics/src/libs/workspace/workspace.options.d.ts
new file mode 100644
index 0000000..bcc9408
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/workspace.options.d.ts
@@ -0,0 +1,44 @@
+export interface WorkspaceOptions {
+ /**
+ * NanoForge workspace name
+ */
+ name: string;
+
+ /**
+ * Version of engine packages (nanoforge, @nanoforge-dev/cli),
+ * resolved to the newest registry release that respects MIN_RELEASE_AGE_HOURS.
+ */
+ engineVersion: string;
+
+ /** Resolved @nanoforge-dev/cli version/protocol (see {@link engineVersion}). */
+ cliVersion: string;
+
+ /** Resolved typescript version (TypeScript language only). */
+ typescriptVersion: string;
+
+ /**
+ * NanoForge workspace language
+ */
+ language: "js" | "ts";
+
+ /**
+ * With strict mode (TypeScript or JavaScript but only on compatible editor)
+ */
+ strict: boolean;
+
+ /**
+ * The used package manager
+ */
+ packageManager: "npm" | "yarn" | "pnpm" | "bun";
+
+ /**
+ * Package names allowed to run install/build scripts (always includes `bun`).
+ * See {@link WorkspaceSchema.allowBuilds}.
+ */
+ allowBuilds: string[];
+
+ /**
+ * Generate a Dockerfile building every app in the workspace into one image
+ */
+ docker: boolean;
+}
diff --git a/libs/schematics/src/libs/workspace/workspace.schema.d.ts b/libs/schematics/src/libs/workspace/workspace.schema.d.ts
new file mode 100644
index 0000000..fd61e17
--- /dev/null
+++ b/libs/schematics/src/libs/workspace/workspace.schema.d.ts
@@ -0,0 +1,41 @@
+export interface WorkspaceSchema {
+ /**
+ * NanoForge workspace name
+ */
+ name: string;
+
+ /**
+ * NanoForge workspace destination directory
+ */
+ directory: string;
+
+ /**
+ * NanoForge workspace language
+ */
+ language: "js" | "ts";
+
+ /**
+ * With strict mode (TypeScript or JavaScript but only on compatible editor)
+ */
+ strict: boolean;
+
+ /**
+ * The used package manager
+ */
+ packageManager: "npm" | "yarn" | "pnpm" | "bun";
+
+ /**
+ * Package names allowed to run install/build scripts. Written to
+ * pnpm-workspace.yaml's `allowBuilds` map (pnpm), package.json's
+ * `allowScripts` map (npm) or `trustedDependencies` array (bun). Ignored
+ * for yarn. `bun` is always included in addition to this list since
+ * @nanoforge-dev/cli transitively depends on it and it has its own
+ * install script.
+ */
+ allowBuilds: string[];
+
+ /**
+ * Generate a Dockerfile building every app in the workspace into one image
+ */
+ docker: boolean;
+}
diff --git a/libs/schematics/src/utils/formatting.spec.ts b/libs/schematics/src/utils/formatting.spec.ts
new file mode 100644
index 0000000..dfaf4b4
--- /dev/null
+++ b/libs/schematics/src/utils/formatting.spec.ts
@@ -0,0 +1,101 @@
+import { describe, expect, it } from "vitest";
+
+import { toCamelCase, toKebabCase, toPascalCase } from "./formatting";
+
+describe("toKebabCase", () => {
+ it("should convert camelCase to kebab-case", () => {
+ expect(toKebabCase("myVariableName")).toBe("my-variable-name");
+ });
+
+ it("should convert PascalCase to kebab-case", () => {
+ expect(toKebabCase("MyVariableName")).toBe("my-variable-name");
+ });
+
+ it("should convert spaces to hyphens", () => {
+ expect(toKebabCase("my variable name")).toBe("my-variable-name");
+ });
+
+ it("should convert underscores to hyphens", () => {
+ expect(toKebabCase("my_variable_name")).toBe("my-variable-name");
+ });
+
+ it("should return an already kebab-case string unchanged", () => {
+ expect(toKebabCase("my-variable-name")).toBe("my-variable-name");
+ });
+
+ it("should handle a single word", () => {
+ expect(toKebabCase("hello")).toBe("hello");
+ });
+
+ it("should handle an empty string", () => {
+ expect(toKebabCase("")).toBe("");
+ });
+});
+
+describe("toPascalCase", () => {
+ it("should convert camelCase to PascalCase", () => {
+ expect(toPascalCase("myVariableName")).toBe("MyVariableName");
+ });
+
+ it("should convert kebab-case to PascalCase", () => {
+ expect(toPascalCase("my-variable-name")).toBe("MyVariableName");
+ });
+
+ it("should convert snake_case to PascalCase", () => {
+ expect(toPascalCase("my_variable_name")).toBe("MyVariableName");
+ });
+
+ it("should convert spaces to PascalCase", () => {
+ expect(toPascalCase("my variable name")).toBe("MyVariableName");
+ });
+
+ it("should handle already PascalCase string", () => {
+ expect(toPascalCase("MyVariableName")).toBe("MyVariableName");
+ });
+
+ it("should handle a single word", () => {
+ expect(toPascalCase("hello")).toBe("Hello");
+ });
+
+ it("should handle an empty string", () => {
+ expect(toPascalCase("")).toBe("");
+ });
+
+ it("should handle SCREAMING_SNAKE_CASE", () => {
+ expect(toPascalCase("MY_VARIABLE_NAME")).toBe("MyVariableName");
+ });
+});
+
+describe("toCamelCase", () => {
+ it("should convert kebab-case to camelCase", () => {
+ expect(toCamelCase("my-variable-name")).toBe("myVariableName");
+ });
+
+ it("should convert snake_case to camelCase", () => {
+ expect(toCamelCase("my_variable_name")).toBe("myVariableName");
+ });
+
+ it("should convert spaces to camelCase", () => {
+ expect(toCamelCase("my variable name")).toBe("myVariableName");
+ });
+
+ it("should handle already camelCase string", () => {
+ expect(toCamelCase("myVariableName")).toBe("myVariableName");
+ });
+
+ it("should convert PascalCase to camelCase", () => {
+ expect(toCamelCase("MyVariableName")).toBe("myVariableName");
+ });
+
+ it("should handle a single word", () => {
+ expect(toCamelCase("hello")).toBe("hello");
+ });
+
+ it("should handle an empty string", () => {
+ expect(toCamelCase("")).toBe("");
+ });
+
+ it("should handle SCREAMING_SNAKE_CASE", () => {
+ expect(toCamelCase("MY_VARIABLE_NAME")).toBe("myVariableName");
+ });
+});
diff --git a/libs/schematics/src/utils/formatting.ts b/libs/schematics/src/utils/formatting.ts
new file mode 100644
index 0000000..d86154e
--- /dev/null
+++ b/libs/schematics/src/utils/formatting.ts
@@ -0,0 +1,30 @@
+const toWords = (str: string): string[] => {
+ return str
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
+ .replace(/[-_]+/g, " ")
+ .trim()
+ .split(/\s+/)
+ .filter(Boolean);
+};
+
+export const toKebabCase = (str: string): string => {
+ return toWords(str)
+ .map((word) => word.toLowerCase())
+ .join("-");
+};
+
+export const toPascalCase = (str: string): string => {
+ return toWords(str)
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+ .join("");
+};
+
+export const toCamelCase = (str: string): string => {
+ const words = toWords(str);
+ return words
+ .map((word, i) =>
+ i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
+ )
+ .join("");
+};
diff --git a/libs/schematics/src/utils/name.spec.ts b/libs/schematics/src/utils/name.spec.ts
new file mode 100644
index 0000000..350acbf
--- /dev/null
+++ b/libs/schematics/src/utils/name.spec.ts
@@ -0,0 +1,23 @@
+import { basename } from "node:path";
+import { describe, expect, it } from "vitest";
+
+import { resolvePackageName } from "./name";
+
+describe("resolvePackageName", () => {
+ it("should return the base filename for a simple path", () => {
+ expect(resolvePackageName("my-package")).toBe("my-package");
+ });
+
+ it("should return the base filename from a nested path", () => {
+ expect(resolvePackageName("some/path/my-package")).toBe("my-package");
+ });
+
+ it("should return scoped package name when path starts with @", () => {
+ expect(resolvePackageName("@scope/my-package")).toBe("@scope/my-package");
+ });
+
+ it("should return cwd basename when path is '.'", () => {
+ const expected = basename(process.cwd());
+ expect(resolvePackageName(".")).toBe(expected);
+ });
+});
diff --git a/libs/schematics/src/utils/name.ts b/libs/schematics/src/utils/name.ts
new file mode 100644
index 0000000..649feea
--- /dev/null
+++ b/libs/schematics/src/utils/name.ts
@@ -0,0 +1,12 @@
+import { basename, parse } from "path";
+
+export const resolvePackageName = (path: string): string => {
+ const { base: baseFilename, dir: dirname } = parse(path);
+ if (baseFilename === ".") {
+ return basename(process.cwd());
+ }
+ if (dirname.match(/^@[^\s]/)) {
+ return `${dirname}/${baseFilename}`;
+ }
+ return baseFilename;
+};
diff --git a/libs/schematics/src/utils/object.spec.ts b/libs/schematics/src/utils/object.spec.ts
new file mode 100644
index 0000000..aa4bd25
--- /dev/null
+++ b/libs/schematics/src/utils/object.spec.ts
@@ -0,0 +1,66 @@
+import { describe, expect, it } from "vitest";
+
+import { deepMerge, isObject } from "./object";
+
+describe("isObject", () => {
+ it("should return true for a plain object", () => {
+ expect(isObject({})).toBe(true);
+ });
+
+ it("should return true for an object with properties", () => {
+ expect(isObject({ a: 1 })).toBe(true);
+ });
+
+ it("should return false for an array", () => {
+ expect(isObject([1, 2, 3])).toBe(false);
+ });
+
+ it("should return falsy for null", () => {
+ expect(isObject(null)).toBeFalsy();
+ });
+
+ it("should return falsy for undefined", () => {
+ expect(isObject(undefined)).toBeFalsy();
+ });
+
+ it("should return false for a string", () => {
+ expect(isObject("hello")).toBe(false);
+ });
+
+ it("should return false for a number", () => {
+ expect(isObject(42)).toBe(false);
+ });
+});
+
+describe("deepMerge", () => {
+ it("should merge flat objects", () => {
+ expect(deepMerge({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
+ });
+
+ it("should override values from source", () => {
+ expect(deepMerge({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
+ });
+
+ it("should deep merge nested objects", () => {
+ const target = { a: { b: 1, c: 2 } };
+ const source = { a: { c: 3, d: 4 } };
+ expect(deepMerge(target, source)).toEqual({ a: { b: 1, c: 3, d: 4 } });
+ });
+
+ it("should handle multiple sources", () => {
+ expect(deepMerge({ a: 1 }, { b: 2 }, { c: 3 })).toEqual({ a: 1, b: 2, c: 3 });
+ });
+
+ it("should return target when source is empty", () => {
+ const target = { a: 1 };
+ expect(deepMerge(target, {})).toEqual({ a: 1 });
+ });
+
+ it("should create nested keys in target if missing", () => {
+ expect(deepMerge({}, { a: { b: 1 } })).toEqual({ a: { b: 1 } });
+ });
+
+ it("should not override a primitive with an object when target is not an object", () => {
+ expect(deepMerge({ a: 1 }, { a: { b: 2 } })).toEqual({ a: 1 });
+ });
+});
diff --git a/libs/schematics/src/utils/object.ts b/libs/schematics/src/utils/object.ts
new file mode 100644
index 0000000..b42a4e2
--- /dev/null
+++ b/libs/schematics/src/utils/object.ts
@@ -0,0 +1,21 @@
+export const isObject = (item: any) => {
+ return item && typeof item === "object" && !Array.isArray(item);
+};
+
+export const deepMerge = (target: any, ...sources: [any, ...any[]]): any => {
+ if (!sources.length) return target;
+ const source = sources.shift();
+
+ if (isObject(target) && isObject(source)) {
+ for (const key in source) {
+ if (isObject(source[key])) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ deepMerge(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+
+ return deepMerge(target, ...sources);
+};
diff --git a/libs/schematics/src/utils/path.ts b/libs/schematics/src/utils/path.ts
new file mode 100644
index 0000000..ecd11a8
--- /dev/null
+++ b/libs/schematics/src/utils/path.ts
@@ -0,0 +1,6 @@
+import { join } from "path";
+
+export const joinRelative = (...paths: [string, ...string[]]): string => {
+ const path = join(...paths);
+ return path.startsWith("./") || path.startsWith("../") ? path : `./${path}`;
+};
diff --git a/libs/schematics/src/utils/registry.spec.ts b/libs/schematics/src/utils/registry.spec.ts
new file mode 100644
index 0000000..a90034f
--- /dev/null
+++ b/libs/schematics/src/utils/registry.spec.ts
@@ -0,0 +1,106 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import { fetchTrustedVersion, fetchTrustedVersions } from "./registry";
+
+const HOUR = 60 * 60 * 1000;
+
+const mockRegistryResponse = (time: Record, ok = true) => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockResolvedValue({
+ ok,
+ json: () => Promise.resolve({ time }),
+ }),
+ );
+};
+
+describe("fetchTrustedVersion", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-09-10T00:00:00Z"));
+ });
+
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ vi.useRealTimers();
+ });
+
+ it("picks the newest version published at least 48h ago", async () => {
+ mockRegistryResponse({
+ created: "2020-01-01T00:00:00Z",
+ modified: "2026-09-09T00:00:00Z",
+ "1.0.0": new Date(Date.now() - 100 * HOUR).toISOString(),
+ "1.1.0": new Date(Date.now() - 72 * HOUR).toISOString(),
+ "1.2.0": new Date(Date.now() - 1 * HOUR).toISOString(), // too fresh
+ });
+
+ const version = await fetchTrustedVersion("some-pkg", "0.0.0");
+ expect(version).toBe("^1.1.0");
+ });
+
+ it("excludes prerelease and non-semver tags", async () => {
+ mockRegistryResponse({
+ "1.0.0": new Date(Date.now() - 100 * HOUR).toISOString(),
+ "1.1.0-beta.1": new Date(Date.now() - 100 * HOUR).toISOString(),
+ });
+
+ const version = await fetchTrustedVersion("some-pkg", "0.0.0");
+ expect(version).toBe("^1.0.0");
+ });
+
+ it("restricts to a requested major version", async () => {
+ mockRegistryResponse({
+ "6.0.3": new Date(Date.now() - 100 * HOUR).toISOString(),
+ "7.0.2": new Date(Date.now() - 100 * HOUR).toISOString(),
+ });
+
+ const version = await fetchTrustedVersion("typescript", "6.0.0", { major: 6 });
+ expect(version).toBe("^6.0.3");
+ });
+
+ it("falls back when every version is too fresh", async () => {
+ mockRegistryResponse({
+ "1.0.0": new Date(Date.now() - 1 * HOUR).toISOString(),
+ });
+
+ const version = await fetchTrustedVersion("some-pkg", "0.0.0");
+ expect(version).toBe("0.0.0");
+ });
+
+ it("falls back when the registry response is not ok", async () => {
+ mockRegistryResponse({}, false);
+ const version = await fetchTrustedVersion("missing-pkg", "0.0.0");
+ expect(version).toBe("0.0.0");
+ });
+
+ it("falls back when the fetch rejects", async () => {
+ vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network down")));
+ const version = await fetchTrustedVersion("some-pkg", "0.0.0");
+ expect(version).toBe("0.0.0");
+ });
+});
+
+describe("fetchTrustedVersions", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-09-10T00:00:00Z"));
+ });
+
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ vi.useRealTimers();
+ });
+
+ it("resolves multiple packages and keys the result by package name", async () => {
+ mockRegistryResponse({
+ "2.5.0": new Date(Date.now() - 100 * HOUR).toISOString(),
+ });
+
+ const versions = await fetchTrustedVersions({
+ "pkg-a": { fallback: "0.0.0" },
+ "pkg-b": { fallback: "0.0.1" },
+ });
+
+ expect(versions).toEqual({ "pkg-a": "^2.5.0", "pkg-b": "^2.5.0" });
+ });
+});
diff --git a/libs/schematics/src/utils/registry.ts b/libs/schematics/src/utils/registry.ts
new file mode 100644
index 0000000..e3de420
--- /dev/null
+++ b/libs/schematics/src/utils/registry.ts
@@ -0,0 +1,106 @@
+import { MIN_RELEASE_AGE_HOURS } from "~/defaults";
+
+const REGISTRY_URL = "https://registry.npmjs.org";
+const DEFAULT_TIMEOUT_MS = 3000;
+
+const RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
+
+const compareVersions = (a: string, b: string): number => {
+ const partsA = a.split(".").map(Number);
+ const partsB = b.split(".").map(Number);
+ for (let i = 0; i < 3; i++) {
+ const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
+ if (diff !== 0) {
+ return diff;
+ }
+ }
+ return 0;
+};
+
+export interface FetchTrustedVersionOptions {
+ /**
+ * Only consider versions published at least this many hours ago, mirroring
+ * pnpm's `minimumReleaseAge` supply-chain protection for a freshly
+ * published (and potentially compromised) release.
+ * @default MIN_RELEASE_AGE_HOURS
+ */
+ minAgeHours?: number;
+
+ /**
+ * Restrict candidates to this major version (e.g. `6` only considers
+ * `6.x.x`). Useful to avoid silently jumping a generated project onto a
+ * new major release.
+ */
+ major?: number;
+
+ /** Network timeout in milliseconds. @default 3000 */
+ timeoutMs?: number;
+}
+
+/**
+ * Resolves the newest published version of an npm package that is at least
+ * `minAgeHours` old, so freshly (and potentially maliciously) published
+ * versions aren't picked up immediately. Falls back to `fallback` on any
+ * network error, timeout, missing package, or when no version satisfies the
+ * constraints, so callers never have to handle a rejected promise.
+ */
+export const fetchTrustedVersion = async (
+ packageName: string,
+ fallback: string,
+ options: FetchTrustedVersionOptions = {},
+): Promise => {
+ const { minAgeHours = MIN_RELEASE_AGE_HOURS, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
+
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
+
+ try {
+ const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {
+ signal: controller.signal,
+ });
+ if (!response.ok) {
+ return fallback;
+ }
+
+ const data = (await response.json()) as { time?: Record };
+ const time = data.time;
+ if (!time) {
+ return fallback;
+ }
+
+ const cutoff = Date.now() - minAgeHours * 60 * 60 * 1000;
+ const eligible = Object.entries(time)
+ .filter(([version]) => RELEASE_VERSION_PATTERN.test(version))
+ .filter(([version]) => major === undefined || Number(version.split(".")[0]) === major)
+ .filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff)
+ .map(([version]) => version)
+ .sort(compareVersions);
+
+ return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;
+ } catch {
+ return fallback;
+ } finally {
+ clearTimeout(timeout);
+ }
+};
+
+/**
+ * Resolves several packages' trusted versions in parallel. Keys of `specs`
+ * are the package name to query the registry for; values are the fallback
+ * to use if that lookup fails. Returns a map from the *same* keys to the
+ * resolved (or fallback) version — typed so callers don't get `| undefined`
+ * back for a key they just passed in.
+ */
+export const fetchTrustedVersions = async <
+ T extends Record,
+>(
+ specs: T,
+): Promise<{ [K in keyof T]: string }> => {
+ const entries = await Promise.all(
+ Object.entries(specs).map(async ([packageName, { fallback, major }]) => {
+ const version = await fetchTrustedVersion(packageName, fallback, { major });
+ return [packageName, version] as const;
+ }),
+ );
+ return Object.fromEntries(entries) as { [K in keyof T]: string };
+};
diff --git a/libs/schematics/src/utils/types.ts b/libs/schematics/src/utils/types.ts
new file mode 100644
index 0000000..2f49720
--- /dev/null
+++ b/libs/schematics/src/utils/types.ts
@@ -0,0 +1,15 @@
+export type DeepPartial =
+ | T
+ | (T extends Date
+ ? T
+ : T extends Array
+ ? DeepPartial[]
+ : T extends Map
+ ? Map, DeepPartial>
+ : T extends Set
+ ? Set>
+ : T extends object
+ ? {
+ [K in keyof T]?: DeepPartial;
+ }
+ : T);
diff --git a/libs/schematics/tsconfig.json b/libs/schematics/tsconfig.json
new file mode 100644
index 0000000..af69dd6
--- /dev/null
+++ b/libs/schematics/tsconfig.json
@@ -0,0 +1,56 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ // Mapped from https://www.typescriptlang.org/tsconfig
+ "compilerOptions": {
+ // Type Checking
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "skipLibCheck": true,
+ "strict": true,
+
+ // Modules
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ // Emit
+ "declaration": true,
+ "declarationMap": true,
+ "importHelpers": false,
+ "newLine": "lf",
+ "noEmitHelpers": true,
+ "outDir": "dist",
+ "removeComments": false,
+ "sourceMap": true,
+
+ // Interop Constraints
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ // Language and Environment
+ "rootDir": ".",
+ "lib": ["ESNext", "esnext.disposable"],
+ "types": ["node"],
+ "target": "ESNext",
+ "experimentalDecorators": true,
+ "useDefineForClassFields": true,
+ "paths": {
+ "~/*": ["./src/*"],
+ "@utils/*": ["./src/utils/*"]
+ }
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist", "e2e", "src/libs/**/files/**/*", "src/**/*.spec.ts"]
+}
diff --git a/libs/schematics/tsdown.config.ts b/libs/schematics/tsdown.config.ts
new file mode 100644
index 0000000..e779764
--- /dev/null
+++ b/libs/schematics/tsdown.config.ts
@@ -0,0 +1,41 @@
+import { type UserConfig, defineConfig } from "tsdown";
+
+function createTsdownConfig({
+ entry = ["src/index.ts"],
+ outDir = "dist",
+ format = ["esm", "cjs"],
+ shims = true,
+}: UserConfig = {}) {
+ return defineConfig({
+ entry,
+ outDir,
+ format,
+ shims,
+ dts: true,
+ fixedExtension: false,
+ platform: "node",
+ sourcemap: true,
+ target: "esnext",
+ treeshake: false,
+ deps: {
+ skipNodeModulesBundle: true,
+ },
+ });
+}
+
+function createLibTsdownConfig(name: string) {
+ return createTsdownConfig({
+ entry: [`src/libs/${name}/${name}.factory.ts`],
+ outDir: `dist/libs/${name}`,
+ format: ["esm"],
+ shims: false,
+ });
+}
+
+export default [
+ createTsdownConfig(),
+ createLibTsdownConfig("workspace"),
+ createLibTsdownConfig("project"),
+ createLibTsdownConfig("component"),
+ createLibTsdownConfig("system"),
+];
diff --git a/libs/schematics/vitest.config.e2e.ts b/libs/schematics/vitest.config.e2e.ts
new file mode 100644
index 0000000..928e373
--- /dev/null
+++ b/libs/schematics/vitest.config.e2e.ts
@@ -0,0 +1,20 @@
+import { resolve } from "node:path";
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ "~": resolve(__dirname, "src"),
+ "@utils": resolve(__dirname, "src/utils"),
+ },
+ },
+ test: {
+ include: ["e2e/**/*.test.ts"],
+ passWithNoTests: true,
+ coverage: {
+ provider: "v8",
+ include: ["dist/**/*.js"],
+ exclude: ["dist/libs/**/files/**/*", "**/index.js"],
+ },
+ },
+});
diff --git a/libs/schematics/vitest.config.ts b/libs/schematics/vitest.config.ts
new file mode 100644
index 0000000..46a68f8
--- /dev/null
+++ b/libs/schematics/vitest.config.ts
@@ -0,0 +1,27 @@
+import { resolve } from "node:path";
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ "~": resolve(__dirname, "src"),
+ "@utils": resolve(__dirname, "src/utils"),
+ },
+ },
+ test: {
+ include: ["src/**/*.spec.ts"],
+ passWithNoTests: true,
+ coverage: {
+ provider: "v8",
+ include: ["src/**/*.ts"],
+ exclude: [
+ "src/**/*.spec.ts",
+ "src/libs/**/*",
+ "**/*.{interface,type,d}.ts",
+ "**/{interfaces,types}/*.ts",
+ "**/{interfaces,types}.ts",
+ "**/index.{js,ts}",
+ ],
+ },
+ },
+});
diff --git a/package.json b/package.json
index d653f47..82b1552 100644
--- a/package.json
+++ b/package.json
@@ -12,10 +12,11 @@
"bugs": "https://github.com/NanoForge-dev/cli/issues",
"license": "MIT",
"contributors": [
- "Bill ",
- "Exelo ",
+ "Exelo ",
"Fexkoser ",
- "Tchips "
+ "Harper ",
+ "Josephine ",
+ "Leo "
],
"files": [
"dist"
@@ -31,8 +32,15 @@
"type": "git",
"url": "git+https://github.com/NanoForge-dev/CLI.git"
},
- "funding": "",
+ "funding": "https://github.com/NanoForge-dev/CLI?sponsor",
"scripts": {
+ "repo:build": "turbo run build --concurrency=5",
+ "repo:build:dev": "turbo run build:dev --concurrency=5",
+ "repo:lint": "turbo run lint --concurrency=5",
+ "repo:format": "turbo run format --concurrency=5",
+ "repo:test": "pnpm run repo:test:unit && pnpm run repo:test:e2e",
+ "repo:test:unit": "turbo run test:unit --concurrency=5",
+ "repo:test:e2e": "turbo run test:e2e --concurrency=5",
"build": "tsc --noEmit && tsdown",
"build:dev": "tsc --noEmit && NODE_ENV=development tsdown",
"start": "node dist/nf.js",
@@ -53,9 +61,11 @@
"@angular-devkit/schematics": "catalog:schematics",
"@angular-devkit/schematics-cli": "catalog:schematics",
"@inquirer/prompts": "catalog:cli",
+ "@nanoforge-dev/config": "workspace:*",
+ "@nanoforge-dev/editor": "catalog:core",
"@nanoforge-dev/loader-client": "catalog:loader",
"@nanoforge-dev/loader-server": "catalog:loader",
- "@nanoforge-dev/schematics": "catalog:schematics",
+ "@nanoforge-dev/schematics": "workspace:*",
"ansis": "catalog:cli",
"bun": "catalog:core",
"chokidar": "catalog:libs",
@@ -79,21 +89,19 @@
"@trivago/prettier-plugin-sort-imports": "catalog:lint",
"@types/inquirer": "catalog:cli",
"@types/node": "catalog:core",
- "@vitest/coverage-v8": "catalog:tests",
+ "@vitest/coverage-v8": "catalog:test",
"eslint": "catalog:lint",
"husky": "catalog:ci",
"lint-staged": "catalog:ci",
"prettier": "catalog:lint",
"tsdown": "catalog:build",
+ "turbo": "catalog:build",
"typescript": "catalog:build",
- "vitest": "catalog:tests"
+ "vitest": "catalog:test"
},
- "optionalDependencies": {
- "@nanoforge-dev/editor": "catalog:core"
- },
- "packageManager": "pnpm@11.10.0",
+ "packageManager": "pnpm@12.0.0",
"engines": {
- "node": "25"
+ "node": "26"
},
"publishConfig": {
"access": "public"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 949b5b0..8a79d9a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,3 +1,104 @@
+---
+lockfileVersion: '9.0'
+
+importers:
+
+ .:
+ configDependencies: {}
+ packageManagerDependencies:
+ pnpm:
+ specifier: 12.0.0
+ version: 12.0.0
+
+packages:
+
+ '@pnpm/exe.darwin-arm64@12.0.0':
+ resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@pnpm/exe.darwin-x64@12.0.0':
+ resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@pnpm/exe.linux-arm64-musl@12.0.0':
+ resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@pnpm/exe.linux-arm64@12.0.0':
+ resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@pnpm/exe.linux-x64-musl@12.0.0':
+ resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@pnpm/exe.linux-x64@12.0.0':
+ resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@pnpm/exe.win32-arm64@12.0.0':
+ resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@pnpm/exe.win32-x64@12.0.0':
+ resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==}
+ cpu: [x64]
+ os: [win32]
+
+ pnpm@12.0.0:
+ resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
+ engines: {node: '>=18.*'}
+ hasBin: true
+
+snapshots:
+
+ '@pnpm/exe.darwin-arm64@12.0.0':
+ optional: true
+
+ '@pnpm/exe.darwin-x64@12.0.0':
+ optional: true
+
+ '@pnpm/exe.linux-arm64-musl@12.0.0':
+ optional: true
+
+ '@pnpm/exe.linux-arm64@12.0.0':
+ optional: true
+
+ '@pnpm/exe.linux-x64-musl@12.0.0':
+ optional: true
+
+ '@pnpm/exe.linux-x64@12.0.0':
+ optional: true
+
+ '@pnpm/exe.win32-arm64@12.0.0':
+ optional: true
+
+ '@pnpm/exe.win32-x64@12.0.0':
+ optional: true
+
+ pnpm@12.0.0:
+ optionalDependencies:
+ '@pnpm/exe.darwin-arm64': 12.0.0
+ '@pnpm/exe.darwin-x64': 12.0.0
+ '@pnpm/exe.linux-arm64': 12.0.0
+ '@pnpm/exe.linux-arm64-musl': 12.0.0
+ '@pnpm/exe.linux-x64': 12.0.0
+ '@pnpm/exe.linux-x64-musl': 12.0.0
+ '@pnpm/exe.win32-arm64': 12.0.0
+ '@pnpm/exe.win32-x64': 12.0.0
+
+---
lockfileVersion: '9.0'
settings:
@@ -6,19 +107,25 @@ settings:
catalogs:
build:
+ cpx2:
+ specifier: ^9.0.0
+ version: 9.0.3
tsdown:
- specifier: ^0.22.3
- version: 0.22.3
+ specifier: ^0.23.0
+ version: 0.23.0
+ turbo:
+ specifier: ^2.11.3
+ version: 2.11.3
typescript:
specifier: ^6.0.3
version: 6.0.3
ci:
'@commitlint/cli':
- specifier: ^21.2.0
- version: 21.2.0
+ specifier: ^21.2.3
+ version: 21.2.3
'@commitlint/config-conventional':
- specifier: ^21.2.0
- version: 21.2.0
+ specifier: ^21.2.3
+ version: 21.2.3
'@favware/cliff-jumper':
specifier: ^6.1.0
version: 6.1.0
@@ -29,18 +136,18 @@ catalogs:
specifier: ^9.1.7
version: 9.1.7
lint-staged:
- specifier: ^17.0.8
- version: 17.0.8
+ specifier: ^17.5.1
+ version: 17.5.1
cli:
'@inquirer/prompts':
- specifier: ^8.5.2
- version: 8.5.2
+ specifier: ^8.7.2
+ version: 8.7.2
'@types/inquirer':
specifier: ^9.0.10
version: 9.0.10
ansis:
- specifier: ^4.3.1
- version: 4.3.1
+ specifier: ^4.4.0
+ version: 4.4.0
commander:
specifier: ^15.0.0
version: 15.0.0
@@ -55,11 +162,11 @@ catalogs:
specifier: ^0.1.4
version: 0.1.4
'@types/node':
- specifier: ^26.1.0
- version: 26.1.0
+ specifier: ^26.6.2
+ version: 26.6.2
bun:
- specifier: ^1.3.14
- version: 1.3.14
+ specifier: ^1.4.2
+ version: 1.4.2
libs:
chokidar:
specifier: ^5.0.0
@@ -71,17 +178,23 @@ catalogs:
specifier: ^0.15.1
version: 0.15.1
dotenv:
- specifier: ^17.4.2
- version: 17.4.2
+ specifier: ^18.0.3
+ version: 18.0.3
open:
- specifier: ^11.0.0
- version: 11.0.0
+ specifier: ^11.0.4
+ version: 11.0.4
rc9:
- specifier: ^3.0.1
- version: 3.0.1
+ specifier: ^3.1.0
+ version: 3.1.0
reflect-metadata:
specifier: ^0.2.2
version: 0.2.2
+ unrun:
+ specifier: ^0.3.0
+ version: 0.3.1
+ zod:
+ specifier: ^4.5.4
+ version: 4.6.5
lint:
'@nanoforge-dev/utils-eslint-config':
specifier: ^1.0.2
@@ -93,35 +206,38 @@ catalogs:
specifier: ^6.0.2
version: 6.0.2
eslint:
- specifier: ^10.6.0
- version: 10.6.0
+ specifier: ^10.11.0
+ version: 10.11.0
prettier:
- specifier: ^3.9.4
- version: 3.9.4
+ specifier: ^3.9.9
+ version: 3.9.9
loader:
'@nanoforge-dev/loader-client':
- specifier: ^1.4.0
- version: 1.4.0
+ specifier: ^1.5.1
+ version: 1.5.1
'@nanoforge-dev/loader-server':
- specifier: ^1.4.0
- version: 1.4.0
+ specifier: ^1.5.1
+ version: 1.5.1
schematics:
+ '@angular-devkit/core':
+ specifier: ^22.0.4
+ version: 22.1.8
'@angular-devkit/schematics':
- specifier: ^22.0.5
- version: 22.0.5
+ specifier: ^22.1.8
+ version: 22.1.8
'@angular-devkit/schematics-cli':
- specifier: ^22.0.5
- version: 22.0.5
- '@nanoforge-dev/schematics':
- specifier: ^2.2.1
- version: 2.2.1
- tests:
+ specifier: ^22.1.8
+ version: 22.1.8
+ rxjs:
+ specifier: ^7.8.2
+ version: 7.8.2
+ test:
'@vitest/coverage-v8':
- specifier: ^4.1.10
- version: 4.1.10
+ specifier: ^5.0.1
+ version: 5.0.1
vitest:
- specifier: ^4.1.10
- version: 4.1.10
+ specifier: ^5.0.1
+ version: 5.0.1
importers:
@@ -129,28 +245,34 @@ importers:
dependencies:
'@angular-devkit/schematics':
specifier: catalog:schematics
- version: 22.0.5(chokidar@5.0.0)
+ version: 22.1.8(chokidar@5.0.0)
'@angular-devkit/schematics-cli':
specifier: catalog:schematics
- version: 22.0.5(@types/node@26.1.0)(chokidar@5.0.0)
+ version: 22.1.8(@types/node@26.6.2)(chokidar@5.0.0)
'@inquirer/prompts':
specifier: catalog:cli
- version: 8.5.2(@types/node@26.1.0)
+ version: 8.7.2(@types/node@26.6.2)
+ '@nanoforge-dev/config':
+ specifier: workspace:*
+ version: link:libs/config
+ '@nanoforge-dev/editor':
+ specifier: catalog:core
+ version: 0.1.4
'@nanoforge-dev/loader-client':
specifier: catalog:loader
- version: 1.4.0
+ version: 1.5.1
'@nanoforge-dev/loader-server':
specifier: catalog:loader
- version: 1.4.0
+ version: 1.5.1
'@nanoforge-dev/schematics':
- specifier: catalog:schematics
- version: 2.2.1(chokidar@5.0.0)
+ specifier: workspace:*
+ version: link:libs/schematics
ansis:
specifier: catalog:cli
- version: 4.3.1
+ version: 4.4.0
bun:
specifier: catalog:core
- version: 1.3.14
+ version: 1.4.2
chokidar:
specifier: catalog:libs
version: 5.0.0
@@ -165,29 +287,29 @@ importers:
version: 15.0.0
dotenv:
specifier: catalog:libs
- version: 17.4.2
+ version: 18.0.3
node-emoji:
specifier: catalog:cli
version: 2.2.0
open:
specifier: catalog:libs
- version: 11.0.0
+ version: 11.0.4
ora:
specifier: catalog:cli
version: 9.4.1
rc9:
specifier: catalog:libs
- version: 3.0.1
+ version: 3.1.0
reflect-metadata:
specifier: catalog:libs
version: 0.2.2
devDependencies:
'@commitlint/cli':
specifier: catalog:ci
- version: 21.2.0(@types/node@26.1.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
+ version: 21.2.3(@types/node@26.6.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
'@commitlint/config-conventional':
specifier: catalog:ci
- version: 21.2.0
+ version: 21.2.3
'@favware/cliff-jumper':
specifier: catalog:ci
version: 6.1.0
@@ -196,47 +318,150 @@ importers:
version: 2.1.5
'@nanoforge-dev/utils-eslint-config':
specifier: catalog:lint
- version: 1.0.2(@types/eslint@9.6.1)(eslint@10.6.0(jiti@2.6.1))(prettier@3.9.4)(typescript@6.0.3)
+ version: 1.0.2(@types/eslint@9.6.1)(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9)(supports-color@10.2.2)(typescript@6.0.3)
'@nanoforge-dev/utils-prettier-config':
specifier: catalog:lint
version: 1.0.2
'@trivago/prettier-plugin-sort-imports':
specifier: catalog:lint
- version: 6.0.2(prettier@3.9.4)
+ version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2)
'@types/inquirer':
specifier: catalog:cli
version: 9.0.10
'@types/node':
specifier: catalog:core
- version: 26.1.0
+ version: 26.6.2
'@vitest/coverage-v8':
- specifier: catalog:tests
- version: 4.1.10(vitest@4.1.10)
+ specifier: catalog:test
+ version: 5.0.1(vitest@5.0.1)
eslint:
specifier: catalog:lint
- version: 10.6.0(jiti@2.6.1)
+ version: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
husky:
specifier: catalog:ci
version: 9.1.7
lint-staged:
specifier: catalog:ci
- version: 17.0.8
+ version: 17.5.1
prettier:
specifier: catalog:lint
- version: 3.9.4
+ version: 3.9.9
tsdown:
specifier: catalog:build
- version: 0.22.3(typescript@6.0.3)
+ version: 0.23.0(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13))
+ turbo:
+ specifier: catalog:build
+ version: 2.11.3
typescript:
specifier: catalog:build
version: 6.0.3
vitest:
- specifier: catalog:tests
- version: 4.1.10(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0))
- optionalDependencies:
- '@nanoforge-dev/editor':
+ specifier: catalog:test
+ version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))
+
+ libs/config:
+ dependencies:
+ unrun:
+ specifier: catalog:libs
+ version: 0.3.1(synckit@0.11.13)
+ zod:
+ specifier: catalog:libs
+ version: 4.6.5
+ devDependencies:
+ '@favware/cliff-jumper':
+ specifier: catalog:ci
+ version: 6.1.0
+ '@nanoforge-dev/utils-eslint-config':
+ specifier: catalog:lint
+ version: 1.0.2(@types/eslint@9.6.1)(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9)(supports-color@10.2.2)(typescript@6.0.3)
+ '@nanoforge-dev/utils-prettier-config':
+ specifier: catalog:lint
+ version: 1.0.2
+ '@trivago/prettier-plugin-sort-imports':
+ specifier: catalog:lint
+ version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2)
+ eslint:
+ specifier: catalog:lint
+ version: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
+ prettier:
+ specifier: catalog:lint
+ version: 3.9.9
+ tsdown:
+ specifier: catalog:build
+ version: 0.23.0(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13))
+ typescript:
+ specifier: catalog:build
+ version: 6.0.3
+ vitest:
+ specifier: catalog:test
+ version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))
+
+ libs/schematics:
+ dependencies:
+ '@angular-devkit/core':
+ specifier: catalog:schematics
+ version: 22.1.8(chokidar@5.0.0)
+ '@angular-devkit/schematics':
+ specifier: catalog:schematics
+ version: 22.1.8(chokidar@5.0.0)
+ rxjs:
+ specifier: catalog:schematics
+ version: 7.8.2
+ devDependencies:
+ '@angular-devkit/schematics-cli':
+ specifier: catalog:schematics
+ version: 22.1.8(@types/node@26.6.2)(chokidar@5.0.0)
+ '@commitlint/cli':
+ specifier: catalog:ci
+ version: 21.2.3(@types/node@26.6.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)
+ '@commitlint/config-conventional':
+ specifier: catalog:ci
+ version: 21.2.3
+ '@favware/cliff-jumper':
+ specifier: catalog:ci
+ version: 6.1.0
+ '@nanoforge-dev/actions':
+ specifier: catalog:ci
+ version: 2.1.5
+ '@nanoforge-dev/utils-eslint-config':
+ specifier: catalog:lint
+ version: 1.0.2(@types/eslint@9.6.1)(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9)(supports-color@10.2.2)(typescript@6.0.3)
+ '@nanoforge-dev/utils-prettier-config':
+ specifier: catalog:lint
+ version: 1.0.2
+ '@trivago/prettier-plugin-sort-imports':
+ specifier: catalog:lint
+ version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2)
+ '@types/node':
specifier: catalog:core
- version: 0.1.4
+ version: 26.6.2
+ '@vitest/coverage-v8':
+ specifier: catalog:test
+ version: 5.0.1(vitest@5.0.1)
+ cpx2:
+ specifier: catalog:build
+ version: 9.0.3
+ eslint:
+ specifier: catalog:lint
+ version: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
+ husky:
+ specifier: catalog:ci
+ version: 9.1.7
+ lint-staged:
+ specifier: catalog:ci
+ version: 17.5.1
+ prettier:
+ specifier: catalog:lint
+ version: 3.9.9
+ tsdown:
+ specifier: catalog:build
+ version: 0.23.0(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13))
+ typescript:
+ specifier: catalog:build
+ version: 6.0.3
+ vitest:
+ specifier: catalog:test
+ version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))
packages:
@@ -258,8 +483,8 @@ packages:
'@actions/io@3.0.2':
resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==}
- '@angular-devkit/core@22.0.5':
- resolution: {integrity: sha512-xxd3b9X0bbdGuYqDj1OgxtlGotrb/gsxQ5+4aqivWBHq4q/1RI8JfVB7gqcoYTvAfTq63Dwsk7Qd33hC4yJ1Wg==}
+ '@angular-devkit/core@22.1.8':
+ resolution: {integrity: sha512-34lsgg2FwMVBX7nR/ZqzRUcdvdYPvSB8XAEr2GudXAyZwLI9ehzfQlagAWR/gEb8p4uCFZW0r7uU0toxydq4Rw==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
peerDependencies:
chokidar: ^5.0.0
@@ -267,27 +492,23 @@ packages:
chokidar:
optional: true
- '@angular-devkit/schematics-cli@22.0.5':
- resolution: {integrity: sha512-X305DwlHLyVE+RoM2C9KjKvShi2FiS4pkjYH6CvfydUmtyVxGj34kgtIPf8Kk+Jn7St7fRja5IUAcxczBgL3SQ==}
+ '@angular-devkit/schematics-cli@22.1.8':
+ resolution: {integrity: sha512-SsGG8ompVzprmYE9sHi2L7DYZ+i1sD+YDfun6Rg2vkTCusejtD4SSGVxwuR6++dT22ZpN16OqUTmii/0bhE4xA==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
hasBin: true
- '@angular-devkit/schematics@22.0.5':
- resolution: {integrity: sha512-yKkImZuFLIbylmk1REyoW3EWQeqnr6qwtrzs4EPeaJgzjj+MZurq5slQ3fanK9lfX2q+W+dnSQFKBXHxlUGJ8Q==}
+ '@angular-devkit/schematics@22.1.8':
+ resolution: {integrity: sha512-Pv3cPa/44kvEwYcqwx4Ns5dhIDmcFNSHhbpheqiEG1Z/u4e2t4zHKDtE3eHZB+8o+IcC7xJj+d+AqGR44RoZDA==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
'@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.29.7':
- resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
+ '@babel/generator@7.29.8':
+ resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
engines: {node: '>=6.9.0'}
- '@babel/generator@8.0.0':
- resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/helper-globals@7.29.7':
resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
engines: {node: '>=6.9.0'}
@@ -296,103 +517,92 @@ packages:
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-string-parser@8.0.0':
- resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/helper-validator-identifier@7.29.7':
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@8.0.2':
- resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
- '@babel/parser@7.29.7':
- resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@8.0.0':
- resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==}
- engines: {node: ^22.18.0 || >=24.11.0}
- hasBin: true
-
'@babel/template@7.29.7':
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
engines: {node: '>=6.9.0'}
- '@babel/traverse@7.29.7':
- resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
+ '@babel/traverse@7.29.8':
+ resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.29.7':
- resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ '@babel/types@7.29.8':
+ resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
engines: {node: '>=6.9.0'}
- '@babel/types@8.0.0':
- resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
- '@commitlint/cli@21.2.0':
- resolution: {integrity: sha512-4GLVIhUaT3c3GBlQ0GB80/5H3xXdn/Tgw4lrsuoOQVDu2wl4Xw0GuzSar8xZKSMv4H3xaKaQXmvH91GmdyYBZA==}
+ '@cacheable/memory@2.2.0':
+ resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==}
+
+ '@cacheable/utils@2.5.0':
+ resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
+
+ '@commitlint/cli@21.2.3':
+ resolution: {integrity: sha512-5yOX6IRjcrIReCxpyAd04hgI/yhUMpVBJ/ouwFV0wk9yx5+fYRDWZWJ5h55Tbedgfol+5m2mU5nqzbB8g3v0vQ==}
engines: {node: '>=22.12.0'}
hasBin: true
- '@commitlint/config-conventional@21.2.0':
- resolution: {integrity: sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==}
+ '@commitlint/config-conventional@21.2.3':
+ resolution: {integrity: sha512-EP2n5DN2GwRWNVrLX0Jx3mpQlW5PfFi9HnD9OJ8hN/H0xFqpCFyHeGDhg+0P8/21iMMcdkcQgbNqmVklebqYsQ==}
engines: {node: '>=22.12.0'}
- '@commitlint/config-validator@21.2.0':
- resolution: {integrity: sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==}
+ '@commitlint/config-validator@21.2.3':
+ resolution: {integrity: sha512-JQ13v0GafSM4fBYX8UowSy++07Was20auq0NcTdnzSEq6/05Cq5GBUMIfKkqjzyBwC9sJFmPpKWgLLhH7cygkw==}
engines: {node: '>=22.12.0'}
- '@commitlint/ensure@21.2.0':
- resolution: {integrity: sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==}
+ '@commitlint/ensure@21.2.3':
+ resolution: {integrity: sha512-gEjNKgRocbODI5/sMx2Qc9XOf2/zFTNV2aEK1QZ4gKDKxDC/VR7us3QtIX1C5d5xxHxx0I/tEbYbdSP1Am2LwA==}
engines: {node: '>=22.12.0'}
'@commitlint/execute-rule@21.0.1':
resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==}
engines: {node: '>=22.12.0'}
- '@commitlint/format@21.2.0':
- resolution: {integrity: sha512-c4q64xaav2U83t7k7RyzJerBZurPer7FxUOY0RL5L/6CZijZ7K+s6HIBGIghj0ey1P2+seRX0J9XQYtDued6tg==}
+ '@commitlint/format@21.2.3':
+ resolution: {integrity: sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==}
engines: {node: '>=22.12.0'}
- '@commitlint/is-ignored@21.2.0':
- resolution: {integrity: sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==}
+ '@commitlint/is-ignored@21.2.3':
+ resolution: {integrity: sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==}
engines: {node: '>=22.12.0'}
- '@commitlint/lint@21.2.0':
- resolution: {integrity: sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==}
+ '@commitlint/lint@21.2.3':
+ resolution: {integrity: sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==}
engines: {node: '>=22.12.0'}
- '@commitlint/load@21.2.0':
- resolution: {integrity: sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==}
+ '@commitlint/load@21.2.3':
+ resolution: {integrity: sha512-cDrL8lOo23Lk7knGzQdjbFcq7z2JgvjwV6PhbESVvEc+Fe21MeLjPKBJi8nKjg7j+jh79CVbVSUbZc104thXRw==}
engines: {node: '>=22.12.0'}
'@commitlint/message@21.2.0':
resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==}
engines: {node: '>=22.12.0'}
- '@commitlint/parse@21.2.0':
- resolution: {integrity: sha512-QHWxG4d0PLTF634/AdyZ0MQS+CLn5YOuJlCFhMMlSGKFxzYGUetkHBj18xgBD+6fVzUrA2lrCdi/vlS2f/oYXg==}
+ '@commitlint/parse@21.2.3':
+ resolution: {integrity: sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==}
engines: {node: '>=22.12.0'}
- '@commitlint/read@21.2.0':
- resolution: {integrity: sha512-ELx8Ovh/JoAw5lpvDgxc6Y0We9skf2IPI2RFN+gnYgDGjRdMSF8zeodxhZmcclLWzfUIF7hXLOa8gOlllYcvBA==}
+ '@commitlint/read@21.2.3':
+ resolution: {integrity: sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==}
engines: {node: '>=22.12.0'}
- '@commitlint/resolve-extends@21.2.0':
- resolution: {integrity: sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==}
+ '@commitlint/resolve-extends@21.2.3':
+ resolution: {integrity: sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==}
engines: {node: '>=22.12.0'}
- '@commitlint/rules@21.2.0':
- resolution: {integrity: sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==}
+ '@commitlint/rules@21.2.3':
+ resolution: {integrity: sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==}
engines: {node: '>=22.12.0'}
'@commitlint/to-lines@21.0.1':
@@ -403,8 +613,8 @@ packages:
resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==}
engines: {node: '>=22.12.0'}
- '@commitlint/types@21.2.0':
- resolution: {integrity: sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==}
+ '@commitlint/types@21.2.3':
+ resolution: {integrity: sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==}
engines: {node: '>=22.12.0'}
'@conventional-changelog/git-client@2.7.0':
@@ -419,8 +629,20 @@ packages:
conventional-commits-parser:
optional: true
- '@conventional-changelog/template@1.2.1':
- resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==}
+ '@conventional-changelog/git-client@3.1.2':
+ resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==}
+ engines: {node: '>=22'}
+ peerDependencies:
+ conventional-commits-filter: ^6.0.1
+ conventional-commits-parser: ^7.1.2
+ peerDependenciesMeta:
+ conventional-commits-filter:
+ optional: true
+ conventional-commits-parser:
+ optional: true
+
+ '@conventional-changelog/template@1.4.0':
+ resolution: {integrity: sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==}
engines: {node: '>=22'}
'@dprint/formatter@0.5.1':
@@ -432,17 +654,8 @@ packages:
'@dprint/toml@0.7.0':
resolution: {integrity: sha512-eFaQTcfxKHB+YyTh83x7GEv+gDPuj9q5NFOTaoj5rZmQTbj6OgjjMxUicmS1R8zYcx8YAq5oA9J3YFa5U6x2gA==}
- '@emnapi/core@1.11.1':
- resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
-
- '@emnapi/runtime@1.11.1':
- resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
-
- '@emnapi/wasi-threads@1.2.2':
- resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
-
- '@eslint-community/eslint-utils@4.9.1':
- resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+ '@eslint-community/eslint-utils@4.10.1':
+ resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@@ -455,24 +668,24 @@ packages:
resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/config-helpers@0.6.0':
- resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==}
+ '@eslint/config-helpers@0.7.0':
+ resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@eslint/core@1.2.1':
resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/js@9.39.4':
- resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==}
+ '@eslint/js@9.39.5':
+ resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@3.0.5':
resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/plugin-kit@0.7.2':
- resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==}
+ '@eslint/plugin-kit@0.7.3':
+ resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@favware/cliff-jumper@6.1.0':
@@ -504,12 +717,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
- '@inquirer/ansi@2.0.7':
- resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==}
+ '@inquirer/ansi@2.0.8':
+ resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
- '@inquirer/checkbox@5.2.1':
- resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==}
+ '@inquirer/checkbox@5.2.5':
+ resolution: {integrity: sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -517,8 +730,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/confirm@6.1.1':
- resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==}
+ '@inquirer/confirm@6.3.2':
+ resolution: {integrity: sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -526,8 +739,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/core@11.2.1':
- resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==}
+ '@inquirer/core@12.0.3':
+ resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -535,8 +748,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/editor@5.2.2':
- resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==}
+ '@inquirer/editor@5.3.3':
+ resolution: {integrity: sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -544,8 +757,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/expand@5.1.1':
- resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==}
+ '@inquirer/expand@5.1.5':
+ resolution: {integrity: sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -553,8 +766,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/external-editor@3.0.3':
- resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==}
+ '@inquirer/external-editor@3.0.5':
+ resolution: {integrity: sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -562,12 +775,12 @@ packages:
'@types/node':
optional: true
- '@inquirer/figures@2.0.7':
- resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==}
+ '@inquirer/figures@2.0.9':
+ resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
- '@inquirer/input@5.1.2':
- resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==}
+ '@inquirer/input@5.1.6':
+ resolution: {integrity: sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -575,8 +788,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/number@4.1.1':
- resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==}
+ '@inquirer/number@4.2.3':
+ resolution: {integrity: sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -584,8 +797,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/password@5.1.1':
- resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==}
+ '@inquirer/password@5.2.2':
+ resolution: {integrity: sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -593,17 +806,17 @@ packages:
'@types/node':
optional: true
- '@inquirer/prompts@8.4.2':
- resolution: {integrity: sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==}
- engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
+ '@inquirer/prompts@8.5.2':
+ resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
- '@inquirer/prompts@8.5.2':
- resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==}
+ '@inquirer/prompts@8.7.2':
+ resolution: {integrity: sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -611,8 +824,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/rawlist@5.3.1':
- resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==}
+ '@inquirer/rawlist@5.3.5':
+ resolution: {integrity: sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -620,8 +833,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/search@4.2.1':
- resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==}
+ '@inquirer/search@4.3.3':
+ resolution: {integrity: sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -629,8 +842,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/select@5.2.1':
- resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==}
+ '@inquirer/select@5.2.5':
+ resolution: {integrity: sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -638,8 +851,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/type@4.0.7':
- resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==}
+ '@inquirer/type@4.1.1':
+ resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
peerDependencies:
'@types/node': '>=18'
@@ -658,12 +871,21 @@ packages:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
- '@jridgewell/sourcemap-codec@1.5.5':
- resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+ '@jridgewell/sourcemap-codec@1.6.0':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@keyv/bigmap@1.3.1':
+ resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ keyv: ^5.6.0
+
+ '@keyv/serialize@1.1.1':
+ resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
+
'@nanoforge-dev/actions@2.1.5':
resolution: {integrity: sha512-aXFw1PGS8NmSEGAIJ1g5X+0kAiaWYPOvxkLttmKR1qO95O17mcpkKXfTdoSIyFnqPF2YnZjq9KR7Cvs27fNewg==}
engines: {node: '25'}
@@ -672,21 +894,17 @@ packages:
resolution: {integrity: sha512-bqbrH8ulFSiZz1iisJHtHeEzj1RV/70uErCYHeW4DIhVPsWbBfWKFvg/ghAbmPk5vsj0YnhDX6DSpik4S1QoiQ==}
engines: {node: '25'}
- '@nanoforge-dev/loader-client@1.4.0':
- resolution: {integrity: sha512-YYE41uqp/7+IXRXLi71UuWFBUXf8ZguMf32A/8qzCmbQN+gUtBdTKLwm+2VVcvih6mueCQCGnulH9t5SmC8+Qg==}
- engines: {node: '25'}
+ '@nanoforge-dev/loader-client@1.5.1':
+ resolution: {integrity: sha512-1PueGpj8DbjRcZTVVCKAkKi4A8gyBaFTkfp78fFrW47J0HZcfS8pq+AaNaBjEUOozn0E7L+U8zGf/5laxTRe1A==}
+ engines: {node: '26'}
- '@nanoforge-dev/loader-server@1.4.0':
- resolution: {integrity: sha512-jvNoHxx54BAfjUTMZHb/T/8ApbdPV/NNGwiQITUBprH4Uv8jQmbR7zNB5Z6/vIEjSVVxrU0b4fFa+BkdnKX/iQ==}
- engines: {node: '25'}
-
- '@nanoforge-dev/loader-website@1.4.0':
- resolution: {integrity: sha512-+5el/zkSJQL9qCd+4a6fGpIDMQi26QRTWXJ24mJFMaDC74P1lvqnes0u0l0+/s9dunJxfgea0Epse0/Jj/3M3Q==}
- engines: {node: '25'}
+ '@nanoforge-dev/loader-server@1.5.1':
+ resolution: {integrity: sha512-RNqA0zpEhUXENKu5chJ+vQOhr8qK+a7z6XePmiCJygpSScJ8p1dlWquFYNwu9dBLHBP8BhK9dSFvXjPvHAYBxQ==}
+ engines: {node: '26'}
- '@nanoforge-dev/schematics@2.2.1':
- resolution: {integrity: sha512-MdmhFncakgejGRIf1HTPKTskhdv8bIst21uWRSZCXGgGv0UNYEfDiziOsWPVCzGc9dXei59RIDznBwgpAMeXLg==}
- engines: {node: '25'}
+ '@nanoforge-dev/loader-website@1.5.1':
+ resolution: {integrity: sha512-Ugu5CvfbsCK2wG8RXBHkkVlNHkSlXz2xXxfqglID7Cb4E8PHO0p1hbE6e7+1fWXAdFhEivGQHSI1POIvgyP1Qg==}
+ engines: {node: '26'}
'@nanoforge-dev/utils-eslint-config@1.0.2':
resolution: {integrity: sha512-hIsVGGpVIW3RvPZocWEf15Y5eqmIxTtLfWI66wqx+AO6PHcatUBhEi4sk1i54Qb2yGFueRBS/dSXj0kxG4Rbcw==}
@@ -696,31 +914,31 @@ packages:
resolution: {integrity: sha512-dTa2ixPJmWVD+L13Iuxx51JIm3gaX+gnRCe8jA0PFw0xjMjLYv+0ZgbWSSVlNmowp6fqpOzfPdWADkkPNQs8LQ==}
engines: {node: '25'}
- '@napi-rs/wasm-runtime@1.1.6':
- resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
- peerDependencies:
- '@emnapi/core': ^1.7.1
- '@emnapi/runtime': ^1.7.1
-
'@octokit/auth-token@6.0.0':
resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==}
engines: {node: '>= 20'}
- '@octokit/core@7.0.6':
- resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==}
+ '@octokit/core@7.0.8':
+ resolution: {integrity: sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==}
engines: {node: '>= 20'}
- '@octokit/endpoint@11.0.3':
- resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==}
+ '@octokit/endpoint@11.0.5':
+ resolution: {integrity: sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==}
engines: {node: '>= 20'}
- '@octokit/graphql@9.0.3':
- resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==}
+ '@octokit/graphql@9.0.5':
+ resolution: {integrity: sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==}
engines: {node: '>= 20'}
'@octokit/openapi-types@27.0.0':
resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==}
+ '@octokit/openapi-types@28.0.0':
+ resolution: {integrity: sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==}
+
+ '@octokit/openapi-types@29.0.1':
+ resolution: {integrity: sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==}
+
'@octokit/plugin-paginate-rest@14.0.0':
resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==}
engines: {node: '>= 20'}
@@ -733,105 +951,95 @@ packages:
peerDependencies:
'@octokit/core': '>=6'
- '@octokit/plugin-retry@8.1.0':
- resolution: {integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==}
+ '@octokit/plugin-retry@8.1.1':
+ resolution: {integrity: sha512-VCVvZ/R1+u3WuiBWpNavZ0mY4aaJNAsENrpBP9aLSR2QyOpQgd7DhM5j4AW7z4MQpnJYgwBPf0XqPQoNBRdQwg==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': '>=7'
- '@octokit/request-error@7.1.0':
- resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==}
+ '@octokit/request-error@7.1.2':
+ resolution: {integrity: sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==}
engines: {node: '>= 20'}
- '@octokit/request@10.0.11':
- resolution: {integrity: sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==}
+ '@octokit/request@10.0.16':
+ resolution: {integrity: sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==}
engines: {node: '>= 20'}
'@octokit/types@16.0.0':
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
- '@oven/bun-darwin-aarch64@1.3.14':
- resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==}
- cpu: [arm64]
- os: [darwin]
+ '@octokit/types@17.0.0':
+ resolution: {integrity: sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==}
- '@oven/bun-darwin-x64-baseline@1.3.14':
- resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==}
- cpu: [x64]
+ '@octokit/types@18.0.0':
+ resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==}
+
+ '@oven/bun-darwin-aarch64@1.4.2':
+ resolution: {integrity: sha512-MXdZkP1featqxZ+/VTXWG1BVjM4OGBehVY2Q88EeUj/7L0UMeCGItmyPYTN+wxvlGJ6F66JEtzsw+GvQWewnag==}
+ cpu: [arm64]
os: [darwin]
- '@oven/bun-darwin-x64@1.3.14':
- resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==}
+ '@oven/bun-darwin-x64@1.4.2':
+ resolution: {integrity: sha512-gZTxZuLjkUhAWjTETu3tw0WhsEdNkJ64daj60ybhPf835a2yollV3yTkK9JozvzKPx4TRFzLSl8C+U525pxVbw==}
cpu: [x64]
os: [darwin]
- '@oven/bun-freebsd-aarch64@1.3.14':
- resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==}
+ '@oven/bun-freebsd-aarch64@1.4.2':
+ resolution: {integrity: sha512-SMNItMw1Z8QeeQVKnw8jA7xQNkeXdP+OPgin4Wi/QTx/B8RHHLnuZfqmFy7NtVeT2NF0kKYppW4WWd2CCYZjhQ==}
cpu: [arm64]
os: [freebsd]
- '@oven/bun-freebsd-x64@1.3.14':
- resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==}
+ '@oven/bun-freebsd-x64@1.4.2':
+ resolution: {integrity: sha512-THbPKXhO54N0DpFRKZNDZpQ7dpbX0bWASuARckAUS9wRtFIHsiY+uULXJvxJGo2YD1YewvXQ4G8Fj7XT5oBCiw==}
cpu: [x64]
os: [freebsd]
- '@oven/bun-linux-aarch64-android@1.3.14':
- resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==}
+ '@oven/bun-linux-aarch64-android@1.4.2':
+ resolution: {integrity: sha512-3mZKO2rhsNgbAUtAHC1UKUlF2zTxFraDZT/Elv8wzyH0fJL9h+Iv3TgB9lO63w89PRn3eFe+NRA1bhVgikKNPQ==}
cpu: [arm64]
os: [android]
- '@oven/bun-linux-aarch64-musl@1.3.14':
- resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==}
+ '@oven/bun-linux-aarch64-musl@1.4.2':
+ resolution: {integrity: sha512-+Sm6y+lSiSFBOtXmnekp5Q6n1tUKlyv71FCPWBc61Cgb14T5eBs8SN/nh4MUCOKzONkI3O+as3MGUgikS4aCBQ==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
- '@oven/bun-linux-aarch64@1.3.14':
- resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==}
+ '@oven/bun-linux-aarch64@1.4.2':
+ resolution: {integrity: sha512-3BBP9ovJ2RGHFH6Ae1CAtxNtG1+YY6GD6rmYbsUosoAk9+OEl6zeDQ/k4fBkc6dYOJCtWnx8hUxzNzQATSmvYQ==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
- '@oven/bun-linux-x64-android@1.3.14':
- resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==}
+ '@oven/bun-linux-x64-android@1.4.2':
+ resolution: {integrity: sha512-6HC5tzcC79113n2IHCTJMWv+HsQImv4ZFEK2XpYLxY6HbT8tM4cUM2Zv1bHZBQsS3jv/zYBamDJ1UX7If0d5tw==}
cpu: [x64]
os: [android]
- '@oven/bun-linux-x64-baseline@1.3.14':
- resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==}
- cpu: [x64]
- os: [linux]
-
- '@oven/bun-linux-x64-musl-baseline@1.3.14':
- resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==}
- cpu: [x64]
- os: [linux]
-
- '@oven/bun-linux-x64-musl@1.3.14':
- resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==}
+ '@oven/bun-linux-x64-musl@1.4.2':
+ resolution: {integrity: sha512-vVTKUg1bnPhRP/Hp73jIVoFh2vPFNYEqYX0ERKfZBOQEEHitNAeukZzzuUDZS0SoDCIpuWUGSpd/CDMbjdR+Uw==}
cpu: [x64]
os: [linux]
+ libc: [musl]
- '@oven/bun-linux-x64@1.3.14':
- resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==}
+ '@oven/bun-linux-x64@1.4.2':
+ resolution: {integrity: sha512-9/E/UXOTpSo3YsV5g+FhtTd/qTpiWoKuxS12cqtuYA1ssu9fRAoPQnipFgGyck3tWO63iUdxBiygq+kELFawng==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
- '@oven/bun-windows-aarch64@1.3.14':
- resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==}
+ '@oven/bun-windows-aarch64@1.4.2':
+ resolution: {integrity: sha512-8EJ1ST7339WJE3poPW5nBgVW/lWf9HBz4W27ZUNhburKmcBLOByPyE6DP9fHD8FQGm5c+ilUN2hX1mrW0jxq9Q==}
cpu: [arm64]
os: [win32]
- '@oven/bun-windows-x64-baseline@1.3.14':
- resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==}
+ '@oven/bun-windows-x64@1.4.2':
+ resolution: {integrity: sha512-+bN6OuVld/9diT/RLSXSW7JE6CvNE3gL9XsAEjULi1nUsXd6DNO6GuA9jNdNb3r8PdJFnYHr5aypNV1Oj3Rd9g==}
cpu: [x64]
os: [win32]
- '@oven/bun-windows-x64@1.3.14':
- resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==}
- cpu: [x64]
- os: [win32]
-
- '@oxc-project/types@0.138.0':
- resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==}
+ '@oxc-project/types@0.151.0':
+ resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==}
'@oxfmt/binding-android-arm-eabi@0.35.0':
resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==}
@@ -959,100 +1167,101 @@ packages:
resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
engines: {node: ^14.18.0 || >=16.0.0}
- '@quansync/fs@1.0.0':
- resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
+ '@quansync/fs@1.1.0':
+ resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==}
+
+ '@rolldown/binding-android-arm-eabi@1.2.10':
+ resolution: {integrity: sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
- '@rolldown/binding-android-arm64@1.1.4':
- resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==}
+ '@rolldown/binding-android-arm64@1.2.10':
+ resolution: {integrity: sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
- '@rolldown/binding-darwin-arm64@1.1.4':
- resolution: {integrity: sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==}
+ '@rolldown/binding-darwin-arm64@1.2.10':
+ resolution: {integrity: sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
- '@rolldown/binding-darwin-x64@1.1.4':
- resolution: {integrity: sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==}
+ '@rolldown/binding-darwin-x64@1.2.10':
+ resolution: {integrity: sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
- '@rolldown/binding-freebsd-x64@1.1.4':
- resolution: {integrity: sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==}
+ '@rolldown/binding-freebsd-x64@1.2.10':
+ resolution: {integrity: sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
- '@rolldown/binding-linux-arm-gnueabihf@1.1.4':
- resolution: {integrity: sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==}
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.10':
+ resolution: {integrity: sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
- '@rolldown/binding-linux-arm64-gnu@1.1.4':
- resolution: {integrity: sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==}
+ '@rolldown/binding-linux-arm64-gnu@1.2.10':
+ resolution: {integrity: sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-arm64-musl@1.1.4':
- resolution: {integrity: sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==}
+ '@rolldown/binding-linux-arm64-musl@1.2.10':
+ resolution: {integrity: sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@rolldown/binding-linux-ppc64-gnu@1.1.4':
- resolution: {integrity: sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==}
+ '@rolldown/binding-linux-ppc64-gnu@1.2.10':
+ resolution: {integrity: sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-s390x-gnu@1.1.4':
- resolution: {integrity: sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==}
+ '@rolldown/binding-linux-s390x-gnu@1.2.10':
+ resolution: {integrity: sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-x64-gnu@1.1.4':
- resolution: {integrity: sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==}
+ '@rolldown/binding-linux-x64-gnu@1.2.10':
+ resolution: {integrity: sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-x64-musl@1.1.4':
- resolution: {integrity: sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==}
+ '@rolldown/binding-linux-x64-musl@1.2.10':
+ resolution: {integrity: sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
- '@rolldown/binding-openharmony-arm64@1.1.4':
- resolution: {integrity: sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==}
+ '@rolldown/binding-openharmony-arm64@1.2.10':
+ resolution: {integrity: sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
- '@rolldown/binding-wasm32-wasi@1.1.4':
- resolution: {integrity: sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==}
- engines: {node: ^20.19.0 || >=22.12.0}
- cpu: [wasm32]
-
- '@rolldown/binding-win32-arm64-msvc@1.1.4':
- resolution: {integrity: sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==}
+ '@rolldown/binding-win32-arm64-msvc@1.2.10':
+ resolution: {integrity: sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
- '@rolldown/binding-win32-x64-msvc@1.1.4':
- resolution: {integrity: sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==}
+ '@rolldown/binding-win32-x64-msvc@1.2.10':
+ resolution: {integrity: sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -1075,6 +1284,10 @@ packages:
resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==}
engines: {node: '>=18'}
+ '@simple-libs/child-process-utils@2.0.0':
+ resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==}
+ engines: {node: '>=22'}
+
'@simple-libs/stream-utils@1.2.0':
resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==}
engines: {node: '>=18'}
@@ -1091,9 +1304,6 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
- '@standard-schema/spec@1.1.0':
- resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
-
'@trivago/prettier-plugin-sort-imports@6.0.2':
resolution: {integrity: sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==}
engines: {node: '>= 20'}
@@ -1113,8 +1323,35 @@ packages:
svelte:
optional: true
- '@tybys/wasm-util@0.10.3':
- resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
+ '@turbo/darwin-64@2.11.3':
+ resolution: {integrity: sha512-D4EIKTgPpxFQL5aZNbNqy57XOiRldM+mwP1ME9ZLM4DJoV3oNK7GKyQWVusz1umjiFNmLvunNGw7Jq4RUyYL3A==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@turbo/darwin-arm64@2.11.3':
+ resolution: {integrity: sha512-WOvgWIO86LzBQmL9aRFekROing1CZMVDjmdD7Dk6R65qFAFdrlKkufojenjW1AbUAqytpG6xsXzq8UEScdSdDQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@turbo/linux-64@2.11.3':
+ resolution: {integrity: sha512-zIgNl9KkMnsdU54JrnazELatpMJDSktKjD3kjKVliUNMl/5uhFRrqQ+Z1qaACKw/2ybL7xzlsHir9s5q7sYlQg==}
+ cpu: [x64]
+ os: [android, linux]
+
+ '@turbo/linux-arm64@2.11.3':
+ resolution: {integrity: sha512-aJToWHx5JYvpPwIrAt1DuYrHWcpQGjGf0XVPRMKQBHou//JUYR6oowHeUZp9Oq7e3XDWA75E37C2GnNaBqCVnA==}
+ cpu: [arm64]
+ os: [android, linux]
+
+ '@turbo/windows-64@2.11.3':
+ resolution: {integrity: sha512-hu5biiwVGdMvHB/vvaoPjtudNBn3TqcsKK2A06ap3O0wnSZwAtTXe92tKTckpecDeVs+IC+4r8s2wXpbdSPWoA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@turbo/windows-arm64@2.11.3':
+ resolution: {integrity: sha512-naDEb/31UmOOJOChi+LebLMKWKa1kId0yig1yTRZzDViDg1lBw1Ptu5iQYCvOfOOrx6swUUucf3zPWuPGWz7Jw==}
+ cpu: [arm64]
+ os: [win32]
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -1134,14 +1371,11 @@ packages:
'@types/inquirer@9.0.10':
resolution: {integrity: sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA==}
- '@types/jsesc@2.5.1':
- resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==}
-
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/node@26.1.0':
- resolution: {integrity: sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
'@types/through@0.0.33':
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
@@ -1149,79 +1383,84 @@ packages:
'@types/validator@13.15.10':
resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==}
- '@typescript-eslint/eslint-plugin@8.62.1':
- resolution: {integrity: sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==}
+ '@typescript-eslint/eslint-plugin@8.70.1':
+ resolution: {integrity: sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.62.1
+ '@typescript-eslint/parser': ^8.70.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.62.1':
- resolution: {integrity: sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==}
+ '@typescript-eslint/parser@8.70.1':
+ resolution: {integrity: sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.62.1':
- resolution: {integrity: sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==}
+ '@typescript-eslint/project-service@8.70.1':
+ resolution: {integrity: sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/scope-manager@8.62.1':
- resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==}
+ '@typescript-eslint/scope-manager@8.70.1':
+ resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.62.1':
- resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==}
+ '@typescript-eslint/tsconfig-utils@8.70.1':
+ resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.62.1':
- resolution: {integrity: sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==}
+ '@typescript-eslint/type-utils@8.70.1':
+ resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/types@8.62.1':
- resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==}
+ '@typescript-eslint/types@8.70.1':
+ resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.62.1':
- resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==}
+ '@typescript-eslint/typescript-estree@8.70.1':
+ resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.62.1':
- resolution: {integrity: sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==}
+ '@typescript-eslint/utils@8.70.1':
+ resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/visitor-keys@8.62.1':
- resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==}
+ '@typescript-eslint/visitor-keys@8.70.1':
+ resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@vitest/coverage-v8@4.1.10':
- resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==}
+ '@vitest/coverage-v8@5.0.1':
+ resolution: {integrity: sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==}
peerDependencies:
- '@vitest/browser': 4.1.10
- vitest: 4.1.10
+ '@vitest/browser': 5.0.1
+ vitest: 5.0.1
peerDependenciesMeta:
'@vitest/browser':
optional: true
- '@vitest/expect@4.1.10':
- resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
+ '@vitest/istanbul-lib-coverage@1.0.1':
+ resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==}
+ engines: {node: '>=22'}
- '@vitest/mocker@4.1.10':
- resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
+ '@vitest/istanbul-lib-report@1.0.1':
+ resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==}
+ engines: {node: '>=22'}
+
+ '@vitest/mocker@5.0.1':
+ resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -1231,28 +1470,151 @@ packages:
vite:
optional: true
- '@vitest/pretty-format@4.1.10':
- resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
+ '@vitest/spy@5.0.1':
+ resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==}
+
+ '@yuku-codegen/binding-android-arm64@0.10.2':
+ resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@yuku-codegen/binding-darwin-arm64@0.10.2':
+ resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@yuku-codegen/binding-darwin-x64@0.10.2':
+ resolution: {integrity: sha512-AJOUpR2s3LF9AWiiub/Uyc/UoXNTWcq8hK3cVI6fUSXtwh34dG21J6hRuNlO6JVJFTo4o3ScVvZOrh/YFfUEAA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@yuku-codegen/binding-freebsd-x64@0.10.2':
+ resolution: {integrity: sha512-ms7DcZu87u5CiK/wMwvpKAvAmtaqKjCQIXNweMLypG6qbuiaOMQf1jpbB5+j46xBcgCovo8TQMcv0bCQLKIL9w==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@yuku-codegen/binding-linux-arm-gnu@0.10.2':
+ resolution: {integrity: sha512-xJDpYAsKV5+eaiBhTYl05fvT6sst4PsCeuIFMRu9b0/WCSrNQPfCDtAQ5/MS6xNpsdujdZEPR+gmfWk3ZG5i3A==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@yuku-codegen/binding-linux-arm-musl@0.10.2':
+ resolution: {integrity: sha512-qfTkgd7AEx61l4K9VtXWCGTxc/fmXJ4ZheDz3i+/AAJUtg4sa0bPrc0VBxPggB/rayR8Z3+JfrBItuyanBJJ9Q==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@yuku-codegen/binding-linux-arm64-gnu@0.10.2':
+ resolution: {integrity: sha512-4e6Mifm/4UdjtU3D8mATIrvgP+xEiH8xtQOjd0zpd72XKWT7ug3sdyhq2utkkZFP6BvYp7SgZrI+aR4VeIOHdw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@yuku-codegen/binding-linux-arm64-musl@0.10.2':
+ resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
- '@vitest/runner@4.1.10':
- resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
+ '@yuku-codegen/binding-linux-x64-gnu@0.10.2':
+ resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
- '@vitest/snapshot@4.1.10':
- resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
+ '@yuku-codegen/binding-linux-x64-musl@0.10.2':
+ resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
- '@vitest/spy@4.1.10':
- resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
+ '@yuku-codegen/binding-win32-arm64@0.10.2':
+ resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==}
+ cpu: [arm64]
+ os: [win32]
- '@vitest/utils@4.1.10':
- resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
+ '@yuku-codegen/binding-win32-x64@0.10.2':
+ resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@yuku-parser/binding-android-arm64@0.10.2':
+ resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@yuku-parser/binding-darwin-arm64@0.10.2':
+ resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@yuku-parser/binding-darwin-x64@0.10.2':
+ resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@yuku-parser/binding-freebsd-x64@0.10.2':
+ resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@yuku-parser/binding-linux-arm-gnu@0.10.2':
+ resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@yuku-parser/binding-linux-arm-musl@0.10.2':
+ resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@yuku-parser/binding-linux-arm64-gnu@0.10.2':
+ resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@yuku-parser/binding-linux-arm64-musl@0.10.2':
+ resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@yuku-parser/binding-linux-x64-gnu@0.10.2':
+ resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@yuku-parser/binding-linux-x64-musl@0.10.2':
+ resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@yuku-parser/binding-win32-arm64@0.10.2':
+ resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@yuku-parser/binding-win32-x64@0.10.2':
+ resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@yuku-toolchain/types@0.10.2':
+ resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
- acorn@8.17.0:
- resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
+ acorn@8.18.0:
+ resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -1274,31 +1636,31 @@ packages:
resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
engines: {node: '>=18'}
- ansi-regex@6.2.2:
- resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ ansi-regex@6.3.0:
+ resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
engines: {node: '>=12'}
ansi-styles@6.2.3:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
- ansis@4.3.1:
- resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==}
+ ansis@4.4.0:
+ resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==}
engines: {node: '>=14'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ argue-cli@3.2.0:
+ resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==}
+ engines: {node: '>=22'}
+
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
- ast-kit@3.0.0:
- resolution: {integrity: sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
- ast-v8-to-istanbul@1.0.4:
- resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==}
+ ast-v8-to-istanbul@1.0.7:
+ resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1310,25 +1672,22 @@ packages:
before-after-hook@4.0.0:
resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==}
- birpc@4.0.0:
- resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==}
-
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
- brace-expansion@2.1.1:
- resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
+ brace-expansion@2.1.7:
+ resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
- brace-expansion@5.0.7:
- resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
- engines: {node: 18 || 20 || >=22}
+ brace-expansion@5.0.12:
+ resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
+ engines: {node: 20 || >=22}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- bun@1.3.14:
- resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==}
+ bun@1.4.2:
+ resolution: {integrity: sha512-TrSXo6HJfIEaczpb3kjX82I2pL47vK1QUNmHRCUdz9IzaOwa9lzOXSWwu2l18YHE3sNfGRapVLd4nNm+22vVVA==}
cpu: [arm64, x64]
os: [darwin, linux, android, freebsd, win32]
hasBin: true
@@ -1341,6 +1700,9 @@ packages:
resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==}
engines: {node: '>=20.19.0'}
+ cacheable@2.5.0:
+ resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==}
+
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -1382,10 +1744,6 @@ packages:
resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
engines: {node: '>=18.20'}
- cli-truncate@5.2.0:
- resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
- engines: {node: '>=20'}
-
cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
@@ -1405,16 +1763,16 @@ packages:
resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==}
engines: {node: '>=22.12.0'}
- content-type@2.0.0:
- resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
- engines: {node: '>=18'}
+ content-type@3.1.1:
+ resolution: {integrity: sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==}
+ engines: {node: '>=22'}
- conventional-changelog-angular@9.2.1:
- resolution: {integrity: sha512-oWSL6ZhnXbYraOFTK3PgRAQJ8fADDAEv5K6AdeyQPLvjFmhG8+ejL0jZZp/R7vTmGJaBvZEE+sE7dB4bCv7sAw==}
+ conventional-changelog-angular@9.4.0:
+ resolution: {integrity: sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==}
engines: {node: '>=22'}
- conventional-changelog-conventionalcommits@10.2.1:
- resolution: {integrity: sha512-n4Kr1HFMTf3iMbES0TMxKIcYtUUv4rKqyQQp2JwfOEfFCOfGT3Tq4mCyJ8S9/YPyWhydjfKrrvnyl+gCjA+mJQ==}
+ conventional-changelog-conventionalcommits@10.4.0:
+ resolution: {integrity: sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==}
engines: {node: '>=22'}
conventional-changelog-preset-loader@5.0.0:
@@ -1430,8 +1788,8 @@ packages:
engines: {node: '>=18'}
hasBin: true
- conventional-commits-parser@7.0.1:
- resolution: {integrity: sha512-6VtskFpPsNkGVk/TY2RnV/MEdKfvCPBtQZN9x8jh9+k5RWBQ+tiaWn5UFCzTr0Dd88iKx7xghxbjBRp5uIzp3g==}
+ conventional-commits-parser@7.1.2:
+ resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==}
engines: {node: '>=22'}
hasBin: true
@@ -1440,9 +1798,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
- convert-source-map@2.0.0:
- resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
-
cosmiconfig-typescript-loader@6.3.0:
resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==}
engines: {node: '>=v18'}
@@ -1460,10 +1815,19 @@ packages:
typescript:
optional: true
+ cpx2@9.0.3:
+ resolution: {integrity: sha512-SHeJspK34eVoLWuCrBW6YdcflnZwJifgADJNRkitsuZN2bi4+iby4slavs1qANeNiiZnIWRFG4vNcHp8UHrE5g==}
+ engines: {node: ^22.0.0 || >=24.0.0, npm: '>=10'}
+ hasBin: true
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ debounce@3.0.0:
+ resolution: {integrity: sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==}
+ engines: {node: '>=20'}
+
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -1480,8 +1844,8 @@ packages:
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
engines: {node: '>=18'}
- default-browser@5.5.0:
- resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
+ default-browser@5.5.1:
+ resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==}
engines: {node: '>=18'}
define-lazy-prop@3.0.0:
@@ -1502,6 +1866,11 @@ packages:
resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
engines: {node: '>=12'}
+ dotenv@18.0.3:
+ resolution: {integrity: sha512-/8CJTyL817qPnfbQrJgOEcrU3fDrJkRIoXBE566wHuvIXKOCHUZhPqGPbCPi9SogjdGnreF0TlvnTgK6J/8zpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
dts-resolver@3.0.0:
resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==}
engines: {node: ^22.18.0 || >=24.0.0}
@@ -1517,8 +1886,8 @@ packages:
emojilib@2.4.0:
resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
- empathic@2.0.1:
- resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==}
+ empathic@2.1.0:
+ resolution: {integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==}
engines: {node: '>=14'}
env-paths@2.2.1:
@@ -1532,11 +1901,11 @@ packages:
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
- es-module-lexer@2.3.0:
- resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==}
+ es-module-lexer@2.3.2:
+ resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
- es-toolkit@1.49.0:
- resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==}
+ es-toolkit@1.52.0:
+ resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
@@ -1598,8 +1967,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@10.6.0:
- resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==}
+ eslint@10.11.0:
+ resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1631,9 +2000,6 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
- eventemitter3@5.0.4:
- resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
-
execa@9.6.1:
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
engines: {node: ^18.19.0 || >=20.5.0}
@@ -1660,8 +2026,8 @@ packages:
fast-string-width@3.0.2:
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
- fast-uri@3.1.3:
- resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==}
+ fast-uri@3.1.8:
+ resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==}
fast-wrap-ansi@0.2.2:
resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
@@ -1679,24 +2045,25 @@ packages:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
- file-entry-cache@8.0.0:
- resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
- engines: {node: '>=16.0.0'}
+ file-entry-cache@11.1.5:
+ resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
+ find-index@0.1.1:
+ resolution: {integrity: sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==}
+
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
- flat-cache@4.0.1:
- resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
- engines: {node: '>=16'}
+ flat-cache@6.1.23:
+ resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==}
- flatted@3.4.2:
- resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
+ flatted@3.4.4:
+ resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
@@ -1707,63 +2074,65 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-east-asian-width@1.6.0:
- resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
+ get-east-asian-width@1.7.0:
+ resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==}
engines: {node: '>=18'}
get-stream@9.0.1:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
- get-tsconfig@5.0.0-beta.5:
- resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==}
+ get-tsconfig@5.0.0-beta.6:
+ resolution: {integrity: sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw==}
engines: {node: '>=20.20.0'}
- git-cliff-darwin-arm64@2.13.1:
- resolution: {integrity: sha512-3ebPUnUlLmrSZDHknSZQmyZV7DEJVtKse8I25Am0cENET5Py9u9Hg9k8IRXdiDtHtPDs6MYZx7BOi11WtcfqSg==}
+ git-cliff-darwin-arm64@2.14.2:
+ resolution: {integrity: sha512-Ylk39hv5flEUrXeoGvH6M3WjHBNERFH7a9DVEcGU2a8Q7a7fPtVasNk1fMuJ7CVbZCRIzUNyQV0ejRpxBdFnag==}
cpu: [arm64]
os: [darwin]
- git-cliff-darwin-x64@2.13.1:
- resolution: {integrity: sha512-KZfggGAiw1EvZH3BOUclEU4eGCP2+Z+lH/N2Ni3FH9L2M1U7FYJqqaMhqgO8azTj67betvDshH8WBUkIkSsVxA==}
+ git-cliff-darwin-x64@2.14.2:
+ resolution: {integrity: sha512-3FZNm0BGCIim/3G6uHSWAakrCaGmC47lcI+Cob9CZPMBYHKrenYk0i6B8MlObSXr2Lr7Ve1BpGbd/foQg4NOiA==}
cpu: [x64]
os: [darwin]
- git-cliff-linux-arm64@2.13.1:
- resolution: {integrity: sha512-clNRcNzdvk4GKyTt3ZhhWqcrjVRghcUcGNSV/Y87YJf3Mc/zl9ajUAhnXPOBSX/KWBr2od5SmkCt0qGYagY07g==}
+ git-cliff-linux-arm64@2.14.2:
+ resolution: {integrity: sha512-3oMDpoyfsy2angD9ulcCBtyWCkzZqN3spmmtb0jic8OKAu1p+CALS8oep7J8AdgkDojuLo6bdHiSZIM9PeTxmQ==}
cpu: [arm64]
os: [linux]
- git-cliff-linux-x64@2.13.1:
- resolution: {integrity: sha512-gZcIQhIQ1lDUMD8UieUSEZAYoyZN7nPd8O3VQoj1Ddhqll4UAS1Zxms1SU3U8pb0UTdc2m3ia/wtOnyhvbjdjQ==}
+ git-cliff-linux-x64@2.14.2:
+ resolution: {integrity: sha512-4EaBYwBiCupECZ2rzC6CelNSGYvkvtJeja4wXLDGtfKnRhCN/WNoqbM+hvrtm32uf5qovfoNoDA906F8itw+oQ==}
cpu: [x64]
os: [linux]
- git-cliff-windows-arm64@2.13.1:
- resolution: {integrity: sha512-+XubuQv68DuDwF0u6Af5d889MEf/RD48VBQS7ZmPi4sUSCXAZqRm1/ApCsStLqxMDCf1+7s05B/kNbm9wjV80A==}
+ git-cliff-windows-arm64@2.14.2:
+ resolution: {integrity: sha512-0oc0xD/6A+BvInz8imBJulXVOlR9M0nL+d3ibL0lwJvxUVTMQzvoCnsn9OxpsdzXrEisnTvaf+GExoKVJkUcDw==}
cpu: [arm64]
os: [win32]
- git-cliff-windows-x64@2.13.1:
- resolution: {integrity: sha512-Bi8ehp1VMkomY7M/356PgovKQ8CBRiuOOkq+aWC2evQuMFfXWjG0GBlPED9QkZoUJ4iQ5ygB5DYdK3BjhaOyPw==}
+ git-cliff-windows-x64@2.14.2:
+ resolution: {integrity: sha512-RFmCMiwGMU8O+rF//wxT7L10ssoDtzI+QFxlbjpA8E1BAEafkmLTQkKalT88ipEweD46P+Sfc20v8RXuE7V/UA==}
cpu: [x64]
os: [win32]
- git-cliff@2.13.1:
- resolution: {integrity: sha512-2BzXwrom+SMHeNA5Ut+MtzqXejg0wbVwmzj3k7e9w62UQoyCDrM9UIcvtl6hnT3jocEQ1zLRQBaXXx97Gmnk7A==}
+ git-cliff@2.14.2:
+ resolution: {integrity: sha512-hlXIq4k2Tdj9C59WsD7FsLE0xRGFDozTUuoeqg9toMyVohaVxXiSiLnhM/IQyCmhOh5M4zuNckr2o8qMeuffbg==}
engines: {node: ^18.19 || >=20.6}
hasBin: true
- git-raw-commits@5.0.1:
- resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==}
- engines: {node: '>=18'}
- deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
- hasBin: true
-
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ glob2base@0.0.12:
+ resolution: {integrity: sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==}
+ engines: {node: '>= 0.10'}
+
+ glob@13.0.6:
+ resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
+ engines: {node: 18 || 20 || >=22}
+
global-directory@5.0.0:
resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==}
engines: {node: '>=20'}
@@ -1772,19 +2141,22 @@ packages:
resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
engines: {node: '>=18'}
- has-flag@4.0.0:
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
- engines: {node: '>=8'}
-
has-flag@5.0.1:
resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==}
engines: {node: '>=12'}
- hookable@6.1.1:
- resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==}
+ hashery@1.5.1:
+ resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==}
+ engines: {node: '>=20'}
+
+ hookable@6.1.2:
+ resolution: {integrity: sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==}
- html-escaper@2.0.2:
- resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ hookified@1.15.1:
+ resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
+
+ hookified@2.2.0:
+ resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==}
human-signals@8.0.1:
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
@@ -1803,16 +2175,16 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
- ignore@7.0.5:
- resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ ignore@7.0.10:
+ resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==}
engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
- import-without-cache@0.4.0:
- resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==}
+ import-without-cache@0.4.1:
+ resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==}
engines: {node: ^22.18.0 || >=24.0.0}
imurmurhash@0.1.4:
@@ -1839,10 +2211,6 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- is-fullwidth-code-point@5.1.0:
- resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
- engines: {node: '>=18'}
-
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -1883,18 +2251,6 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- istanbul-lib-coverage@3.2.2:
- resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
- engines: {node: '>=8'}
-
- istanbul-lib-report@3.0.1:
- resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
- engines: {node: '>=10'}
-
- istanbul-reports@3.2.0:
- resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
- engines: {node: '>=8'}
-
javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
@@ -1908,8 +2264,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- js-yaml@4.3.0:
- resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==}
+ js-yaml@4.3.2:
+ resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
hasBin: true
jsesc@3.1.0:
@@ -1917,9 +2273,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
- json-buffer@3.0.1:
- resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
-
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@@ -1932,108 +2285,104 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
- json-with-bigint@3.5.8:
- resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==}
+ json-with-bigint@3.5.12:
+ resolution: {integrity: sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==}
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
- keyv@4.5.4:
- resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ keyv@5.6.0:
+ resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
- libphonenumber-js@1.13.8:
- resolution: {integrity: sha512-80xal1m93rADejw2pMp2MSzFhHCPLEspjHxnH2UtqI+DgAmElsbmLMiqk9niwH9NWAfjsRtaJI+qBrOEmRx9nQ==}
+ libphonenumber-js@1.13.13:
+ resolution: {integrity: sha512-hV6SmWnyQ8U1QXhhqbqYBHIStNnscG7A5XbGodgdHhf/8N5PwhYpJA5U/yHDjYwPYTd8uNGXqq416XctuvK2PA==}
- lightningcss-android-arm64@1.32.0:
- resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
- lightningcss-darwin-arm64@1.32.0:
- resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
- lightningcss-darwin-x64@1.32.0:
- resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
- lightningcss-freebsd-x64@1.32.0:
- resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
- lightningcss-linux-arm-gnueabihf@1.32.0:
- resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
- lightningcss-linux-arm64-gnu@1.32.0:
- resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
- lightningcss-linux-arm64-musl@1.32.0:
- resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
- lightningcss-linux-x64-gnu@1.32.0:
- resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
- lightningcss-linux-x64-musl@1.32.0:
- resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
- lightningcss-win32-arm64-msvc@1.32.0:
- resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
- lightningcss-win32-x64-msvc@1.32.0:
- resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
- lightningcss@1.32.0:
- resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
engines: {node: '>= 12.0.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
- lint-staged@17.0.8:
- resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==}
+ lint-staged@17.5.1:
+ resolution: {integrity: sha512-7EDuco1xnBMeVpvAbeMq1U5KXwJGLmY8q6l+Ye78r36C4mPc+Vg1Z2SK8gHyRTyvPro90A0q5/FAZ+Au23d6QQ==}
engines: {node: '>=22.22.1'}
hasBin: true
- listr2@10.2.2:
- resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==}
- engines: {node: '>=22.13.0'}
-
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -2045,28 +2394,23 @@ packages:
resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==}
engines: {node: '>=18'}
- log-update@6.1.0:
- resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
- engines: {node: '>=18'}
+ lru-cache@11.5.3:
+ resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==}
+ engines: {node: 20 || >=22}
- magic-string@0.30.21:
- resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ magic-string@1.0.0:
+ resolution: {integrity: sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==}
- magicast@0.5.3:
- resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==}
+ magic-string@1.4.2:
+ resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==}
- make-dir@4.0.0:
- resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
- engines: {node: '>=10'}
+ magicast@0.5.5:
+ resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==}
meow@13.2.0:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
- meow@14.1.0:
- resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==}
- engines: {node: '>=20'}
-
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -2075,14 +2419,17 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
- minimatch@10.2.5:
- resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ minimatch@10.2.6:
+ resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
engines: {node: 18 || 20 || >=22}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
engines: {node: '>=16 || 14 >=14.17'}
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
minipass@7.1.3:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -2098,8 +2445,8 @@ packages:
resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
engines: {node: ^20.17.0 || >=22.9.0}
- nanoid@3.3.15:
- resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
+ nanoid@3.3.19:
+ resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -2114,29 +2461,29 @@ packages:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
- obug@2.1.3:
- resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
+ obug@2.2.1:
+ resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
engines: {node: '>=12.20.0'}
- ohash@2.0.11:
- resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+ obug@3.0.0:
+ resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==}
+ engines: {node: '>=12.20.0'}
+
+ ohash@2.0.12:
+ resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==}
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
- open@11.0.0:
- resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
+ open@11.0.4:
+ resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==}
engines: {node: '>=20'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
- ora@9.4.0:
- resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==}
- engines: {node: '>=20'}
-
ora@9.4.1:
resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==}
engines: {node: '>=20'}
@@ -2154,6 +2501,10 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
+ p-map@7.0.8:
+ resolution: {integrity: sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==}
+ engines: {node: '>=18'}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2184,8 +2535,9 @@ packages:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
- pathe@2.0.3:
- resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ path-scurry@2.0.2:
+ resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
+ engines: {node: 18 || 20 || >=22}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2194,26 +2546,30 @@ packages:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
- picomatch@4.0.4:
- resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
- engines: {node: '>=12'}
-
picomatch@4.0.5:
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'}
+ picomatch@4.0.7:
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
plur@5.1.0:
resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- postcss@8.5.16:
- resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==}
+ postcss@8.5.28:
+ resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
+ powershell-utils@0.2.1:
+ resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==}
+ engines: {node: '>=20'}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -2222,27 +2578,31 @@ packages:
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
engines: {node: '>=6.0.0'}
- prettier@3.9.4:
- resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==}
+ prettier@3.9.9:
+ resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
engines: {node: '>=14'}
hasBin: true
- pretty-ms@9.3.0:
- resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
+ pretty-ms@9.3.1:
+ resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==}
engines: {node: '>=18'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ qified@0.10.1:
+ resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
+ engines: {node: '>=20'}
+
quansync@1.0.0:
resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
- rc9@3.0.1:
- resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
+ rc9@3.1.0:
+ resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==}
- readdirp@5.0.0:
- resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
+ readdirp@5.1.1:
+ resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
engines: {node: '>= 20.19.0'}
reflect-metadata@0.2.2:
@@ -2267,30 +2627,30 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
- rfdc@1.4.1:
- resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
-
- rolldown-plugin-dts@0.26.0:
- resolution: {integrity: sha512-e+kEPtUiDES0htk5iqkSeF4EzAV7R+vugGB44iPDuw1Kw9E+WyL1VG7PaV0IIjGHLiacztMBcMTyrr8ON9CT1Q==}
- engines: {node: ^22.18.0 || >=24.11.0}
+ rolldown-plugin-dts@0.28.6:
+ resolution: {integrity: sha512-qKrFtBfRfR2hP233m7Ic9zf3wv6MSYdZghWKMdEO6dRYZGlNfINJAa1P5ZVvyHh3mrcd7IJQvIY8L9vnm+v5rQ==}
+ engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0}
peerDependencies:
- '@ts-macro/tsc': ^0.3.6
- '@typescript/native-preview': '>=7.0.0-dev.20260325.1'
- rolldown: ^1.0.0
- typescript: ^5.0.0 || ^6.0.0
+ '@typescript/native-preview': '*'
+ '@volar/typescript': ~2.4.0
+ '@vue/language-core': ~3.2.0 || ~3.3.0
+ rolldown: ^1.2.0
+ typescript: ^5.0.0 || ^6.0.0 || ~7.0.0
vue-tsc: ~3.2.0 || ~3.3.0
peerDependenciesMeta:
- '@ts-macro/tsc':
- optional: true
'@typescript/native-preview':
optional: true
+ '@volar/typescript':
+ optional: true
+ '@vue/language-core':
+ optional: true
typescript:
optional: true
vue-tsc:
optional: true
- rolldown@1.1.4:
- resolution: {integrity: sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==}
+ rolldown@1.2.10:
+ resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -2317,6 +2677,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.10.0:
+ resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==}
+ engines: {node: '>= 0.4'}
+
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -2328,16 +2692,8 @@ packages:
resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
engines: {node: '>=8'}
- slice-ansi@7.1.2:
- resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
- engines: {node: '>=18'}
-
- slice-ansi@8.0.0:
- resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
- engines: {node: '>=20'}
-
- smol-toml@1.7.0:
- resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==}
+ smol-toml@1.9.0:
+ resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==}
engines: {node: '>= 18'}
source-map-js@1.2.1:
@@ -2351,8 +2707,8 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- std-env@4.1.0:
- resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
+ std-env@4.2.0:
+ resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
stdin-discarder@0.3.2:
resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==}
@@ -2366,8 +2722,8 @@ packages:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
- string-width@8.2.1:
- resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
+ string-width@8.2.2:
+ resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
engines: {node: '>=20'}
strip-ansi@7.2.0:
@@ -2378,14 +2734,13 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
+ subarg@1.0.0:
+ resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==}
+
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
- supports-color@7.2.0:
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
- engines: {node: '>=8'}
-
supports-hyperlinks@4.5.0:
resolution: {integrity: sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==}
engines: {node: '>=20'}
@@ -2394,15 +2749,20 @@ packages:
resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
engines: {node: ^14.18.0 || >=16.0.0}
- tar@7.5.19:
- resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==}
+ tar@7.5.22:
+ resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==}
engines: {node: '>=18'}
- tinybench@2.9.0:
- resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+ tinybench@6.1.4:
+ resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==}
+ engines: {node: '>=20.0.0'}
+
+ tinyexec@1.3.0:
+ resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
+ engines: {node: '>=18'}
- tinyexec@1.2.4:
- resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
+ tinyexec@1.3.1:
+ resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
engines: {node: '>=18'}
tinyglobby@0.2.17:
@@ -2413,8 +2773,8 @@ packages:
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
engines: {node: ^20.0.0 || >=22.0.0}
- tinyrainbow@3.1.0:
- resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
+ tinyrainbow@3.1.1:
+ resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
engines: {node: '>=14.0.0'}
to-regex-range@5.0.1:
@@ -2431,19 +2791,19 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
- tsdown@0.22.3:
- resolution: {integrity: sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==}
- engines: {node: ^22.18.0 || >=24.11.0}
+ tsdown@0.23.0:
+ resolution: {integrity: sha512-BaT+ep1xnj5hdyICLd5r5SYYE+TXnI1ATePLykv9vcKpHpFTWUWRH+x1AF/E2qcu3YB6+vI8IdyxIIIffEqw5Q==}
+ engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0}
hasBin: true
peerDependencies:
'@arethetypeswrong/core': ^0.18.1
- '@tsdown/css': 0.22.3
- '@tsdown/exe': 0.22.3
+ '@tsdown/css': 0.23.0
+ '@tsdown/exe': 0.23.0
'@vitejs/devtools': '*'
publint: ^0.3.8
tsx: '*'
- typescript: ^5.0.0 || ^6.0.0
- unplugin-unused: ^0.5.0
+ typescript: ^5.0.0 || ^6.0.0 || ^7.0.0
+ unplugin-unused: '>=0.5.0'
unrun: '*'
peerDependenciesMeta:
'@arethetypeswrong/core':
@@ -2472,12 +2832,16 @@ packages:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
+ turbo@2.11.3:
+ resolution: {integrity: sha512-Pfg2dWNOUPwJYbR+eYAcjoBbo4Kmoc8uQb80hzKrBgNEQtxvwf2ucXG7C28MyLVhV/zJSKvChrR+2CP/BONpoA==}
+ hasBin: true
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- typescript-eslint@8.62.1:
- resolution: {integrity: sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==}
+ typescript-eslint@8.70.1:
+ resolution: {integrity: sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2491,11 +2855,11 @@ packages:
unconfig-core@7.5.0:
resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==}
- undici-types@8.3.0:
- resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
+ undici-types@8.9.0:
+ resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
- undici@6.27.0:
- resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==}
+ undici@6.28.1:
+ resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==}
engines: {node: '>=18.17'}
unicode-emoji-modifier-base@1.0.0:
@@ -2509,6 +2873,16 @@ packages:
universal-user-agent@7.0.3:
resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==}
+ unrun@0.3.1:
+ resolution: {integrity: sha512-onIck/oNnCaytwths1ZVp1LK2Gq2hPoyFhiHebObuUXqR3S0uHuLLaBK8K6mRRgV7Ptip8AnNvaUsgzwWwBZuA==}
+ engines: {node: ^22.13.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ synckit: ^0.11.11
+ peerDependenciesMeta:
+ synckit:
+ optional: true
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -2516,13 +2890,17 @@ packages:
resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==}
engines: {node: '>= 0.10'}
- vite@8.1.2:
- resolution: {integrity: sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ==}
+ verkit@0.4.1:
+ resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==}
+ engines: {node: '>=18.12.0'}
+
+ vite@8.3.0:
+ resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
- '@vitejs/devtools': ^0.3.0
+ '@vitejs/devtools': ^0.7.1
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
@@ -2559,23 +2937,23 @@ packages:
yaml:
optional: true
- vitest@4.1.10:
- resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
- engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ vitest@5.0.1:
+ resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==}
+ engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
- '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
- '@vitest/browser-playwright': 4.1.10
- '@vitest/browser-preview': 4.1.10
- '@vitest/browser-webdriverio': 4.1.10
- '@vitest/coverage-istanbul': 4.1.10
- '@vitest/coverage-v8': 4.1.10
- '@vitest/ui': 4.1.10
+ '@types/node': ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 5.0.1
+ '@vitest/browser-preview': 5.0.1
+ '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0
+ '@vitest/coverage-istanbul': 5.0.1
+ '@vitest/coverage-v8': 5.0.1
+ '@vitest/ui': 5.0.1
happy-dom: '*'
jsdom: '*'
- vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ vite: ^6.4.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
@@ -2614,16 +2992,12 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- wrap-ansi@10.0.0:
- resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==}
- engines: {node: '>=20'}
-
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
- wsl-utils@0.3.1:
- resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
+ wsl-utils@1.0.0:
+ resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==}
engines: {node: '>=20'}
y18n@5.0.8:
@@ -2634,8 +3008,8 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
- yaml@2.9.0:
- resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
+ yaml@2.9.1:
+ resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==}
engines: {node: '>= 14.6'}
hasBin: true
@@ -2643,18 +3017,30 @@ packages:
resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
- yargs@18.0.0:
- resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
+ yargs@18.2.0:
+ resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==}
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- yoctocolors@2.1.2:
- resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
+ yoctocolors@2.2.0:
+ resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==}
engines: {node: '>=18'}
+ yuku-ast@0.10.2:
+ resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==}
+
+ yuku-codegen@0.10.2:
+ resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==}
+
+ yuku-parser@0.10.2:
+ resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==}
+
+ zod@4.6.5:
+ resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
+
snapshots:
'@actions/core@3.0.1':
@@ -2669,51 +3055,51 @@ snapshots:
'@actions/github@9.1.1':
dependencies:
'@actions/http-client': 3.0.2
- '@octokit/core': 7.0.6
- '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6)
- '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6)
- '@octokit/request': 10.0.11
- '@octokit/request-error': 7.1.0
- undici: 6.27.0
+ '@octokit/core': 7.0.8
+ '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.8)
+ '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.8)
+ '@octokit/request': 10.0.16
+ '@octokit/request-error': 7.1.2
+ undici: 6.28.1
'@actions/http-client@3.0.2':
dependencies:
tunnel: 0.0.6
- undici: 6.27.0
+ undici: 6.28.1
'@actions/http-client@4.0.1':
dependencies:
tunnel: 0.0.6
- undici: 6.27.0
+ undici: 6.28.1
'@actions/io@3.0.2': {}
- '@angular-devkit/core@22.0.5(chokidar@5.0.0)':
+ '@angular-devkit/core@22.1.8(chokidar@5.0.0)':
dependencies:
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
jsonc-parser: 3.3.1
- picomatch: 4.0.4
+ picomatch: 4.0.5
rxjs: 7.8.2
source-map: 0.7.6
optionalDependencies:
chokidar: 5.0.0
- '@angular-devkit/schematics-cli@22.0.5(@types/node@26.1.0)(chokidar@5.0.0)':
+ '@angular-devkit/schematics-cli@22.1.8(@types/node@26.6.2)(chokidar@5.0.0)':
dependencies:
- '@angular-devkit/core': 22.0.5(chokidar@5.0.0)
- '@angular-devkit/schematics': 22.0.5(chokidar@5.0.0)
- '@inquirer/prompts': 8.4.2(@types/node@26.1.0)
+ '@angular-devkit/core': 22.1.8(chokidar@5.0.0)
+ '@angular-devkit/schematics': 22.1.8(chokidar@5.0.0)
+ '@inquirer/prompts': 8.5.2(@types/node@26.6.2)
transitivePeerDependencies:
- '@types/node'
- chokidar
- '@angular-devkit/schematics@22.0.5(chokidar@5.0.0)':
+ '@angular-devkit/schematics@22.1.8(chokidar@5.0.0)':
dependencies:
- '@angular-devkit/core': 22.0.5(chokidar@5.0.0)
+ '@angular-devkit/core': 22.1.8(chokidar@5.0.0)
jsonc-parser: 3.3.1
- magic-string: 0.30.21
- ora: 9.4.0
+ magic-string: 1.0.0
+ ora: 9.4.1
rxjs: 7.8.2
transitivePeerDependencies:
- chokidar
@@ -2724,130 +3110,136 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/generator@7.29.7':
+ '@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.7
- '@babel/types': 7.29.7
+ '@babel/parser': 7.29.9
+ '@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
- '@babel/generator@8.0.0':
- dependencies:
- '@babel/parser': 8.0.0
- '@babel/types': 8.0.0
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
- '@types/jsesc': 2.5.1
- jsesc: 3.1.0
-
'@babel/helper-globals@7.29.7': {}
'@babel/helper-string-parser@7.29.7': {}
- '@babel/helper-string-parser@8.0.0': {}
-
'@babel/helper-validator-identifier@7.29.7': {}
- '@babel/helper-validator-identifier@8.0.2': {}
-
- '@babel/parser@7.29.7':
+ '@babel/parser@7.29.9':
dependencies:
- '@babel/types': 7.29.7
-
- '@babel/parser@8.0.0':
- dependencies:
- '@babel/types': 8.0.0
+ '@babel/types': 7.29.8
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.7
- '@babel/types': 7.29.7
+ '@babel/parser': 7.29.9
+ '@babel/types': 7.29.8
- '@babel/traverse@7.29.7':
+ '@babel/traverse@7.29.8(supports-color@10.2.2)':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/generator': 7.29.7
+ '@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.7
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
- '@babel/types': 7.29.7
- debug: 4.4.3
+ '@babel/types': 7.29.8
+ debug: 4.4.3(supports-color@10.2.2)
transitivePeerDependencies:
- supports-color
- '@babel/types@7.29.7':
+ '@babel/types@7.29.8':
dependencies:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
- '@babel/types@8.0.0':
+ '@bcoe/v8-coverage@1.0.2': {}
+
+ '@cacheable/memory@2.2.0':
dependencies:
- '@babel/helper-string-parser': 8.0.0
- '@babel/helper-validator-identifier': 8.0.2
+ '@cacheable/utils': 2.5.0
+ '@keyv/bigmap': 1.3.1(keyv@5.6.0)
+ hookified: 1.15.1
+ keyv: 5.6.0
- '@bcoe/v8-coverage@1.0.2': {}
+ '@cacheable/utils@2.5.0':
+ dependencies:
+ hashery: 1.5.1
+ keyv: 5.6.0
- '@commitlint/cli@21.2.0(@types/node@26.1.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
+ '@commitlint/cli@21.2.3(@types/node@26.6.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
dependencies:
- '@commitlint/config-conventional': 21.2.0
- '@commitlint/format': 21.2.0
- '@commitlint/lint': 21.2.0
- '@commitlint/load': 21.2.0(@types/node@26.1.0)(typescript@6.0.3)
- '@commitlint/read': 21.2.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
- '@commitlint/types': 21.2.0
- tinyexec: 1.2.4
- yargs: 18.0.0
+ '@commitlint/config-conventional': 21.2.3
+ '@commitlint/format': 21.2.3
+ '@commitlint/lint': 21.2.3
+ '@commitlint/load': 21.2.3(@types/node@26.6.2)(typescript@6.0.3)
+ '@commitlint/read': 21.2.3(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
+ '@commitlint/types': 21.2.3
+ tinyexec: 1.3.1
+ yargs: 18.2.0
transitivePeerDependencies:
- '@types/node'
- conventional-commits-filter
- conventional-commits-parser
- typescript
- '@commitlint/config-conventional@21.2.0':
+ '@commitlint/cli@21.2.3(@types/node@26.6.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)':
dependencies:
- '@commitlint/types': 21.2.0
- conventional-changelog-conventionalcommits: 10.2.1
+ '@commitlint/config-conventional': 21.2.3
+ '@commitlint/format': 21.2.3
+ '@commitlint/lint': 21.2.3
+ '@commitlint/load': 21.2.3(@types/node@26.6.2)(typescript@6.0.3)
+ '@commitlint/read': 21.2.3(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)
+ '@commitlint/types': 21.2.3
+ tinyexec: 1.3.1
+ yargs: 18.2.0
+ transitivePeerDependencies:
+ - '@types/node'
+ - conventional-commits-filter
+ - conventional-commits-parser
+ - typescript
- '@commitlint/config-validator@21.2.0':
+ '@commitlint/config-conventional@21.2.3':
dependencies:
- '@commitlint/types': 21.2.0
+ '@commitlint/types': 21.2.3
+ conventional-changelog-conventionalcommits: 10.4.0
+
+ '@commitlint/config-validator@21.2.3':
+ dependencies:
+ '@commitlint/types': 21.2.3
ajv: 8.20.0
- '@commitlint/ensure@21.2.0':
+ '@commitlint/ensure@21.2.3':
dependencies:
- '@commitlint/types': 21.2.0
- es-toolkit: 1.49.0
+ '@commitlint/types': 21.2.3
+ es-toolkit: 1.52.0
'@commitlint/execute-rule@21.0.1': {}
- '@commitlint/format@21.2.0':
+ '@commitlint/format@21.2.3':
dependencies:
- '@commitlint/types': 21.2.0
+ '@commitlint/types': 21.2.3
picocolors: 1.1.1
- '@commitlint/is-ignored@21.2.0':
+ '@commitlint/is-ignored@21.2.3':
dependencies:
- '@commitlint/types': 21.2.0
+ '@commitlint/types': 21.2.3
semver: 7.8.5
- '@commitlint/lint@21.2.0':
+ '@commitlint/lint@21.2.3':
dependencies:
- '@commitlint/is-ignored': 21.2.0
- '@commitlint/parse': 21.2.0
- '@commitlint/rules': 21.2.0
- '@commitlint/types': 21.2.0
+ '@commitlint/is-ignored': 21.2.3
+ '@commitlint/parse': 21.2.3
+ '@commitlint/rules': 21.2.3
+ '@commitlint/types': 21.2.3
- '@commitlint/load@21.2.0(@types/node@26.1.0)(typescript@6.0.3)':
+ '@commitlint/load@21.2.3(@types/node@26.6.2)(typescript@6.0.3)':
dependencies:
- '@commitlint/config-validator': 21.2.0
+ '@commitlint/config-validator': 21.2.3
'@commitlint/execute-rule': 21.0.1
- '@commitlint/resolve-extends': 21.2.0
- '@commitlint/types': 21.2.0
+ '@commitlint/resolve-extends': 21.2.3
+ '@commitlint/types': 21.2.3
cosmiconfig: 9.0.2(typescript@6.0.3)
- cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3)
- es-toolkit: 1.49.0
+ cosmiconfig-typescript-loader: 6.3.0(@types/node@26.6.2)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3)
+ es-toolkit: 1.52.0
is-plain-obj: 4.1.0
picocolors: 1.1.1
transitivePeerDependencies:
@@ -2856,36 +3248,46 @@ snapshots:
'@commitlint/message@21.2.0': {}
- '@commitlint/parse@21.2.0':
+ '@commitlint/parse@21.2.3':
+ dependencies:
+ '@commitlint/types': 21.2.3
+ conventional-changelog-angular: 9.4.0
+ conventional-commits-parser: 7.1.2
+
+ '@commitlint/read@21.2.3(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)':
dependencies:
- '@commitlint/types': 21.2.0
- conventional-changelog-angular: 9.2.1
- conventional-commits-parser: 7.0.1
+ '@commitlint/top-level': 21.2.0
+ '@commitlint/types': 21.2.3
+ '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
+ tinyexec: 1.3.1
+ transitivePeerDependencies:
+ - conventional-commits-filter
+ - conventional-commits-parser
- '@commitlint/read@21.2.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)':
+ '@commitlint/read@21.2.3(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)':
dependencies:
'@commitlint/top-level': 21.2.0
- '@commitlint/types': 21.2.0
- git-raw-commits: 5.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
- tinyexec: 1.2.4
+ '@commitlint/types': 21.2.3
+ '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)
+ tinyexec: 1.3.1
transitivePeerDependencies:
- conventional-commits-filter
- conventional-commits-parser
- '@commitlint/resolve-extends@21.2.0':
+ '@commitlint/resolve-extends@21.2.3':
dependencies:
- '@commitlint/config-validator': 21.2.0
- '@commitlint/types': 21.2.0
- es-toolkit: 1.49.0
+ '@commitlint/config-validator': 21.2.3
+ '@commitlint/types': 21.2.3
+ es-toolkit: 1.52.0
global-directory: 5.0.0
resolve-from: 5.0.0
- '@commitlint/rules@21.2.0':
+ '@commitlint/rules@21.2.3':
dependencies:
- '@commitlint/ensure': 21.2.0
+ '@commitlint/ensure': 21.2.3
'@commitlint/message': 21.2.0
'@commitlint/to-lines': 21.0.1
- '@commitlint/types': 21.2.0
+ '@commitlint/types': 21.2.3
'@commitlint/to-lines@21.0.1': {}
@@ -2893,21 +3295,39 @@ snapshots:
dependencies:
escalade: 3.2.0
- '@commitlint/types@21.2.0':
+ '@commitlint/types@21.2.3':
dependencies:
- conventional-commits-parser: 7.0.1
+ conventional-commits-parser: 7.1.2
picocolors: 1.1.1
'@conventional-changelog/git-client@2.7.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)':
dependencies:
- '@simple-libs/child-process-utils': 1.0.2
- '@simple-libs/stream-utils': 1.2.0
+ '@simple-libs/child-process-utils': 1.0.2
+ '@simple-libs/stream-utils': 1.2.0
+ semver: 7.8.5
+ optionalDependencies:
+ conventional-commits-filter: 5.0.0
+ conventional-commits-parser: 6.4.0
+
+ '@conventional-changelog/git-client@3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)':
+ dependencies:
+ '@simple-libs/child-process-utils': 2.0.0
+ '@simple-libs/stream-utils': 2.0.0
semver: 7.8.5
optionalDependencies:
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.4.0
- '@conventional-changelog/template@1.2.1': {}
+ '@conventional-changelog/git-client@3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)':
+ dependencies:
+ '@simple-libs/child-process-utils': 2.0.0
+ '@simple-libs/stream-utils': 2.0.0
+ semver: 7.8.5
+ optionalDependencies:
+ conventional-commits-filter: 5.0.0
+ conventional-commits-parser: 7.1.2
+
+ '@conventional-changelog/template@1.4.0': {}
'@dprint/formatter@0.5.1': {}
@@ -2915,38 +3335,22 @@ snapshots:
'@dprint/toml@0.7.0': {}
- '@emnapi/core@1.11.1':
- dependencies:
- '@emnapi/wasi-threads': 1.2.2
- tslib: 2.8.1
- optional: true
-
- '@emnapi/runtime@1.11.1':
- dependencies:
- tslib: 2.8.1
- optional: true
-
- '@emnapi/wasi-threads@1.2.2':
- dependencies:
- tslib: 2.8.1
- optional: true
-
- '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.6.1))':
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))':
dependencies:
- eslint: 10.6.0(jiti@2.6.1)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/config-array@0.23.5':
+ '@eslint/config-array@0.23.5(supports-color@10.2.2)':
dependencies:
'@eslint/object-schema': 3.0.5
- debug: 4.4.3
- minimatch: 10.2.5
+ debug: 4.4.3(supports-color@10.2.2)
+ minimatch: 10.2.6
transitivePeerDependencies:
- supports-color
- '@eslint/config-helpers@0.6.0':
+ '@eslint/config-helpers@0.7.0':
dependencies:
'@eslint/core': 1.2.1
@@ -2954,11 +3358,11 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/js@9.39.4': {}
+ '@eslint/js@9.39.5': {}
'@eslint/object-schema@3.0.5': {}
- '@eslint/plugin-kit@0.7.2':
+ '@eslint/plugin-kit@0.7.3':
dependencies:
'@eslint/core': 1.2.1
levn: 0.4.1
@@ -2967,18 +3371,18 @@ snapshots:
dependencies:
'@favware/colorette-spinner': 1.0.1
'@octokit/auth-token': 6.0.0
- '@octokit/core': 7.0.6
- '@octokit/plugin-retry': 8.1.0(@octokit/core@7.0.6)
+ '@octokit/core': 7.0.8
+ '@octokit/plugin-retry': 8.1.1(@octokit/core@7.0.8)
'@sapphire/result': 2.8.0
'@sapphire/utilities': 3.18.2
colorette: 2.0.20
commander: 14.0.3
conventional-recommended-bump: 11.2.0
execa: 9.6.1
- git-cliff: 2.13.1
- js-yaml: 4.3.0
+ git-cliff: 2.14.2
+ js-yaml: 4.3.2
semver: 7.8.5
- smol-toml: 1.7.0
+ smol-toml: 1.9.0
'@favware/colorette-spinner@1.0.1':
dependencies:
@@ -3000,158 +3404,165 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
- '@inquirer/ansi@2.0.7': {}
+ '@inquirer/ansi@2.0.8': {}
- '@inquirer/checkbox@5.2.1(@types/node@26.1.0)':
+ '@inquirer/checkbox@5.2.5(@types/node@26.6.2)':
dependencies:
- '@inquirer/ansi': 2.0.7
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/figures': 2.0.7
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/ansi': 2.0.8
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/figures': 2.0.9
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/confirm@6.1.1(@types/node@26.1.0)':
+ '@inquirer/confirm@6.3.2(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/core@11.2.1(@types/node@26.1.0)':
+ '@inquirer/core@12.0.3(@types/node@26.6.2)':
dependencies:
- '@inquirer/ansi': 2.0.7
- '@inquirer/figures': 2.0.7
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/ansi': 2.0.8
+ '@inquirer/figures': 2.0.9
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
cli-width: 4.1.0
fast-wrap-ansi: 0.2.2
mute-stream: 3.0.0
signal-exit: 4.1.0
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/editor@5.2.2(@types/node@26.1.0)':
+ '@inquirer/editor@5.3.3(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/external-editor': 3.0.3(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/external-editor': 3.0.5(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/expand@5.1.1(@types/node@26.1.0)':
+ '@inquirer/expand@5.1.5(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/external-editor@3.0.3(@types/node@26.1.0)':
+ '@inquirer/external-editor@3.0.5(@types/node@26.6.2)':
dependencies:
chardet: 2.2.0
iconv-lite: 0.7.3
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/figures@2.0.7': {}
+ '@inquirer/figures@2.0.9': {}
- '@inquirer/input@5.1.2(@types/node@26.1.0)':
+ '@inquirer/input@5.1.6(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/number@4.1.1(@types/node@26.1.0)':
+ '@inquirer/number@4.2.3(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/password@5.1.1(@types/node@26.1.0)':
+ '@inquirer/password@5.2.2(@types/node@26.6.2)':
dependencies:
- '@inquirer/ansi': 2.0.7
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/ansi': 2.0.8
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
-
- '@inquirer/prompts@8.4.2(@types/node@26.1.0)':
- dependencies:
- '@inquirer/checkbox': 5.2.1(@types/node@26.1.0)
- '@inquirer/confirm': 6.1.1(@types/node@26.1.0)
- '@inquirer/editor': 5.2.2(@types/node@26.1.0)
- '@inquirer/expand': 5.1.1(@types/node@26.1.0)
- '@inquirer/input': 5.1.2(@types/node@26.1.0)
- '@inquirer/number': 4.1.1(@types/node@26.1.0)
- '@inquirer/password': 5.1.1(@types/node@26.1.0)
- '@inquirer/rawlist': 5.3.1(@types/node@26.1.0)
- '@inquirer/search': 4.2.1(@types/node@26.1.0)
- '@inquirer/select': 5.2.1(@types/node@26.1.0)
+ '@types/node': 26.6.2
+
+ '@inquirer/prompts@8.5.2(@types/node@26.6.2)':
+ dependencies:
+ '@inquirer/checkbox': 5.2.5(@types/node@26.6.2)
+ '@inquirer/confirm': 6.3.2(@types/node@26.6.2)
+ '@inquirer/editor': 5.3.3(@types/node@26.6.2)
+ '@inquirer/expand': 5.1.5(@types/node@26.6.2)
+ '@inquirer/input': 5.1.6(@types/node@26.6.2)
+ '@inquirer/number': 4.2.3(@types/node@26.6.2)
+ '@inquirer/password': 5.2.2(@types/node@26.6.2)
+ '@inquirer/rawlist': 5.3.5(@types/node@26.6.2)
+ '@inquirer/search': 4.3.3(@types/node@26.6.2)
+ '@inquirer/select': 5.2.5(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
-
- '@inquirer/prompts@8.5.2(@types/node@26.1.0)':
- dependencies:
- '@inquirer/checkbox': 5.2.1(@types/node@26.1.0)
- '@inquirer/confirm': 6.1.1(@types/node@26.1.0)
- '@inquirer/editor': 5.2.2(@types/node@26.1.0)
- '@inquirer/expand': 5.1.1(@types/node@26.1.0)
- '@inquirer/input': 5.1.2(@types/node@26.1.0)
- '@inquirer/number': 4.1.1(@types/node@26.1.0)
- '@inquirer/password': 5.1.1(@types/node@26.1.0)
- '@inquirer/rawlist': 5.3.1(@types/node@26.1.0)
- '@inquirer/search': 4.2.1(@types/node@26.1.0)
- '@inquirer/select': 5.2.1(@types/node@26.1.0)
+ '@types/node': 26.6.2
+
+ '@inquirer/prompts@8.7.2(@types/node@26.6.2)':
+ dependencies:
+ '@inquirer/checkbox': 5.2.5(@types/node@26.6.2)
+ '@inquirer/confirm': 6.3.2(@types/node@26.6.2)
+ '@inquirer/editor': 5.3.3(@types/node@26.6.2)
+ '@inquirer/expand': 5.1.5(@types/node@26.6.2)
+ '@inquirer/input': 5.1.6(@types/node@26.6.2)
+ '@inquirer/number': 4.2.3(@types/node@26.6.2)
+ '@inquirer/password': 5.2.2(@types/node@26.6.2)
+ '@inquirer/rawlist': 5.3.5(@types/node@26.6.2)
+ '@inquirer/search': 4.3.3(@types/node@26.6.2)
+ '@inquirer/select': 5.2.5(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/rawlist@5.3.1(@types/node@26.1.0)':
+ '@inquirer/rawlist@5.3.5(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/search@4.2.1(@types/node@26.1.0)':
+ '@inquirer/search@4.3.3(@types/node@26.6.2)':
dependencies:
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/figures': 2.0.7
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/figures': 2.0.9
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/select@5.2.1(@types/node@26.1.0)':
+ '@inquirer/select@5.2.5(@types/node@26.6.2)':
dependencies:
- '@inquirer/ansi': 2.0.7
- '@inquirer/core': 11.2.1(@types/node@26.1.0)
- '@inquirer/figures': 2.0.7
- '@inquirer/type': 4.0.7(@types/node@26.1.0)
+ '@inquirer/ansi': 2.0.8
+ '@inquirer/core': 12.0.3(@types/node@26.6.2)
+ '@inquirer/figures': 2.0.9
+ '@inquirer/type': 4.1.1(@types/node@26.6.2)
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
- '@inquirer/type@4.0.7(@types/node@26.1.0)':
+ '@inquirer/type@4.1.1(@types/node@26.6.2)':
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.3
- optional: true
'@jridgewell/gen-mapping@0.3.13':
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/sourcemap-codec': 1.6.0
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
- '@jridgewell/sourcemap-codec@1.5.5': {}
+ '@jridgewell/sourcemap-codec@1.6.0': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ '@keyv/bigmap@1.3.1(keyv@5.6.0)':
+ dependencies:
+ hashery: 1.5.1
+ hookified: 1.15.1
+ keyv: 5.6.0
+
+ '@keyv/serialize@1.1.1': {}
'@nanoforge-dev/actions@2.1.5':
dependencies:
@@ -3162,45 +3573,37 @@ snapshots:
'@nanoforge-dev/editor@0.1.4':
dependencies:
- bun: 1.3.14
+ bun: 1.4.2
class-transformer: 0.5.1
class-validator: 0.15.1
dotenv: 17.4.2
micromatch: 4.0.8
- tar: 7.5.19
+ tar: 7.5.22
typescript: 6.0.3
- optional: true
- '@nanoforge-dev/loader-client@1.4.0':
+ '@nanoforge-dev/loader-client@1.5.1':
dependencies:
- '@nanoforge-dev/loader-website': 1.4.0
- bun: 1.3.14
+ '@nanoforge-dev/loader-website': 1.5.1
+ bun: 1.4.2
commander: 15.0.0
- '@nanoforge-dev/loader-server@1.4.0':
+ '@nanoforge-dev/loader-server@1.5.1':
dependencies:
+ bun: 1.4.2
commander: 15.0.0
- '@nanoforge-dev/loader-website@1.4.0': {}
-
- '@nanoforge-dev/schematics@2.2.1(chokidar@5.0.0)':
- dependencies:
- '@angular-devkit/core': 22.0.5(chokidar@5.0.0)
- '@angular-devkit/schematics': 22.0.5(chokidar@5.0.0)
- rxjs: 7.8.2
- transitivePeerDependencies:
- - chokidar
+ '@nanoforge-dev/loader-website@1.5.1': {}
- '@nanoforge-dev/utils-eslint-config@1.0.2(@types/eslint@9.6.1)(eslint@10.6.0(jiti@2.6.1))(prettier@3.9.4)(typescript@6.0.3)':
+ '@nanoforge-dev/utils-eslint-config@1.0.2(@types/eslint@9.6.1)(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9)(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@eslint/js': 9.39.4
+ '@eslint/js': 9.39.5
'@favware/cliff-jumper': 6.1.0
- eslint-config-prettier: 10.1.8(eslint@10.6.0(jiti@2.6.1))
+ eslint-config-prettier: 10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
eslint-formatter-pretty: 7.1.0
- eslint-plugin-format: 1.5.0(eslint@10.6.0(jiti@2.6.1))
- eslint-plugin-prettier: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.6.0(jiti@2.6.1)))(eslint@10.6.0(jiti@2.6.1))(prettier@3.9.4)
+ eslint-plugin-format: 1.5.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
+ eslint-plugin-prettier: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9)
globals: 16.5.0
- typescript-eslint: 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
+ typescript-eslint: 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
transitivePeerDependencies:
- '@types/eslint'
- eslint
@@ -3210,121 +3613,114 @@ snapshots:
'@nanoforge-dev/utils-prettier-config@1.0.2': {}
- '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
- dependencies:
- '@emnapi/core': 1.11.1
- '@emnapi/runtime': 1.11.1
- '@tybys/wasm-util': 0.10.3
- optional: true
-
'@octokit/auth-token@6.0.0': {}
- '@octokit/core@7.0.6':
+ '@octokit/core@7.0.8':
dependencies:
'@octokit/auth-token': 6.0.0
- '@octokit/graphql': 9.0.3
- '@octokit/request': 10.0.11
- '@octokit/request-error': 7.1.0
- '@octokit/types': 16.0.0
+ '@octokit/graphql': 9.0.5
+ '@octokit/request': 10.0.16
+ '@octokit/request-error': 7.1.2
+ '@octokit/types': 18.0.0
before-after-hook: 4.0.0
universal-user-agent: 7.0.3
- '@octokit/endpoint@11.0.3':
+ '@octokit/endpoint@11.0.5':
dependencies:
- '@octokit/types': 16.0.0
+ '@octokit/types': 18.0.0
universal-user-agent: 7.0.3
- '@octokit/graphql@9.0.3':
+ '@octokit/graphql@9.0.5':
dependencies:
- '@octokit/request': 10.0.11
- '@octokit/types': 16.0.0
+ '@octokit/request': 10.0.16
+ '@octokit/types': 18.0.0
universal-user-agent: 7.0.3
'@octokit/openapi-types@27.0.0': {}
- '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)':
+ '@octokit/openapi-types@28.0.0': {}
+
+ '@octokit/openapi-types@29.0.1': {}
+
+ '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.8)':
dependencies:
- '@octokit/core': 7.0.6
+ '@octokit/core': 7.0.8
'@octokit/types': 16.0.0
- '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)':
+ '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.8)':
dependencies:
- '@octokit/core': 7.0.6
+ '@octokit/core': 7.0.8
'@octokit/types': 16.0.0
- '@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)':
+ '@octokit/plugin-retry@8.1.1(@octokit/core@7.0.8)':
dependencies:
- '@octokit/core': 7.0.6
- '@octokit/request-error': 7.1.0
- '@octokit/types': 16.0.0
+ '@octokit/core': 7.0.8
+ '@octokit/request-error': 7.1.2
+ '@octokit/types': 17.0.0
bottleneck: 2.19.5
- '@octokit/request-error@7.1.0':
+ '@octokit/request-error@7.1.2':
dependencies:
- '@octokit/types': 16.0.0
+ '@octokit/types': 18.0.0
- '@octokit/request@10.0.11':
+ '@octokit/request@10.0.16':
dependencies:
- '@octokit/endpoint': 11.0.3
- '@octokit/request-error': 7.1.0
- '@octokit/types': 16.0.0
- content-type: 2.0.0
- json-with-bigint: 3.5.8
+ '@octokit/endpoint': 11.0.5
+ '@octokit/request-error': 7.1.2
+ '@octokit/types': 18.0.0
+ content-type: 3.1.1
+ json-with-bigint: 3.5.12
universal-user-agent: 7.0.3
'@octokit/types@16.0.0':
dependencies:
'@octokit/openapi-types': 27.0.0
- '@oven/bun-darwin-aarch64@1.3.14':
- optional: true
-
- '@oven/bun-darwin-x64-baseline@1.3.14':
- optional: true
-
- '@oven/bun-darwin-x64@1.3.14':
- optional: true
+ '@octokit/types@17.0.0':
+ dependencies:
+ '@octokit/openapi-types': 28.0.0
- '@oven/bun-freebsd-aarch64@1.3.14':
- optional: true
+ '@octokit/types@18.0.0':
+ dependencies:
+ '@octokit/openapi-types': 29.0.1
- '@oven/bun-freebsd-x64@1.3.14':
+ '@oven/bun-darwin-aarch64@1.4.2':
optional: true
- '@oven/bun-linux-aarch64-android@1.3.14':
+ '@oven/bun-darwin-x64@1.4.2':
optional: true
- '@oven/bun-linux-aarch64-musl@1.3.14':
+ '@oven/bun-freebsd-aarch64@1.4.2':
optional: true
- '@oven/bun-linux-aarch64@1.3.14':
+ '@oven/bun-freebsd-x64@1.4.2':
optional: true
- '@oven/bun-linux-x64-android@1.3.14':
+ '@oven/bun-linux-aarch64-android@1.4.2':
optional: true
- '@oven/bun-linux-x64-baseline@1.3.14':
+ '@oven/bun-linux-aarch64-musl@1.4.2':
optional: true
- '@oven/bun-linux-x64-musl-baseline@1.3.14':
+ '@oven/bun-linux-aarch64@1.4.2':
optional: true
- '@oven/bun-linux-x64-musl@1.3.14':
+ '@oven/bun-linux-x64-android@1.4.2':
optional: true
- '@oven/bun-linux-x64@1.3.14':
+ '@oven/bun-linux-x64-musl@1.4.2':
optional: true
- '@oven/bun-windows-aarch64@1.3.14':
+ '@oven/bun-linux-x64@1.4.2':
optional: true
- '@oven/bun-windows-x64-baseline@1.3.14':
+ '@oven/bun-windows-aarch64@1.4.2':
optional: true
- '@oven/bun-windows-x64@1.3.14':
+ '@oven/bun-windows-x64@1.4.2':
optional: true
- '@oxc-project/types@0.138.0': {}
+ '@oxc-project/types@0.151.0': {}
'@oxfmt/binding-android-arm-eabi@0.35.0':
optional: true
@@ -3385,57 +3781,53 @@ snapshots:
'@pkgr/core@0.3.6': {}
- '@quansync/fs@1.0.0':
+ '@quansync/fs@1.1.0':
dependencies:
quansync: 1.0.0
- '@rolldown/binding-android-arm64@1.1.4':
+ '@rolldown/binding-android-arm-eabi@1.2.10':
optional: true
- '@rolldown/binding-darwin-arm64@1.1.4':
+ '@rolldown/binding-android-arm64@1.2.10':
optional: true
- '@rolldown/binding-darwin-x64@1.1.4':
+ '@rolldown/binding-darwin-arm64@1.2.10':
optional: true
- '@rolldown/binding-freebsd-x64@1.1.4':
+ '@rolldown/binding-darwin-x64@1.2.10':
optional: true
- '@rolldown/binding-linux-arm-gnueabihf@1.1.4':
+ '@rolldown/binding-freebsd-x64@1.2.10':
optional: true
- '@rolldown/binding-linux-arm64-gnu@1.1.4':
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.10':
optional: true
- '@rolldown/binding-linux-arm64-musl@1.1.4':
+ '@rolldown/binding-linux-arm64-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-ppc64-gnu@1.1.4':
+ '@rolldown/binding-linux-arm64-musl@1.2.10':
optional: true
- '@rolldown/binding-linux-s390x-gnu@1.1.4':
+ '@rolldown/binding-linux-ppc64-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-x64-gnu@1.1.4':
+ '@rolldown/binding-linux-s390x-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-x64-musl@1.1.4':
+ '@rolldown/binding-linux-x64-gnu@1.2.10':
optional: true
- '@rolldown/binding-openharmony-arm64@1.1.4':
+ '@rolldown/binding-linux-x64-musl@1.2.10':
optional: true
- '@rolldown/binding-wasm32-wasi@1.1.4':
- dependencies:
- '@emnapi/core': 1.11.1
- '@emnapi/runtime': 1.11.1
- '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@rolldown/binding-openharmony-arm64@1.2.10':
optional: true
- '@rolldown/binding-win32-arm64-msvc@1.1.4':
+ '@rolldown/binding-win32-arm64-msvc@1.2.10':
optional: true
- '@rolldown/binding-win32-x64-msvc@1.1.4':
+ '@rolldown/binding-win32-x64-msvc@1.2.10':
optional: true
'@rolldown/pluginutils@1.0.1': {}
@@ -3450,6 +3842,10 @@ snapshots:
dependencies:
'@simple-libs/stream-utils': 1.2.0
+ '@simple-libs/child-process-utils@2.0.0':
+ dependencies:
+ '@simple-libs/stream-utils': 2.0.0
+
'@simple-libs/stream-utils@1.2.0': {}
'@simple-libs/stream-utils@2.0.0': {}
@@ -3458,25 +3854,36 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
- '@standard-schema/spec@1.1.0': {}
-
- '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.4)':
+ '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)(supports-color@10.2.2)':
dependencies:
- '@babel/generator': 7.29.7
- '@babel/parser': 7.29.7
- '@babel/traverse': 7.29.7
- '@babel/types': 7.29.7
+ '@babel/generator': 7.29.8
+ '@babel/parser': 7.29.9
+ '@babel/traverse': 7.29.8(supports-color@10.2.2)
+ '@babel/types': 7.29.8
javascript-natural-sort: 0.7.1
lodash-es: 4.18.1
minimatch: 9.0.9
parse-imports-exports: 0.2.4
- prettier: 3.9.4
+ prettier: 3.9.9
transitivePeerDependencies:
- supports-color
- '@tybys/wasm-util@0.10.3':
- dependencies:
- tslib: 2.8.1
+ '@turbo/darwin-64@2.11.3':
+ optional: true
+
+ '@turbo/darwin-arm64@2.11.3':
+ optional: true
+
+ '@turbo/linux-64@2.11.3':
+ optional: true
+
+ '@turbo/linux-arm64@2.11.3':
+ optional: true
+
+ '@turbo/windows-64@2.11.3':
+ optional: true
+
+ '@turbo/windows-arm64@2.11.3':
optional: true
'@types/chai@5.2.3':
@@ -3500,88 +3907,86 @@ snapshots:
'@types/through': 0.0.33
rxjs: 7.8.2
- '@types/jsesc@2.5.1': {}
-
'@types/json-schema@7.0.15': {}
- '@types/node@26.1.0':
+ '@types/node@26.6.2':
dependencies:
- undici-types: 8.3.0
+ undici-types: 8.9.0
'@types/through@0.0.33':
dependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
'@types/validator@13.15.10': {}
- '@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3))(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- '@typescript-eslint/scope-manager': 8.62.1
- '@typescript-eslint/type-utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- '@typescript-eslint/visitor-keys': 8.62.1
- eslint: 10.6.0(jiti@2.6.1)
- ignore: 7.0.5
+ '@typescript-eslint/parser': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/type-utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/visitor-keys': 8.70.1
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
+ ignore: 7.0.10
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/scope-manager': 8.62.1
- '@typescript-eslint/types': 8.62.1
- '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/visitor-keys': 8.62.1
- debug: 4.4.3
- eslint: 10.6.0(jiti@2.6.1)
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/visitor-keys': 8.70.1
+ debug: 4.4.3(supports-color@10.2.2)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.62.1(typescript@6.0.3)':
+ '@typescript-eslint/project-service@8.70.1(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.62.1
- debug: 4.4.3
+ '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3)
+ '@typescript-eslint/types': 8.70.1
+ debug: 4.4.3(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.62.1':
+ '@typescript-eslint/scope-manager@8.70.1':
dependencies:
- '@typescript-eslint/types': 8.62.1
- '@typescript-eslint/visitor-keys': 8.62.1
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/visitor-keys': 8.70.1
- '@typescript-eslint/tsconfig-utils@8.62.1(typescript@6.0.3)':
+ '@typescript-eslint/tsconfig-utils@8.70.1(typescript@6.0.3)':
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/types': 8.62.1
- '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- debug: 4.4.3
- eslint: 10.6.0(jiti@2.6.1)
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ debug: 4.4.3(supports-color@10.2.2)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.62.1': {}
+ '@typescript-eslint/types@8.70.1': {}
- '@typescript-eslint/typescript-estree@8.62.1(typescript@6.0.3)':
+ '@typescript-eslint/typescript-estree@8.70.1(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/project-service': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.62.1
- '@typescript-eslint/visitor-keys': 8.62.1
- debug: 4.4.3
- minimatch: 10.2.5
+ '@typescript-eslint/project-service': 8.70.1(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3)
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/visitor-keys': 8.70.1
+ debug: 4.4.3(supports-color@10.2.2)
+ minimatch: 10.2.6
semver: 7.8.5
tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -3589,82 +3994,130 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)':
+ '@typescript-eslint/utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.6.1))
- '@typescript-eslint/scope-manager': 8.62.1
- '@typescript-eslint/types': 8.62.1
- '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3)
- eslint: 10.6.0(jiti@2.6.1)
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@6.0.3)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.62.1':
+ '@typescript-eslint/visitor-keys@8.70.1':
dependencies:
- '@typescript-eslint/types': 8.62.1
+ '@typescript-eslint/types': 8.70.1
eslint-visitor-keys: 5.0.1
- '@vitest/coverage-v8@4.1.10(vitest@4.1.10)':
+ '@vitest/coverage-v8@5.0.1(vitest@5.0.1)':
dependencies:
'@bcoe/v8-coverage': 1.0.2
- '@vitest/utils': 4.1.10
- ast-v8-to-istanbul: 1.0.4
- istanbul-lib-coverage: 3.2.2
- istanbul-lib-report: 3.0.1
- istanbul-reports: 3.2.0
- magicast: 0.5.3
- obug: 2.1.3
- std-env: 4.1.0
- tinyrainbow: 3.1.0
- vitest: 4.1.10(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0))
-
- '@vitest/expect@4.1.10':
- dependencies:
- '@standard-schema/spec': 1.1.0
- '@types/chai': 5.2.3
- '@vitest/spy': 4.1.10
- '@vitest/utils': 4.1.10
- chai: 6.2.2
- tinyrainbow: 3.1.0
+ '@vitest/istanbul-lib-coverage': 1.0.1
+ '@vitest/istanbul-lib-report': 1.0.1
+ ast-v8-to-istanbul: 1.0.7
+ magicast: 0.5.5
+ obug: 2.2.1
+ std-env: 4.2.0
+ tinyrainbow: 3.1.1
+ vitest: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))
+
+ '@vitest/istanbul-lib-coverage@1.0.1': {}
- '@vitest/mocker@4.1.10(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0))':
+ '@vitest/istanbul-lib-report@1.0.1':
dependencies:
- '@vitest/spy': 4.1.10
+ '@vitest/istanbul-lib-coverage': 1.0.1
+
+ '@vitest/mocker@5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ '@vitest/spy': 5.0.1
estree-walker: 3.0.3
- magic-string: 0.30.21
+ magic-string: 1.4.2
optionalDependencies:
- vite: 8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0)
+ vite: 8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1)
- '@vitest/pretty-format@4.1.10':
- dependencies:
- tinyrainbow: 3.1.0
+ '@vitest/spy@5.0.1': {}
- '@vitest/runner@4.1.10':
- dependencies:
- '@vitest/utils': 4.1.10
- pathe: 2.0.3
+ '@yuku-codegen/binding-android-arm64@0.10.2':
+ optional: true
- '@vitest/snapshot@4.1.10':
- dependencies:
- '@vitest/pretty-format': 4.1.10
- '@vitest/utils': 4.1.10
- magic-string: 0.30.21
- pathe: 2.0.3
+ '@yuku-codegen/binding-darwin-arm64@0.10.2':
+ optional: true
- '@vitest/spy@4.1.10': {}
+ '@yuku-codegen/binding-darwin-x64@0.10.2':
+ optional: true
- '@vitest/utils@4.1.10':
- dependencies:
- '@vitest/pretty-format': 4.1.10
- convert-source-map: 2.0.0
- tinyrainbow: 3.1.0
+ '@yuku-codegen/binding-freebsd-x64@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-arm-gnu@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-arm-musl@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-arm64-gnu@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-arm64-musl@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-x64-gnu@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-linux-x64-musl@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-win32-arm64@0.10.2':
+ optional: true
+
+ '@yuku-codegen/binding-win32-x64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-android-arm64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-darwin-arm64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-darwin-x64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-freebsd-x64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-arm-gnu@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-arm-musl@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-arm64-gnu@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-arm64-musl@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-x64-gnu@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-linux-x64-musl@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-win32-arm64@0.10.2':
+ optional: true
+
+ '@yuku-parser/binding-win32-x64@0.10.2':
+ optional: true
+
+ '@yuku-toolchain/types@0.10.2': {}
- acorn-jsx@5.3.2(acorn@8.17.0):
+ acorn-jsx@5.3.2(acorn@8.18.0):
dependencies:
- acorn: 8.17.0
+ acorn: 8.18.0
- acorn@8.17.0: {}
+ acorn@8.18.0: {}
ajv-formats@3.0.1(ajv@8.20.0):
optionalDependencies:
@@ -3680,7 +4133,7 @@ snapshots:
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
- fast-uri: 3.1.3
+ fast-uri: 3.1.8
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
@@ -3688,23 +4141,19 @@ snapshots:
dependencies:
environment: 1.1.0
- ansi-regex@6.2.2: {}
+ ansi-regex@6.3.0: {}
ansi-styles@6.2.3: {}
- ansis@4.3.1: {}
+ ansis@4.4.0: {}
argparse@2.0.1: {}
- assertion-error@2.0.1: {}
+ argue-cli@3.2.0: {}
- ast-kit@3.0.0:
- dependencies:
- '@babel/parser': 8.0.0
- estree-walker: 3.0.3
- pathe: 2.0.3
+ assertion-error@2.0.1: {}
- ast-v8-to-istanbul@1.0.4:
+ ast-v8-to-istanbul@1.0.7:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
estree-walker: 3.0.3
@@ -3716,41 +4165,34 @@ snapshots:
before-after-hook@4.0.0: {}
- birpc@4.0.0: {}
-
bottleneck@2.19.5: {}
- brace-expansion@2.1.1:
+ brace-expansion@2.1.7:
dependencies:
balanced-match: 1.0.2
- brace-expansion@5.0.7:
+ brace-expansion@5.0.12:
dependencies:
balanced-match: 4.0.4
braces@3.0.3:
dependencies:
fill-range: 7.1.1
- optional: true
- bun@1.3.14:
+ bun@1.4.2:
optionalDependencies:
- '@oven/bun-darwin-aarch64': 1.3.14
- '@oven/bun-darwin-x64': 1.3.14
- '@oven/bun-darwin-x64-baseline': 1.3.14
- '@oven/bun-freebsd-aarch64': 1.3.14
- '@oven/bun-freebsd-x64': 1.3.14
- '@oven/bun-linux-aarch64': 1.3.14
- '@oven/bun-linux-aarch64-android': 1.3.14
- '@oven/bun-linux-aarch64-musl': 1.3.14
- '@oven/bun-linux-x64': 1.3.14
- '@oven/bun-linux-x64-android': 1.3.14
- '@oven/bun-linux-x64-baseline': 1.3.14
- '@oven/bun-linux-x64-musl': 1.3.14
- '@oven/bun-linux-x64-musl-baseline': 1.3.14
- '@oven/bun-windows-aarch64': 1.3.14
- '@oven/bun-windows-x64': 1.3.14
- '@oven/bun-windows-x64-baseline': 1.3.14
+ '@oven/bun-darwin-aarch64': 1.4.2
+ '@oven/bun-darwin-x64': 1.4.2
+ '@oven/bun-freebsd-aarch64': 1.4.2
+ '@oven/bun-freebsd-x64': 1.4.2
+ '@oven/bun-linux-aarch64': 1.4.2
+ '@oven/bun-linux-aarch64-android': 1.4.2
+ '@oven/bun-linux-aarch64-musl': 1.4.2
+ '@oven/bun-linux-x64': 1.4.2
+ '@oven/bun-linux-x64-android': 1.4.2
+ '@oven/bun-linux-x64-musl': 1.4.2
+ '@oven/bun-windows-aarch64': 1.4.2
+ '@oven/bun-windows-x64': 1.4.2
bundle-name@4.1.0:
dependencies:
@@ -3758,6 +4200,14 @@ snapshots:
cac@7.0.0: {}
+ cacheable@2.5.0:
+ dependencies:
+ '@cacheable/memory': 2.2.0
+ '@cacheable/utils': 2.5.0
+ hookified: 1.15.1
+ keyv: 5.6.0
+ qified: 0.10.1
+
callsites@3.1.0: {}
chai@6.2.2: {}
@@ -3770,17 +4220,16 @@ snapshots:
chokidar@5.0.0:
dependencies:
- readdirp: 5.0.0
+ readdirp: 5.1.1
- chownr@3.0.0:
- optional: true
+ chownr@3.0.0: {}
class-transformer@0.5.1: {}
class-validator@0.15.1:
dependencies:
'@types/validator': 13.15.10
- libphonenumber-js: 1.13.8
+ libphonenumber-js: 1.13.13
validator: 13.15.35
cli-cursor@5.0.0:
@@ -3789,11 +4238,6 @@ snapshots:
cli-spinners@3.4.0: {}
- cli-truncate@5.2.0:
- dependencies:
- slice-ansi: 8.0.0
- string-width: 8.2.1
-
cli-width@4.1.0: {}
cliui@9.0.1:
@@ -3808,15 +4252,15 @@ snapshots:
commander@15.0.0: {}
- content-type@2.0.0: {}
+ content-type@3.1.1: {}
- conventional-changelog-angular@9.2.1:
+ conventional-changelog-angular@9.4.0:
dependencies:
- '@conventional-changelog/template': 1.2.1
+ '@conventional-changelog/template': 1.4.0
- conventional-changelog-conventionalcommits@10.2.1:
+ conventional-changelog-conventionalcommits@10.4.0:
dependencies:
- '@conventional-changelog/template': 1.2.1
+ '@conventional-changelog/template': 1.4.0
conventional-changelog-preset-loader@5.0.0: {}
@@ -3827,10 +4271,10 @@ snapshots:
'@simple-libs/stream-utils': 1.2.0
meow: 13.2.0
- conventional-commits-parser@7.0.1:
+ conventional-commits-parser@7.1.2:
dependencies:
'@simple-libs/stream-utils': 2.0.0
- meow: 14.1.0
+ argue-cli: 3.2.0
conventional-recommended-bump@11.2.0:
dependencies:
@@ -3840,11 +4284,9 @@ snapshots:
conventional-commits-parser: 6.4.0
meow: 13.2.0
- convert-source-map@2.0.0: {}
-
- cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3):
+ cosmiconfig-typescript-loader@6.3.0(@types/node@26.6.2)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3):
dependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
cosmiconfig: 9.0.2(typescript@6.0.3)
jiti: 2.6.1
typescript: 6.0.3
@@ -3853,26 +4295,41 @@ snapshots:
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.1
- js-yaml: 4.3.0
+ js-yaml: 4.3.2
parse-json: 5.2.0
optionalDependencies:
typescript: 6.0.3
+ cpx2@9.0.3:
+ dependencies:
+ debounce: 3.0.0
+ glob: 13.0.6
+ glob2base: 0.0.12
+ ignore: 7.0.10
+ minimatch: 10.2.6
+ p-map: 7.0.8
+ shell-quote: 1.10.0
+ subarg: 1.0.0
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- debug@4.4.3:
+ debounce@3.0.0: {}
+
+ debug@4.4.3(supports-color@10.2.2):
dependencies:
ms: 2.1.3
+ optionalDependencies:
+ supports-color: 10.2.2
deep-is@0.1.4: {}
default-browser-id@5.0.1: {}
- default-browser@5.5.0:
+ default-browser@5.5.1:
dependencies:
bundle-name: 4.1.0
default-browser-id: 5.0.1
@@ -3887,13 +4344,15 @@ snapshots:
dotenv@17.4.2: {}
+ dotenv@18.0.3: {}
+
dts-resolver@3.0.0: {}
emoji-regex@10.6.0: {}
emojilib@2.4.0: {}
- empathic@2.0.1: {}
+ empathic@2.1.0: {}
env-paths@2.2.1: {}
@@ -3903,17 +4362,17 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
- es-module-lexer@2.3.0: {}
+ es-module-lexer@2.3.2: {}
- es-toolkit@1.49.0: {}
+ es-toolkit@1.52.0: {}
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
- eslint-config-prettier@10.1.8(eslint@10.6.0(jiti@2.6.1)):
+ eslint-config-prettier@10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)):
dependencies:
- eslint: 10.6.0(jiti@2.6.1)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
eslint-formatter-pretty@7.1.0:
dependencies:
@@ -3923,38 +4382,38 @@ snapshots:
eslint-rule-docs: 1.1.235
log-symbols: 7.0.1
plur: 5.1.0
- string-width: 8.2.1
+ string-width: 8.2.2
supports-hyperlinks: 4.5.0
- eslint-formatting-reporter@0.0.0(eslint@10.6.0(jiti@2.6.1)):
+ eslint-formatting-reporter@0.0.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)):
dependencies:
- eslint: 10.6.0(jiti@2.6.1)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
prettier-linter-helpers: 1.0.1
eslint-parser-plain@0.1.1: {}
- eslint-plugin-format@1.5.0(eslint@10.6.0(jiti@2.6.1)):
+ eslint-plugin-format@1.5.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)):
dependencies:
'@dprint/formatter': 0.5.1
'@dprint/markdown': 0.21.1
'@dprint/toml': 0.7.0
- eslint: 10.6.0(jiti@2.6.1)
- eslint-formatting-reporter: 0.0.0(eslint@10.6.0(jiti@2.6.1))
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
+ eslint-formatting-reporter: 0.0.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
eslint-parser-plain: 0.1.1
- ohash: 2.0.11
+ ohash: 2.0.12
oxfmt: 0.35.0
- prettier: 3.9.4
+ prettier: 3.9.9
synckit: 0.11.13
- eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.6.0(jiti@2.6.1)))(eslint@10.6.0(jiti@2.6.1))(prettier@3.9.4):
+ eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.9):
dependencies:
- eslint: 10.6.0(jiti@2.6.1)
- prettier: 3.9.4
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
+ prettier: 3.9.9
prettier-linter-helpers: 1.0.1
synckit: 0.11.13
optionalDependencies:
'@types/eslint': 9.6.1
- eslint-config-prettier: 10.1.8(eslint@10.6.0(jiti@2.6.1))
+ eslint-config-prettier: 10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
eslint-rule-docs@1.1.235: {}
@@ -3969,21 +4428,21 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.6.0(jiti@2.6.1):
+ eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.6.1))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.23.5
- '@eslint/config-helpers': 0.6.0
+ '@eslint/config-array': 0.23.5(supports-color@10.2.2)
+ '@eslint/config-helpers': 0.7.0
'@eslint/core': 1.2.1
- '@eslint/plugin-kit': 0.7.2
+ '@eslint/plugin-kit': 0.7.3
'@humanfs/node': 0.16.8
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.9
ajv: 6.15.0
cross-spawn: 7.0.6
- debug: 4.4.3
+ debug: 4.4.3(supports-color@10.2.2)
escape-string-regexp: 4.0.0
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
@@ -3991,14 +4450,14 @@ snapshots:
esquery: 1.7.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
- file-entry-cache: 8.0.0
+ file-entry-cache: 11.1.5
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
- minimatch: 10.2.5
+ minimatch: 10.2.6
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
@@ -4008,8 +4467,8 @@ snapshots:
espree@11.2.0:
dependencies:
- acorn: 8.17.0
- acorn-jsx: 5.3.2(acorn@8.17.0)
+ acorn: 8.18.0
+ acorn-jsx: 5.3.2(acorn@8.18.0)
eslint-visitor-keys: 5.0.1
esquery@1.7.0:
@@ -4028,8 +4487,6 @@ snapshots:
esutils@2.0.3: {}
- eventemitter3@5.0.4: {}
-
execa@9.6.1:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
@@ -4040,10 +4497,10 @@ snapshots:
is-plain-obj: 4.1.0
is-stream: 4.0.1
npm-run-path: 6.0.0
- pretty-ms: 9.3.0
+ pretty-ms: 9.3.1
signal-exit: 4.1.0
strip-final-newline: 4.0.0
- yoctocolors: 2.1.2
+ yoctocolors: 2.2.0
expect-type@1.4.0: {}
@@ -4061,111 +4518,119 @@ snapshots:
dependencies:
fast-string-truncated-width: 3.0.3
- fast-uri@3.1.3: {}
+ fast-uri@3.1.8: {}
fast-wrap-ansi@0.2.2:
dependencies:
fast-string-width: 3.0.2
- fdir@6.5.0(picomatch@4.0.5):
+ fdir@6.5.0(picomatch@4.0.7):
optionalDependencies:
- picomatch: 4.0.5
+ picomatch: 4.0.7
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
- file-entry-cache@8.0.0:
+ file-entry-cache@11.1.5:
dependencies:
- flat-cache: 4.0.1
+ flat-cache: 6.1.23
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
- optional: true
+
+ find-index@0.1.1: {}
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
- flat-cache@4.0.1:
+ flat-cache@6.1.23:
dependencies:
- flatted: 3.4.2
- keyv: 4.5.4
+ cacheable: 2.5.0
+ flatted: 3.4.4
+ hookified: 1.15.1
- flatted@3.4.2: {}
+ flatted@3.4.4: {}
fsevents@2.3.3:
optional: true
get-caller-file@2.0.5: {}
- get-east-asian-width@1.6.0: {}
+ get-east-asian-width@1.7.0: {}
get-stream@9.0.1:
dependencies:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
- get-tsconfig@5.0.0-beta.5:
+ get-tsconfig@5.0.0-beta.6:
dependencies:
resolve-pkg-maps: 1.0.0
- git-cliff-darwin-arm64@2.13.1:
+ git-cliff-darwin-arm64@2.14.2:
optional: true
- git-cliff-darwin-x64@2.13.1:
+ git-cliff-darwin-x64@2.14.2:
optional: true
- git-cliff-linux-arm64@2.13.1:
+ git-cliff-linux-arm64@2.14.2:
optional: true
- git-cliff-linux-x64@2.13.1:
+ git-cliff-linux-x64@2.14.2:
optional: true
- git-cliff-windows-arm64@2.13.1:
+ git-cliff-windows-arm64@2.14.2:
optional: true
- git-cliff-windows-x64@2.13.1:
+ git-cliff-windows-x64@2.14.2:
optional: true
- git-cliff@2.13.1:
+ git-cliff@2.14.2:
dependencies:
execa: 9.6.1
optionalDependencies:
- git-cliff-darwin-arm64: 2.13.1
- git-cliff-darwin-x64: 2.13.1
- git-cliff-linux-arm64: 2.13.1
- git-cliff-linux-x64: 2.13.1
- git-cliff-windows-arm64: 2.13.1
- git-cliff-windows-x64: 2.13.1
-
- git-raw-commits@5.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0):
- dependencies:
- '@conventional-changelog/git-client': 2.7.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
- meow: 13.2.0
- transitivePeerDependencies:
- - conventional-commits-filter
- - conventional-commits-parser
+ git-cliff-darwin-arm64: 2.14.2
+ git-cliff-darwin-x64: 2.14.2
+ git-cliff-linux-arm64: 2.14.2
+ git-cliff-linux-x64: 2.14.2
+ git-cliff-windows-arm64: 2.14.2
+ git-cliff-windows-x64: 2.14.2
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
+ glob2base@0.0.12:
+ dependencies:
+ find-index: 0.1.1
+
+ glob@13.0.6:
+ dependencies:
+ minimatch: 10.2.6
+ minipass: 7.1.3
+ path-scurry: 2.0.2
+
global-directory@5.0.0:
dependencies:
ini: 6.0.0
globals@16.5.0: {}
- has-flag@4.0.0: {}
-
has-flag@5.0.1: {}
- hookable@6.1.1: {}
+ hashery@1.5.1:
+ dependencies:
+ hookified: 1.15.1
+
+ hookable@6.1.2: {}
+
+ hookified@1.15.1: {}
- html-escaper@2.0.2: {}
+ hookified@2.2.0: {}
human-signals@8.0.1: {}
@@ -4177,14 +4642,14 @@ snapshots:
ignore@5.3.2: {}
- ignore@7.0.5: {}
+ ignore@7.0.10: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
- import-without-cache@0.4.0: {}
+ import-without-cache@0.4.1: {}
imurmurhash@0.1.4: {}
@@ -4198,10 +4663,6 @@ snapshots:
is-extglob@2.1.1: {}
- is-fullwidth-code-point@5.1.0:
- dependencies:
- get-east-asian-width: 1.6.0
-
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -4214,8 +4675,7 @@ snapshots:
is-interactive@2.0.0: {}
- is-number@7.0.0:
- optional: true
+ is-number@7.0.0: {}
is-plain-obj@4.1.0: {}
@@ -4229,19 +4689,6 @@ snapshots:
isexe@2.0.0: {}
- istanbul-lib-coverage@3.2.2: {}
-
- istanbul-lib-report@3.0.1:
- dependencies:
- istanbul-lib-coverage: 3.2.2
- make-dir: 4.0.0
- supports-color: 7.2.0
-
- istanbul-reports@3.2.0:
- dependencies:
- html-escaper: 2.0.2
- istanbul-lib-report: 3.0.1
-
javascript-natural-sort@0.7.1: {}
jiti@2.6.1: {}
@@ -4250,14 +4697,12 @@ snapshots:
js-tokens@4.0.0: {}
- js-yaml@4.3.0:
+ js-yaml@4.3.2:
dependencies:
argparse: 2.0.1
jsesc@3.1.0: {}
- json-buffer@3.0.1: {}
-
json-parse-even-better-errors@2.3.1: {}
json-schema-traverse@0.4.1: {}
@@ -4266,88 +4711,79 @@ snapshots:
json-stable-stringify-without-jsonify@1.0.1: {}
- json-with-bigint@3.5.8: {}
+ json-with-bigint@3.5.12: {}
jsonc-parser@3.3.1: {}
- keyv@4.5.4:
+ keyv@5.6.0:
dependencies:
- json-buffer: 3.0.1
+ '@keyv/serialize': 1.1.1
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
- libphonenumber-js@1.13.8: {}
+ libphonenumber-js@1.13.13: {}
- lightningcss-android-arm64@1.32.0:
+ lightningcss-android-arm64@1.33.0:
optional: true
- lightningcss-darwin-arm64@1.32.0:
+ lightningcss-darwin-arm64@1.33.0:
optional: true
- lightningcss-darwin-x64@1.32.0:
+ lightningcss-darwin-x64@1.33.0:
optional: true
- lightningcss-freebsd-x64@1.32.0:
+ lightningcss-freebsd-x64@1.33.0:
optional: true
- lightningcss-linux-arm-gnueabihf@1.32.0:
+ lightningcss-linux-arm-gnueabihf@1.33.0:
optional: true
- lightningcss-linux-arm64-gnu@1.32.0:
+ lightningcss-linux-arm64-gnu@1.33.0:
optional: true
- lightningcss-linux-arm64-musl@1.32.0:
+ lightningcss-linux-arm64-musl@1.33.0:
optional: true
- lightningcss-linux-x64-gnu@1.32.0:
+ lightningcss-linux-x64-gnu@1.33.0:
optional: true
- lightningcss-linux-x64-musl@1.32.0:
+ lightningcss-linux-x64-musl@1.33.0:
optional: true
- lightningcss-win32-arm64-msvc@1.32.0:
+ lightningcss-win32-arm64-msvc@1.33.0:
optional: true
- lightningcss-win32-x64-msvc@1.32.0:
+ lightningcss-win32-x64-msvc@1.33.0:
optional: true
- lightningcss@1.32.0:
+ lightningcss@1.33.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
- lightningcss-android-arm64: 1.32.0
- lightningcss-darwin-arm64: 1.32.0
- lightningcss-darwin-x64: 1.32.0
- lightningcss-freebsd-x64: 1.32.0
- lightningcss-linux-arm-gnueabihf: 1.32.0
- lightningcss-linux-arm64-gnu: 1.32.0
- lightningcss-linux-arm64-musl: 1.32.0
- lightningcss-linux-x64-gnu: 1.32.0
- lightningcss-linux-x64-musl: 1.32.0
- lightningcss-win32-arm64-msvc: 1.32.0
- lightningcss-win32-x64-msvc: 1.32.0
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
lines-and-columns@1.2.4: {}
- lint-staged@17.0.8:
+ lint-staged@17.5.1:
dependencies:
- listr2: 10.2.2
- picomatch: 4.0.5
+ picomatch: 4.0.7
string-argv: 0.3.2
- tinyexec: 1.2.4
+ tinyexec: 1.3.1
optionalDependencies:
- yaml: 2.9.0
-
- listr2@10.2.2:
- dependencies:
- cli-truncate: 5.2.0
- eventemitter3: 5.0.4
- log-update: 6.1.0
- rfdc: 1.4.1
- wrap-ansi: 10.0.0
+ yaml: 2.9.1
locate-path@6.0.0:
dependencies:
@@ -4358,63 +4794,54 @@ snapshots:
log-symbols@7.0.1:
dependencies:
is-unicode-supported: 2.1.0
- yoctocolors: 2.1.2
+ yoctocolors: 2.2.0
- log-update@6.1.0:
- dependencies:
- ansi-escapes: 7.3.0
- cli-cursor: 5.0.0
- slice-ansi: 7.1.2
- strip-ansi: 7.2.0
- wrap-ansi: 9.0.2
+ lru-cache@11.5.3: {}
- magic-string@0.30.21:
+ magic-string@1.0.0:
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/sourcemap-codec': 1.6.0
- magicast@0.5.3:
+ magic-string@1.4.2:
dependencies:
- '@babel/parser': 7.29.7
- '@babel/types': 7.29.7
- source-map-js: 1.2.1
+ '@jridgewell/sourcemap-codec': 1.6.0
- make-dir@4.0.0:
+ magicast@0.5.5:
dependencies:
- semver: 7.8.5
+ '@babel/parser': 7.29.9
+ '@babel/types': 7.29.8
+ source-map-js: 1.2.1
meow@13.2.0: {}
- meow@14.1.0: {}
-
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.2
- optional: true
mimic-function@5.0.1: {}
- minimatch@10.2.5:
+ minimatch@10.2.6:
dependencies:
- brace-expansion: 5.0.7
+ brace-expansion: 5.0.12
minimatch@9.0.9:
dependencies:
- brace-expansion: 2.1.1
+ brace-expansion: 2.1.7
- minipass@7.1.3:
- optional: true
+ minimist@1.2.8: {}
+
+ minipass@7.1.3: {}
minizlib@3.1.0:
dependencies:
minipass: 7.1.3
- optional: true
ms@2.1.3: {}
mute-stream@3.0.0: {}
- nanoid@3.3.15: {}
+ nanoid@3.3.19: {}
natural-compare@1.4.0: {}
@@ -4430,22 +4857,24 @@ snapshots:
path-key: 4.0.0
unicorn-magic: 0.3.0
- obug@2.1.3: {}
+ obug@2.2.1: {}
+
+ obug@3.0.0: {}
- ohash@2.0.11: {}
+ ohash@2.0.12: {}
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
- open@11.0.0:
+ open@11.0.4:
dependencies:
- default-browser: 5.5.0
+ default-browser: 5.5.1
define-lazy-prop: 3.0.0
is-in-ssh: 1.0.0
is-inside-container: 1.0.0
- powershell-utils: 0.1.0
- wsl-utils: 0.3.1
+ powershell-utils: 0.2.1
+ wsl-utils: 1.0.0
optionator@0.9.4:
dependencies:
@@ -4456,17 +4885,6 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
- ora@9.4.0:
- dependencies:
- chalk: 5.6.2
- cli-cursor: 5.0.0
- cli-spinners: 3.4.0
- is-interactive: 2.0.0
- is-unicode-supported: 2.1.0
- log-symbols: 7.0.1
- stdin-discarder: 0.3.2
- string-width: 8.2.1
-
ora@9.4.1:
dependencies:
chalk: 5.6.2
@@ -4476,7 +4894,7 @@ snapshots:
is-unicode-supported: 2.1.0
log-symbols: 7.0.1
stdin-discarder: 0.3.2
- string-width: 8.2.1
+ string-width: 8.2.2
oxfmt@0.35.0:
dependencies:
@@ -4510,6 +4928,8 @@ snapshots:
dependencies:
p-limit: 3.1.0
+ p-map@7.0.8: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -4535,51 +4955,59 @@ snapshots:
path-key@4.0.0: {}
- pathe@2.0.3: {}
+ path-scurry@2.0.2:
+ dependencies:
+ lru-cache: 11.5.3
+ minipass: 7.1.3
picocolors@1.1.1: {}
- picomatch@2.3.2:
- optional: true
-
- picomatch@4.0.4: {}
+ picomatch@2.3.2: {}
picomatch@4.0.5: {}
+ picomatch@4.0.7: {}
+
plur@5.1.0:
dependencies:
irregular-plurals: 3.5.0
- postcss@8.5.16:
+ postcss@8.5.28:
dependencies:
- nanoid: 3.3.15
+ nanoid: 3.3.19
picocolors: 1.1.1
source-map-js: 1.2.1
powershell-utils@0.1.0: {}
+ powershell-utils@0.2.1: {}
+
prelude-ls@1.2.1: {}
prettier-linter-helpers@1.0.1:
dependencies:
fast-diff: 1.3.0
- prettier@3.9.4: {}
+ prettier@3.9.9: {}
- pretty-ms@9.3.0:
+ pretty-ms@9.3.1:
dependencies:
parse-ms: 4.0.0
punycode@2.3.1: {}
+ qified@0.10.1:
+ dependencies:
+ hookified: 2.2.0
+
quansync@1.0.0: {}
- rc9@3.0.1:
+ rc9@3.1.0:
dependencies:
defu: 6.1.7
destr: 2.0.5
- readdirp@5.0.0: {}
+ readdirp@5.1.1: {}
reflect-metadata@0.2.2: {}
@@ -4596,44 +5024,40 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
- rfdc@1.4.1: {}
-
- rolldown-plugin-dts@0.26.0(rolldown@1.1.4)(typescript@6.0.3):
+ rolldown-plugin-dts@0.28.6(rolldown@1.2.10)(typescript@6.0.3):
dependencies:
- '@babel/generator': 8.0.0
- '@babel/helper-validator-identifier': 8.0.2
- '@babel/parser': 8.0.0
- ast-kit: 3.0.0
- birpc: 4.0.0
dts-resolver: 3.0.0
- get-tsconfig: 5.0.0-beta.5
- obug: 2.1.3
- rolldown: 1.1.4
+ get-tsconfig: 5.0.0-beta.6
+ obug: 3.0.0
+ rolldown: 1.2.10
+ yuku-ast: 0.10.2
+ yuku-codegen: 0.10.2
+ yuku-parser: 0.10.2
optionalDependencies:
typescript: 6.0.3
transitivePeerDependencies:
- oxc-resolver
- rolldown@1.1.4:
+ rolldown@1.2.10:
dependencies:
- '@oxc-project/types': 0.138.0
+ '@oxc-project/types': 0.151.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
- '@rolldown/binding-android-arm64': 1.1.4
- '@rolldown/binding-darwin-arm64': 1.1.4
- '@rolldown/binding-darwin-x64': 1.1.4
- '@rolldown/binding-freebsd-x64': 1.1.4
- '@rolldown/binding-linux-arm-gnueabihf': 1.1.4
- '@rolldown/binding-linux-arm64-gnu': 1.1.4
- '@rolldown/binding-linux-arm64-musl': 1.1.4
- '@rolldown/binding-linux-ppc64-gnu': 1.1.4
- '@rolldown/binding-linux-s390x-gnu': 1.1.4
- '@rolldown/binding-linux-x64-gnu': 1.1.4
- '@rolldown/binding-linux-x64-musl': 1.1.4
- '@rolldown/binding-openharmony-arm64': 1.1.4
- '@rolldown/binding-wasm32-wasi': 1.1.4
- '@rolldown/binding-win32-arm64-msvc': 1.1.4
- '@rolldown/binding-win32-x64-msvc': 1.1.4
+ '@rolldown/binding-android-arm-eabi': 1.2.10
+ '@rolldown/binding-android-arm64': 1.2.10
+ '@rolldown/binding-darwin-arm64': 1.2.10
+ '@rolldown/binding-darwin-x64': 1.2.10
+ '@rolldown/binding-freebsd-x64': 1.2.10
+ '@rolldown/binding-linux-arm-gnueabihf': 1.2.10
+ '@rolldown/binding-linux-arm64-gnu': 1.2.10
+ '@rolldown/binding-linux-arm64-musl': 1.2.10
+ '@rolldown/binding-linux-ppc64-gnu': 1.2.10
+ '@rolldown/binding-linux-s390x-gnu': 1.2.10
+ '@rolldown/binding-linux-x64-gnu': 1.2.10
+ '@rolldown/binding-linux-x64-musl': 1.2.10
+ '@rolldown/binding-openharmony-arm64': 1.2.10
+ '@rolldown/binding-win32-arm64-msvc': 1.2.10
+ '@rolldown/binding-win32-x64-msvc': 1.2.10
run-applescript@7.1.0: {}
@@ -4651,6 +5075,8 @@ snapshots:
shebang-regex@3.0.0: {}
+ shell-quote@1.10.0: {}
+
siginfo@2.0.0: {}
signal-exit@4.1.0: {}
@@ -4659,17 +5085,7 @@ snapshots:
dependencies:
unicode-emoji-modifier-base: 1.0.0
- slice-ansi@7.1.2:
- dependencies:
- ansi-styles: 6.2.3
- is-fullwidth-code-point: 5.1.0
-
- slice-ansi@8.0.0:
- dependencies:
- ansi-styles: 6.2.3
- is-fullwidth-code-point: 5.1.0
-
- smol-toml@1.7.0: {}
+ smol-toml@1.9.0: {}
source-map-js@1.2.1: {}
@@ -4677,7 +5093,7 @@ snapshots:
stackback@0.0.2: {}
- std-env@4.1.0: {}
+ std-env@4.2.0: {}
stdin-discarder@0.3.2: {}
@@ -4686,25 +5102,25 @@ snapshots:
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
- get-east-asian-width: 1.6.0
+ get-east-asian-width: 1.7.0
strip-ansi: 7.2.0
- string-width@8.2.1:
+ string-width@8.2.2:
dependencies:
- get-east-asian-width: 1.6.0
+ get-east-asian-width: 1.7.0
strip-ansi: 7.2.0
strip-ansi@7.2.0:
dependencies:
- ansi-regex: 6.2.2
+ ansi-regex: 6.3.0
strip-final-newline@4.0.0: {}
- supports-color@10.2.2: {}
-
- supports-color@7.2.0:
+ subarg@1.0.0:
dependencies:
- has-flag: 4.0.0
+ minimist: 1.2.8
+
+ supports-color@10.2.2: {}
supports-hyperlinks@4.5.0:
dependencies:
@@ -4715,32 +5131,32 @@ snapshots:
dependencies:
'@pkgr/core': 0.3.6
- tar@7.5.19:
+ tar@7.5.22:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.3
minizlib: 3.1.0
yallist: 5.0.0
- optional: true
- tinybench@2.9.0: {}
+ tinybench@6.1.4: {}
+
+ tinyexec@1.3.0: {}
- tinyexec@1.2.4: {}
+ tinyexec@1.3.1: {}
tinyglobby@0.2.17:
dependencies:
- fdir: 6.5.0(picomatch@4.0.5)
- picomatch: 4.0.5
+ fdir: 6.5.0(picomatch@4.0.7)
+ picomatch: 4.0.7
tinypool@2.1.0: {}
- tinyrainbow@3.1.0: {}
+ tinyrainbow@3.1.1: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
- optional: true
tree-kill@1.2.2: {}
@@ -4748,28 +5164,29 @@ snapshots:
dependencies:
typescript: 6.0.3
- tsdown@0.22.3(typescript@6.0.3):
+ tsdown@0.23.0(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)):
dependencies:
- ansis: 4.3.1
cac: 7.0.0
defu: 6.1.7
- empathic: 2.0.1
- hookable: 6.1.1
- import-without-cache: 0.4.0
- obug: 2.1.3
- picomatch: 4.0.5
- rolldown: 1.1.4
- rolldown-plugin-dts: 0.26.0(rolldown@1.1.4)(typescript@6.0.3)
- semver: 7.8.5
- tinyexec: 1.2.4
+ empathic: 2.1.0
+ hookable: 6.1.2
+ import-without-cache: 0.4.1
+ obug: 2.2.1
+ picomatch: 4.0.7
+ rolldown: 1.2.10
+ rolldown-plugin-dts: 0.28.6(rolldown@1.2.10)(typescript@6.0.3)
+ tinyexec: 1.3.1
tinyglobby: 0.2.17
tree-kill: 1.2.2
unconfig-core: 7.5.0
+ verkit: 0.4.1
optionalDependencies:
typescript: 6.0.3
+ unrun: 0.3.1(synckit@0.11.13)
transitivePeerDependencies:
- - '@ts-macro/tsc'
- '@typescript/native-preview'
+ - '@volar/typescript'
+ - '@vue/language-core'
- oxc-resolver
- vue-tsc
@@ -4777,17 +5194,26 @@ snapshots:
tunnel@0.0.6: {}
+ turbo@2.11.3:
+ optionalDependencies:
+ '@turbo/darwin-64': 2.11.3
+ '@turbo/darwin-arm64': 2.11.3
+ '@turbo/linux-64': 2.11.3
+ '@turbo/linux-arm64': 2.11.3
+ '@turbo/windows-64': 2.11.3
+ '@turbo/windows-arm64': 2.11.3
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
- typescript-eslint@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3):
+ typescript-eslint@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3))(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3)
- '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3)
- eslint: 10.6.0(jiti@2.6.1)
+ '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -4796,12 +5222,12 @@ snapshots:
unconfig-core@7.5.0:
dependencies:
- '@quansync/fs': 1.0.0
+ '@quansync/fs': 1.1.0
quansync: 1.0.0
- undici-types@8.3.0: {}
+ undici-types@8.9.0: {}
- undici@6.27.0: {}
+ undici@6.28.1: {}
unicode-emoji-modifier-base@1.0.0: {}
@@ -4809,50 +5235,52 @@ snapshots:
universal-user-agent@7.0.3: {}
+ unrun@0.3.1(synckit@0.11.13):
+ dependencies:
+ rolldown: 1.2.10
+ optionalDependencies:
+ synckit: 0.11.13
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
validator@13.15.35: {}
- vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0):
+ verkit@0.4.1: {}
+
+ vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1):
dependencies:
- lightningcss: 1.32.0
- picomatch: 4.0.5
- postcss: 8.5.16
- rolldown: 1.1.4
+ lightningcss: 1.33.0
+ picomatch: 4.0.7
+ postcss: 8.5.28
+ rolldown: 1.2.10
tinyglobby: 0.2.17
optionalDependencies:
- '@types/node': 26.1.0
+ '@types/node': 26.6.2
fsevents: 2.3.3
jiti: 2.6.1
- yaml: 2.9.0
-
- vitest@4.1.10(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0)):
- dependencies:
- '@vitest/expect': 4.1.10
- '@vitest/mocker': 4.1.10(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0))
- '@vitest/pretty-format': 4.1.10
- '@vitest/runner': 4.1.10
- '@vitest/snapshot': 4.1.10
- '@vitest/spy': 4.1.10
- '@vitest/utils': 4.1.10
- es-module-lexer: 2.3.0
+ yaml: 2.9.1
+
+ vitest@5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1)):
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/mocker': 5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))
+ chai: 6.2.2
+ es-module-lexer: 2.3.2
expect-type: 1.4.0
- magic-string: 0.30.21
- obug: 2.1.3
- pathe: 2.0.3
- picomatch: 4.0.5
- std-env: 4.1.0
- tinybench: 2.9.0
- tinyexec: 1.2.4
+ magic-string: 1.4.2
+ obug: 2.2.1
+ picomatch: 4.0.7
+ std-env: 4.2.0
+ tinybench: 6.1.4
+ tinyexec: 1.3.0
tinyglobby: 0.2.17
- tinyrainbow: 3.1.0
- vite: 8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0)
+ vite: 8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1)
why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 26.1.0
- '@vitest/coverage-v8': 4.1.10(vitest@4.1.10)
+ '@types/node': 26.6.2
+ '@vitest/coverage-v8': 5.0.1(vitest@5.0.1)
transitivePeerDependencies:
- msw
@@ -4867,42 +5295,76 @@ snapshots:
word-wrap@1.2.5: {}
- wrap-ansi@10.0.0:
- dependencies:
- ansi-styles: 6.2.3
- string-width: 8.2.1
- strip-ansi: 7.2.0
-
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
string-width: 7.2.0
strip-ansi: 7.2.0
- wsl-utils@0.3.1:
+ wsl-utils@1.0.0:
dependencies:
is-wsl: 3.1.1
powershell-utils: 0.1.0
y18n@5.0.8: {}
- yallist@5.0.0:
- optional: true
+ yallist@5.0.0: {}
- yaml@2.9.0:
+ yaml@2.9.1:
optional: true
yargs-parser@22.0.0: {}
- yargs@18.0.0:
+ yargs@18.2.0:
dependencies:
cliui: 9.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
- string-width: 7.2.0
+ string-width: 8.2.2
y18n: 5.0.8
yargs-parser: 22.0.0
yocto-queue@0.1.0: {}
- yoctocolors@2.1.2: {}
+ yoctocolors@2.2.0: {}
+
+ yuku-ast@0.10.2:
+ dependencies:
+ '@yuku-toolchain/types': 0.10.2
+
+ yuku-codegen@0.10.2:
+ dependencies:
+ '@yuku-toolchain/types': 0.10.2
+ optionalDependencies:
+ '@yuku-codegen/binding-android-arm64': 0.10.2
+ '@yuku-codegen/binding-darwin-arm64': 0.10.2
+ '@yuku-codegen/binding-darwin-x64': 0.10.2
+ '@yuku-codegen/binding-freebsd-x64': 0.10.2
+ '@yuku-codegen/binding-linux-arm-gnu': 0.10.2
+ '@yuku-codegen/binding-linux-arm-musl': 0.10.2
+ '@yuku-codegen/binding-linux-arm64-gnu': 0.10.2
+ '@yuku-codegen/binding-linux-arm64-musl': 0.10.2
+ '@yuku-codegen/binding-linux-x64-gnu': 0.10.2
+ '@yuku-codegen/binding-linux-x64-musl': 0.10.2
+ '@yuku-codegen/binding-win32-arm64': 0.10.2
+ '@yuku-codegen/binding-win32-x64': 0.10.2
+
+ yuku-parser@0.10.2:
+ dependencies:
+ '@yuku-toolchain/types': 0.10.2
+ yuku-ast: 0.10.2
+ optionalDependencies:
+ '@yuku-parser/binding-android-arm64': 0.10.2
+ '@yuku-parser/binding-darwin-arm64': 0.10.2
+ '@yuku-parser/binding-darwin-x64': 0.10.2
+ '@yuku-parser/binding-freebsd-x64': 0.10.2
+ '@yuku-parser/binding-linux-arm-gnu': 0.10.2
+ '@yuku-parser/binding-linux-arm-musl': 0.10.2
+ '@yuku-parser/binding-linux-arm64-gnu': 0.10.2
+ '@yuku-parser/binding-linux-arm64-musl': 0.10.2
+ '@yuku-parser/binding-linux-x64-gnu': 0.10.2
+ '@yuku-parser/binding-linux-x64-musl': 0.10.2
+ '@yuku-parser/binding-win32-arm64': 0.10.2
+ '@yuku-parser/binding-win32-x64': 0.10.2
+
+ zod@4.6.5: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 2cd98ab..cecfa10 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,53 +1,64 @@
+packages:
+ - .
+ - libs/*
+
allowBuilds:
bun: true
catalogs:
build:
- tsdown: ^0.22.3
+ cpx2: ^9.0.0
+ tsdown: ^0.23.0
+ turbo: ^2.11.3
typescript: ^6.0.3
ci:
- '@commitlint/cli': ^21.2.0
- '@commitlint/config-conventional': ^21.2.0
+ '@commitlint/cli': ^21.2.3
+ '@commitlint/config-conventional': ^21.2.3
'@favware/cliff-jumper': ^6.1.0
'@nanoforge-dev/actions': ^2.1.5
husky: ^9.1.7
- lint-staged: ^17.0.8
+ lint-staged: ^17.5.1
cli:
- '@inquirer/prompts': ^8.5.2
+ '@inquirer/prompts': ^8.7.2
'@types/inquirer': ^9.0.10
- ansis: ^4.3.1
+ ansis: ^4.4.0
commander: ^15.0.0
node-emoji: ^2.2.0
ora: ^9.4.1
core:
'@nanoforge-dev/editor': ^0.1.4
- '@types/node': ^26.1.0
- bun: ^1.3.14
+ '@types/bun': ^1.3.14
+ '@types/node': ^26.6.2
+ bun: ^1.4.2
libs:
chokidar: ^5.0.0
class-transformer: ^0.5.1
class-validator: ^0.15.1
- dotenv: ^17.4.2
- open: ^11.0.0
- rc9: ^3.0.1
+ dotenv: ^18.0.3
+ open: ^11.0.4
+ rc9: ^3.1.0
reflect-metadata: ^0.2.2
+ unrun: ^0.3.0
+ zod: ^4.5.4
lint:
'@nanoforge-dev/utils-eslint-config': ^1.0.2
'@nanoforge-dev/utils-prettier-config': ^1.0.2
'@trivago/prettier-plugin-sort-imports': ^6.0.2
- eslint: ^10.6.0
- prettier: ^3.9.4
+ eslint: ^10.11.0
+ prettier: ^3.9.9
loader:
- '@nanoforge-dev/loader-client': ^1.4.0
- '@nanoforge-dev/loader-server': ^1.4.0
+ '@nanoforge-dev/loader-client': ^1.5.1
+ '@nanoforge-dev/loader-server': ^1.5.1
schematics:
- '@angular-devkit/schematics': ^22.0.5
- '@angular-devkit/schematics-cli': ^22.0.5
- '@nanoforge-dev/schematics': ^2.2.1
- tests:
- '@vitest/coverage-v8': ^4.1.10
- vitest: ^4.1.10
+ '@angular-devkit/core': ^22.0.4
+ '@angular-devkit/schematics': ^22.1.8
+ '@angular-devkit/schematics-cli': ^22.1.8
+ rxjs: ^7.8.2
+ test:
+ '@vitest/coverage-v8': ^5.0.1
+ vitest: ^5.0.1
minimumReleaseAgeExclude:
- - '@nanoforge-dev/editor@0.1.4'
- - '@nanoforge-dev/schematics@2.2.1'
+ - '@nanoforge-dev/loader-client@1.5.1'
+ - '@nanoforge-dev/loader-server@1.5.1'
+ - '@nanoforge-dev/loader-website@1.5.1'
diff --git a/src/action/actions/build.action.ts b/src/action/actions/build.action.ts
index df8ae2f..f3c118f 100644
--- a/src/action/actions/build.action.ts
+++ b/src/action/actions/build.action.ts
@@ -1,7 +1,13 @@
+import { defaultClientConfig, defaultServerConfig } from "@nanoforge-dev/config";
import { watch } from "chokidar";
-import { dirname, join } from "node:path";
+import { dirname, join, relative } from "node:path";
-import { type Config } from "@lib/config";
+import {
+ type ResolvedProject,
+ type WorkspaceConfig,
+ parseWorkspaceConfig,
+ resolveProjects,
+} from "@lib/config";
import {
type Input,
getDirectoryInput,
@@ -16,14 +22,13 @@ import { copyFiles, resetFolder } from "@utils/files";
import { getCwd } from "@utils/path";
import { runSafe } from "@utils/run-safe";
-import { getConfig } from "~/action/common/config";
-
import { AbstractAction, type HandleResult } from "../abstract.action";
interface BuildTarget {
name: string;
+ directory: string;
entry: string;
- static: string;
+ static?: string;
output: string;
platform: "browser" | "node";
}
@@ -35,12 +40,12 @@ export class BuildAction extends AbstractAction {
public async handle(_args: Input, options: Input): Promise {
const directory = getDirectoryInput(options);
- const config = await getConfig(options, directory);
+ const workspaceConfig = await parseWorkspaceConfig(directory);
const isEditor = getEditorInput(options);
const isWatch = getWatchInput(options);
- const targets = this.resolveTargets(config, options, isEditor);
- const results = await this.buildAll(targets, directory, isWatch);
+ const targets = this.resolveTargets(workspaceConfig, directory, options, isEditor);
+ const results = await this.buildAll(targets, isWatch);
if (isWatch) {
return this.enterWatchMode();
@@ -49,85 +54,77 @@ export class BuildAction extends AbstractAction {
return { success: results.every(Boolean) };
}
- private resolveTargets(config: Config, options: Input, isEditor: boolean): BuildTarget[] {
- const targets: BuildTarget[] = [];
-
- if (config.client.enable)
- targets.push(
- this.createTarget(
- "Client",
-
- "browser",
- getStringInputWithDefault(
- options,
- "clientEntry",
- !isEditor ? config.client.build.entry : config.client.editor.entry,
- ),
- getStringInputWithDefault(options, "clientStaticDir", config.client.build.staticDir),
- getStringInputWithDefault(options, "clientOutDir", config.client.outDir),
- ),
- );
- if (config.server.enable)
- targets.push(
- this.createTarget(
- "Server",
- "node",
- getStringInputWithDefault(
- options,
- "serverEntry",
- !isEditor ? config.server.build.entry : config.server.editor.entry,
- ),
- getStringInputWithDefault(options, "serverStaticDir", config.server.build.staticDir),
- getStringInputWithDefault(options, "serverOutDir", config.server.outDir),
- ),
- );
-
- return targets;
+ private resolveTargets(
+ workspaceConfig: WorkspaceConfig,
+ baseDirectory: string,
+ options: Input,
+ isEditor: boolean,
+ ): BuildTarget[] {
+ const projects = resolveProjects(workspaceConfig, baseDirectory);
+
+ return projects.map((project) =>
+ this.createTarget(project, baseDirectory, options, isEditor, projects.length > 1),
+ );
}
private createTarget(
- name: string,
- platform: "browser" | "node",
- entryFile: string,
- staticDir: string,
- outDir: string,
+ project: ResolvedProject,
+ baseDirectory: string,
+ options: Input,
+ isEditor: boolean,
+ disambiguate: boolean,
): BuildTarget {
+ const isClient = project.config.type === "client";
+ const label = isClient ? "Client" : "Server";
+ const relativePath = relative(baseDirectory, project.directory);
+ const name = disambiguate && relativePath !== "" ? `${label} (${relativePath})` : label;
+ const defaults = isClient ? defaultClientConfig : defaultServerConfig;
+
+ const defaultEntry = isEditor
+ ? (project.config.editor?.entryFile ?? defaults.editor.entryFile)
+ : (project.config.entryFile ?? defaults.entryFile);
+
return {
name,
- entry: entryFile,
- static: staticDir,
- output: outDir,
- platform,
+ directory: project.directory,
+ entry: getStringInputWithDefault(
+ options,
+ isClient ? "clientEntry" : "serverEntry",
+ defaultEntry,
+ ),
+ static: getStringInputWithDefault(
+ options,
+ isClient ? "clientStaticDir" : "serverStaticDir",
+ project.config.dir?.assets ?? defaults.dir.assets,
+ ),
+ output: getStringInputWithDefault(
+ options,
+ isClient ? "clientOutDir" : "serverOutDir",
+ project.config.out?.dir ?? defaults.out.dir,
+ ),
+ platform: isClient ? "browser" : "node",
};
}
- private async buildAll(
- targets: BuildTarget[],
- directory: string,
- isWatch: boolean,
- ): Promise {
+ private async buildAll(targets: BuildTarget[], isWatch: boolean): Promise {
const results: boolean[] = [];
for (const target of targets) {
- const result = await this.buildTarget(target, directory, isWatch);
+ const result = await this.buildTarget(target, isWatch);
results.push(result);
}
return results;
}
- private async buildTarget(
- target: BuildTarget,
- directory: string,
- isWatch: boolean,
- ): Promise {
+ private async buildTarget(target: BuildTarget, isWatch: boolean): Promise {
const packageManager = PackageManagerFactory.create(PackageManagerName.LOCAL_BUN);
const executeBuild = (rebuild = false) =>
runSafe(() => {
- this.resetOut(target.output, directory);
- this.copyFiles(target, directory);
+ this.resetOut(target.output, target.directory);
+ this.copyFiles(target, target.directory);
return packageManager.build(
target.name,
- directory,
+ target.directory,
target.entry,
target.output,
["--asset-naming", "[name].[ext]", "--target", target.platform],
@@ -136,7 +133,7 @@ export class BuildAction extends AbstractAction {
}, false);
if (isWatch) {
- this.watchDirectory(directory, target.entry, () => executeBuild(true));
+ this.watchDirectory(target.directory, target.entry, () => executeBuild(true));
}
const result = await executeBuild();
@@ -160,6 +157,7 @@ export class BuildAction extends AbstractAction {
}
private copyFiles(target: BuildTarget, directory: string): void {
+ if (!target.static) return;
const from = getCwd(join(directory, target.static));
const to = getCwd(join(directory, target.output));
copyFiles(from, to);
diff --git a/src/action/actions/create.action.ts b/src/action/actions/create.action.ts
index a2b6605..9c2a05f 100644
--- a/src/action/actions/create.action.ts
+++ b/src/action/actions/create.action.ts
@@ -1,18 +1,20 @@
+import { defaultClientConfig, defaultServerConfig } from "@nanoforge-dev/config";
+
+import { parseWorkspaceConfig } from "@lib/config";
import {
type Input,
getCreateNameInputOrAsk,
getCreateTypeInput,
getDirectoryInput,
getPathInputWithDefault,
- getServerInput,
} from "@lib/input";
import { Collection, CollectionFactory } from "@lib/schematics";
import { Messages } from "@lib/ui";
+import { CLIError } from "@utils/errors";
import { capitalize } from "@utils/formatting";
import { AbstractAction, type HandleResult } from "../abstract.action";
-import { getConfig } from "../common/config";
import { executeSchematic } from "../common/schematics";
interface CreateValues {
@@ -29,22 +31,33 @@ export class CreateAction extends AbstractAction {
public async handle(args: Input, options: Input): Promise {
const directory = getDirectoryInput(options);
- const config = await getConfig(options, directory, true);
+ const workspaceConfig = await parseWorkspaceConfig(directory);
+
+ if (workspaceConfig.type === "workspace") {
+ throw new CLIError(
+ `Cannot run 'create' at a workspace root ('${directory}').`,
+ "Run it from within a specific client/server project directory instead (-d ).",
+ );
+ }
+
+ const { config } = workspaceConfig;
+ const isClient = config.type === "client";
+ const defaults = isClient ? defaultClientConfig : defaultServerConfig;
const type = getCreateTypeInput(args);
const name = await getCreateNameInputOrAsk(options);
- const isServer = getServerInput(options);
const path = getPathInputWithDefault(
options,
- config[isServer ? "server" : "client"].dirs[type === "component" ? "components" : "systems"],
+ config.dir?.[type === "component" ? "components" : "systems"] ??
+ defaults.dir[type === "component" ? "components" : "systems"],
);
await this.generateElement(directory, type, {
name,
directory: path,
- part: isServer ? "server" : "client",
- language: config.language,
+ part: config.type,
+ language: config.language ?? defaults.language,
});
return {};
diff --git a/src/action/actions/dev.action.ts b/src/action/actions/dev.action.ts
index e7b88fc..2191b04 100644
--- a/src/action/actions/dev.action.ts
+++ b/src/action/actions/dev.action.ts
@@ -1,4 +1,4 @@
-import { type Input, getDevGenerateInput, getDirectoryInput, getEditorInput } from "@lib/input";
+import { type Input, getDirectoryInput, getEditorInput } from "@lib/input";
import { PackageManagerFactory } from "@lib/package-manager";
import { Messages } from "@lib/ui";
@@ -13,23 +13,18 @@ export class DevAction extends AbstractAction {
public async handle(_args: Input, options: Input): Promise {
const directory = getDirectoryInput(options);
- const generate = getDevGenerateInput(options);
const editor = getEditorInput(options);
- const tasks = this.buildTaskList(directory, generate, editor);
+ const tasks = this.buildTaskList(directory, editor);
await Promise.all(tasks);
return { keepAlive: true };
}
- private buildTaskList(directory: string, generate: boolean, editor: boolean): Promise[] {
+ private buildTaskList(directory: string, editor: boolean): Promise[] {
const tasks: Promise[] = [];
const extraFlags = editor ? ["--editor"] : [];
- if (generate) {
- tasks.push(this.runSubCommand("generate", directory, { silent: true, extraFlags }));
- }
-
tasks.push(this.runSubCommand("build", directory, { silent: true, extraFlags }));
tasks.push(this.runSubCommand("start", directory, { silent: false }));
diff --git a/src/action/actions/generate.action.ts b/src/action/actions/generate.action.ts
deleted file mode 100644
index 06389cf..0000000
--- a/src/action/actions/generate.action.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { join } from "node:path";
-
-import { type Config } from "@lib/config";
-import { type Input, getDirectoryInput, getEditorInput, getWatchInput } from "@lib/input";
-import { Collection, CollectionFactory } from "@lib/schematics";
-import { Messages } from "@lib/ui";
-
-import { getCwd } from "@utils/path";
-
-import { AbstractAction, type HandleResult } from "../abstract.action";
-import { getConfig } from "../common/config";
-import { executeSchematic } from "../common/schematics";
-
-interface GenerateValues {
- directory: string;
- language: string;
- initFunctions: boolean;
-}
-
-export class GenerateAction extends AbstractAction {
- protected startMessage = Messages.GENERATE_START;
- protected successMessage = Messages.GENERATE_SUCCESS;
- protected failureMessage = Messages.GENERATE_FAILED;
-
- public async handle(_args: Input, options: Input): Promise {
- const directory = getDirectoryInput(options);
- const config = await getConfig(options, directory);
- const isEditor = getEditorInput(options);
- const isWatch = getWatchInput(options);
-
- await this.generateParts(config, directory, isEditor, isWatch);
-
- if (isWatch) {
- return this.enterWatchMode();
- }
-
- return {};
- }
-
- private extractValues(config: Config): GenerateValues {
- return {
- directory: ".",
- language: config.language,
- initFunctions: config.initFunctions,
- };
- }
-
- private async generateParts(
- config: Config,
- directory: string,
- isEditor: boolean,
- watch: boolean,
- ): Promise {
- const collection = CollectionFactory.create(Collection.NANOFORGE, directory);
- const values = this.extractValues(config);
-
- if (config.client.enable)
- await executeSchematic(
- "Client main file",
- collection,
- "part-main",
- {
- ...values,
- part: "client",
- outFile: !isEditor ? config.client.build.entry : config.client.editor.entry,
- saveFile: config.client.editor.save,
- editor: isEditor,
- },
- watch ? this.watchPath(directory, values.directory, config.client.editor.save) : undefined,
- );
-
- if (config.server.enable)
- await executeSchematic(
- "Server main file",
- collection,
- "part-main",
- {
- ...values,
- part: "server",
- outFile: !isEditor ? config.server.build.entry : config.server.editor.entry,
- saveFile: config.server.editor.save,
- editor: isEditor,
- },
- this.watchPath(directory, values.directory, config.server.editor.save),
- );
- }
-
- private watchPath(directory: string, subDir: string, saveFile: string): string {
- return join(getCwd(directory), subDir, saveFile);
- }
-
- private enterWatchMode(): HandleResult {
- console.info();
- console.info(Messages.GENERATE_WATCH_START);
- console.info();
- return { keepAlive: true };
- }
-}
diff --git a/src/action/actions/new.action.ts b/src/action/actions/new.action.ts
index 6220c46..bab6fb8 100644
--- a/src/action/actions/new.action.ts
+++ b/src/action/actions/new.action.ts
@@ -6,7 +6,6 @@ import {
getDirectoryInput,
getEditorInput,
getNewDockerOrAsk,
- getNewInitFunctionsWithDefault,
getNewLanguageInputOrAsk,
getNewLintInput,
getNewNameInputOrAsk,
@@ -31,8 +30,7 @@ interface NewValues {
packageManager: string;
language: string;
strict: boolean;
- server: boolean;
- initFunctions: boolean;
+ multiplayer: boolean;
skipInstall: boolean;
docker: boolean;
lint: boolean;
@@ -71,8 +69,7 @@ export class NewAction extends AbstractAction {
packageManager: await getNewPackageManagerInputOrAsk(inputs),
language: await getNewLanguageInputOrAsk(inputs),
strict: await getNewStrictOrAsk(inputs),
- server: await getNewServerOrAsk(inputs),
- initFunctions: getNewInitFunctionsWithDefault(inputs),
+ multiplayer: await getNewServerOrAsk(inputs),
skipInstall: await getNewSkipInstallOrAsk(inputs),
docker: await getNewDockerOrAsk(inputs),
lint: getNewLintInput(inputs),
@@ -94,91 +91,63 @@ export class NewAction extends AbstractAction {
console.info(Messages.NEW_GENERATION_START);
console.info();
- await this.generateApplication(collection, values);
- await this.generateConfiguration(collection, values);
- await this.generateClientParts(collection, values);
- if (values.docker) await this.generateDocker(collection, values);
-
- if (values.server) {
- await this.generateServerParts(collection, values);
+ if (values.multiplayer) {
+ await this.generateWorkspace(collection, values);
+ await this.generateClientProject(collection, values);
+ await this.generateServerProject(collection, values);
+ } else {
+ await this.generateClientProject(collection, values);
}
}
- private generateApplication(
+ private generateWorkspace(
collection: ReturnType,
values: NewValues,
) {
- return executeSchematic("Application", collection, "application", {
+ return executeSchematic("Workspace", collection, "workspace", {
name: values.name,
directory: values.directory,
- packageManager: values.packageManager,
language: values.language,
strict: values.strict,
- server: values.server,
- lint: values.lint,
- editor: values.editor,
- });
- }
-
- private generateConfiguration(
- collection: ReturnType,
- values: NewValues,
- ) {
- return executeSchematic("Configuration", collection, "configuration", {
- name: values.name,
- directory: values.directory,
- server: values.server,
- language: values.language,
- initFunctions: values.initFunctions,
- });
- }
-
- private async generateClientParts(
- collection: ReturnType,
- values: NewValues,
- ) {
- const partOptions = this.partOptions(values, "client");
-
- await executeSchematic("Client base", collection, "part-base", {
- ...partOptions,
- server: values.server,
- });
- await executeSchematic("Client main file", collection, "part-main", {
- ...partOptions,
+ packageManager: values.packageManager,
+ docker: values.docker,
});
}
- private async generateServerParts(
+ private generateClientProject(
collection: ReturnType,
values: NewValues,
) {
- const partOptions = this.partOptions(values, "server");
-
- await executeSchematic("Server base", collection, "part-base", {
- ...partOptions,
- server: values.server,
- });
- await executeSchematic("Server main file", collection, "part-main", {
- ...partOptions,
+ return executeSchematic("Client project", collection, "project", {
+ ...this.projectOptions(values, "client"),
+ hasServer: values.multiplayer,
});
}
- private async generateDocker(
+ private generateServerProject(
collection: ReturnType,
values: NewValues,
) {
- await executeSchematic("Docker", collection, "docker", {
- directory: values.directory,
- packageManager: values.packageManager,
- });
+ return executeSchematic(
+ "Server project",
+ collection,
+ "project",
+ this.projectOptions(values, "server"),
+ );
}
- private partOptions(values: NewValues, part: "client" | "server") {
+ private projectOptions(values: NewValues, part: "client" | "server") {
return {
part,
- directory: values.directory,
+ workspaceName: values.multiplayer ? values.name : undefined,
+ name: values.multiplayer ? part : values.name,
+ directory: values.multiplayer ? join(values.directory, "apps", part) : values.directory,
language: values.language,
- initFunctions: values.initFunctions,
+ strict: values.strict,
+ packageManager: values.packageManager,
+ workspace: values.multiplayer,
+ docker: values.docker,
+ editor: values.editor,
};
}
diff --git a/src/action/actions/start.action.ts b/src/action/actions/start.action.ts
index 2198e37..8aca366 100644
--- a/src/action/actions/start.action.ts
+++ b/src/action/actions/start.action.ts
@@ -1,7 +1,13 @@
+import { type ClientConfig, defaultClientConfig, defaultServerConfig } from "@nanoforge-dev/config";
import dotenv from "dotenv";
import { join, resolve } from "path";
-import { type Config } from "@lib/config";
+import {
+ type ResolvedProject,
+ type WorkspaceConfig,
+ parseWorkspaceConfig,
+ resolveProjects,
+} from "@lib/config";
import {
type Input,
getDirectoryInput,
@@ -9,18 +15,16 @@ import {
getStringInputWithDefault,
getWatchInput,
} from "@lib/input";
-import { PackageManagerFactory } from "@lib/package-manager";
+import { type PackageManager, PackageManagerFactory } from "@lib/package-manager";
import { Messages } from "@lib/ui";
import { CLIError } from "@utils/errors";
import { getCwd, getModulePath } from "@utils/path";
import { runSafe } from "@utils/run-safe";
-import { getConfig } from "~/action/common/config";
-
import { AbstractAction, type HandleResult } from "../abstract.action";
-interface SSLOptions {
+interface TLSOptions {
cert: string;
key: string;
}
@@ -30,6 +34,27 @@ interface FullEnv {
server: Record;
}
+interface ClientStartTarget {
+ directory: string;
+ outDir: string;
+ platform: "client";
+ port: string;
+ tls?: TLSOptions;
+}
+
+interface ServerStartTarget {
+ directory: string;
+ outDir: string;
+ platform: "server";
+}
+
+type StartTarget = ClientStartTarget | ServerStartTarget;
+
+const isClientTarget = (target: StartTarget): target is ClientStartTarget =>
+ target.platform === "client";
+const isServerTarget = (target: StartTarget): target is ServerStartTarget =>
+ target.platform === "server";
+
export class StartAction extends AbstractAction {
protected startMessage = Messages.START_START;
protected successMessage = Messages.START_SUCCESS;
@@ -37,155 +62,170 @@ export class StartAction extends AbstractAction {
public async handle(_args: Input, options: Input): Promise {
const directory = getDirectoryInput(options);
- const config = await getConfig(options, directory);
- const clientDir = getStringInputWithDefault(options, "clientDir", config.client.outDir);
- const serverDir = getStringInputWithDefault(options, "serverDir", config.server.outDir);
+ const workspaceConfig = await parseWorkspaceConfig(directory);
const watch = getWatchInput(options);
- const port = getStringInputWithDefault(options, "port", config.client.port);
- const ssl = this.resolveSSL(options, config);
-
- const tasks = this.buildStartTasks(config, directory, {
- clientDir,
- serverDir,
- watch,
- port,
- ssl,
- });
+
+ const targets = this.resolveTargets(workspaceConfig, directory, options);
+ const serverTargets = targets.filter(isServerTarget);
+ const clientTargets = targets.filter(isClientTarget);
+
+ if (targets.length === 0) {
+ throw new CLIError(
+ `No client or server project found at '${directory}'.`,
+ "Check your nanoforge.config's 'packages' patterns, or run 'nf new' to scaffold one.",
+ );
+ }
+
+ const env = this.parseEnv(directory);
+ const packageManager = await PackageManagerFactory.find(directory);
+ const tasks = this.buildStartTasks(packageManager, serverTargets, clientTargets, watch, env);
await Promise.all(tasks);
return { keepAlive: true };
}
- private resolveSSL(options: Input, config: Config): SSLOptions | undefined {
+ private resolveTLS(options: Input, config: ClientConfig): TLSOptions | undefined {
const cliCert = getStringInput(options, "cert");
const cliKey = getStringInput(options, "key");
- const isSslRequested = Boolean(cliCert || cliKey || config.ssl?.enable);
+ const tls = config.tls;
+ const configTls = tls && tls.enable ? tls : undefined;
+ const isTlsRequested = Boolean(cliCert || cliKey || configTls);
- if (!isSslRequested) return undefined;
+ if (!isTlsRequested) return undefined;
- const cert = cliCert ? cliCert : config.ssl?.cert;
- const key = cliKey ? cliKey : config.ssl?.key;
+ const cert = cliCert ?? configTls?.cert;
+ const key = cliKey ?? configTls?.key;
if (!cert) {
throw new CLIError(
- "No certificate found for SSL.",
- "Please provide a certificate path with --cert or configure 'ssl.cert' in your nanoforge.config.json.",
+ "No certificate found for TLS.",
+ "Please provide a certificate path with --cert or configure 'tls.cert' in your nanoforge.config.ts",
);
}
if (!key) {
throw new CLIError(
- "No key found for SSL.",
- "Please provide a key path with --key or configure 'ssl.key' in your nanoforge.config.json.",
+ "No key found for TLS.",
+ "Please provide a key path with --key or configure 'tls.key' in your nanoforge.config.ts",
);
}
+ return { cert, key };
+ }
+
+ private resolveTargets(
+ workspaceConfig: WorkspaceConfig,
+ baseDirectory: string,
+ options: Input,
+ ): StartTarget[] {
+ const projects = resolveProjects(workspaceConfig, baseDirectory);
+ return projects.map((project) => this.createTarget(project, options));
+ }
+
+ private createTarget(project: ResolvedProject, options: Input): StartTarget {
+ if (project.config.type === "server") {
+ return {
+ directory: project.directory,
+ outDir: getStringInputWithDefault(
+ options,
+ "serverDir",
+ project.config.out?.dir ?? defaultServerConfig.out.dir,
+ ),
+ platform: "server",
+ };
+ }
+
return {
- cert,
- key,
+ directory: project.directory,
+ outDir: getStringInputWithDefault(
+ options,
+ "clientDir",
+ project.config.out?.dir ?? defaultClientConfig.out.dir,
+ ),
+ platform: "client",
+ port: getStringInputWithDefault(
+ options,
+ "port",
+ project.config.port ?? defaultClientConfig.port,
+ ),
+ tls: this.resolveTLS(options, project.config),
};
}
private buildStartTasks(
- config: Config,
- directory: string,
- options: {
- clientDir: string;
- serverDir: string;
- watch: boolean;
- port: string;
- ssl?: SSLOptions;
- },
+ packageManager: PackageManager,
+ serverTargets: ServerStartTarget[],
+ clientTargets: ClientStartTarget[],
+ watch: boolean,
+ env: FullEnv,
): Promise[] {
- const env = this.parseEnv(directory);
const tasks: Promise[] = [];
- const { clientDir, serverDir, watch, port, ssl } = options;
- if (config.server.enable)
- tasks.push(this.startServer(directory, config, { serverDir, watch }, env));
+ for (const target of serverTargets) {
+ tasks.push(this.startServer(packageManager, target, watch, env));
+ }
- if (config.client.enable)
- tasks.push(
- this.startClient(directory, config, { clientDir, serverDir, watch, port, ssl }, env),
- );
+ for (const target of clientTargets) {
+ tasks.push(this.startClient(packageManager, target, { watch, serverTargets }, env));
+ }
return tasks;
}
private async startClient(
- directory: string,
- config: Config,
- options: {
- clientDir: string;
- serverDir: string;
- watch: boolean;
- port: string;
- ssl?: SSLOptions;
- },
+ packageManager: PackageManager,
+ target: ClientStartTarget,
+ options: { watch: boolean; serverTargets: ServerStartTarget[] },
env: FullEnv,
): Promise {
const loaderPath = getModulePath("@nanoforge-dev/loader-client/package.json", true);
- const params = this.buildClientParams(directory, config, options);
- await this.runLoader("Client", loaderPath, params, env.client);
+ const params = this.buildClientParams(target, options);
+ await this.runLoader(packageManager, "Client", loaderPath, params, env.client);
}
private async startServer(
- directory: string,
- config: Config,
- options: { serverDir: string; watch: boolean },
+ packageManager: PackageManager,
+ target: ServerStartTarget,
+ watch: boolean,
env: FullEnv,
): Promise {
const loaderPath = getModulePath("@nanoforge-dev/loader-server/package.json", true);
- const params = this.buildServerParams(directory, config, options);
- await this.runLoader("Server", loaderPath, params, env.server);
+ const params = this.buildServerParams(target, watch);
+ await this.runLoader(packageManager, "Server", loaderPath, params, env.server);
}
private buildClientParams(
- directory: string,
- config: Config,
- options: {
- clientDir: string;
- serverDir: string;
- watch: boolean;
- port: string;
- ssl?: SSLOptions;
- },
+ target: ClientStartTarget,
+ options: { watch: boolean; serverTargets: ServerStartTarget[] },
): string[] {
const params: Record = {
- "-d": getCwd(join(directory, options.clientDir)),
- "-p": options.port,
+ "-d": getCwd(join(target.directory, target.outDir)),
+ "-p": target.port,
};
- if (options.watch) params["--watch"] = true;
if (options.watch) {
params["--watch"] = true;
- if (config.server.enable) {
- params["--watch-server-dir"] = getCwd(join(directory, options.serverDir));
+ const [server] = options.serverTargets;
+ if (server && options.serverTargets.length === 1) {
+ params["--watch-server-dir"] = getCwd(join(server.directory, server.outDir));
}
}
- if (options.ssl) {
- params["--cert"] = options.ssl.cert;
- params["--key"] = options.ssl.key;
+ if (target.tls) {
+ params["--cert"] = target.tls.cert;
+ params["--key"] = target.tls.key;
}
return this.buildParams(params);
}
- private buildServerParams(
- directory: string,
- _config: Config,
- options: {
- serverDir: string;
- watch: boolean;
- },
- ): string[] {
+ private buildServerParams(target: ServerStartTarget, watch: boolean): string[] {
const params: Record = {
- "-d": getCwd(join(directory, options.serverDir)),
+ "-d": getCwd(join(target.directory, target.outDir)),
};
- if (options.watch) params["--watch"] = true;
+ if (watch) params["--watch"] = true;
return this.buildParams(params);
}
@@ -227,13 +267,13 @@ export class StartAction extends AbstractAction {
}
private async runLoader(
+ packageManager: PackageManager,
name: string,
directory: string,
params: string[],
env: Record,
): Promise {
await runSafe(async () => {
- const packageManager = await PackageManagerFactory.find(directory);
await packageManager.run(name, directory, "start", params, env, [], true);
});
}
diff --git a/src/action/common/config.ts b/src/action/common/config.ts
deleted file mode 100644
index f7e879e..0000000
--- a/src/action/common/config.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { type Config, loadConfig } from "@lib/config";
-import { type Input, getConfigInput } from "@lib/input";
-
-export const getConfig = (inputs: Input, dir: string, noThrow?: boolean): Promise => {
- return loadConfig(dir, getConfigInput(inputs), noThrow);
-};
diff --git a/src/action/index.ts b/src/action/index.ts
index 0d06368..ad55b22 100644
--- a/src/action/index.ts
+++ b/src/action/index.ts
@@ -2,7 +2,6 @@ export * from "./actions/build.action";
export * from "./actions/create.action";
export * from "./actions/dev.action";
export * from "./actions/editor.action";
-export * from "./actions/generate.action";
export * from "./actions/install.action";
export * from "./actions/login.action";
export * from "./actions/logout.action";
diff --git a/src/command/command.loader.ts b/src/command/command.loader.ts
index e06c1d5..51c4192 100644
--- a/src/command/command.loader.ts
+++ b/src/command/command.loader.ts
@@ -8,7 +8,6 @@ import {
CreateAction,
DevAction,
EditorAction,
- GenerateAction,
InstallAction,
LoginAction,
LogoutAction,
@@ -22,7 +21,6 @@ import { BuildCommand } from "./commands/build.command";
import { CreateCommand } from "./commands/create.command";
import { DevCommand } from "./commands/dev.command";
import { EditorCommand } from "./commands/editor.command";
-import { GenerateCommand } from "./commands/generate.command";
import { InstallCommand } from "./commands/install.command";
import { LoginCommand } from "./commands/login.command";
import { LogoutCommand } from "./commands/logout.command";
@@ -39,7 +37,6 @@ export class CommandLoader {
new StartCommand(new StartAction()).load(program);
new DevCommand(new DevAction()).load(program);
new EditorCommand(new EditorAction()).load(program);
- new GenerateCommand(new GenerateAction()).load(program);
new CreateCommand(new CreateAction()).load(program);
new LoginCommand(new LoginAction()).load(program);
new LogoutCommand(new LogoutAction()).load(program);
diff --git a/src/command/commands/build.command.ts b/src/command/commands/build.command.ts
index 61e6472..8619ef1 100644
--- a/src/command/commands/build.command.ts
+++ b/src/command/commands/build.command.ts
@@ -1,12 +1,9 @@
import { type Command } from "commander";
-import { CONFIG_FILE_NAME } from "@lib/constants";
-
import { AbstractCommand } from "../abstract.command";
interface BuildOptions {
directory?: string;
- config?: string;
clientEntry?: string;
serverEntry?: string;
clientStaticDir?: string;
@@ -23,7 +20,6 @@ export class BuildCommand extends AbstractCommand {
.command("build")
.description("build your game")
.option("-d, --directory ", "specify the working directory of the command")
- .option("-c, --config ", "path to the config file", CONFIG_FILE_NAME)
.option("--client-entry ", "specify the entry file of the client")
.option("--server-entry ", "specify the entry file of the server")
.option("--client-static-dir ", "specify the static directory of the client")
@@ -35,7 +31,6 @@ export class BuildCommand extends AbstractCommand {
.action(async (rawOptions: BuildOptions) => {
const options = AbstractCommand.mapToInput({
directory: rawOptions.directory,
- config: rawOptions.config,
clientEntry: rawOptions.clientEntry,
serverEntry: rawOptions.serverEntry,
clientStaticDir: rawOptions.clientStaticDir,
diff --git a/src/command/commands/create.command.ts b/src/command/commands/create.command.ts
index 7f91988..30d0950 100644
--- a/src/command/commands/create.command.ts
+++ b/src/command/commands/create.command.ts
@@ -1,14 +1,10 @@
import { type Command } from "commander";
-import { CONFIG_FILE_NAME } from "@lib/constants";
-
import { AbstractCommand } from "../abstract.command";
interface CreateOptions {
directory?: string;
- config: string;
name?: string;
- server: boolean;
path?: string;
}
@@ -18,13 +14,7 @@ export class CreateCommand extends AbstractCommand {
.command("create ")
.description("create nanoforge components or systems")
.option("-d, --directory ", "specify the working directory of the command")
- .option("-c, --config ", "path to the config file", CONFIG_FILE_NAME)
.option("-n, --name ", "name of the component/system")
- .option(
- "-s, --server",
- "install components/systems on server (default install on client)",
- false,
- )
.option(
"-p, --path ",
"path to the component/system folder (default: /)",
@@ -36,9 +26,7 @@ export class CreateCommand extends AbstractCommand {
const options = AbstractCommand.mapToInput({
directory: rawOptions.directory,
- config: rawOptions.config,
name: rawOptions.name,
- server: rawOptions.server,
path: rawOptions.path,
});
diff --git a/src/command/commands/dev.command.ts b/src/command/commands/dev.command.ts
index a985e9f..b239d7e 100644
--- a/src/command/commands/dev.command.ts
+++ b/src/command/commands/dev.command.ts
@@ -7,7 +7,6 @@ import { AbstractCommand } from "../abstract.command";
interface DevOptions {
directory?: string;
config?: string;
- generate?: boolean;
editor?: boolean;
}
@@ -18,13 +17,11 @@ export class DevCommand extends AbstractCommand {
.description("run your game in dev mode")
.option("-d, --directory ", "specify the working directory of the command")
.option("-c, --config ", "path to the config file", CONFIG_FILE_NAME)
- .option("--generate", "generate app from config", false)
.option("-e, --editor", "run the editor", false)
.action(async (rawOptions: DevOptions) => {
const options = AbstractCommand.mapToInput({
directory: rawOptions.directory,
config: rawOptions.config,
- generate: rawOptions.generate,
editor: rawOptions.editor,
});
diff --git a/src/command/commands/generate.command.ts b/src/command/commands/generate.command.ts
deleted file mode 100644
index 11203f7..0000000
--- a/src/command/commands/generate.command.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { type Command } from "commander";
-
-import { CONFIG_FILE_NAME } from "@lib/constants";
-
-import { AbstractCommand } from "../abstract.command";
-
-interface GenerateOptions {
- directory?: string;
- config?: string;
- editor?: boolean;
- watch?: boolean;
-}
-
-export class GenerateCommand extends AbstractCommand {
- public load(program: Command) {
- program
- .command("generate")
- .description("generate nanoforge files from config")
- .option("-d, --directory ", "specify the working directory of the command")
- .option("-c, --config ", "path to the config file", CONFIG_FILE_NAME)
- .option("--editor", "specify if the project must generate editor main file")
- .option("--watch", "generate app in watching mode", false)
- .action(async (rawOptions: GenerateOptions) => {
- const options = AbstractCommand.mapToInput({
- directory: rawOptions.directory,
- config: rawOptions.config,
- editor: rawOptions.editor,
- watch: rawOptions.watch,
- });
-
- await this.action.run(new Map(), options);
- });
- }
-}
diff --git a/src/command/commands/start.command.ts b/src/command/commands/start.command.ts
index 4299acc..036afda 100644
--- a/src/command/commands/start.command.ts
+++ b/src/command/commands/start.command.ts
@@ -1,12 +1,9 @@
import { type Command } from "commander";
-import { CONFIG_FILE_NAME } from "@lib/constants";
-
import { AbstractCommand } from "../abstract.command";
interface StartOptions {
directory?: string;
- config?: string;
port?: string;
clientDir?: string;
serverDir?: string;
@@ -21,17 +18,15 @@ export class StartCommand extends AbstractCommand {
.command("start")
.description("start your game")
.option("-d, --directory ", "specify the working directory of the command")
- .option("-c, --config ", "path to the config file", CONFIG_FILE_NAME)
.option("-p, --port ", "specify the port of the loader (the website to load the game)")
- .option("--client-dir ", "specify the directory of the client")
- .option("--server-dir ", "specify the directory of the server")
+ .option("--client-dir ", "specify the output directory of the client")
+ .option("--server-dir ", "specify the output directory of the server")
.option("--watch", "run app in watching mode", false)
- .option("--cert ", "path to the SSL certificate for HTTPS")
- .option("--key ", "path to the SSL key for HTTPS")
+ .option("--cert ", "path to the TLS certificate for HTTPS")
+ .option("--key ", "path to the TLS key for HTTPS")
.action(async (rawOptions: StartOptions) => {
const options = AbstractCommand.mapToInput({
directory: rawOptions.directory,
- config: rawOptions.config,
port: rawOptions.port,
clientDir: rawOptions.clientDir,
serverDir: rawOptions.serverDir,
diff --git a/src/lib/config/config-defaults.ts b/src/lib/config/config-defaults.ts
deleted file mode 100644
index fe84202..0000000
--- a/src/lib/config/config-defaults.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { type Config } from "./config.type";
-
-export const CONFIG_DEFAULTS: Config = {
- name: "nanoforge-app",
- language: "ts",
- initFunctions: false,
- ssl: {
- enable: false,
- cert: "",
- key: "",
- },
- client: {
- enable: true,
- port: "3000",
- outDir: ".nanoforge/client",
- build: {
- entry: "client/main.ts",
- staticDir: "client/static",
- },
- editor: {
- entry: ".nanoforge/editor/client/main.ts",
- save: ".nanoforge/client.save.json",
- },
- dirs: {
- components: "client/components",
- systems: "client/systems",
- },
- },
- server: {
- enable: false,
- outDir: ".nanoforge/server",
- build: {
- entry: "server/main.ts",
- staticDir: "server/static",
- },
- editor: {
- entry: ".nanoforge/editor/server/main.ts",
- save: ".nanoforge/server.save.json",
- },
- dirs: {
- components: "server/components",
- systems: "server/systems",
- },
- },
-};
diff --git a/src/lib/config/config-loader.spec.ts b/src/lib/config/config-loader.spec.ts
index d61c13b..b39997a 100644
--- a/src/lib/config/config-loader.spec.ts
+++ b/src/lib/config/config-loader.spec.ts
@@ -1,105 +1,49 @@
-import { plainToInstance } from "class-transformer";
-import { validate } from "class-validator";
-import { existsSync, readFileSync } from "node:fs";
+import { parseConfig } from "@nanoforge-dev/config";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
+import { loadConfig } from "./config-loader";
+
vi.mock("node:fs", () => ({
existsSync: vi.fn(),
- readFileSync: vi.fn(),
-}));
-
-vi.mock("class-transformer", () => ({
- plainToInstance: vi.fn(),
- Expose: () => () => {},
- Type: () => () => {},
}));
-vi.mock("class-validator", () => ({
- validate: vi.fn(),
- IsBoolean: () => () => {},
- IsEnum: () => () => {},
- IsNotEmpty: () => () => {},
- IsPort: () => () => {},
- IsString: () => () => {},
- ValidateNested: () => () => {},
+vi.mock("@nanoforge-dev/config", () => ({
+ parseConfig: vi.fn(),
}));
describe("loadConfig", () => {
beforeEach(() => {
vi.resetAllMocks();
- vi.resetModules();
});
- it("should load config from a named file", async () => {
- const rawConfig = { name: "test-app", language: "ts" };
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(rawConfig));
- vi.mocked(plainToInstance).mockReturnValue(rawConfig as any);
- vi.mocked(validate).mockResolvedValue([]);
+ it("loads nanoforge.config.ts when it exists", async () => {
+ vi.mocked(existsSync).mockImplementation((path) => String(path).endsWith(".ts"));
+ const resolved = { type: "client" as const };
+ vi.mocked(parseConfig).mockResolvedValue(resolved);
- const { loadConfig: freshLoad } = await import("./config-loader");
- const result = await freshLoad("/project", "custom.json");
+ const result = await loadConfig("/project");
- expect(readFileSync).toHaveBeenCalledWith("/project/custom.json", "utf-8");
- expect(result).toEqual(rawConfig);
+ expect(parseConfig).toHaveBeenCalledWith(join("/project", "nanoforge.config.ts"));
+ expect(result).toBe(resolved);
});
- it("should search for nanoforge.config.json when no name provided", async () => {
- const rawConfig = { name: "my-app" };
- vi.mocked(existsSync).mockReturnValue(true);
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(rawConfig));
- vi.mocked(plainToInstance).mockReturnValue(rawConfig as any);
- vi.mocked(validate).mockResolvedValue([]);
+ it("falls back to nanoforge.config.js when no .ts file exists", async () => {
+ vi.mocked(existsSync).mockImplementation((path) => String(path).endsWith(".js"));
+ const resolved = { type: "server" as const };
+ vi.mocked(parseConfig).mockResolvedValue(resolved);
- const { loadConfig: freshLoad } = await import("./config-loader");
- const result = await freshLoad("/project");
+ const result = await loadConfig("/project");
- expect(existsSync).toHaveBeenCalled();
- expect(result).toEqual(rawConfig);
+ expect(parseConfig).toHaveBeenCalledWith(join("/project", "nanoforge.config.js"));
+ expect(result).toBe(resolved);
});
- it("should throw when no config file is found", async () => {
+ it("throws when neither nanoforge.config.ts nor .js exists", async () => {
vi.mocked(existsSync).mockReturnValue(false);
- const { loadConfig: freshLoad } = await import("./config-loader");
-
- await expect(freshLoad("/project")).rejects.toThrow("Configuration file not found at path:");
- });
-
- it("should throw when config file cannot be parsed", async () => {
- vi.mocked(readFileSync).mockReturnValue("invalid json");
- vi.mocked(existsSync).mockReturnValue(true);
-
- const { loadConfig: freshLoad } = await import("./config-loader");
-
- await expect(freshLoad("/project")).rejects.toThrow("File System Error [read config file]");
- });
-
- it("should throw on validation errors", async () => {
- const rawConfig = { name: "" };
- vi.mocked(existsSync).mockReturnValue(true);
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(rawConfig));
- vi.mocked(plainToInstance).mockReturnValue(rawConfig as any);
- vi.mocked(validate).mockResolvedValue([
- { property: "name", toString: () => "name must not be empty" } as any,
- ]);
-
- const { loadConfig: freshLoad } = await import("./config-loader");
-
- await expect(freshLoad("/project")).rejects.toThrow("Invalid config");
- });
-
- it("should cache config after first load", async () => {
- const rawConfig = { name: "cached-app" };
- vi.mocked(existsSync).mockReturnValue(true);
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(rawConfig));
- vi.mocked(plainToInstance).mockReturnValue(rawConfig as any);
- vi.mocked(validate).mockResolvedValue([]);
-
- const { loadConfig: freshLoad } = await import("./config-loader");
-
- await freshLoad("/project");
- await freshLoad("/project");
-
- expect(readFileSync).toHaveBeenCalledTimes(1);
+ await expect(loadConfig("/project")).rejects.toThrow("Configuration file not found at path:");
+ expect(parseConfig).not.toHaveBeenCalled();
});
});
diff --git a/src/lib/config/config-loader.ts b/src/lib/config/config-loader.ts
index 86ae6d3..e7697f0 100644
--- a/src/lib/config/config-loader.ts
+++ b/src/lib/config/config-loader.ts
@@ -1,54 +1,26 @@
-import { plainToInstance } from "class-transformer";
-import { validate } from "class-validator";
-import { existsSync, readFileSync } from "node:fs";
+import { type NanoforgeConfig, parseConfig } from "@nanoforge-dev/config";
+import { existsSync } from "node:fs";
import { join } from "node:path";
-import { CONFIG_FILE_NAME } from "@lib/constants";
+import { ConfigNotFoundError } from "@utils/errors";
-import { CLIError, ConfigNotFoundError, FileSystemError } from "@utils/errors";
-import { deepMerge } from "@utils/object";
+const CONFIG_BASE_NAME = "nanoforge.config";
+const CONFIG_EXTENSIONS = ["ts", "js"];
-import { CONFIG_DEFAULTS } from "./config-defaults";
-import { Config } from "./config.type";
-
-let config: Config | null;
-
-const getConfigPath = (directory: string, name?: string) => {
- if (name) {
- return join(directory, name);
- } else {
- for (const n of [CONFIG_FILE_NAME]) {
- const path = join(directory, n);
- if (existsSync(path)) return path;
- }
- throw new ConfigNotFoundError(join(directory, CONFIG_FILE_NAME));
+const getConfigPath = (directory: string): string => {
+ for (const ext of CONFIG_EXTENSIONS) {
+ const path = join(directory, `${CONFIG_BASE_NAME}.${ext}`);
+ if (existsSync(path)) return path;
}
+ throw new ConfigNotFoundError(join(directory, `${CONFIG_BASE_NAME}.ts`));
};
-export const loadConfig = async (
- directory: string,
- name?: string,
- noThrow: boolean = false,
-): Promise => {
- if (config) return config;
-
- let rawData;
-
- const path = getConfigPath(directory, name);
- try {
- rawData = deepMerge(CONFIG_DEFAULTS, JSON.parse(readFileSync(path, "utf-8")));
- } catch {
- rawData = noThrow ? CONFIG_DEFAULTS : null;
- }
- if (!rawData) throw new FileSystemError("read config file", path);
-
- const data = plainToInstance(Config, rawData, {
- excludeExtraneousValues: true,
- });
- const errors = await validate(data);
- if (errors.length > 0)
- throw new CLIError(`Invalid config:\n${errors.toString().replace(/,/g, "\n")}`);
-
- config = data;
- return config;
+/**
+ * Looks up a `nanoforge.config.ts` file in `directory`, falling back to
+ * `nanoforge.config.js` when no `.ts` file exists. Throws a
+ * `ConfigNotFoundError` when neither is present.
+ */
+export const loadConfig = async (directory: string): Promise => {
+ const path = getConfigPath(directory);
+ return parseConfig(path);
};
diff --git a/src/lib/config/config.type.ts b/src/lib/config/config.type.ts
deleted file mode 100644
index b50435c..0000000
--- a/src/lib/config/config.type.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { Expose, Type } from "class-transformer";
-import { IsBoolean, IsEnum, IsNotEmpty, IsPort, IsString, ValidateNested } from "class-validator";
-
-export class BuildConfig {
- @Expose()
- @IsString()
- @IsNotEmpty()
- entry!: string;
-
- @Expose()
- @IsString()
- @IsNotEmpty()
- staticDir!: string;
-}
-
-export class EditorConfig {
- @Expose()
- @IsString()
- @IsNotEmpty()
- entry!: string;
-
- @Expose()
- @IsString()
- @IsNotEmpty()
- save!: string;
-}
-
-export class DirsConfig {
- @Expose()
- @IsString()
- @IsNotEmpty()
- components!: string;
-
- @Expose()
- @IsString()
- @IsNotEmpty()
- systems!: string;
-}
-
-export class ClientConfig {
- @Expose()
- @IsBoolean()
- enable!: boolean;
-
- @Expose()
- @IsPort()
- port!: string;
-
- @Expose()
- @IsString()
- @IsNotEmpty()
- outDir!: string;
-
- @Expose()
- @Type(() => BuildConfig)
- @ValidateNested()
- build!: BuildConfig;
-
- @Expose()
- @Type(() => EditorConfig)
- @ValidateNested()
- editor!: EditorConfig;
-
- @Expose()
- @Type(() => DirsConfig)
- @ValidateNested()
- dirs!: DirsConfig;
-}
-
-export class ServerConfig {
- @Expose()
- @IsBoolean()
- enable!: boolean;
-
- @Expose()
- @IsString()
- @IsNotEmpty()
- outDir!: string;
-
- @Expose()
- @Type(() => BuildConfig)
- @ValidateNested()
- build!: BuildConfig;
-
- @Expose()
- @Type(() => EditorConfig)
- @ValidateNested()
- editor!: EditorConfig;
-
- @Expose()
- @Type(() => DirsConfig)
- @ValidateNested()
- dirs!: DirsConfig;
-}
-
-export class SslConfig {
- @Expose()
- @IsBoolean()
- enable!: boolean;
-
- @Expose()
- @IsString()
- cert!: string;
-
- @Expose()
- @IsString()
- key!: string;
-}
-
-export class Config {
- @Expose()
- @IsString()
- @IsNotEmpty()
- name!: string;
-
- @Expose()
- @IsEnum(["ts", "js"])
- language!: "ts" | "js";
-
- @Expose()
- @IsBoolean()
- initFunctions!: boolean;
-
- @Expose()
- @Type(() => ClientConfig)
- @ValidateNested()
- client!: ClientConfig;
-
- @Expose()
- @Type(() => ServerConfig)
- @ValidateNested()
- server!: ServerConfig;
-
- @Expose()
- @Type(() => SslConfig)
- @ValidateNested()
- ssl!: SslConfig;
-}
diff --git a/src/lib/config/index.ts b/src/lib/config/index.ts
index aacfbbc..a8646a7 100644
--- a/src/lib/config/index.ts
+++ b/src/lib/config/index.ts
@@ -1,2 +1,6 @@
-export * from "./config.type";
export * from "./config-loader";
+export * from "./workspace-config-parser";
+export * from "./workspace-config.types";
+export * from "./workspace-loader";
+export { ConfigParseError } from "@nanoforge-dev/config";
+export type { NanoforgeConfig as Config } from "@nanoforge-dev/config";
diff --git a/src/lib/config/workspace-config-parser.spec.ts b/src/lib/config/workspace-config-parser.spec.ts
new file mode 100644
index 0000000..5617a67
--- /dev/null
+++ b/src/lib/config/workspace-config-parser.spec.ts
@@ -0,0 +1,133 @@
+import { existsSync, statSync } from "node:fs";
+import { glob } from "node:fs/promises";
+import { join } from "node:path";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { ConfigNotFoundError } from "@utils/errors";
+
+import { loadConfig } from "./config-loader";
+import { parseWorkspaceConfig } from "./workspace-config-parser";
+
+vi.mock("node:fs", () => ({
+ existsSync: vi.fn(),
+ statSync: vi.fn(),
+}));
+
+vi.mock("node:fs/promises", () => ({
+ glob: vi.fn(),
+}));
+
+vi.mock("./config-loader", () => ({
+ loadConfig: vi.fn(),
+}));
+
+const asDirectory = () => ({ isDirectory: () => true });
+
+const asyncIterableOf = (values: T[]): AsyncIterable => ({
+ [Symbol.asyncIterator]: async function* () {
+ for (const value of values) yield value;
+ },
+});
+
+describe("parseWorkspaceConfig", () => {
+ beforeEach(() => {
+ vi.resetAllMocks();
+ vi.mocked(existsSync).mockReturnValue(true);
+ vi.mocked(statSync).mockReturnValue(asDirectory() as ReturnType);
+ });
+
+ it("resolves a root project config", async () => {
+ const config = { type: "client" as const };
+ vi.mocked(loadConfig).mockResolvedValue(config);
+
+ const result = await parseWorkspaceConfig("/app");
+
+ expect(result).toEqual({ type: "project", config });
+ });
+
+ it("throws when the root config is a lib", async () => {
+ vi.mocked(loadConfig).mockResolvedValue({ type: "lib" as const });
+
+ await expect(parseWorkspaceConfig("/app")).rejects.toThrow(
+ "the root config cannot be of type 'lib'",
+ );
+ });
+
+ it("resolves every directory a package pattern matches", async () => {
+ const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] };
+ const clientConfig = { type: "client" as const };
+ const libConfig = { type: "lib" as const };
+
+ vi.mocked(glob).mockReturnValue(
+ asyncIterableOf(["apps/client", "apps/foo-lib"]) as ReturnType,
+ );
+ vi.mocked(loadConfig).mockImplementation(async (directory: string) => {
+ if (directory === "/app") return workspaceConfig;
+ if (directory === join("/app", "apps/client")) return clientConfig;
+ if (directory === join("/app", "apps/foo-lib")) return libConfig;
+ throw new Error(`unexpected directory: ${directory}`);
+ });
+
+ const result = await parseWorkspaceConfig("/app");
+
+ expect(result).toEqual({
+ type: "workspace",
+ config: workspaceConfig,
+ projects: {
+ "apps/client": { type: "project", config: clientConfig },
+ "apps/foo-lib": { type: "lib", config: libConfig },
+ },
+ });
+ });
+
+ it("skips a matched directory with no config instead of throwing", async () => {
+ const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] };
+ const clientConfig = { type: "client" as const };
+
+ vi.mocked(glob).mockReturnValue(
+ asyncIterableOf(["apps/client", "apps/empty"]) as ReturnType,
+ );
+ vi.mocked(loadConfig).mockImplementation(async (directory: string) => {
+ if (directory === "/app") return workspaceConfig;
+ if (directory === join("/app", "apps/client")) return clientConfig;
+ throw new ConfigNotFoundError(join(directory, "nanoforge.config.ts"));
+ });
+
+ const result = await parseWorkspaceConfig("/app");
+
+ expect(result).toEqual({
+ type: "workspace",
+ config: workspaceConfig,
+ projects: {
+ "apps/client": { type: "project", config: clientConfig },
+ },
+ });
+ });
+
+ it("warns instead of throwing when a pattern matches no project or lib", async () => {
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] };
+
+ vi.mocked(glob).mockReturnValue(asyncIterableOf([]) as ReturnType);
+ vi.mocked(loadConfig).mockResolvedValue(workspaceConfig);
+
+ const result = await parseWorkspaceConfig("/app");
+
+ expect(result).toEqual({ type: "workspace", config: workspaceConfig, projects: {} });
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("apps/*"));
+ });
+
+ it("throws when a matched package is itself a workspace", async () => {
+ const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] };
+
+ vi.mocked(glob).mockReturnValue(asyncIterableOf(["apps/nested"]) as ReturnType);
+ vi.mocked(loadConfig).mockImplementation(async (directory: string) => {
+ if (directory === "/app") return workspaceConfig;
+ return { type: "workspace" as const };
+ });
+
+ await expect(parseWorkspaceConfig("/app")).rejects.toThrow(
+ "a workspace package cannot itself be of type 'workspace'",
+ );
+ });
+});
diff --git a/src/lib/config/workspace-config-parser.ts b/src/lib/config/workspace-config-parser.ts
new file mode 100644
index 0000000..eaa62d5
--- /dev/null
+++ b/src/lib/config/workspace-config-parser.ts
@@ -0,0 +1,87 @@
+import type { NanoforgeConfig } from "@nanoforge-dev/config";
+import { existsSync, statSync } from "node:fs";
+import { glob } from "node:fs/promises";
+import { join } from "node:path";
+
+import { CLIError, ConfigNotFoundError } from "@utils/errors";
+
+import { loadConfig } from "./config-loader";
+import type {
+ BaseWorkspaceConfig,
+ LibConfig,
+ ProjectConfig,
+ WorkspaceConfig,
+} from "./workspace-config.types";
+
+const resolvePackageConfig = async (
+ packageDir: string,
+): Promise => {
+ let config: NanoforgeConfig;
+ try {
+ config = await loadConfig(packageDir);
+ } catch (error) {
+ if (error instanceof ConfigNotFoundError) return undefined;
+ throw error;
+ }
+
+ if (config.type === "workspace") {
+ throw new CLIError(
+ `Invalid config at '${packageDir}': a workspace package cannot itself be of type 'workspace'.`,
+ "Nest projects and libs under the root workspace instead of another workspace.",
+ );
+ }
+
+ return config.type === "lib" ? { type: "lib", config } : { type: "project", config };
+};
+
+const resolvePackagePattern = async (
+ directory: string,
+ pattern: string,
+): Promise> => {
+ const projects: Record = {};
+
+ for await (const relativePath of glob(pattern, { cwd: directory })) {
+ const packageDir = join(directory, relativePath);
+ if (!existsSync(packageDir) || !statSync(packageDir).isDirectory()) continue;
+
+ const entry = await resolvePackageConfig(packageDir);
+ if (entry) projects[relativePath] = entry;
+ }
+
+ if (Object.keys(projects).length === 0) {
+ console.warn(
+ `[nanoforge] No project or lib config found for workspace package pattern '${pattern}' in '${directory}'.`,
+ );
+ }
+
+ return projects;
+};
+
+const parseBaseWorkspaceConfig = async (
+ directory: string,
+ config: Extract,
+): Promise => {
+ const patterns = config.packages ?? [];
+
+ const projects: Record = {};
+ for (const pattern of patterns) {
+ Object.assign(projects, await resolvePackagePattern(directory, pattern));
+ }
+
+ return { type: "workspace", config, projects };
+};
+
+export const parseWorkspaceConfig = async (directory: string): Promise => {
+ const config = await loadConfig(directory);
+
+ if (config.type === "lib") {
+ throw new CLIError(
+ `Invalid config at '${directory}': the root config cannot be of type 'lib'.`,
+ "Use a 'workspace' or a 'client'/'server' project config at the root.",
+ );
+ }
+
+ if (config.type === "workspace") return parseBaseWorkspaceConfig(directory, config);
+
+ return { type: "project", config };
+};
diff --git a/src/lib/config/workspace-config.types.ts b/src/lib/config/workspace-config.types.ts
new file mode 100644
index 0000000..1fe329e
--- /dev/null
+++ b/src/lib/config/workspace-config.types.ts
@@ -0,0 +1,24 @@
+import type {
+ ClientConfig,
+ LibConfig as NfLibConfig,
+ WorkspaceConfig as NfWorkspaceConfig,
+ ServerConfig,
+} from "@nanoforge-dev/config";
+
+export interface ProjectConfig {
+ type: "project";
+ config: ClientConfig | ServerConfig;
+}
+
+export interface LibConfig {
+ type: "lib";
+ config: NfLibConfig;
+}
+
+export interface BaseWorkspaceConfig {
+ type: "workspace";
+ config: NfWorkspaceConfig;
+ projects: Record;
+}
+
+export type WorkspaceConfig = ProjectConfig | BaseWorkspaceConfig;
diff --git a/src/lib/config/workspace-loader.ts b/src/lib/config/workspace-loader.ts
new file mode 100644
index 0000000..df7d376
--- /dev/null
+++ b/src/lib/config/workspace-loader.ts
@@ -0,0 +1,28 @@
+import type { ClientConfig, ServerConfig } from "@nanoforge-dev/config";
+import { join } from "node:path";
+
+import type { WorkspaceConfig } from "./workspace-config.types";
+
+export interface ResolvedProject {
+ directory: string;
+ config: ClientConfig | ServerConfig;
+}
+
+export const resolveProjects = (
+ workspace: WorkspaceConfig,
+ baseDirectory: string,
+): ResolvedProject[] => {
+ if (workspace.type === "project") {
+ return [{ directory: baseDirectory, config: workspace.config }];
+ }
+
+ return Object.entries(workspace.projects)
+ .filter(
+ (entry): entry is [string, Extract<(typeof entry)[1], { type: "project" }>] =>
+ entry[1].type === "project",
+ )
+ .map(([relativePath, project]) => ({
+ directory: join(baseDirectory, relativePath),
+ config: project.config,
+ }));
+};
diff --git a/src/lib/input/inputs/dev/generate.input.spec.ts b/src/lib/input/inputs/dev/generate.input.spec.ts
deleted file mode 100644
index c49fc62..0000000
--- a/src/lib/input/inputs/dev/generate.input.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { describe, expect, it } from "vitest";
-
-import { type Input } from "../../input.type";
-import { getDevGenerateInput } from "./generate.input";
-
-const createInput = (entries: [string, any][]): Input => {
- return new Map(entries.map(([key, value]) => [key, { value }]));
-};
-
-describe("getDevGenerateInput", () => {
- it("should return the generate value when provided", () => {
- const input = createInput([["generate", true]]);
- expect(getDevGenerateInput(input)).toBe(true);
- });
-
- it("should return false as default when generate is missing", () => {
- const input = createInput([]);
- expect(getDevGenerateInput(input)).toBe(false);
- });
-});
diff --git a/src/lib/input/inputs/dev/generate.input.ts b/src/lib/input/inputs/dev/generate.input.ts
deleted file mode 100644
index be1647f..0000000
--- a/src/lib/input/inputs/dev/generate.input.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { getBooleanInputWithDefault } from "../../base-inputs";
-import { type Input } from "../../input.type";
-
-export const getDevGenerateInput = (inputs: Input): boolean => {
- return getBooleanInputWithDefault(inputs, "generate", false);
-};
diff --git a/src/lib/input/inputs/dev/index.ts b/src/lib/input/inputs/dev/index.ts
deleted file mode 100644
index c0d1098..0000000
--- a/src/lib/input/inputs/dev/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./generate.input";
diff --git a/src/lib/input/inputs/index.ts b/src/lib/input/inputs/index.ts
index 18b6fde..607306f 100644
--- a/src/lib/input/inputs/index.ts
+++ b/src/lib/input/inputs/index.ts
@@ -7,7 +7,6 @@ export * from "./server.input";
export * from "./watch.input";
export * from "./create";
-export * from "./dev";
export * from "./editor";
export * from "./install";
export * from "./login-out";
diff --git a/src/lib/input/inputs/new/index.ts b/src/lib/input/inputs/new/index.ts
index 9532ed4..af35a3a 100644
--- a/src/lib/input/inputs/new/index.ts
+++ b/src/lib/input/inputs/new/index.ts
@@ -1,5 +1,4 @@
export * from "./docker.input";
-export * from "./init-functions.input";
export * from "./language.input";
export * from "./lint.input";
export * from "./package-manager.input";
diff --git a/src/lib/input/inputs/new/init-functions.input.spec.ts b/src/lib/input/inputs/new/init-functions.input.spec.ts
deleted file mode 100644
index 15c9d53..0000000
--- a/src/lib/input/inputs/new/init-functions.input.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { describe, expect, it } from "vitest";
-
-import { type Input } from "../../input.type";
-import { getNewInitFunctionsWithDefault } from "./init-functions.input";
-
-const createInput = (entries: [string, any][]): Input => {
- return new Map(entries.map(([key, value]) => [key, { value }]));
-};
-
-describe("getNewInitFunctionsWithDefault", () => {
- it("should return the value when provided", () => {
- const input = createInput([["initFunctions", true]]);
- expect(getNewInitFunctionsWithDefault(input)).toBe(true);
- });
-
- it("should return false as default when missing", () => {
- const input = createInput([]);
- expect(getNewInitFunctionsWithDefault(input)).toBe(false);
- });
-});
diff --git a/src/lib/input/inputs/new/init-functions.input.ts b/src/lib/input/inputs/new/init-functions.input.ts
deleted file mode 100644
index 5c20e74..0000000
--- a/src/lib/input/inputs/new/init-functions.input.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { getBooleanInputWithDefault } from "../../base-inputs";
-import { type Input } from "../../input.type";
-
-export const getNewInitFunctionsWithDefault = (inputs: Input) => {
- return getBooleanInputWithDefault(inputs, "initFunctions", false);
-};
diff --git a/src/lib/schematics/nanoforge.collection.spec.ts b/src/lib/schematics/nanoforge.collection.spec.ts
index a3dadeb..545e30e 100644
--- a/src/lib/schematics/nanoforge.collection.spec.ts
+++ b/src/lib/schematics/nanoforge.collection.spec.ts
@@ -11,29 +11,29 @@ describe("NanoforgeCollection", () => {
it("should return all available schematics", () => {
const schematics = collection.getSchematics();
const names = schematics.map((s) => s.name);
- expect(names).toContain("application");
- expect(names).toContain("configuration");
- expect(names).toContain("part-base");
- expect(names).toContain("part-main");
+ expect(names).toContain("project");
+ expect(names).toContain("workspace");
+ expect(names).toContain("component");
+ expect(names).toContain("system");
});
it("should have aliases for each schematic", () => {
const schematics = collection.getSchematics();
const aliases = schematics.map((s) => s.alias);
- expect(aliases).toContain("application");
- expect(aliases).toContain("config");
- expect(aliases).toContain("base");
- expect(aliases).toContain("main");
+ expect(aliases).toContain("project");
+ expect(aliases).toContain("workspace");
+ expect(aliases).toContain("component");
+ expect(aliases).toContain("system");
});
});
describe("execute", () => {
it("should accept a valid schematic name", async () => {
- await expect(collection.execute("application", [])).resolves.not.toThrow();
+ await expect(collection.execute("project", [])).resolves.not.toThrow();
});
it("should accept a valid schematic alias", async () => {
- await expect(collection.execute("config", [])).resolves.not.toThrow();
+ await expect(collection.execute("workspace", [])).resolves.not.toThrow();
});
it("should reject an invalid schematic name", async () => {
diff --git a/src/lib/schematics/nanoforge.collection.ts b/src/lib/schematics/nanoforge.collection.ts
index 4989cec..16731ff 100644
--- a/src/lib/schematics/nanoforge.collection.ts
+++ b/src/lib/schematics/nanoforge.collection.ts
@@ -14,29 +14,14 @@ export interface Schematic {
export class NanoforgeCollection extends AbstractCollection {
private static schematics: Schematic[] = [
{
- name: "application",
- alias: "application",
- description: "Generate a new application",
+ name: "project",
+ alias: "project",
+ description: "Generate a new project",
},
{
- name: "configuration",
- alias: "config",
- description: "Generate a CLI configuration file",
- },
- {
- name: "part-base",
- alias: "base",
- description: "Generate a NanoForge Part Base",
- },
- {
- name: "part-main",
- alias: "main",
- description: "Generate a NanoForge Part Main file",
- },
- {
- name: "docker",
- alias: "docker",
- description: "Generate a Dockerfile for the application",
+ name: "workspace",
+ alias: "workspace",
+ description: "Generate a new workspace",
},
{
name: "component",
diff --git a/src/lib/ui/messages.ts b/src/lib/ui/messages.ts
index 8b5d426..a138117 100644
--- a/src/lib/ui/messages.ts
+++ b/src/lib/ui/messages.ts
@@ -57,12 +57,6 @@ export const Messages = {
CREATE_FAILED: failure("Creation failed!"),
CREATE_NAME_QUESTION: "What is the name of your component/system?",
- // --- Generate ---
- GENERATE_START: "NanoForge Generate",
- GENERATE_SUCCESS: success("Generation succeeded!"),
- GENERATE_FAILED: failure("Generation failed!"),
- GENERATE_WATCH_START: "Watching for changes...",
-
// --- Dev ---
DEV_START: "NanoForge Dev Mode",
DEV_SUCCESS: "Dev mode ended",
diff --git a/tsdown.config.ts b/tsdown.config.ts
index 4d95f0a..073c2e3 100644
--- a/tsdown.config.ts
+++ b/tsdown.config.ts
@@ -21,9 +21,9 @@ function createTsdownConfig({
platform: "node",
target: "esnext",
treeshake: false,
- deps: {
- skipNodeModulesBundle: true,
- },
+ // No `deps.neverBundle: true` here: it externalizes anything matching tsdown's
+ // npm-package-specifier heuristic, which our `@lib/*` and `@utils/*` path aliases
+ // match too, leaving them as unresolvable imports in the built output.
env: dotenv.config({
path: resolve(
process.cwd(),
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 0000000..42e96c0
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,65 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "remoteCache": {
+ "enabled": true
+ },
+ "globalDependencies": ["tsconfig.json"],
+ "tasks": {
+ "build": {
+ "dependsOn": ["^build"],
+ "inputs": ["src/**", "test/**", "package.json", "tsconfig.json", "tsdown.config.ts"],
+ "outputs": ["dist/**"],
+ "outputLogs": "errors-only"
+ },
+ "build:dev": {
+ "dependsOn": ["^build:dev"],
+ "inputs": ["src/**", "test/**", "package.json", "tsconfig.json", "tsdown.config.ts"],
+ "outputs": ["dist/**"],
+ "outputLogs": "errors-only"
+ },
+ "format": {
+ "dependsOn": ["^format"],
+ "inputs": ["**"],
+ "outputs": [],
+ "outputLogs": "errors-only"
+ },
+ "lint": {
+ "dependsOn": ["^lint"],
+ "inputs": ["**"],
+ "outputs": [],
+ "outputLogs": "errors-only"
+ },
+ "test:unit": {
+ "dependsOn": ["^test:unit"],
+ "inputs": [
+ "src/**",
+ "test/**",
+ "package.json",
+ "tsconfig.json",
+ "tsdown.config.ts",
+ "vitest.config.ts"
+ ],
+ "outputs": [],
+ "outputLogs": "errors-only"
+ },
+ "test:e2e": {
+ "dependsOn": ["build", "^test:e2e"],
+ "inputs": [
+ "src/**",
+ "e2e/**",
+ "package.json",
+ "tsconfig.json",
+ "tsdown.config.ts",
+ "vitest.config.e2e.ts"
+ ],
+ "outputs": [],
+ "outputLogs": "errors-only"
+ },
+ "docs": {
+ "dependsOn": ["^docs"],
+ "inputs": ["src/**", "test/**", "package.json", "tsconfig.json", "../../docs/templates/**"],
+ "outputs": ["../../docs/references/**"],
+ "outputLogs": "errors-only"
+ }
+ }
+}