From 3d924b3b8da294028f2f612f1ed4960847d31a86 Mon Sep 17 00:00:00 2001 From: Exelo Date: Wed, 2 Sep 2026 02:50:05 +0900 Subject: [PATCH 01/15] feat: add config lib and monorepo config files (#197) * feat: add config lib and monorepo config files * refactor: update tsdown config --- .gitignore | 4 +- .nvmrc | 2 +- .prettierignore | 2 + cliff.toml | 2 +- eslint.config.js | 7 +- libs/config/.cliff-jumperrc.json | 8 + libs/config/.gitignore | 234 ++ libs/config/.prettierignore | 11 + libs/config/CHANGELOG.md | 3 + libs/config/LICENSE | 21 + libs/config/README.md | 83 + libs/config/cliff.toml | 79 + libs/config/eslint.config.js | 3 + libs/config/package.json | 87 + libs/config/prettier.config.js | 3 + libs/config/src/defaults/default-configs.ts | 54 + libs/config/src/define-config.ts | 7 + libs/config/src/index.ts | 25 + libs/config/src/merge/deep-merge.ts | 35 + libs/config/src/merge/resolve-config.ts | 51 + libs/config/src/types/base-config.type.ts | 6 + libs/config/src/types/client-config.type.ts | 5 + libs/config/src/types/index.ts | 14 + libs/config/src/types/lib-config.type.ts | 15 + libs/config/src/types/mixins.type.ts | 25 + libs/config/src/types/server-config.type.ts | 5 + .../config/src/types/workspace-config.type.ts | 5 + libs/config/test/define-config.spec.ts | 20 + libs/config/test/resolve-config.spec.ts | 76 + libs/config/tsconfig.json | 15 + libs/config/tsconfig.spec.json | 10 + libs/config/tsdown.config.ts | 24 + package.json | 24 +- pnpm-lock.yaml | 2547 +++++++++-------- pnpm-workspace.yaml | 13 +- tsdown.config.ts | 2 +- turbo.json | 39 + 37 files changed, 2414 insertions(+), 1152 deletions(-) create mode 100644 libs/config/.cliff-jumperrc.json create mode 100644 libs/config/.gitignore create mode 100644 libs/config/.prettierignore create mode 100644 libs/config/CHANGELOG.md create mode 100644 libs/config/LICENSE create mode 100644 libs/config/README.md create mode 100644 libs/config/cliff.toml create mode 100644 libs/config/eslint.config.js create mode 100644 libs/config/package.json create mode 100644 libs/config/prettier.config.js create mode 100644 libs/config/src/defaults/default-configs.ts create mode 100644 libs/config/src/define-config.ts create mode 100644 libs/config/src/index.ts create mode 100644 libs/config/src/merge/deep-merge.ts create mode 100644 libs/config/src/merge/resolve-config.ts create mode 100644 libs/config/src/types/base-config.type.ts create mode 100644 libs/config/src/types/client-config.type.ts create mode 100644 libs/config/src/types/index.ts create mode 100644 libs/config/src/types/lib-config.type.ts create mode 100644 libs/config/src/types/mixins.type.ts create mode 100644 libs/config/src/types/server-config.type.ts create mode 100644 libs/config/src/types/workspace-config.type.ts create mode 100644 libs/config/test/define-config.spec.ts create mode 100644 libs/config/test/resolve-config.spec.ts create mode 100644 libs/config/tsconfig.json create mode 100644 libs/config/tsconfig.spec.json create mode 100644 libs/config/tsdown.config.ts create mode 100644 turbo.json 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/.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/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/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..fb92022 --- /dev/null +++ b/libs/config/README.md @@ -0,0 +1,83 @@ +
+
+

+ NanoForge +

+
+

+ npm version + npm downloads + Tests status + Last commit + Contributors +

+
+ +## 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. + +## 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..2965183 --- /dev/null +++ b/libs/config/package.json @@ -0,0 +1,87 @@ +{ + "$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": [ + "Bill ", + "Exelo ", + "Fexkoser ", + "Josephine ", + "Tchips " + ], + "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" + }, + "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", + "unrun": "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..26d216c --- /dev/null +++ b/libs/config/src/defaults/default-configs.ts @@ -0,0 +1,54 @@ +import type { ClientConfig, LibConfig, ServerConfig, WorkspaceConfig } from "../types"; + +export const defaultWorkspaceConfig: Omit = { + packages: [], +}; + +export const defaultLibConfig: Omit = { + dir: { + assets: "assets", + shared: "shared", + + components: "shared/components", + systems: "shared/systems", + scenes: "shared/scenes", + }, +}; + +export const defaultClientConfig: Omit = { + 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", + }, + + libs: [], +}; + +export const defaultServerConfig: Omit = { + 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", + }, + + 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..83547cd --- /dev/null +++ b/libs/config/src/index.ts @@ -0,0 +1,25 @@ +export { + defaultClientConfig, + defaultLibConfig, + defaultServerConfig, + defaultWorkspaceConfig, +} from "./defaults/default-configs"; +export { defineConfig } from "./define-config"; +export { + resolveClientConfig, + resolveConfig, + resolveLibConfig, + resolveServerConfig, + resolveWorkspaceConfig, +} from "./merge/resolve-config"; +export type { + BaseConfig, + BuildableConfig, + ClientConfig, + ContainLibConfig, + LibConfig, + NanoforgeConfig, + ServerConfig, + SourceableConfig, + 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..2d20d88 --- /dev/null +++ b/libs/config/src/merge/deep-merge.ts @@ -0,0 +1,35 @@ +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: Partial, +): 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..69ebf7b --- /dev/null +++ b/libs/config/src/merge/resolve-config.ts @@ -0,0 +1,51 @@ +import { + defaultClientConfig, + defaultLibConfig, + defaultServerConfig, + defaultWorkspaceConfig, +} from "../defaults/default-configs"; +import type { + ClientConfig, + LibConfig, + NanoforgeConfig, + ServerConfig, + WorkspaceConfig, +} from "../types"; +import { deepMerge } from "./deep-merge"; + +export const resolveWorkspaceConfig = (config: WorkspaceConfig): WorkspaceConfig => ({ + ...deepMerge(defaultWorkspaceConfig, config), + type: "workspace", +}); + +export const resolveLibConfig = (config: LibConfig): LibConfig => ({ + ...deepMerge(defaultLibConfig, config), + type: "lib", +}); + +export const resolveClientConfig = (config: ClientConfig): ClientConfig => ({ + ...deepMerge(defaultClientConfig, config), + type: "client", +}); + +export const resolveServerConfig = (config: ServerConfig): ServerConfig => ({ + ...deepMerge(defaultServerConfig, config), + type: "server", +}); + +/** + * Merges a `nanoforge.config.ts` config against the defaults matching its + * `type`. + */ +export const resolveConfig = (config: NanoforgeConfig): NanoforgeConfig => { + 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/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..bcacd00 --- /dev/null +++ b/libs/config/src/types/client-config.type.ts @@ -0,0 +1,5 @@ +import type { BaseConfig } from "./base-config.type"; +import type { BuildableConfig, ContainLibConfig, SourceableConfig } from "./mixins.type"; + +export interface ClientConfig + extends BaseConfig<"client">, SourceableConfig, BuildableConfig, ContainLibConfig {} diff --git a/libs/config/src/types/index.ts b/libs/config/src/types/index.ts new file mode 100644 index 0000000..fabef4c --- /dev/null +++ b/libs/config/src/types/index.ts @@ -0,0 +1,14 @@ +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, SourceableConfig } 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..9c2f32b --- /dev/null +++ b/libs/config/src/types/mixins.type.ts @@ -0,0 +1,25 @@ +export interface ContainLibConfig { + libs?: string[]; +} + +export interface BuildableConfig { + entryFile?: string; + out?: { + dir?: string; + mainFile?: 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..769e898 --- /dev/null +++ b/libs/config/src/types/server-config.type.ts @@ -0,0 +1,5 @@ +import type { BaseConfig } from "./base-config.type"; +import type { BuildableConfig, ContainLibConfig, SourceableConfig } from "./mixins.type"; + +export interface ServerConfig + extends BaseConfig<"server">, SourceableConfig, BuildableConfig, ContainLibConfig {} 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/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/package.json b/package.json index d653f47..ad4fafa 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "license": "MIT", "contributors": [ "Bill ", - "Exelo ", + "Exelo ", "Fexkoser ", + "Josephine ", "Tchips " ], "files": [ @@ -31,18 +32,18 @@ "type": "git", "url": "git+https://github.com/NanoForge-dev/CLI.git" }, - "funding": "", + "funding": "https://github.com/NanoForge-dev/CLI?sponsor", "scripts": { - "build": "tsc --noEmit && tsdown", - "build:dev": "tsc --noEmit && NODE_ENV=development tsdown", + "build": "turbo run build --concurrency=5 --filter='!@nanoforge-dev/cli' && tsc --noEmit && tsdown", + "build:dev": "turbo run build --concurrency=5 --filter='!@nanoforge-dev/cli' && tsc --noEmit && NODE_ENV=development tsdown", "start": "node dist/nf.js", - "lint": "prettier --check . && eslint --format=pretty src", - "format": "prettier --write . && eslint --fix --format=pretty src", + "lint": "prettier --check . && eslint --format=pretty src && turbo run lint --concurrency=5 --filter='!@nanoforge-dev/cli'", + "format": "prettier --write . && eslint --fix --format=pretty src && turbo run format --concurrency=5 --filter='!@nanoforge-dev/cli'", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r . --include-path '.'", "release": "cliff-jumper", "test": "pnpm run test:unit && pnpm run test:e2e", - "test:unit": "vitest run -c vitest.config.ts", + "test:unit": "vitest run -c vitest.config.ts && turbo run test:unit --concurrency=5 --filter='!@nanoforge-dev/cli'", "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", @@ -79,21 +80,22 @@ "@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..d810bb3 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: @@ -8,17 +109,23 @@ catalogs: build: tsdown: specifier: ^0.22.3 - version: 0.22.3 + version: 0.22.14 + turbo: + specifier: ^2.10.12 + version: 2.10.12 typescript: specifier: ^6.0.3 version: 6.0.3 + unrun: + specifier: ^0.3.0 + version: 0.3.1 ci: '@commitlint/cli': specifier: ^21.2.0 - version: 21.2.0 + version: 21.2.2 '@commitlint/config-conventional': specifier: ^21.2.0 - version: 21.2.0 + version: 21.2.2 '@favware/cliff-jumper': specifier: ^6.1.0 version: 6.1.0 @@ -30,11 +137,11 @@ catalogs: version: 9.1.7 lint-staged: specifier: ^17.0.8 - version: 17.0.8 + version: 17.4.1 cli: '@inquirer/prompts': specifier: ^8.5.2 - version: 8.5.2 + version: 8.7.0 '@types/inquirer': specifier: ^9.0.10 version: 9.0.10 @@ -56,10 +163,10 @@ catalogs: version: 0.1.4 '@types/node': specifier: ^26.1.0 - version: 26.1.0 + version: 26.4.0 bun: specifier: ^1.3.14 - version: 1.3.14 + version: 1.4.0 libs: chokidar: specifier: ^5.0.0 @@ -75,7 +182,7 @@ catalogs: version: 17.4.2 open: specifier: ^11.0.0 - version: 11.0.0 + version: 11.0.2 rc9: specifier: ^3.0.1 version: 3.0.1 @@ -94,34 +201,34 @@ catalogs: version: 6.0.2 eslint: specifier: ^10.6.0 - version: 10.6.0 + version: 10.9.1 prettier: specifier: ^3.9.4 - version: 3.9.4 + version: 3.9.6 loader: '@nanoforge-dev/loader-client': specifier: ^1.4.0 - version: 1.4.0 + version: 1.5.0 '@nanoforge-dev/loader-server': specifier: ^1.4.0 - version: 1.4.0 + version: 1.5.0 schematics: '@angular-devkit/schematics': specifier: ^22.0.5 - version: 22.0.5 + version: 22.1.6 '@angular-devkit/schematics-cli': specifier: ^22.0.5 - version: 22.0.5 + version: 22.1.6 '@nanoforge-dev/schematics': specifier: ^2.2.1 version: 2.2.1 - tests: + test: '@vitest/coverage-v8': specifier: ^4.1.10 - version: 4.1.10 + version: 4.1.11 vitest: specifier: ^4.1.10 - version: 4.1.10 + version: 4.1.11 importers: @@ -129,19 +236,19 @@ importers: dependencies: '@angular-devkit/schematics': specifier: catalog:schematics - version: 22.0.5(chokidar@5.0.0) + version: 22.1.6(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.6(@types/node@26.4.0)(chokidar@5.0.0) '@inquirer/prompts': specifier: catalog:cli - version: 8.5.2(@types/node@26.1.0) + version: 8.7.0(@types/node@26.4.0) '@nanoforge-dev/loader-client': specifier: catalog:loader - version: 1.4.0 + version: 1.5.0 '@nanoforge-dev/loader-server': specifier: catalog:loader - version: 1.4.0 + version: 1.5.0 '@nanoforge-dev/schematics': specifier: catalog:schematics version: 2.2.1(chokidar@5.0.0) @@ -150,7 +257,7 @@ importers: version: 4.3.1 bun: specifier: catalog:core - version: 1.3.14 + version: 1.4.0 chokidar: specifier: catalog:libs version: 5.0.0 @@ -171,7 +278,7 @@ importers: version: 2.2.0 open: specifier: catalog:libs - version: 11.0.0 + version: 11.0.2 ora: specifier: catalog:cli version: 9.4.1 @@ -184,10 +291,10 @@ importers: 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.2(@types/node@26.4.0)(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.2 '@favware/cliff-jumper': specifier: catalog:ci version: 6.1.0 @@ -196,48 +303,84 @@ 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(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.4.0 '@vitest/coverage-v8': - specifier: catalog:tests - version: 4.1.10(vitest@4.1.10) + specifier: catalog:test + version: 4.1.11(vitest@4.1.11) eslint: specifier: catalog:lint - version: 10.6.0(jiti@2.6.1) + version: 10.9.1(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.4.1 prettier: specifier: catalog:lint - version: 3.9.4 + version: 3.9.6 tsdown: specifier: catalog:build - version: 0.22.3(typescript@6.0.3) + version: 0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) + turbo: + specifier: catalog:build + version: 2.10.12 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)) + specifier: catalog:test + version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) optionalDependencies: '@nanoforge-dev/editor': specifier: catalog:core version: 0.1.4 + libs/config: + 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) + eslint: + specifier: catalog:lint + version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + prettier: + specifier: catalog:lint + version: 3.9.6 + tsdown: + specifier: catalog:build + version: 0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) + typescript: + specifier: catalog:build + version: 6.0.3 + unrun: + specifier: catalog:build + version: 0.3.1(synckit@0.11.13) + vitest: + specifier: catalog:test + version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + packages: '@actions/core@3.0.1': @@ -258,8 +401,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.6': + resolution: {integrity: sha512-KLBsZoc2RhOy0xSdeYcLoSOqV/fBP3feBmhY9o12ry2IuyW/17sCmWr6XbIfTIYQN6M98Y1ewmwVNFsvV5b0gA==} 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 +410,23 @@ packages: chokidar: optional: true - '@angular-devkit/schematics-cli@22.0.5': - resolution: {integrity: sha512-X305DwlHLyVE+RoM2C9KjKvShi2FiS4pkjYH6CvfydUmtyVxGj34kgtIPf8Kk+Jn7St7fRja5IUAcxczBgL3SQ==} + '@angular-devkit/schematics-cli@22.1.6': + resolution: {integrity: sha512-KO+vQiWgWW6i2H3XGu1E6AeZ6cyeehF1WTejATEZ3f02LROBkGyZGDAmdtSPtd3tmaGt3TVY25Mq1laybByKMA==} 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.6': + resolution: {integrity: sha512-IbDO9KbQyQm20uinsfT8d9ZtQw41/WVutI/65mAw39WZfN8Ky+MOgOUJCGLiSccMprvZ9YdodBTnN9bLH149Ag==} 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,55 +435,38 @@ 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.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} 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==} + '@commitlint/cli@21.2.2': + resolution: {integrity: sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==} engines: {node: '>=22.12.0'} hasBin: true - '@commitlint/config-conventional@21.2.0': - resolution: {integrity: sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==} + '@commitlint/config-conventional@21.2.2': + resolution: {integrity: sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==} engines: {node: '>=22.12.0'} '@commitlint/config-validator@21.2.0': @@ -359,40 +481,40 @@ packages: 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.2': + resolution: {integrity: sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==} engines: {node: '>=22.12.0'} - '@commitlint/is-ignored@21.2.0': - resolution: {integrity: sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==} + '@commitlint/is-ignored@21.2.2': + resolution: {integrity: sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==} engines: {node: '>=22.12.0'} - '@commitlint/lint@21.2.0': - resolution: {integrity: sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==} + '@commitlint/lint@21.2.2': + resolution: {integrity: sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==} engines: {node: '>=22.12.0'} - '@commitlint/load@21.2.0': - resolution: {integrity: sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==} + '@commitlint/load@21.2.2': + resolution: {integrity: sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==} 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.2': + resolution: {integrity: sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==} engines: {node: '>=22.12.0'} - '@commitlint/read@21.2.0': - resolution: {integrity: sha512-ELx8Ovh/JoAw5lpvDgxc6Y0We9skf2IPI2RFN+gnYgDGjRdMSF8zeodxhZmcclLWzfUIF7hXLOa8gOlllYcvBA==} + '@commitlint/read@21.2.1': + resolution: {integrity: sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==} engines: {node: '>=22.12.0'} - '@commitlint/resolve-extends@21.2.0': - resolution: {integrity: sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==} + '@commitlint/resolve-extends@21.2.2': + resolution: {integrity: sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==} engines: {node: '>=22.12.0'} - '@commitlint/rules@21.2.0': - resolution: {integrity: sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==} + '@commitlint/rules@21.2.2': + resolution: {integrity: sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==} engines: {node: '>=22.12.0'} '@commitlint/to-lines@21.0.1': @@ -419,8 +541,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 +566,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,16 +580,16 @@ 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': @@ -508,8 +633,8 @@ packages: resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} 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.3': + resolution: {integrity: sha512-XEYX2WA8SBkLPczL6/yXPHLPCvDoptmh9v56Cy05BSV1Smk1vWy19bTC4qJBuIffw7+6l4CcaYYzGqG60RfW1g==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -517,8 +642,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.1.1': - resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + '@inquirer/confirm@6.3.0': + resolution: {integrity: sha512-pZHXJImFtERmSNMBHcjwuz8Ck5vEFEYNUZnwbb8aJpjHv/TwGuFErNxF2Hp8+V+pNJs2EYPMlyWscvFEqO9jOQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -526,8 +651,8 @@ packages: '@types/node': optional: true - '@inquirer/core@11.2.1': - resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + '@inquirer/core@12.0.1': + resolution: {integrity: sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -535,8 +660,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.2.2': - resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} + '@inquirer/editor@5.3.1': + resolution: {integrity: sha512-y43COoyVUjPWIobn2Qep/uI1drPS78aaZZZ9kVi94Tyu/GuW2N8d8Q4rifJXGAXCEAXCPTTMjD8gC1HyvM5ukA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -544,8 +669,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.1.1': - resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} + '@inquirer/expand@5.1.3': + resolution: {integrity: sha512-3NQJiXNJ/aj9wiAsr7pECdp5Qe9J0X9YUJCKsaFXS+ddOxfL6J4AIl3w3T4Gq3kK0WsQY5GMoDokK5X94m6lHw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -553,8 +678,8 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@3.0.3': - resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} + '@inquirer/external-editor@3.0.4': + resolution: {integrity: sha512-tZbbaK2ovq6vlrRBNQvjrypmrED/p5x2ncIHQ79cD55tei3dD96v5glMMA+6tiq7K104i/25DVYKWVPJuV6ptA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -562,12 +687,12 @@ packages: '@types/node': optional: true - '@inquirer/figures@2.0.7': - resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + '@inquirer/figures@2.0.8': + resolution: {integrity: sha512-tApbon79GM9ry56ja/Ud3SY2CL4TQsao9fIwDQbgTeNY55025GdMzQ2+UdegV/lx51VNGUB59M0v0nMpybYY4Q==} 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.4': + resolution: {integrity: sha512-3xQkQrOvgOzpSN2ciTVdRDlg1FWMCA8l+0KfB6SNlILoTCGzJTzO/gc0Rwjcb3usuGyKdaGtI6OiyMdeMeLWkg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -575,8 +700,8 @@ packages: '@types/node': optional: true - '@inquirer/number@4.1.1': - resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} + '@inquirer/number@4.2.1': + resolution: {integrity: sha512-5KaqwZNLRpUuWcoCrYghPP9TMaXL5v2Sk4xqePM7RCVegcJStoXdWibio60YIC1bec+z1fCyb67N6XPJIkZtGA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -584,8 +709,8 @@ packages: '@types/node': optional: true - '@inquirer/password@5.1.1': - resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} + '@inquirer/password@5.2.0': + resolution: {integrity: sha512-CvVcW09emkBESEOW+4R8CjLNkP3fB3XrjeL8CDvfpjgrJN+V9oerXmJAXXM3l+4xqYPD5Yaujzy/Ph0PLOdDuA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -593,17 +718,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.0': + resolution: {integrity: sha512-yQwBMYvpJ6jqrXtKiOwRD5XezjJoyt3VQvIyjsr5Arqb519nfIohOQymWVJ8/vEgg8xtZerrCsqlSaqt/LPC9A==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -611,8 +736,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.3.1': - resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} + '@inquirer/rawlist@5.3.3': + resolution: {integrity: sha512-Mu7WrtmDLaXBDEyrRLS70SZgX9ZSm4Up1w0ZxiH8C1OOp9oaVCn2k8q3QGgmlnhsKYUhuaU3zFWhAP6wxkVIMA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -620,8 +745,8 @@ packages: '@types/node': optional: true - '@inquirer/search@4.2.1': - resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} + '@inquirer/search@4.3.1': + resolution: {integrity: sha512-0VWOvsHWI0rPj6CG70MoP4oXNCB6adcyN8bVFZXnh11eLDdPIK2f2XCmva78acPPDfJisfab7qNakwBh7hdBXw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -629,8 +754,8 @@ packages: '@types/node': optional: true - '@inquirer/select@5.2.1': - resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} + '@inquirer/select@5.2.3': + resolution: {integrity: sha512-KuRTodDa6xBXX2noIpjuitpX/QT7Sfav7dIZ/OfUY54Hxg95nrGoshSzxx6Ey7qqLbImKdiGkSDt7KjPXjQgmA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -638,8 +763,8 @@ packages: '@types/node': optional: true - '@inquirer/type@4.0.7': - resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + '@inquirer/type@4.1.0': + resolution: {integrity: sha512-FMiJpuHUG3Dk0ex+UIXkre7i+i4OcwHWk9YdcVtZHFwb/r2rnrU2ipTCNAB7A+QOP0ryzIcqOfy76fRyyvOEAw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -658,8 +783,8 @@ 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==} @@ -672,16 +797,16 @@ 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==} + '@nanoforge-dev/loader-client@1.5.0': + resolution: {integrity: sha512-FUcrfXlcaG4dsjya7fUUFkr5zwKB0nCihYeEOC1K54gl4XV6S1lgNnAfdkl4jZfzyyw4jcZUB+73t4ymBhz08g==} engines: {node: '25'} - '@nanoforge-dev/loader-server@1.4.0': - resolution: {integrity: sha512-jvNoHxx54BAfjUTMZHb/T/8ApbdPV/NNGwiQITUBprH4Uv8jQmbR7zNB5Z6/vIEjSVVxrU0b4fFa+BkdnKX/iQ==} + '@nanoforge-dev/loader-server@1.5.0': + resolution: {integrity: sha512-1rly9IgunGz8Ux6lGDugUXgmNbnNjWGM8ABy7EsdrKWV74gyj7cuU6X69UolO28XUPCdbeISNcBEZV5hLK8Rlg==} engines: {node: '25'} - '@nanoforge-dev/loader-website@1.4.0': - resolution: {integrity: sha512-+5el/zkSJQL9qCd+4a6fGpIDMQi26QRTWXJ24mJFMaDC74P1lvqnes0u0l0+/s9dunJxfgea0Epse0/Jj/3M3Q==} + '@nanoforge-dev/loader-website@1.5.0': + resolution: {integrity: sha512-X37wlYyoL/q8ZPk5I8W3z01novfDSopizVQXbk/sw0dRXoONtIb6xnZLlN3z4jkDkPOy6IVes0bLGr8h4aFOcA==} engines: {node: '25'} '@nanoforge-dev/schematics@2.2.1': @@ -696,31 +821,28 @@ 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.7': + resolution: {integrity: sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + '@octokit/endpoint@11.0.4': + resolution: {integrity: sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==} engines: {node: '>= 20'} - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + '@octokit/graphql@9.0.4': + resolution: {integrity: sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==} 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/plugin-paginate-rest@14.0.0': resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} engines: {node: '>= 20'} @@ -733,105 +855,92 @@ 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.1': + resolution: {integrity: sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==} engines: {node: '>= 20'} - '@octokit/request@10.0.11': - resolution: {integrity: sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==} + '@octokit/request@10.0.15': + resolution: {integrity: sha512-3CBg9aJ0hO9Pjyij8LbK/xYtEaPws9SW7xKz67daPNxQB1q5Y9OMA7DDOG0A6Hwf9ygGu3tvzusg0LXQ8/wAjA==} 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] + '@oven/bun-darwin-aarch64@1.4.0': + resolution: {integrity: sha512-GCpf8QuFLsyioVawP5HrMxA1ZRBlu6Hq9RNnSc3UTUWAzIxBso9trjoZczw1HdgpqSssFkszfIV2zmOzFTjhkw==} + cpu: [arm64] os: [darwin] - '@oven/bun-darwin-x64@1.3.14': - resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==} + '@oven/bun-darwin-x64@1.4.0': + resolution: {integrity: sha512-cIrhwOr0SPEraewznhC+c/k6TG8bwFn5uZ4EJuXwjiKJLcAF36q7/bGjWkeXSe48JwMcPRUR054JXF7+cRwSSA==} cpu: [x64] os: [darwin] - '@oven/bun-freebsd-aarch64@1.3.14': - resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==} + '@oven/bun-freebsd-aarch64@1.4.0': + resolution: {integrity: sha512-09x7wnjMR6M5KGBDBhVl2CpfoCIQOkVDbPX2KfIhpXv4N6grbWE7dfLPw/Ydi9gaUMGhU7UKhoz444Nu6RCycA==} cpu: [arm64] os: [freebsd] - '@oven/bun-freebsd-x64@1.3.14': - resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==} + '@oven/bun-freebsd-x64@1.4.0': + resolution: {integrity: sha512-dRwzti/qJqV1HWplU27iUWUqp+f2DtFSf2yqQKSb+HH2dDOC//Uqd9u/A5h1DMsLszfP5OGP9UwQIKxVwFODaA==} cpu: [x64] os: [freebsd] - '@oven/bun-linux-aarch64-android@1.3.14': - resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==} + '@oven/bun-linux-aarch64-android@1.4.0': + resolution: {integrity: sha512-HpPIxJfDNPBPhiBNMyZoo/dOLijARfsx5j72vNuLtaTvl0Hh7HUculxjsOQ2WSyGoCgqXMEr1Qqjab1im9u1RA==} cpu: [arm64] os: [android] - '@oven/bun-linux-aarch64-musl@1.3.14': - resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==} + '@oven/bun-linux-aarch64-musl@1.4.0': + resolution: {integrity: sha512-RUjAAkJ/CdNV++zVxyANWshPc73CECYsfhk0fWAkoJjtywxJ2BwXzI6nopBBDMfs0HS+fhRGn6zGwU8ccxLeJg==} 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.0': + resolution: {integrity: sha512-Y5yAtCbHK6JjprXEtkdklDQFPADgs+CkfcliyY5g4JJ8baGHyQSrfpSkX3XVJ2C+aBLsdwNDdW+oczMsAwx6uA==} 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.0': + resolution: {integrity: sha512-u++KyLlfMn36yWz+AgJs+fZtS46UFDNpSSZhrcitkytONtNwq0X6Q9BDVEFXxYl/+Eec0xme1rb6MgW+U35WeA==} 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.0': + resolution: {integrity: sha512-C1Dv+ISL8YKEKM9jAHzNifOcRUoziy6UMxh+yVXjUCP6QnbRhENDHLaIWWkQZJyBLTn0I3xozflorAlHiGzGqA==} cpu: [x64] os: [linux] + libc: [musl] - '@oven/bun-linux-x64@1.3.14': - resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==} + '@oven/bun-linux-x64@1.4.0': + resolution: {integrity: sha512-Du44zebtPXJujvMLmtIxEQ6ykOhYt7L/Q+YIGVm+Yy+Pj/fpOnq60ggwIpKp/pGAFbYHNiTrA3JTjuZ9MTbZIg==} 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.0': + resolution: {integrity: sha512-FBAYaQpJBP0asgqzL6NFUfjdQqsV+kvTpJ/eWxPKj+RcDgIfPSuE8kvQuPYu5pa8u8JTujYMjmuyvHxVuQsInA==} cpu: [arm64] os: [win32] - '@oven/bun-windows-x64-baseline@1.3.14': - resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==} - cpu: [x64] - os: [win32] - - '@oven/bun-windows-x64@1.3.14': - resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==} + '@oven/bun-windows-x64@1.4.0': + resolution: {integrity: sha512-jRKv1NPLznMSZY5BEWciMF7zv0Tiyo2pQSxAJ3w+YWJ6y3VWNJQQQdLlV5Jx8lbOFDrJdrc9dD3GV17k3BP41A==} cpu: [x64] os: [win32] - '@oxc-project/types@0.138.0': - resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} + '@oxc-project/types@0.147.0': + resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} @@ -962,97 +1071,98 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm64@1.1.4': - resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==} + '@rolldown/binding-android-arm-eabi@1.2.6': + resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.6': + resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} 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.6': + resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} 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.6': + resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} 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.6': + resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} 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.6': + resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} 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.6': + resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} 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.6': + resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} 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.6': + resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} 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.6': + resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} 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.6': + resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} 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.6': + resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} 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.6': + resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} 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.6': + resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} 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.6': + resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1075,6 +1185,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'} @@ -1113,8 +1227,35 @@ packages: svelte: optional: true - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@turbo/darwin-64@2.10.12': + resolution: {integrity: sha512-9nKgKoF6ZOUsM+or0OtNf+TTJSfGvDNP7ZFv/ZGWVwOSCkumyctQiTeHwB4UNljHTnC41AqylgbunLDHoccNrA==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.10.12': + resolution: {integrity: sha512-H4Elb1jqTZVeIC9bbcNwjSzemZ6RegoTOVHeuV5Osirt2Z8UguTyisMEkvZjPVZgMeN9J4ERZBFad40tFnkb7w==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.10.12': + resolution: {integrity: sha512-lr7KIotukvjZwEXiFSYAeOH3BWzjFVBbSzTbv0fuGFsNukYyH0+g1hB5ecqnJkgkYU+KHEMG1edOhnjiKON1wQ==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.10.12': + resolution: {integrity: sha512-f0pZDTtvzB5SuNwuXBaKbZHUCMCukgc8nMlHEuvLmj91Fzec+MEbr3cAvGNor5htEDqZnO6Lxt9N/GPI/77oGA==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.10.12': + resolution: {integrity: sha512-SDOueJRjS/QcykWf2KCRtTLmIl5YMKsLbXkXQGhDwcTXvKXZiS5ih5lBl/gkwZIpYFjqA/rAlfMzlAFcVHNe0g==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.10.12': + resolution: {integrity: sha512-0i0mVUa4kKk+/B3RwEwPMf9CB+T7ul56hn5FFHNA4VUNTOoLBEd6aNf3FaKfCatDNZ6cicCEf6if9QUTVyzzcA==} + cpu: [arm64] + os: [win32] '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1134,14 +1275,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.4.0': + resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} @@ -1149,79 +1287,79 @@ 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.68.0': + resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.62.1 + '@typescript-eslint/parser': ^8.68.0 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.68.0': + resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} 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.68.0': + resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} 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.68.0': + resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} 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.68.0': + resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} 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.68.0': + resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} 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.68.0': + resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} 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.68.0': + resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} 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.68.0': + resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} 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.68.0': + resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} 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@4.1.11': + resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} peerDependencies: - '@vitest/browser': 4.1.10 - vitest: 4.1.10 + '@vitest/browser': 4.1.11 + vitest: 4.1.11 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1231,28 +1369,163 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + '@yuku-codegen/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==} + cpu: [arm64] + os: [linux] + libc: [musl] - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==} + 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.8.7': + resolution: {integrity: sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==} + 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.8.7': + resolution: {integrity: sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} + cpu: [x64] + os: [win32] - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@yuku-toolchain/types@0.8.7': + resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} 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,8 +1547,8 @@ 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: @@ -1289,16 +1562,16 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argue-cli@3.1.0: + resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + 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.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1310,25 +1583,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.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + 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.0: + resolution: {integrity: sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw==} cpu: [arm64, x64] os: [darwin, linux, android, freebsd, win32] hasBin: true @@ -1382,10 +1652,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 +1671,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.0.0: + resolution: {integrity: sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw==} + 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 +1696,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 @@ -1480,8 +1746,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: @@ -1532,11 +1798,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 +1864,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.9.1: + resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1631,9 +1897,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 +1923,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.6: + resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1695,8 +1958,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - 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==} @@ -1754,12 +2017,6 @@ packages: 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'} @@ -1803,8 +2060,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -1839,10 +2096,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'} @@ -1908,8 +2161,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: @@ -1932,8 +2185,8 @@ 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==} @@ -1945,95 +2198,91 @@ packages: 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.12: + resolution: {integrity: sha512-uLVeV1c9OTk6qkdqnj+mpMD+ZdnZ0szVyWu58HwMmpwkHA1gCEkyjd3veZQXDnuw9KEwSRjcc9B1pS9XKIN1fA==} - 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.4.1: + resolution: {integrity: sha512-FmJeudcalbSfg1du+JCfvi5vS6Qt08KgbfLWiHinbef+2JJwUZwAWVoaO1AcJVUTWPfk0t30PMQNwPAeCzYQ+Q==} 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,15 +2294,14 @@ 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'} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.3: - resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + magic-string@1.0.0: + resolution: {integrity: sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==} + + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -2063,10 +2311,6 @@ packages: 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,8 +2319,8 @@ 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: @@ -2098,8 +2342,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.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2114,29 +2358,25 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + 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.2: + resolution: {integrity: sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==} 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'} @@ -2194,26 +2434,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.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} 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,13 +2466,13 @@ 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.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} 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: @@ -2241,8 +2485,8 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} - 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 +2511,27 @@ 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==} + rolldown-plugin-dts@0.27.14: + resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: - '@ts-macro/tsc': ^0.3.6 - '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 rolldown: ^1.0.0 - typescript: ^5.0.0 || ^6.0.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 typescript: optional: true vue-tsc: optional: true - rolldown@1.1.4: - resolution: {integrity: sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==} + rolldown@1.2.6: + resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2328,16 +2569,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.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} source-map-js@1.2.1: @@ -2351,8 +2584,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 +2599,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: @@ -2394,15 +2627,15 @@ 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==} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -2413,8 +2646,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,18 +2664,18 @@ packages: peerDependencies: typescript: '>=4.8.4' - tsdown@0.22.3: - resolution: {integrity: sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==} + tsdown@0.22.14: + resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.3 - '@tsdown/exe': 0.22.3 + '@tsdown/css': 0.22.14 + '@tsdown/exe': 0.22.14 '@vitejs/devtools': '*' publint: ^0.3.8 tsx: '*' - typescript: ^5.0.0 || ^6.0.0 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 unplugin-unused: ^0.5.0 unrun: '*' peerDependenciesMeta: @@ -2472,12 +2705,16 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + turbo@2.10.12: + resolution: {integrity: sha512-AswgMPnpOoaVZHrrSBejETzEbuIA69OVGwfkHwfrY0A23VjWXBANzgq9+OymWOHAIArB7D1+1z498WY8fGg1Jw==} + 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.68.0: + resolution: {integrity: sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2494,8 +2731,8 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@6.27.0: - resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} + undici@6.28.0: + resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} engines: {node: '>=18.17'} unicode-emoji-modifier-base@1.0.0: @@ -2509,6 +2746,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 +2763,17 @@ packages: resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} - vite@8.1.2: - resolution: {integrity: sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ==} + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} 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.4.0 || ^0.5.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -2559,20 +2810,20 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.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 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2614,16 +2865,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: @@ -2643,18 +2890,27 @@ 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.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} 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.8.7: + resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} + + yuku-codegen@0.8.7: + resolution: {integrity: sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==} + + yuku-parser@0.8.7: + resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} + snapshots: '@actions/core@3.0.1': @@ -2669,51 +2925,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.7 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.7) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.7) + '@octokit/request': 10.0.15 + '@octokit/request-error': 7.1.1 + undici: 6.28.0 '@actions/http-client@3.0.2': dependencies: tunnel: 0.0.6 - undici: 6.27.0 + undici: 6.28.0 '@actions/http-client@4.0.1': dependencies: tunnel: 0.0.6 - undici: 6.27.0 + undici: 6.28.0 '@actions/io@3.0.2': {} - '@angular-devkit/core@22.0.5(chokidar@5.0.0)': + '@angular-devkit/core@22.1.6(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.6(@types/node@26.4.0)(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.6(chokidar@5.0.0) + '@angular-devkit/schematics': 22.1.6(chokidar@5.0.0) + '@inquirer/prompts': 8.5.2(@types/node@26.4.0) transitivePeerDependencies: - '@types/node' - chokidar - '@angular-devkit/schematics@22.0.5(chokidar@5.0.0)': + '@angular-devkit/schematics@22.1.6(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 22.0.5(chokidar@5.0.0) + '@angular-devkit/core': 22.1.6(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,91 +2980,69 @@ 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.8 + '@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': - dependencies: - '@babel/types': 7.29.7 - - '@babel/parser@8.0.0': + '@babel/parser@7.29.8': 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.8 + '@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.8 '@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': - dependencies: - '@babel/helper-string-parser': 8.0.0 - '@babel/helper-validator-identifier': 8.0.2 - '@bcoe/v8-coverage@1.0.2': {} - '@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.2(@types/node@26.4.0)(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/config-conventional': 21.2.2 + '@commitlint/format': 21.2.2 + '@commitlint/lint': 21.2.2 + '@commitlint/load': 21.2.2(@types/node@26.4.0)(typescript@6.0.3) + '@commitlint/read': 21.2.1(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 + tinyexec: 1.3.0 + yargs: 18.1.0 transitivePeerDependencies: - '@types/node' - conventional-commits-filter - conventional-commits-parser - typescript - '@commitlint/config-conventional@21.2.0': + '@commitlint/config-conventional@21.2.2': dependencies: '@commitlint/types': 21.2.0 - conventional-changelog-conventionalcommits: 10.2.1 + conventional-changelog-conventionalcommits: 10.4.0 '@commitlint/config-validator@21.2.0': dependencies: @@ -2818,36 +3052,36 @@ snapshots: '@commitlint/ensure@21.2.0': dependencies: '@commitlint/types': 21.2.0 - es-toolkit: 1.49.0 + es-toolkit: 1.52.0 '@commitlint/execute-rule@21.0.1': {} - '@commitlint/format@21.2.0': + '@commitlint/format@21.2.2': dependencies: '@commitlint/types': 21.2.0 picocolors: 1.1.1 - '@commitlint/is-ignored@21.2.0': + '@commitlint/is-ignored@21.2.2': dependencies: '@commitlint/types': 21.2.0 semver: 7.8.5 - '@commitlint/lint@21.2.0': + '@commitlint/lint@21.2.2': dependencies: - '@commitlint/is-ignored': 21.2.0 - '@commitlint/parse': 21.2.0 - '@commitlint/rules': 21.2.0 + '@commitlint/is-ignored': 21.2.2 + '@commitlint/parse': 21.2.2 + '@commitlint/rules': 21.2.2 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.0(@types/node@26.1.0)(typescript@6.0.3)': + '@commitlint/load@21.2.2(@types/node@26.4.0)(typescript@6.0.3)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 - '@commitlint/resolve-extends': 21.2.0 + '@commitlint/resolve-extends': 21.2.2 '@commitlint/types': 21.2.0 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.4.0)(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,31 +3090,31 @@ snapshots: '@commitlint/message@21.2.0': {} - '@commitlint/parse@21.2.0': + '@commitlint/parse@21.2.2': dependencies: '@commitlint/types': 21.2.0 - conventional-changelog-angular: 9.2.1 - conventional-commits-parser: 7.0.1 + conventional-changelog-angular: 9.4.0 + conventional-commits-parser: 7.1.2 - '@commitlint/read@21.2.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)': + '@commitlint/read@21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)': 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 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) + tinyexec: 1.3.0 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser - '@commitlint/resolve-extends@21.2.0': + '@commitlint/resolve-extends@21.2.2': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/types': 21.2.0 - es-toolkit: 1.49.0 + es-toolkit: 1.52.0 global-directory: 5.0.0 resolve-from: 5.0.0 - '@commitlint/rules@21.2.0': + '@commitlint/rules@21.2.2': dependencies: '@commitlint/ensure': 21.2.0 '@commitlint/message': 21.2.0 @@ -2895,7 +3129,7 @@ snapshots: '@commitlint/types@21.2.0': 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)': @@ -2907,7 +3141,16 @@ snapshots: 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@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.4.0': {} '@dprint/formatter@0.5.1': {} @@ -2915,38 +3158,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': + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))': 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))': - dependencies: - eslint: 10.6.0(jiti@2.6.1) + eslint: 10.9.1(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,7 +3181,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@9.39.4': {} + '@eslint/js@9.39.5': {} '@eslint/object-schema@3.0.5': {} @@ -2967,8 +3194,8 @@ 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.7 + '@octokit/plugin-retry': 8.1.1(@octokit/core@7.0.7) '@sapphire/result': 2.8.0 '@sapphire/utilities': 3.18.2 colorette: 2.0.20 @@ -2976,9 +3203,9 @@ snapshots: conventional-recommended-bump: 11.2.0 execa: 9.6.1 git-cliff: 2.13.1 - js-yaml: 4.3.0 + js-yaml: 4.3.2 semver: 7.8.5 - smol-toml: 1.7.0 + smol-toml: 1.8.0 '@favware/colorette-spinner@1.0.1': dependencies: @@ -3002,137 +3229,137 @@ snapshots: '@inquirer/ansi@2.0.7': {} - '@inquirer/checkbox@5.2.1(@types/node@26.1.0)': + '@inquirer/checkbox@5.2.3(@types/node@26.4.0)': 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/core': 12.0.1(@types/node@26.4.0) + '@inquirer/figures': 2.0.8 + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/confirm@6.1.1(@types/node@26.1.0)': + '@inquirer/confirm@6.3.0(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/core@11.2.1(@types/node@26.1.0)': + '@inquirer/core@12.0.1(@types/node@26.4.0)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.0) + '@inquirer/figures': 2.0.8 + '@inquirer/type': 4.1.0(@types/node@26.4.0) 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.4.0 - '@inquirer/editor@5.2.2(@types/node@26.1.0)': + '@inquirer/editor@5.3.1(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/external-editor': 3.0.4(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/expand@5.1.1(@types/node@26.1.0)': + '@inquirer/expand@5.1.3(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/external-editor@3.0.3(@types/node@26.1.0)': + '@inquirer/external-editor@3.0.4(@types/node@26.4.0)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/figures@2.0.7': {} + '@inquirer/figures@2.0.8': {} - '@inquirer/input@5.1.2(@types/node@26.1.0)': + '@inquirer/input@5.1.4(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/number@4.1.1(@types/node@26.1.0)': + '@inquirer/number@4.2.1(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/password@5.1.1(@types/node@26.1.0)': + '@inquirer/password@5.2.0(@types/node@26.4.0)': 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/core': 12.0.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) 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.4.0 + + '@inquirer/prompts@8.5.2(@types/node@26.4.0)': + dependencies: + '@inquirer/checkbox': 5.2.3(@types/node@26.4.0) + '@inquirer/confirm': 6.3.0(@types/node@26.4.0) + '@inquirer/editor': 5.3.1(@types/node@26.4.0) + '@inquirer/expand': 5.1.3(@types/node@26.4.0) + '@inquirer/input': 5.1.4(@types/node@26.4.0) + '@inquirer/number': 4.2.1(@types/node@26.4.0) + '@inquirer/password': 5.2.0(@types/node@26.4.0) + '@inquirer/rawlist': 5.3.3(@types/node@26.4.0) + '@inquirer/search': 4.3.1(@types/node@26.4.0) + '@inquirer/select': 5.2.3(@types/node@26.4.0) 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.4.0 + + '@inquirer/prompts@8.7.0(@types/node@26.4.0)': + dependencies: + '@inquirer/checkbox': 5.2.3(@types/node@26.4.0) + '@inquirer/confirm': 6.3.0(@types/node@26.4.0) + '@inquirer/editor': 5.3.1(@types/node@26.4.0) + '@inquirer/expand': 5.1.3(@types/node@26.4.0) + '@inquirer/input': 5.1.4(@types/node@26.4.0) + '@inquirer/number': 4.2.1(@types/node@26.4.0) + '@inquirer/password': 5.2.0(@types/node@26.4.0) + '@inquirer/rawlist': 5.3.3(@types/node@26.4.0) + '@inquirer/search': 4.3.1(@types/node@26.4.0) + '@inquirer/select': 5.2.3(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/rawlist@5.3.1(@types/node@26.1.0)': + '@inquirer/rawlist@5.3.3(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/search@4.2.1(@types/node@26.1.0)': + '@inquirer/search@4.3.1(@types/node@26.4.0)': 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.1(@types/node@26.4.0) + '@inquirer/figures': 2.0.8 + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/select@5.2.1(@types/node@26.1.0)': + '@inquirer/select@5.2.3(@types/node@26.4.0)': 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/core': 12.0.1(@types/node@26.4.0) + '@inquirer/figures': 2.0.8 + '@inquirer/type': 4.1.0(@types/node@26.4.0) optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 - '@inquirer/type@4.0.7(@types/node@26.1.0)': + '@inquirer/type@4.1.0(@types/node@26.4.0)': optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -3141,17 +3368,17 @@ snapshots: '@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 '@nanoforge-dev/actions@2.1.5': dependencies: @@ -3162,45 +3389,45 @@ snapshots: '@nanoforge-dev/editor@0.1.4': dependencies: - bun: 1.3.14 + bun: 1.4.0 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.0': dependencies: - '@nanoforge-dev/loader-website': 1.4.0 - bun: 1.3.14 + '@nanoforge-dev/loader-website': 1.5.0 + bun: 1.4.0 commander: 15.0.0 - '@nanoforge-dev/loader-server@1.4.0': + '@nanoforge-dev/loader-server@1.5.0': dependencies: commander: 15.0.0 - '@nanoforge-dev/loader-website@1.4.0': {} + '@nanoforge-dev/loader-website@1.5.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) + '@angular-devkit/core': 22.1.6(chokidar@5.0.0) + '@angular-devkit/schematics': 22.1.6(chokidar@5.0.0) rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.9.1(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.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6) globals: 16.5.0 - typescript-eslint: 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@6.0.3) + typescript-eslint: 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) transitivePeerDependencies: - '@types/eslint' - eslint @@ -3210,121 +3437,108 @@ 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.7': 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.4 + '@octokit/request': 10.0.15 + '@octokit/request-error': 7.1.1 + '@octokit/types': 17.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.3': + '@octokit/endpoint@11.0.4': dependencies: - '@octokit/types': 16.0.0 + '@octokit/types': 17.0.0 universal-user-agent: 7.0.3 - '@octokit/graphql@9.0.3': + '@octokit/graphql@9.0.4': dependencies: - '@octokit/request': 10.0.11 - '@octokit/types': 16.0.0 + '@octokit/request': 10.0.15 + '@octokit/types': 17.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/plugin-paginate-rest@14.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@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.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@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.7)': dependencies: - '@octokit/core': 7.0.6 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 + '@octokit/core': 7.0.7 + '@octokit/request-error': 7.1.1 + '@octokit/types': 17.0.0 bottleneck: 2.19.5 - '@octokit/request-error@7.1.0': + '@octokit/request-error@7.1.1': dependencies: - '@octokit/types': 16.0.0 + '@octokit/types': 17.0.0 - '@octokit/request@10.0.11': + '@octokit/request@10.0.15': 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.4 + '@octokit/request-error': 7.1.1 + '@octokit/types': 17.0.0 + content-type: 3.0.0 + 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 - - '@oven/bun-freebsd-aarch64@1.3.14': - optional: true + '@octokit/types@17.0.0': + dependencies: + '@octokit/openapi-types': 28.0.0 - '@oven/bun-freebsd-x64@1.3.14': + '@oven/bun-darwin-aarch64@1.4.0': optional: true - '@oven/bun-linux-aarch64-android@1.3.14': + '@oven/bun-darwin-x64@1.4.0': optional: true - '@oven/bun-linux-aarch64-musl@1.3.14': + '@oven/bun-freebsd-aarch64@1.4.0': optional: true - '@oven/bun-linux-aarch64@1.3.14': + '@oven/bun-freebsd-x64@1.4.0': optional: true - '@oven/bun-linux-x64-android@1.3.14': + '@oven/bun-linux-aarch64-android@1.4.0': optional: true - '@oven/bun-linux-x64-baseline@1.3.14': + '@oven/bun-linux-aarch64-musl@1.4.0': optional: true - '@oven/bun-linux-x64-musl-baseline@1.3.14': + '@oven/bun-linux-aarch64@1.4.0': optional: true - '@oven/bun-linux-x64-musl@1.3.14': + '@oven/bun-linux-x64-android@1.4.0': optional: true - '@oven/bun-linux-x64@1.3.14': + '@oven/bun-linux-x64-musl@1.4.0': optional: true - '@oven/bun-windows-aarch64@1.3.14': + '@oven/bun-linux-x64@1.4.0': optional: true - '@oven/bun-windows-x64-baseline@1.3.14': + '@oven/bun-windows-aarch64@1.4.0': optional: true - '@oven/bun-windows-x64@1.3.14': + '@oven/bun-windows-x64@1.4.0': optional: true - '@oxc-project/types@0.138.0': {} + '@oxc-project/types@0.147.0': {} '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -3389,53 +3603,49 @@ snapshots: dependencies: quansync: 1.0.0 - '@rolldown/binding-android-arm64@1.1.4': + '@rolldown/binding-android-arm-eabi@1.2.6': optional: true - '@rolldown/binding-darwin-arm64@1.1.4': + '@rolldown/binding-android-arm64@1.2.6': optional: true - '@rolldown/binding-darwin-x64@1.1.4': + '@rolldown/binding-darwin-arm64@1.2.6': optional: true - '@rolldown/binding-freebsd-x64@1.1.4': + '@rolldown/binding-darwin-x64@1.2.6': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.4': + '@rolldown/binding-freebsd-x64@1.2.6': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.4': + '@rolldown/binding-linux-arm-gnueabihf@1.2.6': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.4': + '@rolldown/binding-linux-arm64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.4': + '@rolldown/binding-linux-arm64-musl@1.2.6': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.4': + '@rolldown/binding-linux-ppc64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.4': + '@rolldown/binding-linux-s390x-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-musl@1.1.4': + '@rolldown/binding-linux-x64-gnu@1.2.6': optional: true - '@rolldown/binding-openharmony-arm64@1.1.4': + '@rolldown/binding-linux-x64-musl@1.2.6': 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.6': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.4': + '@rolldown/binding-win32-arm64-msvc@1.2.6': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.4': + '@rolldown/binding-win32-x64-msvc@1.2.6': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -3450,6 +3660,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': {} @@ -3460,23 +3674,36 @@ snapshots: '@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.6)(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.8 + '@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.6 transitivePeerDependencies: - supports-color - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 + '@turbo/darwin-64@2.10.12': + optional: true + + '@turbo/darwin-arm64@2.10.12': + optional: true + + '@turbo/linux-64@2.10.12': + optional: true + + '@turbo/linux-arm64@2.10.12': + optional: true + + '@turbo/windows-64@2.10.12': + optional: true + + '@turbo/windows-arm64@2.10.12': optional: true '@types/chai@5.2.3': @@ -3500,88 +3727,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.4.0': dependencies: undici-types: 8.3.0 '@types/through@0.0.33': dependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 '@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.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(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.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/type-utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.68.0 + eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + ignore: 7.0.6 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.68.0(eslint@10.9.1(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.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.68.0 + debug: 4.4.3(supports-color@10.2.2) + eslint: 10.9.1(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.68.0(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.68.0(typescript@6.0.3) + '@typescript-eslint/types': 8.68.0 + 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.68.0': dependencies: - '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/visitor-keys': 8.62.1 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 - '@typescript-eslint/tsconfig-utils@8.62.1(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.68.0(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.68.0(eslint@10.9.1(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.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(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.9.1(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.68.0': {} - '@typescript-eslint/typescript-estree@8.62.1(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.68.0(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.68.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 + 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 +3814,156 @@ 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.68.0(eslint@10.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2)) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) + eslint: 10.9.1(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.68.0': dependencies: - '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/types': 8.68.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': + '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.10 - ast-v8-to-istanbul: 1.0.4 + '@vitest/utils': 4.1.11 + ast-v8-to-istanbul: 1.0.5 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)) + magicast: 0.5.4 + obug: 2.1.4 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) - '@vitest/expect@4.1.10': + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.10 + '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.2(@types/node@26.1.0)(jiti@2.6.1)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0) - '@vitest/pretty-format@4.1.10': + '@vitest/pretty-format@4.1.11': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/runner@4.1.10': + '@vitest/runner@4.1.11': dependencies: - '@vitest/utils': 4.1.10 + '@vitest/utils': 4.1.11 pathe: 2.0.3 - '@vitest/snapshot@4.1.10': + '@vitest/snapshot@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.10': {} + '@vitest/spy@4.1.11': {} - '@vitest/utils@4.1.10': + '@vitest/utils@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 + '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 + + '@yuku-codegen/binding-android-arm64@0.8.7': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.8.7': + optional: true - acorn-jsx@5.3.2(acorn@8.17.0): + '@yuku-codegen/binding-darwin-x64@0.8.7': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.8.7': + optional: true + + '@yuku-codegen/binding-win32-x64@0.8.7': + optional: true + + '@yuku-parser/binding-android-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-darwin-x64@0.8.7': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.8.7': + optional: true + + '@yuku-parser/binding-win32-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-win32-x64@0.8.7': + optional: true + + '@yuku-toolchain/types@0.8.7': {} + + 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 +3979,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3688,7 +3987,7 @@ snapshots: dependencies: environment: 1.1.0 - ansi-regex@6.2.2: {} + ansi-regex@6.3.0: {} ansi-styles@6.2.3: {} @@ -3696,15 +3995,11 @@ snapshots: argparse@2.0.1: {} - assertion-error@2.0.1: {} + argue-cli@3.1.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.5: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -3716,15 +4011,13 @@ snapshots: before-after-hook@4.0.0: {} - birpc@4.0.0: {} - bottleneck@2.19.5: {} - brace-expansion@2.1.1: + brace-expansion@2.1.4: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -3733,24 +4026,20 @@ snapshots: fill-range: 7.1.1 optional: true - bun@1.3.14: + bun@1.4.0: 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.0 + '@oven/bun-darwin-x64': 1.4.0 + '@oven/bun-freebsd-aarch64': 1.4.0 + '@oven/bun-freebsd-x64': 1.4.0 + '@oven/bun-linux-aarch64': 1.4.0 + '@oven/bun-linux-aarch64-android': 1.4.0 + '@oven/bun-linux-aarch64-musl': 1.4.0 + '@oven/bun-linux-x64': 1.4.0 + '@oven/bun-linux-x64-android': 1.4.0 + '@oven/bun-linux-x64-musl': 1.4.0 + '@oven/bun-windows-aarch64': 1.4.0 + '@oven/bun-windows-x64': 1.4.0 bundle-name@4.1.0: dependencies: @@ -3770,7 +4059,7 @@ snapshots: chokidar@5.0.0: dependencies: - readdirp: 5.0.0 + readdirp: 5.1.1 chownr@3.0.0: optional: true @@ -3780,7 +4069,7 @@ snapshots: class-validator@0.15.1: dependencies: '@types/validator': 13.15.10 - libphonenumber-js: 1.13.8 + libphonenumber-js: 1.13.12 validator: 13.15.35 cli-cursor@5.0.0: @@ -3789,11 +4078,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 +4092,15 @@ snapshots: commander@15.0.0: {} - content-type@2.0.0: {} + content-type@3.0.0: {} - 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 +4111,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.1.0 conventional-recommended-bump@11.2.0: dependencies: @@ -3842,9 +4126,9 @@ snapshots: 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.4.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 typescript: 6.0.3 @@ -3853,7 +4137,7 @@ 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 @@ -3864,15 +4148,17 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + 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 @@ -3903,17 +4189,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.9.1(jiti@2.6.1)(supports-color@10.2.2)): dependencies: - eslint: 10.6.0(jiti@2.6.1) + eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) eslint-formatter-pretty@7.1.0: dependencies: @@ -3923,38 +4209,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.9.1(jiti@2.6.1)(supports-color@10.2.2)): dependencies: - eslint: 10.6.0(jiti@2.6.1) + eslint: 10.9.1(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.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2) + eslint-formatting-reporter: 0.0.0(eslint@10.9.1(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.6 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.9.1(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6): dependencies: - eslint: 10.6.0(jiti@2.6.1) - prettier: 3.9.4 + eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + prettier: 3.9.6 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.9.1(jiti@2.6.1)(supports-color@10.2.2)) eslint-rule-docs@1.1.235: {} @@ -3969,12 +4255,12 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0(jiti@2.6.1): + eslint@10.9.1(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.9.1(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 '@humanfs/node': 0.16.8 @@ -3983,7 +4269,7 @@ snapshots: '@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 @@ -3998,7 +4284,7 @@ snapshots: 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 +4294,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 +4314,6 @@ snapshots: esutils@2.0.3: {} - eventemitter3@5.0.4: {} - execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -4040,10 +4324,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,15 +4345,15 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.3: {} + fast-uri@3.1.6: {} 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: @@ -4091,10 +4375,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 - flatted@3.4.2: {} + flatted@3.4.4: {} fsevents@2.3.3: optional: true @@ -4141,14 +4425,6 @@ snapshots: 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 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -4177,7 +4453,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.6: {} import-fresh@3.3.1: dependencies: @@ -4198,10 +4474,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 @@ -4250,7 +4522,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.3.0: + js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -4266,7 +4538,7 @@ 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: {} @@ -4279,76 +4551,67 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.13.8: {} + libphonenumber-js@1.13.12: {} - 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.4.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.0 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 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4358,24 +4621,20 @@ 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: + magic-string@0.30.21: 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 + '@jridgewell/sourcemap-codec': 1.6.0 - 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: + magicast@0.5.4: dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 source-map-js: 1.2.1 make-dir@4.0.0: @@ -4384,8 +4643,6 @@ snapshots: meow@13.2.0: {} - meow@14.1.0: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -4394,13 +4651,13 @@ snapshots: mimic-function@5.0.1: {} - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.9 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.1 + brace-expansion: 2.1.4 minipass@7.1.3: optional: true @@ -4414,7 +4671,7 @@ snapshots: mute-stream@3.0.0: {} - nanoid@3.3.15: {} + nanoid@3.3.18: {} natural-compare@1.4.0: {} @@ -4430,22 +4687,22 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 - obug@2.1.3: {} + obug@2.1.4: {} - 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.2: 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 +4713,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 +4722,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: @@ -4542,31 +4788,33 @@ snapshots: picomatch@2.3.2: optional: true - picomatch@4.0.4: {} - 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.26: dependencies: - nanoid: 3.3.15 + nanoid: 3.3.18 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.6: {} - pretty-ms@9.3.0: + pretty-ms@9.3.1: dependencies: parse-ms: 4.0.0 @@ -4579,7 +4827,7 @@ snapshots: defu: 6.1.7 destr: 2.0.5 - readdirp@5.0.0: {} + readdirp@5.1.1: {} reflect-metadata@0.2.2: {} @@ -4596,44 +4844,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.27.14(rolldown@1.2.6)(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 + obug: 2.1.4 + rolldown: 1.2.6 + yuku-ast: 0.8.7 + yuku-codegen: 0.8.7 + yuku-parser: 0.8.7 optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: - oxc-resolver - rolldown@1.1.4: + rolldown@1.2.6: dependencies: - '@oxc-project/types': 0.138.0 + '@oxc-project/types': 0.147.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.6 + '@rolldown/binding-android-arm64': 1.2.6 + '@rolldown/binding-darwin-arm64': 1.2.6 + '@rolldown/binding-darwin-x64': 1.2.6 + '@rolldown/binding-freebsd-x64': 1.2.6 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 + '@rolldown/binding-linux-arm64-gnu': 1.2.6 + '@rolldown/binding-linux-arm64-musl': 1.2.6 + '@rolldown/binding-linux-ppc64-gnu': 1.2.6 + '@rolldown/binding-linux-s390x-gnu': 1.2.6 + '@rolldown/binding-linux-x64-gnu': 1.2.6 + '@rolldown/binding-linux-x64-musl': 1.2.6 + '@rolldown/binding-openharmony-arm64': 1.2.6 + '@rolldown/binding-win32-arm64-msvc': 1.2.6 + '@rolldown/binding-win32-x64-msvc': 1.2.6 run-applescript@7.1.0: {} @@ -4659,17 +4903,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.8.0: {} source-map-js@1.2.1: {} @@ -4677,7 +4911,7 @@ snapshots: stackback@0.0.2: {} - std-env@4.1.0: {} + std-env@4.2.0: {} stdin-discarder@0.3.2: {} @@ -4689,14 +4923,14 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 - string-width@8.2.1: + string-width@8.2.2: dependencies: get-east-asian-width: 1.6.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: {} @@ -4715,7 +4949,7 @@ 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 @@ -4726,16 +4960,16 @@ snapshots: tinybench@2.9.0: {} - tinyexec@1.2.4: {} + tinyexec@1.3.0: {} 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: @@ -4748,7 +4982,7 @@ snapshots: dependencies: typescript: 6.0.3 - tsdown@0.22.3(typescript@6.0.3): + tsdown@0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -4756,20 +4990,21 @@ snapshots: 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 + obug: 2.1.4 + picomatch: 4.0.7 + rolldown: 1.2.6 + rolldown-plugin-dts: 0.27.14(rolldown@1.2.6)(typescript@6.0.3) + tinyexec: 1.3.0 tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig-core: 7.5.0 + verkit: 0.3.2 optionalDependencies: typescript: 6.0.3 + unrun: 0.3.1(synckit@0.11.13) transitivePeerDependencies: - - '@ts-macro/tsc' - '@typescript/native-preview' + - '@volar/typescript' - oxc-resolver - vue-tsc @@ -4777,17 +5012,26 @@ snapshots: tunnel@0.0.6: {} + turbo@2.10.12: + optionalDependencies: + '@turbo/darwin-64': 2.10.12 + '@turbo/darwin-arm64': 2.10.12 + '@turbo/linux-64': 2.10.12 + '@turbo/linux-arm64': 2.10.12 + '@turbo/windows-64': 2.10.12 + '@turbo/windows-arm64': 2.10.12 + 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.68.0(eslint@10.9.1(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.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/parser': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4801,7 +5045,7 @@ snapshots: undici-types@8.3.0: {} - undici@6.27.0: {} + undici@6.28.0: {} unicode-emoji-modifier-base@1.0.0: {} @@ -4809,50 +5053,58 @@ snapshots: universal-user-agent@7.0.3: {} + unrun@0.3.1(synckit@0.11.13): + dependencies: + rolldown: 1.2.6 + 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.3.2: {} + + vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0): 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.26 + rolldown: 1.2.6 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.0 + '@types/node': 26.4.0 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)): + vitest@4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.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 + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 expect-type: 1.4.0 magic-string: 0.30.21 - obug: 2.1.3 + obug: 2.1.4 pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.1.0 + picomatch: 4.0.7 + std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.2.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) + tinyrainbow: 3.1.1 + vite: 8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0) 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.4.0 + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) transitivePeerDependencies: - msw @@ -4867,19 +5119,13 @@ 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 @@ -4894,15 +5140,54 @@ snapshots: yargs-parser@22.0.0: {} - yargs@18.0.0: + yargs@18.1.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.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + + yuku-codegen@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-x64': 0.8.7 + '@yuku-codegen/binding-freebsd-x64': 0.8.7 + '@yuku-codegen/binding-linux-arm-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm-musl': 0.8.7 + '@yuku-codegen/binding-linux-arm64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm64-musl': 0.8.7 + '@yuku-codegen/binding-linux-x64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-x64-musl': 0.8.7 + '@yuku-codegen/binding-win32-arm64': 0.8.7 + '@yuku-codegen/binding-win32-x64': 0.8.7 + + yuku-parser@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + yuku-ast: 0.8.7 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.8.7 + '@yuku-parser/binding-darwin-arm64': 0.8.7 + '@yuku-parser/binding-darwin-x64': 0.8.7 + '@yuku-parser/binding-freebsd-x64': 0.8.7 + '@yuku-parser/binding-linux-arm-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm-musl': 0.8.7 + '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm64-musl': 0.8.7 + '@yuku-parser/binding-linux-x64-gnu': 0.8.7 + '@yuku-parser/binding-linux-x64-musl': 0.8.7 + '@yuku-parser/binding-win32-arm64': 0.8.7 + '@yuku-parser/binding-win32-x64': 0.8.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2cd98ab..0c147bb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,10 +1,17 @@ +packages: + - . + - libs/* +# - libs/loader/* + allowBuilds: bun: true catalogs: build: tsdown: ^0.22.3 + turbo: ^2.10.12 typescript: ^6.0.3 + unrun: ^0.3.0 ci: '@commitlint/cli': ^21.2.0 '@commitlint/config-conventional': ^21.2.0 @@ -44,10 +51,6 @@ catalogs: '@angular-devkit/schematics': ^22.0.5 '@angular-devkit/schematics-cli': ^22.0.5 '@nanoforge-dev/schematics': ^2.2.1 - tests: + test: '@vitest/coverage-v8': ^4.1.10 vitest: ^4.1.10 - -minimumReleaseAgeExclude: - - '@nanoforge-dev/editor@0.1.4' - - '@nanoforge-dev/schematics@2.2.1' diff --git a/tsdown.config.ts b/tsdown.config.ts index 4d95f0a..ba29b93 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -22,7 +22,7 @@ function createTsdownConfig({ target: "esnext", treeshake: false, deps: { - skipNodeModulesBundle: true, + neverBundle: true, }, env: dotenv.config({ path: resolve( diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..c44a392 --- /dev/null +++ b/turbo.json @@ -0,0 +1,39 @@ +{ + "$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" + }, + "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"], + "outputs": [], + "outputLogs": "errors-only" + }, + "docs": { + "dependsOn": ["^docs"], + "inputs": ["src/**", "test/**", "package.json", "tsconfig.json", "../../docs/templates/**"], + "outputs": ["../../docs/references/**"], + "outputLogs": "errors-only" + } + } +} From 066f34788c6ad56b134c450e0619eaaaf1ec605f Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 3 Sep 2026 19:15:58 +0900 Subject: [PATCH 02/15] feat: add schematics (#200) * feat: add schematics to cli * fix: change position of docs * fix: upgrade packages * fix: fix tests --- .husky/pre-push | 6 +- README.md | 2 +- docs/docs/schematics/1-overview.mdx | 62 ++ .../schematics/2-schematics/1-application.mdx | 80 +++ .../2-schematics/2-configuration.mdx | 50 ++ .../schematics/2-schematics/3-part-base.mdx | 115 ++++ .../schematics/2-schematics/4-part-main.mdx | 77 +++ .../docs/schematics/2-schematics/5-docker.mdx | 70 +++ .../schematics/2-schematics/6-component.mdx | 120 ++++ .../docs/schematics/2-schematics/7-system.mdx | 105 ++++ e2e/cli-install.test.ts | 2 +- e2e/cli-new.test.ts | 4 +- libs/config/README.md | 1 + libs/config/package.json | 2 +- libs/config/vitest.config.ts | 18 + libs/schematics/.cliff-jumperrc.json | 8 + libs/schematics/.gitignore | 226 +++++++ libs/schematics/.prettierignore | 14 + libs/schematics/CHANGELOG.md | 245 ++++++++ libs/schematics/LICENSE | 21 + libs/schematics/README.md | 61 ++ libs/schematics/cliff.toml | 79 +++ libs/schematics/commitlint.config.cjs | 4 + libs/schematics/e2e/application.test.ts | 170 +++++ libs/schematics/e2e/component.test.ts | 171 +++++ libs/schematics/e2e/configuration.test.ts | 108 ++++ libs/schematics/e2e/docker.test.ts | 45 ++ libs/schematics/e2e/part-base.test.ts | 122 ++++ libs/schematics/e2e/part-main.test.ts | 262 ++++++++ libs/schematics/e2e/system.test.ts | 171 +++++ libs/schematics/e2e/tsconfig.json | 8 + libs/schematics/eslint.config.js | 3 + libs/schematics/package.json | 108 ++++ libs/schematics/prettier.config.js | 6 + libs/schematics/src/collection.json | 40 ++ libs/schematics/src/defaults.ts | 6 + libs/schematics/src/index.ts | 1 + .../libs/application/application.factory.ts | 87 +++ .../libs/application/application.options.d.ts | 56 ++ .../libs/application/application.schema.d.ts | 61 ++ .../libs/application/files/js/.prettierignore | 8 + .../src/libs/application/files/js/README.md | 166 +++++ .../src/libs/application/files/js/__dot__env | 6 + .../application/files/js/__dot__gitignore | 134 ++++ .../application/files/js/eslint.config.js | 11 + .../libs/application/files/js/jsconfig.json | 35 ++ .../files/js/package.json.template | 48 ++ .../files/js/pnpm-workspace.yaml.template | 3 + .../application/files/js/prettier.config.js | 6 + .../libs/application/files/ts/.prettierignore | 8 + .../src/libs/application/files/ts/README.md | 166 +++++ .../src/libs/application/files/ts/__dot__env | 6 + .../application/files/ts/__dot__gitignore | 134 ++++ .../application/files/ts/eslint.config.js | 15 + .../files/ts/package.json.template | 52 ++ .../files/ts/pnpm-workspace.yaml.template | 3 + .../application/files/ts/prettier.config.js | 6 + .../libs/application/files/ts/tsconfig.json | 41 ++ .../src/libs/application/schema.json | 70 +++ .../src/libs/component/component.factory.ts | 41 ++ .../src/libs/component/component.options.d.ts | 16 + .../src/libs/component/component.schema.d.ts | 21 + .../files/js/__fileName__.component.js | 72 +++ .../files/ts/__fileName__.component.ts | 55 ++ .../schematics/src/libs/component/schema.json | 30 + .../configuration/configuration.factory.ts | 94 +++ .../configuration/configuration.options.d.ts | 1 + .../configuration/configuration.schema.d.ts | 26 + .../configuration/files/nanoforge.config.json | 1 + .../src/libs/configuration/schema.json | 34 + .../src/libs/docker/docker.factory.ts | 36 ++ .../src/libs/docker/docker.options.d.ts | 6 + .../src/libs/docker/docker.schema.d.ts | 11 + .../src/libs/docker/files/.dockerignore | 1 + .../src/libs/docker/files/Dockerfile | 25 + libs/schematics/src/libs/docker/schema.json | 19 + .../js/__nanoforgeFolder__/client.save.json | 77 +++ .../js/__nanoforgeFolder__/server.save.json | 49 ++ .../__part__/components/example.component.js | 72 +++ .../files/js/__part__/init/after-init.js | 12 + .../js/__part__/init/after-registry-init.js | 14 + .../files/js/__part__/init/after-run.js | 12 + .../files/js/__part__/init/before-init.js | 13 + .../js/__part__/init/before-registry-init.js | 14 + .../files/js/__part__/init/before-run.js | 12 + .../js/__part__/systems/example.system.js | 44 ++ .../ts/__nanoforgeFolder__/client.save.json | 77 +++ .../ts/__nanoforgeFolder__/server.save.json | 49 ++ .../__part__/components/example.component.ts | 55 ++ .../files/ts/__part__/init/after-init.ts | 5 + .../ts/__part__/init/after-registry-init.ts | 6 + .../files/ts/__part__/init/after-run.ts | 5 + .../files/ts/__part__/init/before-init.ts | 5 + .../ts/__part__/init/before-registry-init.ts | 6 + .../files/ts/__part__/init/before-run.ts | 5 + .../ts/__part__/systems/example.system.ts | 31 + .../src/libs/part-base/part-base.factory.ts | 51 ++ .../src/libs/part-base/part-base.options.d.ts | 21 + .../src/libs/part-base/part-base.schema.d.ts | 26 + .../schematics/src/libs/part-base/schema.json | 35 ++ .../src/libs/part-main/part-main.factory.ts | 84 +++ .../src/libs/part-main/part-main.options.d.ts | 1 + .../src/libs/part-main/part-main.schema.d.ts | 36 ++ .../schematics/src/libs/part-main/schema.json | 43 ++ .../system/files/js/__fileName__.system.js | 44 ++ .../system/files/ts/__fileName__.system.ts | 31 + libs/schematics/src/libs/system/schema.json | 30 + .../src/libs/system/system.factory.ts | 41 ++ .../src/libs/system/system.options.d.ts | 16 + .../src/libs/system/system.schema.d.ts | 21 + .../utils/config/config.declarator.spec.ts | 38 ++ .../src/utils/config/config.declarator.ts | 10 + .../src/utils/config/config.finder.spec.ts | 72 +++ .../src/utils/config/config.finder.ts | 33 + .../src/utils/config/config.type.ts | 31 + libs/schematics/src/utils/formatting.spec.ts | 101 +++ libs/schematics/src/utils/formatting.ts | 30 + libs/schematics/src/utils/main/const.ts | 10 + libs/schematics/src/utils/main/enums.ts | 8 + .../src/utils/main/main-functions.spec.ts | 135 ++++ .../src/utils/main/main-functions.ts | 61 ++ libs/schematics/src/utils/main/main-utils.ts | 7 + .../src/utils/main/main.generator.spec.ts | 222 +++++++ .../src/utils/main/main.generator.ts | 224 +++++++ libs/schematics/src/utils/main/save.type.ts | 38 ++ libs/schematics/src/utils/name.spec.ts | 23 + libs/schematics/src/utils/name.ts | 12 + libs/schematics/src/utils/object.spec.ts | 66 ++ libs/schematics/src/utils/object.ts | 21 + libs/schematics/src/utils/path.ts | 6 + libs/schematics/src/utils/types.ts | 15 + libs/schematics/tsconfig.json | 56 ++ libs/schematics/tsdown.config.ts | 44 ++ libs/schematics/vitest.config.e2e.ts | 20 + libs/schematics/vitest.config.ts | 27 + package.json | 19 +- pnpm-lock.yaml | 590 +++++++++++++----- pnpm-workspace.yaml | 39 +- tsdown.config.ts | 6 +- turbo.json | 28 +- 140 files changed, 7061 insertions(+), 178 deletions(-) create mode 100644 docs/docs/schematics/1-overview.mdx create mode 100644 docs/docs/schematics/2-schematics/1-application.mdx create mode 100644 docs/docs/schematics/2-schematics/2-configuration.mdx create mode 100644 docs/docs/schematics/2-schematics/3-part-base.mdx create mode 100644 docs/docs/schematics/2-schematics/4-part-main.mdx create mode 100644 docs/docs/schematics/2-schematics/5-docker.mdx create mode 100644 docs/docs/schematics/2-schematics/6-component.mdx create mode 100644 docs/docs/schematics/2-schematics/7-system.mdx create mode 100644 libs/config/vitest.config.ts create mode 100644 libs/schematics/.cliff-jumperrc.json create mode 100644 libs/schematics/.gitignore create mode 100644 libs/schematics/.prettierignore create mode 100644 libs/schematics/CHANGELOG.md create mode 100644 libs/schematics/LICENSE create mode 100644 libs/schematics/README.md create mode 100644 libs/schematics/cliff.toml create mode 100644 libs/schematics/commitlint.config.cjs create mode 100644 libs/schematics/e2e/application.test.ts create mode 100644 libs/schematics/e2e/component.test.ts create mode 100644 libs/schematics/e2e/configuration.test.ts create mode 100644 libs/schematics/e2e/docker.test.ts create mode 100644 libs/schematics/e2e/part-base.test.ts create mode 100644 libs/schematics/e2e/part-main.test.ts create mode 100644 libs/schematics/e2e/system.test.ts create mode 100644 libs/schematics/e2e/tsconfig.json create mode 100644 libs/schematics/eslint.config.js create mode 100644 libs/schematics/package.json create mode 100644 libs/schematics/prettier.config.js create mode 100644 libs/schematics/src/collection.json create mode 100644 libs/schematics/src/defaults.ts create mode 100644 libs/schematics/src/index.ts create mode 100644 libs/schematics/src/libs/application/application.factory.ts create mode 100644 libs/schematics/src/libs/application/application.options.d.ts create mode 100644 libs/schematics/src/libs/application/application.schema.d.ts create mode 100644 libs/schematics/src/libs/application/files/js/.prettierignore create mode 100644 libs/schematics/src/libs/application/files/js/README.md create mode 100644 libs/schematics/src/libs/application/files/js/__dot__env create mode 100644 libs/schematics/src/libs/application/files/js/__dot__gitignore create mode 100644 libs/schematics/src/libs/application/files/js/eslint.config.js create mode 100644 libs/schematics/src/libs/application/files/js/jsconfig.json create mode 100644 libs/schematics/src/libs/application/files/js/package.json.template create mode 100644 libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/application/files/js/prettier.config.js create mode 100644 libs/schematics/src/libs/application/files/ts/.prettierignore create mode 100644 libs/schematics/src/libs/application/files/ts/README.md create mode 100644 libs/schematics/src/libs/application/files/ts/__dot__env create mode 100644 libs/schematics/src/libs/application/files/ts/__dot__gitignore create mode 100644 libs/schematics/src/libs/application/files/ts/eslint.config.js create mode 100644 libs/schematics/src/libs/application/files/ts/package.json.template create mode 100644 libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/application/files/ts/prettier.config.js create mode 100644 libs/schematics/src/libs/application/files/ts/tsconfig.json create mode 100644 libs/schematics/src/libs/application/schema.json create mode 100644 libs/schematics/src/libs/component/component.factory.ts create mode 100644 libs/schematics/src/libs/component/component.options.d.ts create mode 100644 libs/schematics/src/libs/component/component.schema.d.ts create mode 100644 libs/schematics/src/libs/component/files/js/__fileName__.component.js create mode 100644 libs/schematics/src/libs/component/files/ts/__fileName__.component.ts create mode 100644 libs/schematics/src/libs/component/schema.json create mode 100644 libs/schematics/src/libs/configuration/configuration.factory.ts create mode 100644 libs/schematics/src/libs/configuration/configuration.options.d.ts create mode 100644 libs/schematics/src/libs/configuration/configuration.schema.d.ts create mode 100644 libs/schematics/src/libs/configuration/files/nanoforge.config.json create mode 100644 libs/schematics/src/libs/configuration/schema.json create mode 100644 libs/schematics/src/libs/docker/docker.factory.ts create mode 100644 libs/schematics/src/libs/docker/docker.options.d.ts create mode 100644 libs/schematics/src/libs/docker/docker.schema.d.ts create mode 100644 libs/schematics/src/libs/docker/files/.dockerignore create mode 100644 libs/schematics/src/libs/docker/files/Dockerfile create mode 100644 libs/schematics/src/libs/docker/schema.json create mode 100644 libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json create mode 100644 libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js create mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js create mode 100644 libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json create mode 100644 libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts create mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts create mode 100644 libs/schematics/src/libs/part-base/part-base.factory.ts create mode 100644 libs/schematics/src/libs/part-base/part-base.options.d.ts create mode 100644 libs/schematics/src/libs/part-base/part-base.schema.d.ts create mode 100644 libs/schematics/src/libs/part-base/schema.json create mode 100644 libs/schematics/src/libs/part-main/part-main.factory.ts create mode 100644 libs/schematics/src/libs/part-main/part-main.options.d.ts create mode 100644 libs/schematics/src/libs/part-main/part-main.schema.d.ts create mode 100644 libs/schematics/src/libs/part-main/schema.json create mode 100644 libs/schematics/src/libs/system/files/js/__fileName__.system.js create mode 100644 libs/schematics/src/libs/system/files/ts/__fileName__.system.ts create mode 100644 libs/schematics/src/libs/system/schema.json create mode 100644 libs/schematics/src/libs/system/system.factory.ts create mode 100644 libs/schematics/src/libs/system/system.options.d.ts create mode 100644 libs/schematics/src/libs/system/system.schema.d.ts create mode 100644 libs/schematics/src/utils/config/config.declarator.spec.ts create mode 100644 libs/schematics/src/utils/config/config.declarator.ts create mode 100644 libs/schematics/src/utils/config/config.finder.spec.ts create mode 100644 libs/schematics/src/utils/config/config.finder.ts create mode 100644 libs/schematics/src/utils/config/config.type.ts create mode 100644 libs/schematics/src/utils/formatting.spec.ts create mode 100644 libs/schematics/src/utils/formatting.ts create mode 100644 libs/schematics/src/utils/main/const.ts create mode 100644 libs/schematics/src/utils/main/enums.ts create mode 100644 libs/schematics/src/utils/main/main-functions.spec.ts create mode 100644 libs/schematics/src/utils/main/main-functions.ts create mode 100644 libs/schematics/src/utils/main/main-utils.ts create mode 100644 libs/schematics/src/utils/main/main.generator.spec.ts create mode 100644 libs/schematics/src/utils/main/main.generator.ts create mode 100644 libs/schematics/src/utils/main/save.type.ts create mode 100644 libs/schematics/src/utils/name.spec.ts create mode 100644 libs/schematics/src/utils/name.ts create mode 100644 libs/schematics/src/utils/object.spec.ts create mode 100644 libs/schematics/src/utils/object.ts create mode 100644 libs/schematics/src/utils/path.ts create mode 100644 libs/schematics/src/utils/types.ts create mode 100644 libs/schematics/tsconfig.json create mode 100644 libs/schematics/tsdown.config.ts create mode 100644 libs/schematics/vitest.config.e2e.ts create mode 100644 libs/schematics/vitest.config.ts 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/README.md b/README.md index 8bf0143..231c65d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ npm version npm downloads Tests status - Documentation status + Release status Last commit Contributors

diff --git a/docs/docs/schematics/1-overview.mdx b/docs/docs/schematics/1-overview.mdx new file mode 100644 index 0000000..a031fec --- /dev/null +++ b/docs/docs/schematics/1-overview.mdx @@ -0,0 +1,62 @@ +--- +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 generate`. 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 a complete NanoForge project with `package.json`, TypeScript or JavaScript config, + linting, and optional server setup. + + + Generate a `nanoforge.config.json` configuration file consumed by the NanoForge CLI. + + + Create the base folder structure for a client or server part, including example component, + system, save file, and optional lifecycle hooks. + + + Generate the `main.ts` entry point for a client or server part by reading the `.nanoforge` save + file. + + + Generate a production-ready `Dockerfile` and `.dockerignore` tailored to your package manager. + + + 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-application.mdx b/docs/docs/schematics/2-schematics/1-application.mdx new file mode 100644 index 0000000..9554bc3 --- /dev/null +++ b/docs/docs/schematics/2-schematics/1-application.mdx @@ -0,0 +1,80 @@ +--- +title: Application +description: Generate a complete NanoForge application scaffold +--- + +import { ToolTip } from "mintlify/components"; + +## Overview + +The `application` schematic creates a fully structured NanoForge project with all the base files needed to start development — `package.json`, linting configuration, TypeScript or JavaScript setup, and optionally `.env` for server-enabled apps. + +## Usage + +```bash +nf new +``` + +Or via Angular DevKit schematics directly: + +```bash +schematics @nanoforge-dev/schematics:application --name=my-app +``` + +## Options + +| Option | Type | Default | Description | +| ---------------- | ------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | `nanoforge-app` | The name of the application | +| `version` | `string` | `0.0.0` | Initial version in `package.json` | +| `author` | `string` | `""` | Author field in `package.json` | +| `description` | `string` | `""` | Description field in `package.json` | +| `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 | +| `lint` | `boolean` | `true` | Generate linting configuration files | +| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager to target in scripts and lockfile references | +| `server` | `boolean` | `false` | Configure a server for the application | +| `editor` | `boolean` | `false` | Include editor dependencies | +| `initFunctions` | `boolean` | `false` | Add init function lifecycle hooks | + +## Generated structure + +``` +my-app/ +├── package.json +├── tsconfig.json # TypeScript only +├── jsconfig.json # JavaScript only +├── eslint.config.js # if lint: true +├── prettier.config.js # if lint: true +├── .prettierignore # if lint: true +├── .env # if server: true +├── .gitignore +└── README.md +``` + +## Examples + +**Minimal TypeScript app:** + +```bash +schematics @nanoforge-dev/schematics:application --name=my-game +``` + +**JavaScript app with pnpm and server:** + +```bash +schematics @nanoforge-dev/schematics:application \ + --name=my-game \ + --language=js \ + --packageManager=pnpm \ + --server=true +``` + +**TypeScript app without linting:** + +```bash +schematics @nanoforge-dev/schematics:application \ + --name=my-game \ + --lint=false +``` diff --git a/docs/docs/schematics/2-schematics/2-configuration.mdx b/docs/docs/schematics/2-schematics/2-configuration.mdx new file mode 100644 index 0000000..8514f09 --- /dev/null +++ b/docs/docs/schematics/2-schematics/2-configuration.mdx @@ -0,0 +1,50 @@ +--- +title: Configuration +description: Generate a NanoForge configuration file +--- + +import { Tooltip } from "mintlify/components"; + +## Overview + +The `configuration` schematic creates a `nanoforge.config.json` file at the root of your project. This file is the central configuration consumed by the NanoForge CLI for builds, starts, and other operations. + +## Usage + +```bash +schematics @nanoforge-dev/schematics:configuration +``` + +## Options + +| Option | Type | Default | Description | +| --------------- | -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | `nanoforge-app` | The name of the application | +| `directory` | `string` | `.` | Destination directory | +| `server` | `boolean` | `false` | Include server configuration | +| `language` | `"ts" \| "js"` | `ts` | Language used in the project | +| `initFunctions` | `boolean` | `false` | Reference init functions in configuration | + +## Generated file + +``` +nanoforge.config.json +``` + +The generated file is an empty JSON object `{}` that you populate with the fields the NanoForge CLI expects. Refer to the [CLI documentation](https://github.com/NanoForge-dev/CLI) for the full list of supported fields. + +## Examples + +**Generate configuration at the project root:** + +```bash +schematics @nanoforge-dev/schematics:configuration --name=my-game +``` + +**Generate configuration for a server-enabled project:** + +```bash +schematics @nanoforge-dev/schematics:configuration \ + --name=my-game \ + --server=true +``` diff --git a/docs/docs/schematics/2-schematics/3-part-base.mdx b/docs/docs/schematics/2-schematics/3-part-base.mdx new file mode 100644 index 0000000..eec16aa --- /dev/null +++ b/docs/docs/schematics/2-schematics/3-part-base.mdx @@ -0,0 +1,115 @@ +--- +title: Part Base +description: Generate the base structure for a NanoForge client or server part +--- + +import { Tooltip } from "mintlify/components"; + +## Overview + +The `part-base` schematic generates the foundational folder structure for a NanoForge **client** or **server** part. It creates example components and systems, the `.nanoforge` save file used to track ECS state, and optionally lifecycle init functions. + +## Usage + +```bash +schematics @nanoforge-dev/schematics:part-base --part=client +``` + +## Options + +| Option | Type | Default | Description | +| --------------- | ---------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `part` | `"client" \| "server"` | _(required)_ | The part of the application to generate | +| `directory` | `string` | `.` | Destination directory for the generated files | +| `language` | `"ts" \| "js"` | `ts` | Language to use for generated source files | +| `server` | `boolean` | `false` | Whether the application has a server | +| `initFunctions` | `boolean` | `false` | Add lifecycle init function hooks | + +## Generated structure + +``` +{directory}/ +├── {part}/ +│ ├── components/ +│ │ └── example.component.ts +│ ├── systems/ +│ │ └── example.system.ts +│ └── init/ # if initFunctions: true +│ ├── before-init.ts +│ ├── after-init.ts +│ ├── before-registry-init.ts +│ ├── after-registry-init.ts +│ ├── before-run.ts +│ └── after-run.ts +└── .nanoforge/ + └── {part}.save.json +``` + +## Save file + +The `.nanoforge/{part}.save.json` file is the source of truth for ECS state. It lists the libraries, components, systems, and entities registered for this part. The `part-main` schematic reads this file to generate the `main.ts` entry point. + +```json +{ + "libraries": [ ... ], + "components": [ + { + "name": "ExampleComponent", + "path": "./components/example.component.ts", + "paramsNames": ["paramA", "paramB", "paramC"] + } + ], + "systems": [ + { + "name": "exampleSystem", + "path": "./systems/example.system.ts" + } + ], + "entities": [ + { + "id": "exampleEntity", + "components": { + "ExampleComponent": { "paramA": "example", "paramB": 10 } + } + } + ] +} +``` + +## Init functions + +When `initFunctions` is `true`, the following lifecycle hooks are scaffolded: + +| File | Hook | Called | +| ------------------------- | -------------------- | ----------------------------------- | +| `before-init.ts` | `beforeInit` | Before the app initializes | +| `after-init.ts` | `afterInit` | After the app initializes | +| `before-registry-init.ts` | `beforeRegistryInit` | Before the ECS registry initializes | +| `after-registry-init.ts` | `afterRegistryInit` | After the ECS registry initializes | +| `before-run.ts` | `beforeRun` | Before the game loop starts | +| `after-run.ts` | `afterRun` | After the game loop ends | + +## Examples + +**Generate a TypeScript client part:** + +```bash +schematics @nanoforge-dev/schematics:part-base --part=client +``` + +**Generate a server part with init hooks:** + +```bash +schematics @nanoforge-dev/schematics:part-base \ + --part=server \ + --initFunctions=true +``` + +**Generate a JavaScript client in a specific directory:** + +```bash +schematics @nanoforge-dev/schematics:part-base \ + --part=client \ + --language=js \ + --directory=./my-game +``` diff --git a/docs/docs/schematics/2-schematics/4-part-main.mdx b/docs/docs/schematics/2-schematics/4-part-main.mdx new file mode 100644 index 0000000..60a88dc --- /dev/null +++ b/docs/docs/schematics/2-schematics/4-part-main.mdx @@ -0,0 +1,77 @@ +--- +title: Part Main +description: Generate the main entry point for a NanoForge client or server part +--- + +import { Tooltip } from "mintlify/components"; + +## Overview + +The `part-main` schematic reads the `.nanoforge/{part}.save.json` file produced by `part-base` and generates a fully wired `main.ts` (or `main.js`) entry point. It registers all libraries, components, systems, and entities declared in the save file. + + + This schematic must be run after `part-base`. + + +## Usage + +```bash +schematics @nanoforge-dev/schematics:part-main --part=client +``` + +## Options + +| Option | Type | Default | Description | +| --------------- | ---------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `part` | `"client" \| "server"` | _(required)_ | The part of the application to generate | +| `directory` | `string` | `.` | Project root directory | +| `language` | `"ts" \| "js"` | `ts` | Language of the generated entry point | +| `initFunctions` | `boolean` | `false` | Wire init lifecycle functions into the main file | +| `saveFile` | `string` | — | Custom path to the save JSON file | +| `outFile` | `string` | — | Custom output path for the generated main file | +| `editor` | `boolean` | `false` | Generate the editor variant of the main file | + +## Output path resolution + +The generated file is placed at one of the following paths, in priority order: + +1. `outFile` — if provided explicitly +2. `.nanoforge/editor/{part}/main.{language}` — if `editor: true` +3. `{part}/main.{language}` — default + +## How it works + +`part-main` reads `{directory}/.nanoforge/{part}.save.json`, builds the full application bootstrap code, then writes the result as the entry point. Every time you add a component, system, or entity through the NanoForge CLI or editor, regenerating `part-main` keeps the entry point in sync with the save file. + +## Examples + +**Generate a client main file:** + +```bash +schematics @nanoforge-dev/schematics:part-main --part=client +``` + +**Generate a server main file with init hooks:** + +```bash +schematics @nanoforge-dev/schematics:part-main \ + --part=server \ + --initFunctions=true +``` + +**Generate with a custom save file and output path:** + +```bash +schematics @nanoforge-dev/schematics:part-main \ + --part=client \ + --saveFile=./custom.save.json \ + --outFile=./src/client/main.ts +``` + +**Regenerate the editor entry point:** + +```bash +schematics @nanoforge-dev/schematics:part-main \ + --part=client \ + --editor=true +``` diff --git a/docs/docs/schematics/2-schematics/5-docker.mdx b/docs/docs/schematics/2-schematics/5-docker.mdx new file mode 100644 index 0000000..ce32b7d --- /dev/null +++ b/docs/docs/schematics/2-schematics/5-docker.mdx @@ -0,0 +1,70 @@ +--- +title: Docker +description: Generate a Dockerfile for a NanoForge application +--- + +import { Tooltip } from "mintlify/components"; + +## Overview + +The `docker` schematic generates a production-ready `Dockerfile` and a `.dockerignore` at the specified directory. The Dockerfile is tailored to the package manager you use — selecting the correct base image, lockfile, and install command automatically. + +## Usage + +```bash +schematics @nanoforge-dev/schematics:docker +``` + +## Options + +| Option | Type | Default | Description | +| ---------------- | ------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `directory` | `string` | `.` | Destination directory for the generated files | +| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager used by the application | + +## Generated files + +``` +{directory}/ +├── Dockerfile +└── .dockerignore +``` + +## Dockerfile behaviour by package manager + +| Package manager | Base image | Install command | +| --------------- | -------------- | -------------------------------------------------- | +| `npm` | `node:25-slim` | `npm install --no-lockfile` | +| `yarn` | `node:25-slim` | `yarn install --frozen-lockfile` | +| `pnpm` | `node:25-slim` | `pnpm install --frozen-lockfile --allow-build=bun` | +| `bun` | `oven/bun:1.3` | `bun install --frozen-lockfile` | + +The Dockerfile follows a standard multi-step approach: + +1. Copy `package.json` and the lockfile. +2. Install dependencies with frozen lockfile for reproducible builds. +3. Copy the rest of the source. +4. Run `build`. +5. Start with `start`. + +## Examples + +**Generate a Dockerfile for an npm project:** + +```bash +schematics @nanoforge-dev/schematics:docker +``` + +**Generate a Dockerfile for a pnpm project:** + +```bash +schematics @nanoforge-dev/schematics:docker --packageManager=pnpm +``` + +**Generate in a specific directory:** + +```bash +schematics @nanoforge-dev/schematics:docker \ + --packageManager=bun \ + --directory=./my-game +``` diff --git a/docs/docs/schematics/2-schematics/6-component.mdx b/docs/docs/schematics/2-schematics/6-component.mdx new file mode 100644 index 0000000..9b14080 --- /dev/null +++ b/docs/docs/schematics/2-schematics/6-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/7-system.mdx b/docs/docs/schematics/2-schematics/7-system.mdx new file mode 100644 index 0000000..51ecf8d --- /dev/null +++ b/docs/docs/schematics/2-schematics/7-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/e2e/cli-install.test.ts b/e2e/cli-install.test.ts index 3106204..4d4149a 100644 --- a/e2e/cli-install.test.ts +++ b/e2e/cli-install.test.ts @@ -125,7 +125,7 @@ describe("nf install (without library name)", () => { "--strict", "--no-server", "--no-init-functions", - "--no-skip-install", + "--skip-install", "--no-docker", "--no-git", "-d", diff --git a/e2e/cli-new.test.ts b/e2e/cli-new.test.ts index 931e601..143ac7a 100644 --- a/e2e/cli-new.test.ts +++ b/e2e/cli-new.test.ts @@ -104,7 +104,7 @@ describe("nf new (JavaScript, with server)", () => { "--no-strict", "--server", "--init-functions", - "--no-skip-install", + "--skip-install", "--no-docker", "--no-git", "-d", @@ -210,7 +210,7 @@ describe("nf new (with typescript with docker option)", () => { "--strict", "--no-server", "--no-init-functions", - "--no-skip-install", + "--skip-install", "--docker", "--no-git", "-d", diff --git a/libs/config/README.md b/libs/config/README.md index fb92022..c38cd9a 100644 --- a/libs/config/README.md +++ b/libs/config/README.md @@ -8,6 +8,7 @@ npm version npm downloads Tests status + Release status Last commit Contributors

diff --git a/libs/config/package.json b/libs/config/package.json index 2965183..fcb2007 100644 --- a/libs/config/package.json +++ b/libs/config/package.json @@ -51,7 +51,7 @@ "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", + "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", 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..2caa355 --- /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": ".", + "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..43f1d5f --- /dev/null +++ b/libs/schematics/README.md @@ -0,0 +1,61 @@ +
+
+

+ NanoForge +

+
+

+ npm version + npm downloads + Tests status + Release status + Last commit + Contributors +

+
+ +## About + +This repository contains the Schematics of NanoForge. 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. + +- `application` : Base of a Nanoforge app +- `configuration` : Template of `nanoforge.config.json` with premade fields +- `part-base` : Base of Nanoforge client or server +- `part-main` : Client or server `main.ts` from a config +- `component` : ECS component class for a client or server part +- `system` : ECS system function for a client or server part +- `docker` : Dockerfile and `.dockerignore` tailored to your package manager + +## 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..9c2cd71 --- /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 = "schematics" 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/application.test.ts b/libs/schematics/e2e/application.test.ts new file mode 100644 index 0000000..aa0e92f --- /dev/null +++ b/libs/schematics/e2e/application.test.ts @@ -0,0 +1,170 @@ +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("application schematic", () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + describe("with TypeScript (default)", () => { + let tree: UnitTestTree; + let packageJson: any; + + beforeAll(async () => { + tree = await runner.runSchematic("application", { name: "my-app" }); + packageJson = JSON.parse(tree.readContent("/my-app/package.json")); + }); + + it("should generate project files", () => { + expect(tree.files).toContain("/my-app/package.json"); + expect(tree.files).toContain("/my-app/tsconfig.json"); + expect(tree.files).toContain("/my-app/eslint.config.js"); + expect(tree.files).toContain("/my-app/prettier.config.js"); + expect(tree.files).toContain("/my-app/README.md"); + expect(tree.files).toContain("/my-app/.gitignore"); + expect(tree.files).not.toContain("/my-app/.env"); + expect(tree.files).not.toContain("/my-app/pnpm-workspace.yaml"); + }); + + it("should set the project name in package.json", () => { + expect(packageJson.name).toBe("my-app"); + }); + + it("should not include ecs-server by default", () => { + expect(packageJson.devDependencies["@nanoforge-dev/ecs-server"]).toBeUndefined(); + }); + + it("should not include pnpm config by default", () => { + expect(packageJson).not.toHaveProperty("pnpm"); + }); + + it("should not include init functions docs by default", () => { + expect(tree.readContent("/my-app/README.md")).not.toContain( + "│ └── init/ # Lifecycle hooks (before-init, after-run, …)", + ); + }); + }); + + describe("with JavaScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("application", { + name: "js-app", + language: "js", + }); + }); + + it("should generate JS project files", () => { + expect(tree.files).toContain("/js-app/package.json"); + expect(tree.files).toContain("/js-app/jsconfig.json"); + expect(tree.files).not.toContain("/js-app/tsconfig.json"); + expect(tree.files).not.toContain("/js-app/.env"); + }); + }); + + describe("with JavaScript and no lint", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("application", { + name: "js-no-lint-app", + language: "js", + lint: false, + }); + }); + + it("should generate JS project files without lint files", () => { + expect(tree.files).toContain("/js-no-lint-app/package.json"); + expect(tree.files).not.toContain("/js-no-lint-app/eslint.config.json"); + expect(tree.files).not.toContain("/js-no-lint-app/prettier.config.json"); + expect(tree.files).not.toContain("/js-no-lint-app/.prettierignore"); + expect(tree.files).not.toContain("/js-no-lint-app/jsconfig.json"); + expect(tree.files).not.toContain("/js-no-lint-app/tsconfig.json"); + }); + }); + + describe("with server enabled", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("application", { + name: "server-app", + server: true, + }); + }); + + it("should include env file", () => { + expect(tree.files).toContain("/server-app/.env"); + }); + + it("should include ecs-server dependency", () => { + const packageJson = JSON.parse(tree.readContent("/server-app/package.json")); + expect(packageJson.devDependencies["@nanoforge-dev/ecs-server"]).toBeDefined(); + }); + }); + + describe("with custom options", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("application", { + name: "custom-app", + version: "1.2.3", + author: "Test Author", + description: "A test project", + editor: true, + }); + }); + + it("should apply custom options to package.json", () => { + const packageJson = JSON.parse(tree.readContent("/custom-app/package.json")); + expect(packageJson.version).toBe("1.2.3"); + expect(packageJson.author).toBe("Test Author"); + expect(packageJson.description).toBe("A test project"); + expect(packageJson.devDependencies["@nanoforge-dev/core-editor"]).toBeDefined(); + expect(packageJson.devDependencies["@nanoforge-dev/graphics-2d-editor"]).toBeDefined(); + }); + }); + + describe("with custom directory", () => { + it("should generate files in the specified directory", async () => { + const tree = await runner.runSchematic("application", { + name: "my-app", + directory: "custom-dir", + }); + expect(tree.files).toContain("/custom-dir/package.json"); + }); + }); + + describe("with pnpm", () => { + it("should generate specifications of pnpm", async () => { + const tree = await runner.runSchematic("application", { + name: "pnpm-app", + packageManager: "pnpm", + server: true, + }); + expect(tree.files).toContain("/pnpm-app/package.json"); + expect(tree.files).toContain("/pnpm-app/pnpm-workspace.yaml"); + const packageJson = JSON.parse(tree.readContent("/pnpm-app/package.json")); + expect(packageJson).toHaveProperty("pnpm", { + neverBuiltDependencies: [], + }); + }); + }); + + describe("with init functions", () => { + it("should generate specifications of init functions", async () => { + const tree = await runner.runSchematic("application", { + name: "init-functions-app", + initFunctions: true, + }); + expect(tree.files).toContain("/init-functions-app/README.md"); + const content = tree.readContent("/init-functions-app/README.md"); + expect(content).toContain( + "│ └── init/ # Lifecycle hooks (before-init, after-run, …)", + ); + }); + }); +}); 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/configuration.test.ts b/libs/schematics/e2e/configuration.test.ts new file mode 100644 index 0000000..474e9cc --- /dev/null +++ b/libs/schematics/e2e/configuration.test.ts @@ -0,0 +1,108 @@ +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("configuration schematic", () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + describe("on an empty tree", () => { + let tree: UnitTestTree; + let config: Record; + + beforeAll(async () => { + tree = await runner.runSchematic("configuration", { + name: "my-name", + }); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); + }); + + it("should generate nanoforge.config.json", () => { + expect(tree.files).toContain("/nanoforge.config.json"); + }); + + it("should create a valid JSON config file", () => { + expect(() => JSON.parse(tree.readContent("/nanoforge.config.json"))).not.toThrow(); + }); + + it("should include default client build config", () => { + expect(config.name).toBe("my-name"); + expect(config.language).toBe("ts"); + expect(config.initFunctions).not.toBeDefined(); + }); + + it("should include default client build config", () => { + expect(config.client).toBeDefined(); + expect(config.client.enable).toBe(true); + }); + + it("should have server disabled", () => { + expect(config.server.enable).toBe(false); + }); + }); + + describe("with js", () => { + let tree: UnitTestTree; + let config: Record; + + beforeAll(async () => { + tree = await runner.runSchematic("configuration", { + name: "my-name", + language: "js", + initFunctions: true, + }); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); + }); + + it("should generate nanoforge.config.json", () => { + expect(tree.files).toContain("/nanoforge.config.json"); + }); + + it("should include default client build config", () => { + expect(config.name).toBe("my-name"); + expect(config.language).toBe("js"); + expect(config.initFunctions).toBe(true); + }); + + it("should include default client build config", () => { + expect(config.client).toBeDefined(); + expect(config.client.build.entry).toBe("client/main.js"); + }); + + it("should include default editor config", () => { + expect(config.client.editor.entry).toBe(".nanoforge/editor/client/main.js"); + }); + }); + + describe("with server enabled", () => { + let tree: UnitTestTree; + let config: Record; + + beforeAll(async () => { + tree = await runner.runSchematic("configuration", { + server: true, + }); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); + }); + + it("should generate config file", () => { + expect(tree.files).toContain("/nanoforge.config.json"); + }); + + it("should include server config with enable flag", () => { + expect(config.server).toBeDefined(); + expect(config.server.enable).toBe(true); + }); + }); + + describe("with custom directory", () => { + it("should generate config in the specified directory", async () => { + const tree = await runner.runSchematic("configuration", { + name: "my-app", + directory: "custom-dir", + }); + expect(tree.files).toContain("/custom-dir/nanoforge.config.json"); + }); + }); +}); diff --git a/libs/schematics/e2e/docker.test.ts b/libs/schematics/e2e/docker.test.ts new file mode 100644 index 0000000..f77fc19 --- /dev/null +++ b/libs/schematics/e2e/docker.test.ts @@ -0,0 +1,45 @@ +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("docker schematic", () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + describe("with npm package manager", () => { + let tree: UnitTestTree; + + describe.each(["npm", "yarn", "pnpm", "bun"] as const)( + "with ${packageManager} package manager", + (packageManager) => { + beforeAll(async () => { + tree = await runner.runSchematic("docker", { + directory: `${packageManager}-test-app`, + packageManager, + }); + }); + + it("should create Dockerfile", () => { + expect(tree.files).toContain(`/${packageManager}-test-app/Dockerfile`); + expect(tree.files).toContain(`/${packageManager}-test-app/.dockerignore`); + }); + + it("should create docker file with install command", () => { + const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); + expect(content).toContain(`RUN ${packageManager} install`); + }); + + it("should create docker file with build command", () => { + const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); + expect(content).toContain(`RUN ${packageManager} run build`); + }); + + it("should create docker file with start command", () => { + const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); + expect(content).toContain(`CMD ["${packageManager}", "run", "start"]`); + }); + }, + ); + }); +}); diff --git a/libs/schematics/e2e/part-base.test.ts b/libs/schematics/e2e/part-base.test.ts new file mode 100644 index 0000000..0e917f3 --- /dev/null +++ b/libs/schematics/e2e/part-base.test.ts @@ -0,0 +1,122 @@ +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("part-base schematic", () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + describe("client part with TypeScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-base", { + directory: "my-app", + part: "client", + language: "ts", + }); + }); + + it("should generate client directory structure", () => { + expect(tree.files).toContain("/my-app/client/components/example.component.ts"); + expect(tree.files).toContain("/my-app/client/systems/example.system.ts"); + }); + + it("should generate the client save file", () => { + expect(tree.files).toContain("/my-app/.nanoforge/client.save.json"); + }); + + it("should not generate server save file", () => { + expect(tree.files).not.toContain("/my-app/.nanoforge/server.save.json"); + }); + + it("should not generate init functions by default", () => { + const initFiles = tree.files.filter((f) => f.includes("/init/")); + expect(initFiles).toHaveLength(0); + }); + + it("should generate a valid save file", () => { + const save = JSON.parse(tree.readContent("/my-app/.nanoforge/client.save.json")); + expect(save.libraries).toBeDefined(); + expect(save.components).toBeDefined(); + expect(save.systems).toBeDefined(); + expect(save.entities).toBeDefined(); + }); + }); + + describe("server part with TypeScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-base", { + directory: "my-app", + part: "server", + language: "ts", + }); + }); + + it("should generate server directory structure", () => { + expect(tree.files).toContain("/my-app/server/components/example.component.ts"); + expect(tree.files).toContain("/my-app/server/systems/example.system.ts"); + }); + + it("should generate the server save file", () => { + expect(tree.files).toContain("/my-app/.nanoforge/server.save.json"); + }); + + it("should not generate client save file", () => { + expect(tree.files).not.toContain("/my-app/.nanoforge/client.save.json"); + }); + }); + + describe("with init functions enabled", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-base", { + directory: "my-app", + part: "client", + language: "ts", + initFunctions: true, + }); + }); + + it("should generate init function files", () => { + expect(tree.files).toContain("/my-app/client/init/before-init.ts"); + expect(tree.files).toContain("/my-app/client/init/after-init.ts"); + expect(tree.files).toContain("/my-app/client/init/before-registry-init.ts"); + expect(tree.files).toContain("/my-app/client/init/after-registry-init.ts"); + expect(tree.files).toContain("/my-app/client/init/before-run.ts"); + expect(tree.files).toContain("/my-app/client/init/after-run.ts"); + }); + }); + + describe("with JavaScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-base", { + directory: "my-app", + part: "client", + language: "js", + }); + }); + + it("should generate JS files", () => { + expect(tree.files).toContain("/my-app/client/components/example.component.js"); + expect(tree.files).toContain("/my-app/client/systems/example.system.js"); + }); + }); + + describe("with custom directory", () => { + it("should generate files in the specified directory", async () => { + const tree = await runner.runSchematic("part-base", { + name: "my-app", + part: "client", + directory: "custom-dir", + }); + expect(tree.files).toContain("/custom-dir/client/components/example.component.ts"); + }); + }); +}); diff --git a/libs/schematics/e2e/part-main.test.ts b/libs/schematics/e2e/part-main.test.ts new file mode 100644 index 0000000..736f8b6 --- /dev/null +++ b/libs/schematics/e2e/part-main.test.ts @@ -0,0 +1,262 @@ +import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +import { type Save } from "@utils/main/save.type"; + +const collectionPath = resolve(__dirname, "../dist/collection.json"); +const tmpDir = resolve(__dirname, "../.tmp-e2e"); + +const clientSave: Save = { + libraries: [ + { + id: "ecsLibrary", + type: "component-system", + name: "ECSClientLibrary", + path: "@nanoforge-dev/ecs-client", + }, + { + id: "graphicsLibrary", + type: "graphics", + name: "Graphics2DLibrary", + path: "@nanoforge-dev/graphics-2d", + }, + ], + components: [ + { + name: "ExampleComponent", + path: "./components/example.component.ts", + paramsNames: ["exampleValueName", "exampleNum", "__RESERVED_ASSET_exampleAsset"], + }, + ], + systems: [{ name: "exampleSystem", path: "./systems/example.system.js" }], + entities: [ + { + id: "player", + components: { + ExampleComponent: { + exampleValueName: "test", + exampleNum: 5, + exampleAsset: "/path/to/asset", + }, + }, + }, + ], +}; + +const serverSave: Save = { + libraries: [ + { + id: "ecsLibrary", + type: "component-system", + name: "ECSServerLibrary", + path: "@nanoforge-dev/ecs-server", + }, + ], + components: [], + systems: [], + entities: [], +}; + +describe("part-main schematic", () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + + beforeAll(() => { + mkdirSync(tmpDir, { recursive: true }); + writeFileSync(resolve(tmpDir, "client.save.json"), JSON.stringify(clientSave)); + writeFileSync(resolve(tmpDir, "server.save.json"), JSON.stringify(serverSave)); + }); + + afterAll(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + describe("client main with TypeScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-main", { + directory: "my-app", + part: "client", + language: "ts", + saveFile: resolve(tmpDir, "client.save.json"), + }); + }); + + it("should generate client/main.ts", () => { + expect(tree.files).toContain("/my-app/client/main.ts"); + }); + + it("should contain NanoforgeFactory.createClient()", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain("NanoforgeFactory.createClient()"); + }); + + it("should contain typed main function", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain("export async function main(options: IRunOptions)"); + }); + + it("should import and instantiate libraries", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"'); + expect(content).toContain('import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d"'); + expect(content).toContain("const ecsLibrary = new ECSClientLibrary();"); + expect(content).toContain("const graphicsLibrary = new Graphics2DLibrary();"); + }); + + it("should register entities and systems", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain("const player = registry.spawnEntity();"); + expect(content).toContain( + 'registry.addComponent(player, new ExampleComponent("test", 5, assetManagerLibrary.getAsset("/path/to/asset")));', + ); + expect(content).toContain("registry.addSystem(exampleSystem);"); + }); + + it("should not contain editor libs", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).not.toContain("@nanoforge-dev/graphics-2d-editor"); + }); + }); + + describe("server main with TypeScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-main", { + directory: "my-app", + part: "server", + language: "ts", + saveFile: resolve(tmpDir, "server.save.json"), + }); + }); + + it("should generate server/main.ts", () => { + expect(tree.files).toContain("/my-app/server/main.ts"); + }); + + it("should contain NanoforgeFactory.createServer()", () => { + const content = tree.readContent("/my-app/server/main.ts"); + expect(content).toContain("NanoforgeFactory.createServer()"); + }); + }); + + describe("client main with JavaScript", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-main", { + directory: "my-app", + part: "client", + language: "js", + saveFile: resolve(tmpDir, "client.save.json"), + }); + }); + + it("should generate client/main.js", () => { + expect(tree.files).toContain("/my-app/client/main.js"); + }); + + it("should not contain TypeScript type annotations", () => { + const content = tree.readContent("/my-app/client/main.js"); + expect(content).not.toContain("IRunOptions"); + expect(content).toContain("export async function main(options)"); + }); + }); + + describe("with init functions enabled", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-main", { + directory: "my-app", + part: "client", + language: "ts", + initFunctions: true, + saveFile: resolve(tmpDir, "client.save.json"), + }); + }); + + it("should import init functions", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain('import { beforeInit } from "./init/before-init"'); + expect(content).toContain('import { afterRun } from "./init/after-run"'); + }); + + it("should call init functions in the main body", () => { + const content = tree.readContent("/my-app/client/main.ts"); + expect(content).toContain("await beforeInit(app);"); + expect(content).toContain("await afterInit(app);"); + expect(content).toContain("await beforeRegistryInit(app, registry);"); + expect(content).toContain("await afterRegistryInit(app, registry);"); + expect(content).toContain("await beforeRun(app);"); + expect(content).toContain("await afterRun(app);"); + }); + }); + + describe("editor mode", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("part-main", { + directory: "my-app", + part: "client", + language: "ts", + editor: true, + saveFile: resolve(tmpDir, "client.save.json"), + }); + }); + + it("should generate the editor main file under .nanoforge/editor", () => { + expect(tree.files).toContain("/my-app/.nanoforge/editor/client/main.ts"); + }); + + it("should import from @nanoforge-dev/core-editor", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain('from "@nanoforge-dev/core-editor"'); + }); + + it("should use IEditorRunOptions type", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain("IEditorRunOptions"); + expect(content).toContain("export async function main(options: IEditorRunOptions)"); + }); + + it("should use entity paramsValues from editor save", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain( + 'options.editor.save.entities[0].components["ExampleComponent"]["exampleValueName"]', + ); + expect(content).toContain( + 'assetManagerLibrary.getAsset(options.editor.save.entities[0].components["ExampleComponent"]["exampleAsset"])', + ); + }); + + it("should import components and systems with relative path to root", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain( + 'import { ExampleComponent } from "../../../client/components/example.component"', + ); + expect(content).toContain( + 'import { exampleSystem } from "../../../client/systems/example.system"', + ); + }); + + it("should create a entityId reserved component", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain( + 'registry.addComponent(player, { name: "__RESERVED_entityId", entityId: "player" });', + ); + }); + + it("should init graphics editor", () => { + const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); + expect(content).toContain( + 'import { Graphics2DEditorLibrary } from "@nanoforge-dev/graphics-2d-editor"', + ); + expect(content).toContain("const graphicsEditorLibrary = new Graphics2DEditorLibrary();"); + }); + }); +}); 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/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..80641fd --- /dev/null +++ b/libs/schematics/package.json @@ -0,0 +1,108 @@ +{ + "$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/schematics#readme", + "bugs": "https://github.com/NanoForge-dev/schematics/issues", + "license": "MIT", + "contributors": [ + "Bill ", + "Exelo ", + "Fexkoser ", + "Josephine ", + "Tchips " + ], + "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/schematics.git" + }, + "funding": "https://github.com/NanoForge-dev/schematics?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@11.9.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..19462ce --- /dev/null +++ b/libs/schematics/src/collection.json @@ -0,0 +1,40 @@ +{ + "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "application": { + "factory": "./libs/application/application.factory#main", + "description": "Create a NanoForge Base application.", + "schema": "./libs/application/schema.json" + }, + "configuration": { + "factory": "./libs/configuration/configuration.factory#main", + "description": "Create a NanoForge Configuration.", + "schema": "./libs/configuration/schema.json" + }, + "part-base": { + "factory": "./libs/part-base/part-base.factory#main", + "description": "Create a NanoForge Base for client or server.", + "schema": "./libs/part-base/schema.json" + }, + "part-main": { + "factory": "./libs/part-main/part-main.factory#main", + "description": "Create a Main file for client or server.", + "schema": "./libs/part-main/schema.json" + }, + "docker": { + "factory": "./libs/docker/docker.factory#main", + "description": "Create a Dockerfile for the application.", + "schema": "./libs/docker/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..5c44c97 --- /dev/null +++ b/libs/schematics/src/defaults.ts @@ -0,0 +1,6 @@ +export const DEFAULT_APP_NAME = "nanoforge-app"; +export const DEFAULT_VERSION = "0.0.0"; +export const DEFAULT_AUTHOR = ""; +export const DEFAULT_DESCRIPTION = ""; +export const DEFAULT_LANGUAGE = "ts"; +export const DEFAULT_PACKAGE_MANAGER = "npm"; 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/application/application.factory.ts b/libs/schematics/src/libs/application/application.factory.ts new file mode 100644 index 0000000..5e0bb21 --- /dev/null +++ b/libs/schematics/src/libs/application/application.factory.ts @@ -0,0 +1,87 @@ +import { type Path, join, normalize, strings } from "@angular-devkit/core"; +import { + type Rule, + type SchematicContext, + type Source, + type Tree, + apply, + mergeWith, + move, + renameTemplateFiles, + template, + url, +} from "@angular-devkit/schematics"; +import { firstValueFrom } from "rxjs"; + +import { + DEFAULT_APP_NAME, + DEFAULT_AUTHOR, + DEFAULT_DESCRIPTION, + DEFAULT_LANGUAGE, + DEFAULT_PACKAGE_MANAGER, + DEFAULT_VERSION, +} from "~/defaults"; + +import { type ApplicationOptions } from "./application.options"; +import { type ApplicationSchema } from "./application.schema"; + +const transform = (schema: ApplicationSchema): ApplicationOptions => { + return { + name: schema.name ?? DEFAULT_APP_NAME, + version: schema.version ?? DEFAULT_VERSION, + author: schema.author ?? DEFAULT_AUTHOR, + description: schema.description ?? DEFAULT_DESCRIPTION, + language: schema.language ?? DEFAULT_LANGUAGE, + strict: schema.strict ?? true, + lint: schema.lint ?? true, + packageManager: schema.packageManager ?? DEFAULT_PACKAGE_MANAGER, + server: schema.server ?? false, + editor: schema.editor ?? false, + initFunctions: schema.initFunctions ?? false, + }; +}; + +const generate = (options: ApplicationOptions, path: string): Source => { + return apply(url(join("./files" as Path, options.language)), [ + template({ + dot: ".", + ...strings, + ...options, + }), + renameTemplateFiles(), + move(normalize(path)), + ]); +}; + +export const main = (schema: ApplicationSchema): Rule => { + const options = transform(schema); + const path = schema.directory ?? options.name; + + return async (baseTree: Tree, context: SchematicContext) => { + let tree = await mergeWith(generate(options, path))(baseTree, context); + if (!tree) return tree; + if (!("delete" in tree)) { + if (typeof tree === "function") return tree; + tree = await firstValueFrom(tree); + } + + const basePath = join("/" as Path, path); + + if (!options.lint) { + tree.delete(join(basePath, "eslint.config.js")); + tree.delete(join(basePath, "prettier.config.js")); + tree.delete(join(basePath, ".prettierignore")); + if (options.language === "js") tree.delete(join(basePath, "jsconfig.json")); + } + + if (!options.server) { + tree.delete(join(basePath, ".env")); + } + + if (options.packageManager !== "pnpm") { + tree.delete(join(basePath, "pnpm-workspace.yaml")); + } + + return tree; + }; +}; diff --git a/libs/schematics/src/libs/application/application.options.d.ts b/libs/schematics/src/libs/application/application.options.d.ts new file mode 100644 index 0000000..71201d8 --- /dev/null +++ b/libs/schematics/src/libs/application/application.options.d.ts @@ -0,0 +1,56 @@ +export interface ApplicationOptions { + /** + * NanoForge application name + */ + name: string; + + /** + * NanoForge application version + */ + version: string; + + /** + * NanoForge application author + */ + author: string; + + /** + * NanoForge application description + */ + description: string; + + /** + * NanoForge Application language + */ + language: "js" | "ts"; + + /** + * With strict mode (TypeScript or JavaScript but only on compatible editor) + */ + strict: boolean; + + /** + * Generate lint files + */ + lint: boolean; + + /** + * The used package manager + */ + packageManager: "npm" | "yarn" | "pnpm" | "bun"; + + /** + * Configure a server for the application + */ + server: boolean; + + /** + * Add editor dependencies + */ + editor: boolean; + + /** + * Add init functions to the application + */ + initFunctions: boolean; +} diff --git a/libs/schematics/src/libs/application/application.schema.d.ts b/libs/schematics/src/libs/application/application.schema.d.ts new file mode 100644 index 0000000..60579c3 --- /dev/null +++ b/libs/schematics/src/libs/application/application.schema.d.ts @@ -0,0 +1,61 @@ +export interface ApplicationSchema { + /** + * NanoForge application name + */ + name: string; + + /** + * NanoForge application version + */ + version?: string; + + /** + * NanoForge application author + */ + author?: string; + + /** + * NanoForge application description + */ + description?: string; + + /** + * NanoForge application destination directory + */ + directory?: string; + + /** + * NanoForge Application language + */ + language?: "js" | "ts"; + + /** + * With strict mode (TypeScript or JavaScript but only on compatible editor) + */ + strict?: boolean; + + /** + * Generate lint files + */ + lint?: boolean; + + /** + * The used package manager + */ + packageManager?: "npm" | "yarn" | "pnpm" | "bun"; + + /** + * Configure a server for the application + */ + server?: boolean; + + /** + * Add editor dependencies + */ + editor: boolean; + + /** + * Add init functions to the application + */ + initFunctions: boolean; +} diff --git a/libs/schematics/src/libs/application/files/js/.prettierignore b/libs/schematics/src/libs/application/files/js/.prettierignore new file mode 100644 index 0000000..2f0ede7 --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/.prettierignore @@ -0,0 +1,8 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock +bun.lock + +/node_modules +/.nanoforge diff --git a/libs/schematics/src/libs/application/files/js/README.md b/libs/schematics/src/libs/application/files/js/README.md new file mode 100644 index 0000000..94b63de --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/README.md @@ -0,0 +1,166 @@ +# <%= name %> + +--- + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ +- Package manager: **<%= packageManager %>** +- [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `<%= packageManager %> install -g @nanoforge-dev/cli` + +--- + +## Installation + +```bash +<%= packageManager %> install +``` + +--- + +## Development + +Start the game in watch mode with hot reload: + +```bash +nf dev +``` + +<% if (editor) { %>Open the visual editor (optional — lets you edit entities, components and systems without touching code): +```bash +nf editor +``` + +<% } %>--- + +## Project structure + +``` +<%= name %>/ +├── .nanoforge/ # NanoForge internal save files (do not edit manually) +├── client/ # Client-side code (runs in the browser) +│ ├── static/ # Static assets +│ ├── components/ # ECS components +│ ├── systems/ # ECS systems<% if (initFunctions) { %> +│ └── init/ # Lifecycle hooks (before-init, after-run, …)<% } %><% if (server) { %> +├── server/ # Server-side code +│ ├── static/ +│ ├── components/ +│ ├── systems/<% if (initFunctions) { %> +│ └── init/<% } %><% } %> +├── package.json +├── jsconfig.json +└── nanoforge.config.json # Game configuration +``` + +--- + +## Modifying the game + +### Add a component + +```bash +nf generate component --part client +``` + +A component is a plain class that holds data for an entity. Edit the generated file in `client/components/`: + +```js +export class MyComponent { + constructor(speed) { + this.speed = speed; + } +} + +export default MyComponent.name; +``` + +### Add a system + +```bash +nf generate system --part client +``` + +A system is a function that runs every tick and operates on entities that have specific components. Edit the generated file in `client/systems/`: + +```js +import { MyComponent } from "../components/my.component.js"; + +export const mySystem = (registry, ctx) => { + const entities = registry.getZipper([MyComponent]); + + entities.forEach((entity) => { + entity.MyComponent.speed += 1; + }); +}; + +export default mySystem.name; +``` + +<% if (server) { %> +### Server-side code + +Server components and systems live in `server/` and are imported from `@nanoforge-dev/ecs-server`. They follow the same pattern as the client side. + +Configure the server connection in `.env`: + +| Variable | Default | Description | +|---------------------------------------|-------------|---------------------------------| +| `NANOFORGE_CLIENT_SERVER_ADDRESS` | `127.0.0.1` | Server address (client-side) | +| `NANOFORGE_CLIENT_SERVER_TCP_PORT` | `4444` | TCP port the client connects to | +| `NANOFORGE_CLIENT_SERVER_UDP_PORT` | `4445` | UDP port the client connects to | +| `NANOFORGE_SERVER_LISTENING_TCP_PORT` | `4444` | TCP port the server listens on | +| `NANOFORGE_SERVER_LISTENING_UDP_PORT` | `4445` | UDP port the server listens on | + +<% } %>--- + +## Build + +Compile the game for production: + +```bash +<%= packageManager %> run build +# or +nf build +``` + +Output is written to the `.nanoforge/` directory. + +--- + +## Run + +Start the game server and open it in a browser: + +```bash +<%= packageManager %> run start +# or +nf start +``` + +<% if (lint) { %>--- + +## Code quality + +```bash +# Check formatting and linting +<%= packageManager %> run lint + +# Auto-fix formatting and linting +<%= packageManager %> run format +``` + +<% } %>--- + +## CLI reference + +| Command | Description | +|--------------------------------|-------------------------------------------| +| `nf dev` | Start in development mode with hot reload | +| `nf build` | Build for production | +| `nf start` | Run the built game | +| `nf generate component ` | Scaffold a new component | +| `nf generate system ` | Scaffold a new system |<% if (editor) { %> +| `nf editor` | Open the visual editor |<% } %> + +Full CLI documentation: diff --git a/libs/schematics/src/libs/application/files/js/__dot__env b/libs/schematics/src/libs/application/files/js/__dot__env new file mode 100644 index 0000000..75b4595 --- /dev/null +++ b/libs/schematics/src/libs/application/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/application/files/js/__dot__gitignore b/libs/schematics/src/libs/application/files/js/__dot__gitignore new file mode 100644 index 0000000..ba6da87 --- /dev/null +++ b/libs/schematics/src/libs/application/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/application/files/js/eslint.config.js b/libs/schematics/src/libs/application/files/js/eslint.config.js new file mode 100644 index 0000000..a90e742 --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/eslint.config.js @@ -0,0 +1,11 @@ +import pluginJs from "@eslint/js"; +import eslintConfigPrettier from "eslint-config-prettier"; +import globals from "globals"; + +export default [ + { files: ["{client,server}/**/*.{js}"] }, + { languageOptions: { globals: globals.node } }, + + pluginJs.configs.recommended, + eslintConfigPrettier, +]; diff --git a/libs/schematics/src/libs/application/files/js/jsconfig.json b/libs/schematics/src/libs/application/files/js/jsconfig.json new file mode 100644 index 0000000..5b67de8 --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/jsconfig.json @@ -0,0 +1,35 @@ +{ + "$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 + }, + "include": ["client/**/*.js", "server/**/*.js"], + "exclude": ["node_modules", ".nanoforge"] +} diff --git a/libs/schematics/src/libs/application/files/js/package.json.template b/libs/schematics/src/libs/application/files/js/package.json.template new file mode 100644 index 0000000..4d2bd37 --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/package.json.template @@ -0,0 +1,48 @@ +{ + "name": "<%= name %>", + "version": "<%= version %>", + "description": "<%= description %>", + "author": "<%= author %>", + "scripts": { + "build": "nf build", + "start": "nf start"<% if (lint) { %>, + "lint": "prettier --check . && eslint --format=pretty \"{client,server}/**/*.js\"", + "format": "prettier --write . && eslint --fix --format=pretty \"{client,server}/**/*.js\""<% } %> + }, + "devDependencies": {<% if (lint) { %> + "@eslint/js": "^9.39.0",<% } %> + "@nanoforge-dev/asset-manager": "^1", + "@nanoforge-dev/cli": "latest", + "@nanoforge-dev/common": "^1", + "@nanoforge-dev/config": "^1", + "@nanoforge-dev/core": "^1",<% if (editor) { %> + "@nanoforge-dev/core-editor": "^1",<% } %> + "@nanoforge-dev/ecs-client": "^1",<% if (server) { %> + "@nanoforge-dev/ecs-server": "^1",<% } %> + "@nanoforge-dev/graphics-2d": "^1",<% if (editor) { %> + "@nanoforge-dev/graphics-2d-editor": "^1",<% } %> + "@nanoforge-dev/input": "^1", + "@nanoforge-dev/music": "^1",<% if (server) { %> + "@nanoforge-dev/network-client": "^1", + "@nanoforge-dev/network-server": "^1",<% } %> + "@nanoforge-dev/sound": "^1"<% if (lint) { %>, + "eslint": "^9.39.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "globals": "^16.5.0", + "lint-staged": "^16.2.6", + "prettier": "^3.6.2"<% } %> + },<% if (lint) { %> + "lint-staged": { + "**/*.js": [ + "prettier --write" + ], + "{client,server}/**/*.js": [ + "eslint --fix" + ] + },<% } %><% if (packageManager === "pnpm") { %> + "pnpm": { + "neverBuiltDependencies": [] + },<% } %> + "private": true +} diff --git a/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template b/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template new file mode 100644 index 0000000..236b123 --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template @@ -0,0 +1,3 @@ +allowBuilds: + bun: true<% if (server) { %> + wrtc: true<% } %> diff --git a/libs/schematics/src/libs/application/files/js/prettier.config.js b/libs/schematics/src/libs/application/files/js/prettier.config.js new file mode 100644 index 0000000..8e5015d --- /dev/null +++ b/libs/schematics/src/libs/application/files/js/prettier.config.js @@ -0,0 +1,6 @@ +export default { + useTabs: false, + singleQuote: false, + trailingComma: "all", + printWidth: 100, +}; diff --git a/libs/schematics/src/libs/application/files/ts/.prettierignore b/libs/schematics/src/libs/application/files/ts/.prettierignore new file mode 100644 index 0000000..2f0ede7 --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/.prettierignore @@ -0,0 +1,8 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock +bun.lock + +/node_modules +/.nanoforge diff --git a/libs/schematics/src/libs/application/files/ts/README.md b/libs/schematics/src/libs/application/files/ts/README.md new file mode 100644 index 0000000..44878e2 --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/README.md @@ -0,0 +1,166 @@ +# <%= name %> + +--- + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ +- Package manager: **<%= packageManager %>** +- [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `<%= packageManager %> install -g @nanoforge-dev/cli` + +--- + +## Installation + +```bash +<%= packageManager %> install +``` + +--- + +## Development + +Start the game in watch mode with hot reload: + +```bash +nf dev +``` + +<% if (editor) { %>Open the visual editor (optional — lets you edit entities, components and systems without touching code): +```bash +nf editor +``` + +<% } %>--- + +## Project structure + +``` +<%= name %>/ +├── .nanoforge/ # NanoForge internal save files (do not edit manually) +├── client/ # Client-side code (runs in the browser) +│ ├── static/ # Static assets +│ ├── components/ # ECS components +│ ├── systems/ # ECS systems<% if (initFunctions) { %> +│ └── init/ # Lifecycle hooks (before-init, after-run, …)<% } %><% if (server) { %> +├── server/ # Server-side code +│ ├── static/ +│ ├── components/ +│ ├── systems/<% if (initFunctions) { %> +│ └── init/<% } %><% } %> +├── package.json +├── tsconfig.json +└── nanoforge.config.json # Game configuration +``` + +--- + +## Modifying the game + +### Add a component + +```bash +nf generate component --part client +``` + +A component is a plain class that holds data for an entity. Edit the generated file in `client/components/`: + +```ts +export class MyComponent { + constructor(public speed: number) {} +} + +export default MyComponent.name; +``` + +### Add a system + +```bash +nf generate system --part client +``` + +A system is a function that runs every tick and operates on entities that have specific components. Edit the generated file in `client/systems/`: + +```ts +import { type Context } from "@nanoforge-dev/common"; +import { type Registry } from "@nanoforge-dev/ecs-client"; +import { MyComponent } from "../components/my.component"; + +export const mySystem = (registry: Registry, ctx: Context) => { + const entities = registry.getZipper([MyComponent]); + + entities.forEach((entity) => { + entity.MyComponent.speed += 1; + }); +}; + +export default mySystem.name; +``` + +<% if (server) { %> +### Server-side code + +Server components and systems live in `server/` and are imported from `@nanoforge-dev/ecs-server`. They follow the same pattern as the client side. + +Configure the server connection in `.env`: + +| Variable | Default | Description | +|---------------------------------------|-------------|---------------------------------| +| `NANOFORGE_CLIENT_SERVER_ADDRESS` | `127.0.0.1` | Server address (client-side) | +| `NANOFORGE_CLIENT_SERVER_TCP_PORT` | `4444` | TCP port the client connects to | +| `NANOFORGE_CLIENT_SERVER_UDP_PORT` | `4445` | UDP port the client connects to | +| `NANOFORGE_SERVER_LISTENING_TCP_PORT` | `4444` | TCP port the server listens on | +| `NANOFORGE_SERVER_LISTENING_UDP_PORT` | `4445` | UDP port the server listens on | + +<% } %>--- + +## Build + +Compile the game for production: + +```bash +<%= packageManager %> run build +# or +nf build +``` + +Output is written to the `.nanoforge/` directory. + +--- + +## Run + +Start the game server and open it in a browser: + +```bash +<%= packageManager %> run start +# or +nf start +``` + +<% if (lint) { %>--- + +## Code quality + +```bash +# Check formatting and linting +<%= packageManager %> run lint + +# Auto-fix formatting and linting +<%= packageManager %> run format +``` + +<% } %>--- + +## CLI reference + +| Command | Description | +|--------------------------------|-------------------------------------------| +| `nf dev` | Start in development mode with hot reload | +| `nf build` | Build for production | +| `nf start` | Run the built game | +| `nf generate component ` | Scaffold a new component | +| `nf generate system ` | Scaffold a new system |<% if (editor) { %> +| `nf editor` | Open the visual editor |<% } %> + +Full CLI documentation: diff --git a/libs/schematics/src/libs/application/files/ts/__dot__env b/libs/schematics/src/libs/application/files/ts/__dot__env new file mode 100644 index 0000000..75b4595 --- /dev/null +++ b/libs/schematics/src/libs/application/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/application/files/ts/__dot__gitignore b/libs/schematics/src/libs/application/files/ts/__dot__gitignore new file mode 100644 index 0000000..ba6da87 --- /dev/null +++ b/libs/schematics/src/libs/application/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/application/files/ts/eslint.config.js b/libs/schematics/src/libs/application/files/ts/eslint.config.js new file mode 100644 index 0000000..2f3639d --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/eslint.config.js @@ -0,0 +1,15 @@ +import pluginJs from "@eslint/js"; +import eslintConfigPrettier from "eslint-config-prettier"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default [ + { files: ["{client,server}/**/*.{ts}"] }, + { languageOptions: { globals: globals.node } }, + + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + ...tseslint.configs.strict, + eslintConfigPrettier, + { ignores: ["**/*.js", "**/*.d.ts"] }, +]; diff --git a/libs/schematics/src/libs/application/files/ts/package.json.template b/libs/schematics/src/libs/application/files/ts/package.json.template new file mode 100644 index 0000000..3f4a160 --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/package.json.template @@ -0,0 +1,52 @@ +{ + "name": "<%= name %>", + "version": "<%= version %>", + "description": "<%= description %>", + "author": "<%= author %>", + "scripts": { + "build": "nf build", + "start": "nf start"<% if (lint) { %>, + "lint": "prettier --check . && eslint --format=pretty \"{client,server}/**/*.ts\"", + "format": "prettier --write . && eslint --fix --format=pretty \"{client,server}/**/*.ts\""<% } %> + }, + "devDependencies": {<% if (lint) { %> + "@eslint/js": "^9.39.0",<% } %> + "@nanoforge-dev/asset-manager": "^1", + "@nanoforge-dev/cli": "latest", + "@nanoforge-dev/common": "^1", + "@nanoforge-dev/config": "^1", + "@nanoforge-dev/core": "^1",<% if (editor) { %> + "@nanoforge-dev/core-editor": "^1",<% } %> + "@nanoforge-dev/ecs-client": "^1",<% if (server) { %> + "@nanoforge-dev/ecs-server": "^1",<% } %> + "@nanoforge-dev/graphics-2d": "^1",<% if (editor) { %> + "@nanoforge-dev/graphics-2d-editor": "^1",<% } %> + "@nanoforge-dev/input": "^1", + "@nanoforge-dev/music": "^1",<% if (server) { %> + "@nanoforge-dev/network-client": "^1", + "@nanoforge-dev/network-server": "^1",<% } %> + "@nanoforge-dev/sound": "^1",<% if (lint) { %> + "@typescript-eslint/eslint-plugin": "^8.46.2", + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^9.39.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "globals": "^16.5.0", + "lint-staged": "^16.2.6", + "prettier": "^3.6.2",<% } %> + "typescript": "^5.9.3"<% if (lint) { %>, + "typescript-eslint": "^8.46.2"<% } %> + },<% if (lint) { %> + "lint-staged": { + "**/*.ts": [ + "prettier --write" + ], + "{client,server}/**/*.ts": [ + "eslint --fix" + ] + },<% } %><% if (packageManager === "pnpm") { %> + "pnpm": { + "neverBuiltDependencies": [] + },<% } %> + "private": true +} diff --git a/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template b/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template new file mode 100644 index 0000000..236b123 --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template @@ -0,0 +1,3 @@ +allowBuilds: + bun: true<% if (server) { %> + wrtc: true<% } %> diff --git a/libs/schematics/src/libs/application/files/ts/prettier.config.js b/libs/schematics/src/libs/application/files/ts/prettier.config.js new file mode 100644 index 0000000..8e5015d --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/prettier.config.js @@ -0,0 +1,6 @@ +export default { + useTabs: false, + singleQuote: false, + trailingComma: "all", + printWidth: 100, +}; diff --git a/libs/schematics/src/libs/application/files/ts/tsconfig.json b/libs/schematics/src/libs/application/files/ts/tsconfig.json new file mode 100644 index 0000000..cdbe237 --- /dev/null +++ b/libs/schematics/src/libs/application/files/ts/tsconfig.json @@ -0,0 +1,41 @@ +{ + "$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, + }, + "include": ["client/**/*.ts", "server/**/*.ts"], + "exclude": ["node_modules", ".nanoforge"] +} diff --git a/libs/schematics/src/libs/application/schema.json b/libs/schematics/src/libs/application/schema.json new file mode 100644 index 0000000..d570149 --- /dev/null +++ b/libs/schematics/src/libs/application/schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsNanoForgeBaseApplication", + "title": "NanoForge Base Application Options Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the application", + "default": "nanoforge-app" + }, + "version": { + "type": "string", + "description": "NanoForge application version", + "default": "0.0.0" + }, + "author": { + "type": "string", + "description": "NanoForge application author", + "default": "" + }, + "description": { + "type": "string", + "description": "NanoForge application description", + "default": "" + }, + "directory": { + "type": "string", + "description": "NanoForge application destination directory" + }, + "language": { + "type": "string", + "enum": ["ts", "js"], + "description": "NanoForge application language", + "default": "ts" + }, + "strict": { + "type": "boolean", + "description": "With strict mode (TypeScript or JavaScript but only on compatible editor)", + "default": true + }, + "lint": { + "type": "boolean", + "description": "Generate lint files", + "default": true + }, + "packageManager": { + "type": "string", + "enum": ["npm", "yarn", "pnpm", "bun"], + "description": "NanoForge application package manager", + "default": "npm" + }, + "server": { + "type": "boolean", + "description": "Configure a server for the application", + "default": false + }, + "editor": { + "type": "boolean", + "description": "Add editor dependencies", + "default": false + }, + "initFunctions": { + "type": "boolean", + "description": "Add init functions to the application", + "default": false + } + }, + "required": ["name"] +} 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/configuration/configuration.factory.ts b/libs/schematics/src/libs/configuration/configuration.factory.ts new file mode 100644 index 0000000..a4a0a17 --- /dev/null +++ b/libs/schematics/src/libs/configuration/configuration.factory.ts @@ -0,0 +1,94 @@ +import { type Path, join, normalize, strings } from "@angular-devkit/core"; +import { + type Rule, + type Source, + type Tree, + apply, + branchAndMerge, + chain, + mergeWith, + move, + template, + url, +} from "@angular-devkit/schematics"; + +import { ConfigDeclarator } from "@utils/config/config.declarator"; +import { ConfigFinder } from "@utils/config/config.finder"; +import { type Config } from "@utils/config/config.type"; +import { type DeepPartial } from "@utils/types"; + +import { type ConfigurationOptions } from "./configuration.options"; +import { type ConfigurationSchema } from "./configuration.schema"; + +const transform = (schema: ConfigurationSchema): ConfigurationOptions => { + void schema; + + return {}; +}; + +const generate = (options: ConfigurationOptions, path: string): Source => { + return apply(url(join("./files" as Path)), [ + template({ + ...strings, + ...options, + }), + move(normalize(path)), + ]); +}; + +const addConfiguration = (options: DeepPartial, path: Path) => { + return (tree: Tree) => { + const config = new ConfigFinder(tree).find(path); + if (!config) return tree; + + const content = tree.read(config)?.toString(); + const declarator = new ConfigDeclarator(); + + if (!content) return tree; + + tree.overwrite(config, declarator.declare(content, options)); + return tree; + }; +}; + +const getConfig = (schema: ConfigurationSchema): DeepPartial => { + const res: DeepPartial = { + name: schema.name, + language: schema.language, + client: { + enable: true, + }, + server: { + enable: schema.server ?? false, + }, + }; + + if (schema.initFunctions) { + res["initFunctions"] = true; + } + + if (schema.language === "js") { + if ("client" in res && res.client) { + res.client["build"] = { entry: "client/main.js" }; + res.client["editor"] = { entry: ".nanoforge/editor/client/main.js" }; + } + if (schema.server && "server" in res && res.server) { + res.server["build"] = { entry: "server/main.js" }; + res.server["editor"] = { entry: ".nanoforge/editor/client/main.js" }; + } + } + + return res; +}; + +export const main = (schema: ConfigurationSchema): Rule => { + const options = transform(schema); + const directory = schema.directory; + + return branchAndMerge( + chain([ + mergeWith(generate(options, directory)), + addConfiguration(getConfig(schema), directory as Path), + ]), + ); +}; diff --git a/libs/schematics/src/libs/configuration/configuration.options.d.ts b/libs/schematics/src/libs/configuration/configuration.options.d.ts new file mode 100644 index 0000000..3061ac5 --- /dev/null +++ b/libs/schematics/src/libs/configuration/configuration.options.d.ts @@ -0,0 +1 @@ +export interface ConfigurationOptions {} diff --git a/libs/schematics/src/libs/configuration/configuration.schema.d.ts b/libs/schematics/src/libs/configuration/configuration.schema.d.ts new file mode 100644 index 0000000..fb569f6 --- /dev/null +++ b/libs/schematics/src/libs/configuration/configuration.schema.d.ts @@ -0,0 +1,26 @@ +export interface ConfigurationSchema { + /** + * The name of the application + */ + name: string; + + /** + * NanoForge application destination directory + */ + directory: string; + + /** + * Configure a server for the application + */ + server: boolean; + + /** + * NanoForge Application language + */ + language: "js" | "ts"; + + /** + * Add init functions to the application + */ + initFunctions: boolean; +} diff --git a/libs/schematics/src/libs/configuration/files/nanoforge.config.json b/libs/schematics/src/libs/configuration/files/nanoforge.config.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/libs/schematics/src/libs/configuration/files/nanoforge.config.json @@ -0,0 +1 @@ +{} diff --git a/libs/schematics/src/libs/configuration/schema.json b/libs/schematics/src/libs/configuration/schema.json new file mode 100644 index 0000000..427d3da --- /dev/null +++ b/libs/schematics/src/libs/configuration/schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsNanoForgeConfiguration", + "title": "NanoForge Base Application Options Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the application", + "default": "nanoforge-app" + }, + "directory": { + "type": "string", + "description": "NanoForge application destination directory", + "default": "." + }, + "server": { + "type": "boolean", + "description": "Server configuration", + "default": false + }, + "language": { + "type": "string", + "enum": ["ts", "js"], + "description": "NanoForge application language", + "default": "ts" + }, + "initFunctions": { + "type": "boolean", + "description": "Add init functions to the main application", + "default": false + } + } +} diff --git a/libs/schematics/src/libs/docker/docker.factory.ts b/libs/schematics/src/libs/docker/docker.factory.ts new file mode 100644 index 0000000..34909a4 --- /dev/null +++ b/libs/schematics/src/libs/docker/docker.factory.ts @@ -0,0 +1,36 @@ +import { type Path, normalize, strings } from "@angular-devkit/core"; +import { + type Rule, + type Source, + apply, + mergeWith, + move, + template, + url, +} from "@angular-devkit/schematics"; + +import { DEFAULT_PACKAGE_MANAGER } from "~/defaults"; + +import { type DockerOptions } from "./docker.options"; +import { type DockerSchema } from "./docker.schema"; + +const transform = (schema: DockerSchema): DockerOptions => { + return { + packageManager: schema.packageManager ?? DEFAULT_PACKAGE_MANAGER, + }; +}; + +const generate = (options: DockerOptions, path: string): Source => { + return apply(url("./files" as Path), [ + template({ + ...strings, + ...options, + }), + move(normalize(path)), + ]); +}; + +export const main = (schema: DockerSchema): Rule => { + const options = transform(schema); + return mergeWith(generate(options, schema.directory)); +}; diff --git a/libs/schematics/src/libs/docker/docker.options.d.ts b/libs/schematics/src/libs/docker/docker.options.d.ts new file mode 100644 index 0000000..489d255 --- /dev/null +++ b/libs/schematics/src/libs/docker/docker.options.d.ts @@ -0,0 +1,6 @@ +export interface DockerOptions { + /** + * NanoForge application package manager + */ + packageManager: "npm" | "yarn" | "pnpm" | "bun"; +} diff --git a/libs/schematics/src/libs/docker/docker.schema.d.ts b/libs/schematics/src/libs/docker/docker.schema.d.ts new file mode 100644 index 0000000..f3c29da --- /dev/null +++ b/libs/schematics/src/libs/docker/docker.schema.d.ts @@ -0,0 +1,11 @@ +export interface DockerSchema { + /** + * Target directory for generated Docker files + */ + directory: string; + + /** + * NanoForge application package manager + */ + packageManager: "npm" | "yarn" | "pnpm" | "bun"; +} diff --git a/libs/schematics/src/libs/docker/files/.dockerignore b/libs/schematics/src/libs/docker/files/.dockerignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/libs/schematics/src/libs/docker/files/.dockerignore @@ -0,0 +1 @@ +node_modules/ diff --git a/libs/schematics/src/libs/docker/files/Dockerfile b/libs/schematics/src/libs/docker/files/Dockerfile new file mode 100644 index 0000000..62571d5 --- /dev/null +++ b/libs/schematics/src/libs/docker/files/Dockerfile @@ -0,0 +1,25 @@ +<% if (packageManager === 'bun') { %>FROM oven/bun:1.3<% } else { %>FROM node:25-slim<% } %> +<% if (packageManager === 'pnpm') { %> +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN npm install -g pnpm +<% } %><% if (packageManager === 'yarn') { %> +RUN npm install -g yarn +<% } %> +WORKDIR /app + +COPY package.json ./ +<% if (packageManager === 'npm') { %>COPY package-lock.json ./ +<% } else if (packageManager === 'yarn') { %>COPY yarn.lock ./ +<% } else if (packageManager === 'pnpm') { %>COPY pnpm-lock.yaml ./ +<% } else if (packageManager === 'bun') { %>COPY bun.lock ./ +<% } %> +<% if (packageManager === 'npm') { %>RUN npm install --no-lockfile +<% } else if (packageManager === 'pnpm') { %>RUN pnpm install --frozen-lockfile --allow-build=bun +<% } else { %>RUN <%= packageManager %> install --frozen-lockfile +<% } %> +COPY . . + +RUN <%= packageManager %> run build + +CMD ["<%= packageManager %>", "run", "start"] diff --git a/libs/schematics/src/libs/docker/schema.json b/libs/schematics/src/libs/docker/schema.json new file mode 100644 index 0000000..5055696 --- /dev/null +++ b/libs/schematics/src/libs/docker/schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsNanoForgeDocker", + "title": "NanoForge Base Docker Options Schema", + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "NanoForge application destination directory", + "default": "." + }, + "packageManager": { + "type": "string", + "enum": ["npm", "yarn", "pnpm", "bun"], + "description": "NanoForge application package manager", + "default": "npm" + } + } +} diff --git a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json new file mode 100644 index 0000000..843e417 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json @@ -0,0 +1,77 @@ +{ + "libraries": [ + { + "id": "assetManagerLibrary", + "type": "asset-manager", + "name": "AssetManagerLibrary", + "path": "@nanoforge-dev/asset-manager", + "params": [] + }, + { + "id": "ecsLibrary", + "type": "component-system", + "name": "ECSClientLibrary", + "path": "@nanoforge-dev/ecs-client", + "params": [] + }, + { + "id": "graphicsLibrary", + "type": "graphics", + "name": "Graphics2DLibrary", + "path": "@nanoforge-dev/graphics-2d", + "params": [] + }, + { + "id": "inputLibrary", + "type": "input", + "name": "InputLibrary", + "path": "@nanoforge-dev/input", + "params": [] + }, + { + "id": "musicLibrary", + "type": "music", + "name": "MusicLibrary", + "path": "@nanoforge-dev/music", + "params": [] + },<% if (server) { %> + { + "id": "networkLibrary", + "type": "network", + "name": "NetworkClientLibrary", + "path": "@nanoforge-dev/network-client", + "params": [] + },<% } %> + { + "id": "soundLibrary", + "type": "sound", + "name": "SoundLibrary", + "path": "@nanoforge-dev/sound", + "params": [] + } + ], + "components": [ + { + "name": "ExampleComponent", + "path": "./components/example.component.js", + "paramsNames": ["paramA", "paramB", "paramC"] + } + ], + "systems": [ + { + "name": "exampleSystem", + "path": "./systems/example.system.js" + } + ], + "entities": [ + { + "id": "exampleEntity", + "components": { + "ExampleComponent": { + "paramA": "example", + "paramB": 10 + } + } + } + ] +} diff --git a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json new file mode 100644 index 0000000..ba77067 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json @@ -0,0 +1,49 @@ +{ + "libraries": [ + { + "id": "assetManagerLibrary", + "type": "asset-manager", + "name": "AssetManagerLibrary", + "path": "@nanoforge-dev/asset-manager", + "params": [] + }, + { + "id": "ecsLibrary", + "type": "component-system", + "name": "ECSServerLibrary", + "path": "@nanoforge-dev/ecs-server", + "params": [] + }<% if (server) { %>, + { + "id": "networkLibrary", + "type": "network", + "name": "NetworkServerLibrary", + "path": "@nanoforge-dev/network-server", + "params": [] + }<% } %> + ], + "components": [ + { + "name": "ExampleComponent", + "path": "./components/example.component.js", + "paramsNames": ["paramA", "paramB", "paramC"] + } + ], + "systems": [ + { + "name": "exampleSystem", + "path": "./systems/example.system.js" + } + ], + "entities": [ + { + "id": "exampleEntity", + "components": { + "ExampleComponent": { + "paramA": "example", + "paramB": 10 + } + } + } + ] +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js b/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js new file mode 100644 index 0000000..bf9728b --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js @@ -0,0 +1,72 @@ +/** + * @typedef {import("@nanoforge-dev/ecs-<%= part %>").EditorComponentManifest} EditorComponentManifest + */ + +export class ExampleComponent { + static name = "ExampleComponent"; + name = this.constructor.name; + + paramA; + paramB; + paramC; + + /** + * Example 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 ExampleComponent.name; + +/** + * Editor component manifest + * * Required for the editor to display the component and generate code + * @type {EditorComponentManifest} + */ +export const EDITOR_COMPONENT_MANIFEST = { + name: "Example", + description: "Example 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/part-base/files/js/__part__/init/after-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js new file mode 100644 index 0000000..74d2124 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js @@ -0,0 +1,12 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + */ + +/** + * Actions that are executed after the application initialization + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @returns {Promise} + */ +export async function afterInit(app) { + // Actions after app initialization +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js new file mode 100644 index 0000000..8de3bf6 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js @@ -0,0 +1,14 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + * @typedef {import("@nanoforge-dev/ecs").Registry} Registry + */ + +/** + * Actions that are executed after the registry components, entities and systems initialization + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @param {Registry} registry - ECS registry instance + * @returns {Promise} + */ +export async function afterRegistryInit(app, registry) { + // Actions after registry components, entities and systems initialization +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js new file mode 100644 index 0000000..e21bbca --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js @@ -0,0 +1,12 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + */ + +/** + * Actions that are executed when the application ended + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @returns {Promise} + */ +export async function afterRun(app) { + // Actions after app ended +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js new file mode 100644 index 0000000..602b388 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js @@ -0,0 +1,13 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + */ + +/** + * Actions that are executed before the application initialization + * But are executed after base libraries addition + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @returns {Promise} + */ +export async function beforeInit(app) { + // Actions before app initialization +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js new file mode 100644 index 0000000..ae6c374 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js @@ -0,0 +1,14 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + * @typedef {import("@nanoforge-dev/ecs").Registry} Registry + */ + +/** + * Actions that are executed before the registry components, entities and systems initialization + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @param {Registry} registry - ECS registry instance + * @returns {Promise} + */ +export async function beforeRegistryInit(app, registry) { + // Actions before registry components, entities and systems initialization +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js new file mode 100644 index 0000000..e77e47a --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js @@ -0,0 +1,12 @@ +/** + * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> + */ + +/** + * Actions that are executed before the application started + * @param {<%= appClass %>} app - Nanoforge <%= part %> instance + * @returns {Promise} + */ +export async function beforeRun(app) { + // Actions before app started +} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js b/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js new file mode 100644 index 0000000..c178bfa --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.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"; + +/** + * Example 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 exampleSystem = (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 exampleSystem.name; + +/** + * Editor component manifest + * * Required for the editor to display the system and generate code + * @type {EditorSystemManifest} + */ +export const EDITOR_SYSTEM_MANIFEST = { + name: "Example", + description: + "This system end the game when paramB reaches 0 for any entity with ExampleComponent", + dependencies: ["ExampleComponent"], +}; diff --git a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json new file mode 100644 index 0000000..c3be787 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json @@ -0,0 +1,77 @@ +{ + "libraries": [ + { + "id": "assetManagerLibrary", + "type": "asset-manager", + "name": "AssetManagerLibrary", + "path": "@nanoforge-dev/asset-manager", + "params": [] + }, + { + "id": "ecsLibrary", + "type": "component-system", + "name": "ECSClientLibrary", + "path": "@nanoforge-dev/ecs-client", + "params": [] + }, + { + "id": "graphicsLibrary", + "type": "graphics", + "name": "Graphics2DLibrary", + "path": "@nanoforge-dev/graphics-2d", + "params": [] + }, + { + "id": "inputLibrary", + "type": "input", + "name": "InputLibrary", + "path": "@nanoforge-dev/input", + "params": [] + }, + { + "id": "musicLibrary", + "type": "music", + "name": "MusicLibrary", + "path": "@nanoforge-dev/music", + "params": [] + },<% if (server) { %> + { + "id": "networkLibrary", + "type": "network", + "name": "NetworkClientLibrary", + "path": "@nanoforge-dev/network-client", + "params": [] + },<% } %> + { + "id": "soundLibrary", + "type": "sound", + "name": "SoundLibrary", + "path": "@nanoforge-dev/sound", + "params": [] + } + ], + "components": [ + { + "name": "ExampleComponent", + "path": "./components/example.component.ts", + "paramsNames": ["paramA", "paramB", "paramC"] + } + ], + "systems": [ + { + "name": "exampleSystem", + "path": "./systems/example.system.ts" + } + ], + "entities": [ + { + "id": "exampleEntity", + "components": { + "ExampleComponent": { + "paramA": "example", + "paramB": 10 + } + } + } + ] +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json new file mode 100644 index 0000000..47f7bba --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json @@ -0,0 +1,49 @@ +{ + "libraries": [ + { + "id": "assetManagerLibrary", + "type": "asset-manager", + "name": "AssetManagerLibrary", + "path": "@nanoforge-dev/asset-manager", + "params": [] + }, + { + "id": "ecsLibrary", + "type": "component-system", + "name": "ECSServerLibrary", + "path": "@nanoforge-dev/ecs-server", + "params": [] + }<% if (server) { %>, + { + "id": "networkLibrary", + "type": "network", + "name": "NetworkServerLibrary", + "path": "@nanoforge-dev/network-server", + "params": [] + }<% } %> + ], + "components": [ + { + "name": "ExampleComponent", + "path": "./components/example.component.ts", + "paramsNames": ["paramA", "paramB", "paramC"] + } + ], + "systems": [ + { + "name": "exampleSystem", + "path": "./systems/example.system.ts" + } + ], + "entities": [ + { + "id": "exampleEntity", + "components": { + "ExampleComponent": { + "paramA": "example", + "paramB": 10 + } + } + } + ] +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts new file mode 100644 index 0000000..4f98934 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts @@ -0,0 +1,55 @@ +import { type EditorComponentManifest } from "@nanoforge-dev/ecs-<%= part %>"; + +export class ExampleComponent { + 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 ExampleComponent.name; + +// * Required for the editor to display the component and generate code +export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = { + name: "Example", + description: "Example 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/part-base/files/ts/__part__/init/after-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts new file mode 100644 index 0000000..c5c3cee --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts @@ -0,0 +1,5 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; + +export async function afterInit(app: <%= appClass %>) { + // Actions after app initialization +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts new file mode 100644 index 0000000..8233635 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts @@ -0,0 +1,6 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; +import { type Registry } from "@nanoforge-dev/ecs"; + +export async function afterRegistryInit(app: <%= appClass %>, registry: Registry) { + // Actions after registry components, entities and systems initialization +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts new file mode 100644 index 0000000..1d72689 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts @@ -0,0 +1,5 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; + +export async function afterRun(app: <%= appClass %>) { + // Actions after app ended +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts new file mode 100644 index 0000000..b45b2b8 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts @@ -0,0 +1,5 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; + +export async function beforeInit(app: <%= appClass %>) { + // Actions before app initialization +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts new file mode 100644 index 0000000..91eda4a --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts @@ -0,0 +1,6 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; +import { type Registry } from "@nanoforge-dev/ecs"; + +export async function beforeRegistryInit(app: <%= appClass %>, registry: Registry) { + // Actions before registry components, entities and systems initialization +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts new file mode 100644 index 0000000..2e0c679 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts @@ -0,0 +1,5 @@ +import { type <%= appClass %> } from "@nanoforge-dev/core"; + +export async function beforeRun(app: <%= appClass %>) { + // Actions before app started +} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts new file mode 100644 index 0000000..10162b1 --- /dev/null +++ b/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.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 exampleSystem = (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 exampleSystem.name; + +// * Required for the editor to display the system and generate code +export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = { + name: "Example", + description: + "This system end the game when paramB reaches 0 for any entity with ExampleComponent", + dependencies: ["ExampleComponent"], +}; diff --git a/libs/schematics/src/libs/part-base/part-base.factory.ts b/libs/schematics/src/libs/part-base/part-base.factory.ts new file mode 100644 index 0000000..2144656 --- /dev/null +++ b/libs/schematics/src/libs/part-base/part-base.factory.ts @@ -0,0 +1,51 @@ +import { type Path, join, normalize, strings } from "@angular-devkit/core"; +import { + type Rule, + type Source, + apply, + filter, + mergeWith, + move, + template, + url, +} from "@angular-devkit/schematics"; + +import { type PartBaseOptions } from "./part-base.options"; +import { type PartBaseSchema } from "./part-base.schema"; + +const transform = (schema: PartBaseSchema): PartBaseOptions => { + return { + part: schema.part, + server: schema.server, + appClass: schema.part === "client" ? "NanoforgeClient" : "NanoforgeServer", + nanoforgeFolder: ".nanoforge", + }; +}; + +const generate = ( + options: PartBaseOptions, + path: string, + initFunctions: boolean, + language: "ts" | "js", +): Source => { + const rules = [ + template({ + ...strings, + ...options, + }), + move(normalize(path)), + filter((path) => { + const splited = path.split("/"); + return splited.at(-2) !== ".nanoforge" || splited.at(-1) === `${options.part}.save.json`; + }), + ]; + if (!initFunctions) rules.push(filter((path) => path.split("/").at(-2) !== "init")); + + return apply(url(join("./files" as Path, language)), rules); +}; + +export const main = (schema: PartBaseSchema): Rule => { + const options = transform(schema); + + return mergeWith(generate(options, schema.directory, schema.initFunctions, schema.language)); +}; diff --git a/libs/schematics/src/libs/part-base/part-base.options.d.ts b/libs/schematics/src/libs/part-base/part-base.options.d.ts new file mode 100644 index 0000000..9e5934b --- /dev/null +++ b/libs/schematics/src/libs/part-base/part-base.options.d.ts @@ -0,0 +1,21 @@ +export interface PartBaseOptions { + /** + * The part of the application to generate + */ + part: "client" | "server"; + + /** + * App class name + */ + appClass: string; + + /** + * Configure a server for the application + */ + server: boolean; + + /** + * Name of Nanoforge folder + */ + nanoforgeFolder: string; +} diff --git a/libs/schematics/src/libs/part-base/part-base.schema.d.ts b/libs/schematics/src/libs/part-base/part-base.schema.d.ts new file mode 100644 index 0000000..d708ed0 --- /dev/null +++ b/libs/schematics/src/libs/part-base/part-base.schema.d.ts @@ -0,0 +1,26 @@ +export interface PartBaseSchema { + /** + * NanoForge application destination directory + */ + directory: string; + + /** + * The part of the application to generate + */ + part: "client" | "server"; + + /** + * NanoForge Application language + */ + language: "js" | "ts"; + + /** + * Add init functions to the application + */ + initFunctions: boolean; + + /** + * Configure a server for the application + */ + server: boolean; +} diff --git a/libs/schematics/src/libs/part-base/schema.json b/libs/schematics/src/libs/part-base/schema.json new file mode 100644 index 0000000..95d3bc1 --- /dev/null +++ b/libs/schematics/src/libs/part-base/schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsNanoForgePartBase", + "title": "NanoForge Part Base Options Schema", + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "NanoForge application 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" + }, + "initFunctions": { + "type": "boolean", + "description": "Add init functions to the application", + "default": false + }, + "server": { + "type": "boolean", + "description": "There is a Server in app", + "default": false + } + }, + "required": ["part"] +} diff --git a/libs/schematics/src/libs/part-main/part-main.factory.ts b/libs/schematics/src/libs/part-main/part-main.factory.ts new file mode 100644 index 0000000..1bd9770 --- /dev/null +++ b/libs/schematics/src/libs/part-main/part-main.factory.ts @@ -0,0 +1,84 @@ +import { type Path, join, normalize, strings } from "@angular-devkit/core"; +import { + type Rule, + type Source, + type Tree, + apply, + asSource, + mergeWith, + move, + template, +} from "@angular-devkit/schematics"; +import * as fs from "fs"; + +import { generateMain } from "@utils/main/main-functions"; +import { type Save, SaveLibraryTypeEnum } from "@utils/main/save.type"; + +import { type PartMainOptions } from "./part-main.options"; +import { type PartMainSchema } from "./part-main.schema"; + +const transform = (schema: PartMainSchema): PartMainOptions => { + void schema; + + return {}; +}; + +const generate = (options: PartMainOptions, path: string, schema: PartMainSchema): Source => { + const rules = [ + template({ + ...strings, + ...options, + }), + move(normalize(path)), + ]; + + return apply(asSource(writeMain(schema, path)), rules); +}; + +const getMainPath = (schema: PartMainSchema): string => { + if (schema.outFile) return schema.outFile; + if (schema.editor) + return join(".nanoforge/editor" as Path, schema.part, `main.${schema.language}`); + return join(schema.part as Path, `main.${schema.language}`); +}; + +const writeMain = (schema: PartMainSchema, path: string) => { + return (tree: Tree) => { + let save = getSave(path, schema.part, schema.saveFile); + + const resPath = getMainPath(schema); + + if (schema.editor) { + save = getEditorSave(save); + } + + const content = generateMain(schema, save); + fs.rmSync(join(path as Path, resPath), { force: true }); + tree.create(resPath, content); + return tree; + }; +}; + +const getSave = (path: string, part: "client" | "server", saveFile?: string): Save => { + return JSON.parse( + fs.readFileSync(saveFile ?? join(path as Path, `.nanoforge/${part}.save.json`), "utf-8"), + ) as Save; +}; + +const getEditorSave = (save: Save): Save => { + if (save.libraries.some((lib) => lib.type === SaveLibraryTypeEnum.GRAPHICS)) + save.libraries.push({ + id: "graphicsEditorLibrary", + type: "graphics-editor", + name: "Graphics2DEditorLibrary", + path: "@nanoforge-dev/graphics-2d-editor", + }); + + return save; +}; + +export const main = (schema: PartMainSchema): Rule => { + const options = transform(schema); + + return mergeWith(generate(options, schema.directory, schema)); +}; diff --git a/libs/schematics/src/libs/part-main/part-main.options.d.ts b/libs/schematics/src/libs/part-main/part-main.options.d.ts new file mode 100644 index 0000000..d16f512 --- /dev/null +++ b/libs/schematics/src/libs/part-main/part-main.options.d.ts @@ -0,0 +1 @@ +export interface PartMainOptions {} diff --git a/libs/schematics/src/libs/part-main/part-main.schema.d.ts b/libs/schematics/src/libs/part-main/part-main.schema.d.ts new file mode 100644 index 0000000..f2a474a --- /dev/null +++ b/libs/schematics/src/libs/part-main/part-main.schema.d.ts @@ -0,0 +1,36 @@ +export interface PartMainSchema { + /** + * NanoForge application destination directory + */ + directory: string; + + /** + * The part of the application to generate + */ + part: "client" | "server"; + + /** + * NanoForge Application language + */ + language: "js" | "ts"; + + /** + * Add init functions to the application + */ + initFunctions: boolean; + + /** + * Save file path with components and systems in JSON format + */ + saveFile?: string; + + /** + * Path of the out file + */ + outFile?: string; + + /** + * Build main editor + */ + editor: boolean; +} diff --git a/libs/schematics/src/libs/part-main/schema.json b/libs/schematics/src/libs/part-main/schema.json new file mode 100644 index 0000000..d2f84a8 --- /dev/null +++ b/libs/schematics/src/libs/part-main/schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsNanoForgePartMain", + "title": "NanoForge Part Main Options Schema", + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "NanoForge application 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" + }, + "initFunctions": { + "type": "boolean", + "description": "Add init functions to the main application", + "default": false + }, + "saveFile": { + "type": "string", + "description": "Save file path with components and systems in JSON format" + }, + "outFile": { + "type": "string", + "description": "Path of the out file" + }, + "editor": { + "type": "boolean", + "description": "Build main editor", + "default": false + } + }, + "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/utils/config/config.declarator.spec.ts b/libs/schematics/src/utils/config/config.declarator.spec.ts new file mode 100644 index 0000000..6d4c34b --- /dev/null +++ b/libs/schematics/src/utils/config/config.declarator.spec.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; + +import { ConfigDeclarator } from "./config.declarator"; + +describe("ConfigDeclarator", () => { + it("should return a valid JSON string", () => { + const result = new ConfigDeclarator().declare("{}", {}); + expect(() => JSON.parse(result)).not.toThrow(); + }); + + it("should include default client config", () => { + const result = JSON.parse(new ConfigDeclarator().declare("{}", {})); + expect(result.client).toBeUndefined(); + }); + + it("should not include server config when server is not enabled", () => { + const result = JSON.parse(new ConfigDeclarator().declare("{}", {})); + expect(result.server).toBeUndefined(); + }); + + it("should format output with 2-space indentation", () => { + const result = new ConfigDeclarator().declare('{ "server": { "enable": true } }', {}); + expect(result).toContain("\n "); + }); + + it("should include server config when baseContent has server.enable true", () => { + const baseContent = JSON.stringify({ server: { enable: true } }); + const result = JSON.parse(new ConfigDeclarator().declare(baseContent, {})); + expect(result.server).toBeDefined(); + expect(result.server.enable).toBe(true); + }); + + it("should include server config when options has server.enable true", () => { + const result = JSON.parse(new ConfigDeclarator().declare("{}", { server: { enable: true } })); + expect(result.server).toBeDefined(); + expect(result.server.enable).toBe(true); + }); +}); diff --git a/libs/schematics/src/utils/config/config.declarator.ts b/libs/schematics/src/utils/config/config.declarator.ts new file mode 100644 index 0000000..1ad2405 --- /dev/null +++ b/libs/schematics/src/utils/config/config.declarator.ts @@ -0,0 +1,10 @@ +import { type Config } from "@utils/config/config.type"; +import { deepMerge } from "@utils/object"; +import { type DeepPartial } from "@utils/types"; + +export class ConfigDeclarator { + public declare(baseContent: string, options: DeepPartial): string { + const content = JSON.parse(baseContent); + return JSON.stringify(deepMerge(content, options), null, 2); + } +} diff --git a/libs/schematics/src/utils/config/config.finder.spec.ts b/libs/schematics/src/utils/config/config.finder.spec.ts new file mode 100644 index 0000000..e37a8e3 --- /dev/null +++ b/libs/schematics/src/utils/config/config.finder.spec.ts @@ -0,0 +1,72 @@ +import { type Path } from "@angular-devkit/core"; +import { type DirEntry, type Tree } from "@angular-devkit/schematics"; +import { describe, expect, it, vi } from "vitest"; + +import { ConfigFinder } from "./config.finder"; + +function createMockDirEntry( + path: string, + subfiles: string[], + parent: DirEntry | null = null, +): DirEntry { + return { + path: path as Path, + subfiles: subfiles as any, + subdirs: [] as any, + parent, + dir: vi.fn() as any, + file: vi.fn() as any, + visit: vi.fn() as any, + }; +} + +function createMockTree(dirEntry: DirEntry): Tree { + return { + getDir: vi.fn().mockReturnValue(dirEntry), + } as unknown as Tree; +} + +describe("ConfigFinder", () => { + it("should find nanoforge.config.json in the given directory", () => { + const dir = createMockDirEntry("/project", ["nanoforge.config.json"]); + const tree = createMockTree(dir); + const finder = new ConfigFinder(tree); + + const result = finder.find("/project" as Path); + + expect(result).toBe("/project/nanoforge.config.json"); + }); + + it("should search parent directories recursively", () => { + const parent = createMockDirEntry("/", ["nanoforge.config.json"]); + const child = createMockDirEntry("/project/src", [], parent); + const tree = createMockTree(child); + const finder = new ConfigFinder(tree); + + const result = finder.find("/project/src" as Path); + + expect(result).toBe("/nanoforge.config.json"); + }); + + it("should return null when config is not found", () => { + const root = createMockDirEntry("/", [], null); + const child = createMockDirEntry("/project", [], root); + const tree = createMockTree(child); + const finder = new ConfigFinder(tree); + + const result = finder.find("/project" as Path); + + expect(result).toBeNull(); + }); + + it("should ignore files that do not match nanoforge.config.json", () => { + const root = createMockDirEntry("/", [], null); + const dir = createMockDirEntry("/project", ["package.json", "tsconfig.json"], root); + const tree = createMockTree(dir); + const finder = new ConfigFinder(tree); + + const result = finder.find("/project" as Path); + + expect(result).toBeNull(); + }); +}); diff --git a/libs/schematics/src/utils/config/config.finder.ts b/libs/schematics/src/utils/config/config.finder.ts new file mode 100644 index 0000000..04ff4f4 --- /dev/null +++ b/libs/schematics/src/utils/config/config.finder.ts @@ -0,0 +1,33 @@ +import { type Path, join } from "@angular-devkit/core"; +import { type DirEntry, type Tree } from "@angular-devkit/schematics"; + +export interface FindOptions { + path: Path; +} + +export class ConfigFinder { + constructor(private tree: Tree) {} + + public find(path: Path): Path | null { + const dir = this.tree.getDir(path); + return this.findIn(dir); + } + + /** + * Recursively searches for the module file in the given directory. + * + * @param directory - The directory to search in. + * @returns The path to the module file, or null if not found. + */ + private findIn(directory: DirEntry | null): Path | null { + if (!directory) { + return null; + } + const moduleFilename = directory.subfiles.find((filename) => + /^nanoforge.config.json$/.test(filename), + ); + return moduleFilename !== undefined + ? join(directory.path, moduleFilename.valueOf()) + : this.findIn(directory.parent); + } +} diff --git a/libs/schematics/src/utils/config/config.type.ts b/libs/schematics/src/utils/config/config.type.ts new file mode 100644 index 0000000..a0df082 --- /dev/null +++ b/libs/schematics/src/utils/config/config.type.ts @@ -0,0 +1,31 @@ +export interface BuildConfig { + entry: string; +} + +export interface EditorConfig { + entry: string; + save: string; +} + +export interface ClientConfig { + enable: boolean; + port: string; + outDir: string; + build: BuildConfig; + editor: EditorConfig; +} + +export interface ServerConfig { + enable: boolean; + outDir: string; + build: BuildConfig; + editor: EditorConfig; +} + +export interface Config { + name: string; + language: "ts" | "js"; + initFunctions: boolean; + client: ClientConfig; + server: ServerConfig; +} 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/main/const.ts b/libs/schematics/src/utils/main/const.ts new file mode 100644 index 0000000..e685784 --- /dev/null +++ b/libs/schematics/src/utils/main/const.ts @@ -0,0 +1,10 @@ +import { SaveLibraryTypeEnum } from "@utils/main/save.type"; + +export const LIBS_FUNCTIONS_NAME: Record = { + [SaveLibraryTypeEnum.ASSET_MANAGER]: "useAssetManager", + [SaveLibraryTypeEnum.COMPONENT_SYSTEM]: "useComponentSystem", + [SaveLibraryTypeEnum.GRAPHICS]: "useGraphics", + [SaveLibraryTypeEnum.INPUT]: "useInput", + [SaveLibraryTypeEnum.NETWORK]: "useNetwork", + [SaveLibraryTypeEnum.SOUND]: "useSound", +}; diff --git a/libs/schematics/src/utils/main/enums.ts b/libs/schematics/src/utils/main/enums.ts new file mode 100644 index 0000000..9bbeb67 --- /dev/null +++ b/libs/schematics/src/utils/main/enums.ts @@ -0,0 +1,8 @@ +export enum InitFunctionEnum { + BEFORE_INIT = "beforeInit", + AFTER_INIT = "afterInit", + BEFORE_REGISTRY_INIT = "beforeRegistryInit", + AFTER_REGISTRY_INIT = "afterRegistryInit", + BEFORE_RUN = "beforeRun", + AFTER_RUN = "afterRun", +} diff --git a/libs/schematics/src/utils/main/main-functions.spec.ts b/libs/schematics/src/utils/main/main-functions.spec.ts new file mode 100644 index 0000000..0f5874f --- /dev/null +++ b/libs/schematics/src/utils/main/main-functions.spec.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from "vitest"; + +import { generateMain } from "./main-functions"; +import { type Save, SaveLibraryTypeEnum } from "./save.type"; + +const emptySave: Save = { + libraries: [], + components: [], + systems: [], + entities: [], +}; + +describe("generateMain", () => { + it("should generate a client main with TypeScript types", () => { + const result = generateMain( + { part: "client", language: "ts", initFunctions: false, editor: false }, + emptySave, + ); + expect(result).toContain("NanoforgeFactory.createClient()"); + expect(result).toContain("IRunOptions"); + expect(result).toContain("export async function main(options: IRunOptions)"); + }); + + it("should generate a server main without types", () => { + const result = generateMain( + { part: "server", language: "js", initFunctions: false, editor: false }, + emptySave, + ); + expect(result).toContain("NanoforgeFactory.createServer()"); + expect(result).not.toContain("IRunOptions"); + expect(result).toContain("export async function main(options)"); + }); + + it("should include init function imports and calls when enabled", () => { + const result = generateMain( + { part: "client", language: "ts", initFunctions: true, editor: false }, + emptySave, + ); + expect(result).toContain('import { beforeInit } from "./init/before-init";'); + expect(result).toContain("await beforeInit(app);"); + expect(result).toContain("await afterInit(app);"); + expect(result).toContain("await beforeRegistryInit(app, registry);"); + expect(result).toContain("await afterRegistryInit(app, registry);"); + expect(result).toContain("await beforeRun(app);"); + expect(result).toContain("await afterRun(app);"); + }); + + it("should not include init functions when disabled", () => { + const result = generateMain( + { part: "client", language: "ts", initFunctions: false, editor: false }, + emptySave, + ); + expect(result).not.toContain("beforeInit"); + expect(result).not.toContain("afterRun"); + }); + + it("should generate library imports, instances, and init calls", () => { + const save: Save = { + ...emptySave, + libraries: [ + { + id: "gfx", + type: SaveLibraryTypeEnum.GRAPHICS, + name: "Graphics", + path: "@nanoforge/graphics", + }, + ], + }; + const result = generateMain( + { part: "client", language: "ts", initFunctions: false, editor: false }, + save, + ); + expect(result).toContain('import { Graphics } from "@nanoforge/graphics";'); + expect(result).toContain("const gfx = new Graphics();"); + expect(result).toContain("app.useGraphics(gfx);"); + }); + + it("should generate entity and system registration", () => { + const save: Save = { + libraries: [ + { + id: "ecs", + type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, + name: "ECS", + path: "@nanoforge/ecs", + }, + ], + components: [{ name: "Position", path: "./components/position", paramsNames: ["x", "y"] }], + systems: [{ name: "PhysicsSystem", path: "./systems/physics" }], + entities: [ + { + id: "player", + components: { Position: { x: 1, y: 2 } }, + }, + ], + }; + const result = generateMain( + { part: "client", language: "ts", initFunctions: false, editor: false }, + save, + ); + expect(result).toContain("const registry = ecs.registry;"); + expect(result).toContain("const player = registry.spawnEntity();"); + expect(result).toContain("registry.addComponent(player, new Position(1, 2));"); + expect(result).toContain("registry.addSystem(PhysicsSystem);"); + }); + + it("should generate entity even in reverse order", () => { + const save: Save = { + libraries: [ + { + id: "ecs", + type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, + name: "ECS", + path: "@nanoforge/ecs", + }, + ], + components: [{ name: "Position", path: "./components/position", paramsNames: ["y", "x"] }], + systems: [{ name: "PhysicsSystem", path: "./systems/physics" }], + entities: [ + { + id: "player", + components: { Position: { x: 1, y: 2 } }, + }, + ], + }; + const result = generateMain( + { part: "client", language: "ts", initFunctions: false, editor: false }, + save, + ); + expect(result).toContain("const registry = ecs.registry;"); + expect(result).toContain("const player = registry.spawnEntity();"); + expect(result).toContain("registry.addComponent(player, new Position(2, 1));"); + expect(result).toContain("registry.addSystem(PhysicsSystem);"); + }); +}); diff --git a/libs/schematics/src/utils/main/main-functions.ts b/libs/schematics/src/utils/main/main-functions.ts new file mode 100644 index 0000000..23c7e46 --- /dev/null +++ b/libs/schematics/src/utils/main/main-functions.ts @@ -0,0 +1,61 @@ +import { join } from "path"; + +import { InitFunctionEnum } from "@utils/main/enums"; + +import { MainGenerator } from "./main.generator"; +import { type Save } from "./save.type"; + +export interface MainOptions { + part: "client" | "server"; + language: "js" | "ts"; + initFunctions: boolean; + editor: boolean; + outFile?: string; +} + +export const generateMain = (options: MainOptions, save: Save) => { + const isServer = options.part === "server"; + const hasTypes = options.language === "ts"; + const initFunctions = options.initFunctions; + + return ( + !options.editor + ? new MainGenerator() + : new MainGenerator( + true, + join( + "../".repeat(options.outFile ? options.outFile.split("/").length - 1 : 3), + options.part, + ), + ) + ) + .generateBaseImports(hasTypes) + .generateLibsImports(save.libraries) + .generateInitFunctionsImportsIfNeeded(initFunctions) + .generateComponentsImports(save.components) + .generateSystemsImports(save.systems) + + .generateMainFunction(hasTypes, (generator) => { + generator + .generateApp(isServer) + + .generateLibsInstances(save.libraries) + .generateLibsInit(save.libraries) + + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_INIT) + .generateAppInit() + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_INIT) + + .generateRegistry(save.libraries) + + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_REGISTRY_INIT) + .generateEntities(save.libraries, save.components, save.entities) + .generateSystems(save.systems) + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_REGISTRY_INIT) + + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_RUN) + .generateAppRun(initFunctions) + .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_RUN); + }) + .toString(); +}; diff --git a/libs/schematics/src/utils/main/main-utils.ts b/libs/schematics/src/utils/main/main-utils.ts new file mode 100644 index 0000000..05c79e0 --- /dev/null +++ b/libs/schematics/src/utils/main/main-utils.ts @@ -0,0 +1,7 @@ +export const getStringParam = (value: any): string => { + if (typeof value === "string") return `"${value}"`; + if (typeof value === "number") return value.toString(); + if (typeof value === "boolean") return value ? "true" : "false"; + if (typeof value === "object") return JSON.stringify(value); + return "undefined"; +}; diff --git a/libs/schematics/src/utils/main/main.generator.spec.ts b/libs/schematics/src/utils/main/main.generator.spec.ts new file mode 100644 index 0000000..2cd0223 --- /dev/null +++ b/libs/schematics/src/utils/main/main.generator.spec.ts @@ -0,0 +1,222 @@ +import { describe, expect, it } from "vitest"; + +import { MainGenerator } from "./main.generator"; +import { SaveLibraryTypeEnum } from "./save.type"; + +describe("MainGenerator", () => { + it("should start with an empty buffer", () => { + expect(new MainGenerator().toString()).toBe(""); + }); + + describe("generateBaseImports", () => { + it("should include type import when hasTypes is true", () => { + const result = new MainGenerator().generateBaseImports(true).toString(); + expect(result).toContain('import { type IRunOptions } from "@nanoforge-dev/common";'); + expect(result).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core";'); + }); + + it("should omit type import when hasTypes is false", () => { + const result = new MainGenerator().generateBaseImports(false).toString(); + expect(result).not.toContain("IRunOptions"); + expect(result).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core";'); + }); + }); + + describe("generateApp", () => { + it("should generate createClient call for client", () => { + const result = new MainGenerator().generateApp(false).toString(); + expect(result).toContain("NanoforgeFactory.createClient()"); + }); + + it("should generate createServer call for server", () => { + const result = new MainGenerator().generateApp(true).toString(); + expect(result).toContain("NanoforgeFactory.createServer()"); + }); + }); + + describe("generateAppInit", () => { + it("should generate await app.init(options)", () => { + const result = new MainGenerator().generateAppInit().toString(); + expect(result).toContain("await app.init(options);"); + }); + }); + + describe("generateAppRun", () => { + it("should generate await app.run()", () => { + const result = new MainGenerator().generateAppRun(false).toString(); + expect(result).toContain("await app.run();"); + }); + }); + + describe("generateMainFunction", () => { + it("should generate typed function signature when hasTypes is true", () => { + const result = new MainGenerator().generateMainFunction(true, () => {}).toString(); + expect(result).toContain("export async function main(options: IRunOptions) {"); + expect(result).toContain("}"); + }); + + it("should generate untyped function signature when hasTypes is false", () => { + const result = new MainGenerator().generateMainFunction(false, () => {}).toString(); + expect(result).toContain("export async function main(options) {"); + }); + + it("should indent content inside the function body", () => { + const result = new MainGenerator() + .generateMainFunction(false, (gen) => { + gen.generateAppInit(); + }) + .toString(); + expect(result).toContain(" await app.init(options);"); + }); + }); + + describe("generateLibsImports", () => { + it("should generate sorted import statements", () => { + const libs = [ + { + id: "gfx", + type: SaveLibraryTypeEnum.GRAPHICS, + name: "Graphics", + path: "@nanoforge/graphics", + }, + { + id: "ecs", + type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, + name: "ECS", + path: "@nanoforge/ecs", + }, + ]; + const result = new MainGenerator().generateLibsImports(libs).toString(); + const lines = result.trim().split("\n"); + expect(lines[0]).toContain("@nanoforge/ecs"); + expect(lines[1]).toContain("@nanoforge/graphics"); + }); + }); + + describe("generateLibsInstances", () => { + it("should generate const declarations for each library", () => { + const libs = [{ id: "gfx", type: SaveLibraryTypeEnum.GRAPHICS, name: "Graphics", path: "" }]; + const result = new MainGenerator().generateLibsInstances(libs).toString(); + expect(result).toContain("const gfx = new Graphics();"); + }); + }); + + describe("generateLibsInit", () => { + it("should use known init function for known library types", () => { + const libs = [{ id: "gfx", type: SaveLibraryTypeEnum.GRAPHICS, name: "Graphics", path: "" }]; + const result = new MainGenerator().generateLibsInit(libs).toString(); + expect(result).toContain("app.useGraphics(gfx);"); + }); + + it("should use app.use with Symbol for unknown library types", () => { + const libs = [{ id: "custom", type: "custom-lib", name: "Custom", path: "" }]; + const result = new MainGenerator().generateLibsInit(libs).toString(); + expect(result).toContain('app.use(Symbol("custom-lib"), custom);'); + }); + }); + + describe("generateRegistry", () => { + it("should use component-system library id for registry", () => { + const libs = [ + { id: "myEcs", type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, name: "ECS", path: "" }, + ]; + const result = new MainGenerator().generateRegistry(libs).toString(); + expect(result).toContain("const registry = myEcs.registry;"); + }); + + it("should fallback to ecsLibrary when no component-system lib is found", () => { + const result = new MainGenerator().generateRegistry([]).toString(); + expect(result).toContain("const registry = ecsLibrary.registry;"); + }); + }); + + describe("generateEntities", () => { + it("should generate entity spawn and component additions", () => { + const components = [ + { + name: "Position", + path: "non", + paramsNames: ["x", "y"], + }, + { + name: "Velocity", + path: "non", + paramsNames: ["fast", "__RESERVED_ASSET_asset"], + }, + ]; + const entities = [ + { + id: "player", + components: { + Position: { + x: 1, + y: 2, + }, + Velocity: { + fast: 3, + asset: "test", + }, + }, + }, + ]; + const result = new MainGenerator().generateEntities([], components, entities).toString(); + expect(result).toContain("const player = registry.spawnEntity();"); + expect(result).toContain("registry.addComponent(player, new Position(1, 2));"); + expect(result).toContain( + 'registry.addComponent(player, new Velocity(3, assetManagerLibrary.getAsset("test")));', + ); + }); + }); + + describe("generateSystems", () => { + it("should generate addSystem calls", () => { + const systems = [ + { name: "PhysicsSystem", path: "" }, + { name: "RenderSystem", path: "" }, + ]; + const result = new MainGenerator().generateSystems(systems).toString(); + expect(result).toContain("registry.addSystem(PhysicsSystem);"); + expect(result).toContain("registry.addSystem(RenderSystem);"); + }); + }); + + describe("generateInitFunctionIfNeeded", () => { + it("should generate init function call when needed is true", () => { + const result = new MainGenerator() + .generateInitFunctionIfNeeded(true, "beforeInit" as any) + .toString(); + expect(result).toContain("await beforeInit(app);"); + }); + + it("should not generate anything when needed is false", () => { + const result = new MainGenerator() + .generateInitFunctionIfNeeded(false, "beforeInit" as any) + .toString(); + expect(result).toBe(""); + }); + + it("should pass registry param for registry init functions", () => { + const result = new MainGenerator() + .generateInitFunctionIfNeeded(true, "beforeRegistryInit" as any) + .toString(); + expect(result).toContain("await beforeRegistryInit(app, registry);"); + }); + }); + + describe("generateInitFunctionsImportsIfNeeded", () => { + it("should generate all init function imports when needed", () => { + const result = new MainGenerator().generateInitFunctionsImportsIfNeeded(true).toString(); + expect(result).toContain("afterInit"); + expect(result).toContain("afterRegistryInit"); + expect(result).toContain("afterRun"); + expect(result).toContain("beforeInit"); + expect(result).toContain("beforeRegistryInit"); + expect(result).toContain("beforeRun"); + }); + + it("should not generate imports when not needed", () => { + const result = new MainGenerator().generateInitFunctionsImportsIfNeeded(false).toString(); + expect(result).toBe(""); + }); + }); +}); diff --git a/libs/schematics/src/utils/main/main.generator.ts b/libs/schematics/src/utils/main/main.generator.ts new file mode 100644 index 0000000..bc20904 --- /dev/null +++ b/libs/schematics/src/utils/main/main.generator.ts @@ -0,0 +1,224 @@ +import { joinRelative } from "@utils/path"; + +import { LIBS_FUNCTIONS_NAME } from "./const"; +import { InitFunctionEnum } from "./enums"; +import { getStringParam } from "./main-utils"; +import { + type SaveComponent, + type SaveEntity, + type SaveLibrary, + SaveLibraryTypeEnum, + type SaveSystem, +} from "./save.type"; + +export class MainGenerator { + private buffer = ""; + private indentation = 0; + private readonly editor: boolean; + private readonly pathToRoot: string; + + constructor(editor: boolean = false, pathToRoot: string = "") { + this.editor = editor; + this.pathToRoot = pathToRoot; + } + + toString(): string { + return this.buffer; + } + + generateBaseImports(hasTypes: boolean): MainGenerator { + if (this.editor) { + if (hasTypes) + this.writeLine( + `import { type IEditorRunOptions, NanoforgeFactory } from "@nanoforge-dev/core-editor";`, + ); + else this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core-editor";`); + } else { + if (hasTypes) this.writeLine(`import { type IRunOptions } from "@nanoforge-dev/common";`); + this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core";`); + } + + this.endSection(); + return this; + } + + generateLibsImports(libs: SaveLibrary[]): MainGenerator { + return this.generateImports(libs, false); + } + + generateComponentsImports(libs: SaveComponent[]): MainGenerator { + return this.generateImports(libs, true); + } + + generateSystemsImports(libs: SaveSystem[]): MainGenerator { + return this.generateImports(libs, true); + } + + generateMainFunction(hasTypes: boolean, cb: (generator: MainGenerator) => void): MainGenerator { + if (this.editor) + this.writeLine( + `export async function main(options${hasTypes ? ": IEditorRunOptions" : ""}) {`, + ); + else this.writeLine(`export async function main(options${hasTypes ? ": IRunOptions" : ""}) {`); + this.indentation += 1; + cb(this); + this.indentation -= 1; + this.writeLine("}"); + return this; + } + + generateApp(isServer: boolean): MainGenerator { + this.writeLine(`const app = NanoforgeFactory.create${isServer ? "Server" : "Client"}();`); + this.endSection(); + return this; + } + + generateAppInit(): MainGenerator { + this.writeLine(`await app.init(options);`); + this.endSection(); + return this; + } + + generateAppRun(hasInitFunctions: boolean): MainGenerator { + this.writeLine(`await app.run();`); + if (hasInitFunctions) this.endSection(); + return this; + } + + generateLibsInstances(libs: SaveLibrary[]): MainGenerator { + libs.forEach(({ id, name }) => this.writeLine(`const ${id} = new ${name}();`)); + this.endSection(); + return this; + } + + generateLibsInit(libs: SaveLibrary[]): MainGenerator { + libs.forEach(({ id, type }) => this.writeLine(this.getInitFunction(id, type))); + this.endSection(); + return this; + } + + generateRegistry(libs: SaveLibrary[]): MainGenerator { + const libName = + libs.find((lib) => lib.type === SaveLibraryTypeEnum.COMPONENT_SYSTEM)?.id ?? "ecsLibrary"; + this.writeLine(`const registry = ${libName}.registry;`); + this.endSection(); + return this; + } + + generateEntities( + libs: SaveLibrary[], + components: SaveComponent[], + entities: SaveEntity[], + ): MainGenerator { + entities.forEach((entity, index) => this.generateEntity(libs, components, entity, index)); + return this; + } + + generateSystems(systems: SaveSystem[]): MainGenerator { + systems.forEach(({ name }) => this.writeLine(`registry.addSystem(${name});`)); + this.endSection(); + return this; + } + + generateInitFunctionIfNeeded(needed: boolean, func: InitFunctionEnum): MainGenerator { + if (needed) return this.generateInitFunction(func); + return this; + } + + generateInitFunctionsImportsIfNeeded(needed: boolean): MainGenerator { + if (!needed) return this; + return this.generateImports( + [ + { name: "afterInit", path: "./init/after-init" }, + { name: "afterRegistryInit", path: "./init/after-registry-init" }, + { name: "afterRun", path: "./init/after-run" }, + { name: "beforeInit", path: "./init/before-init" }, + { name: "beforeRegistryInit", path: "./init/before-registry-init" }, + { name: "beforeRun", path: "./init/before-run" }, + ], + true, + ); + } + + private generateInitFunction(func: InitFunctionEnum): MainGenerator { + const params = ["app"]; + if ( + [InitFunctionEnum.BEFORE_REGISTRY_INIT, InitFunctionEnum.AFTER_REGISTRY_INIT].includes(func) + ) + params.push("registry"); + this.writeLine(`await ${func}(${params.join(", ")});`); + if (func !== InitFunctionEnum.AFTER_RUN) this.endSection(); + return this; + } + + private generateImports(els: { name: string; path: string }[], relative: boolean): MainGenerator { + [...els] + .sort((a, b) => a.path.localeCompare(b.path)) + .forEach(({ name, path }) => { + path = path.replace(/\.(?:ts|js)$/, ""); + this.writeLine( + `import { ${name} } from "${relative ? joinRelative(this.pathToRoot, path) : path}";`, + ); + }); + this.endSection(); + return this; + } + + private generateEntity( + libs: SaveLibrary[], + components: SaveComponent[], + entity: SaveEntity, + entityIndex: number, + ): void { + this.writeLine(`const ${entity.id} = registry.spawnEntity();`); + Object.entries(entity.components).forEach(([componentName, rawParams]) => { + const originalsParams = components.find(({ name }) => name === componentName)?.paramsNames; + if (!originalsParams) + throw new Error("Missing component in entity or inexistent saved component"); + const params: string[] = originalsParams.map((ogParam) => { + let isAsset = false; + if (ogParam.startsWith("__RESERVED_ASSET_")) { + isAsset = true; + ogParam = ogParam.replace("__RESERVED_ASSET_", ""); + } + + const param = this.editor + ? `options.editor.save.entities[${entityIndex}].components["${componentName}"]["${ogParam}"]` + : getStringParam(rawParams[ogParam]); + if (isAsset) return this.getAssetParam(param, libs); + return param; + }); + this.writeLine( + `registry.addComponent(${entity.id}, new ${componentName}(${params.join(", ")}));`, + ); + }); + if (this.editor) { + this.writeLine( + `registry.addComponent(${entity.id}, { name: "__RESERVED_entityId", entityId: "${entity.id}" });`, + ); + } + this.endSection(); + } + + private getAssetParam(param: string, libs: SaveLibrary[]): string { + const libName = + libs.find((lib) => lib.type === SaveLibraryTypeEnum.ASSET_MANAGER)?.id ?? + "assetManagerLibrary"; + return `${libName}.getAsset(${param})`; + } + + private getInitFunction(name: string, type: SaveLibraryTypeEnum | string): string { + const baseName = LIBS_FUNCTIONS_NAME[type]; + if (baseName) return `app.${baseName}(${name});`; + return `app.use(Symbol("${type}"), ${name});`; + } + + private writeLine(text: string, indentation = true): void { + const line = `${" ".repeat(indentation ? this.indentation * 2 : 0)}${text}\n`; + this.buffer += line; + } + + private endSection(): void { + this.writeLine(""); + } +} diff --git a/libs/schematics/src/utils/main/save.type.ts b/libs/schematics/src/utils/main/save.type.ts new file mode 100644 index 0000000..36372a3 --- /dev/null +++ b/libs/schematics/src/utils/main/save.type.ts @@ -0,0 +1,38 @@ +export enum SaveLibraryTypeEnum { + COMPONENT_SYSTEM = "component-system", + GRAPHICS = "graphics", + ASSET_MANAGER = "asset-manager", + NETWORK = "network", + INPUT = "input", + SOUND = "sound", +} + +export interface SaveLibrary { + id: string; + type: SaveLibraryTypeEnum | string; + name: string; + path: string; +} + +export interface SaveComponent { + name: string; + path: string; + paramsNames: string[]; +} + +export interface SaveSystem { + name: string; + path: string; +} + +export interface SaveEntity { + id: string; + components: Record>; +} + +export interface Save { + libraries: SaveLibrary[]; + components: SaveComponent[]; + systems: SaveSystem[]; + entities: SaveEntity[]; +} 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/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..df494a8 --- /dev/null +++ b/libs/schematics/tsdown.config.ts @@ -0,0 +1,44 @@ +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("application"), + createLibTsdownConfig("configuration"), + createLibTsdownConfig("part-base"), + createLibTsdownConfig("part-main"), + createLibTsdownConfig("docker"), + 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 ad4fafa..834fb62 100644 --- a/package.json +++ b/package.json @@ -34,16 +34,23 @@ }, "funding": "https://github.com/NanoForge-dev/CLI?sponsor", "scripts": { - "build": "turbo run build --concurrency=5 --filter='!@nanoforge-dev/cli' && tsc --noEmit && tsdown", - "build:dev": "turbo run build --concurrency=5 --filter='!@nanoforge-dev/cli' && tsc --noEmit && NODE_ENV=development tsdown", + "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", - "lint": "prettier --check . && eslint --format=pretty src && turbo run lint --concurrency=5 --filter='!@nanoforge-dev/cli'", - "format": "prettier --write . && eslint --fix --format=pretty src && turbo run format --concurrency=5 --filter='!@nanoforge-dev/cli'", + "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 '.'", "release": "cliff-jumper", "test": "pnpm run test:unit && pnpm run test:e2e", - "test:unit": "vitest run -c vitest.config.ts && turbo run test:unit --concurrency=5 --filter='!@nanoforge-dev/cli'", + "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", @@ -56,7 +63,7 @@ "@inquirer/prompts": "catalog:cli", "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d810bb3..420850e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,8 +107,11 @@ settings: catalogs: build: + cpx2: + specifier: ^9.0.0 + version: 9.0.0 tsdown: - specifier: ^0.22.3 + specifier: ^0.22.14 version: 0.22.14 turbo: specifier: ^2.10.12 @@ -121,10 +124,10 @@ catalogs: version: 0.3.1 ci: '@commitlint/cli': - specifier: ^21.2.0 + specifier: ^21.2.2 version: 21.2.2 '@commitlint/config-conventional': - specifier: ^21.2.0 + specifier: ^21.2.2 version: 21.2.2 '@favware/cliff-jumper': specifier: ^6.1.0 @@ -136,11 +139,11 @@ catalogs: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^17.0.8 + specifier: ^17.4.1 version: 17.4.1 cli: '@inquirer/prompts': - specifier: ^8.5.2 + specifier: ^8.7.0 version: 8.7.0 '@types/inquirer': specifier: ^9.0.10 @@ -161,11 +164,14 @@ catalogs: '@nanoforge-dev/editor': specifier: ^0.1.4 version: 0.1.4 + '@types/bun': + specifier: ^1.3.14 + version: 1.4.0 '@types/node': - specifier: ^26.1.0 + specifier: ^26.4.0 version: 26.4.0 bun: - specifier: ^1.3.14 + specifier: ^1.4.0 version: 1.4.0 libs: chokidar: @@ -181,7 +187,7 @@ catalogs: specifier: ^17.4.2 version: 17.4.2 open: - specifier: ^11.0.0 + specifier: ^11.0.2 version: 11.0.2 rc9: specifier: ^3.0.1 @@ -200,34 +206,37 @@ catalogs: specifier: ^6.0.2 version: 6.0.2 eslint: - specifier: ^10.6.0 + specifier: ^10.9.1 version: 10.9.1 prettier: - specifier: ^3.9.4 + specifier: ^3.9.6 version: 3.9.6 loader: '@nanoforge-dev/loader-client': - specifier: ^1.4.0 + specifier: ^1.5.0 version: 1.5.0 '@nanoforge-dev/loader-server': - specifier: ^1.4.0 + specifier: ^1.5.0 version: 1.5.0 schematics: + '@angular-devkit/core': + specifier: ^22.0.4 + version: 22.1.6 '@angular-devkit/schematics': - specifier: ^22.0.5 + specifier: ^22.1.6 version: 22.1.6 '@angular-devkit/schematics-cli': - specifier: ^22.0.5 + specifier: ^22.1.6 version: 22.1.6 - '@nanoforge-dev/schematics': - specifier: ^2.2.1 - version: 2.2.1 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 test: '@vitest/coverage-v8': - specifier: ^4.1.10 + specifier: ^4.1.11 version: 4.1.11 vitest: - specifier: ^4.1.10 + specifier: ^4.1.11 version: 4.1.11 importers: @@ -250,8 +259,8 @@ importers: specifier: catalog:loader version: 1.5.0 '@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 @@ -381,6 +390,171 @@ importers: specifier: catalog:test version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + libs/loader/client: + dependencies: + '@nanoforge-dev/loader-website': + specifier: workspace:* + version: link:../website + bun: + specifier: catalog:core + version: 1.4.0 + commander: + specifier: catalog:cli + version: 15.0.0 + 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) + '@types/bun': + specifier: catalog:core + version: 1.4.0 + eslint: + specifier: catalog:lint + version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + prettier: + specifier: catalog:lint + version: 3.9.6 + typescript: + specifier: catalog:build + version: 6.0.3 + + libs/loader/server: + dependencies: + commander: + specifier: catalog:cli + version: 15.0.0 + 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) + bun: + specifier: catalog:core + version: 1.4.0 + eslint: + specifier: catalog:lint + version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + prettier: + specifier: catalog:lint + version: 3.9.6 + typescript: + specifier: catalog:build + version: 6.0.3 + + libs/loader/website: + 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) + '@types/bun': + specifier: catalog:core + version: 1.4.0 + bun: + specifier: catalog:core + version: 1.4.0 + eslint: + specifier: catalog:lint + version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + prettier: + specifier: catalog:lint + version: 3.9.6 + typescript: + specifier: catalog:build + version: 6.0.3 + + libs/schematics: + dependencies: + '@angular-devkit/core': + specifier: catalog:schematics + version: 22.1.6(chokidar@5.0.0) + '@angular-devkit/schematics': + specifier: catalog:schematics + version: 22.1.6(chokidar@5.0.0) + rxjs: + specifier: catalog:schematics + version: 7.8.2 + devDependencies: + '@angular-devkit/schematics-cli': + specifier: catalog:schematics + version: 22.1.6(@types/node@26.4.0)(chokidar@5.0.0) + '@commitlint/cli': + specifier: catalog:ci + version: 21.2.2(@types/node@26.4.0)(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.2 + '@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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) + '@types/node': + specifier: catalog:core + version: 26.4.0 + '@vitest/coverage-v8': + specifier: catalog:test + version: 4.1.11(vitest@4.1.11) + cpx2: + specifier: catalog:build + version: 9.0.0 + eslint: + specifier: catalog:lint + version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + husky: + specifier: catalog:ci + version: 9.1.7 + lint-staged: + specifier: catalog:ci + version: 17.4.1 + prettier: + specifier: catalog:lint + version: 3.9.6 + tsdown: + specifier: catalog:build + version: 0.22.14(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: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + packages: '@actions/core@3.0.1': @@ -809,10 +983,6 @@ packages: resolution: {integrity: sha512-X37wlYyoL/q8ZPk5I8W3z01novfDSopizVQXbk/sw0dRXoONtIb6xnZLlN3z4jkDkPOy6IVes0bLGr8h4aFOcA==} engines: {node: '25'} - '@nanoforge-dev/schematics@2.2.1': - resolution: {integrity: sha512-MdmhFncakgejGRIf1HTPKTskhdv8bIst21uWRSZCXGgGv0UNYEfDiziOsWPVCzGc9dXei59RIDznBwgpAMeXLg==} - engines: {node: '25'} - '@nanoforge-dev/utils-eslint-config@1.0.2': resolution: {integrity: sha512-hIsVGGpVIW3RvPZocWEf15Y5eqmIxTtLfWI66wqx+AO6PHcatUBhEi4sk1i54Qb2yGFueRBS/dSXj0kxG4Rbcw==} engines: {node: '25'} @@ -825,16 +995,16 @@ packages: resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} engines: {node: '>= 20'} - '@octokit/core@7.0.7': - resolution: {integrity: sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==} + '@octokit/core@7.0.8': + resolution: {integrity: sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.4': - resolution: {integrity: sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==} + '@octokit/endpoint@11.0.5': + resolution: {integrity: sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==} engines: {node: '>= 20'} - '@octokit/graphql@9.0.4': - resolution: {integrity: sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==} + '@octokit/graphql@9.0.5': + resolution: {integrity: sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==} engines: {node: '>= 20'} '@octokit/openapi-types@27.0.0': @@ -843,6 +1013,9 @@ packages: '@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'} @@ -861,12 +1034,12 @@ packages: peerDependencies: '@octokit/core': '>=7' - '@octokit/request-error@7.1.1': - resolution: {integrity: sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==} + '@octokit/request-error@7.1.2': + resolution: {integrity: sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==} engines: {node: '>= 20'} - '@octokit/request@10.0.15': - resolution: {integrity: sha512-3CBg9aJ0hO9Pjyij8LbK/xYtEaPws9SW7xKz67daPNxQB1q5Y9OMA7DDOG0A6Hwf9ygGu3tvzusg0LXQ8/wAjA==} + '@octokit/request@10.0.16': + resolution: {integrity: sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==} engines: {node: '>= 20'} '@octokit/types@16.0.0': @@ -875,6 +1048,9 @@ packages: '@octokit/types@17.0.0': resolution: {integrity: sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==} + '@octokit/types@18.0.0': + resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} + '@oven/bun-darwin-aarch64@1.4.0': resolution: {integrity: sha512-GCpf8QuFLsyioVawP5HrMxA1ZRBlu6Hq9RNnSc3UTUWAzIxBso9trjoZczw1HdgpqSssFkszfIV2zmOzFTjhkw==} cpu: [arm64] @@ -1257,6 +1433,9 @@ packages: cpu: [arm64] os: [win32] + '@types/bun@1.4.0': + resolution: {integrity: sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1287,63 +1466,63 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - '@typescript-eslint/eslint-plugin@8.68.0': - resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} + '@typescript-eslint/eslint-plugin@8.69.0': + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.68.0 + '@typescript-eslint/parser': ^8.69.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.68.0': - resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} + '@typescript-eslint/parser@8.69.0': + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} 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.68.0': - resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + '@typescript-eslint/project-service@8.69.0': + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.68.0': - resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + '@typescript-eslint/scope-manager@8.69.0': + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.68.0': - resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} + '@typescript-eslint/tsconfig-utils@8.69.0': + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.68.0': - resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} + '@typescript-eslint/type-utils@8.69.0': + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} 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.68.0': - resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + '@typescript-eslint/types@8.69.0': + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.68.0': - resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} + '@typescript-eslint/typescript-estree@8.69.0': + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.68.0': - resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} + '@typescript-eslint/utils@8.69.0': + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} 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.68.0': - resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + '@typescript-eslint/visitor-keys@8.69.0': + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@4.1.11': @@ -1597,6 +1776,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bun-types@1.4.0: + resolution: {integrity: sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q==} + bun@1.4.0: resolution: {integrity: sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw==} cpu: [arm64, x64] @@ -1726,10 +1908,19 @@ packages: typescript: optional: true + cpx2@9.0.0: + resolution: {integrity: sha512-KRZGQCmHrM10xfLjrCBTQHpEIJgKtZyOOO5fygu9KE00IjQOL76FHjeON62R5PWS/W2J5ofgRJKF8rpUlEuLPQ==} + 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'} @@ -1950,6 +2141,9 @@ packages: 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'} @@ -2021,6 +2215,14 @@ packages: 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'} @@ -2060,8 +2262,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -2294,6 +2496,10 @@ packages: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2327,6 +2533,9 @@ packages: 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'} @@ -2394,6 +2603,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@7.0.7: + resolution: {integrity: sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==} + engines: {node: '>=18'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2424,6 +2637,10 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2558,6 +2775,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==} @@ -2611,6 +2832,9 @@ 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'} @@ -2713,8 +2937,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.68.0: - resolution: {integrity: sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==} + typescript-eslint@8.69.0: + resolution: {integrity: sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2925,11 +3149,11 @@ snapshots: '@actions/github@9.1.1': dependencies: '@actions/http-client': 3.0.2 - '@octokit/core': 7.0.7 - '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.7) - '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.7) - '@octokit/request': 10.0.15 - '@octokit/request-error': 7.1.1 + '@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.0 '@actions/http-client@3.0.2': @@ -3039,6 +3263,22 @@ snapshots: - conventional-commits-parser - typescript + '@commitlint/cli@21.2.2(@types/node@26.4.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + dependencies: + '@commitlint/config-conventional': 21.2.2 + '@commitlint/format': 21.2.2 + '@commitlint/lint': 21.2.2 + '@commitlint/load': 21.2.2(@types/node@26.4.0)(typescript@6.0.3) + '@commitlint/read': 21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2) + '@commitlint/types': 21.2.0 + tinyexec: 1.3.0 + yargs: 18.1.0 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + '@commitlint/config-conventional@21.2.2': dependencies: '@commitlint/types': 21.2.0 @@ -3106,6 +3346,16 @@ snapshots: - conventional-commits-filter - conventional-commits-parser + '@commitlint/read@21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)': + dependencies: + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.0 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2) + tinyexec: 1.3.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + '@commitlint/resolve-extends@21.2.2': dependencies: '@commitlint/config-validator': 21.2.0 @@ -3150,6 +3400,15 @@ snapshots: 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@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': {} @@ -3194,8 +3453,8 @@ snapshots: dependencies: '@favware/colorette-spinner': 1.0.1 '@octokit/auth-token': 6.0.0 - '@octokit/core': 7.0.7 - '@octokit/plugin-retry': 8.1.1(@octokit/core@7.0.7) + '@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 @@ -3410,14 +3669,6 @@ snapshots: '@nanoforge-dev/loader-website@1.5.0': {} - '@nanoforge-dev/schematics@2.2.1(chokidar@5.0.0)': - dependencies: - '@angular-devkit/core': 22.1.6(chokidar@5.0.0) - '@angular-devkit/schematics': 22.1.6(chokidar@5.0.0) - rxjs: 7.8.2 - transitivePeerDependencies: - - chokidar - '@nanoforge-dev/utils-eslint-config@1.0.2(@types/eslint@9.6.1)(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@eslint/js': 9.39.5 @@ -3427,7 +3678,7 @@ snapshots: eslint-plugin-format: 1.5.0(eslint@10.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6) globals: 16.5.0 - typescript-eslint: 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + typescript-eslint: 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) transitivePeerDependencies: - '@types/eslint' - eslint @@ -3439,57 +3690,59 @@ snapshots: '@octokit/auth-token@6.0.0': {} - '@octokit/core@7.0.7': + '@octokit/core@7.0.8': dependencies: '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.4 - '@octokit/request': 10.0.15 - '@octokit/request-error': 7.1.1 - '@octokit/types': 17.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.4': + '@octokit/endpoint@11.0.5': dependencies: - '@octokit/types': 17.0.0 + '@octokit/types': 18.0.0 universal-user-agent: 7.0.3 - '@octokit/graphql@9.0.4': + '@octokit/graphql@9.0.5': dependencies: - '@octokit/request': 10.0.15 - '@octokit/types': 17.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/openapi-types@28.0.0': {} - '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.7)': + '@octokit/openapi-types@29.0.1': {} + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.8)': dependencies: - '@octokit/core': 7.0.7 + '@octokit/core': 7.0.8 '@octokit/types': 16.0.0 - '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.7)': + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.8)': dependencies: - '@octokit/core': 7.0.7 + '@octokit/core': 7.0.8 '@octokit/types': 16.0.0 - '@octokit/plugin-retry@8.1.1(@octokit/core@7.0.7)': + '@octokit/plugin-retry@8.1.1(@octokit/core@7.0.8)': dependencies: - '@octokit/core': 7.0.7 - '@octokit/request-error': 7.1.1 + '@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.1': + '@octokit/request-error@7.1.2': dependencies: - '@octokit/types': 17.0.0 + '@octokit/types': 18.0.0 - '@octokit/request@10.0.15': + '@octokit/request@10.0.16': dependencies: - '@octokit/endpoint': 11.0.4 - '@octokit/request-error': 7.1.1 - '@octokit/types': 17.0.0 + '@octokit/endpoint': 11.0.5 + '@octokit/request-error': 7.1.2 + '@octokit/types': 18.0.0 content-type: 3.0.0 json-with-bigint: 3.5.12 universal-user-agent: 7.0.3 @@ -3502,6 +3755,10 @@ snapshots: dependencies: '@octokit/openapi-types': 28.0.0 + '@octokit/types@18.0.0': + dependencies: + '@octokit/openapi-types': 29.0.1 + '@oven/bun-darwin-aarch64@1.4.0': optional: true @@ -3706,6 +3963,10 @@ snapshots: '@turbo/windows-arm64@2.10.12': optional: true + '@types/bun@1.4.0': + dependencies: + bun-types: 1.4.0 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -3739,57 +4000,57 @@ snapshots: '@types/validator@13.15.10': {} - '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(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.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/type-utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/parser': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/type-utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) - ignore: 7.0.6 + ignore: 7.0.8 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.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3(supports-color@10.2.2) eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.68.0(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.69.0(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 debug: 4.4.3(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.68.0': + '@typescript-eslint/scope-manager@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 - '@typescript-eslint/tsconfig-utils@8.68.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -3797,14 +4058,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.68.0': {} + '@typescript-eslint/types@8.69.0': {} - '@typescript-eslint/typescript-estree@8.68.0(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.69.0(supports-color@10.2.2)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/project-service': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.6 semver: 7.8.5 @@ -3814,20 +4075,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/utils@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.68.0': + '@typescript-eslint/visitor-keys@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': @@ -4026,6 +4287,10 @@ snapshots: fill-range: 7.1.1 optional: true + bun-types@1.4.0: + dependencies: + '@types/node': 26.4.0 + bun@1.4.0: optionalDependencies: '@oven/bun-darwin-aarch64': 1.4.0 @@ -4142,12 +4407,25 @@ snapshots: optionalDependencies: typescript: 6.0.3 + cpx2@9.0.0: + dependencies: + debounce: 3.0.0 + glob: 13.0.6 + glob2base: 0.0.12 + ignore: 7.0.8 + minimatch: 10.2.6 + p-map: 7.0.7 + 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 + debounce@3.0.0: {} + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 @@ -4368,6 +4646,8 @@ snapshots: to-regex-range: 5.0.1 optional: true + find-index@0.1.1: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -4429,6 +4709,16 @@ snapshots: 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 @@ -4453,7 +4743,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.6: {} + ignore@7.0.8: {} import-fresh@3.3.1: dependencies: @@ -4623,6 +4913,8 @@ snapshots: is-unicode-supported: 2.1.0 yoctocolors: 2.2.0 + lru-cache@11.5.2: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -4659,8 +4951,9 @@ snapshots: dependencies: brace-expansion: 2.1.4 - minipass@7.1.3: - optional: true + minimist@1.2.8: {} + + minipass@7.1.3: {} minizlib@3.1.0: dependencies: @@ -4756,6 +5049,8 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@7.0.7: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4781,6 +5076,11 @@ snapshots: path-key@4.0.0: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -4895,6 +5195,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.10.0: {} + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -4934,6 +5236,10 @@ snapshots: strip-final-newline@4.0.0: {} + subarg@1.0.0: + dependencies: + minimist: 1.2.8 + supports-color@10.2.2: {} supports-color@7.2.0: @@ -5025,12 +5331,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3): + typescript-eslint@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/parser': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.68.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/parser': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) typescript: 6.0.3 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0c147bb..110a13b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,26 +1,27 @@ packages: - . - libs/* -# - libs/loader/* + - libs/loader/* allowBuilds: bun: true catalogs: build: - tsdown: ^0.22.3 + cpx2: ^9.0.0 + tsdown: ^0.22.14 turbo: ^2.10.12 typescript: ^6.0.3 unrun: ^0.3.0 ci: - '@commitlint/cli': ^21.2.0 - '@commitlint/config-conventional': ^21.2.0 + '@commitlint/cli': ^21.2.2 + '@commitlint/config-conventional': ^21.2.2 '@favware/cliff-jumper': ^6.1.0 '@nanoforge-dev/actions': ^2.1.5 husky: ^9.1.7 - lint-staged: ^17.0.8 + lint-staged: ^17.4.1 cli: - '@inquirer/prompts': ^8.5.2 + '@inquirer/prompts': ^8.7.0 '@types/inquirer': ^9.0.10 ansis: ^4.3.1 commander: ^15.0.0 @@ -28,29 +29,31 @@ catalogs: 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.4.0 + bun: ^1.4.0 libs: chokidar: ^5.0.0 class-transformer: ^0.5.1 class-validator: ^0.15.1 dotenv: ^17.4.2 - open: ^11.0.0 + open: ^11.0.2 rc9: ^3.0.1 reflect-metadata: ^0.2.2 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.9.1 + prettier: ^3.9.6 loader: - '@nanoforge-dev/loader-client': ^1.4.0 - '@nanoforge-dev/loader-server': ^1.4.0 + '@nanoforge-dev/loader-client': ^1.5.0 + '@nanoforge-dev/loader-server': ^1.5.0 schematics: - '@angular-devkit/schematics': ^22.0.5 - '@angular-devkit/schematics-cli': ^22.0.5 - '@nanoforge-dev/schematics': ^2.2.1 + '@angular-devkit/core': ^22.0.4 + '@angular-devkit/schematics': ^22.1.6 + '@angular-devkit/schematics-cli': ^22.1.6 + rxjs: ^7.8.2 test: - '@vitest/coverage-v8': ^4.1.10 - vitest: ^4.1.10 + '@vitest/coverage-v8': ^4.1.11 + vitest: ^4.1.11 diff --git a/tsdown.config.ts b/tsdown.config.ts index ba29b93..073c2e3 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -21,9 +21,9 @@ function createTsdownConfig({ platform: "node", target: "esnext", treeshake: false, - deps: { - neverBundle: 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 index c44a392..42e96c0 100644 --- a/turbo.json +++ b/turbo.json @@ -11,6 +11,12 @@ "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": ["**"], @@ -25,7 +31,27 @@ }, "test:unit": { "dependsOn": ["^test:unit"], - "inputs": ["src/**", "test/**", "package.json", "tsconfig.json", "tsdown.config.ts"], + "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" }, From 406842e5345a9677954c49c64dadda6e81b45f1b Mon Sep 17 00:00:00 2001 From: Exelo Date: Sat, 12 Sep 2026 23:31:08 +0900 Subject: [PATCH 03/15] feat: add config parser (#214) * feat: add config parser * chore: change package.json fields order --- libs/config/README.md | 24 ++++++ libs/config/package.json | 5 +- libs/config/src/index.ts | 3 + libs/config/src/parse/config-error.ts | 14 +++ libs/config/src/parse/load-config-module.ts | 38 +++++++++ libs/config/src/parse/parse-config.ts | 10 +++ libs/config/src/parse/validate-config.ts | 85 +++++++++++++++++++ libs/config/test/parse-config.spec.ts | 49 +++++++++++ .../parse/fixtures/invalid-type-config.ts | 3 + .../test/parse/fixtures/no-default-export.ts | 3 + .../test/parse/fixtures/throwing-config.ts | 1 + .../test/parse/fixtures/valid-config.js | 4 + .../test/parse/fixtures/valid-config.ts | 4 + pnpm-workspace.yaml | 4 +- 14 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 libs/config/src/parse/config-error.ts create mode 100644 libs/config/src/parse/load-config-module.ts create mode 100644 libs/config/src/parse/parse-config.ts create mode 100644 libs/config/src/parse/validate-config.ts create mode 100644 libs/config/test/parse-config.spec.ts create mode 100644 libs/config/test/parse/fixtures/invalid-type-config.ts create mode 100644 libs/config/test/parse/fixtures/no-default-export.ts create mode 100644 libs/config/test/parse/fixtures/throwing-config.ts create mode 100644 libs/config/test/parse/fixtures/valid-config.js create mode 100644 libs/config/test/parse/fixtures/valid-config.ts diff --git a/libs/config/README.md b/libs/config/README.md index c38cd9a..05cc5c3 100644 --- a/libs/config/README.md +++ b/libs/config/README.md @@ -62,6 +62,30 @@ const resolved = resolveConfig({ type: "client", dir: { assets: "static" } }); 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] diff --git a/libs/config/package.json b/libs/config/package.json index fcb2007..25a6b79 100644 --- a/libs/config/package.json +++ b/libs/config/package.json @@ -57,6 +57,10 @@ "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", @@ -66,7 +70,6 @@ "prettier": "catalog:lint", "tsdown": "catalog:build", "typescript": "catalog:build", - "unrun": "catalog:build", "vitest": "catalog:test" }, "packageManager": "pnpm@12.0.0", diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index 83547cd..c4d0991 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -5,6 +5,9 @@ export { 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, diff --git a/libs/config/src/parse/config-error.ts b/libs/config/src/parse/config-error.ts new file mode 100644 index 0000000..d97e854 --- /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); + 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..d3ca6b2 --- /dev/null +++ b/libs/config/src/parse/parse-config.ts @@ -0,0 +1,10 @@ +import { resolveConfig } from "../merge/resolve-config"; +import type { NanoforgeConfig } from "../types"; +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..c504586 --- /dev/null +++ b/libs/config/src/parse/validate-config.ts @@ -0,0 +1,85 @@ +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 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, +}); + +const serverConfigSchema = z.object({ + type: z.literal("server"), + ...sourceableConfigShape, + ...buildableConfigShape, + ...containLibConfigShape, +}); + +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/test/parse-config.spec.ts b/libs/config/test/parse-config.spec.ts new file mode 100644 index 0000000..fd5fb6e --- /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.cause).toBeInstanceOf(Error); + }); + + 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/pnpm-workspace.yaml b/pnpm-workspace.yaml index 110a13b..10feea7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,6 @@ packages: - . - libs/* - - libs/loader/* allowBuilds: bun: true @@ -12,7 +11,6 @@ catalogs: tsdown: ^0.22.14 turbo: ^2.10.12 typescript: ^6.0.3 - unrun: ^0.3.0 ci: '@commitlint/cli': ^21.2.2 '@commitlint/config-conventional': ^21.2.2 @@ -40,6 +38,8 @@ catalogs: open: ^11.0.2 rc9: ^3.0.1 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 From 9ffb07c62304518d12913b16a4938c012024565f Mon Sep 17 00:00:00 2001 From: Exelo Date: Sun, 13 Sep 2026 22:31:28 +0900 Subject: [PATCH 04/15] feat: add new project and workspace schematics and remove old ones (#216) * feat: add new project and workspace schematics and remove old ones * fix: remove comments in generated code and add name to project --- docs/docs/schematics/1-overview.mdx | 26 +- .../schematics/2-schematics/1-application.mdx | 80 --- .../schematics/2-schematics/1-workspace.mdx | 69 +++ .../2-schematics/2-configuration.mdx | 50 -- .../schematics/2-schematics/2-project.mdx | 98 ++++ .../{6-component.mdx => 3-component.mdx} | 0 .../schematics/2-schematics/3-part-base.mdx | 115 ----- .../schematics/2-schematics/4-part-main.mdx | 77 --- .../{7-system.mdx => 4-system.mdx} | 0 .../docs/schematics/2-schematics/5-docker.mdx | 70 --- docs/guides/cli/1-overview.mdx | 2 +- e2e/helpers/write-project-config.ts | 36 ++ libs/schematics/README.md | 7 +- libs/schematics/e2e/application.test.ts | 170 ------- libs/schematics/e2e/configuration.test.ts | 108 ---- libs/schematics/e2e/docker.test.ts | 45 -- libs/schematics/e2e/part-base.test.ts | 122 ----- libs/schematics/e2e/part-main.test.ts | 262 ---------- libs/schematics/e2e/project.test.ts | 477 ++++++++++++++++++ libs/schematics/e2e/workspace.test.ts | 265 ++++++++++ libs/schematics/src/collection.json | 31 +- libs/schematics/src/defaults.ts | 22 +- .../libs/application/application.factory.ts | 87 ---- .../libs/application/application.options.d.ts | 56 -- .../libs/application/application.schema.d.ts | 61 --- .../libs/application/files/js/.prettierignore | 8 - .../src/libs/application/files/js/README.md | 166 ------ .../application/files/js/eslint.config.js | 11 - .../files/js/package.json.template | 48 -- .../files/js/pnpm-workspace.yaml.template | 3 - .../application/files/js/prettier.config.js | 6 - .../libs/application/files/ts/.prettierignore | 8 - .../src/libs/application/files/ts/README.md | 166 ------ .../src/libs/application/files/ts/__dot__env | 6 - .../application/files/ts/eslint.config.js | 15 - .../files/ts/package.json.template | 52 -- .../files/ts/pnpm-workspace.yaml.template | 3 - .../application/files/ts/prettier.config.js | 6 - .../src/libs/application/schema.json | 70 --- .../configuration/configuration.factory.ts | 94 ---- .../configuration/configuration.options.d.ts | 1 - .../configuration/configuration.schema.d.ts | 26 - .../configuration/files/nanoforge.config.json | 1 - .../src/libs/configuration/schema.json | 34 -- .../src/libs/docker/docker.factory.ts | 36 -- .../src/libs/docker/docker.options.d.ts | 6 - .../src/libs/docker/docker.schema.d.ts | 11 - .../src/libs/docker/files/.dockerignore | 1 - .../src/libs/docker/files/Dockerfile | 25 - libs/schematics/src/libs/docker/schema.json | 19 - .../js/__nanoforgeFolder__/client.save.json | 77 --- .../js/__nanoforgeFolder__/server.save.json | 49 -- .../__part__/components/example.component.js | 72 --- .../files/js/__part__/init/after-init.js | 12 - .../js/__part__/init/after-registry-init.js | 14 - .../files/js/__part__/init/after-run.js | 12 - .../files/js/__part__/init/before-init.js | 13 - .../js/__part__/init/before-registry-init.js | 14 - .../files/js/__part__/init/before-run.js | 12 - .../js/__part__/systems/example.system.js | 44 -- .../ts/__nanoforgeFolder__/client.save.json | 77 --- .../ts/__nanoforgeFolder__/server.save.json | 49 -- .../__part__/components/example.component.ts | 55 -- .../files/ts/__part__/init/after-init.ts | 5 - .../ts/__part__/init/after-registry-init.ts | 6 - .../files/ts/__part__/init/after-run.ts | 5 - .../files/ts/__part__/init/before-init.ts | 5 - .../ts/__part__/init/before-registry-init.ts | 6 - .../files/ts/__part__/init/before-run.ts | 5 - .../ts/__part__/systems/example.system.ts | 31 -- .../src/libs/part-base/part-base.factory.ts | 51 -- .../src/libs/part-base/part-base.options.d.ts | 21 - .../src/libs/part-base/part-base.schema.d.ts | 26 - .../schematics/src/libs/part-base/schema.json | 35 -- .../src/libs/part-main/part-main.factory.ts | 84 --- .../src/libs/part-main/part-main.options.d.ts | 1 - .../src/libs/part-main/part-main.schema.d.ts | 36 -- .../schematics/src/libs/part-main/schema.json | 43 -- .../src/libs/project/files/js/.dockerignore | 5 + .../src/libs/project/files/js/Dockerfile | 39 ++ .../src/libs/project/files/js/README.md | 53 ++ .../project/files/js/assets/__dot__gitkeep | 0 .../src/libs/project/files/js/jsconfig.json | 6 + .../libs/project/files/js/nanoforge.config.js | 6 + .../project/files/js/package.json.template | 21 + .../files/js/pnpm-workspace.yaml.template | 3 + .../drawable-circle-2d.component.js | 404 +++++++++++++++ .../src/components/position-2d.component.js | 42 ++ .../src/libs/project/files/js/src/main.js | 43 ++ .../files/js/src/systems/draw-2d.system.js | 33 ++ .../files/js/src/systems/move-2d.system.js | 34 ++ .../js/src/systems/position-sync.system.js | 35 ++ .../src/libs/project/files/ts/.dockerignore | 5 + .../src/libs/project/files/ts/Dockerfile | 39 ++ .../src/libs/project/files/ts/README.md | 53 ++ .../files/js => project/files/ts}/__dot__env | 0 .../js => project/files/ts}/__dot__gitignore | 0 .../project/files/ts/assets/__dot__gitkeep | 0 .../libs/project/files/ts/nanoforge.config.ts | 5 + .../project/files/ts/package.json.template | 21 + .../files/ts/pnpm-workspace.yaml.template | 3 + .../drawable-circle-2d.component.ts | 367 ++++++++++++++ .../src/components/position-2d.component.ts | 35 ++ .../src/libs/project/files/ts/src/main.ts | 39 ++ .../files/ts/src/systems/draw-2d.system.ts | 25 + .../files/ts/src/systems/move-2d.system.ts | 26 + .../ts/src/systems/position-sync.system.ts | 27 + .../files/ts/tsconfig.json | 2 +- .../src/libs/project/project.factory.ts | 135 +++++ .../src/libs/project/project.options.d.ts | 92 ++++ .../src/libs/project/project.schema.d.ts | 87 ++++ libs/schematics/src/libs/project/schema.json | 83 +++ .../src/libs/workspace/files/js/.dockerignore | 5 + .../src/libs/workspace/files/js/Dockerfile | 39 ++ .../src/libs/workspace/files/js/README.md | 51 ++ .../libs/workspace/files/js/__dot__gitignore | 137 +++++ .../files/js/jsconfig.json | 2 +- .../workspace/files/js/nanoforge.config.js | 6 + .../workspace/files/js/package.json.template | 19 + .../files/js/pnpm-workspace.yaml.template | 6 + .../src/libs/workspace/files/ts/.dockerignore | 5 + .../src/libs/workspace/files/ts/Dockerfile | 39 ++ .../src/libs/workspace/files/ts/README.md | 51 ++ .../files/ts/__dot__gitignore | 6 +- .../workspace/files/ts/nanoforge.config.ts | 6 + .../workspace/files/ts/package.json.template | 20 + .../files/ts/pnpm-workspace.yaml.template | 6 + .../src/libs/workspace/files/ts/tsconfig.json | 40 ++ .../schematics/src/libs/workspace/schema.json | 45 ++ .../src/libs/workspace/workspace.factory.ts | 84 +++ .../src/libs/workspace/workspace.options.d.ts | 44 ++ .../src/libs/workspace/workspace.schema.d.ts | 41 ++ .../utils/config/config.declarator.spec.ts | 38 -- .../src/utils/config/config.declarator.ts | 10 - .../src/utils/config/config.finder.spec.ts | 72 --- .../src/utils/config/config.finder.ts | 33 -- .../src/utils/config/config.type.ts | 31 -- libs/schematics/src/utils/main/const.ts | 10 - libs/schematics/src/utils/main/enums.ts | 8 - .../src/utils/main/main-functions.spec.ts | 135 ----- .../src/utils/main/main-functions.ts | 61 --- libs/schematics/src/utils/main/main-utils.ts | 7 - .../src/utils/main/main.generator.spec.ts | 222 -------- .../src/utils/main/main.generator.ts | 224 -------- libs/schematics/src/utils/main/save.type.ts | 38 -- libs/schematics/src/utils/registry.spec.ts | 106 ++++ libs/schematics/src/utils/registry.ts | 106 ++++ libs/schematics/tsdown.config.ts | 7 +- 148 files changed, 3612 insertions(+), 3934 deletions(-) delete mode 100644 docs/docs/schematics/2-schematics/1-application.mdx create mode 100644 docs/docs/schematics/2-schematics/1-workspace.mdx delete mode 100644 docs/docs/schematics/2-schematics/2-configuration.mdx create mode 100644 docs/docs/schematics/2-schematics/2-project.mdx rename docs/docs/schematics/2-schematics/{6-component.mdx => 3-component.mdx} (100%) delete mode 100644 docs/docs/schematics/2-schematics/3-part-base.mdx delete mode 100644 docs/docs/schematics/2-schematics/4-part-main.mdx rename docs/docs/schematics/2-schematics/{7-system.mdx => 4-system.mdx} (100%) delete mode 100644 docs/docs/schematics/2-schematics/5-docker.mdx create mode 100644 e2e/helpers/write-project-config.ts delete mode 100644 libs/schematics/e2e/application.test.ts delete mode 100644 libs/schematics/e2e/configuration.test.ts delete mode 100644 libs/schematics/e2e/docker.test.ts delete mode 100644 libs/schematics/e2e/part-base.test.ts delete mode 100644 libs/schematics/e2e/part-main.test.ts create mode 100644 libs/schematics/e2e/project.test.ts create mode 100644 libs/schematics/e2e/workspace.test.ts delete mode 100644 libs/schematics/src/libs/application/application.factory.ts delete mode 100644 libs/schematics/src/libs/application/application.options.d.ts delete mode 100644 libs/schematics/src/libs/application/application.schema.d.ts delete mode 100644 libs/schematics/src/libs/application/files/js/.prettierignore delete mode 100644 libs/schematics/src/libs/application/files/js/README.md delete mode 100644 libs/schematics/src/libs/application/files/js/eslint.config.js delete mode 100644 libs/schematics/src/libs/application/files/js/package.json.template delete mode 100644 libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template delete mode 100644 libs/schematics/src/libs/application/files/js/prettier.config.js delete mode 100644 libs/schematics/src/libs/application/files/ts/.prettierignore delete mode 100644 libs/schematics/src/libs/application/files/ts/README.md delete mode 100644 libs/schematics/src/libs/application/files/ts/__dot__env delete mode 100644 libs/schematics/src/libs/application/files/ts/eslint.config.js delete mode 100644 libs/schematics/src/libs/application/files/ts/package.json.template delete mode 100644 libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template delete mode 100644 libs/schematics/src/libs/application/files/ts/prettier.config.js delete mode 100644 libs/schematics/src/libs/application/schema.json delete mode 100644 libs/schematics/src/libs/configuration/configuration.factory.ts delete mode 100644 libs/schematics/src/libs/configuration/configuration.options.d.ts delete mode 100644 libs/schematics/src/libs/configuration/configuration.schema.d.ts delete mode 100644 libs/schematics/src/libs/configuration/files/nanoforge.config.json delete mode 100644 libs/schematics/src/libs/configuration/schema.json delete mode 100644 libs/schematics/src/libs/docker/docker.factory.ts delete mode 100644 libs/schematics/src/libs/docker/docker.options.d.ts delete mode 100644 libs/schematics/src/libs/docker/docker.schema.d.ts delete mode 100644 libs/schematics/src/libs/docker/files/.dockerignore delete mode 100644 libs/schematics/src/libs/docker/files/Dockerfile delete mode 100644 libs/schematics/src/libs/docker/schema.json delete mode 100644 libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json delete mode 100644 libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js delete mode 100644 libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts delete mode 100644 libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts delete mode 100644 libs/schematics/src/libs/part-base/part-base.factory.ts delete mode 100644 libs/schematics/src/libs/part-base/part-base.options.d.ts delete mode 100644 libs/schematics/src/libs/part-base/part-base.schema.d.ts delete mode 100644 libs/schematics/src/libs/part-base/schema.json delete mode 100644 libs/schematics/src/libs/part-main/part-main.factory.ts delete mode 100644 libs/schematics/src/libs/part-main/part-main.options.d.ts delete mode 100644 libs/schematics/src/libs/part-main/part-main.schema.d.ts delete mode 100644 libs/schematics/src/libs/part-main/schema.json create mode 100644 libs/schematics/src/libs/project/files/js/.dockerignore create mode 100644 libs/schematics/src/libs/project/files/js/Dockerfile create mode 100644 libs/schematics/src/libs/project/files/js/README.md create mode 100644 libs/schematics/src/libs/project/files/js/assets/__dot__gitkeep create mode 100644 libs/schematics/src/libs/project/files/js/jsconfig.json create mode 100644 libs/schematics/src/libs/project/files/js/nanoforge.config.js create mode 100644 libs/schematics/src/libs/project/files/js/package.json.template create mode 100644 libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/project/files/js/src/components/drawable-circle-2d.component.js create mode 100644 libs/schematics/src/libs/project/files/js/src/components/position-2d.component.js create mode 100644 libs/schematics/src/libs/project/files/js/src/main.js create mode 100644 libs/schematics/src/libs/project/files/js/src/systems/draw-2d.system.js create mode 100644 libs/schematics/src/libs/project/files/js/src/systems/move-2d.system.js create mode 100644 libs/schematics/src/libs/project/files/js/src/systems/position-sync.system.js create mode 100644 libs/schematics/src/libs/project/files/ts/.dockerignore create mode 100644 libs/schematics/src/libs/project/files/ts/Dockerfile create mode 100644 libs/schematics/src/libs/project/files/ts/README.md rename libs/schematics/src/libs/{application/files/js => project/files/ts}/__dot__env (100%) rename libs/schematics/src/libs/{application/files/js => project/files/ts}/__dot__gitignore (100%) create mode 100644 libs/schematics/src/libs/project/files/ts/assets/__dot__gitkeep create mode 100644 libs/schematics/src/libs/project/files/ts/nanoforge.config.ts create mode 100644 libs/schematics/src/libs/project/files/ts/package.json.template create mode 100644 libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/project/files/ts/src/components/drawable-circle-2d.component.ts create mode 100644 libs/schematics/src/libs/project/files/ts/src/components/position-2d.component.ts create mode 100644 libs/schematics/src/libs/project/files/ts/src/main.ts create mode 100644 libs/schematics/src/libs/project/files/ts/src/systems/draw-2d.system.ts create mode 100644 libs/schematics/src/libs/project/files/ts/src/systems/move-2d.system.ts create mode 100644 libs/schematics/src/libs/project/files/ts/src/systems/position-sync.system.ts rename libs/schematics/src/libs/{application => project}/files/ts/tsconfig.json (96%) create mode 100644 libs/schematics/src/libs/project/project.factory.ts create mode 100644 libs/schematics/src/libs/project/project.options.d.ts create mode 100644 libs/schematics/src/libs/project/project.schema.d.ts create mode 100644 libs/schematics/src/libs/project/schema.json create mode 100644 libs/schematics/src/libs/workspace/files/js/.dockerignore create mode 100644 libs/schematics/src/libs/workspace/files/js/Dockerfile create mode 100644 libs/schematics/src/libs/workspace/files/js/README.md create mode 100644 libs/schematics/src/libs/workspace/files/js/__dot__gitignore rename libs/schematics/src/libs/{application => workspace}/files/js/jsconfig.json (95%) create mode 100644 libs/schematics/src/libs/workspace/files/js/nanoforge.config.js create mode 100644 libs/schematics/src/libs/workspace/files/js/package.json.template create mode 100644 libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/workspace/files/ts/.dockerignore create mode 100644 libs/schematics/src/libs/workspace/files/ts/Dockerfile create mode 100644 libs/schematics/src/libs/workspace/files/ts/README.md rename libs/schematics/src/libs/{application => workspace}/files/ts/__dot__gitignore (97%) create mode 100644 libs/schematics/src/libs/workspace/files/ts/nanoforge.config.ts create mode 100644 libs/schematics/src/libs/workspace/files/ts/package.json.template create mode 100644 libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template create mode 100644 libs/schematics/src/libs/workspace/files/ts/tsconfig.json create mode 100644 libs/schematics/src/libs/workspace/schema.json create mode 100644 libs/schematics/src/libs/workspace/workspace.factory.ts create mode 100644 libs/schematics/src/libs/workspace/workspace.options.d.ts create mode 100644 libs/schematics/src/libs/workspace/workspace.schema.d.ts delete mode 100644 libs/schematics/src/utils/config/config.declarator.spec.ts delete mode 100644 libs/schematics/src/utils/config/config.declarator.ts delete mode 100644 libs/schematics/src/utils/config/config.finder.spec.ts delete mode 100644 libs/schematics/src/utils/config/config.finder.ts delete mode 100644 libs/schematics/src/utils/config/config.type.ts delete mode 100644 libs/schematics/src/utils/main/const.ts delete mode 100644 libs/schematics/src/utils/main/enums.ts delete mode 100644 libs/schematics/src/utils/main/main-functions.spec.ts delete mode 100644 libs/schematics/src/utils/main/main-functions.ts delete mode 100644 libs/schematics/src/utils/main/main-utils.ts delete mode 100644 libs/schematics/src/utils/main/main.generator.spec.ts delete mode 100644 libs/schematics/src/utils/main/main.generator.ts delete mode 100644 libs/schematics/src/utils/main/save.type.ts create mode 100644 libs/schematics/src/utils/registry.spec.ts create mode 100644 libs/schematics/src/utils/registry.ts diff --git a/docs/docs/schematics/1-overview.mdx b/docs/docs/schematics/1-overview.mdx index a031fec..0f100bb 100644 --- a/docs/docs/schematics/1-overview.mdx +++ b/docs/docs/schematics/1-overview.mdx @@ -33,28 +33,18 @@ schematics @nanoforge-dev/schematics: [options] ## Available schematics - - Scaffold a complete NanoForge project with `package.json`, TypeScript or JavaScript config, - linting, and optional server setup. + + Scaffold the root of a NanoForge monorepo: `nanoforge.config.ts`, `package.json`, TypeScript or + JavaScript config, and linting. - - Generate a `nanoforge.config.json` configuration file consumed by the NanoForge CLI. + + Generate a client or server project under `apps/` inside a workspace, including its + `main.ts` entry point, example component/system, and optional Dockerfile. - - Create the base folder structure for a client or server part, including example component, - system, save file, and optional lifecycle hooks. - - - Generate the `main.ts` entry point for a client or server part by reading the `.nanoforge` save - file. - - - Generate a production-ready `Dockerfile` and `.dockerignore` tailored to your package manager. - - + 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. diff --git a/docs/docs/schematics/2-schematics/1-application.mdx b/docs/docs/schematics/2-schematics/1-application.mdx deleted file mode 100644 index 9554bc3..0000000 --- a/docs/docs/schematics/2-schematics/1-application.mdx +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: Application -description: Generate a complete NanoForge application scaffold ---- - -import { ToolTip } from "mintlify/components"; - -## Overview - -The `application` schematic creates a fully structured NanoForge project with all the base files needed to start development — `package.json`, linting configuration, TypeScript or JavaScript setup, and optionally `.env` for server-enabled apps. - -## Usage - -```bash -nf new -``` - -Or via Angular DevKit schematics directly: - -```bash -schematics @nanoforge-dev/schematics:application --name=my-app -``` - -## Options - -| Option | Type | Default | Description | -| ---------------- | ------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | `nanoforge-app` | The name of the application | -| `version` | `string` | `0.0.0` | Initial version in `package.json` | -| `author` | `string` | `""` | Author field in `package.json` | -| `description` | `string` | `""` | Description field in `package.json` | -| `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 | -| `lint` | `boolean` | `true` | Generate linting configuration files | -| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager to target in scripts and lockfile references | -| `server` | `boolean` | `false` | Configure a server for the application | -| `editor` | `boolean` | `false` | Include editor dependencies | -| `initFunctions` | `boolean` | `false` | Add init function lifecycle hooks | - -## Generated structure - -``` -my-app/ -├── package.json -├── tsconfig.json # TypeScript only -├── jsconfig.json # JavaScript only -├── eslint.config.js # if lint: true -├── prettier.config.js # if lint: true -├── .prettierignore # if lint: true -├── .env # if server: true -├── .gitignore -└── README.md -``` - -## Examples - -**Minimal TypeScript app:** - -```bash -schematics @nanoforge-dev/schematics:application --name=my-game -``` - -**JavaScript app with pnpm and server:** - -```bash -schematics @nanoforge-dev/schematics:application \ - --name=my-game \ - --language=js \ - --packageManager=pnpm \ - --server=true -``` - -**TypeScript app without linting:** - -```bash -schematics @nanoforge-dev/schematics:application \ - --name=my-game \ - --lint=false -``` 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-configuration.mdx b/docs/docs/schematics/2-schematics/2-configuration.mdx deleted file mode 100644 index 8514f09..0000000 --- a/docs/docs/schematics/2-schematics/2-configuration.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Configuration -description: Generate a NanoForge configuration file ---- - -import { Tooltip } from "mintlify/components"; - -## Overview - -The `configuration` schematic creates a `nanoforge.config.json` file at the root of your project. This file is the central configuration consumed by the NanoForge CLI for builds, starts, and other operations. - -## Usage - -```bash -schematics @nanoforge-dev/schematics:configuration -``` - -## Options - -| Option | Type | Default | Description | -| --------------- | -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | `nanoforge-app` | The name of the application | -| `directory` | `string` | `.` | Destination directory | -| `server` | `boolean` | `false` | Include server configuration | -| `language` | `"ts" \| "js"` | `ts` | Language used in the project | -| `initFunctions` | `boolean` | `false` | Reference init functions in configuration | - -## Generated file - -``` -nanoforge.config.json -``` - -The generated file is an empty JSON object `{}` that you populate with the fields the NanoForge CLI expects. Refer to the [CLI documentation](https://github.com/NanoForge-dev/CLI) for the full list of supported fields. - -## Examples - -**Generate configuration at the project root:** - -```bash -schematics @nanoforge-dev/schematics:configuration --name=my-game -``` - -**Generate configuration for a server-enabled project:** - -```bash -schematics @nanoforge-dev/schematics:configuration \ - --name=my-game \ - --server=true -``` 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..198e11f --- /dev/null +++ b/docs/docs/schematics/2-schematics/2-project.mdx @@ -0,0 +1,98 @@ +--- +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. + +## 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/6-component.mdx b/docs/docs/schematics/2-schematics/3-component.mdx similarity index 100% rename from docs/docs/schematics/2-schematics/6-component.mdx rename to docs/docs/schematics/2-schematics/3-component.mdx diff --git a/docs/docs/schematics/2-schematics/3-part-base.mdx b/docs/docs/schematics/2-schematics/3-part-base.mdx deleted file mode 100644 index eec16aa..0000000 --- a/docs/docs/schematics/2-schematics/3-part-base.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: Part Base -description: Generate the base structure for a NanoForge client or server part ---- - -import { Tooltip } from "mintlify/components"; - -## Overview - -The `part-base` schematic generates the foundational folder structure for a NanoForge **client** or **server** part. It creates example components and systems, the `.nanoforge` save file used to track ECS state, and optionally lifecycle init functions. - -## Usage - -```bash -schematics @nanoforge-dev/schematics:part-base --part=client -``` - -## Options - -| Option | Type | Default | Description | -| --------------- | ---------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `part` | `"client" \| "server"` | _(required)_ | The part of the application to generate | -| `directory` | `string` | `.` | Destination directory for the generated files | -| `language` | `"ts" \| "js"` | `ts` | Language to use for generated source files | -| `server` | `boolean` | `false` | Whether the application has a server | -| `initFunctions` | `boolean` | `false` | Add lifecycle init function hooks | - -## Generated structure - -``` -{directory}/ -├── {part}/ -│ ├── components/ -│ │ └── example.component.ts -│ ├── systems/ -│ │ └── example.system.ts -│ └── init/ # if initFunctions: true -│ ├── before-init.ts -│ ├── after-init.ts -│ ├── before-registry-init.ts -│ ├── after-registry-init.ts -│ ├── before-run.ts -│ └── after-run.ts -└── .nanoforge/ - └── {part}.save.json -``` - -## Save file - -The `.nanoforge/{part}.save.json` file is the source of truth for ECS state. It lists the libraries, components, systems, and entities registered for this part. The `part-main` schematic reads this file to generate the `main.ts` entry point. - -```json -{ - "libraries": [ ... ], - "components": [ - { - "name": "ExampleComponent", - "path": "./components/example.component.ts", - "paramsNames": ["paramA", "paramB", "paramC"] - } - ], - "systems": [ - { - "name": "exampleSystem", - "path": "./systems/example.system.ts" - } - ], - "entities": [ - { - "id": "exampleEntity", - "components": { - "ExampleComponent": { "paramA": "example", "paramB": 10 } - } - } - ] -} -``` - -## Init functions - -When `initFunctions` is `true`, the following lifecycle hooks are scaffolded: - -| File | Hook | Called | -| ------------------------- | -------------------- | ----------------------------------- | -| `before-init.ts` | `beforeInit` | Before the app initializes | -| `after-init.ts` | `afterInit` | After the app initializes | -| `before-registry-init.ts` | `beforeRegistryInit` | Before the ECS registry initializes | -| `after-registry-init.ts` | `afterRegistryInit` | After the ECS registry initializes | -| `before-run.ts` | `beforeRun` | Before the game loop starts | -| `after-run.ts` | `afterRun` | After the game loop ends | - -## Examples - -**Generate a TypeScript client part:** - -```bash -schematics @nanoforge-dev/schematics:part-base --part=client -``` - -**Generate a server part with init hooks:** - -```bash -schematics @nanoforge-dev/schematics:part-base \ - --part=server \ - --initFunctions=true -``` - -**Generate a JavaScript client in a specific directory:** - -```bash -schematics @nanoforge-dev/schematics:part-base \ - --part=client \ - --language=js \ - --directory=./my-game -``` diff --git a/docs/docs/schematics/2-schematics/4-part-main.mdx b/docs/docs/schematics/2-schematics/4-part-main.mdx deleted file mode 100644 index 60a88dc..0000000 --- a/docs/docs/schematics/2-schematics/4-part-main.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Part Main -description: Generate the main entry point for a NanoForge client or server part ---- - -import { Tooltip } from "mintlify/components"; - -## Overview - -The `part-main` schematic reads the `.nanoforge/{part}.save.json` file produced by `part-base` and generates a fully wired `main.ts` (or `main.js`) entry point. It registers all libraries, components, systems, and entities declared in the save file. - - - This schematic must be run after `part-base`. - - -## Usage - -```bash -schematics @nanoforge-dev/schematics:part-main --part=client -``` - -## Options - -| Option | Type | Default | Description | -| --------------- | ---------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `part` | `"client" \| "server"` | _(required)_ | The part of the application to generate | -| `directory` | `string` | `.` | Project root directory | -| `language` | `"ts" \| "js"` | `ts` | Language of the generated entry point | -| `initFunctions` | `boolean` | `false` | Wire init lifecycle functions into the main file | -| `saveFile` | `string` | — | Custom path to the save JSON file | -| `outFile` | `string` | — | Custom output path for the generated main file | -| `editor` | `boolean` | `false` | Generate the editor variant of the main file | - -## Output path resolution - -The generated file is placed at one of the following paths, in priority order: - -1. `outFile` — if provided explicitly -2. `.nanoforge/editor/{part}/main.{language}` — if `editor: true` -3. `{part}/main.{language}` — default - -## How it works - -`part-main` reads `{directory}/.nanoforge/{part}.save.json`, builds the full application bootstrap code, then writes the result as the entry point. Every time you add a component, system, or entity through the NanoForge CLI or editor, regenerating `part-main` keeps the entry point in sync with the save file. - -## Examples - -**Generate a client main file:** - -```bash -schematics @nanoforge-dev/schematics:part-main --part=client -``` - -**Generate a server main file with init hooks:** - -```bash -schematics @nanoforge-dev/schematics:part-main \ - --part=server \ - --initFunctions=true -``` - -**Generate with a custom save file and output path:** - -```bash -schematics @nanoforge-dev/schematics:part-main \ - --part=client \ - --saveFile=./custom.save.json \ - --outFile=./src/client/main.ts -``` - -**Regenerate the editor entry point:** - -```bash -schematics @nanoforge-dev/schematics:part-main \ - --part=client \ - --editor=true -``` diff --git a/docs/docs/schematics/2-schematics/7-system.mdx b/docs/docs/schematics/2-schematics/4-system.mdx similarity index 100% rename from docs/docs/schematics/2-schematics/7-system.mdx rename to docs/docs/schematics/2-schematics/4-system.mdx diff --git a/docs/docs/schematics/2-schematics/5-docker.mdx b/docs/docs/schematics/2-schematics/5-docker.mdx deleted file mode 100644 index ce32b7d..0000000 --- a/docs/docs/schematics/2-schematics/5-docker.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Docker -description: Generate a Dockerfile for a NanoForge application ---- - -import { Tooltip } from "mintlify/components"; - -## Overview - -The `docker` schematic generates a production-ready `Dockerfile` and a `.dockerignore` at the specified directory. The Dockerfile is tailored to the package manager you use — selecting the correct base image, lockfile, and install command automatically. - -## Usage - -```bash -schematics @nanoforge-dev/schematics:docker -``` - -## Options - -| Option | Type | Default | Description | -| ---------------- | ------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `directory` | `string` | `.` | Destination directory for the generated files | -| `packageManager` | `"npm" \| "yarn" \| "pnpm" \| "bun"` | `npm` | Package manager used by the application | - -## Generated files - -``` -{directory}/ -├── Dockerfile -└── .dockerignore -``` - -## Dockerfile behaviour by package manager - -| Package manager | Base image | Install command | -| --------------- | -------------- | -------------------------------------------------- | -| `npm` | `node:25-slim` | `npm install --no-lockfile` | -| `yarn` | `node:25-slim` | `yarn install --frozen-lockfile` | -| `pnpm` | `node:25-slim` | `pnpm install --frozen-lockfile --allow-build=bun` | -| `bun` | `oven/bun:1.3` | `bun install --frozen-lockfile` | - -The Dockerfile follows a standard multi-step approach: - -1. Copy `package.json` and the lockfile. -2. Install dependencies with frozen lockfile for reproducible builds. -3. Copy the rest of the source. -4. Run `build`. -5. Start with `start`. - -## Examples - -**Generate a Dockerfile for an npm project:** - -```bash -schematics @nanoforge-dev/schematics:docker -``` - -**Generate a Dockerfile for a pnpm project:** - -```bash -schematics @nanoforge-dev/schematics:docker --packageManager=pnpm -``` - -**Generate in a specific directory:** - -```bash -schematics @nanoforge-dev/schematics:docker \ - --packageManager=bun \ - --directory=./my-game -``` 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/e2e/helpers/write-project-config.ts b/e2e/helpers/write-project-config.ts new file mode 100644 index 0000000..e7d7953 --- /dev/null +++ b/e2e/helpers/write-project-config.ts @@ -0,0 +1,36 @@ +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; + +interface WriteProjectConfigOptions { + name: string; + language: "ts" | "js"; + initFunctions?: boolean; + server?: boolean; +} + +/** + * `nf new` still scaffolds a `nanoforge.config.json` (schematics is out of scope for this + * change), but the CLI's config loader now only reads `.ts`/`.js` config modules. Tests that + * scaffold a project and then run `build`/`generate`/`create` against it need a hand-written + * `nanoforge.config.ts` alongside the scaffolded JSON — everything not set here falls back to + * the CLI's own defaults, matching what the scaffolded JSON used to rely on too. + */ +export const writeProjectConfig = ( + appDir: string, + { name, language, initFunctions = false, server = false }: WriteProjectConfigOptions, +) => { + const content = `export default ${JSON.stringify( + { + name, + language, + initFunctions, + client: { type: "client", enable: true }, + server: { type: "server", enable: server }, + ssl: { enable: false }, + }, + null, + 2, + )};\n`; + + writeFileSync(join(appDir, "nanoforge.config.ts"), content); +}; diff --git a/libs/schematics/README.md b/libs/schematics/README.md index 43f1d5f..f7ec6de 100644 --- a/libs/schematics/README.md +++ b/libs/schematics/README.md @@ -38,13 +38,10 @@ nf new This repository provide multiples schematics, usable with Angular Devkit schematics. -- `application` : Base of a Nanoforge app -- `configuration` : Template of `nanoforge.config.json` with premade fields -- `part-base` : Base of Nanoforge client or server -- `part-main` : Client or server `main.ts` from a config +- `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 -- `docker` : Dockerfile and `.dockerignore` tailored to your package manager ## Contributing diff --git a/libs/schematics/e2e/application.test.ts b/libs/schematics/e2e/application.test.ts deleted file mode 100644 index aa0e92f..0000000 --- a/libs/schematics/e2e/application.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -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("application schematic", () => { - const runner = new SchematicTestRunner("schematics", collectionPath); - - describe("with TypeScript (default)", () => { - let tree: UnitTestTree; - let packageJson: any; - - beforeAll(async () => { - tree = await runner.runSchematic("application", { name: "my-app" }); - packageJson = JSON.parse(tree.readContent("/my-app/package.json")); - }); - - it("should generate project files", () => { - expect(tree.files).toContain("/my-app/package.json"); - expect(tree.files).toContain("/my-app/tsconfig.json"); - expect(tree.files).toContain("/my-app/eslint.config.js"); - expect(tree.files).toContain("/my-app/prettier.config.js"); - expect(tree.files).toContain("/my-app/README.md"); - expect(tree.files).toContain("/my-app/.gitignore"); - expect(tree.files).not.toContain("/my-app/.env"); - expect(tree.files).not.toContain("/my-app/pnpm-workspace.yaml"); - }); - - it("should set the project name in package.json", () => { - expect(packageJson.name).toBe("my-app"); - }); - - it("should not include ecs-server by default", () => { - expect(packageJson.devDependencies["@nanoforge-dev/ecs-server"]).toBeUndefined(); - }); - - it("should not include pnpm config by default", () => { - expect(packageJson).not.toHaveProperty("pnpm"); - }); - - it("should not include init functions docs by default", () => { - expect(tree.readContent("/my-app/README.md")).not.toContain( - "│ └── init/ # Lifecycle hooks (before-init, after-run, …)", - ); - }); - }); - - describe("with JavaScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("application", { - name: "js-app", - language: "js", - }); - }); - - it("should generate JS project files", () => { - expect(tree.files).toContain("/js-app/package.json"); - expect(tree.files).toContain("/js-app/jsconfig.json"); - expect(tree.files).not.toContain("/js-app/tsconfig.json"); - expect(tree.files).not.toContain("/js-app/.env"); - }); - }); - - describe("with JavaScript and no lint", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("application", { - name: "js-no-lint-app", - language: "js", - lint: false, - }); - }); - - it("should generate JS project files without lint files", () => { - expect(tree.files).toContain("/js-no-lint-app/package.json"); - expect(tree.files).not.toContain("/js-no-lint-app/eslint.config.json"); - expect(tree.files).not.toContain("/js-no-lint-app/prettier.config.json"); - expect(tree.files).not.toContain("/js-no-lint-app/.prettierignore"); - expect(tree.files).not.toContain("/js-no-lint-app/jsconfig.json"); - expect(tree.files).not.toContain("/js-no-lint-app/tsconfig.json"); - }); - }); - - describe("with server enabled", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("application", { - name: "server-app", - server: true, - }); - }); - - it("should include env file", () => { - expect(tree.files).toContain("/server-app/.env"); - }); - - it("should include ecs-server dependency", () => { - const packageJson = JSON.parse(tree.readContent("/server-app/package.json")); - expect(packageJson.devDependencies["@nanoforge-dev/ecs-server"]).toBeDefined(); - }); - }); - - describe("with custom options", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("application", { - name: "custom-app", - version: "1.2.3", - author: "Test Author", - description: "A test project", - editor: true, - }); - }); - - it("should apply custom options to package.json", () => { - const packageJson = JSON.parse(tree.readContent("/custom-app/package.json")); - expect(packageJson.version).toBe("1.2.3"); - expect(packageJson.author).toBe("Test Author"); - expect(packageJson.description).toBe("A test project"); - expect(packageJson.devDependencies["@nanoforge-dev/core-editor"]).toBeDefined(); - expect(packageJson.devDependencies["@nanoforge-dev/graphics-2d-editor"]).toBeDefined(); - }); - }); - - describe("with custom directory", () => { - it("should generate files in the specified directory", async () => { - const tree = await runner.runSchematic("application", { - name: "my-app", - directory: "custom-dir", - }); - expect(tree.files).toContain("/custom-dir/package.json"); - }); - }); - - describe("with pnpm", () => { - it("should generate specifications of pnpm", async () => { - const tree = await runner.runSchematic("application", { - name: "pnpm-app", - packageManager: "pnpm", - server: true, - }); - expect(tree.files).toContain("/pnpm-app/package.json"); - expect(tree.files).toContain("/pnpm-app/pnpm-workspace.yaml"); - const packageJson = JSON.parse(tree.readContent("/pnpm-app/package.json")); - expect(packageJson).toHaveProperty("pnpm", { - neverBuiltDependencies: [], - }); - }); - }); - - describe("with init functions", () => { - it("should generate specifications of init functions", async () => { - const tree = await runner.runSchematic("application", { - name: "init-functions-app", - initFunctions: true, - }); - expect(tree.files).toContain("/init-functions-app/README.md"); - const content = tree.readContent("/init-functions-app/README.md"); - expect(content).toContain( - "│ └── init/ # Lifecycle hooks (before-init, after-run, …)", - ); - }); - }); -}); diff --git a/libs/schematics/e2e/configuration.test.ts b/libs/schematics/e2e/configuration.test.ts deleted file mode 100644 index 474e9cc..0000000 --- a/libs/schematics/e2e/configuration.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -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("configuration schematic", () => { - const runner = new SchematicTestRunner("schematics", collectionPath); - - describe("on an empty tree", () => { - let tree: UnitTestTree; - let config: Record; - - beforeAll(async () => { - tree = await runner.runSchematic("configuration", { - name: "my-name", - }); - config = JSON.parse(tree.readContent("/nanoforge.config.json")); - }); - - it("should generate nanoforge.config.json", () => { - expect(tree.files).toContain("/nanoforge.config.json"); - }); - - it("should create a valid JSON config file", () => { - expect(() => JSON.parse(tree.readContent("/nanoforge.config.json"))).not.toThrow(); - }); - - it("should include default client build config", () => { - expect(config.name).toBe("my-name"); - expect(config.language).toBe("ts"); - expect(config.initFunctions).not.toBeDefined(); - }); - - it("should include default client build config", () => { - expect(config.client).toBeDefined(); - expect(config.client.enable).toBe(true); - }); - - it("should have server disabled", () => { - expect(config.server.enable).toBe(false); - }); - }); - - describe("with js", () => { - let tree: UnitTestTree; - let config: Record; - - beforeAll(async () => { - tree = await runner.runSchematic("configuration", { - name: "my-name", - language: "js", - initFunctions: true, - }); - config = JSON.parse(tree.readContent("/nanoforge.config.json")); - }); - - it("should generate nanoforge.config.json", () => { - expect(tree.files).toContain("/nanoforge.config.json"); - }); - - it("should include default client build config", () => { - expect(config.name).toBe("my-name"); - expect(config.language).toBe("js"); - expect(config.initFunctions).toBe(true); - }); - - it("should include default client build config", () => { - expect(config.client).toBeDefined(); - expect(config.client.build.entry).toBe("client/main.js"); - }); - - it("should include default editor config", () => { - expect(config.client.editor.entry).toBe(".nanoforge/editor/client/main.js"); - }); - }); - - describe("with server enabled", () => { - let tree: UnitTestTree; - let config: Record; - - beforeAll(async () => { - tree = await runner.runSchematic("configuration", { - server: true, - }); - config = JSON.parse(tree.readContent("/nanoforge.config.json")); - }); - - it("should generate config file", () => { - expect(tree.files).toContain("/nanoforge.config.json"); - }); - - it("should include server config with enable flag", () => { - expect(config.server).toBeDefined(); - expect(config.server.enable).toBe(true); - }); - }); - - describe("with custom directory", () => { - it("should generate config in the specified directory", async () => { - const tree = await runner.runSchematic("configuration", { - name: "my-app", - directory: "custom-dir", - }); - expect(tree.files).toContain("/custom-dir/nanoforge.config.json"); - }); - }); -}); diff --git a/libs/schematics/e2e/docker.test.ts b/libs/schematics/e2e/docker.test.ts deleted file mode 100644 index f77fc19..0000000 --- a/libs/schematics/e2e/docker.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -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("docker schematic", () => { - const runner = new SchematicTestRunner("schematics", collectionPath); - - describe("with npm package manager", () => { - let tree: UnitTestTree; - - describe.each(["npm", "yarn", "pnpm", "bun"] as const)( - "with ${packageManager} package manager", - (packageManager) => { - beforeAll(async () => { - tree = await runner.runSchematic("docker", { - directory: `${packageManager}-test-app`, - packageManager, - }); - }); - - it("should create Dockerfile", () => { - expect(tree.files).toContain(`/${packageManager}-test-app/Dockerfile`); - expect(tree.files).toContain(`/${packageManager}-test-app/.dockerignore`); - }); - - it("should create docker file with install command", () => { - const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); - expect(content).toContain(`RUN ${packageManager} install`); - }); - - it("should create docker file with build command", () => { - const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); - expect(content).toContain(`RUN ${packageManager} run build`); - }); - - it("should create docker file with start command", () => { - const content = tree.readContent(`/${packageManager}-test-app/Dockerfile`); - expect(content).toContain(`CMD ["${packageManager}", "run", "start"]`); - }); - }, - ); - }); -}); diff --git a/libs/schematics/e2e/part-base.test.ts b/libs/schematics/e2e/part-base.test.ts deleted file mode 100644 index 0e917f3..0000000 --- a/libs/schematics/e2e/part-base.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -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("part-base schematic", () => { - const runner = new SchematicTestRunner("schematics", collectionPath); - - describe("client part with TypeScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-base", { - directory: "my-app", - part: "client", - language: "ts", - }); - }); - - it("should generate client directory structure", () => { - expect(tree.files).toContain("/my-app/client/components/example.component.ts"); - expect(tree.files).toContain("/my-app/client/systems/example.system.ts"); - }); - - it("should generate the client save file", () => { - expect(tree.files).toContain("/my-app/.nanoforge/client.save.json"); - }); - - it("should not generate server save file", () => { - expect(tree.files).not.toContain("/my-app/.nanoforge/server.save.json"); - }); - - it("should not generate init functions by default", () => { - const initFiles = tree.files.filter((f) => f.includes("/init/")); - expect(initFiles).toHaveLength(0); - }); - - it("should generate a valid save file", () => { - const save = JSON.parse(tree.readContent("/my-app/.nanoforge/client.save.json")); - expect(save.libraries).toBeDefined(); - expect(save.components).toBeDefined(); - expect(save.systems).toBeDefined(); - expect(save.entities).toBeDefined(); - }); - }); - - describe("server part with TypeScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-base", { - directory: "my-app", - part: "server", - language: "ts", - }); - }); - - it("should generate server directory structure", () => { - expect(tree.files).toContain("/my-app/server/components/example.component.ts"); - expect(tree.files).toContain("/my-app/server/systems/example.system.ts"); - }); - - it("should generate the server save file", () => { - expect(tree.files).toContain("/my-app/.nanoforge/server.save.json"); - }); - - it("should not generate client save file", () => { - expect(tree.files).not.toContain("/my-app/.nanoforge/client.save.json"); - }); - }); - - describe("with init functions enabled", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-base", { - directory: "my-app", - part: "client", - language: "ts", - initFunctions: true, - }); - }); - - it("should generate init function files", () => { - expect(tree.files).toContain("/my-app/client/init/before-init.ts"); - expect(tree.files).toContain("/my-app/client/init/after-init.ts"); - expect(tree.files).toContain("/my-app/client/init/before-registry-init.ts"); - expect(tree.files).toContain("/my-app/client/init/after-registry-init.ts"); - expect(tree.files).toContain("/my-app/client/init/before-run.ts"); - expect(tree.files).toContain("/my-app/client/init/after-run.ts"); - }); - }); - - describe("with JavaScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-base", { - directory: "my-app", - part: "client", - language: "js", - }); - }); - - it("should generate JS files", () => { - expect(tree.files).toContain("/my-app/client/components/example.component.js"); - expect(tree.files).toContain("/my-app/client/systems/example.system.js"); - }); - }); - - describe("with custom directory", () => { - it("should generate files in the specified directory", async () => { - const tree = await runner.runSchematic("part-base", { - name: "my-app", - part: "client", - directory: "custom-dir", - }); - expect(tree.files).toContain("/custom-dir/client/components/example.component.ts"); - }); - }); -}); diff --git a/libs/schematics/e2e/part-main.test.ts b/libs/schematics/e2e/part-main.test.ts deleted file mode 100644 index 736f8b6..0000000 --- a/libs/schematics/e2e/part-main.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { SchematicTestRunner, type UnitTestTree } from "@angular-devkit/schematics/testing"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; - -import { type Save } from "@utils/main/save.type"; - -const collectionPath = resolve(__dirname, "../dist/collection.json"); -const tmpDir = resolve(__dirname, "../.tmp-e2e"); - -const clientSave: Save = { - libraries: [ - { - id: "ecsLibrary", - type: "component-system", - name: "ECSClientLibrary", - path: "@nanoforge-dev/ecs-client", - }, - { - id: "graphicsLibrary", - type: "graphics", - name: "Graphics2DLibrary", - path: "@nanoforge-dev/graphics-2d", - }, - ], - components: [ - { - name: "ExampleComponent", - path: "./components/example.component.ts", - paramsNames: ["exampleValueName", "exampleNum", "__RESERVED_ASSET_exampleAsset"], - }, - ], - systems: [{ name: "exampleSystem", path: "./systems/example.system.js" }], - entities: [ - { - id: "player", - components: { - ExampleComponent: { - exampleValueName: "test", - exampleNum: 5, - exampleAsset: "/path/to/asset", - }, - }, - }, - ], -}; - -const serverSave: Save = { - libraries: [ - { - id: "ecsLibrary", - type: "component-system", - name: "ECSServerLibrary", - path: "@nanoforge-dev/ecs-server", - }, - ], - components: [], - systems: [], - entities: [], -}; - -describe("part-main schematic", () => { - const runner = new SchematicTestRunner("schematics", collectionPath); - - beforeAll(() => { - mkdirSync(tmpDir, { recursive: true }); - writeFileSync(resolve(tmpDir, "client.save.json"), JSON.stringify(clientSave)); - writeFileSync(resolve(tmpDir, "server.save.json"), JSON.stringify(serverSave)); - }); - - afterAll(() => { - rmSync(tmpDir, { recursive: true, force: true }); - }); - - describe("client main with TypeScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-main", { - directory: "my-app", - part: "client", - language: "ts", - saveFile: resolve(tmpDir, "client.save.json"), - }); - }); - - it("should generate client/main.ts", () => { - expect(tree.files).toContain("/my-app/client/main.ts"); - }); - - it("should contain NanoforgeFactory.createClient()", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain("NanoforgeFactory.createClient()"); - }); - - it("should contain typed main function", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain("export async function main(options: IRunOptions)"); - }); - - it("should import and instantiate libraries", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"'); - expect(content).toContain('import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d"'); - expect(content).toContain("const ecsLibrary = new ECSClientLibrary();"); - expect(content).toContain("const graphicsLibrary = new Graphics2DLibrary();"); - }); - - it("should register entities and systems", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain("const player = registry.spawnEntity();"); - expect(content).toContain( - 'registry.addComponent(player, new ExampleComponent("test", 5, assetManagerLibrary.getAsset("/path/to/asset")));', - ); - expect(content).toContain("registry.addSystem(exampleSystem);"); - }); - - it("should not contain editor libs", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).not.toContain("@nanoforge-dev/graphics-2d-editor"); - }); - }); - - describe("server main with TypeScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-main", { - directory: "my-app", - part: "server", - language: "ts", - saveFile: resolve(tmpDir, "server.save.json"), - }); - }); - - it("should generate server/main.ts", () => { - expect(tree.files).toContain("/my-app/server/main.ts"); - }); - - it("should contain NanoforgeFactory.createServer()", () => { - const content = tree.readContent("/my-app/server/main.ts"); - expect(content).toContain("NanoforgeFactory.createServer()"); - }); - }); - - describe("client main with JavaScript", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-main", { - directory: "my-app", - part: "client", - language: "js", - saveFile: resolve(tmpDir, "client.save.json"), - }); - }); - - it("should generate client/main.js", () => { - expect(tree.files).toContain("/my-app/client/main.js"); - }); - - it("should not contain TypeScript type annotations", () => { - const content = tree.readContent("/my-app/client/main.js"); - expect(content).not.toContain("IRunOptions"); - expect(content).toContain("export async function main(options)"); - }); - }); - - describe("with init functions enabled", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-main", { - directory: "my-app", - part: "client", - language: "ts", - initFunctions: true, - saveFile: resolve(tmpDir, "client.save.json"), - }); - }); - - it("should import init functions", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain('import { beforeInit } from "./init/before-init"'); - expect(content).toContain('import { afterRun } from "./init/after-run"'); - }); - - it("should call init functions in the main body", () => { - const content = tree.readContent("/my-app/client/main.ts"); - expect(content).toContain("await beforeInit(app);"); - expect(content).toContain("await afterInit(app);"); - expect(content).toContain("await beforeRegistryInit(app, registry);"); - expect(content).toContain("await afterRegistryInit(app, registry);"); - expect(content).toContain("await beforeRun(app);"); - expect(content).toContain("await afterRun(app);"); - }); - }); - - describe("editor mode", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("part-main", { - directory: "my-app", - part: "client", - language: "ts", - editor: true, - saveFile: resolve(tmpDir, "client.save.json"), - }); - }); - - it("should generate the editor main file under .nanoforge/editor", () => { - expect(tree.files).toContain("/my-app/.nanoforge/editor/client/main.ts"); - }); - - it("should import from @nanoforge-dev/core-editor", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain('from "@nanoforge-dev/core-editor"'); - }); - - it("should use IEditorRunOptions type", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain("IEditorRunOptions"); - expect(content).toContain("export async function main(options: IEditorRunOptions)"); - }); - - it("should use entity paramsValues from editor save", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain( - 'options.editor.save.entities[0].components["ExampleComponent"]["exampleValueName"]', - ); - expect(content).toContain( - 'assetManagerLibrary.getAsset(options.editor.save.entities[0].components["ExampleComponent"]["exampleAsset"])', - ); - }); - - it("should import components and systems with relative path to root", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain( - 'import { ExampleComponent } from "../../../client/components/example.component"', - ); - expect(content).toContain( - 'import { exampleSystem } from "../../../client/systems/example.system"', - ); - }); - - it("should create a entityId reserved component", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain( - 'registry.addComponent(player, { name: "__RESERVED_entityId", entityId: "player" });', - ); - }); - - it("should init graphics editor", () => { - const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts"); - expect(content).toContain( - 'import { Graphics2DEditorLibrary } from "@nanoforge-dev/graphics-2d-editor"', - ); - expect(content).toContain("const graphicsEditorLibrary = new Graphics2DEditorLibrary();"); - }); - }); -}); diff --git a/libs/schematics/e2e/project.test.ts b/libs/schematics/e2e/project.test.ts new file mode 100644 index 0000000..340fa1f --- /dev/null +++ b/libs/schematics/e2e/project.test.ts @@ -0,0 +1,477 @@ +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 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 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("uses the workspace-linking protocol 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`), + ); + const expected = packageManager === "npm" ? "*" : "workspace:*"; + expect(packageJson.devDependencies["@nanoforge-dev/ecs"]).toBe(expected); + expect(packageJson.devDependencies["nanoforge"]).toBe(expected); + }); + + 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 init functions enabled", () => { + let tree: UnitTestTree; + + beforeAll(async () => { + tree = await runner.runSchematic("project", { + part: "client", + workspaceName: "init-workspace", + directory: "init-apps/client", + initFunctions: true, + }); + }); + + it("should generate init function files", () => { + expect(tree.files).toContain("/init-apps/client/src/init/before-init.ts"); + expect(tree.files).toContain("/init-apps/client/src/init/after-init.ts"); + expect(tree.files).toContain("/init-apps/client/src/init/before-registry-init.ts"); + expect(tree.files).toContain("/init-apps/client/src/init/after-registry-init.ts"); + expect(tree.files).toContain("/init-apps/client/src/init/before-run.ts"); + expect(tree.files).toContain("/init-apps/client/src/init/after-run.ts"); + }); + + it("should wire the init functions into main.ts", () => { + const content = tree.readContent("/init-apps/client/src/main.ts"); + expect(content).toContain('import { beforeInit } from "./init/before-init"'); + expect(content).toContain("await beforeInit(app);"); + expect(content).toContain("await afterRun(app);"); + }); + }); + + 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("with libs", () => { + it("should list libs in nanoforge.config.ts", async () => { + const tree = await runner.runSchematic("project", { + part: "client", + workspaceName: "libs-workspace", + directory: "libs-apps/client", + libs: ["../../libs/shared"], + }); + const content = tree.readContent("/libs-apps/client/nanoforge.config.ts"); + expect(content).toContain('libs: ["../../libs/shared"]'); + }); + }); + + 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.devDependencies["@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 ignored when generated inside a workspace, even if passed", async () => { + const tree = await runner.runSchematic("project", { + part: "server", + workspaceName: "in-workspace-allow-builds", + directory: "in-workspace-allow-builds-apps/server", + packageManager: "pnpm", + allowBuilds: ["wrtc"], + }); + 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("writes a pnpm-workspace.yaml allowBuilds map for a standalone pnpm project", async () => { + const tree = await runner.runSchematic("project", { + part: "server", + workspaceName: "standalone-allow-builds", + directory: "standalone-allow-builds-apps/server", + workspace: false, + packageManager: "pnpm", + allowBuilds: ["wrtc"], + }); + const pnpmWorkspaceYaml = tree.readContent( + "/standalone-allow-builds-apps/server/pnpm-workspace.yaml", + ); + expect(pnpmWorkspaceYaml).toContain("allowBuilds:"); + expect(pnpmWorkspaceYaml).toContain("wrtc: true"); + }); + + it("does not generate pnpm-workspace.yaml for a standalone pnpm project with no allowBuilds", async () => { + const tree = await runner.runSchematic("project", { + part: "client", + workspaceName: "standalone-no-allow-builds", + directory: "standalone-no-allow-builds-apps/client", + workspace: false, + packageManager: "pnpm", + }); + expect(tree.files).not.toContain( + "/standalone-no-allow-builds-apps/client/pnpm-workspace.yaml", + ); + }); + + it("writes 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", + allowBuilds: ["wrtc"], + }); + const npmPackageJson = JSON.parse( + npmTree.readContent("/standalone-npm-allow-builds-apps/server/package.json"), + ); + expect(npmPackageJson.allowScripts).toEqual({ wrtc: 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", + allowBuilds: ["wrtc"], + }); + const bunPackageJson = JSON.parse( + bunTree.readContent("/standalone-bun-allow-builds-apps/server/package.json"), + ); + expect(bunPackageJson.trustedDependencies).toEqual(["wrtc"]); + }); + }); +}); diff --git a/libs/schematics/e2e/workspace.test.ts b/libs/schematics/e2e/workspace.test.ts new file mode 100644 index 0000000..6356e6b --- /dev/null +++ b/libs/schematics/e2e/workspace.test.ts @@ -0,0 +1,265 @@ +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/tsconfig.spec.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("pins typescript to a specific version, not a range", () => { + 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.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"]).toBe("workspace:*"); + }); + + 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/src/collection.json b/libs/schematics/src/collection.json index 19462ce..3fdca2d 100644 --- a/libs/schematics/src/collection.json +++ b/libs/schematics/src/collection.json @@ -1,30 +1,15 @@ { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { - "application": { - "factory": "./libs/application/application.factory#main", - "description": "Create a NanoForge Base application.", - "schema": "./libs/application/schema.json" + "workspace": { + "factory": "./libs/workspace/workspace.factory#main", + "description": "Create a NanoForge workspace (monorepo root: config, tooling, package manifest).", + "schema": "./libs/workspace/schema.json" }, - "configuration": { - "factory": "./libs/configuration/configuration.factory#main", - "description": "Create a NanoForge Configuration.", - "schema": "./libs/configuration/schema.json" - }, - "part-base": { - "factory": "./libs/part-base/part-base.factory#main", - "description": "Create a NanoForge Base for client or server.", - "schema": "./libs/part-base/schema.json" - }, - "part-main": { - "factory": "./libs/part-main/part-main.factory#main", - "description": "Create a Main file for client or server.", - "schema": "./libs/part-main/schema.json" - }, - "docker": { - "factory": "./libs/docker/docker.factory#main", - "description": "Create a Dockerfile for the application.", - "schema": "./libs/docker/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", diff --git a/libs/schematics/src/defaults.ts b/libs/schematics/src/defaults.ts index 5c44c97..f4621c9 100644 --- a/libs/schematics/src/defaults.ts +++ b/libs/schematics/src/defaults.ts @@ -1,6 +1,22 @@ export const DEFAULT_APP_NAME = "nanoforge-app"; -export const DEFAULT_VERSION = "0.0.0"; -export const DEFAULT_AUTHOR = ""; -export const DEFAULT_DESCRIPTION = ""; 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/libs/application/application.factory.ts b/libs/schematics/src/libs/application/application.factory.ts deleted file mode 100644 index 5e0bb21..0000000 --- a/libs/schematics/src/libs/application/application.factory.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { type Path, join, normalize, strings } from "@angular-devkit/core"; -import { - type Rule, - type SchematicContext, - type Source, - type Tree, - apply, - mergeWith, - move, - renameTemplateFiles, - template, - url, -} from "@angular-devkit/schematics"; -import { firstValueFrom } from "rxjs"; - -import { - DEFAULT_APP_NAME, - DEFAULT_AUTHOR, - DEFAULT_DESCRIPTION, - DEFAULT_LANGUAGE, - DEFAULT_PACKAGE_MANAGER, - DEFAULT_VERSION, -} from "~/defaults"; - -import { type ApplicationOptions } from "./application.options"; -import { type ApplicationSchema } from "./application.schema"; - -const transform = (schema: ApplicationSchema): ApplicationOptions => { - return { - name: schema.name ?? DEFAULT_APP_NAME, - version: schema.version ?? DEFAULT_VERSION, - author: schema.author ?? DEFAULT_AUTHOR, - description: schema.description ?? DEFAULT_DESCRIPTION, - language: schema.language ?? DEFAULT_LANGUAGE, - strict: schema.strict ?? true, - lint: schema.lint ?? true, - packageManager: schema.packageManager ?? DEFAULT_PACKAGE_MANAGER, - server: schema.server ?? false, - editor: schema.editor ?? false, - initFunctions: schema.initFunctions ?? false, - }; -}; - -const generate = (options: ApplicationOptions, path: string): Source => { - return apply(url(join("./files" as Path, options.language)), [ - template({ - dot: ".", - ...strings, - ...options, - }), - renameTemplateFiles(), - move(normalize(path)), - ]); -}; - -export const main = (schema: ApplicationSchema): Rule => { - const options = transform(schema); - const path = schema.directory ?? options.name; - - return async (baseTree: Tree, context: SchematicContext) => { - let tree = await mergeWith(generate(options, path))(baseTree, context); - if (!tree) return tree; - if (!("delete" in tree)) { - if (typeof tree === "function") return tree; - tree = await firstValueFrom(tree); - } - - const basePath = join("/" as Path, path); - - if (!options.lint) { - tree.delete(join(basePath, "eslint.config.js")); - tree.delete(join(basePath, "prettier.config.js")); - tree.delete(join(basePath, ".prettierignore")); - if (options.language === "js") tree.delete(join(basePath, "jsconfig.json")); - } - - if (!options.server) { - tree.delete(join(basePath, ".env")); - } - - if (options.packageManager !== "pnpm") { - tree.delete(join(basePath, "pnpm-workspace.yaml")); - } - - return tree; - }; -}; diff --git a/libs/schematics/src/libs/application/application.options.d.ts b/libs/schematics/src/libs/application/application.options.d.ts deleted file mode 100644 index 71201d8..0000000 --- a/libs/schematics/src/libs/application/application.options.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -export interface ApplicationOptions { - /** - * NanoForge application name - */ - name: string; - - /** - * NanoForge application version - */ - version: string; - - /** - * NanoForge application author - */ - author: string; - - /** - * NanoForge application description - */ - description: string; - - /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * With strict mode (TypeScript or JavaScript but only on compatible editor) - */ - strict: boolean; - - /** - * Generate lint files - */ - lint: boolean; - - /** - * The used package manager - */ - packageManager: "npm" | "yarn" | "pnpm" | "bun"; - - /** - * Configure a server for the application - */ - server: boolean; - - /** - * Add editor dependencies - */ - editor: boolean; - - /** - * Add init functions to the application - */ - initFunctions: boolean; -} diff --git a/libs/schematics/src/libs/application/application.schema.d.ts b/libs/schematics/src/libs/application/application.schema.d.ts deleted file mode 100644 index 60579c3..0000000 --- a/libs/schematics/src/libs/application/application.schema.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -export interface ApplicationSchema { - /** - * NanoForge application name - */ - name: string; - - /** - * NanoForge application version - */ - version?: string; - - /** - * NanoForge application author - */ - author?: string; - - /** - * NanoForge application description - */ - description?: string; - - /** - * NanoForge application destination directory - */ - directory?: string; - - /** - * NanoForge Application language - */ - language?: "js" | "ts"; - - /** - * With strict mode (TypeScript or JavaScript but only on compatible editor) - */ - strict?: boolean; - - /** - * Generate lint files - */ - lint?: boolean; - - /** - * The used package manager - */ - packageManager?: "npm" | "yarn" | "pnpm" | "bun"; - - /** - * Configure a server for the application - */ - server?: boolean; - - /** - * Add editor dependencies - */ - editor: boolean; - - /** - * Add init functions to the application - */ - initFunctions: boolean; -} diff --git a/libs/schematics/src/libs/application/files/js/.prettierignore b/libs/schematics/src/libs/application/files/js/.prettierignore deleted file mode 100644 index 2f0ede7..0000000 --- a/libs/schematics/src/libs/application/files/js/.prettierignore +++ /dev/null @@ -1,8 +0,0 @@ -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock -bun.lock - -/node_modules -/.nanoforge diff --git a/libs/schematics/src/libs/application/files/js/README.md b/libs/schematics/src/libs/application/files/js/README.md deleted file mode 100644 index 94b63de..0000000 --- a/libs/schematics/src/libs/application/files/js/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# <%= name %> - ---- - -## Prerequisites - -- [Node.js](https://nodejs.org/) 18+ -- Package manager: **<%= packageManager %>** -- [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `<%= packageManager %> install -g @nanoforge-dev/cli` - ---- - -## Installation - -```bash -<%= packageManager %> install -``` - ---- - -## Development - -Start the game in watch mode with hot reload: - -```bash -nf dev -``` - -<% if (editor) { %>Open the visual editor (optional — lets you edit entities, components and systems without touching code): -```bash -nf editor -``` - -<% } %>--- - -## Project structure - -``` -<%= name %>/ -├── .nanoforge/ # NanoForge internal save files (do not edit manually) -├── client/ # Client-side code (runs in the browser) -│ ├── static/ # Static assets -│ ├── components/ # ECS components -│ ├── systems/ # ECS systems<% if (initFunctions) { %> -│ └── init/ # Lifecycle hooks (before-init, after-run, …)<% } %><% if (server) { %> -├── server/ # Server-side code -│ ├── static/ -│ ├── components/ -│ ├── systems/<% if (initFunctions) { %> -│ └── init/<% } %><% } %> -├── package.json -├── jsconfig.json -└── nanoforge.config.json # Game configuration -``` - ---- - -## Modifying the game - -### Add a component - -```bash -nf generate component --part client -``` - -A component is a plain class that holds data for an entity. Edit the generated file in `client/components/`: - -```js -export class MyComponent { - constructor(speed) { - this.speed = speed; - } -} - -export default MyComponent.name; -``` - -### Add a system - -```bash -nf generate system --part client -``` - -A system is a function that runs every tick and operates on entities that have specific components. Edit the generated file in `client/systems/`: - -```js -import { MyComponent } from "../components/my.component.js"; - -export const mySystem = (registry, ctx) => { - const entities = registry.getZipper([MyComponent]); - - entities.forEach((entity) => { - entity.MyComponent.speed += 1; - }); -}; - -export default mySystem.name; -``` - -<% if (server) { %> -### Server-side code - -Server components and systems live in `server/` and are imported from `@nanoforge-dev/ecs-server`. They follow the same pattern as the client side. - -Configure the server connection in `.env`: - -| Variable | Default | Description | -|---------------------------------------|-------------|---------------------------------| -| `NANOFORGE_CLIENT_SERVER_ADDRESS` | `127.0.0.1` | Server address (client-side) | -| `NANOFORGE_CLIENT_SERVER_TCP_PORT` | `4444` | TCP port the client connects to | -| `NANOFORGE_CLIENT_SERVER_UDP_PORT` | `4445` | UDP port the client connects to | -| `NANOFORGE_SERVER_LISTENING_TCP_PORT` | `4444` | TCP port the server listens on | -| `NANOFORGE_SERVER_LISTENING_UDP_PORT` | `4445` | UDP port the server listens on | - -<% } %>--- - -## Build - -Compile the game for production: - -```bash -<%= packageManager %> run build -# or -nf build -``` - -Output is written to the `.nanoforge/` directory. - ---- - -## Run - -Start the game server and open it in a browser: - -```bash -<%= packageManager %> run start -# or -nf start -``` - -<% if (lint) { %>--- - -## Code quality - -```bash -# Check formatting and linting -<%= packageManager %> run lint - -# Auto-fix formatting and linting -<%= packageManager %> run format -``` - -<% } %>--- - -## CLI reference - -| Command | Description | -|--------------------------------|-------------------------------------------| -| `nf dev` | Start in development mode with hot reload | -| `nf build` | Build for production | -| `nf start` | Run the built game | -| `nf generate component ` | Scaffold a new component | -| `nf generate system ` | Scaffold a new system |<% if (editor) { %> -| `nf editor` | Open the visual editor |<% } %> - -Full CLI documentation: diff --git a/libs/schematics/src/libs/application/files/js/eslint.config.js b/libs/schematics/src/libs/application/files/js/eslint.config.js deleted file mode 100644 index a90e742..0000000 --- a/libs/schematics/src/libs/application/files/js/eslint.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import pluginJs from "@eslint/js"; -import eslintConfigPrettier from "eslint-config-prettier"; -import globals from "globals"; - -export default [ - { files: ["{client,server}/**/*.{js}"] }, - { languageOptions: { globals: globals.node } }, - - pluginJs.configs.recommended, - eslintConfigPrettier, -]; diff --git a/libs/schematics/src/libs/application/files/js/package.json.template b/libs/schematics/src/libs/application/files/js/package.json.template deleted file mode 100644 index 4d2bd37..0000000 --- a/libs/schematics/src/libs/application/files/js/package.json.template +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "<%= name %>", - "version": "<%= version %>", - "description": "<%= description %>", - "author": "<%= author %>", - "scripts": { - "build": "nf build", - "start": "nf start"<% if (lint) { %>, - "lint": "prettier --check . && eslint --format=pretty \"{client,server}/**/*.js\"", - "format": "prettier --write . && eslint --fix --format=pretty \"{client,server}/**/*.js\""<% } %> - }, - "devDependencies": {<% if (lint) { %> - "@eslint/js": "^9.39.0",<% } %> - "@nanoforge-dev/asset-manager": "^1", - "@nanoforge-dev/cli": "latest", - "@nanoforge-dev/common": "^1", - "@nanoforge-dev/config": "^1", - "@nanoforge-dev/core": "^1",<% if (editor) { %> - "@nanoforge-dev/core-editor": "^1",<% } %> - "@nanoforge-dev/ecs-client": "^1",<% if (server) { %> - "@nanoforge-dev/ecs-server": "^1",<% } %> - "@nanoforge-dev/graphics-2d": "^1",<% if (editor) { %> - "@nanoforge-dev/graphics-2d-editor": "^1",<% } %> - "@nanoforge-dev/input": "^1", - "@nanoforge-dev/music": "^1",<% if (server) { %> - "@nanoforge-dev/network-client": "^1", - "@nanoforge-dev/network-server": "^1",<% } %> - "@nanoforge-dev/sound": "^1"<% if (lint) { %>, - "eslint": "^9.39.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "globals": "^16.5.0", - "lint-staged": "^16.2.6", - "prettier": "^3.6.2"<% } %> - },<% if (lint) { %> - "lint-staged": { - "**/*.js": [ - "prettier --write" - ], - "{client,server}/**/*.js": [ - "eslint --fix" - ] - },<% } %><% if (packageManager === "pnpm") { %> - "pnpm": { - "neverBuiltDependencies": [] - },<% } %> - "private": true -} diff --git a/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template b/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template deleted file mode 100644 index 236b123..0000000 --- a/libs/schematics/src/libs/application/files/js/pnpm-workspace.yaml.template +++ /dev/null @@ -1,3 +0,0 @@ -allowBuilds: - bun: true<% if (server) { %> - wrtc: true<% } %> diff --git a/libs/schematics/src/libs/application/files/js/prettier.config.js b/libs/schematics/src/libs/application/files/js/prettier.config.js deleted file mode 100644 index 8e5015d..0000000 --- a/libs/schematics/src/libs/application/files/js/prettier.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - useTabs: false, - singleQuote: false, - trailingComma: "all", - printWidth: 100, -}; diff --git a/libs/schematics/src/libs/application/files/ts/.prettierignore b/libs/schematics/src/libs/application/files/ts/.prettierignore deleted file mode 100644 index 2f0ede7..0000000 --- a/libs/schematics/src/libs/application/files/ts/.prettierignore +++ /dev/null @@ -1,8 +0,0 @@ -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock -bun.lock - -/node_modules -/.nanoforge diff --git a/libs/schematics/src/libs/application/files/ts/README.md b/libs/schematics/src/libs/application/files/ts/README.md deleted file mode 100644 index 44878e2..0000000 --- a/libs/schematics/src/libs/application/files/ts/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# <%= name %> - ---- - -## Prerequisites - -- [Node.js](https://nodejs.org/) 18+ -- Package manager: **<%= packageManager %>** -- [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `<%= packageManager %> install -g @nanoforge-dev/cli` - ---- - -## Installation - -```bash -<%= packageManager %> install -``` - ---- - -## Development - -Start the game in watch mode with hot reload: - -```bash -nf dev -``` - -<% if (editor) { %>Open the visual editor (optional — lets you edit entities, components and systems without touching code): -```bash -nf editor -``` - -<% } %>--- - -## Project structure - -``` -<%= name %>/ -├── .nanoforge/ # NanoForge internal save files (do not edit manually) -├── client/ # Client-side code (runs in the browser) -│ ├── static/ # Static assets -│ ├── components/ # ECS components -│ ├── systems/ # ECS systems<% if (initFunctions) { %> -│ └── init/ # Lifecycle hooks (before-init, after-run, …)<% } %><% if (server) { %> -├── server/ # Server-side code -│ ├── static/ -│ ├── components/ -│ ├── systems/<% if (initFunctions) { %> -│ └── init/<% } %><% } %> -├── package.json -├── tsconfig.json -└── nanoforge.config.json # Game configuration -``` - ---- - -## Modifying the game - -### Add a component - -```bash -nf generate component --part client -``` - -A component is a plain class that holds data for an entity. Edit the generated file in `client/components/`: - -```ts -export class MyComponent { - constructor(public speed: number) {} -} - -export default MyComponent.name; -``` - -### Add a system - -```bash -nf generate system --part client -``` - -A system is a function that runs every tick and operates on entities that have specific components. Edit the generated file in `client/systems/`: - -```ts -import { type Context } from "@nanoforge-dev/common"; -import { type Registry } from "@nanoforge-dev/ecs-client"; -import { MyComponent } from "../components/my.component"; - -export const mySystem = (registry: Registry, ctx: Context) => { - const entities = registry.getZipper([MyComponent]); - - entities.forEach((entity) => { - entity.MyComponent.speed += 1; - }); -}; - -export default mySystem.name; -``` - -<% if (server) { %> -### Server-side code - -Server components and systems live in `server/` and are imported from `@nanoforge-dev/ecs-server`. They follow the same pattern as the client side. - -Configure the server connection in `.env`: - -| Variable | Default | Description | -|---------------------------------------|-------------|---------------------------------| -| `NANOFORGE_CLIENT_SERVER_ADDRESS` | `127.0.0.1` | Server address (client-side) | -| `NANOFORGE_CLIENT_SERVER_TCP_PORT` | `4444` | TCP port the client connects to | -| `NANOFORGE_CLIENT_SERVER_UDP_PORT` | `4445` | UDP port the client connects to | -| `NANOFORGE_SERVER_LISTENING_TCP_PORT` | `4444` | TCP port the server listens on | -| `NANOFORGE_SERVER_LISTENING_UDP_PORT` | `4445` | UDP port the server listens on | - -<% } %>--- - -## Build - -Compile the game for production: - -```bash -<%= packageManager %> run build -# or -nf build -``` - -Output is written to the `.nanoforge/` directory. - ---- - -## Run - -Start the game server and open it in a browser: - -```bash -<%= packageManager %> run start -# or -nf start -``` - -<% if (lint) { %>--- - -## Code quality - -```bash -# Check formatting and linting -<%= packageManager %> run lint - -# Auto-fix formatting and linting -<%= packageManager %> run format -``` - -<% } %>--- - -## CLI reference - -| Command | Description | -|--------------------------------|-------------------------------------------| -| `nf dev` | Start in development mode with hot reload | -| `nf build` | Build for production | -| `nf start` | Run the built game | -| `nf generate component ` | Scaffold a new component | -| `nf generate system ` | Scaffold a new system |<% if (editor) { %> -| `nf editor` | Open the visual editor |<% } %> - -Full CLI documentation: diff --git a/libs/schematics/src/libs/application/files/ts/__dot__env b/libs/schematics/src/libs/application/files/ts/__dot__env deleted file mode 100644 index 75b4595..0000000 --- a/libs/schematics/src/libs/application/files/ts/__dot__env +++ /dev/null @@ -1,6 +0,0 @@ -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/application/files/ts/eslint.config.js b/libs/schematics/src/libs/application/files/ts/eslint.config.js deleted file mode 100644 index 2f3639d..0000000 --- a/libs/schematics/src/libs/application/files/ts/eslint.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import pluginJs from "@eslint/js"; -import eslintConfigPrettier from "eslint-config-prettier"; -import globals from "globals"; -import tseslint from "typescript-eslint"; - -export default [ - { files: ["{client,server}/**/*.{ts}"] }, - { languageOptions: { globals: globals.node } }, - - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - ...tseslint.configs.strict, - eslintConfigPrettier, - { ignores: ["**/*.js", "**/*.d.ts"] }, -]; diff --git a/libs/schematics/src/libs/application/files/ts/package.json.template b/libs/schematics/src/libs/application/files/ts/package.json.template deleted file mode 100644 index 3f4a160..0000000 --- a/libs/schematics/src/libs/application/files/ts/package.json.template +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "<%= name %>", - "version": "<%= version %>", - "description": "<%= description %>", - "author": "<%= author %>", - "scripts": { - "build": "nf build", - "start": "nf start"<% if (lint) { %>, - "lint": "prettier --check . && eslint --format=pretty \"{client,server}/**/*.ts\"", - "format": "prettier --write . && eslint --fix --format=pretty \"{client,server}/**/*.ts\""<% } %> - }, - "devDependencies": {<% if (lint) { %> - "@eslint/js": "^9.39.0",<% } %> - "@nanoforge-dev/asset-manager": "^1", - "@nanoforge-dev/cli": "latest", - "@nanoforge-dev/common": "^1", - "@nanoforge-dev/config": "^1", - "@nanoforge-dev/core": "^1",<% if (editor) { %> - "@nanoforge-dev/core-editor": "^1",<% } %> - "@nanoforge-dev/ecs-client": "^1",<% if (server) { %> - "@nanoforge-dev/ecs-server": "^1",<% } %> - "@nanoforge-dev/graphics-2d": "^1",<% if (editor) { %> - "@nanoforge-dev/graphics-2d-editor": "^1",<% } %> - "@nanoforge-dev/input": "^1", - "@nanoforge-dev/music": "^1",<% if (server) { %> - "@nanoforge-dev/network-client": "^1", - "@nanoforge-dev/network-server": "^1",<% } %> - "@nanoforge-dev/sound": "^1",<% if (lint) { %> - "@typescript-eslint/eslint-plugin": "^8.46.2", - "@typescript-eslint/parser": "^8.46.2", - "eslint": "^9.39.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "globals": "^16.5.0", - "lint-staged": "^16.2.6", - "prettier": "^3.6.2",<% } %> - "typescript": "^5.9.3"<% if (lint) { %>, - "typescript-eslint": "^8.46.2"<% } %> - },<% if (lint) { %> - "lint-staged": { - "**/*.ts": [ - "prettier --write" - ], - "{client,server}/**/*.ts": [ - "eslint --fix" - ] - },<% } %><% if (packageManager === "pnpm") { %> - "pnpm": { - "neverBuiltDependencies": [] - },<% } %> - "private": true -} diff --git a/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template b/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template deleted file mode 100644 index 236b123..0000000 --- a/libs/schematics/src/libs/application/files/ts/pnpm-workspace.yaml.template +++ /dev/null @@ -1,3 +0,0 @@ -allowBuilds: - bun: true<% if (server) { %> - wrtc: true<% } %> diff --git a/libs/schematics/src/libs/application/files/ts/prettier.config.js b/libs/schematics/src/libs/application/files/ts/prettier.config.js deleted file mode 100644 index 8e5015d..0000000 --- a/libs/schematics/src/libs/application/files/ts/prettier.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - useTabs: false, - singleQuote: false, - trailingComma: "all", - printWidth: 100, -}; diff --git a/libs/schematics/src/libs/application/schema.json b/libs/schematics/src/libs/application/schema.json deleted file mode 100644 index d570149..0000000 --- a/libs/schematics/src/libs/application/schema.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "SchematicsNanoForgeBaseApplication", - "title": "NanoForge Base Application Options Schema", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the application", - "default": "nanoforge-app" - }, - "version": { - "type": "string", - "description": "NanoForge application version", - "default": "0.0.0" - }, - "author": { - "type": "string", - "description": "NanoForge application author", - "default": "" - }, - "description": { - "type": "string", - "description": "NanoForge application description", - "default": "" - }, - "directory": { - "type": "string", - "description": "NanoForge application destination directory" - }, - "language": { - "type": "string", - "enum": ["ts", "js"], - "description": "NanoForge application language", - "default": "ts" - }, - "strict": { - "type": "boolean", - "description": "With strict mode (TypeScript or JavaScript but only on compatible editor)", - "default": true - }, - "lint": { - "type": "boolean", - "description": "Generate lint files", - "default": true - }, - "packageManager": { - "type": "string", - "enum": ["npm", "yarn", "pnpm", "bun"], - "description": "NanoForge application package manager", - "default": "npm" - }, - "server": { - "type": "boolean", - "description": "Configure a server for the application", - "default": false - }, - "editor": { - "type": "boolean", - "description": "Add editor dependencies", - "default": false - }, - "initFunctions": { - "type": "boolean", - "description": "Add init functions to the application", - "default": false - } - }, - "required": ["name"] -} diff --git a/libs/schematics/src/libs/configuration/configuration.factory.ts b/libs/schematics/src/libs/configuration/configuration.factory.ts deleted file mode 100644 index a4a0a17..0000000 --- a/libs/schematics/src/libs/configuration/configuration.factory.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { type Path, join, normalize, strings } from "@angular-devkit/core"; -import { - type Rule, - type Source, - type Tree, - apply, - branchAndMerge, - chain, - mergeWith, - move, - template, - url, -} from "@angular-devkit/schematics"; - -import { ConfigDeclarator } from "@utils/config/config.declarator"; -import { ConfigFinder } from "@utils/config/config.finder"; -import { type Config } from "@utils/config/config.type"; -import { type DeepPartial } from "@utils/types"; - -import { type ConfigurationOptions } from "./configuration.options"; -import { type ConfigurationSchema } from "./configuration.schema"; - -const transform = (schema: ConfigurationSchema): ConfigurationOptions => { - void schema; - - return {}; -}; - -const generate = (options: ConfigurationOptions, path: string): Source => { - return apply(url(join("./files" as Path)), [ - template({ - ...strings, - ...options, - }), - move(normalize(path)), - ]); -}; - -const addConfiguration = (options: DeepPartial, path: Path) => { - return (tree: Tree) => { - const config = new ConfigFinder(tree).find(path); - if (!config) return tree; - - const content = tree.read(config)?.toString(); - const declarator = new ConfigDeclarator(); - - if (!content) return tree; - - tree.overwrite(config, declarator.declare(content, options)); - return tree; - }; -}; - -const getConfig = (schema: ConfigurationSchema): DeepPartial => { - const res: DeepPartial = { - name: schema.name, - language: schema.language, - client: { - enable: true, - }, - server: { - enable: schema.server ?? false, - }, - }; - - if (schema.initFunctions) { - res["initFunctions"] = true; - } - - if (schema.language === "js") { - if ("client" in res && res.client) { - res.client["build"] = { entry: "client/main.js" }; - res.client["editor"] = { entry: ".nanoforge/editor/client/main.js" }; - } - if (schema.server && "server" in res && res.server) { - res.server["build"] = { entry: "server/main.js" }; - res.server["editor"] = { entry: ".nanoforge/editor/client/main.js" }; - } - } - - return res; -}; - -export const main = (schema: ConfigurationSchema): Rule => { - const options = transform(schema); - const directory = schema.directory; - - return branchAndMerge( - chain([ - mergeWith(generate(options, directory)), - addConfiguration(getConfig(schema), directory as Path), - ]), - ); -}; diff --git a/libs/schematics/src/libs/configuration/configuration.options.d.ts b/libs/schematics/src/libs/configuration/configuration.options.d.ts deleted file mode 100644 index 3061ac5..0000000 --- a/libs/schematics/src/libs/configuration/configuration.options.d.ts +++ /dev/null @@ -1 +0,0 @@ -export interface ConfigurationOptions {} diff --git a/libs/schematics/src/libs/configuration/configuration.schema.d.ts b/libs/schematics/src/libs/configuration/configuration.schema.d.ts deleted file mode 100644 index fb569f6..0000000 --- a/libs/schematics/src/libs/configuration/configuration.schema.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -export interface ConfigurationSchema { - /** - * The name of the application - */ - name: string; - - /** - * NanoForge application destination directory - */ - directory: string; - - /** - * Configure a server for the application - */ - server: boolean; - - /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * Add init functions to the application - */ - initFunctions: boolean; -} diff --git a/libs/schematics/src/libs/configuration/files/nanoforge.config.json b/libs/schematics/src/libs/configuration/files/nanoforge.config.json deleted file mode 100644 index 0967ef4..0000000 --- a/libs/schematics/src/libs/configuration/files/nanoforge.config.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/libs/schematics/src/libs/configuration/schema.json b/libs/schematics/src/libs/configuration/schema.json deleted file mode 100644 index 427d3da..0000000 --- a/libs/schematics/src/libs/configuration/schema.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "SchematicsNanoForgeConfiguration", - "title": "NanoForge Base Application Options Schema", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the application", - "default": "nanoforge-app" - }, - "directory": { - "type": "string", - "description": "NanoForge application destination directory", - "default": "." - }, - "server": { - "type": "boolean", - "description": "Server configuration", - "default": false - }, - "language": { - "type": "string", - "enum": ["ts", "js"], - "description": "NanoForge application language", - "default": "ts" - }, - "initFunctions": { - "type": "boolean", - "description": "Add init functions to the main application", - "default": false - } - } -} diff --git a/libs/schematics/src/libs/docker/docker.factory.ts b/libs/schematics/src/libs/docker/docker.factory.ts deleted file mode 100644 index 34909a4..0000000 --- a/libs/schematics/src/libs/docker/docker.factory.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { type Path, normalize, strings } from "@angular-devkit/core"; -import { - type Rule, - type Source, - apply, - mergeWith, - move, - template, - url, -} from "@angular-devkit/schematics"; - -import { DEFAULT_PACKAGE_MANAGER } from "~/defaults"; - -import { type DockerOptions } from "./docker.options"; -import { type DockerSchema } from "./docker.schema"; - -const transform = (schema: DockerSchema): DockerOptions => { - return { - packageManager: schema.packageManager ?? DEFAULT_PACKAGE_MANAGER, - }; -}; - -const generate = (options: DockerOptions, path: string): Source => { - return apply(url("./files" as Path), [ - template({ - ...strings, - ...options, - }), - move(normalize(path)), - ]); -}; - -export const main = (schema: DockerSchema): Rule => { - const options = transform(schema); - return mergeWith(generate(options, schema.directory)); -}; diff --git a/libs/schematics/src/libs/docker/docker.options.d.ts b/libs/schematics/src/libs/docker/docker.options.d.ts deleted file mode 100644 index 489d255..0000000 --- a/libs/schematics/src/libs/docker/docker.options.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface DockerOptions { - /** - * NanoForge application package manager - */ - packageManager: "npm" | "yarn" | "pnpm" | "bun"; -} diff --git a/libs/schematics/src/libs/docker/docker.schema.d.ts b/libs/schematics/src/libs/docker/docker.schema.d.ts deleted file mode 100644 index f3c29da..0000000 --- a/libs/schematics/src/libs/docker/docker.schema.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface DockerSchema { - /** - * Target directory for generated Docker files - */ - directory: string; - - /** - * NanoForge application package manager - */ - packageManager: "npm" | "yarn" | "pnpm" | "bun"; -} diff --git a/libs/schematics/src/libs/docker/files/.dockerignore b/libs/schematics/src/libs/docker/files/.dockerignore deleted file mode 100644 index c2658d7..0000000 --- a/libs/schematics/src/libs/docker/files/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libs/schematics/src/libs/docker/files/Dockerfile b/libs/schematics/src/libs/docker/files/Dockerfile deleted file mode 100644 index 62571d5..0000000 --- a/libs/schematics/src/libs/docker/files/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -<% if (packageManager === 'bun') { %>FROM oven/bun:1.3<% } else { %>FROM node:25-slim<% } %> -<% if (packageManager === 'pnpm') { %> -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN npm install -g pnpm -<% } %><% if (packageManager === 'yarn') { %> -RUN npm install -g yarn -<% } %> -WORKDIR /app - -COPY package.json ./ -<% if (packageManager === 'npm') { %>COPY package-lock.json ./ -<% } else if (packageManager === 'yarn') { %>COPY yarn.lock ./ -<% } else if (packageManager === 'pnpm') { %>COPY pnpm-lock.yaml ./ -<% } else if (packageManager === 'bun') { %>COPY bun.lock ./ -<% } %> -<% if (packageManager === 'npm') { %>RUN npm install --no-lockfile -<% } else if (packageManager === 'pnpm') { %>RUN pnpm install --frozen-lockfile --allow-build=bun -<% } else { %>RUN <%= packageManager %> install --frozen-lockfile -<% } %> -COPY . . - -RUN <%= packageManager %> run build - -CMD ["<%= packageManager %>", "run", "start"] diff --git a/libs/schematics/src/libs/docker/schema.json b/libs/schematics/src/libs/docker/schema.json deleted file mode 100644 index 5055696..0000000 --- a/libs/schematics/src/libs/docker/schema.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "SchematicsNanoForgeDocker", - "title": "NanoForge Base Docker Options Schema", - "type": "object", - "properties": { - "directory": { - "type": "string", - "description": "NanoForge application destination directory", - "default": "." - }, - "packageManager": { - "type": "string", - "enum": ["npm", "yarn", "pnpm", "bun"], - "description": "NanoForge application package manager", - "default": "npm" - } - } -} diff --git a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json deleted file mode 100644 index 843e417..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/client.save.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "libraries": [ - { - "id": "assetManagerLibrary", - "type": "asset-manager", - "name": "AssetManagerLibrary", - "path": "@nanoforge-dev/asset-manager", - "params": [] - }, - { - "id": "ecsLibrary", - "type": "component-system", - "name": "ECSClientLibrary", - "path": "@nanoforge-dev/ecs-client", - "params": [] - }, - { - "id": "graphicsLibrary", - "type": "graphics", - "name": "Graphics2DLibrary", - "path": "@nanoforge-dev/graphics-2d", - "params": [] - }, - { - "id": "inputLibrary", - "type": "input", - "name": "InputLibrary", - "path": "@nanoforge-dev/input", - "params": [] - }, - { - "id": "musicLibrary", - "type": "music", - "name": "MusicLibrary", - "path": "@nanoforge-dev/music", - "params": [] - },<% if (server) { %> - { - "id": "networkLibrary", - "type": "network", - "name": "NetworkClientLibrary", - "path": "@nanoforge-dev/network-client", - "params": [] - },<% } %> - { - "id": "soundLibrary", - "type": "sound", - "name": "SoundLibrary", - "path": "@nanoforge-dev/sound", - "params": [] - } - ], - "components": [ - { - "name": "ExampleComponent", - "path": "./components/example.component.js", - "paramsNames": ["paramA", "paramB", "paramC"] - } - ], - "systems": [ - { - "name": "exampleSystem", - "path": "./systems/example.system.js" - } - ], - "entities": [ - { - "id": "exampleEntity", - "components": { - "ExampleComponent": { - "paramA": "example", - "paramB": 10 - } - } - } - ] -} diff --git a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json b/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json deleted file mode 100644 index ba77067..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__nanoforgeFolder__/server.save.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "libraries": [ - { - "id": "assetManagerLibrary", - "type": "asset-manager", - "name": "AssetManagerLibrary", - "path": "@nanoforge-dev/asset-manager", - "params": [] - }, - { - "id": "ecsLibrary", - "type": "component-system", - "name": "ECSServerLibrary", - "path": "@nanoforge-dev/ecs-server", - "params": [] - }<% if (server) { %>, - { - "id": "networkLibrary", - "type": "network", - "name": "NetworkServerLibrary", - "path": "@nanoforge-dev/network-server", - "params": [] - }<% } %> - ], - "components": [ - { - "name": "ExampleComponent", - "path": "./components/example.component.js", - "paramsNames": ["paramA", "paramB", "paramC"] - } - ], - "systems": [ - { - "name": "exampleSystem", - "path": "./systems/example.system.js" - } - ], - "entities": [ - { - "id": "exampleEntity", - "components": { - "ExampleComponent": { - "paramA": "example", - "paramB": 10 - } - } - } - ] -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js b/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js deleted file mode 100644 index bf9728b..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/components/example.component.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/ecs-<%= part %>").EditorComponentManifest} EditorComponentManifest - */ - -export class ExampleComponent { - static name = "ExampleComponent"; - name = this.constructor.name; - - paramA; - paramB; - paramC; - - /** - * Example 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 ExampleComponent.name; - -/** - * Editor component manifest - * * Required for the editor to display the component and generate code - * @type {EditorComponentManifest} - */ -export const EDITOR_COMPONENT_MANIFEST = { - name: "Example", - description: "Example 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/part-base/files/js/__part__/init/after-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js deleted file mode 100644 index 74d2124..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-init.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - */ - -/** - * Actions that are executed after the application initialization - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @returns {Promise} - */ -export async function afterInit(app) { - // Actions after app initialization -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js deleted file mode 100644 index 8de3bf6..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-registry-init.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - * @typedef {import("@nanoforge-dev/ecs").Registry} Registry - */ - -/** - * Actions that are executed after the registry components, entities and systems initialization - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @param {Registry} registry - ECS registry instance - * @returns {Promise} - */ -export async function afterRegistryInit(app, registry) { - // Actions after registry components, entities and systems initialization -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js deleted file mode 100644 index e21bbca..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/after-run.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - */ - -/** - * Actions that are executed when the application ended - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @returns {Promise} - */ -export async function afterRun(app) { - // Actions after app ended -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js deleted file mode 100644 index 602b388..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-init.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - */ - -/** - * Actions that are executed before the application initialization - * But are executed after base libraries addition - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @returns {Promise} - */ -export async function beforeInit(app) { - // Actions before app initialization -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js deleted file mode 100644 index ae6c374..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-registry-init.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - * @typedef {import("@nanoforge-dev/ecs").Registry} Registry - */ - -/** - * Actions that are executed before the registry components, entities and systems initialization - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @param {Registry} registry - ECS registry instance - * @returns {Promise} - */ -export async function beforeRegistryInit(app, registry) { - // Actions before registry components, entities and systems initialization -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js b/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js deleted file mode 100644 index e77e47a..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/init/before-run.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @typedef {import("@nanoforge-dev/core").<%= appClass %>} <%= appClass %> - */ - -/** - * Actions that are executed before the application started - * @param {<%= appClass %>} app - Nanoforge <%= part %> instance - * @returns {Promise} - */ -export async function beforeRun(app) { - // Actions before app started -} diff --git a/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js b/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js deleted file mode 100644 index c178bfa..0000000 --- a/libs/schematics/src/libs/part-base/files/js/__part__/systems/example.system.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @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"; - -/** - * Example 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 exampleSystem = (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 exampleSystem.name; - -/** - * Editor component manifest - * * Required for the editor to display the system and generate code - * @type {EditorSystemManifest} - */ -export const EDITOR_SYSTEM_MANIFEST = { - name: "Example", - description: - "This system end the game when paramB reaches 0 for any entity with ExampleComponent", - dependencies: ["ExampleComponent"], -}; diff --git a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json deleted file mode 100644 index c3be787..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "libraries": [ - { - "id": "assetManagerLibrary", - "type": "asset-manager", - "name": "AssetManagerLibrary", - "path": "@nanoforge-dev/asset-manager", - "params": [] - }, - { - "id": "ecsLibrary", - "type": "component-system", - "name": "ECSClientLibrary", - "path": "@nanoforge-dev/ecs-client", - "params": [] - }, - { - "id": "graphicsLibrary", - "type": "graphics", - "name": "Graphics2DLibrary", - "path": "@nanoforge-dev/graphics-2d", - "params": [] - }, - { - "id": "inputLibrary", - "type": "input", - "name": "InputLibrary", - "path": "@nanoforge-dev/input", - "params": [] - }, - { - "id": "musicLibrary", - "type": "music", - "name": "MusicLibrary", - "path": "@nanoforge-dev/music", - "params": [] - },<% if (server) { %> - { - "id": "networkLibrary", - "type": "network", - "name": "NetworkClientLibrary", - "path": "@nanoforge-dev/network-client", - "params": [] - },<% } %> - { - "id": "soundLibrary", - "type": "sound", - "name": "SoundLibrary", - "path": "@nanoforge-dev/sound", - "params": [] - } - ], - "components": [ - { - "name": "ExampleComponent", - "path": "./components/example.component.ts", - "paramsNames": ["paramA", "paramB", "paramC"] - } - ], - "systems": [ - { - "name": "exampleSystem", - "path": "./systems/example.system.ts" - } - ], - "entities": [ - { - "id": "exampleEntity", - "components": { - "ExampleComponent": { - "paramA": "example", - "paramB": 10 - } - } - } - ] -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json b/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json deleted file mode 100644 index 47f7bba..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "libraries": [ - { - "id": "assetManagerLibrary", - "type": "asset-manager", - "name": "AssetManagerLibrary", - "path": "@nanoforge-dev/asset-manager", - "params": [] - }, - { - "id": "ecsLibrary", - "type": "component-system", - "name": "ECSServerLibrary", - "path": "@nanoforge-dev/ecs-server", - "params": [] - }<% if (server) { %>, - { - "id": "networkLibrary", - "type": "network", - "name": "NetworkServerLibrary", - "path": "@nanoforge-dev/network-server", - "params": [] - }<% } %> - ], - "components": [ - { - "name": "ExampleComponent", - "path": "./components/example.component.ts", - "paramsNames": ["paramA", "paramB", "paramC"] - } - ], - "systems": [ - { - "name": "exampleSystem", - "path": "./systems/example.system.ts" - } - ], - "entities": [ - { - "id": "exampleEntity", - "components": { - "ExampleComponent": { - "paramA": "example", - "paramB": 10 - } - } - } - ] -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts deleted file mode 100644 index 4f98934..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/components/example.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { type EditorComponentManifest } from "@nanoforge-dev/ecs-<%= part %>"; - -export class ExampleComponent { - 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 ExampleComponent.name; - -// * Required for the editor to display the component and generate code -export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = { - name: "Example", - description: "Example 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/part-base/files/ts/__part__/init/after-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts deleted file mode 100644 index c5c3cee..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-init.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; - -export async function afterInit(app: <%= appClass %>) { - // Actions after app initialization -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts deleted file mode 100644 index 8233635..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-registry-init.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; -import { type Registry } from "@nanoforge-dev/ecs"; - -export async function afterRegistryInit(app: <%= appClass %>, registry: Registry) { - // Actions after registry components, entities and systems initialization -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts deleted file mode 100644 index 1d72689..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/after-run.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; - -export async function afterRun(app: <%= appClass %>) { - // Actions after app ended -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts deleted file mode 100644 index b45b2b8..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-init.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; - -export async function beforeInit(app: <%= appClass %>) { - // Actions before app initialization -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts deleted file mode 100644 index 91eda4a..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-registry-init.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; -import { type Registry } from "@nanoforge-dev/ecs"; - -export async function beforeRegistryInit(app: <%= appClass %>, registry: Registry) { - // Actions before registry components, entities and systems initialization -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts deleted file mode 100644 index 2e0c679..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/init/before-run.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type <%= appClass %> } from "@nanoforge-dev/core"; - -export async function beforeRun(app: <%= appClass %>) { - // Actions before app started -} diff --git a/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts b/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts deleted file mode 100644 index 10162b1..0000000 --- a/libs/schematics/src/libs/part-base/files/ts/__part__/systems/example.system.ts +++ /dev/null @@ -1,31 +0,0 @@ -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 exampleSystem = (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 exampleSystem.name; - -// * Required for the editor to display the system and generate code -export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = { - name: "Example", - description: - "This system end the game when paramB reaches 0 for any entity with ExampleComponent", - dependencies: ["ExampleComponent"], -}; diff --git a/libs/schematics/src/libs/part-base/part-base.factory.ts b/libs/schematics/src/libs/part-base/part-base.factory.ts deleted file mode 100644 index 2144656..0000000 --- a/libs/schematics/src/libs/part-base/part-base.factory.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { type Path, join, normalize, strings } from "@angular-devkit/core"; -import { - type Rule, - type Source, - apply, - filter, - mergeWith, - move, - template, - url, -} from "@angular-devkit/schematics"; - -import { type PartBaseOptions } from "./part-base.options"; -import { type PartBaseSchema } from "./part-base.schema"; - -const transform = (schema: PartBaseSchema): PartBaseOptions => { - return { - part: schema.part, - server: schema.server, - appClass: schema.part === "client" ? "NanoforgeClient" : "NanoforgeServer", - nanoforgeFolder: ".nanoforge", - }; -}; - -const generate = ( - options: PartBaseOptions, - path: string, - initFunctions: boolean, - language: "ts" | "js", -): Source => { - const rules = [ - template({ - ...strings, - ...options, - }), - move(normalize(path)), - filter((path) => { - const splited = path.split("/"); - return splited.at(-2) !== ".nanoforge" || splited.at(-1) === `${options.part}.save.json`; - }), - ]; - if (!initFunctions) rules.push(filter((path) => path.split("/").at(-2) !== "init")); - - return apply(url(join("./files" as Path, language)), rules); -}; - -export const main = (schema: PartBaseSchema): Rule => { - const options = transform(schema); - - return mergeWith(generate(options, schema.directory, schema.initFunctions, schema.language)); -}; diff --git a/libs/schematics/src/libs/part-base/part-base.options.d.ts b/libs/schematics/src/libs/part-base/part-base.options.d.ts deleted file mode 100644 index 9e5934b..0000000 --- a/libs/schematics/src/libs/part-base/part-base.options.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface PartBaseOptions { - /** - * The part of the application to generate - */ - part: "client" | "server"; - - /** - * App class name - */ - appClass: string; - - /** - * Configure a server for the application - */ - server: boolean; - - /** - * Name of Nanoforge folder - */ - nanoforgeFolder: string; -} diff --git a/libs/schematics/src/libs/part-base/part-base.schema.d.ts b/libs/schematics/src/libs/part-base/part-base.schema.d.ts deleted file mode 100644 index d708ed0..0000000 --- a/libs/schematics/src/libs/part-base/part-base.schema.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -export interface PartBaseSchema { - /** - * NanoForge application destination directory - */ - directory: string; - - /** - * The part of the application to generate - */ - part: "client" | "server"; - - /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * Add init functions to the application - */ - initFunctions: boolean; - - /** - * Configure a server for the application - */ - server: boolean; -} diff --git a/libs/schematics/src/libs/part-base/schema.json b/libs/schematics/src/libs/part-base/schema.json deleted file mode 100644 index 95d3bc1..0000000 --- a/libs/schematics/src/libs/part-base/schema.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "SchematicsNanoForgePartBase", - "title": "NanoForge Part Base Options Schema", - "type": "object", - "properties": { - "directory": { - "type": "string", - "description": "NanoForge application 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" - }, - "initFunctions": { - "type": "boolean", - "description": "Add init functions to the application", - "default": false - }, - "server": { - "type": "boolean", - "description": "There is a Server in app", - "default": false - } - }, - "required": ["part"] -} diff --git a/libs/schematics/src/libs/part-main/part-main.factory.ts b/libs/schematics/src/libs/part-main/part-main.factory.ts deleted file mode 100644 index 1bd9770..0000000 --- a/libs/schematics/src/libs/part-main/part-main.factory.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { type Path, join, normalize, strings } from "@angular-devkit/core"; -import { - type Rule, - type Source, - type Tree, - apply, - asSource, - mergeWith, - move, - template, -} from "@angular-devkit/schematics"; -import * as fs from "fs"; - -import { generateMain } from "@utils/main/main-functions"; -import { type Save, SaveLibraryTypeEnum } from "@utils/main/save.type"; - -import { type PartMainOptions } from "./part-main.options"; -import { type PartMainSchema } from "./part-main.schema"; - -const transform = (schema: PartMainSchema): PartMainOptions => { - void schema; - - return {}; -}; - -const generate = (options: PartMainOptions, path: string, schema: PartMainSchema): Source => { - const rules = [ - template({ - ...strings, - ...options, - }), - move(normalize(path)), - ]; - - return apply(asSource(writeMain(schema, path)), rules); -}; - -const getMainPath = (schema: PartMainSchema): string => { - if (schema.outFile) return schema.outFile; - if (schema.editor) - return join(".nanoforge/editor" as Path, schema.part, `main.${schema.language}`); - return join(schema.part as Path, `main.${schema.language}`); -}; - -const writeMain = (schema: PartMainSchema, path: string) => { - return (tree: Tree) => { - let save = getSave(path, schema.part, schema.saveFile); - - const resPath = getMainPath(schema); - - if (schema.editor) { - save = getEditorSave(save); - } - - const content = generateMain(schema, save); - fs.rmSync(join(path as Path, resPath), { force: true }); - tree.create(resPath, content); - return tree; - }; -}; - -const getSave = (path: string, part: "client" | "server", saveFile?: string): Save => { - return JSON.parse( - fs.readFileSync(saveFile ?? join(path as Path, `.nanoforge/${part}.save.json`), "utf-8"), - ) as Save; -}; - -const getEditorSave = (save: Save): Save => { - if (save.libraries.some((lib) => lib.type === SaveLibraryTypeEnum.GRAPHICS)) - save.libraries.push({ - id: "graphicsEditorLibrary", - type: "graphics-editor", - name: "Graphics2DEditorLibrary", - path: "@nanoforge-dev/graphics-2d-editor", - }); - - return save; -}; - -export const main = (schema: PartMainSchema): Rule => { - const options = transform(schema); - - return mergeWith(generate(options, schema.directory, schema)); -}; diff --git a/libs/schematics/src/libs/part-main/part-main.options.d.ts b/libs/schematics/src/libs/part-main/part-main.options.d.ts deleted file mode 100644 index d16f512..0000000 --- a/libs/schematics/src/libs/part-main/part-main.options.d.ts +++ /dev/null @@ -1 +0,0 @@ -export interface PartMainOptions {} diff --git a/libs/schematics/src/libs/part-main/part-main.schema.d.ts b/libs/schematics/src/libs/part-main/part-main.schema.d.ts deleted file mode 100644 index f2a474a..0000000 --- a/libs/schematics/src/libs/part-main/part-main.schema.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface PartMainSchema { - /** - * NanoForge application destination directory - */ - directory: string; - - /** - * The part of the application to generate - */ - part: "client" | "server"; - - /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * Add init functions to the application - */ - initFunctions: boolean; - - /** - * Save file path with components and systems in JSON format - */ - saveFile?: string; - - /** - * Path of the out file - */ - outFile?: string; - - /** - * Build main editor - */ - editor: boolean; -} diff --git a/libs/schematics/src/libs/part-main/schema.json b/libs/schematics/src/libs/part-main/schema.json deleted file mode 100644 index d2f84a8..0000000 --- a/libs/schematics/src/libs/part-main/schema.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "SchematicsNanoForgePartMain", - "title": "NanoForge Part Main Options Schema", - "type": "object", - "properties": { - "directory": { - "type": "string", - "description": "NanoForge application 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" - }, - "initFunctions": { - "type": "boolean", - "description": "Add init functions to the main application", - "default": false - }, - "saveFile": { - "type": "string", - "description": "Save file path with components and systems in JSON format" - }, - "outFile": { - "type": "string", - "description": "Path of the out file" - }, - "editor": { - "type": "boolean", - "description": "Build main editor", - "default": false - } - }, - "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..2badd74 --- /dev/null +++ b/libs/schematics/src/libs/project/files/js/Dockerfile @@ -0,0 +1,39 @@ +<% 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/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..3e2e75e --- /dev/null +++ b/libs/schematics/src/libs/project/files/js/jsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/jsconfig.json", + "extends": "../../jsconfig.json", + "include": ["src/**/*.js"], + "exclude": ["node_modules", "dist", "../../.nanoforge"] +} 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..f9f079f --- /dev/null +++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from "nanoforge/config"; + +export default defineConfig({ + type: "<%= part %>",<% if (libs.length) { %> + libs: [<% libs.forEach(function (lib, i) { %>"<%= lib %>"<% if (i < libs.length - 1) { %>, <% } %><% }); %>],<% } %> +}); 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..64f7c67 --- /dev/null +++ b/libs/schematics/src/libs/project/files/js/package.json.template @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "<%= packageName %>", + "scripts": { + "dev": "nf dev", + "build": "nf build", + "start": "nf start" + }, + "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..2312ae9 --- /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<% }); %> +<% } %> 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..70fed63 --- /dev/null +++ b/libs/schematics/src/libs/project/files/ts/Dockerfile @@ -0,0 +1,39 @@ +<% 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/application/files/js/__dot__env b/libs/schematics/src/libs/project/files/ts/__dot__env similarity index 100% rename from libs/schematics/src/libs/application/files/js/__dot__env rename to libs/schematics/src/libs/project/files/ts/__dot__env diff --git a/libs/schematics/src/libs/application/files/js/__dot__gitignore b/libs/schematics/src/libs/project/files/ts/__dot__gitignore similarity index 100% rename from libs/schematics/src/libs/application/files/js/__dot__gitignore rename to libs/schematics/src/libs/project/files/ts/__dot__gitignore 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..64f7c67 --- /dev/null +++ b/libs/schematics/src/libs/project/files/ts/package.json.template @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "<%= packageName %>", + "scripts": { + "dev": "nf dev", + "build": "nf build", + "start": "nf start" + }, + "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..2312ae9 --- /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<% }); %> +<% } %> 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/application/files/ts/tsconfig.json b/libs/schematics/src/libs/project/files/ts/tsconfig.json similarity index 96% rename from libs/schematics/src/libs/application/files/ts/tsconfig.json rename to libs/schematics/src/libs/project/files/ts/tsconfig.json index cdbe237..5aef325 100644 --- a/libs/schematics/src/libs/application/files/ts/tsconfig.json +++ b/libs/schematics/src/libs/project/files/ts/tsconfig.json @@ -36,6 +36,6 @@ "target": "ESNext", "useDefineForClassFields": true, }, - "include": ["client/**/*.ts", "server/**/*.ts"], + "include": ["src"], "exclude": ["node_modules", ".nanoforge"] } 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..36df6ba --- /dev/null +++ b/libs/schematics/src/libs/project/project.factory.ts @@ -0,0 +1,135 @@ +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, + initFunctions: schema.initFunctions ?? false, + hasServer: schema.hasServer ?? false, + docker: schema.docker ?? false, + editor: schema.editor ?? false, + libs: schema.libs ?? [], + allowBuilds: workspace ? [] : (schema.allowBuilds ?? []), + }; +}; + +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..9722fa2 --- /dev/null +++ b/libs/schematics/src/libs/project/project.options.d.ts @@ -0,0 +1,92 @@ +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; + + /** + * Add init functions to the project + */ + initFunctions: boolean; + + /** + * 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; + + /** + * Relative paths to shared libs this project depends on + */ + libs: string[]; + + /** + * 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..29e10f4 --- /dev/null +++ b/libs/schematics/src/libs/project/project.schema.d.ts @@ -0,0 +1,87 @@ +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; + + /** + * Add init functions to the project + */ + initFunctions: 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; + + /** + * Relative paths to shared libs this project depends on + */ + libs: string[]; + + /** + * Package names allowed to run install/build scripts. Only applies to a + * standalone project (workspace: false) — a project generated inside a + * workspace relies on the workspace root's own `allowBuilds` instead. + * Written to pnpm-workspace.yaml's `allowBuilds` map (pnpm), package.json's + * `allowScripts` map (npm) or `trustedDependencies` array (bun). Ignored + * for yarn. + */ + allowBuilds: string[]; +} 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/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..df78c19 --- /dev/null +++ b/libs/schematics/src/libs/workspace/files/js/Dockerfile @@ -0,0 +1,39 @@ +<% 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__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/application/files/js/jsconfig.json b/libs/schematics/src/libs/workspace/files/js/jsconfig.json similarity index 95% rename from libs/schematics/src/libs/application/files/js/jsconfig.json rename to libs/schematics/src/libs/workspace/files/js/jsconfig.json index 5b67de8..f2e8ecb 100644 --- a/libs/schematics/src/libs/application/files/js/jsconfig.json +++ b/libs/schematics/src/libs/workspace/files/js/jsconfig.json @@ -30,6 +30,6 @@ "target": "ESNext", "useDefineForClassFields": true }, - "include": ["client/**/*.js", "server/**/*.js"], + "include": ["apps/*/src/**/*.js"], "exclude": ["node_modules", ".nanoforge"] } 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..e23904e --- /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<% }); %> +<% } %> 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..df78c19 --- /dev/null +++ b/libs/schematics/src/libs/workspace/files/ts/Dockerfile @@ -0,0 +1,39 @@ +<% 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/application/files/ts/__dot__gitignore b/libs/schematics/src/libs/workspace/files/ts/__dot__gitignore similarity index 97% rename from libs/schematics/src/libs/application/files/ts/__dot__gitignore rename to libs/schematics/src/libs/workspace/files/ts/__dot__gitignore index ba6da87..fc0f862 100644 --- a/libs/schematics/src/libs/application/files/ts/__dot__gitignore +++ b/libs/schematics/src/libs/workspace/files/ts/__dot__gitignore @@ -129,6 +129,6 @@ web_modules/ .pnp.* ### NanoForge template -# Compilation directories -.nanoforge/client -.nanoforge/server +# 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..e23904e --- /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<% }); %> +<% } %> 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..4ccd69c --- /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"] +} 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/config/config.declarator.spec.ts b/libs/schematics/src/utils/config/config.declarator.spec.ts deleted file mode 100644 index 6d4c34b..0000000 --- a/libs/schematics/src/utils/config/config.declarator.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { ConfigDeclarator } from "./config.declarator"; - -describe("ConfigDeclarator", () => { - it("should return a valid JSON string", () => { - const result = new ConfigDeclarator().declare("{}", {}); - expect(() => JSON.parse(result)).not.toThrow(); - }); - - it("should include default client config", () => { - const result = JSON.parse(new ConfigDeclarator().declare("{}", {})); - expect(result.client).toBeUndefined(); - }); - - it("should not include server config when server is not enabled", () => { - const result = JSON.parse(new ConfigDeclarator().declare("{}", {})); - expect(result.server).toBeUndefined(); - }); - - it("should format output with 2-space indentation", () => { - const result = new ConfigDeclarator().declare('{ "server": { "enable": true } }', {}); - expect(result).toContain("\n "); - }); - - it("should include server config when baseContent has server.enable true", () => { - const baseContent = JSON.stringify({ server: { enable: true } }); - const result = JSON.parse(new ConfigDeclarator().declare(baseContent, {})); - expect(result.server).toBeDefined(); - expect(result.server.enable).toBe(true); - }); - - it("should include server config when options has server.enable true", () => { - const result = JSON.parse(new ConfigDeclarator().declare("{}", { server: { enable: true } })); - expect(result.server).toBeDefined(); - expect(result.server.enable).toBe(true); - }); -}); diff --git a/libs/schematics/src/utils/config/config.declarator.ts b/libs/schematics/src/utils/config/config.declarator.ts deleted file mode 100644 index 1ad2405..0000000 --- a/libs/schematics/src/utils/config/config.declarator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type Config } from "@utils/config/config.type"; -import { deepMerge } from "@utils/object"; -import { type DeepPartial } from "@utils/types"; - -export class ConfigDeclarator { - public declare(baseContent: string, options: DeepPartial): string { - const content = JSON.parse(baseContent); - return JSON.stringify(deepMerge(content, options), null, 2); - } -} diff --git a/libs/schematics/src/utils/config/config.finder.spec.ts b/libs/schematics/src/utils/config/config.finder.spec.ts deleted file mode 100644 index e37a8e3..0000000 --- a/libs/schematics/src/utils/config/config.finder.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { type Path } from "@angular-devkit/core"; -import { type DirEntry, type Tree } from "@angular-devkit/schematics"; -import { describe, expect, it, vi } from "vitest"; - -import { ConfigFinder } from "./config.finder"; - -function createMockDirEntry( - path: string, - subfiles: string[], - parent: DirEntry | null = null, -): DirEntry { - return { - path: path as Path, - subfiles: subfiles as any, - subdirs: [] as any, - parent, - dir: vi.fn() as any, - file: vi.fn() as any, - visit: vi.fn() as any, - }; -} - -function createMockTree(dirEntry: DirEntry): Tree { - return { - getDir: vi.fn().mockReturnValue(dirEntry), - } as unknown as Tree; -} - -describe("ConfigFinder", () => { - it("should find nanoforge.config.json in the given directory", () => { - const dir = createMockDirEntry("/project", ["nanoforge.config.json"]); - const tree = createMockTree(dir); - const finder = new ConfigFinder(tree); - - const result = finder.find("/project" as Path); - - expect(result).toBe("/project/nanoforge.config.json"); - }); - - it("should search parent directories recursively", () => { - const parent = createMockDirEntry("/", ["nanoforge.config.json"]); - const child = createMockDirEntry("/project/src", [], parent); - const tree = createMockTree(child); - const finder = new ConfigFinder(tree); - - const result = finder.find("/project/src" as Path); - - expect(result).toBe("/nanoforge.config.json"); - }); - - it("should return null when config is not found", () => { - const root = createMockDirEntry("/", [], null); - const child = createMockDirEntry("/project", [], root); - const tree = createMockTree(child); - const finder = new ConfigFinder(tree); - - const result = finder.find("/project" as Path); - - expect(result).toBeNull(); - }); - - it("should ignore files that do not match nanoforge.config.json", () => { - const root = createMockDirEntry("/", [], null); - const dir = createMockDirEntry("/project", ["package.json", "tsconfig.json"], root); - const tree = createMockTree(dir); - const finder = new ConfigFinder(tree); - - const result = finder.find("/project" as Path); - - expect(result).toBeNull(); - }); -}); diff --git a/libs/schematics/src/utils/config/config.finder.ts b/libs/schematics/src/utils/config/config.finder.ts deleted file mode 100644 index 04ff4f4..0000000 --- a/libs/schematics/src/utils/config/config.finder.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { type Path, join } from "@angular-devkit/core"; -import { type DirEntry, type Tree } from "@angular-devkit/schematics"; - -export interface FindOptions { - path: Path; -} - -export class ConfigFinder { - constructor(private tree: Tree) {} - - public find(path: Path): Path | null { - const dir = this.tree.getDir(path); - return this.findIn(dir); - } - - /** - * Recursively searches for the module file in the given directory. - * - * @param directory - The directory to search in. - * @returns The path to the module file, or null if not found. - */ - private findIn(directory: DirEntry | null): Path | null { - if (!directory) { - return null; - } - const moduleFilename = directory.subfiles.find((filename) => - /^nanoforge.config.json$/.test(filename), - ); - return moduleFilename !== undefined - ? join(directory.path, moduleFilename.valueOf()) - : this.findIn(directory.parent); - } -} diff --git a/libs/schematics/src/utils/config/config.type.ts b/libs/schematics/src/utils/config/config.type.ts deleted file mode 100644 index a0df082..0000000 --- a/libs/schematics/src/utils/config/config.type.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface BuildConfig { - entry: string; -} - -export interface EditorConfig { - entry: string; - save: string; -} - -export interface ClientConfig { - enable: boolean; - port: string; - outDir: string; - build: BuildConfig; - editor: EditorConfig; -} - -export interface ServerConfig { - enable: boolean; - outDir: string; - build: BuildConfig; - editor: EditorConfig; -} - -export interface Config { - name: string; - language: "ts" | "js"; - initFunctions: boolean; - client: ClientConfig; - server: ServerConfig; -} diff --git a/libs/schematics/src/utils/main/const.ts b/libs/schematics/src/utils/main/const.ts deleted file mode 100644 index e685784..0000000 --- a/libs/schematics/src/utils/main/const.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SaveLibraryTypeEnum } from "@utils/main/save.type"; - -export const LIBS_FUNCTIONS_NAME: Record = { - [SaveLibraryTypeEnum.ASSET_MANAGER]: "useAssetManager", - [SaveLibraryTypeEnum.COMPONENT_SYSTEM]: "useComponentSystem", - [SaveLibraryTypeEnum.GRAPHICS]: "useGraphics", - [SaveLibraryTypeEnum.INPUT]: "useInput", - [SaveLibraryTypeEnum.NETWORK]: "useNetwork", - [SaveLibraryTypeEnum.SOUND]: "useSound", -}; diff --git a/libs/schematics/src/utils/main/enums.ts b/libs/schematics/src/utils/main/enums.ts deleted file mode 100644 index 9bbeb67..0000000 --- a/libs/schematics/src/utils/main/enums.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum InitFunctionEnum { - BEFORE_INIT = "beforeInit", - AFTER_INIT = "afterInit", - BEFORE_REGISTRY_INIT = "beforeRegistryInit", - AFTER_REGISTRY_INIT = "afterRegistryInit", - BEFORE_RUN = "beforeRun", - AFTER_RUN = "afterRun", -} diff --git a/libs/schematics/src/utils/main/main-functions.spec.ts b/libs/schematics/src/utils/main/main-functions.spec.ts deleted file mode 100644 index 0f5874f..0000000 --- a/libs/schematics/src/utils/main/main-functions.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { generateMain } from "./main-functions"; -import { type Save, SaveLibraryTypeEnum } from "./save.type"; - -const emptySave: Save = { - libraries: [], - components: [], - systems: [], - entities: [], -}; - -describe("generateMain", () => { - it("should generate a client main with TypeScript types", () => { - const result = generateMain( - { part: "client", language: "ts", initFunctions: false, editor: false }, - emptySave, - ); - expect(result).toContain("NanoforgeFactory.createClient()"); - expect(result).toContain("IRunOptions"); - expect(result).toContain("export async function main(options: IRunOptions)"); - }); - - it("should generate a server main without types", () => { - const result = generateMain( - { part: "server", language: "js", initFunctions: false, editor: false }, - emptySave, - ); - expect(result).toContain("NanoforgeFactory.createServer()"); - expect(result).not.toContain("IRunOptions"); - expect(result).toContain("export async function main(options)"); - }); - - it("should include init function imports and calls when enabled", () => { - const result = generateMain( - { part: "client", language: "ts", initFunctions: true, editor: false }, - emptySave, - ); - expect(result).toContain('import { beforeInit } from "./init/before-init";'); - expect(result).toContain("await beforeInit(app);"); - expect(result).toContain("await afterInit(app);"); - expect(result).toContain("await beforeRegistryInit(app, registry);"); - expect(result).toContain("await afterRegistryInit(app, registry);"); - expect(result).toContain("await beforeRun(app);"); - expect(result).toContain("await afterRun(app);"); - }); - - it("should not include init functions when disabled", () => { - const result = generateMain( - { part: "client", language: "ts", initFunctions: false, editor: false }, - emptySave, - ); - expect(result).not.toContain("beforeInit"); - expect(result).not.toContain("afterRun"); - }); - - it("should generate library imports, instances, and init calls", () => { - const save: Save = { - ...emptySave, - libraries: [ - { - id: "gfx", - type: SaveLibraryTypeEnum.GRAPHICS, - name: "Graphics", - path: "@nanoforge/graphics", - }, - ], - }; - const result = generateMain( - { part: "client", language: "ts", initFunctions: false, editor: false }, - save, - ); - expect(result).toContain('import { Graphics } from "@nanoforge/graphics";'); - expect(result).toContain("const gfx = new Graphics();"); - expect(result).toContain("app.useGraphics(gfx);"); - }); - - it("should generate entity and system registration", () => { - const save: Save = { - libraries: [ - { - id: "ecs", - type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, - name: "ECS", - path: "@nanoforge/ecs", - }, - ], - components: [{ name: "Position", path: "./components/position", paramsNames: ["x", "y"] }], - systems: [{ name: "PhysicsSystem", path: "./systems/physics" }], - entities: [ - { - id: "player", - components: { Position: { x: 1, y: 2 } }, - }, - ], - }; - const result = generateMain( - { part: "client", language: "ts", initFunctions: false, editor: false }, - save, - ); - expect(result).toContain("const registry = ecs.registry;"); - expect(result).toContain("const player = registry.spawnEntity();"); - expect(result).toContain("registry.addComponent(player, new Position(1, 2));"); - expect(result).toContain("registry.addSystem(PhysicsSystem);"); - }); - - it("should generate entity even in reverse order", () => { - const save: Save = { - libraries: [ - { - id: "ecs", - type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, - name: "ECS", - path: "@nanoforge/ecs", - }, - ], - components: [{ name: "Position", path: "./components/position", paramsNames: ["y", "x"] }], - systems: [{ name: "PhysicsSystem", path: "./systems/physics" }], - entities: [ - { - id: "player", - components: { Position: { x: 1, y: 2 } }, - }, - ], - }; - const result = generateMain( - { part: "client", language: "ts", initFunctions: false, editor: false }, - save, - ); - expect(result).toContain("const registry = ecs.registry;"); - expect(result).toContain("const player = registry.spawnEntity();"); - expect(result).toContain("registry.addComponent(player, new Position(2, 1));"); - expect(result).toContain("registry.addSystem(PhysicsSystem);"); - }); -}); diff --git a/libs/schematics/src/utils/main/main-functions.ts b/libs/schematics/src/utils/main/main-functions.ts deleted file mode 100644 index 23c7e46..0000000 --- a/libs/schematics/src/utils/main/main-functions.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { join } from "path"; - -import { InitFunctionEnum } from "@utils/main/enums"; - -import { MainGenerator } from "./main.generator"; -import { type Save } from "./save.type"; - -export interface MainOptions { - part: "client" | "server"; - language: "js" | "ts"; - initFunctions: boolean; - editor: boolean; - outFile?: string; -} - -export const generateMain = (options: MainOptions, save: Save) => { - const isServer = options.part === "server"; - const hasTypes = options.language === "ts"; - const initFunctions = options.initFunctions; - - return ( - !options.editor - ? new MainGenerator() - : new MainGenerator( - true, - join( - "../".repeat(options.outFile ? options.outFile.split("/").length - 1 : 3), - options.part, - ), - ) - ) - .generateBaseImports(hasTypes) - .generateLibsImports(save.libraries) - .generateInitFunctionsImportsIfNeeded(initFunctions) - .generateComponentsImports(save.components) - .generateSystemsImports(save.systems) - - .generateMainFunction(hasTypes, (generator) => { - generator - .generateApp(isServer) - - .generateLibsInstances(save.libraries) - .generateLibsInit(save.libraries) - - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_INIT) - .generateAppInit() - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_INIT) - - .generateRegistry(save.libraries) - - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_REGISTRY_INIT) - .generateEntities(save.libraries, save.components, save.entities) - .generateSystems(save.systems) - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_REGISTRY_INIT) - - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.BEFORE_RUN) - .generateAppRun(initFunctions) - .generateInitFunctionIfNeeded(initFunctions, InitFunctionEnum.AFTER_RUN); - }) - .toString(); -}; diff --git a/libs/schematics/src/utils/main/main-utils.ts b/libs/schematics/src/utils/main/main-utils.ts deleted file mode 100644 index 05c79e0..0000000 --- a/libs/schematics/src/utils/main/main-utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getStringParam = (value: any): string => { - if (typeof value === "string") return `"${value}"`; - if (typeof value === "number") return value.toString(); - if (typeof value === "boolean") return value ? "true" : "false"; - if (typeof value === "object") return JSON.stringify(value); - return "undefined"; -}; diff --git a/libs/schematics/src/utils/main/main.generator.spec.ts b/libs/schematics/src/utils/main/main.generator.spec.ts deleted file mode 100644 index 2cd0223..0000000 --- a/libs/schematics/src/utils/main/main.generator.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { MainGenerator } from "./main.generator"; -import { SaveLibraryTypeEnum } from "./save.type"; - -describe("MainGenerator", () => { - it("should start with an empty buffer", () => { - expect(new MainGenerator().toString()).toBe(""); - }); - - describe("generateBaseImports", () => { - it("should include type import when hasTypes is true", () => { - const result = new MainGenerator().generateBaseImports(true).toString(); - expect(result).toContain('import { type IRunOptions } from "@nanoforge-dev/common";'); - expect(result).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core";'); - }); - - it("should omit type import when hasTypes is false", () => { - const result = new MainGenerator().generateBaseImports(false).toString(); - expect(result).not.toContain("IRunOptions"); - expect(result).toContain('import { NanoforgeFactory } from "@nanoforge-dev/core";'); - }); - }); - - describe("generateApp", () => { - it("should generate createClient call for client", () => { - const result = new MainGenerator().generateApp(false).toString(); - expect(result).toContain("NanoforgeFactory.createClient()"); - }); - - it("should generate createServer call for server", () => { - const result = new MainGenerator().generateApp(true).toString(); - expect(result).toContain("NanoforgeFactory.createServer()"); - }); - }); - - describe("generateAppInit", () => { - it("should generate await app.init(options)", () => { - const result = new MainGenerator().generateAppInit().toString(); - expect(result).toContain("await app.init(options);"); - }); - }); - - describe("generateAppRun", () => { - it("should generate await app.run()", () => { - const result = new MainGenerator().generateAppRun(false).toString(); - expect(result).toContain("await app.run();"); - }); - }); - - describe("generateMainFunction", () => { - it("should generate typed function signature when hasTypes is true", () => { - const result = new MainGenerator().generateMainFunction(true, () => {}).toString(); - expect(result).toContain("export async function main(options: IRunOptions) {"); - expect(result).toContain("}"); - }); - - it("should generate untyped function signature when hasTypes is false", () => { - const result = new MainGenerator().generateMainFunction(false, () => {}).toString(); - expect(result).toContain("export async function main(options) {"); - }); - - it("should indent content inside the function body", () => { - const result = new MainGenerator() - .generateMainFunction(false, (gen) => { - gen.generateAppInit(); - }) - .toString(); - expect(result).toContain(" await app.init(options);"); - }); - }); - - describe("generateLibsImports", () => { - it("should generate sorted import statements", () => { - const libs = [ - { - id: "gfx", - type: SaveLibraryTypeEnum.GRAPHICS, - name: "Graphics", - path: "@nanoforge/graphics", - }, - { - id: "ecs", - type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, - name: "ECS", - path: "@nanoforge/ecs", - }, - ]; - const result = new MainGenerator().generateLibsImports(libs).toString(); - const lines = result.trim().split("\n"); - expect(lines[0]).toContain("@nanoforge/ecs"); - expect(lines[1]).toContain("@nanoforge/graphics"); - }); - }); - - describe("generateLibsInstances", () => { - it("should generate const declarations for each library", () => { - const libs = [{ id: "gfx", type: SaveLibraryTypeEnum.GRAPHICS, name: "Graphics", path: "" }]; - const result = new MainGenerator().generateLibsInstances(libs).toString(); - expect(result).toContain("const gfx = new Graphics();"); - }); - }); - - describe("generateLibsInit", () => { - it("should use known init function for known library types", () => { - const libs = [{ id: "gfx", type: SaveLibraryTypeEnum.GRAPHICS, name: "Graphics", path: "" }]; - const result = new MainGenerator().generateLibsInit(libs).toString(); - expect(result).toContain("app.useGraphics(gfx);"); - }); - - it("should use app.use with Symbol for unknown library types", () => { - const libs = [{ id: "custom", type: "custom-lib", name: "Custom", path: "" }]; - const result = new MainGenerator().generateLibsInit(libs).toString(); - expect(result).toContain('app.use(Symbol("custom-lib"), custom);'); - }); - }); - - describe("generateRegistry", () => { - it("should use component-system library id for registry", () => { - const libs = [ - { id: "myEcs", type: SaveLibraryTypeEnum.COMPONENT_SYSTEM, name: "ECS", path: "" }, - ]; - const result = new MainGenerator().generateRegistry(libs).toString(); - expect(result).toContain("const registry = myEcs.registry;"); - }); - - it("should fallback to ecsLibrary when no component-system lib is found", () => { - const result = new MainGenerator().generateRegistry([]).toString(); - expect(result).toContain("const registry = ecsLibrary.registry;"); - }); - }); - - describe("generateEntities", () => { - it("should generate entity spawn and component additions", () => { - const components = [ - { - name: "Position", - path: "non", - paramsNames: ["x", "y"], - }, - { - name: "Velocity", - path: "non", - paramsNames: ["fast", "__RESERVED_ASSET_asset"], - }, - ]; - const entities = [ - { - id: "player", - components: { - Position: { - x: 1, - y: 2, - }, - Velocity: { - fast: 3, - asset: "test", - }, - }, - }, - ]; - const result = new MainGenerator().generateEntities([], components, entities).toString(); - expect(result).toContain("const player = registry.spawnEntity();"); - expect(result).toContain("registry.addComponent(player, new Position(1, 2));"); - expect(result).toContain( - 'registry.addComponent(player, new Velocity(3, assetManagerLibrary.getAsset("test")));', - ); - }); - }); - - describe("generateSystems", () => { - it("should generate addSystem calls", () => { - const systems = [ - { name: "PhysicsSystem", path: "" }, - { name: "RenderSystem", path: "" }, - ]; - const result = new MainGenerator().generateSystems(systems).toString(); - expect(result).toContain("registry.addSystem(PhysicsSystem);"); - expect(result).toContain("registry.addSystem(RenderSystem);"); - }); - }); - - describe("generateInitFunctionIfNeeded", () => { - it("should generate init function call when needed is true", () => { - const result = new MainGenerator() - .generateInitFunctionIfNeeded(true, "beforeInit" as any) - .toString(); - expect(result).toContain("await beforeInit(app);"); - }); - - it("should not generate anything when needed is false", () => { - const result = new MainGenerator() - .generateInitFunctionIfNeeded(false, "beforeInit" as any) - .toString(); - expect(result).toBe(""); - }); - - it("should pass registry param for registry init functions", () => { - const result = new MainGenerator() - .generateInitFunctionIfNeeded(true, "beforeRegistryInit" as any) - .toString(); - expect(result).toContain("await beforeRegistryInit(app, registry);"); - }); - }); - - describe("generateInitFunctionsImportsIfNeeded", () => { - it("should generate all init function imports when needed", () => { - const result = new MainGenerator().generateInitFunctionsImportsIfNeeded(true).toString(); - expect(result).toContain("afterInit"); - expect(result).toContain("afterRegistryInit"); - expect(result).toContain("afterRun"); - expect(result).toContain("beforeInit"); - expect(result).toContain("beforeRegistryInit"); - expect(result).toContain("beforeRun"); - }); - - it("should not generate imports when not needed", () => { - const result = new MainGenerator().generateInitFunctionsImportsIfNeeded(false).toString(); - expect(result).toBe(""); - }); - }); -}); diff --git a/libs/schematics/src/utils/main/main.generator.ts b/libs/schematics/src/utils/main/main.generator.ts deleted file mode 100644 index bc20904..0000000 --- a/libs/schematics/src/utils/main/main.generator.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { joinRelative } from "@utils/path"; - -import { LIBS_FUNCTIONS_NAME } from "./const"; -import { InitFunctionEnum } from "./enums"; -import { getStringParam } from "./main-utils"; -import { - type SaveComponent, - type SaveEntity, - type SaveLibrary, - SaveLibraryTypeEnum, - type SaveSystem, -} from "./save.type"; - -export class MainGenerator { - private buffer = ""; - private indentation = 0; - private readonly editor: boolean; - private readonly pathToRoot: string; - - constructor(editor: boolean = false, pathToRoot: string = "") { - this.editor = editor; - this.pathToRoot = pathToRoot; - } - - toString(): string { - return this.buffer; - } - - generateBaseImports(hasTypes: boolean): MainGenerator { - if (this.editor) { - if (hasTypes) - this.writeLine( - `import { type IEditorRunOptions, NanoforgeFactory } from "@nanoforge-dev/core-editor";`, - ); - else this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core-editor";`); - } else { - if (hasTypes) this.writeLine(`import { type IRunOptions } from "@nanoforge-dev/common";`); - this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core";`); - } - - this.endSection(); - return this; - } - - generateLibsImports(libs: SaveLibrary[]): MainGenerator { - return this.generateImports(libs, false); - } - - generateComponentsImports(libs: SaveComponent[]): MainGenerator { - return this.generateImports(libs, true); - } - - generateSystemsImports(libs: SaveSystem[]): MainGenerator { - return this.generateImports(libs, true); - } - - generateMainFunction(hasTypes: boolean, cb: (generator: MainGenerator) => void): MainGenerator { - if (this.editor) - this.writeLine( - `export async function main(options${hasTypes ? ": IEditorRunOptions" : ""}) {`, - ); - else this.writeLine(`export async function main(options${hasTypes ? ": IRunOptions" : ""}) {`); - this.indentation += 1; - cb(this); - this.indentation -= 1; - this.writeLine("}"); - return this; - } - - generateApp(isServer: boolean): MainGenerator { - this.writeLine(`const app = NanoforgeFactory.create${isServer ? "Server" : "Client"}();`); - this.endSection(); - return this; - } - - generateAppInit(): MainGenerator { - this.writeLine(`await app.init(options);`); - this.endSection(); - return this; - } - - generateAppRun(hasInitFunctions: boolean): MainGenerator { - this.writeLine(`await app.run();`); - if (hasInitFunctions) this.endSection(); - return this; - } - - generateLibsInstances(libs: SaveLibrary[]): MainGenerator { - libs.forEach(({ id, name }) => this.writeLine(`const ${id} = new ${name}();`)); - this.endSection(); - return this; - } - - generateLibsInit(libs: SaveLibrary[]): MainGenerator { - libs.forEach(({ id, type }) => this.writeLine(this.getInitFunction(id, type))); - this.endSection(); - return this; - } - - generateRegistry(libs: SaveLibrary[]): MainGenerator { - const libName = - libs.find((lib) => lib.type === SaveLibraryTypeEnum.COMPONENT_SYSTEM)?.id ?? "ecsLibrary"; - this.writeLine(`const registry = ${libName}.registry;`); - this.endSection(); - return this; - } - - generateEntities( - libs: SaveLibrary[], - components: SaveComponent[], - entities: SaveEntity[], - ): MainGenerator { - entities.forEach((entity, index) => this.generateEntity(libs, components, entity, index)); - return this; - } - - generateSystems(systems: SaveSystem[]): MainGenerator { - systems.forEach(({ name }) => this.writeLine(`registry.addSystem(${name});`)); - this.endSection(); - return this; - } - - generateInitFunctionIfNeeded(needed: boolean, func: InitFunctionEnum): MainGenerator { - if (needed) return this.generateInitFunction(func); - return this; - } - - generateInitFunctionsImportsIfNeeded(needed: boolean): MainGenerator { - if (!needed) return this; - return this.generateImports( - [ - { name: "afterInit", path: "./init/after-init" }, - { name: "afterRegistryInit", path: "./init/after-registry-init" }, - { name: "afterRun", path: "./init/after-run" }, - { name: "beforeInit", path: "./init/before-init" }, - { name: "beforeRegistryInit", path: "./init/before-registry-init" }, - { name: "beforeRun", path: "./init/before-run" }, - ], - true, - ); - } - - private generateInitFunction(func: InitFunctionEnum): MainGenerator { - const params = ["app"]; - if ( - [InitFunctionEnum.BEFORE_REGISTRY_INIT, InitFunctionEnum.AFTER_REGISTRY_INIT].includes(func) - ) - params.push("registry"); - this.writeLine(`await ${func}(${params.join(", ")});`); - if (func !== InitFunctionEnum.AFTER_RUN) this.endSection(); - return this; - } - - private generateImports(els: { name: string; path: string }[], relative: boolean): MainGenerator { - [...els] - .sort((a, b) => a.path.localeCompare(b.path)) - .forEach(({ name, path }) => { - path = path.replace(/\.(?:ts|js)$/, ""); - this.writeLine( - `import { ${name} } from "${relative ? joinRelative(this.pathToRoot, path) : path}";`, - ); - }); - this.endSection(); - return this; - } - - private generateEntity( - libs: SaveLibrary[], - components: SaveComponent[], - entity: SaveEntity, - entityIndex: number, - ): void { - this.writeLine(`const ${entity.id} = registry.spawnEntity();`); - Object.entries(entity.components).forEach(([componentName, rawParams]) => { - const originalsParams = components.find(({ name }) => name === componentName)?.paramsNames; - if (!originalsParams) - throw new Error("Missing component in entity or inexistent saved component"); - const params: string[] = originalsParams.map((ogParam) => { - let isAsset = false; - if (ogParam.startsWith("__RESERVED_ASSET_")) { - isAsset = true; - ogParam = ogParam.replace("__RESERVED_ASSET_", ""); - } - - const param = this.editor - ? `options.editor.save.entities[${entityIndex}].components["${componentName}"]["${ogParam}"]` - : getStringParam(rawParams[ogParam]); - if (isAsset) return this.getAssetParam(param, libs); - return param; - }); - this.writeLine( - `registry.addComponent(${entity.id}, new ${componentName}(${params.join(", ")}));`, - ); - }); - if (this.editor) { - this.writeLine( - `registry.addComponent(${entity.id}, { name: "__RESERVED_entityId", entityId: "${entity.id}" });`, - ); - } - this.endSection(); - } - - private getAssetParam(param: string, libs: SaveLibrary[]): string { - const libName = - libs.find((lib) => lib.type === SaveLibraryTypeEnum.ASSET_MANAGER)?.id ?? - "assetManagerLibrary"; - return `${libName}.getAsset(${param})`; - } - - private getInitFunction(name: string, type: SaveLibraryTypeEnum | string): string { - const baseName = LIBS_FUNCTIONS_NAME[type]; - if (baseName) return `app.${baseName}(${name});`; - return `app.use(Symbol("${type}"), ${name});`; - } - - private writeLine(text: string, indentation = true): void { - const line = `${" ".repeat(indentation ? this.indentation * 2 : 0)}${text}\n`; - this.buffer += line; - } - - private endSection(): void { - this.writeLine(""); - } -} diff --git a/libs/schematics/src/utils/main/save.type.ts b/libs/schematics/src/utils/main/save.type.ts deleted file mode 100644 index 36372a3..0000000 --- a/libs/schematics/src/utils/main/save.type.ts +++ /dev/null @@ -1,38 +0,0 @@ -export enum SaveLibraryTypeEnum { - COMPONENT_SYSTEM = "component-system", - GRAPHICS = "graphics", - ASSET_MANAGER = "asset-manager", - NETWORK = "network", - INPUT = "input", - SOUND = "sound", -} - -export interface SaveLibrary { - id: string; - type: SaveLibraryTypeEnum | string; - name: string; - path: string; -} - -export interface SaveComponent { - name: string; - path: string; - paramsNames: string[]; -} - -export interface SaveSystem { - name: string; - path: string; -} - -export interface SaveEntity { - id: string; - components: Record>; -} - -export interface Save { - libraries: SaveLibrary[]; - components: SaveComponent[]; - systems: SaveSystem[]; - entities: SaveEntity[]; -} diff --git a/libs/schematics/src/utils/registry.spec.ts b/libs/schematics/src/utils/registry.spec.ts new file mode 100644 index 0000000..6737080 --- /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..ab784ec --- /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) ?? 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/tsdown.config.ts b/libs/schematics/tsdown.config.ts index df494a8..e779764 100644 --- a/libs/schematics/tsdown.config.ts +++ b/libs/schematics/tsdown.config.ts @@ -34,11 +34,8 @@ function createLibTsdownConfig(name: string) { export default [ createTsdownConfig(), - createLibTsdownConfig("application"), - createLibTsdownConfig("configuration"), - createLibTsdownConfig("part-base"), - createLibTsdownConfig("part-main"), - createLibTsdownConfig("docker"), + createLibTsdownConfig("workspace"), + createLibTsdownConfig("project"), createLibTsdownConfig("component"), createLibTsdownConfig("system"), ]; From c1076395a022f66927d9f7dc91231c5bb0d280ce Mon Sep 17 00:00:00 2001 From: Exelo Date: Mon, 14 Sep 2026 03:09:53 +0900 Subject: [PATCH 05/15] fix: remove generate command as it's no longer usefull (#217) BREAKING CHANGE: The `generate` command no longer exist --- .../ISSUE_TEMPLATE/01-command_bug_report.yml | 1 - .github/ISSUE_TEMPLATE/02-feature_request.yml | 1 - .github/issue-labeler.yml | 4 - .github/labeler.yml | 6 - .github/labels.yml | 4 - README.md | 10 - docs/docs/commands/1-overview.mdx | 11 +- docs/docs/commands/2-commands/2-dev.mdx | 15 +- .../2-commands/{6-create.mdx => 5-create.mdx} | 0 docs/docs/commands/2-commands/5-generate.mdx | 39 -- .../{7-install.mdx => 6-install.mdx} | 0 .../2-commands/{8-editor.mdx => 7-editor.mdx} | 0 .../{9-registry.mdx => 8-registry.mdx} | 0 .../configuration/1-configuration-file.mdx | 218 ++++++----- docs/docs/getting-started/1-introduction.mdx | 5 +- docs/docs/getting-started/3-quickstart.mdx | 2 +- docs/docs/schematics/1-overview.mdx | 2 +- docs/guides/cli/3-develop-locally.mdx | 16 - e2e/cli-generate.test.ts | 355 ------------------ e2e/cli-help.test.ts | 14 - e2e/helpers/write-project-config.ts | 2 +- src/action/actions/dev.action.ts | 11 +- src/action/actions/generate.action.ts | 98 ----- src/action/index.ts | 1 - src/command/command.loader.ts | 3 - src/command/commands/dev.command.ts | 3 - src/command/commands/generate.command.ts | 34 -- .../input/inputs/dev/generate.input.spec.ts | 20 - src/lib/input/inputs/dev/generate.input.ts | 6 - src/lib/input/inputs/dev/index.ts | 1 - src/lib/input/inputs/index.ts | 1 - src/lib/ui/messages.ts | 6 - 32 files changed, 146 insertions(+), 743 deletions(-) rename docs/docs/commands/2-commands/{6-create.mdx => 5-create.mdx} (100%) delete mode 100644 docs/docs/commands/2-commands/5-generate.mdx rename docs/docs/commands/2-commands/{7-install.mdx => 6-install.mdx} (100%) rename docs/docs/commands/2-commands/{8-editor.mdx => 7-editor.mdx} (100%) rename docs/docs/commands/2-commands/{9-registry.mdx => 8-registry.mdx} (100%) delete mode 100644 e2e/cli-generate.test.ts delete mode 100644 src/action/actions/generate.action.ts delete mode 100644 src/command/commands/generate.command.ts delete mode 100644 src/lib/input/inputs/dev/generate.input.spec.ts delete mode 100644 src/lib/input/inputs/dev/generate.input.ts delete mode 100644 src/lib/input/inputs/dev/index.ts diff --git a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml index 30d738b..879f998 100644 --- a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml @@ -16,7 +16,6 @@ body: - build - create - dev - - generate - help - install - login diff --git a/.github/ISSUE_TEMPLATE/02-feature_request.yml b/.github/ISSUE_TEMPLATE/02-feature_request.yml index 9012528..bcf2d51 100644 --- a/.github/ISSUE_TEMPLATE/02-feature_request.yml +++ b/.github/ISSUE_TEMPLATE/02-feature_request.yml @@ -14,7 +14,6 @@ body: - build - create - dev - - generate - help - install - login diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml index af0aa6e..a154d38 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -10,10 +10,6 @@ command:dev: - "### Which command is this (bug report|feature request) for\\?\\n\\ndev\\n" -command:generate: - - "### Which command is this (bug - report|feature request) for\\?\\n\\ngenerate\\n" - command:help: - "### Which command is this (bug report|feature request) for\\?\\n\\nhelp\\n" diff --git a/.github/labeler.yml b/.github/labeler.yml index 19c2157..d0086bf 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -16,12 +16,6 @@ command:dev: - src/command/commands/dev.command.ts - src/action/actions/dev.action.ts -command:generate: - - changed-files: - - any-glob-to-any-file: - - src/command/commands/generate.command.ts - - src/action/actions/generate.action.ts - command:install: - changed-files: - any-glob-to-any-file: diff --git a/.github/labels.yml b/.github/labels.yml index 3b1e5c3..0d245ee 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -54,10 +54,6 @@ description: "Related to dev command" color: "aaa3dc" -- name: "command:generate" - description: "Related to generate command" - color: "aaa3dc" - - name: "command:help" description: "Related to help command" color: "aaa3dc" diff --git a/README.md b/README.md index 231c65d..7cb8d15 100644 --- a/README.md +++ b/README.md @@ -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. 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/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 index 0f100bb..1b14655 100644 --- a/docs/docs/schematics/1-overview.mdx +++ b/docs/docs/schematics/1-overview.mdx @@ -9,7 +9,7 @@ import { Card, CardGroup } from "mintlify/components"; 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 generate`. You can also invoke them directly via the `schematics` CLI for advanced use cases. +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 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-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..6171f70 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"); @@ -69,18 +68,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"]); @@ -113,7 +100,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/helpers/write-project-config.ts b/e2e/helpers/write-project-config.ts index e7d7953..86df865 100644 --- a/e2e/helpers/write-project-config.ts +++ b/e2e/helpers/write-project-config.ts @@ -11,7 +11,7 @@ interface WriteProjectConfigOptions { /** * `nf new` still scaffolds a `nanoforge.config.json` (schematics is out of scope for this * change), but the CLI's config loader now only reads `.ts`/`.js` config modules. Tests that - * scaffold a project and then run `build`/`generate`/`create` against it need a hand-written + * scaffold a project and then run `build`/`create` against it need a hand-written * `nanoforge.config.ts` alongside the scaffolded JSON — everything not set here falls back to * the CLI's own defaults, matching what the scaffolded JSON used to rely on too. */ 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/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/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/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/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", From 0c905f0ab0d96cfe473162df40145cd2a9a09886 Mon Sep 17 00:00:00 2001 From: Exelo Date: Tue, 15 Sep 2026 09:18:38 +0900 Subject: [PATCH 06/15] refactor(cli): change new cmd to new archi (#218) --- .../src/libs/project/files/js/Dockerfile | 9 +- .../libs/project/files/js/__dot__gitignore | 134 ++++++++++++++++++ .../src/libs/project/files/js/jsconfig.json | 38 ++++- .../libs/project/files/js/nanoforge.config.js | 3 +- .../files/js/pnpm-workspace.yaml.template | 2 +- .../src/libs/project/files/ts/Dockerfile | 9 +- .../files/ts/pnpm-workspace.yaml.template | 2 +- .../src/libs/project/files/ts/tsconfig.json | 7 +- .../src/libs/project/project.factory.ts | 6 +- .../src/libs/project/project.options.d.ts | 10 -- .../src/libs/project/project.schema.d.ts | 20 --- .../src/libs/workspace/files/js/Dockerfile | 9 +- .../ts => workspace/files/js}/__dot__env | 0 .../src/libs/workspace/files/js/jsconfig.json | 3 +- .../files/js/pnpm-workspace.yaml.template | 2 +- .../src/libs/workspace/files/ts/Dockerfile | 9 +- .../src/libs/workspace/files/ts/__dot__env | 6 + .../files/ts/pnpm-workspace.yaml.template | 2 +- .../src/libs/workspace/files/ts/tsconfig.json | 2 +- libs/schematics/src/utils/registry.ts | 2 +- src/action/actions/new.action.ts | 95 +++++-------- src/lib/input/inputs/new/index.ts | 1 - .../inputs/new/init-functions.input.spec.ts | 20 --- .../input/inputs/new/init-functions.input.ts | 6 - src/lib/schematics/nanoforge.collection.ts | 27 +--- 25 files changed, 238 insertions(+), 186 deletions(-) create mode 100644 libs/schematics/src/libs/project/files/js/__dot__gitignore rename libs/schematics/src/libs/{project/files/ts => workspace/files/js}/__dot__env (100%) create mode 100644 libs/schematics/src/libs/workspace/files/ts/__dot__env delete mode 100644 src/lib/input/inputs/new/init-functions.input.spec.ts delete mode 100644 src/lib/input/inputs/new/init-functions.input.ts diff --git a/libs/schematics/src/libs/project/files/js/Dockerfile b/libs/schematics/src/libs/project/files/js/Dockerfile index 2badd74..7b95dad 100644 --- a/libs/schematics/src/libs/project/files/js/Dockerfile +++ b/libs/schematics/src/libs/project/files/js/Dockerfile @@ -1,10 +1,7 @@ -<% if (packageManager === "bun") { %> -FROM oven/bun:1.3 AS base -<% } else if (packageManager === "pnpm") { %> -FROM ghcr.io/pnpm/pnpm:12 AS base +<% 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 +<% } else { %>FROM node:26-slim AS base <% } %><% if (packageManager === "yarn") { %> RUN npm install -g yarn <% } %> 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/jsconfig.json b/libs/schematics/src/libs/project/files/js/jsconfig.json index 3e2e75e..5653881 100644 --- a/libs/schematics/src/libs/project/files/js/jsconfig.json +++ b/libs/schematics/src/libs/project/files/js/jsconfig.json @@ -1,6 +1,36 @@ { - "$schema": "https://json.schemastore.org/jsconfig.json", - "extends": "../../jsconfig.json", - "include": ["src/**/*.js"], - "exclude": ["node_modules", "dist", "../../.nanoforge"] + "$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 index f9f079f..b05cbcb 100644 --- a/libs/schematics/src/libs/project/files/js/nanoforge.config.js +++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js @@ -1,6 +1,5 @@ import { defineConfig } from "nanoforge/config"; export default defineConfig({ - type: "<%= part %>",<% if (libs.length) { %> - libs: [<% libs.forEach(function (lib, i) { %>"<%= lib %>"<% if (i < libs.length - 1) { %>, <% } %><% }); %>],<% } %> + type: "<%= part %>", }); 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 index 2312ae9..1e7271b 100644 --- a/libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template +++ b/libs/schematics/src/libs/project/files/js/pnpm-workspace.yaml.template @@ -1,3 +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/Dockerfile b/libs/schematics/src/libs/project/files/ts/Dockerfile index 70fed63..3f3e585 100644 --- a/libs/schematics/src/libs/project/files/ts/Dockerfile +++ b/libs/schematics/src/libs/project/files/ts/Dockerfile @@ -1,10 +1,7 @@ -<% if (packageManager === "bun") { %> -FROM oven/bun:1.3 AS base -<% } else if (packageManager === "pnpm") { %> -FROM ghcr.io/pnpm/pnpm:12 AS base +<% 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 +<% } else { %>FROM node:26-slim AS base <% } %><% if (packageManager === "yarn") { %> RUN npm install -g yarn <% } %> 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 index 2312ae9..1e7271b 100644 --- a/libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template +++ b/libs/schematics/src/libs/project/files/ts/pnpm-workspace.yaml.template @@ -1,3 +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/tsconfig.json b/libs/schematics/src/libs/project/files/ts/tsconfig.json index 5aef325..70b5f64 100644 --- a/libs/schematics/src/libs/project/files/ts/tsconfig.json +++ b/libs/schematics/src/libs/project/files/ts/tsconfig.json @@ -1,5 +1,6 @@ { - "$schema": "https://json.schemastore.org/tsconfig.json", + "$schema": "https://json.schemastore.org/tsconfig.json",<% if (workspace) { %> + "extends": "../../tsconfig.json",<% } else { %> // Mapped from https://www.typescriptlang.org/tsconfig "compilerOptions": { // Type Checking @@ -35,7 +36,7 @@ "lib": ["ESNext", "esnext.disposable", "DOM"], "target": "ESNext", "useDefineForClassFields": true, - }, + },<% } %> "include": ["src"], - "exclude": ["node_modules", ".nanoforge"] + "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 index 36df6ba..dff4730 100644 --- a/libs/schematics/src/libs/project/project.factory.ts +++ b/libs/schematics/src/libs/project/project.factory.ts @@ -79,12 +79,10 @@ const transform = async (schema: ProjectSchema): Promise => { inputVersion: depVersions.input, networkVersion: depVersions.network, nanoforgeVersion: depVersions.nanoforge, - initFunctions: schema.initFunctions ?? false, - hasServer: schema.hasServer ?? false, + hasServer: schema.hasServer, docker: schema.docker ?? false, editor: schema.editor ?? false, - libs: schema.libs ?? [], - allowBuilds: workspace ? [] : (schema.allowBuilds ?? []), + allowBuilds: workspace ? [] : ["bun"], }; }; diff --git a/libs/schematics/src/libs/project/project.options.d.ts b/libs/schematics/src/libs/project/project.options.d.ts index 9722fa2..ad4a4ab 100644 --- a/libs/schematics/src/libs/project/project.options.d.ts +++ b/libs/schematics/src/libs/project/project.options.d.ts @@ -57,11 +57,6 @@ export interface ProjectOptions { /** Resolved version/protocol for the nanoforge engine package */ nanoforgeVersion: string; - /** - * Add init functions to the project - */ - initFunctions: boolean; - /** * Only meaningful when `part` is "client": whether the client syncs its * entity position from a companion server instead of owning it locally. @@ -79,11 +74,6 @@ export interface ProjectOptions { */ editor: boolean; - /** - * Relative paths to shared libs this project depends on - */ - libs: string[]; - /** * Package names allowed to run install/build scripts. Only rendered when * `workspace` is false — see {@link ProjectSchema.allowBuilds}. diff --git a/libs/schematics/src/libs/project/project.schema.d.ts b/libs/schematics/src/libs/project/project.schema.d.ts index 29e10f4..5f00a9c 100644 --- a/libs/schematics/src/libs/project/project.schema.d.ts +++ b/libs/schematics/src/libs/project/project.schema.d.ts @@ -52,11 +52,6 @@ export interface ProjectSchema { */ docker: boolean; - /** - * Add init functions to the project - */ - initFunctions: boolean; - /** * Only meaningful when `part` is "client": whether the client syncs its * entity position from a companion server instead of owning it locally. @@ -69,19 +64,4 @@ export interface ProjectSchema { * Add editor dependencies */ editor: boolean; - - /** - * Relative paths to shared libs this project depends on - */ - libs: string[]; - - /** - * Package names allowed to run install/build scripts. Only applies to a - * standalone project (workspace: false) — a project generated inside a - * workspace relies on the workspace root's own `allowBuilds` instead. - * Written to pnpm-workspace.yaml's `allowBuilds` map (pnpm), package.json's - * `allowScripts` map (npm) or `trustedDependencies` array (bun). Ignored - * for yarn. - */ - allowBuilds: string[]; } diff --git a/libs/schematics/src/libs/workspace/files/js/Dockerfile b/libs/schematics/src/libs/workspace/files/js/Dockerfile index df78c19..c28346e 100644 --- a/libs/schematics/src/libs/workspace/files/js/Dockerfile +++ b/libs/schematics/src/libs/workspace/files/js/Dockerfile @@ -1,10 +1,7 @@ -<% if (packageManager === "bun") { %> -FROM oven/bun:1.3 AS base -<% } else if (packageManager === "pnpm") { %> -FROM ghcr.io/pnpm/pnpm:12 AS base +<% 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 +<% } else { %>FROM node:26-slim AS base <% } %><% if (packageManager === "yarn") { %> RUN npm install -g yarn <% } %> diff --git a/libs/schematics/src/libs/project/files/ts/__dot__env b/libs/schematics/src/libs/workspace/files/js/__dot__env similarity index 100% rename from libs/schematics/src/libs/project/files/ts/__dot__env rename to libs/schematics/src/libs/workspace/files/js/__dot__env diff --git a/libs/schematics/src/libs/workspace/files/js/jsconfig.json b/libs/schematics/src/libs/workspace/files/js/jsconfig.json index f2e8ecb..4b7a35e 100644 --- a/libs/schematics/src/libs/workspace/files/js/jsconfig.json +++ b/libs/schematics/src/libs/workspace/files/js/jsconfig.json @@ -30,6 +30,5 @@ "target": "ESNext", "useDefineForClassFields": true }, - "include": ["apps/*/src/**/*.js"], - "exclude": ["node_modules", ".nanoforge"] + "exclude": ["node_modules", ".nanoforge", "dist"] } 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 index e23904e..34f3b9c 100644 --- a/libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template +++ b/libs/schematics/src/libs/workspace/files/js/pnpm-workspace.yaml.template @@ -3,4 +3,4 @@ packages: <% 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/Dockerfile b/libs/schematics/src/libs/workspace/files/ts/Dockerfile index df78c19..c28346e 100644 --- a/libs/schematics/src/libs/workspace/files/ts/Dockerfile +++ b/libs/schematics/src/libs/workspace/files/ts/Dockerfile @@ -1,10 +1,7 @@ -<% if (packageManager === "bun") { %> -FROM oven/bun:1.3 AS base -<% } else if (packageManager === "pnpm") { %> -FROM ghcr.io/pnpm/pnpm:12 AS base +<% 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 +<% } else { %>FROM node:26-slim AS base <% } %><% if (packageManager === "yarn") { %> RUN npm install -g yarn <% } %> 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/pnpm-workspace.yaml.template b/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template index e23904e..34f3b9c 100644 --- a/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template +++ b/libs/schematics/src/libs/workspace/files/ts/pnpm-workspace.yaml.template @@ -3,4 +3,4 @@ packages: <% 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 index 4ccd69c..5156549 100644 --- a/libs/schematics/src/libs/workspace/files/ts/tsconfig.json +++ b/libs/schematics/src/libs/workspace/files/ts/tsconfig.json @@ -36,5 +36,5 @@ "target": "ESNext", "useDefineForClassFields": true, }, - "exclude": ["node_modules", ".nanoforge"] + "exclude": ["node_modules", ".nanoforge", "dist"] } diff --git a/libs/schematics/src/utils/registry.ts b/libs/schematics/src/utils/registry.ts index ab784ec..e3de420 100644 --- a/libs/schematics/src/utils/registry.ts +++ b/libs/schematics/src/utils/registry.ts @@ -76,7 +76,7 @@ export const fetchTrustedVersion = async ( .map(([version]) => version) .sort(compareVersions); - return eligible.at(-1) ?? fallback; + return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback; } catch { return fallback; } finally { 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/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.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", From 388c258cd14eca35a26e3bc7f519aa78ac069563 Mon Sep 17 00:00:00 2001 From: Exelo Date: Fri, 18 Sep 2026 17:57:13 +0900 Subject: [PATCH 07/15] refactor: change build cmd to new archi (#219) * refactor(config): change the config handling to feat with the new archi * refactor(cli): change the build command to fit the new architecture and improve * fix: add good editor entry file --- e2e/cli-build.test.ts | 5 + e2e/cli-create.test.ts | 7 + e2e/cli-help.test.ts | 1 - libs/config/src/defaults/default-configs.ts | 22 ++- libs/config/src/index.ts | 2 + libs/config/src/merge/deep-merge.ts | 4 +- libs/config/src/merge/resolve-config.ts | 11 +- libs/config/src/parse/parse-config.ts | 3 +- libs/config/src/parse/validate-config.ts | 21 +++ libs/config/src/types/client-config.type.ts | 19 +- libs/config/src/types/index.ts | 8 +- libs/config/src/types/mixins.type.ts | 19 ++ libs/config/src/types/server-config.type.ts | 9 +- libs/config/src/types/utils.type.ts | 3 + .../libs/project/files/js/nanoforge.config.js | 4 + package.json | 5 +- pnpm-lock.yaml | 169 +++--------------- src/action/actions/build.action.ts | 130 +++++++------- src/command/commands/build.command.ts | 5 - src/lib/config/config-defaults.ts | 45 ----- src/lib/config/config-loader.spec.ts | 106 +++-------- src/lib/config/config-loader.ts | 64 ++----- src/lib/config/config.type.ts | 138 -------------- src/lib/config/index.ts | 6 +- .../config/workspace-config-parser.spec.ts | 133 ++++++++++++++ src/lib/config/workspace-config-parser.ts | 87 +++++++++ src/lib/config/workspace-config.types.ts | 24 +++ src/lib/config/workspace-loader.ts | 28 +++ 28 files changed, 535 insertions(+), 543 deletions(-) create mode 100644 libs/config/src/types/utils.type.ts delete mode 100644 src/lib/config/config-defaults.ts delete mode 100644 src/lib/config/config.type.ts create mode 100644 src/lib/config/workspace-config-parser.spec.ts create mode 100644 src/lib/config/workspace-config-parser.ts create mode 100644 src/lib/config/workspace-config.types.ts create mode 100644 src/lib/config/workspace-loader.ts diff --git a/e2e/cli-build.test.ts b/e2e/cli-build.test.ts index 82da5d5..95ba92d 100644 --- a/e2e/cli-build.test.ts +++ b/e2e/cli-build.test.ts @@ -3,6 +3,7 @@ import { resolve } from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { runCli } from "./helpers/run-cli"; +import { writeProjectConfig } from "./helpers/write-project-config"; const tmpDir = resolve(__dirname, "../.tmp-e2e-build"); @@ -38,6 +39,8 @@ describe("nf build (TypeScript, no server)", () => { "-d", projectDir, ]); + + writeProjectConfig(appDir, { name: "build-app", language: "ts", server: false }); }); it("should run the build command", async () => { @@ -145,6 +148,8 @@ describe("nf build (TypeScript, with server)", () => { "-d", projectDir, ]); + + writeProjectConfig(appDir, { name: "build-server-app", language: "ts", server: true }); }); it("should run the build command with server enabled", async () => { diff --git a/e2e/cli-create.test.ts b/e2e/cli-create.test.ts index 4e09265..b9f9cdb 100644 --- a/e2e/cli-create.test.ts +++ b/e2e/cli-create.test.ts @@ -3,6 +3,7 @@ import { resolve } from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { runCli } from "./helpers/run-cli"; +import { writeProjectConfig } from "./helpers/write-project-config"; const tmpDir = resolve(__dirname, "../.tmp-e2e-create"); @@ -38,6 +39,8 @@ describe("nf create (TypeScript, client component)", () => { "-d", projectDir, ]); + + writeProjectConfig(appDir, { name: "create-app", language: "ts", server: false }); }); it("should create a component successfully", async () => { @@ -133,6 +136,8 @@ describe("nf create (TypeScript, server component)", () => { "-d", projectDir, ]); + + writeProjectConfig(appDir, { name: "create-server-app", language: "ts", server: true }); }); it("should create a server component successfully", async () => { @@ -213,6 +218,8 @@ describe("nf create (JavaScript)", () => { "-d", projectDir, ]); + + writeProjectConfig(appDir, { name: "create-js-app", language: "js", server: false }); }); it("should create a JavaScript component successfully", async () => { diff --git a/e2e/cli-help.test.ts b/e2e/cli-help.test.ts index 6171f70..f2b8da0 100644 --- a/e2e/cli-help.test.ts +++ b/e2e/cli-help.test.ts @@ -87,7 +87,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"); }); diff --git a/libs/config/src/defaults/default-configs.ts b/libs/config/src/defaults/default-configs.ts index 26d216c..2b5c59d 100644 --- a/libs/config/src/defaults/default-configs.ts +++ b/libs/config/src/defaults/default-configs.ts @@ -1,10 +1,11 @@ import type { ClientConfig, LibConfig, ServerConfig, WorkspaceConfig } from "../types"; +import type { DeepRequired } from "../types/utils.type"; -export const defaultWorkspaceConfig: Omit = { +export const defaultWorkspaceConfig: DeepRequired> = { packages: [], }; -export const defaultLibConfig: Omit = { +export const defaultLibConfig: DeepRequired> = { dir: { assets: "assets", shared: "shared", @@ -15,7 +16,7 @@ export const defaultLibConfig: Omit = { }, }; -export const defaultClientConfig: Omit = { +export const defaultClientConfig: DeepRequired> = { entryFile: "src/main.ts", out: { dir: "dist", @@ -31,10 +32,19 @@ export const defaultClientConfig: Omit = { scenes: "src/scenes", }, + editor: { + entryFile: "src/main.ts", + }, + + port: "3000", + ssl: { + enable: false, + }, + libs: [], }; -export const defaultServerConfig: Omit = { +export const defaultServerConfig: DeepRequired> = { entryFile: "src/main.ts", out: { dir: "dist", @@ -50,5 +60,9 @@ export const defaultServerConfig: Omit = { scenes: "src/scenes", }, + editor: { + entryFile: "src/main.ts", + }, + libs: [], }; diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index c4d0991..30c1b37 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -20,9 +20,11 @@ export type { BuildableConfig, ClientConfig, ContainLibConfig, + EditorConfig, LibConfig, NanoforgeConfig, ServerConfig, SourceableConfig, + SslConfig, WorkspaceConfig, } from "./types"; diff --git a/libs/config/src/merge/deep-merge.ts b/libs/config/src/merge/deep-merge.ts index 2d20d88..0d8d912 100644 --- a/libs/config/src/merge/deep-merge.ts +++ b/libs/config/src/merge/deep-merge.ts @@ -1,3 +1,5 @@ +import type { DeepPartial } from "../types/utils.type"; + const isPlainObject = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); @@ -31,5 +33,5 @@ const mergeUnknown = ( */ export const deepMerge = >( defaults: T, - override: Partial, + 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 index 69ebf7b..6440b3b 100644 --- a/libs/config/src/merge/resolve-config.ts +++ b/libs/config/src/merge/resolve-config.ts @@ -11,24 +11,25 @@ import type { ServerConfig, WorkspaceConfig, } from "../types"; +import type { DeepRequired } from "../types/utils.type"; import { deepMerge } from "./deep-merge"; -export const resolveWorkspaceConfig = (config: WorkspaceConfig): WorkspaceConfig => ({ +export const resolveWorkspaceConfig = (config: WorkspaceConfig): DeepRequired => ({ ...deepMerge(defaultWorkspaceConfig, config), type: "workspace", }); -export const resolveLibConfig = (config: LibConfig): LibConfig => ({ +export const resolveLibConfig = (config: LibConfig): DeepRequired => ({ ...deepMerge(defaultLibConfig, config), type: "lib", }); -export const resolveClientConfig = (config: ClientConfig): ClientConfig => ({ +export const resolveClientConfig = (config: ClientConfig): DeepRequired => ({ ...deepMerge(defaultClientConfig, config), type: "client", }); -export const resolveServerConfig = (config: ServerConfig): ServerConfig => ({ +export const resolveServerConfig = (config: ServerConfig): DeepRequired => ({ ...deepMerge(defaultServerConfig, config), type: "server", }); @@ -37,7 +38,7 @@ export const resolveServerConfig = (config: ServerConfig): ServerConfig => ({ * Merges a `nanoforge.config.ts` config against the defaults matching its * `type`. */ -export const resolveConfig = (config: NanoforgeConfig): NanoforgeConfig => { +export const resolveConfig = (config: NanoforgeConfig): DeepRequired => { switch (config.type) { case "workspace": return resolveWorkspaceConfig(config); diff --git a/libs/config/src/parse/parse-config.ts b/libs/config/src/parse/parse-config.ts index d3ca6b2..466490e 100644 --- a/libs/config/src/parse/parse-config.ts +++ b/libs/config/src/parse/parse-config.ts @@ -1,9 +1,10 @@ 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 => { +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 index c504586..5132f25 100644 --- a/libs/config/src/parse/validate-config.ts +++ b/libs/config/src/parse/validate-config.ts @@ -17,6 +17,23 @@ const containLibConfigShape = { libs: z.array(z.string()).optional(), }; +const editorConfigShape = { + editor: z + .object({ + entryFile: z.string().optional(), + }) + .optional(), +}; + +const sslConfigShape = { + ssl: 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({ @@ -52,6 +69,9 @@ const clientConfigSchema = z.object({ ...sourceableConfigShape, ...buildableConfigShape, ...containLibConfigShape, + ...editorConfigShape, + ...sslConfigShape, + port: z.string().optional(), }); const serverConfigSchema = z.object({ @@ -59,6 +79,7 @@ const serverConfigSchema = z.object({ ...sourceableConfigShape, ...buildableConfigShape, ...containLibConfigShape, + ...editorConfigShape, }); const nanoforgeConfigSchema = z.discriminatedUnion("type", [ diff --git a/libs/config/src/types/client-config.type.ts b/libs/config/src/types/client-config.type.ts index bcacd00..070423c 100644 --- a/libs/config/src/types/client-config.type.ts +++ b/libs/config/src/types/client-config.type.ts @@ -1,5 +1,20 @@ import type { BaseConfig } from "./base-config.type"; -import type { BuildableConfig, ContainLibConfig, SourceableConfig } from "./mixins.type"; +import type { + BuildableConfig, + ContainLibConfig, + EditorConfig, + SourceableConfig, + SslConfig, +} from "./mixins.type"; export interface ClientConfig - extends BaseConfig<"client">, SourceableConfig, BuildableConfig, ContainLibConfig {} + extends + BaseConfig<"client">, + SourceableConfig, + BuildableConfig, + ContainLibConfig, + EditorConfig, + SslConfig { + /** 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 index fabef4c..bdc627f 100644 --- a/libs/config/src/types/index.ts +++ b/libs/config/src/types/index.ts @@ -6,7 +6,13 @@ 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, SourceableConfig } from "./mixins.type"; +export type { + BuildableConfig, + ContainLibConfig, + EditorConfig, + SourceableConfig, + SslConfig, +} from "./mixins.type"; export type { ServerConfig } from "./server-config.type"; export type { WorkspaceConfig } from "./workspace-config.type"; diff --git a/libs/config/src/types/mixins.type.ts b/libs/config/src/types/mixins.type.ts index 9c2f32b..e780b97 100644 --- a/libs/config/src/types/mixins.type.ts +++ b/libs/config/src/types/mixins.type.ts @@ -10,6 +10,25 @@ export interface BuildableConfig { }; } +export interface EditorConfig { + editor?: { + /** Entry point used to build/run this project in editor mode (`--editor`). */ + entryFile?: string; + }; +} + +export interface SslConfig { + ssl?: + | { + enable?: false; + } + | { + enable: true; + cert: string; + key: string; + }; +} + export interface SourceableConfig { dir?: { assets?: string; diff --git a/libs/config/src/types/server-config.type.ts b/libs/config/src/types/server-config.type.ts index 769e898..46ba676 100644 --- a/libs/config/src/types/server-config.type.ts +++ b/libs/config/src/types/server-config.type.ts @@ -1,5 +1,10 @@ import type { BaseConfig } from "./base-config.type"; -import type { BuildableConfig, ContainLibConfig, SourceableConfig } from "./mixins.type"; +import type { + BuildableConfig, + ContainLibConfig, + EditorConfig, + SourceableConfig, +} from "./mixins.type"; export interface ServerConfig - extends BaseConfig<"server">, SourceableConfig, BuildableConfig, ContainLibConfig {} + extends BaseConfig<"server">, SourceableConfig, BuildableConfig, ContainLibConfig, EditorConfig {} 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/schematics/src/libs/project/files/js/nanoforge.config.js b/libs/schematics/src/libs/project/files/js/nanoforge.config.js index b05cbcb..fd98e6b 100644 --- a/libs/schematics/src/libs/project/files/js/nanoforge.config.js +++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js @@ -2,4 +2,8 @@ import { defineConfig } from "nanoforge/config"; export default defineConfig({ type: "<%= part %>", + entryFile: "src/main.js",<% if (libs.length) { %> + editor: { + entryFile: "src/main.js", + }, }); diff --git a/package.json b/package.json index 834fb62..5e251ca 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,8 @@ "@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": "workspace:*", @@ -97,9 +99,6 @@ "typescript": "catalog:build", "vitest": "catalog:test" }, - "optionalDependencies": { - "@nanoforge-dev/editor": "catalog:core" - }, "packageManager": "pnpm@12.0.0", "engines": { "node": "26" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 420850e..a92edc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,9 +119,6 @@ catalogs: typescript: specifier: ^6.0.3 version: 6.0.3 - unrun: - specifier: ^0.3.0 - version: 0.3.1 ci: '@commitlint/cli': specifier: ^21.2.2 @@ -164,9 +161,6 @@ catalogs: '@nanoforge-dev/editor': specifier: ^0.1.4 version: 0.1.4 - '@types/bun': - specifier: ^1.3.14 - version: 1.4.0 '@types/node': specifier: ^26.4.0 version: 26.4.0 @@ -195,6 +189,12 @@ catalogs: 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.5.4 lint: '@nanoforge-dev/utils-eslint-config': specifier: ^1.0.2 @@ -252,6 +252,12 @@ importers: '@inquirer/prompts': specifier: catalog:cli version: 8.7.0(@types/node@26.4.0) + '@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.5.0 @@ -352,12 +358,15 @@ importers: vitest: specifier: catalog:test version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) - optionalDependencies: - '@nanoforge-dev/editor': - specifier: catalog:core - version: 0.1.4 libs/config: + dependencies: + unrun: + specifier: catalog:libs + version: 0.3.1(synckit@0.11.13) + zod: + specifier: catalog:libs + version: 4.5.4 devDependencies: '@favware/cliff-jumper': specifier: catalog:ci @@ -383,111 +392,10 @@ importers: typescript: specifier: catalog:build version: 6.0.3 - unrun: - specifier: catalog:build - version: 0.3.1(synckit@0.11.13) vitest: specifier: catalog:test version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) - libs/loader/client: - dependencies: - '@nanoforge-dev/loader-website': - specifier: workspace:* - version: link:../website - bun: - specifier: catalog:core - version: 1.4.0 - commander: - specifier: catalog:cli - version: 15.0.0 - 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) - '@types/bun': - specifier: catalog:core - version: 1.4.0 - eslint: - specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) - prettier: - specifier: catalog:lint - version: 3.9.6 - typescript: - specifier: catalog:build - version: 6.0.3 - - libs/loader/server: - dependencies: - commander: - specifier: catalog:cli - version: 15.0.0 - 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) - bun: - specifier: catalog:core - version: 1.4.0 - eslint: - specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) - prettier: - specifier: catalog:lint - version: 3.9.6 - typescript: - specifier: catalog:build - version: 6.0.3 - - libs/loader/website: - 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(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.6)(supports-color@10.2.2) - '@types/bun': - specifier: catalog:core - version: 1.4.0 - bun: - specifier: catalog:core - version: 1.4.0 - eslint: - specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) - prettier: - specifier: catalog:lint - version: 3.9.6 - typescript: - specifier: catalog:build - version: 6.0.3 - libs/schematics: dependencies: '@angular-devkit/core': @@ -1433,9 +1341,6 @@ packages: cpu: [arm64] os: [win32] - '@types/bun@1.4.0': - resolution: {integrity: sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1776,9 +1681,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - bun-types@1.4.0: - resolution: {integrity: sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q==} - bun@1.4.0: resolution: {integrity: sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw==} cpu: [arm64, x64] @@ -3135,6 +3037,9 @@ packages: yuku-parser@0.8.7: resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + snapshots: '@actions/core@3.0.1': @@ -3623,7 +3528,6 @@ snapshots: '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 - optional: true '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -3655,7 +3559,6 @@ snapshots: micromatch: 4.0.8 tar: 7.5.22 typescript: 6.0.3 - optional: true '@nanoforge-dev/loader-client@1.5.0': dependencies: @@ -3963,10 +3866,6 @@ snapshots: '@turbo/windows-arm64@2.10.12': optional: true - '@types/bun@1.4.0': - dependencies: - bun-types: 1.4.0 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -4285,11 +4184,6 @@ snapshots: braces@3.0.3: dependencies: fill-range: 7.1.1 - optional: true - - bun-types@1.4.0: - dependencies: - '@types/node': 26.4.0 bun@1.4.0: optionalDependencies: @@ -4326,8 +4220,7 @@ snapshots: dependencies: readdirp: 5.1.1 - chownr@3.0.0: - optional: true + chownr@3.0.0: {} class-transformer@0.5.1: {} @@ -4644,7 +4537,6 @@ snapshots: fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - optional: true find-index@0.1.1: {} @@ -4776,8 +4668,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: {} @@ -4939,7 +4830,6 @@ snapshots: dependencies: braces: 3.0.3 picomatch: 2.3.2 - optional: true mimic-function@5.0.1: {} @@ -4958,7 +4848,6 @@ snapshots: minizlib@3.1.0: dependencies: minipass: 7.1.3 - optional: true ms@2.1.3: {} @@ -5085,8 +4974,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.2: - optional: true + picomatch@2.3.2: {} picomatch@4.0.5: {} @@ -5262,7 +5150,6 @@ snapshots: minipass: 7.1.3 minizlib: 3.1.0 yallist: 5.0.0 - optional: true tinybench@2.9.0: {} @@ -5280,7 +5167,6 @@ snapshots: to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - optional: true tree-kill@1.2.2: {} @@ -5438,8 +5324,7 @@ snapshots: y18n@5.0.8: {} - yallist@5.0.0: - optional: true + yallist@5.0.0: {} yaml@2.9.0: optional: true @@ -5497,3 +5382,5 @@ snapshots: '@yuku-parser/binding-linux-x64-musl': 0.8.7 '@yuku-parser/binding-win32-arm64': 0.8.7 '@yuku-parser/binding-win32-x64': 0.8.7 + + zod@4.5.4: {} 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/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/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..d6da267 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 { getConfig } 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", () => { +describe("getConfig", () => { 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 getConfig("/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 getConfig("/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(getConfig("/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..9ff924f --- /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 { getConfig } 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", () => ({ + getConfig: 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(getConfig).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(getConfig).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(getConfig).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(getConfig).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(getConfig).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(getConfig).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, + })); +}; From a29592a5f1fc14d22b9772d8e8bf42ced7fffa24 Mon Sep 17 00:00:00 2001 From: Exelo Date: Fri, 18 Sep 2026 17:57:13 +0900 Subject: [PATCH 08/15] refactor(cli): change start command to fit the new architecture (#220) * fix: add good editor entry file * refactor(cli): change start command to fit the new architecture * fix(start): add extension on TLS errors and rename SSL to TLS --- README.md | 4 +- docs/docs/commands/2-commands/4-start.mdx | 4 +- e2e/helpers/write-project-config.ts | 2 +- libs/config/src/defaults/default-configs.ts | 2 +- libs/config/src/index.ts | 2 +- libs/config/src/parse/validate-config.ts | 6 +- libs/config/src/types/client-config.type.ts | 4 +- libs/config/src/types/index.ts | 2 +- libs/config/src/types/mixins.type.ts | 4 +- .../libs/project/files/js/nanoforge.config.js | 2 +- src/action/actions/start.action.ts | 208 ++++++++++-------- src/command/commands/start.command.ts | 13 +- 12 files changed, 142 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 7cb8d15..1bb959b 100644 --- a/README.md +++ b/README.md @@ -154,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` 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/e2e/helpers/write-project-config.ts b/e2e/helpers/write-project-config.ts index 86df865..9db4030 100644 --- a/e2e/helpers/write-project-config.ts +++ b/e2e/helpers/write-project-config.ts @@ -26,7 +26,7 @@ export const writeProjectConfig = ( initFunctions, client: { type: "client", enable: true }, server: { type: "server", enable: server }, - ssl: { enable: false }, + tls: { enable: false }, }, null, 2, diff --git a/libs/config/src/defaults/default-configs.ts b/libs/config/src/defaults/default-configs.ts index 2b5c59d..e164859 100644 --- a/libs/config/src/defaults/default-configs.ts +++ b/libs/config/src/defaults/default-configs.ts @@ -37,7 +37,7 @@ export const defaultClientConfig: DeepRequired> = { }, port: "3000", - ssl: { + tls: { enable: false, }, diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index 30c1b37..3f73ba0 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -25,6 +25,6 @@ export type { NanoforgeConfig, ServerConfig, SourceableConfig, - SslConfig, + TlsConfig, WorkspaceConfig, } from "./types"; diff --git a/libs/config/src/parse/validate-config.ts b/libs/config/src/parse/validate-config.ts index 5132f25..debaa39 100644 --- a/libs/config/src/parse/validate-config.ts +++ b/libs/config/src/parse/validate-config.ts @@ -25,8 +25,8 @@ const editorConfigShape = { .optional(), }; -const sslConfigShape = { - ssl: z +const tlsConfigShape = { + tls: z .union([ z.object({ enable: z.literal(false).optional() }), z.object({ enable: z.literal(true), cert: z.string(), key: z.string() }), @@ -70,7 +70,7 @@ const clientConfigSchema = z.object({ ...buildableConfigShape, ...containLibConfigShape, ...editorConfigShape, - ...sslConfigShape, + ...tlsConfigShape, port: z.string().optional(), }); diff --git a/libs/config/src/types/client-config.type.ts b/libs/config/src/types/client-config.type.ts index 070423c..0394e69 100644 --- a/libs/config/src/types/client-config.type.ts +++ b/libs/config/src/types/client-config.type.ts @@ -4,7 +4,7 @@ import type { ContainLibConfig, EditorConfig, SourceableConfig, - SslConfig, + TlsConfig, } from "./mixins.type"; export interface ClientConfig @@ -14,7 +14,7 @@ export interface ClientConfig BuildableConfig, ContainLibConfig, EditorConfig, - SslConfig { + 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 index bdc627f..bcb5ca5 100644 --- a/libs/config/src/types/index.ts +++ b/libs/config/src/types/index.ts @@ -11,7 +11,7 @@ export type { ContainLibConfig, EditorConfig, SourceableConfig, - SslConfig, + TlsConfig, } from "./mixins.type"; export type { ServerConfig } from "./server-config.type"; export type { WorkspaceConfig } from "./workspace-config.type"; diff --git a/libs/config/src/types/mixins.type.ts b/libs/config/src/types/mixins.type.ts index e780b97..5bbad3d 100644 --- a/libs/config/src/types/mixins.type.ts +++ b/libs/config/src/types/mixins.type.ts @@ -17,8 +17,8 @@ export interface EditorConfig { }; } -export interface SslConfig { - ssl?: +export interface TlsConfig { + tls?: | { enable?: false; } diff --git a/libs/schematics/src/libs/project/files/js/nanoforge.config.js b/libs/schematics/src/libs/project/files/js/nanoforge.config.js index fd98e6b..d04d057 100644 --- a/libs/schematics/src/libs/project/files/js/nanoforge.config.js +++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js @@ -2,7 +2,7 @@ import { defineConfig } from "nanoforge/config"; export default defineConfig({ type: "<%= part %>", - entryFile: "src/main.js",<% if (libs.length) { %> + entryFile: "src/main.js", editor: { entryFile: "src/main.js", }, diff --git a/src/action/actions/start.action.ts b/src/action/actions/start.action.ts index 2198e37..e131e5d 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, @@ -16,11 +22,9 @@ 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,166 @@ 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 tasks = this.buildStartTasks(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; - }, + 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(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(target, { watch, serverTargets }, env)); + } return tasks; } private async startClient( - directory: string, - config: Config, - options: { - clientDir: string; - serverDir: string; - watch: boolean; - port: string; - ssl?: SSLOptions; - }, + 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); + const params = this.buildClientParams(target, options); await this.runLoader("Client", loaderPath, params, env.client); } private async startServer( - directory: string, - config: Config, - options: { serverDir: string; watch: boolean }, + target: ServerStartTarget, + watch: boolean, env: FullEnv, ): Promise { const loaderPath = getModulePath("@nanoforge-dev/loader-server/package.json", true); - const params = this.buildServerParams(directory, config, options); + const params = this.buildServerParams(target, watch); await this.runLoader("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); } 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, From 32613ab23471c8ffda273b7c29cf91c872ba63ea Mon Sep 17 00:00:00 2001 From: Exelo Date: Fri, 18 Sep 2026 17:57:14 +0900 Subject: [PATCH 09/15] refactor(cli): change create command to fit the new architecture (#222) * feat(config): add language config * refactor(cli): change create command to fit the new architecture * test: fix tests --- e2e/cli-help.test.ts | 1 - libs/config/src/defaults/default-configs.ts | 4 ++ libs/config/src/index.ts | 1 + libs/config/src/parse/config-error.ts | 2 +- libs/config/src/parse/validate-config.ts | 6 +++ libs/config/src/types/client-config.type.ts | 2 + libs/config/src/types/index.ts | 1 + libs/config/src/types/mixins.type.ts | 5 ++ libs/config/src/types/server-config.type.ts | 9 +++- libs/config/test/parse-config.spec.ts | 2 +- libs/schematics/e2e/project.test.ts | 49 ++----------------- libs/schematics/e2e/workspace.test.ts | 3 +- .../libs/project/files/js/nanoforge.config.js | 1 + src/action/actions/create.action.ts | 27 +++++++--- src/action/common/config.ts | 6 --- src/command/commands/create.command.ts | 12 ----- src/lib/config/config-loader.spec.ts | 10 ++-- .../config/workspace-config-parser.spec.ts | 16 +++--- 18 files changed, 67 insertions(+), 90 deletions(-) delete mode 100644 src/action/common/config.ts diff --git a/e2e/cli-help.test.ts b/e2e/cli-help.test.ts index f2b8da0..662025d 100644 --- a/e2e/cli-help.test.ts +++ b/e2e/cli-help.test.ts @@ -56,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"); diff --git a/libs/config/src/defaults/default-configs.ts b/libs/config/src/defaults/default-configs.ts index e164859..2b06faf 100644 --- a/libs/config/src/defaults/default-configs.ts +++ b/libs/config/src/defaults/default-configs.ts @@ -36,6 +36,8 @@ export const defaultClientConfig: DeepRequired> = { entryFile: "src/main.ts", }, + language: "ts", + port: "3000", tls: { enable: false, @@ -64,5 +66,7 @@ export const defaultServerConfig: DeepRequired> = { entryFile: "src/main.ts", }, + language: "ts", + libs: [], }; diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index 3f73ba0..efce8b3 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -21,6 +21,7 @@ export type { ClientConfig, ContainLibConfig, EditorConfig, + LanguageConfig, LibConfig, NanoforgeConfig, ServerConfig, diff --git a/libs/config/src/parse/config-error.ts b/libs/config/src/parse/config-error.ts index d97e854..54736ca 100644 --- a/libs/config/src/parse/config-error.ts +++ b/libs/config/src/parse/config-error.ts @@ -8,7 +8,7 @@ export class ConfigParseError extends Error { public readonly path: string, options?: { cause?: unknown }, ) { - super(message, options); + super(`${message}${options?.cause ? `\n${options?.cause}` : ""}`); this.name = "ConfigParseError"; } } diff --git a/libs/config/src/parse/validate-config.ts b/libs/config/src/parse/validate-config.ts index debaa39..8a07e97 100644 --- a/libs/config/src/parse/validate-config.ts +++ b/libs/config/src/parse/validate-config.ts @@ -25,6 +25,10 @@ const editorConfigShape = { .optional(), }; +const languageConfigShape = { + language: z.enum(["ts", "js"]).optional(), +}; + const tlsConfigShape = { tls: z .union([ @@ -70,6 +74,7 @@ const clientConfigSchema = z.object({ ...buildableConfigShape, ...containLibConfigShape, ...editorConfigShape, + ...languageConfigShape, ...tlsConfigShape, port: z.string().optional(), }); @@ -80,6 +85,7 @@ const serverConfigSchema = z.object({ ...buildableConfigShape, ...containLibConfigShape, ...editorConfigShape, + ...languageConfigShape, }); const nanoforgeConfigSchema = z.discriminatedUnion("type", [ diff --git a/libs/config/src/types/client-config.type.ts b/libs/config/src/types/client-config.type.ts index 0394e69..b5b1fca 100644 --- a/libs/config/src/types/client-config.type.ts +++ b/libs/config/src/types/client-config.type.ts @@ -3,6 +3,7 @@ import type { BuildableConfig, ContainLibConfig, EditorConfig, + LanguageConfig, SourceableConfig, TlsConfig, } from "./mixins.type"; @@ -14,6 +15,7 @@ export interface ClientConfig 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 index bcb5ca5..3a0523a 100644 --- a/libs/config/src/types/index.ts +++ b/libs/config/src/types/index.ts @@ -10,6 +10,7 @@ export type { BuildableConfig, ContainLibConfig, EditorConfig, + LanguageConfig, SourceableConfig, TlsConfig, } from "./mixins.type"; diff --git a/libs/config/src/types/mixins.type.ts b/libs/config/src/types/mixins.type.ts index 5bbad3d..50785f9 100644 --- a/libs/config/src/types/mixins.type.ts +++ b/libs/config/src/types/mixins.type.ts @@ -17,6 +17,11 @@ export interface EditorConfig { }; } +export interface LanguageConfig { + /** Source language, used to pick `.ts`/`.js` component and system schematic templates. */ + language?: "ts" | "js"; +} + export interface TlsConfig { tls?: | { diff --git a/libs/config/src/types/server-config.type.ts b/libs/config/src/types/server-config.type.ts index 46ba676..c7f3abc 100644 --- a/libs/config/src/types/server-config.type.ts +++ b/libs/config/src/types/server-config.type.ts @@ -3,8 +3,15 @@ import type { BuildableConfig, ContainLibConfig, EditorConfig, + LanguageConfig, SourceableConfig, } from "./mixins.type"; export interface ServerConfig - extends BaseConfig<"server">, SourceableConfig, BuildableConfig, ContainLibConfig, EditorConfig {} + extends + BaseConfig<"server">, + SourceableConfig, + BuildableConfig, + ContainLibConfig, + EditorConfig, + LanguageConfig {} diff --git a/libs/config/test/parse-config.spec.ts b/libs/config/test/parse-config.spec.ts index fd5fb6e..2d98c72 100644 --- a/libs/config/test/parse-config.spec.ts +++ b/libs/config/test/parse-config.spec.ts @@ -30,7 +30,7 @@ describe("parseConfig", () => { 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.cause).toBeInstanceOf(Error); + expect(error.message).toContain("boom"); }); it("rejects with code 'no-default-export' when there's no default export", async () => { diff --git a/libs/schematics/e2e/project.test.ts b/libs/schematics/e2e/project.test.ts index 340fa1f..a489c4f 100644 --- a/libs/schematics/e2e/project.test.ts +++ b/libs/schematics/e2e/project.test.ts @@ -243,7 +243,7 @@ describe("project schematic", () => { describe.each(["npm", "yarn", "pnpm", "bun"] as const)( "with %s package manager", (packageManager) => { - it("uses the workspace-linking protocol by default", async () => { + it("resolves a real registry version by default", async () => { const tree = await runner.runSchematic("project", { part: "client", workspaceName: `${packageManager}-dep-workspace`, @@ -253,9 +253,8 @@ describe("project schematic", () => { const packageJson = JSON.parse( tree.readContent(`/${packageManager}-dep-apps/client/package.json`), ); - const expected = packageManager === "npm" ? "*" : "workspace:*"; - expect(packageJson.devDependencies["@nanoforge-dev/ecs"]).toBe(expected); - expect(packageJson.devDependencies["nanoforge"]).toBe(expected); + 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 () => { @@ -287,35 +286,6 @@ describe("project schematic", () => { }); }); - describe("with init functions enabled", () => { - let tree: UnitTestTree; - - beforeAll(async () => { - tree = await runner.runSchematic("project", { - part: "client", - workspaceName: "init-workspace", - directory: "init-apps/client", - initFunctions: true, - }); - }); - - it("should generate init function files", () => { - expect(tree.files).toContain("/init-apps/client/src/init/before-init.ts"); - expect(tree.files).toContain("/init-apps/client/src/init/after-init.ts"); - expect(tree.files).toContain("/init-apps/client/src/init/before-registry-init.ts"); - expect(tree.files).toContain("/init-apps/client/src/init/after-registry-init.ts"); - expect(tree.files).toContain("/init-apps/client/src/init/before-run.ts"); - expect(tree.files).toContain("/init-apps/client/src/init/after-run.ts"); - }); - - it("should wire the init functions into main.ts", () => { - const content = tree.readContent("/init-apps/client/src/main.ts"); - expect(content).toContain('import { beforeInit } from "./init/before-init"'); - expect(content).toContain("await beforeInit(app);"); - expect(content).toContain("await afterRun(app);"); - }); - }); - describe("with JavaScript", () => { let tree: UnitTestTree; @@ -338,19 +308,6 @@ describe("project schematic", () => { }); }); - describe("with libs", () => { - it("should list libs in nanoforge.config.ts", async () => { - const tree = await runner.runSchematic("project", { - part: "client", - workspaceName: "libs-workspace", - directory: "libs-apps/client", - libs: ["../../libs/shared"], - }); - const content = tree.readContent("/libs-apps/client/nanoforge.config.ts"); - expect(content).toContain('libs: ["../../libs/shared"]'); - }); - }); - 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 diff --git a/libs/schematics/e2e/workspace.test.ts b/libs/schematics/e2e/workspace.test.ts index 6356e6b..f09dda0 100644 --- a/libs/schematics/e2e/workspace.test.ts +++ b/libs/schematics/e2e/workspace.test.ts @@ -26,7 +26,6 @@ describe("workspace schematic", () => { 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/tsconfig.spec.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"); @@ -146,7 +145,7 @@ describe("workspace schematic", () => { // `allowBuilds` map instead. See https://pnpm.io/settings. expect(packageJson).not.toHaveProperty("pnpm"); expect(packageJson.workspaces).toBeUndefined(); - expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toBe("workspace:*"); + expect(packageJson.devDependencies["@nanoforge-dev/cli"]).toMatch(RESOLVED_VERSION); }); it("should generate a pnpm-workspace.yaml with apps only (no libs)", async () => { diff --git a/libs/schematics/src/libs/project/files/js/nanoforge.config.js b/libs/schematics/src/libs/project/files/js/nanoforge.config.js index d04d057..b42a8a5 100644 --- a/libs/schematics/src/libs/project/files/js/nanoforge.config.js +++ b/libs/schematics/src/libs/project/files/js/nanoforge.config.js @@ -6,4 +6,5 @@ export default defineConfig({ editor: { entryFile: "src/main.js", }, + language: "js", }); 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/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/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/lib/config/config-loader.spec.ts b/src/lib/config/config-loader.spec.ts index d6da267..b39997a 100644 --- a/src/lib/config/config-loader.spec.ts +++ b/src/lib/config/config-loader.spec.ts @@ -3,7 +3,7 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { getConfig } from "./config-loader"; +import { loadConfig } from "./config-loader"; vi.mock("node:fs", () => ({ existsSync: vi.fn(), @@ -13,7 +13,7 @@ vi.mock("@nanoforge-dev/config", () => ({ parseConfig: vi.fn(), })); -describe("getConfig", () => { +describe("loadConfig", () => { beforeEach(() => { vi.resetAllMocks(); }); @@ -23,7 +23,7 @@ describe("getConfig", () => { const resolved = { type: "client" as const }; vi.mocked(parseConfig).mockResolvedValue(resolved); - const result = await getConfig("/project"); + const result = await loadConfig("/project"); expect(parseConfig).toHaveBeenCalledWith(join("/project", "nanoforge.config.ts")); expect(result).toBe(resolved); @@ -34,7 +34,7 @@ describe("getConfig", () => { const resolved = { type: "server" as const }; vi.mocked(parseConfig).mockResolvedValue(resolved); - const result = await getConfig("/project"); + const result = await loadConfig("/project"); expect(parseConfig).toHaveBeenCalledWith(join("/project", "nanoforge.config.js")); expect(result).toBe(resolved); @@ -43,7 +43,7 @@ describe("getConfig", () => { it("throws when neither nanoforge.config.ts nor .js exists", async () => { vi.mocked(existsSync).mockReturnValue(false); - await expect(getConfig("/project")).rejects.toThrow("Configuration file not found at path:"); + await expect(loadConfig("/project")).rejects.toThrow("Configuration file not found at path:"); expect(parseConfig).not.toHaveBeenCalled(); }); }); diff --git a/src/lib/config/workspace-config-parser.spec.ts b/src/lib/config/workspace-config-parser.spec.ts index 9ff924f..5617a67 100644 --- a/src/lib/config/workspace-config-parser.spec.ts +++ b/src/lib/config/workspace-config-parser.spec.ts @@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { ConfigNotFoundError } from "@utils/errors"; -import { getConfig } from "./config-loader"; +import { loadConfig } from "./config-loader"; import { parseWorkspaceConfig } from "./workspace-config-parser"; vi.mock("node:fs", () => ({ @@ -18,7 +18,7 @@ vi.mock("node:fs/promises", () => ({ })); vi.mock("./config-loader", () => ({ - getConfig: vi.fn(), + loadConfig: vi.fn(), })); const asDirectory = () => ({ isDirectory: () => true }); @@ -38,7 +38,7 @@ describe("parseWorkspaceConfig", () => { it("resolves a root project config", async () => { const config = { type: "client" as const }; - vi.mocked(getConfig).mockResolvedValue(config); + vi.mocked(loadConfig).mockResolvedValue(config); const result = await parseWorkspaceConfig("/app"); @@ -46,7 +46,7 @@ describe("parseWorkspaceConfig", () => { }); it("throws when the root config is a lib", async () => { - vi.mocked(getConfig).mockResolvedValue({ type: "lib" as const }); + vi.mocked(loadConfig).mockResolvedValue({ type: "lib" as const }); await expect(parseWorkspaceConfig("/app")).rejects.toThrow( "the root config cannot be of type 'lib'", @@ -61,7 +61,7 @@ describe("parseWorkspaceConfig", () => { vi.mocked(glob).mockReturnValue( asyncIterableOf(["apps/client", "apps/foo-lib"]) as ReturnType, ); - vi.mocked(getConfig).mockImplementation(async (directory: string) => { + 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; @@ -87,7 +87,7 @@ describe("parseWorkspaceConfig", () => { vi.mocked(glob).mockReturnValue( asyncIterableOf(["apps/client", "apps/empty"]) as ReturnType, ); - vi.mocked(getConfig).mockImplementation(async (directory: string) => { + 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")); @@ -109,7 +109,7 @@ describe("parseWorkspaceConfig", () => { const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] }; vi.mocked(glob).mockReturnValue(asyncIterableOf([]) as ReturnType); - vi.mocked(getConfig).mockResolvedValue(workspaceConfig); + vi.mocked(loadConfig).mockResolvedValue(workspaceConfig); const result = await parseWorkspaceConfig("/app"); @@ -121,7 +121,7 @@ describe("parseWorkspaceConfig", () => { const workspaceConfig = { type: "workspace" as const, packages: ["apps/*"] }; vi.mocked(glob).mockReturnValue(asyncIterableOf(["apps/nested"]) as ReturnType); - vi.mocked(getConfig).mockImplementation(async (directory: string) => { + vi.mocked(loadConfig).mockImplementation(async (directory: string) => { if (directory === "/app") return workspaceConfig; return { type: "workspace" as const }; }); From 0c7d818438681aa9148840b21d7299720de8011c Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 19:03:25 +0900 Subject: [PATCH 10/15] fix(start): change path for detecting package manager (#224) --- src/action/actions/start.action.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/action/actions/start.action.ts b/src/action/actions/start.action.ts index e131e5d..8aca366 100644 --- a/src/action/actions/start.action.ts +++ b/src/action/actions/start.action.ts @@ -15,7 +15,7 @@ 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"; @@ -77,7 +77,8 @@ export class StartAction extends AbstractAction { } const env = this.parseEnv(directory); - const tasks = this.buildStartTasks(serverTargets, clientTargets, watch, env); + const packageManager = await PackageManagerFactory.find(directory); + const tasks = this.buildStartTasks(packageManager, serverTargets, clientTargets, watch, env); await Promise.all(tasks); return { keepAlive: true }; @@ -152,6 +153,7 @@ export class StartAction extends AbstractAction { } private buildStartTasks( + packageManager: PackageManager, serverTargets: ServerStartTarget[], clientTargets: ClientStartTarget[], watch: boolean, @@ -160,17 +162,18 @@ export class StartAction extends AbstractAction { const tasks: Promise[] = []; for (const target of serverTargets) { - tasks.push(this.startServer(target, watch, env)); + tasks.push(this.startServer(packageManager, target, watch, env)); } for (const target of clientTargets) { - tasks.push(this.startClient(target, { watch, serverTargets }, env)); + tasks.push(this.startClient(packageManager, target, { watch, serverTargets }, env)); } return tasks; } private async startClient( + packageManager: PackageManager, target: ClientStartTarget, options: { watch: boolean; serverTargets: ServerStartTarget[] }, env: FullEnv, @@ -178,10 +181,11 @@ export class StartAction extends AbstractAction { const loaderPath = getModulePath("@nanoforge-dev/loader-client/package.json", true); const params = this.buildClientParams(target, options); - await this.runLoader("Client", loaderPath, params, env.client); + await this.runLoader(packageManager, "Client", loaderPath, params, env.client); } private async startServer( + packageManager: PackageManager, target: ServerStartTarget, watch: boolean, env: FullEnv, @@ -189,7 +193,7 @@ export class StartAction extends AbstractAction { const loaderPath = getModulePath("@nanoforge-dev/loader-server/package.json", true); const params = this.buildServerParams(target, watch); - await this.runLoader("Server", loaderPath, params, env.server); + await this.runLoader(packageManager, "Server", loaderPath, params, env.server); } private buildClientParams( @@ -263,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); }); } From a98dfe26a0e3f703f2dad62a96e9ac9ee149705f Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 19:18:04 +0900 Subject: [PATCH 11/15] ci: add release handling on monorepo (#225) --- .github/CONTRIBUTING.md | 60 +++++++++++++++++-- .../ISSUE_TEMPLATE/01-command_bug_report.yml | 13 +++- .github/ISSUE_TEMPLATE/02-feature_request.yml | 11 ++++ .github/actions/prepare/action.yml | 6 +- .github/issue-labeler.yml | 12 ++++ .github/labeler.yml | 18 ++++++ .github/labels.yml | 12 ++++ .github/workflows/alpha-release.yml | 12 +++- .github/workflows/pre-release.yml | 4 +- .github/workflows/release.yml | 24 +------- .github/workflows/tests.yml | 4 +- README.md | 2 +- libs/schematics/.cliff-jumperrc.json | 2 +- libs/schematics/README.md | 2 +- libs/schematics/cliff.toml | 2 +- libs/schematics/package.json | 13 ++-- .../schematics/nanoforge.collection.spec.ts | 20 +++---- 17 files changed, 162 insertions(+), 55 deletions(-) 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 879f998..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,6 +26,7 @@ body: - build - create - dev + - editor - help - install - login diff --git a/.github/ISSUE_TEMPLATE/02-feature_request.yml b/.github/ISSUE_TEMPLATE/02-feature_request.yml index bcf2d51..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,6 +24,7 @@ body: - build - create - dev + - 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 a154d38..397fb14 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -10,6 +10,10 @@ command:dev: - "### Which command is this (bug report|feature request) for\\?\\n\\ndev\\n" +command:editor: + - "### Which command is this (bug + report|feature request) for\\?\\n\\neditor\\n" + command:help: - "### Which command is this (bug report|feature request) for\\?\\n\\nhelp\\n" @@ -41,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 d0086bf..89e7091 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -16,6 +16,12 @@ command:dev: - src/command/commands/dev.command.ts - src/action/actions/dev.action.ts +command:editor: + - changed-files: + - any-glob-to-any-file: + - src/command/commands/editor.command.ts + - src/action/actions/editor.action.ts + command:install: - changed-files: - any-glob-to-any-file: @@ -63,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 0d245ee..30ae617 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -54,6 +54,10 @@ description: "Related to dev command" color: "aaa3dc" +- name: "command:editor" + description: "Related to editor command" + color: "aaa3dc" + - name: "command:help" description: "Related to help command" color: "aaa3dc" @@ -86,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/README.md b/README.md index 1bb959b..6343fec 100644 --- a/README.md +++ b/README.md @@ -183,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/libs/schematics/.cliff-jumperrc.json b/libs/schematics/.cliff-jumperrc.json index 2caa355..39ace0a 100644 --- a/libs/schematics/.cliff-jumperrc.json +++ b/libs/schematics/.cliff-jumperrc.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json", "name": "schematics", "org": "nanoforge-dev", - "packagePath": ".", + "packagePath": "libs/schematics", "tagTemplate": "{{new-version}}", "identifierBase": false } diff --git a/libs/schematics/README.md b/libs/schematics/README.md index f7ec6de..90ab999 100644 --- a/libs/schematics/README.md +++ b/libs/schematics/README.md @@ -16,7 +16,7 @@ ## About -This repository contains the Schematics of NanoForge. Check [releases][github-releases] to see versions of the Schematics. Nanoforge is a powerful game engine for web browser. +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 diff --git a/libs/schematics/cliff.toml b/libs/schematics/cliff.toml index 9c2cd71..4bb6a65 100644 --- a/libs/schematics/cliff.toml +++ b/libs/schematics/cliff.toml @@ -76,4 +76,4 @@ sort_commits = "newest" [remote.github] owner = "NanoForge-dev" -repo = "schematics" +repo = "CLI" diff --git a/libs/schematics/package.json b/libs/schematics/package.json index 80641fd..70ff2da 100644 --- a/libs/schematics/package.json +++ b/libs/schematics/package.json @@ -8,8 +8,8 @@ "game", "schematics" ], - "homepage": "https://github.com/NanoForge-dev/schematics#readme", - "bugs": "https://github.com/NanoForge-dev/schematics/issues", + "homepage": "https://github.com/NanoForge-dev/CLI#readme", + "bugs": "https://github.com/NanoForge-dev/CLI/issues", "license": "MIT", "contributors": [ "Bill ", @@ -44,9 +44,10 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/NanoForge-dev/schematics.git" + "url": "git+https://github.com/NanoForge-dev/CLI.git", + "directory": "libs/schematics" }, - "funding": "https://github.com/NanoForge-dev/schematics?sponsor", + "funding": "https://github.com/NanoForge-dev/CLI?sponsor", "scripts": { "build": "tsc --noEmit && tsdown", "postbuild": "pnpm run copy:collection && pnpm run copy:lib", @@ -55,7 +56,7 @@ "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/*'", + "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", @@ -89,7 +90,7 @@ "typescript": "catalog:build", "vitest": "catalog:test" }, - "packageManager": "pnpm@11.9.0", + "packageManager": "pnpm@12.0.0", "engines": { "node": "26" }, 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 () => { From d53432a2344c716f69e6733f48d08a3fea2cf592 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 23:05:52 +0900 Subject: [PATCH 12/15] chore: upgrade loader (#227) * chore: upgrade loader to 1.5.1 * test: fix tests --- e2e/cli-build.test.ts | 136 ++++++++-------- e2e/cli-create.test.ts | 171 ++++++--------------- e2e/cli-install.test.ts | 31 ++-- e2e/cli-new-config.test.ts | 120 ++++++--------- e2e/cli-new.test.ts | 165 +++++++++----------- e2e/helpers/project-fixtures.ts | 36 +++++ e2e/helpers/write-project-config.ts | 36 ----- libs/schematics/e2e/project.test.ts | 35 ++--- libs/schematics/e2e/workspace.test.ts | 6 +- libs/schematics/src/utils/registry.spec.ts | 8 +- pnpm-lock.yaml | 39 ++--- pnpm-workspace.yaml | 9 +- 12 files changed, 318 insertions(+), 474 deletions(-) create mode 100644 e2e/helpers/project-fixtures.ts delete mode 100644 e2e/helpers/write-project-config.ts diff --git a/e2e/cli-build.test.ts b/e2e/cli-build.test.ts index 95ba92d..7a917ab 100644 --- a/e2e/cli-build.test.ts +++ b/e2e/cli-build.test.ts @@ -2,11 +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"; -import { writeProjectConfig } from "./helpers/write-project-config"; 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 }); }); @@ -18,67 +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, - ]); - - writeProjectConfig(appDir, { name: "build-app", language: "ts", server: false }); + 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"); @@ -103,99 +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]); - writeProjectConfig(appDir, { name: "build-server-app", language: "ts", server: true }); + 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); }); @@ -204,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 b9f9cdb..cb88640 100644 --- a/e2e/cli-create.test.ts +++ b/e2e/cli-create.test.ts @@ -2,11 +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"; -import { writeProjectConfig } from "./helpers/write-project-config"; 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 }); }); @@ -15,32 +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, - ]); - - writeProjectConfig(appDir, { name: "create-app", language: "ts", server: false }); + await newProject("create-app", projectDir, "ts", false); + stripDefineConfig(appDir); }); it("should create a component successfully", async () => { @@ -58,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 () => { @@ -76,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 () => { @@ -102,42 +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, - ]); - - writeProjectConfig(appDir, { name: "create-server-app", language: "ts", server: true }); + await newProject("create-server-app", projectDir, "ts", true); + stripDefineConfig(workspaceDir); + stripDefineConfig(serverDir); }); it("should create a server component successfully", async () => { @@ -145,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); }); }); @@ -200,46 +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, - ]); - - writeProjectConfig(appDir, { name: "create-js-app", language: "js", server: false }); + 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); }); }); @@ -253,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-install.test.ts b/e2e/cli-install.test.ts index 4d4149a..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,7 +112,6 @@ describe("nf install (without library name)", () => { "npm", "--strict", "--no-server", - "--no-init-functions", "--skip-install", "--no-docker", "--no-git", 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 143ac7a..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,7 +130,6 @@ describe("nf new (JavaScript, with server)", () => { "pnpm", "--no-strict", "--server", - "--init-functions", "--skip-install", "--no-docker", "--no-git", @@ -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,7 +227,6 @@ describe("nf new (with typescript with docker option)", () => { "npm", "--strict", "--no-server", - "--no-init-functions", "--skip-install", "--docker", "--no-git", @@ -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/e2e/helpers/write-project-config.ts b/e2e/helpers/write-project-config.ts deleted file mode 100644 index 9db4030..0000000 --- a/e2e/helpers/write-project-config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { writeFileSync } from "node:fs"; -import { join } from "node:path"; - -interface WriteProjectConfigOptions { - name: string; - language: "ts" | "js"; - initFunctions?: boolean; - server?: boolean; -} - -/** - * `nf new` still scaffolds a `nanoforge.config.json` (schematics is out of scope for this - * change), but the CLI's config loader now only reads `.ts`/`.js` config modules. Tests that - * scaffold a project and then run `build`/`create` against it need a hand-written - * `nanoforge.config.ts` alongside the scaffolded JSON — everything not set here falls back to - * the CLI's own defaults, matching what the scaffolded JSON used to rely on too. - */ -export const writeProjectConfig = ( - appDir: string, - { name, language, initFunctions = false, server = false }: WriteProjectConfigOptions, -) => { - const content = `export default ${JSON.stringify( - { - name, - language, - initFunctions, - client: { type: "client", enable: true }, - server: { type: "server", enable: server }, - tls: { enable: false }, - }, - null, - 2, - )};\n`; - - writeFileSync(join(appDir, "nanoforge.config.ts"), content); -}; diff --git a/libs/schematics/e2e/project.test.ts b/libs/schematics/e2e/project.test.ts index a489c4f..1c2363e 100644 --- a/libs/schematics/e2e/project.test.ts +++ b/libs/schematics/e2e/project.test.ts @@ -356,13 +356,12 @@ describe("project schematic", () => { }); describe("allowBuilds (standalone projects are their own root)", () => { - it("is ignored when generated inside a workspace, even if passed", async () => { + 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", - allowBuilds: ["wrtc"], }); expect(tree.files).not.toContain( "/in-workspace-allow-builds-apps/server/pnpm-workspace.yaml", @@ -374,48 +373,33 @@ describe("project schematic", () => { expect(packageJson).not.toHaveProperty("trustedDependencies"); }); - it("writes a pnpm-workspace.yaml allowBuilds map for a standalone pnpm project", async () => { + it("always allows bun in a pnpm-workspace.yaml for a standalone pnpm project", async () => { const tree = await runner.runSchematic("project", { - part: "server", + part: "client", workspaceName: "standalone-allow-builds", - directory: "standalone-allow-builds-apps/server", + directory: "standalone-allow-builds-apps/client", workspace: false, packageManager: "pnpm", - allowBuilds: ["wrtc"], }); const pnpmWorkspaceYaml = tree.readContent( - "/standalone-allow-builds-apps/server/pnpm-workspace.yaml", + "/standalone-allow-builds-apps/client/pnpm-workspace.yaml", ); expect(pnpmWorkspaceYaml).toContain("allowBuilds:"); - expect(pnpmWorkspaceYaml).toContain("wrtc: true"); - }); - - it("does not generate pnpm-workspace.yaml for a standalone pnpm project with no allowBuilds", async () => { - const tree = await runner.runSchematic("project", { - part: "client", - workspaceName: "standalone-no-allow-builds", - directory: "standalone-no-allow-builds-apps/client", - workspace: false, - packageManager: "pnpm", - }); - expect(tree.files).not.toContain( - "/standalone-no-allow-builds-apps/client/pnpm-workspace.yaml", - ); + expect(pnpmWorkspaceYaml).toContain("bun: true"); }); - it("writes allowScripts/trustedDependencies for a standalone npm/bun project", async () => { + 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", - allowBuilds: ["wrtc"], }); const npmPackageJson = JSON.parse( npmTree.readContent("/standalone-npm-allow-builds-apps/server/package.json"), ); - expect(npmPackageJson.allowScripts).toEqual({ wrtc: true }); + expect(npmPackageJson.allowScripts).toEqual({ bun: true }); const bunTree = await runner.runSchematic("project", { part: "server", @@ -423,12 +407,11 @@ describe("project schematic", () => { directory: "standalone-bun-allow-builds-apps/server", workspace: false, packageManager: "bun", - allowBuilds: ["wrtc"], }); const bunPackageJson = JSON.parse( bunTree.readContent("/standalone-bun-allow-builds-apps/server/package.json"), ); - expect(bunPackageJson.trustedDependencies).toEqual(["wrtc"]); + expect(bunPackageJson.trustedDependencies).toEqual(["bun"]); }); }); }); diff --git a/libs/schematics/e2e/workspace.test.ts b/libs/schematics/e2e/workspace.test.ts index f09dda0..10445b1 100644 --- a/libs/schematics/e2e/workspace.test.ts +++ b/libs/schematics/e2e/workspace.test.ts @@ -64,13 +64,13 @@ describe("workspace schematic", () => { expect(packageJson.devDependencies["typescript"]).toMatch(RESOLVED_VERSION); }); - it("pins typescript to a specific version, not a range", () => { - expect(packageJson.devDependencies["typescript"]).toMatch(/^\d+\.\d+\.\d+$/); + 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.split(".")[0]).toBe("6"); + expect(version.replace(/^\^/, "").split(".")[0]).toBe("6"); }); it("should declare a workspace-typed nanoforge.config.ts with apps only (no libs)", () => { diff --git a/libs/schematics/src/utils/registry.spec.ts b/libs/schematics/src/utils/registry.spec.ts index 6737080..a90034f 100644 --- a/libs/schematics/src/utils/registry.spec.ts +++ b/libs/schematics/src/utils/registry.spec.ts @@ -35,7 +35,7 @@ describe("fetchTrustedVersion", () => { }); const version = await fetchTrustedVersion("some-pkg", "0.0.0"); - expect(version).toBe("1.1.0"); + expect(version).toBe("^1.1.0"); }); it("excludes prerelease and non-semver tags", async () => { @@ -45,7 +45,7 @@ describe("fetchTrustedVersion", () => { }); const version = await fetchTrustedVersion("some-pkg", "0.0.0"); - expect(version).toBe("1.0.0"); + expect(version).toBe("^1.0.0"); }); it("restricts to a requested major version", async () => { @@ -55,7 +55,7 @@ describe("fetchTrustedVersion", () => { }); const version = await fetchTrustedVersion("typescript", "6.0.0", { major: 6 }); - expect(version).toBe("6.0.3"); + expect(version).toBe("^6.0.3"); }); it("falls back when every version is too fresh", async () => { @@ -101,6 +101,6 @@ describe("fetchTrustedVersions", () => { "pkg-b": { fallback: "0.0.1" }, }); - expect(versions).toEqual({ "pkg-a": "2.5.0", "pkg-b": "2.5.0" }); + expect(versions).toEqual({ "pkg-a": "^2.5.0", "pkg-b": "^2.5.0" }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a92edc3..18eb710 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,11 +213,11 @@ catalogs: version: 3.9.6 loader: '@nanoforge-dev/loader-client': - specifier: ^1.5.0 - version: 1.5.0 + specifier: ^1.5.1 + version: 1.5.1 '@nanoforge-dev/loader-server': - specifier: ^1.5.0 - version: 1.5.0 + specifier: ^1.5.1 + version: 1.5.1 schematics: '@angular-devkit/core': specifier: ^22.0.4 @@ -260,10 +260,10 @@ importers: version: 0.1.4 '@nanoforge-dev/loader-client': specifier: catalog:loader - version: 1.5.0 + version: 1.5.1 '@nanoforge-dev/loader-server': specifier: catalog:loader - version: 1.5.0 + version: 1.5.1 '@nanoforge-dev/schematics': specifier: workspace:* version: link:libs/schematics @@ -879,17 +879,17 @@ packages: resolution: {integrity: sha512-bqbrH8ulFSiZz1iisJHtHeEzj1RV/70uErCYHeW4DIhVPsWbBfWKFvg/ghAbmPk5vsj0YnhDX6DSpik4S1QoiQ==} engines: {node: '25'} - '@nanoforge-dev/loader-client@1.5.0': - resolution: {integrity: sha512-FUcrfXlcaG4dsjya7fUUFkr5zwKB0nCihYeEOC1K54gl4XV6S1lgNnAfdkl4jZfzyyw4jcZUB+73t4ymBhz08g==} - 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.5.0': - resolution: {integrity: sha512-1rly9IgunGz8Ux6lGDugUXgmNbnNjWGM8ABy7EsdrKWV74gyj7cuU6X69UolO28XUPCdbeISNcBEZV5hLK8Rlg==} - engines: {node: '25'} + '@nanoforge-dev/loader-server@1.5.1': + resolution: {integrity: sha512-RNqA0zpEhUXENKu5chJ+vQOhr8qK+a7z6XePmiCJygpSScJ8p1dlWquFYNwu9dBLHBP8BhK9dSFvXjPvHAYBxQ==} + engines: {node: '26'} - '@nanoforge-dev/loader-website@1.5.0': - resolution: {integrity: sha512-X37wlYyoL/q8ZPk5I8W3z01novfDSopizVQXbk/sw0dRXoONtIb6xnZLlN3z4jkDkPOy6IVes0bLGr8h4aFOcA==} - 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==} @@ -3560,17 +3560,18 @@ snapshots: tar: 7.5.22 typescript: 6.0.3 - '@nanoforge-dev/loader-client@1.5.0': + '@nanoforge-dev/loader-client@1.5.1': dependencies: - '@nanoforge-dev/loader-website': 1.5.0 + '@nanoforge-dev/loader-website': 1.5.1 bun: 1.4.0 commander: 15.0.0 - '@nanoforge-dev/loader-server@1.5.0': + '@nanoforge-dev/loader-server@1.5.1': dependencies: + bun: 1.4.0 commander: 15.0.0 - '@nanoforge-dev/loader-website@1.5.0': {} + '@nanoforge-dev/loader-website@1.5.1': {} '@nanoforge-dev/utils-eslint-config@1.0.2(@types/eslint@9.6.1)(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(typescript@6.0.3)': dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 10feea7..5c5c96a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -47,8 +47,8 @@ catalogs: eslint: ^10.9.1 prettier: ^3.9.6 loader: - '@nanoforge-dev/loader-client': ^1.5.0 - '@nanoforge-dev/loader-server': ^1.5.0 + '@nanoforge-dev/loader-client': ^1.5.1 + '@nanoforge-dev/loader-server': ^1.5.1 schematics: '@angular-devkit/core': ^22.0.4 '@angular-devkit/schematics': ^22.1.6 @@ -57,3 +57,8 @@ catalogs: test: '@vitest/coverage-v8': ^4.1.11 vitest: ^4.1.11 + +minimumReleaseAgeExclude: + - '@nanoforge-dev/loader-client@1.5.1' + - '@nanoforge-dev/loader-server@1.5.1' + - '@nanoforge-dev/loader-website@1.5.1' From 78da77e12b7d8c1761c74547395b1b9872e0ba65 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 23:22:44 +0900 Subject: [PATCH 13/15] chore: update contributors list (#229) --- libs/config/package.json | 4 ++-- libs/schematics/package.json | 4 ++-- package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/config/package.json b/libs/config/package.json index 25a6b79..66a0346 100644 --- a/libs/config/package.json +++ b/libs/config/package.json @@ -12,11 +12,11 @@ "bugs": "https://github.com/NanoForge-dev/CLI/issues", "license": "MIT", "contributors": [ - "Bill ", "Exelo ", "Fexkoser ", + "Harper ", "Josephine ", - "Tchips " + "Leo " ], "files": [ "dist" diff --git a/libs/schematics/package.json b/libs/schematics/package.json index 70ff2da..4be5c5f 100644 --- a/libs/schematics/package.json +++ b/libs/schematics/package.json @@ -12,11 +12,11 @@ "bugs": "https://github.com/NanoForge-dev/CLI/issues", "license": "MIT", "contributors": [ - "Bill ", "Exelo ", "Fexkoser ", + "Harper ", "Josephine ", - "Tchips " + "Leo " ], "files": [ "dist" diff --git a/package.json b/package.json index 5e251ca..82b1552 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ "bugs": "https://github.com/NanoForge-dev/cli/issues", "license": "MIT", "contributors": [ - "Bill ", "Exelo ", "Fexkoser ", + "Harper ", "Josephine ", - "Tchips " + "Leo " ], "files": [ "dist" From 23e408883a9934870419551f6e89d06fc22cb1a9 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 23:23:09 +0900 Subject: [PATCH 14/15] feat: put network lib in dependencies (#226) --- docs/docs/schematics/2-schematics/2-project.mdx | 2 ++ libs/schematics/e2e/project.test.ts | 14 +++++++++++++- .../libs/project/files/js/package.json.template | 9 ++++++--- .../libs/project/files/ts/package.json.template | 9 ++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/docs/schematics/2-schematics/2-project.mdx b/docs/docs/schematics/2-schematics/2-project.mdx index 198e11f..dcd7c79 100644 --- a/docs/docs/schematics/2-schematics/2-project.mdx +++ b/docs/docs/schematics/2-schematics/2-project.mdx @@ -11,6 +11,8 @@ The `project` schematic generates a complete client or server project under `dir 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 diff --git a/libs/schematics/e2e/project.test.ts b/libs/schematics/e2e/project.test.ts index 1c2363e..2f0637e 100644 --- a/libs/schematics/e2e/project.test.ts +++ b/libs/schematics/e2e/project.test.ts @@ -48,6 +48,12 @@ describe("project schematic", () => { 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"'); @@ -98,6 +104,12 @@ describe("project schematic", () => { 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 })"); @@ -337,7 +349,7 @@ describe("project schematic", () => { server.readContent("/ecs-alias-server-apps/server/package.json"), ); expect(serverPackageJson.devDependencies["@nanoforge-dev/ecs"]).toMatch(RESOLVED_VERSION); - expect(serverPackageJson.devDependencies["@nanoforge-dev/network"]).toMatch(RESOLVED_VERSION); + expect(serverPackageJson.dependencies["@nanoforge-dev/network"]).toMatch(RESOLVED_VERSION); }); }); diff --git a/libs/schematics/src/libs/project/files/js/package.json.template b/libs/schematics/src/libs/project/files/js/package.json.template index 64f7c67..9124d89 100644 --- a/libs/schematics/src/libs/project/files/js/package.json.template +++ b/libs/schematics/src/libs/project/files/js/package.json.template @@ -5,12 +5,15 @@ "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-dev/input": "<%= inputVersion %>", + "@nanoforge-dev/network": "<%= networkVersion %>",<% } %> "nanoforge": "<%= nanoforgeVersion %>" },<% if (packageManager === "npm" && allowBuilds.length > 0) { %> "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %> diff --git a/libs/schematics/src/libs/project/files/ts/package.json.template b/libs/schematics/src/libs/project/files/ts/package.json.template index 64f7c67..9124d89 100644 --- a/libs/schematics/src/libs/project/files/ts/package.json.template +++ b/libs/schematics/src/libs/project/files/ts/package.json.template @@ -5,12 +5,15 @@ "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-dev/input": "<%= inputVersion %>", + "@nanoforge-dev/network": "<%= networkVersion %>",<% } %> "nanoforge": "<%= nanoforgeVersion %>" },<% if (packageManager === "npm" && allowBuilds.length > 0) { %> "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %> From ea29098125d1e394fc55a7f713d619e9841ffd57 Mon Sep 17 00:00:00 2001 From: Exelo Date: Thu, 24 Sep 2026 23:28:50 +0900 Subject: [PATCH 15/15] chore: upgrade deps (#230) --- pnpm-lock.yaml | 2049 +++++++++++++++++++++---------------------- pnpm-workspace.yaml | 36 +- 2 files changed, 1034 insertions(+), 1051 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18eb710..8a79d9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,23 +109,23 @@ catalogs: build: cpx2: specifier: ^9.0.0 - version: 9.0.0 + version: 9.0.3 tsdown: - specifier: ^0.22.14 - version: 0.22.14 + specifier: ^0.23.0 + version: 0.23.0 turbo: - specifier: ^2.10.12 - version: 2.10.12 + specifier: ^2.11.3 + version: 2.11.3 typescript: specifier: ^6.0.3 version: 6.0.3 ci: '@commitlint/cli': - specifier: ^21.2.2 - version: 21.2.2 + specifier: ^21.2.3 + version: 21.2.3 '@commitlint/config-conventional': - specifier: ^21.2.2 - version: 21.2.2 + specifier: ^21.2.3 + version: 21.2.3 '@favware/cliff-jumper': specifier: ^6.1.0 version: 6.1.0 @@ -136,18 +136,18 @@ catalogs: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^17.4.1 - version: 17.4.1 + specifier: ^17.5.1 + version: 17.5.1 cli: '@inquirer/prompts': - specifier: ^8.7.0 - version: 8.7.0 + 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 @@ -162,11 +162,11 @@ catalogs: specifier: ^0.1.4 version: 0.1.4 '@types/node': - specifier: ^26.4.0 - version: 26.4.0 + specifier: ^26.6.2 + version: 26.6.2 bun: - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.4.2 + version: 1.4.2 libs: chokidar: specifier: ^5.0.0 @@ -178,14 +178,14 @@ 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.2 - version: 11.0.2 + 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 @@ -194,7 +194,7 @@ catalogs: version: 0.3.1 zod: specifier: ^4.5.4 - version: 4.5.4 + version: 4.6.5 lint: '@nanoforge-dev/utils-eslint-config': specifier: ^1.0.2 @@ -206,11 +206,11 @@ catalogs: specifier: ^6.0.2 version: 6.0.2 eslint: - specifier: ^10.9.1 - version: 10.9.1 + specifier: ^10.11.0 + version: 10.11.0 prettier: - specifier: ^3.9.6 - version: 3.9.6 + specifier: ^3.9.9 + version: 3.9.9 loader: '@nanoforge-dev/loader-client': specifier: ^1.5.1 @@ -221,23 +221,23 @@ catalogs: schematics: '@angular-devkit/core': specifier: ^22.0.4 - version: 22.1.6 + version: 22.1.8 '@angular-devkit/schematics': - specifier: ^22.1.6 - version: 22.1.6 + specifier: ^22.1.8 + version: 22.1.8 '@angular-devkit/schematics-cli': - specifier: ^22.1.6 - version: 22.1.6 + specifier: ^22.1.8 + version: 22.1.8 rxjs: specifier: ^7.8.2 version: 7.8.2 test: '@vitest/coverage-v8': - specifier: ^4.1.11 - version: 4.1.11 + specifier: ^5.0.1 + version: 5.0.1 vitest: - specifier: ^4.1.11 - version: 4.1.11 + specifier: ^5.0.1 + version: 5.0.1 importers: @@ -245,13 +245,13 @@ importers: dependencies: '@angular-devkit/schematics': specifier: catalog:schematics - version: 22.1.6(chokidar@5.0.0) + version: 22.1.8(chokidar@5.0.0) '@angular-devkit/schematics-cli': specifier: catalog:schematics - version: 22.1.6(@types/node@26.4.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.7.0(@types/node@26.4.0) + version: 8.7.2(@types/node@26.6.2) '@nanoforge-dev/config': specifier: workspace:* version: link:libs/config @@ -269,10 +269,10 @@ importers: version: link:libs/schematics ansis: specifier: catalog:cli - version: 4.3.1 + version: 4.4.0 bun: specifier: catalog:core - version: 1.4.0 + version: 1.4.2 chokidar: specifier: catalog:libs version: 5.0.0 @@ -287,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.2 + 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.2(@types/node@26.4.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.2 + version: 21.2.3 '@favware/cliff-jumper': specifier: catalog:ci version: 6.1.0 @@ -318,46 +318,46 @@ importers: version: 2.1.5 '@nanoforge-dev/utils-eslint-config': specifier: catalog:lint - version: 1.0.2(@types/eslint@9.6.1)(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(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.6)(supports-color@10.2.2) + 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.4.0 + version: 26.6.2 '@vitest/coverage-v8': specifier: catalog:test - version: 4.1.11(vitest@4.1.11) + version: 5.0.1(vitest@5.0.1) eslint: specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + 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.4.1 + version: 17.5.1 prettier: specifier: catalog:lint - version: 3.9.6 + version: 3.9.9 tsdown: specifier: catalog:build - version: 0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) + version: 0.23.0(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) turbo: specifier: catalog:build - version: 2.10.12 + version: 2.11.3 typescript: specifier: catalog:build version: 6.0.3 vitest: specifier: catalog:test - version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + 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: @@ -366,57 +366,57 @@ importers: version: 0.3.1(synckit@0.11.13) zod: specifier: catalog:libs - version: 4.5.4 + 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.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(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.6)(supports-color@10.2.2) + version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2) eslint: specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + version: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) prettier: specifier: catalog:lint - version: 3.9.6 + version: 3.9.9 tsdown: specifier: catalog:build - version: 0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) + 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: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + 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.6(chokidar@5.0.0) + version: 22.1.8(chokidar@5.0.0) '@angular-devkit/schematics': specifier: catalog:schematics - version: 22.1.6(chokidar@5.0.0) + 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.6(@types/node@26.4.0)(chokidar@5.0.0) + version: 22.1.8(@types/node@26.6.2)(chokidar@5.0.0) '@commitlint/cli': specifier: catalog:ci - version: 21.2.2(@types/node@26.4.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + 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.2 + version: 21.2.3 '@favware/cliff-jumper': specifier: catalog:ci version: 6.1.0 @@ -425,43 +425,43 @@ importers: version: 2.1.5 '@nanoforge-dev/utils-eslint-config': specifier: catalog:lint - version: 1.0.2(@types/eslint@9.6.1)(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(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.6)(supports-color@10.2.2) + version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2) '@types/node': specifier: catalog:core - version: 26.4.0 + version: 26.6.2 '@vitest/coverage-v8': specifier: catalog:test - version: 4.1.11(vitest@4.1.11) + version: 5.0.1(vitest@5.0.1) cpx2: specifier: catalog:build - version: 9.0.0 + version: 9.0.3 eslint: specifier: catalog:lint - version: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + 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.4.1 + version: 17.5.1 prettier: specifier: catalog:lint - version: 3.9.6 + version: 3.9.9 tsdown: specifier: catalog:build - version: 0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)) + 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: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + 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: @@ -483,8 +483,8 @@ packages: '@actions/io@3.0.2': resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} - '@angular-devkit/core@22.1.6': - resolution: {integrity: sha512-KLBsZoc2RhOy0xSdeYcLoSOqV/fBP3feBmhY9o12ry2IuyW/17sCmWr6XbIfTIYQN6M98Y1ewmwVNFsvV5b0gA==} + '@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 @@ -492,13 +492,13 @@ packages: chokidar: optional: true - '@angular-devkit/schematics-cli@22.1.6': - resolution: {integrity: sha512-KO+vQiWgWW6i2H3XGu1E6AeZ6cyeehF1WTejATEZ3f02LROBkGyZGDAmdtSPtd3tmaGt3TVY25Mq1laybByKMA==} + '@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.1.6': - resolution: {integrity: sha512-IbDO9KbQyQm20uinsfT8d9ZtQw41/WVutI/65mAw39WZfN8Ky+MOgOUJCGLiSccMprvZ9YdodBTnN9bLH149Ag==} + '@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': @@ -521,8 +521,8 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true @@ -542,61 +542,67 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@commitlint/cli@21.2.2': - resolution: {integrity: sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==} + '@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.2': - resolution: {integrity: sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==} + '@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.2': - resolution: {integrity: sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==} + '@commitlint/format@21.2.3': + resolution: {integrity: sha512-KKwCjXYqA8r295uhLnulDXL9yekdXmj2yp2Q0XsBPhxQ2iPuvRE8F43j7g7yexo69wTdmUQmFz15jpderluzUQ==} engines: {node: '>=22.12.0'} - '@commitlint/is-ignored@21.2.2': - resolution: {integrity: sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==} + '@commitlint/is-ignored@21.2.3': + resolution: {integrity: sha512-Ng4nRj/LBAOvc+GQu7bsFW4/fL89LvZ14OOC/ERLbUpnG6QBwWRzY/1oXGwPs3C8YHWjJObin4kLYo2YeEn70w==} engines: {node: '>=22.12.0'} - '@commitlint/lint@21.2.2': - resolution: {integrity: sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==} + '@commitlint/lint@21.2.3': + resolution: {integrity: sha512-lUZcSuVLLEDkhge9ERk8Rp+FXQtrUhCK+h328BFnzKXrTuXGdqiYMEpTlNWdZaOrjyMhMJaz354ZaWEGJxcLeg==} engines: {node: '>=22.12.0'} - '@commitlint/load@21.2.2': - resolution: {integrity: sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==} + '@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.2': - resolution: {integrity: sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==} + '@commitlint/parse@21.2.3': + resolution: {integrity: sha512-Jt61QpF1xA88damCELe8I0FlDZPSpXNc8IhN7z5kZ0EcDYPjdZ+ZHPvsSXl69r2MIlLnNKeHhOn2cmVpziFE9w==} engines: {node: '>=22.12.0'} - '@commitlint/read@21.2.1': - resolution: {integrity: sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==} + '@commitlint/read@21.2.3': + resolution: {integrity: sha512-szAepuLA1vTTr7fuudEg7jYyVHudf+FH+vptU91bhxE1WnE8TKrfTAi44HVllz8XpI8Pvp4GjR/iP5Nshgh+og==} engines: {node: '>=22.12.0'} - '@commitlint/resolve-extends@21.2.2': - resolution: {integrity: sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==} + '@commitlint/resolve-extends@21.2.3': + resolution: {integrity: sha512-bqJdmAtXgAQel+l02uIsF7rHgKgx1AGZdGBjam7uK4XW54ZAQtB6XAIUXezYprQiCcRMpBh4fHs5JodXK4mVZA==} engines: {node: '>=22.12.0'} - '@commitlint/rules@21.2.2': - resolution: {integrity: sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==} + '@commitlint/rules@21.2.3': + resolution: {integrity: sha512-5+YnI/LuTx3JDl9GjqfP2z9x96M8KKDrd+4jOel/T0L1BB3oAmBlSN8xppf63QsudiVEgR8C0gRoAMrPVrms6g==} engines: {node: '>=22.12.0'} '@commitlint/to-lines@21.0.1': @@ -607,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': @@ -678,8 +684,8 @@ packages: 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': @@ -711,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.3': - resolution: {integrity: sha512-XEYX2WA8SBkLPczL6/yXPHLPCvDoptmh9v56Cy05BSV1Smk1vWy19bTC4qJBuIffw7+6l4CcaYYzGqG60RfW1g==} + '@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' @@ -724,8 +730,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.3.0': - resolution: {integrity: sha512-pZHXJImFtERmSNMBHcjwuz8Ck5vEFEYNUZnwbb8aJpjHv/TwGuFErNxF2Hp8+V+pNJs2EYPMlyWscvFEqO9jOQ==} + '@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' @@ -733,8 +739,8 @@ packages: '@types/node': optional: true - '@inquirer/core@12.0.1': - resolution: {integrity: sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==} + '@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' @@ -742,8 +748,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.3.1': - resolution: {integrity: sha512-y43COoyVUjPWIobn2Qep/uI1drPS78aaZZZ9kVi94Tyu/GuW2N8d8Q4rifJXGAXCEAXCPTTMjD8gC1HyvM5ukA==} + '@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' @@ -751,8 +757,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.1.3': - resolution: {integrity: sha512-3NQJiXNJ/aj9wiAsr7pECdp5Qe9J0X9YUJCKsaFXS+ddOxfL6J4AIl3w3T4Gq3kK0WsQY5GMoDokK5X94m6lHw==} + '@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' @@ -760,8 +766,8 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@3.0.4': - resolution: {integrity: sha512-tZbbaK2ovq6vlrRBNQvjrypmrED/p5x2ncIHQ79cD55tei3dD96v5glMMA+6tiq7K104i/25DVYKWVPJuV6ptA==} + '@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' @@ -769,12 +775,12 @@ packages: '@types/node': optional: true - '@inquirer/figures@2.0.8': - resolution: {integrity: sha512-tApbon79GM9ry56ja/Ud3SY2CL4TQsao9fIwDQbgTeNY55025GdMzQ2+UdegV/lx51VNGUB59M0v0nMpybYY4Q==} + '@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.4': - resolution: {integrity: sha512-3xQkQrOvgOzpSN2ciTVdRDlg1FWMCA8l+0KfB6SNlILoTCGzJTzO/gc0Rwjcb3usuGyKdaGtI6OiyMdeMeLWkg==} + '@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' @@ -782,8 +788,8 @@ packages: '@types/node': optional: true - '@inquirer/number@4.2.1': - resolution: {integrity: sha512-5KaqwZNLRpUuWcoCrYghPP9TMaXL5v2Sk4xqePM7RCVegcJStoXdWibio60YIC1bec+z1fCyb67N6XPJIkZtGA==} + '@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' @@ -791,8 +797,8 @@ packages: '@types/node': optional: true - '@inquirer/password@5.2.0': - resolution: {integrity: sha512-CvVcW09emkBESEOW+4R8CjLNkP3fB3XrjeL8CDvfpjgrJN+V9oerXmJAXXM3l+4xqYPD5Yaujzy/Ph0PLOdDuA==} + '@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' @@ -809,8 +815,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@8.7.0': - resolution: {integrity: sha512-yQwBMYvpJ6jqrXtKiOwRD5XezjJoyt3VQvIyjsr5Arqb519nfIohOQymWVJ8/vEgg8xtZerrCsqlSaqt/LPC9A==} + '@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' @@ -818,8 +824,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.3.3': - resolution: {integrity: sha512-Mu7WrtmDLaXBDEyrRLS70SZgX9ZSm4Up1w0ZxiH8C1OOp9oaVCn2k8q3QGgmlnhsKYUhuaU3zFWhAP6wxkVIMA==} + '@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' @@ -827,8 +833,8 @@ packages: '@types/node': optional: true - '@inquirer/search@4.3.1': - resolution: {integrity: sha512-0VWOvsHWI0rPj6CG70MoP4oXNCB6adcyN8bVFZXnh11eLDdPIK2f2XCmva78acPPDfJisfab7qNakwBh7hdBXw==} + '@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' @@ -836,8 +842,8 @@ packages: '@types/node': optional: true - '@inquirer/select@5.2.3': - resolution: {integrity: sha512-KuRTodDa6xBXX2noIpjuitpX/QT7Sfav7dIZ/OfUY54Hxg95nrGoshSzxx6Ey7qqLbImKdiGkSDt7KjPXjQgmA==} + '@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' @@ -845,8 +851,8 @@ packages: '@types/node': optional: true - '@inquirer/type@4.1.0': - resolution: {integrity: sha512-FMiJpuHUG3Dk0ex+UIXkre7i+i4OcwHWk9YdcVtZHFwb/r2rnrU2ipTCNAB7A+QOP0ryzIcqOfy76fRyyvOEAw==} + '@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' @@ -871,6 +877,15 @@ packages: '@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'} @@ -959,72 +974,72 @@ packages: '@octokit/types@18.0.0': resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} - '@oven/bun-darwin-aarch64@1.4.0': - resolution: {integrity: sha512-GCpf8QuFLsyioVawP5HrMxA1ZRBlu6Hq9RNnSc3UTUWAzIxBso9trjoZczw1HdgpqSssFkszfIV2zmOzFTjhkw==} + '@oven/bun-darwin-aarch64@1.4.2': + resolution: {integrity: sha512-MXdZkP1featqxZ+/VTXWG1BVjM4OGBehVY2Q88EeUj/7L0UMeCGItmyPYTN+wxvlGJ6F66JEtzsw+GvQWewnag==} cpu: [arm64] os: [darwin] - '@oven/bun-darwin-x64@1.4.0': - resolution: {integrity: sha512-cIrhwOr0SPEraewznhC+c/k6TG8bwFn5uZ4EJuXwjiKJLcAF36q7/bGjWkeXSe48JwMcPRUR054JXF7+cRwSSA==} + '@oven/bun-darwin-x64@1.4.2': + resolution: {integrity: sha512-gZTxZuLjkUhAWjTETu3tw0WhsEdNkJ64daj60ybhPf835a2yollV3yTkK9JozvzKPx4TRFzLSl8C+U525pxVbw==} cpu: [x64] os: [darwin] - '@oven/bun-freebsd-aarch64@1.4.0': - resolution: {integrity: sha512-09x7wnjMR6M5KGBDBhVl2CpfoCIQOkVDbPX2KfIhpXv4N6grbWE7dfLPw/Ydi9gaUMGhU7UKhoz444Nu6RCycA==} + '@oven/bun-freebsd-aarch64@1.4.2': + resolution: {integrity: sha512-SMNItMw1Z8QeeQVKnw8jA7xQNkeXdP+OPgin4Wi/QTx/B8RHHLnuZfqmFy7NtVeT2NF0kKYppW4WWd2CCYZjhQ==} cpu: [arm64] os: [freebsd] - '@oven/bun-freebsd-x64@1.4.0': - resolution: {integrity: sha512-dRwzti/qJqV1HWplU27iUWUqp+f2DtFSf2yqQKSb+HH2dDOC//Uqd9u/A5h1DMsLszfP5OGP9UwQIKxVwFODaA==} + '@oven/bun-freebsd-x64@1.4.2': + resolution: {integrity: sha512-THbPKXhO54N0DpFRKZNDZpQ7dpbX0bWASuARckAUS9wRtFIHsiY+uULXJvxJGo2YD1YewvXQ4G8Fj7XT5oBCiw==} cpu: [x64] os: [freebsd] - '@oven/bun-linux-aarch64-android@1.4.0': - resolution: {integrity: sha512-HpPIxJfDNPBPhiBNMyZoo/dOLijARfsx5j72vNuLtaTvl0Hh7HUculxjsOQ2WSyGoCgqXMEr1Qqjab1im9u1RA==} + '@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.4.0': - resolution: {integrity: sha512-RUjAAkJ/CdNV++zVxyANWshPc73CECYsfhk0fWAkoJjtywxJ2BwXzI6nopBBDMfs0HS+fhRGn6zGwU8ccxLeJg==} + '@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.4.0': - resolution: {integrity: sha512-Y5yAtCbHK6JjprXEtkdklDQFPADgs+CkfcliyY5g4JJ8baGHyQSrfpSkX3XVJ2C+aBLsdwNDdW+oczMsAwx6uA==} + '@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.4.0': - resolution: {integrity: sha512-u++KyLlfMn36yWz+AgJs+fZtS46UFDNpSSZhrcitkytONtNwq0X6Q9BDVEFXxYl/+Eec0xme1rb6MgW+U35WeA==} + '@oven/bun-linux-x64-android@1.4.2': + resolution: {integrity: sha512-6HC5tzcC79113n2IHCTJMWv+HsQImv4ZFEK2XpYLxY6HbT8tM4cUM2Zv1bHZBQsS3jv/zYBamDJ1UX7If0d5tw==} cpu: [x64] os: [android] - '@oven/bun-linux-x64-musl@1.4.0': - resolution: {integrity: sha512-C1Dv+ISL8YKEKM9jAHzNifOcRUoziy6UMxh+yVXjUCP6QnbRhENDHLaIWWkQZJyBLTn0I3xozflorAlHiGzGqA==} + '@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.4.0': - resolution: {integrity: sha512-Du44zebtPXJujvMLmtIxEQ6ykOhYt7L/Q+YIGVm+Yy+Pj/fpOnq60ggwIpKp/pGAFbYHNiTrA3JTjuZ9MTbZIg==} + '@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.4.0': - resolution: {integrity: sha512-FBAYaQpJBP0asgqzL6NFUfjdQqsV+kvTpJ/eWxPKj+RcDgIfPSuE8kvQuPYu5pa8u8JTujYMjmuyvHxVuQsInA==} + '@oven/bun-windows-aarch64@1.4.2': + resolution: {integrity: sha512-8EJ1ST7339WJE3poPW5nBgVW/lWf9HBz4W27ZUNhburKmcBLOByPyE6DP9fHD8FQGm5c+ilUN2hX1mrW0jxq9Q==} cpu: [arm64] os: [win32] - '@oven/bun-windows-x64@1.4.0': - resolution: {integrity: sha512-jRKv1NPLznMSZY5BEWciMF7zv0Tiyo2pQSxAJ3w+YWJ6y3VWNJQQQdLlV5Jx8lbOFDrJdrc9dD3GV17k3BP41A==} + '@oven/bun-windows-x64@1.4.2': + resolution: {integrity: sha512-+bN6OuVld/9diT/RLSXSW7JE6CvNE3gL9XsAEjULi1nUsXd6DNO6GuA9jNdNb3r8PdJFnYHr5aypNV1Oj3Rd9g==} cpu: [x64] os: [win32] - '@oxc-project/types@0.147.0': - resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} + '@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==} @@ -1152,101 +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.6': - resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} + '@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.2.6': - resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} + '@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.2.6': - resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} + '@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.2.6': - resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} + '@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.2.6': - resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} + '@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.2.6': - resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} + '@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.2.6': - resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} + '@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.2.6': - resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} + '@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.2.6': - resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} + '@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.2.6': - resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} + '@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.2.6': - resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} + '@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.2.6': - resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} + '@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.2.6': - resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} + '@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-win32-arm64-msvc@1.2.6': - resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} + '@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.2.6': - resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} + '@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] @@ -1289,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'} @@ -1311,33 +1323,33 @@ packages: svelte: optional: true - '@turbo/darwin-64@2.10.12': - resolution: {integrity: sha512-9nKgKoF6ZOUsM+or0OtNf+TTJSfGvDNP7ZFv/ZGWVwOSCkumyctQiTeHwB4UNljHTnC41AqylgbunLDHoccNrA==} + '@turbo/darwin-64@2.11.3': + resolution: {integrity: sha512-D4EIKTgPpxFQL5aZNbNqy57XOiRldM+mwP1ME9ZLM4DJoV3oNK7GKyQWVusz1umjiFNmLvunNGw7Jq4RUyYL3A==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.10.12': - resolution: {integrity: sha512-H4Elb1jqTZVeIC9bbcNwjSzemZ6RegoTOVHeuV5Osirt2Z8UguTyisMEkvZjPVZgMeN9J4ERZBFad40tFnkb7w==} + '@turbo/darwin-arm64@2.11.3': + resolution: {integrity: sha512-WOvgWIO86LzBQmL9aRFekROing1CZMVDjmdD7Dk6R65qFAFdrlKkufojenjW1AbUAqytpG6xsXzq8UEScdSdDQ==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.10.12': - resolution: {integrity: sha512-lr7KIotukvjZwEXiFSYAeOH3BWzjFVBbSzTbv0fuGFsNukYyH0+g1hB5ecqnJkgkYU+KHEMG1edOhnjiKON1wQ==} + '@turbo/linux-64@2.11.3': + resolution: {integrity: sha512-zIgNl9KkMnsdU54JrnazELatpMJDSktKjD3kjKVliUNMl/5uhFRrqQ+Z1qaACKw/2ybL7xzlsHir9s5q7sYlQg==} cpu: [x64] os: [android, linux] - '@turbo/linux-arm64@2.10.12': - resolution: {integrity: sha512-f0pZDTtvzB5SuNwuXBaKbZHUCMCukgc8nMlHEuvLmj91Fzec+MEbr3cAvGNor5htEDqZnO6Lxt9N/GPI/77oGA==} + '@turbo/linux-arm64@2.11.3': + resolution: {integrity: sha512-aJToWHx5JYvpPwIrAt1DuYrHWcpQGjGf0XVPRMKQBHou//JUYR6oowHeUZp9Oq7e3XDWA75E37C2GnNaBqCVnA==} cpu: [arm64] os: [android, linux] - '@turbo/windows-64@2.10.12': - resolution: {integrity: sha512-SDOueJRjS/QcykWf2KCRtTLmIl5YMKsLbXkXQGhDwcTXvKXZiS5ih5lBl/gkwZIpYFjqA/rAlfMzlAFcVHNe0g==} + '@turbo/windows-64@2.11.3': + resolution: {integrity: sha512-hu5biiwVGdMvHB/vvaoPjtudNBn3TqcsKK2A06ap3O0wnSZwAtTXe92tKTckpecDeVs+IC+4r8s2wXpbdSPWoA==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.10.12': - resolution: {integrity: sha512-0i0mVUa4kKk+/B3RwEwPMf9CB+T7ul56hn5FFHNA4VUNTOoLBEd6aNf3FaKfCatDNZ6cicCEf6if9QUTVyzzcA==} + '@turbo/windows-arm64@2.11.3': + resolution: {integrity: sha512-naDEb/31UmOOJOChi+LebLMKWKa1kId0yig1yTRZzDViDg1lBw1Ptu5iQYCvOfOOrx6swUUucf3zPWuPGWz7Jw==} cpu: [arm64] os: [win32] @@ -1362,8 +1374,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@26.4.0': - resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} + '@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==} @@ -1371,79 +1383,84 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - '@typescript-eslint/eslint-plugin@8.69.0': - resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} + '@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.69.0 + '@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.69.0': - resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} + '@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.69.0': - resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} + '@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.69.0': - resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} + '@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.69.0': - resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} + '@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.69.0': - resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} + '@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.69.0': - resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} + '@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.69.0': - resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} + '@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.69.0': - resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} + '@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.69.0': - resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} + '@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.11': - resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} + '@vitest/coverage-v8@5.0.1': + resolution: {integrity: sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==} peerDependencies: - '@vitest/browser': 4.1.11 - vitest: 4.1.11 + '@vitest/browser': 5.0.1 + vitest: 5.0.1 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/istanbul-lib-coverage@1.0.1': + resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + engines: {node: '>=22'} + + '@vitest/istanbul-lib-report@1.0.1': + resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + engines: {node: '>=22'} - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@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 @@ -1453,155 +1470,143 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + '@vitest/spy@5.0.1': + resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==} - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} - - '@yuku-codegen/binding-android-arm64@0.8.7': - resolution: {integrity: sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==} + '@yuku-codegen/binding-android-arm64@0.10.2': + resolution: {integrity: sha512-Oc2KInVkPfUjEB4PeV6X0NvIoyYTzDe/WmqFrTwBqKs6YKnz4A7XwWQvJTb7M0rLz9a6WgpUUiiS5QKaCi4O/g==} cpu: [arm64] os: [android] - '@yuku-codegen/binding-darwin-arm64@0.8.7': - resolution: {integrity: sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==} + '@yuku-codegen/binding-darwin-arm64@0.10.2': + resolution: {integrity: sha512-3H7eNPIHJndUJXZ4QUGBPq1zC6RGcUZfm7bymJes+/N7wTVWUn1bxZ9JcozYHpkWQNm9f23G8YWbaHD9aYH72A==} cpu: [arm64] os: [darwin] - '@yuku-codegen/binding-darwin-x64@0.8.7': - resolution: {integrity: sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==} + '@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.8.7': - resolution: {integrity: sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==} + '@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.8.7': - resolution: {integrity: sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==} + '@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.8.7': - resolution: {integrity: sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==} + '@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.8.7': - resolution: {integrity: sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==} + '@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.8.7': - resolution: {integrity: sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==} + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': + resolution: {integrity: sha512-RdsJrUfDYFVV3JOmWFUWwSu31fa1APn12xDeIKxgl/YWxrabwtxsDBNjF2561c7tmcbiewdCUvngqRs8AyGVLA==} cpu: [arm64] os: [linux] libc: [musl] - '@yuku-codegen/binding-linux-x64-gnu@0.8.7': - resolution: {integrity: sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==} + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': + resolution: {integrity: sha512-mVzWimEPPreaPyXIUvxsHoJzvO7ckZ5j0LgQFHz04zQIRwt9A9T2hA7K5/jYjJKkMxWG/U2VjhGNwVhtyU+uqg==} cpu: [x64] os: [linux] libc: [glibc] - '@yuku-codegen/binding-linux-x64-musl@0.8.7': - resolution: {integrity: sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==} + '@yuku-codegen/binding-linux-x64-musl@0.10.2': + resolution: {integrity: sha512-Y77fyISurmr2mvO1yeq3u+x/WJbACgPR4w+lzEVx30ViCxZzIGrZPRN1yEdZ9xUNPNsIO5thBHIdRNG+UGUG+Q==} cpu: [x64] os: [linux] libc: [musl] - '@yuku-codegen/binding-win32-arm64@0.8.7': - resolution: {integrity: sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==} + '@yuku-codegen/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-1+tGLyG0u5YYy0lev4ck7/0sd8xJ9SZde0dLut7qLDPZV5WVTV8x6OxNXUne/PGuHZyO1vK+txPQzzHOyXHGSw==} cpu: [arm64] os: [win32] - '@yuku-codegen/binding-win32-x64@0.8.7': - resolution: {integrity: sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==} + '@yuku-codegen/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-8a2SExRohRbwC0MY4VpOF0/RSckrJ2ASZqAor5/RYSJJaeadR93n10gZzcKT6pY9ukiSOIZCRKVRD2tH73T0qA==} cpu: [x64] os: [win32] - '@yuku-parser/binding-android-arm64@0.8.7': - resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} + '@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.8.7': - resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} + '@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.8.7': - resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} + '@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.8.7': - resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} + '@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.8.7': - resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} + '@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.8.7': - resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} + '@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.8.7': - resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} + '@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.8.7': - resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} + '@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.8.7': - resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} + '@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.8.7': - resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} + '@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.8.7': - resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} + '@yuku-parser/binding-win32-arm64@0.10.2': + resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} cpu: [arm64] os: [win32] - '@yuku-parser/binding-win32-x64@0.8.7': - resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} + '@yuku-parser/binding-win32-x64@0.10.2': + resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} cpu: [x64] os: [win32] - '@yuku-toolchain/types@0.8.7': - resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} + '@yuku-toolchain/types@0.10.2': + resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1639,23 +1644,23 @@ packages: 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.1.0: - resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + 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-v8-to-istanbul@1.0.5: - resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1670,19 +1675,19 @@ packages: bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - brace-expansion@2.1.4: - resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + brace-expansion@2.1.7: + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + 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.4.0: - resolution: {integrity: sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw==} + bun@1.4.2: + resolution: {integrity: sha512-TrSXo6HJfIEaczpb3kjX82I2pL47vK1QUNmHRCUdz9IzaOwa9lzOXSWwu2l18YHE3sNfGRapVLd4nNm+22vVVA==} cpu: [arm64, x64] os: [darwin, linux, android, freebsd, win32] hasBin: true @@ -1695,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'} @@ -1755,8 +1763,8 @@ packages: resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} engines: {node: '>=22.12.0'} - content-type@3.0.0: - resolution: {integrity: sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw==} + content-type@3.1.1: + resolution: {integrity: sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==} engines: {node: '>=22'} conventional-changelog-angular@9.4.0: @@ -1790,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'} @@ -1810,8 +1815,8 @@ packages: typescript: optional: true - cpx2@9.0.0: - resolution: {integrity: sha512-KRZGQCmHrM10xfLjrCBTQHpEIJgKtZyOOO5fygu9KE00IjQOL76FHjeON62R5PWS/W2J5ofgRJKF8rpUlEuLPQ==} + cpx2@9.0.3: + resolution: {integrity: sha512-SHeJspK34eVoLWuCrBW6YdcflnZwJifgADJNRkitsuZN2bi4+iby4slavs1qANeNiiZnIWRFG4vNcHp8UHrE5g==} engines: {node: ^22.0.0 || >=24.0.0, npm: '>=10'} hasBin: true @@ -1861,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} @@ -1876,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: @@ -1957,8 +1967,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.9.1: - resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==} + eslint@10.11.0: + resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -2016,8 +2026,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.6: - resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} + 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==} @@ -2035,9 +2045,8 @@ 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==} @@ -2050,9 +2059,8 @@ packages: 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.4: resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} @@ -2066,50 +2074,50 @@ 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 @@ -2133,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==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} human-signals@8.0.1: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} @@ -2164,16 +2175,16 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.8: - resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} + 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: @@ -2240,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==} @@ -2274,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==} @@ -2295,15 +2291,15 @@ packages: 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.12: - resolution: {integrity: sha512-uLVeV1c9OTk6qkdqnj+mpMD+ZdnZ0szVyWu58HwMmpwkHA1gCEkyjd3veZQXDnuw9KEwSRjcc9B1pS9XKIN1fA==} + libphonenumber-js@1.13.13: + resolution: {integrity: sha512-hV6SmWnyQ8U1QXhhqbqYBHIStNnscG7A5XbGodgdHhf/8N5PwhYpJA5U/yHDjYwPYTd8uNGXqq416XctuvK2PA==} lightningcss-android-arm64@1.33.0: resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} @@ -2382,8 +2378,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@17.4.1: - resolution: {integrity: sha512-FmJeudcalbSfg1du+JCfvi5vS6Qt08KgbfLWiHinbef+2JJwUZwAWVoaO1AcJVUTWPfk0t30PMQNwPAeCzYQ+Q==} + lint-staged@17.5.1: + resolution: {integrity: sha512-7EDuco1xnBMeVpvAbeMq1U5KXwJGLmY8q6l+Ye78r36C4mPc+Vg1Z2SK8gHyRTyvPro90A0q5/FAZ+Au23d6QQ==} engines: {node: '>=22.22.1'} hasBin: true @@ -2398,22 +2394,18 @@ packages: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} - lru-cache@11.5.2: - resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + 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.4: - resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} + 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==} @@ -2453,8 +2445,8 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2469,8 +2461,12 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} engines: {node: '>=12.20.0'} ohash@2.0.12: @@ -2480,8 +2476,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - open@11.0.2: - resolution: {integrity: sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==} + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} engines: {node: '>=20'} optionator@0.9.4: @@ -2505,8 +2501,8 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@7.0.7: - resolution: {integrity: sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==} + p-map@7.0.8: + resolution: {integrity: sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==} engines: {node: '>=18'} parent-module@1.0.1: @@ -2543,9 +2539,6 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2565,8 +2558,8 @@ packages: resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -2585,8 +2578,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true @@ -2598,11 +2591,15 @@ packages: 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.1.1: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} @@ -2630,13 +2627,14 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - rolldown-plugin-dts@0.27.14: - resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} - 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: '@typescript/native-preview': '*' '@volar/typescript': ~2.4.0 - rolldown: ^1.0.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: @@ -2644,13 +2642,15 @@ packages: optional: true '@volar/typescript': optional: true + '@vue/language-core': + optional: true typescript: optional: true vue-tsc: optional: true - rolldown@1.2.6: - resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} + rolldown@1.2.10: + resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2692,8 +2692,8 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} - smol-toml@1.8.0: - resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} + smol-toml@1.9.0: + resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==} engines: {node: '>= 18'} source-map-js@1.2.1: @@ -2741,10 +2741,6 @@ packages: 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'} @@ -2757,13 +2753,18 @@ packages: 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.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -2790,19 +2791,19 @@ packages: peerDependencies: typescript: '>=4.8.4' - tsdown@0.22.14: - resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} - 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.14 - '@tsdown/exe': 0.22.14 + '@tsdown/css': 0.23.0 + '@tsdown/exe': 0.23.0 '@vitejs/devtools': '*' publint: ^0.3.8 tsx: '*' typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 - unplugin-unused: ^0.5.0 + unplugin-unused: '>=0.5.0' unrun: '*' peerDependenciesMeta: '@arethetypeswrong/core': @@ -2831,16 +2832,16 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - turbo@2.10.12: - resolution: {integrity: sha512-AswgMPnpOoaVZHrrSBejETzEbuIA69OVGwfkHwfrY0A23VjWXBANzgq9+OymWOHAIArB7D1+1z498WY8fGg1Jw==} + 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.69.0: - resolution: {integrity: sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==} + 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 @@ -2854,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.28.0: - resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} + undici@6.28.1: + resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==} engines: {node: '>=18.17'} unicode-emoji-modifier-base@1.0.0: @@ -2889,17 +2890,17 @@ packages: resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} - verkit@0.3.2: - resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + verkit@0.4.1: + resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==} engines: {node: '>=18.12.0'} - vite@8.2.2: - resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} + 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.4.0 || ^0.5.0 + '@vitejs/devtools': ^0.7.1 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -2936,23 +2937,23 @@ packages: yaml: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - 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.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 + '@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 @@ -3007,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 @@ -3016,8 +3017,8 @@ packages: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@18.1.0: - resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} + 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: @@ -3028,17 +3029,17 @@ packages: resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} engines: {node: '>=18'} - yuku-ast@0.8.7: - resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} + yuku-ast@0.10.2: + resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} - yuku-codegen@0.8.7: - resolution: {integrity: sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==} + yuku-codegen@0.10.2: + resolution: {integrity: sha512-hentl2dtrF6cPjiAirtkfxfFZBA6Hdm61UqRJ7cA0qrlDNMk9PZGOjw5w1mx3E0hu/6pHcJF4dJW+D0SXHPZOA==} - yuku-parser@0.8.7: - resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} + yuku-parser@0.10.2: + resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} - zod@4.5.4: - resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} snapshots: @@ -3059,21 +3060,21 @@ snapshots: '@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.0 + undici: 6.28.1 '@actions/http-client@3.0.2': dependencies: tunnel: 0.0.6 - undici: 6.28.0 + undici: 6.28.1 '@actions/http-client@4.0.1': dependencies: tunnel: 0.0.6 - undici: 6.28.0 + undici: 6.28.1 '@actions/io@3.0.2': {} - '@angular-devkit/core@22.1.6(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) @@ -3084,18 +3085,18 @@ snapshots: optionalDependencies: chokidar: 5.0.0 - '@angular-devkit/schematics-cli@22.1.6(@types/node@26.4.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.1.6(chokidar@5.0.0) - '@angular-devkit/schematics': 22.1.6(chokidar@5.0.0) - '@inquirer/prompts': 8.5.2(@types/node@26.4.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.1.6(chokidar@5.0.0)': + '@angular-devkit/schematics@22.1.8(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 22.1.6(chokidar@5.0.0) + '@angular-devkit/core': 22.1.8(chokidar@5.0.0) jsonc-parser: 3.3.1 magic-string: 1.0.0 ora: 9.4.1 @@ -3111,7 +3112,7 @@ snapshots: '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -3123,14 +3124,14 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.29.8': + '@babel/parser@7.29.9': dependencies: '@babel/types': 7.29.8 '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 '@babel/traverse@7.29.8(supports-color@10.2.2)': @@ -3138,7 +3139,7 @@ snapshots: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/template': 7.29.7 '@babel/types': 7.29.8 debug: 4.4.3(supports-color@10.2.2) @@ -3152,80 +3153,92 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@commitlint/cli@21.2.2(@types/node@26.4.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)': + '@cacheable/memory@2.2.0': dependencies: - '@commitlint/config-conventional': 21.2.2 - '@commitlint/format': 21.2.2 - '@commitlint/lint': 21.2.2 - '@commitlint/load': 21.2.2(@types/node@26.4.0)(typescript@6.0.3) - '@commitlint/read': 21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) - '@commitlint/types': 21.2.0 - tinyexec: 1.3.0 - yargs: 18.1.0 + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@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.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/cli@21.2.2(@types/node@26.4.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + '@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/config-conventional': 21.2.2 - '@commitlint/format': 21.2.2 - '@commitlint/lint': 21.2.2 - '@commitlint/load': 21.2.2(@types/node@26.4.0)(typescript@6.0.3) - '@commitlint/read': 21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2) - '@commitlint/types': 21.2.0 - tinyexec: 1.3.0 - yargs: 18.1.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@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-conventional@21.2.2': + '@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.0': + '@commitlint/config-validator@21.2.3': dependencies: - '@commitlint/types': 21.2.0 + '@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 + '@commitlint/types': 21.2.3 es-toolkit: 1.52.0 '@commitlint/execute-rule@21.0.1': {} - '@commitlint/format@21.2.2': + '@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.2': + '@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.2': + '@commitlint/lint@21.2.3': dependencies: - '@commitlint/is-ignored': 21.2.2 - '@commitlint/parse': 21.2.2 - '@commitlint/rules': 21.2.2 - '@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.2(@types/node@26.4.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.2 - '@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.4.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) es-toolkit: 1.52.0 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -3235,46 +3248,46 @@ snapshots: '@commitlint/message@21.2.0': {} - '@commitlint/parse@21.2.2': + '@commitlint/parse@21.2.3': dependencies: - '@commitlint/types': 21.2.0 + '@commitlint/types': 21.2.3 conventional-changelog-angular: 9.4.0 conventional-commits-parser: 7.1.2 - '@commitlint/read@21.2.1(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@6.4.0)': dependencies: '@commitlint/top-level': 21.2.0 - '@commitlint/types': 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.0 + tinyexec: 1.3.1 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser - '@commitlint/read@21.2.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@7.1.2)': + '@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 + '@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.0 + tinyexec: 1.3.1 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser - '@commitlint/resolve-extends@21.2.2': + '@commitlint/resolve-extends@21.2.3': dependencies: - '@commitlint/config-validator': 21.2.0 - '@commitlint/types': 21.2.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.2': + '@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': {} @@ -3282,7 +3295,7 @@ snapshots: dependencies: escalade: 3.2.0 - '@commitlint/types@21.2.0': + '@commitlint/types@21.2.3': dependencies: conventional-commits-parser: 7.1.2 picocolors: 1.1.1 @@ -3322,9 +3335,9 @@ snapshots: '@dprint/toml@0.7.0': {} - '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))': dependencies: - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + 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': {} @@ -3349,7 +3362,7 @@ snapshots: '@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 @@ -3366,10 +3379,10 @@ snapshots: commander: 14.0.3 conventional-recommended-bump: 11.2.0 execa: 9.6.1 - git-cliff: 2.13.1 + git-cliff: 2.14.2 js-yaml: 4.3.2 semver: 7.8.5 - smol-toml: 1.8.0 + smol-toml: 1.9.0 '@favware/colorette-spinner@1.0.1': dependencies: @@ -3391,139 +3404,139 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@2.0.7': {} + '@inquirer/ansi@2.0.8': {} - '@inquirer/checkbox@5.2.3(@types/node@26.4.0)': + '@inquirer/checkbox@5.2.5(@types/node@26.6.2)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/figures': 2.0.8 - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/confirm@6.3.0(@types/node@26.4.0)': + '@inquirer/confirm@6.3.2(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/core@12.0.1(@types/node@26.4.0)': + '@inquirer/core@12.0.3(@types/node@26.6.2)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.8 - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/editor@5.3.1(@types/node@26.4.0)': + '@inquirer/editor@5.3.3(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/external-editor': 3.0.4(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/expand@5.1.3(@types/node@26.4.0)': + '@inquirer/expand@5.1.5(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/external-editor@3.0.4(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/figures@2.0.8': {} + '@inquirer/figures@2.0.9': {} - '@inquirer/input@5.1.4(@types/node@26.4.0)': + '@inquirer/input@5.1.6(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/number@4.2.1(@types/node@26.4.0)': + '@inquirer/number@4.2.3(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/password@5.2.0(@types/node@26.4.0)': + '@inquirer/password@5.2.2(@types/node@26.6.2)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 - - '@inquirer/prompts@8.5.2(@types/node@26.4.0)': - dependencies: - '@inquirer/checkbox': 5.2.3(@types/node@26.4.0) - '@inquirer/confirm': 6.3.0(@types/node@26.4.0) - '@inquirer/editor': 5.3.1(@types/node@26.4.0) - '@inquirer/expand': 5.1.3(@types/node@26.4.0) - '@inquirer/input': 5.1.4(@types/node@26.4.0) - '@inquirer/number': 4.2.1(@types/node@26.4.0) - '@inquirer/password': 5.2.0(@types/node@26.4.0) - '@inquirer/rawlist': 5.3.3(@types/node@26.4.0) - '@inquirer/search': 4.3.1(@types/node@26.4.0) - '@inquirer/select': 5.2.3(@types/node@26.4.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.4.0 - - '@inquirer/prompts@8.7.0(@types/node@26.4.0)': - dependencies: - '@inquirer/checkbox': 5.2.3(@types/node@26.4.0) - '@inquirer/confirm': 6.3.0(@types/node@26.4.0) - '@inquirer/editor': 5.3.1(@types/node@26.4.0) - '@inquirer/expand': 5.1.3(@types/node@26.4.0) - '@inquirer/input': 5.1.4(@types/node@26.4.0) - '@inquirer/number': 4.2.1(@types/node@26.4.0) - '@inquirer/password': 5.2.0(@types/node@26.4.0) - '@inquirer/rawlist': 5.3.3(@types/node@26.4.0) - '@inquirer/search': 4.3.1(@types/node@26.4.0) - '@inquirer/select': 5.2.3(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/rawlist@5.3.3(@types/node@26.4.0)': + '@inquirer/rawlist@5.3.5(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/search@4.3.1(@types/node@26.4.0)': + '@inquirer/search@4.3.3(@types/node@26.6.2)': dependencies: - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/figures': 2.0.8 - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/select@5.2.3(@types/node@26.4.0)': + '@inquirer/select@5.2.5(@types/node@26.6.2)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 12.0.1(@types/node@26.4.0) - '@inquirer/figures': 2.0.8 - '@inquirer/type': 4.1.0(@types/node@26.4.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.4.0 + '@types/node': 26.6.2 - '@inquirer/type@4.1.0(@types/node@26.4.0)': + '@inquirer/type@4.1.1(@types/node@26.6.2)': optionalDependencies: - '@types/node': 26.4.0 + '@types/node': 26.6.2 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -3543,6 +3556,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@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: '@actions/core': 3.0.1 @@ -3552,7 +3573,7 @@ snapshots: '@nanoforge-dev/editor@0.1.4': dependencies: - bun: 1.4.0 + bun: 1.4.2 class-transformer: 0.5.1 class-validator: 0.15.1 dotenv: 17.4.2 @@ -3563,26 +3584,26 @@ snapshots: '@nanoforge-dev/loader-client@1.5.1': dependencies: '@nanoforge-dev/loader-website': 1.5.1 - bun: 1.4.0 + bun: 1.4.2 commander: 15.0.0 '@nanoforge-dev/loader-server@1.5.1': dependencies: - bun: 1.4.0 + bun: 1.4.2 commander: 15.0.0 '@nanoforge-dev/loader-website@1.5.1': {} - '@nanoforge-dev/utils-eslint-config@1.0.2(@types/eslint@9.6.1)(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6)(supports-color@10.2.2)(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.5 '@favware/cliff-jumper': 6.1.0 - eslint-config-prettier: 10.1.8(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)) + 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.9.1(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.9.1(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6) + 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.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(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 @@ -3647,7 +3668,7 @@ snapshots: '@octokit/endpoint': 11.0.5 '@octokit/request-error': 7.1.2 '@octokit/types': 18.0.0 - content-type: 3.0.0 + content-type: 3.1.1 json-with-bigint: 3.5.12 universal-user-agent: 7.0.3 @@ -3663,43 +3684,43 @@ snapshots: dependencies: '@octokit/openapi-types': 29.0.1 - '@oven/bun-darwin-aarch64@1.4.0': + '@oven/bun-darwin-aarch64@1.4.2': optional: true - '@oven/bun-darwin-x64@1.4.0': + '@oven/bun-darwin-x64@1.4.2': optional: true - '@oven/bun-freebsd-aarch64@1.4.0': + '@oven/bun-freebsd-aarch64@1.4.2': optional: true - '@oven/bun-freebsd-x64@1.4.0': + '@oven/bun-freebsd-x64@1.4.2': optional: true - '@oven/bun-linux-aarch64-android@1.4.0': + '@oven/bun-linux-aarch64-android@1.4.2': optional: true - '@oven/bun-linux-aarch64-musl@1.4.0': + '@oven/bun-linux-aarch64-musl@1.4.2': optional: true - '@oven/bun-linux-aarch64@1.4.0': + '@oven/bun-linux-aarch64@1.4.2': optional: true - '@oven/bun-linux-x64-android@1.4.0': + '@oven/bun-linux-x64-android@1.4.2': optional: true - '@oven/bun-linux-x64-musl@1.4.0': + '@oven/bun-linux-x64-musl@1.4.2': optional: true - '@oven/bun-linux-x64@1.4.0': + '@oven/bun-linux-x64@1.4.2': optional: true - '@oven/bun-windows-aarch64@1.4.0': + '@oven/bun-windows-aarch64@1.4.2': optional: true - '@oven/bun-windows-x64@1.4.0': + '@oven/bun-windows-x64@1.4.2': optional: true - '@oxc-project/types@0.147.0': {} + '@oxc-project/types@0.151.0': {} '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -3760,53 +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-arm-eabi@1.2.6': + '@rolldown/binding-android-arm-eabi@1.2.10': optional: true - '@rolldown/binding-android-arm64@1.2.6': + '@rolldown/binding-android-arm64@1.2.10': optional: true - '@rolldown/binding-darwin-arm64@1.2.6': + '@rolldown/binding-darwin-arm64@1.2.10': optional: true - '@rolldown/binding-darwin-x64@1.2.6': + '@rolldown/binding-darwin-x64@1.2.10': optional: true - '@rolldown/binding-freebsd-x64@1.2.6': + '@rolldown/binding-freebsd-x64@1.2.10': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': + '@rolldown/binding-linux-arm-gnueabihf@1.2.10': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.6': + '@rolldown/binding-linux-arm64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.6': + '@rolldown/binding-linux-arm64-musl@1.2.10': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.6': + '@rolldown/binding-linux-ppc64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.6': + '@rolldown/binding-linux-s390x-gnu@1.2.10': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.6': + '@rolldown/binding-linux-x64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-x64-musl@1.2.6': + '@rolldown/binding-linux-x64-musl@1.2.10': optional: true - '@rolldown/binding-openharmony-arm64@1.2.6': + '@rolldown/binding-openharmony-arm64@1.2.10': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.6': + '@rolldown/binding-win32-arm64-msvc@1.2.10': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.6': + '@rolldown/binding-win32-x64-msvc@1.2.10': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -3833,38 +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.6)(supports-color@10.2.2)': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)(supports-color@10.2.2)': dependencies: '@babel/generator': 7.29.8 - '@babel/parser': 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.6 + prettier: 3.9.9 transitivePeerDependencies: - supports-color - '@turbo/darwin-64@2.10.12': + '@turbo/darwin-64@2.11.3': optional: true - '@turbo/darwin-arm64@2.10.12': + '@turbo/darwin-arm64@2.11.3': optional: true - '@turbo/linux-64@2.10.12': + '@turbo/linux-64@2.11.3': optional: true - '@turbo/linux-arm64@2.10.12': + '@turbo/linux-arm64@2.11.3': optional: true - '@turbo/windows-64@2.10.12': + '@turbo/windows-64@2.11.3': optional: true - '@turbo/windows-arm64@2.10.12': + '@turbo/windows-arm64@2.11.3': optional: true '@types/chai@5.2.3': @@ -3890,82 +3909,82 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@26.4.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.4.0 + '@types/node': 26.6.2 '@types/validator@13.15.10': {} - '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(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.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/type-utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.69.0 - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) - ignore: 7.0.8 + '@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.69.0(eslint@10.9.1(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)': dependencies: - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.69.0 + '@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.9.1(jiti@2.6.1)(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.69.0(supports-color@10.2.2)(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.69.0(typescript@6.0.3) - '@typescript-eslint/types': 8.69.0 + '@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.69.0': + '@typescript-eslint/scope-manager@8.70.1': dependencies: - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/visitor-keys': 8.69.0 + '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/visitor-keys': 8.70.1 - '@typescript-eslint/tsconfig-utils@8.69.0(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.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(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.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@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.9.1(jiti@2.6.1)(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.69.0': {} + '@typescript-eslint/types@8.70.1': {} - '@typescript-eslint/typescript-estree@8.69.0(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)': dependencies: - '@typescript-eslint/project-service': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/visitor-keys': 8.69.0 + '@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 @@ -3975,150 +3994,124 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.69.0(eslint@10.9.1(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)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)) - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + '@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.69.0': + '@typescript-eslint/visitor-keys@8.70.1': dependencies: - '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/types': 8.70.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': + '@vitest/coverage-v8@5.0.1(vitest@5.0.1)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.11 - ast-v8-to-istanbul: 1.0.5 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.4 - obug: 2.1.4 + '@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: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) + 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/expect@4.1.11': + '@vitest/istanbul-lib-report@1.0.1': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 - tinyrainbow: 3.1.1 + '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0))': + '@vitest/mocker@5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1))': dependencies: - '@vitest/spy': 4.1.11 + '@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.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0) - - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 + vite: 8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1) - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.11': {} - - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@vitest/spy@5.0.1': {} - '@yuku-codegen/binding-android-arm64@0.8.7': + '@yuku-codegen/binding-android-arm64@0.10.2': optional: true - '@yuku-codegen/binding-darwin-arm64@0.8.7': + '@yuku-codegen/binding-darwin-arm64@0.10.2': optional: true - '@yuku-codegen/binding-darwin-x64@0.8.7': + '@yuku-codegen/binding-darwin-x64@0.10.2': optional: true - '@yuku-codegen/binding-freebsd-x64@0.8.7': + '@yuku-codegen/binding-freebsd-x64@0.10.2': optional: true - '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + '@yuku-codegen/binding-linux-arm-gnu@0.10.2': optional: true - '@yuku-codegen/binding-linux-arm-musl@0.8.7': + '@yuku-codegen/binding-linux-arm-musl@0.10.2': optional: true - '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + '@yuku-codegen/binding-linux-arm64-gnu@0.10.2': optional: true - '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + '@yuku-codegen/binding-linux-arm64-musl@0.10.2': optional: true - '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + '@yuku-codegen/binding-linux-x64-gnu@0.10.2': optional: true - '@yuku-codegen/binding-linux-x64-musl@0.8.7': + '@yuku-codegen/binding-linux-x64-musl@0.10.2': optional: true - '@yuku-codegen/binding-win32-arm64@0.8.7': + '@yuku-codegen/binding-win32-arm64@0.10.2': optional: true - '@yuku-codegen/binding-win32-x64@0.8.7': + '@yuku-codegen/binding-win32-x64@0.10.2': optional: true - '@yuku-parser/binding-android-arm64@0.8.7': + '@yuku-parser/binding-android-arm64@0.10.2': optional: true - '@yuku-parser/binding-darwin-arm64@0.8.7': + '@yuku-parser/binding-darwin-arm64@0.10.2': optional: true - '@yuku-parser/binding-darwin-x64@0.8.7': + '@yuku-parser/binding-darwin-x64@0.10.2': optional: true - '@yuku-parser/binding-freebsd-x64@0.8.7': + '@yuku-parser/binding-freebsd-x64@0.10.2': optional: true - '@yuku-parser/binding-linux-arm-gnu@0.8.7': + '@yuku-parser/binding-linux-arm-gnu@0.10.2': optional: true - '@yuku-parser/binding-linux-arm-musl@0.8.7': + '@yuku-parser/binding-linux-arm-musl@0.10.2': optional: true - '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + '@yuku-parser/binding-linux-arm64-gnu@0.10.2': optional: true - '@yuku-parser/binding-linux-arm64-musl@0.8.7': + '@yuku-parser/binding-linux-arm64-musl@0.10.2': optional: true - '@yuku-parser/binding-linux-x64-gnu@0.8.7': + '@yuku-parser/binding-linux-x64-gnu@0.10.2': optional: true - '@yuku-parser/binding-linux-x64-musl@0.8.7': + '@yuku-parser/binding-linux-x64-musl@0.10.2': optional: true - '@yuku-parser/binding-win32-arm64@0.8.7': + '@yuku-parser/binding-win32-arm64@0.10.2': optional: true - '@yuku-parser/binding-win32-x64@0.8.7': + '@yuku-parser/binding-win32-x64@0.10.2': optional: true - '@yuku-toolchain/types@0.8.7': {} + '@yuku-toolchain/types@0.10.2': {} acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -4140,7 +4133,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.6 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4152,15 +4145,15 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.3.1: {} + ansis@4.4.0: {} argparse@2.0.1: {} - argue-cli@3.1.0: {} + argue-cli@3.2.0: {} assertion-error@2.0.1: {} - ast-v8-to-istanbul@1.0.5: + ast-v8-to-istanbul@1.0.7: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -4174,11 +4167,11 @@ snapshots: bottleneck@2.19.5: {} - brace-expansion@2.1.4: + brace-expansion@2.1.7: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.9: + brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -4186,20 +4179,20 @@ snapshots: dependencies: fill-range: 7.1.1 - bun@1.4.0: + bun@1.4.2: optionalDependencies: - '@oven/bun-darwin-aarch64': 1.4.0 - '@oven/bun-darwin-x64': 1.4.0 - '@oven/bun-freebsd-aarch64': 1.4.0 - '@oven/bun-freebsd-x64': 1.4.0 - '@oven/bun-linux-aarch64': 1.4.0 - '@oven/bun-linux-aarch64-android': 1.4.0 - '@oven/bun-linux-aarch64-musl': 1.4.0 - '@oven/bun-linux-x64': 1.4.0 - '@oven/bun-linux-x64-android': 1.4.0 - '@oven/bun-linux-x64-musl': 1.4.0 - '@oven/bun-windows-aarch64': 1.4.0 - '@oven/bun-windows-x64': 1.4.0 + '@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: @@ -4207,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: {} @@ -4228,7 +4229,7 @@ snapshots: class-validator@0.15.1: dependencies: '@types/validator': 13.15.10 - libphonenumber-js: 1.13.12 + libphonenumber-js: 1.13.13 validator: 13.15.35 cli-cursor@5.0.0: @@ -4251,7 +4252,7 @@ snapshots: commander@15.0.0: {} - content-type@3.0.0: {} + content-type@3.1.1: {} conventional-changelog-angular@9.4.0: dependencies: @@ -4273,7 +4274,7 @@ snapshots: conventional-commits-parser@7.1.2: dependencies: '@simple-libs/stream-utils': 2.0.0 - argue-cli: 3.1.0 + argue-cli: 3.2.0 conventional-recommended-bump@11.2.0: dependencies: @@ -4283,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.4.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.4.0 + '@types/node': 26.6.2 cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 typescript: 6.0.3 @@ -4301,14 +4300,14 @@ snapshots: optionalDependencies: typescript: 6.0.3 - cpx2@9.0.0: + cpx2@9.0.3: dependencies: debounce: 3.0.0 glob: 13.0.6 glob2base: 0.0.12 - ignore: 7.0.8 + ignore: 7.0.10 minimatch: 10.2.6 - p-map: 7.0.7 + p-map: 7.0.8 shell-quote: 1.10.0 subarg: 1.0.0 @@ -4345,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: {} @@ -4369,9 +4370,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)): + eslint-config-prettier@10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)): dependencies: - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) eslint-formatter-pretty@7.1.0: dependencies: @@ -4384,35 +4385,35 @@ snapshots: string-width: 8.2.2 supports-hyperlinks: 4.5.0 - eslint-formatting-reporter@0.0.0(eslint@10.9.1(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)): dependencies: - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + 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.9.1(jiti@2.6.1)(supports-color@10.2.2)): + 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.9.1(jiti@2.6.1)(supports-color@10.2.2) - eslint-formatting-reporter: 0.0.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)) + 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.12 oxfmt: 0.35.0 - prettier: 3.9.6 + 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.9.1(jiti@2.6.1)(supports-color@10.2.2)))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.9.6): + 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.9.1(jiti@2.6.1)(supports-color@10.2.2) - prettier: 3.9.6 + 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.9.1(jiti@2.6.1)(supports-color@10.2.2)) + 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: {} @@ -4427,14 +4428,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2): + eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2)) + '@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(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 @@ -4449,7 +4450,7 @@ 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 @@ -4517,7 +4518,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.6: {} + fast-uri@3.1.8: {} fast-wrap-ansi@0.2.2: dependencies: @@ -4531,9 +4532,9 @@ snapshots: 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: @@ -4546,10 +4547,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@4.0.1: + flat-cache@6.1.23: dependencies: + cacheable: 2.5.0 flatted: 3.4.4 - keyv: 4.5.4 + hookified: 1.15.1 flatted@3.4.4: {} @@ -4558,45 +4560,45 @@ snapshots: 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-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: @@ -4618,13 +4620,17 @@ snapshots: 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 - html-escaper@2.0.2: {} + hookable@6.1.2: {} + + hookified@1.15.1: {} + + hookified@2.2.0: {} human-signals@8.0.1: {} @@ -4636,14 +4642,14 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.8: {} + 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: {} @@ -4683,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: {} @@ -4710,8 +4703,6 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} @@ -4724,16 +4715,16 @@ snapshots: 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.12: {} + libphonenumber-js@1.13.13: {} lightningcss-android-arm64@1.33.0: optional: true @@ -4786,13 +4777,13 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@17.4.1: + lint-staged@17.5.1: dependencies: picomatch: 4.0.7 string-argv: 0.3.2 - tinyexec: 1.3.0 + tinyexec: 1.3.1 optionalDependencies: - yaml: 2.9.0 + yaml: 2.9.1 locate-path@6.0.0: dependencies: @@ -4805,26 +4796,22 @@ snapshots: is-unicode-supported: 2.1.0 yoctocolors: 2.2.0 - lru-cache@11.5.2: {} + lru-cache@11.5.3: {} - magic-string@0.30.21: + magic-string@1.0.0: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - magic-string@1.0.0: + magic-string@1.4.2: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - magicast@0.5.4: + magicast@0.5.5: dependencies: - '@babel/parser': 7.29.8 + '@babel/parser': 7.29.9 '@babel/types': 7.29.8 source-map-js: 1.2.1 - make-dir@4.0.0: - dependencies: - semver: 7.8.5 - meow@13.2.0: {} micromatch@4.0.8: @@ -4836,11 +4823,11 @@ snapshots: minimatch@10.2.6: dependencies: - brace-expansion: 5.0.9 + brace-expansion: 5.0.12 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.4 + brace-expansion: 2.1.7 minimist@1.2.8: {} @@ -4854,7 +4841,7 @@ snapshots: mute-stream@3.0.0: {} - nanoid@3.3.18: {} + nanoid@3.3.19: {} natural-compare@1.4.0: {} @@ -4870,7 +4857,9 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 - obug@2.1.4: {} + obug@2.2.1: {} + + obug@3.0.0: {} ohash@2.0.12: {} @@ -4878,7 +4867,7 @@ snapshots: dependencies: mimic-function: 5.0.1 - open@11.0.2: + open@11.0.4: dependencies: default-browser: 5.5.1 define-lazy-prop: 3.0.0 @@ -4939,7 +4928,7 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@7.0.7: {} + p-map@7.0.8: {} parent-module@1.0.1: dependencies: @@ -4968,11 +4957,9 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.5.2 + lru-cache: 11.5.3 minipass: 7.1.3 - pathe@2.0.3: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -4985,9 +4972,9 @@ snapshots: dependencies: irregular-plurals: 3.5.0 - postcss@8.5.26: + postcss@8.5.28: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -5001,7 +4988,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.6: {} + prettier@3.9.9: {} pretty-ms@9.3.1: dependencies: @@ -5009,9 +4996,13 @@ snapshots: 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 @@ -5033,40 +5024,40 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - rolldown-plugin-dts@0.27.14(rolldown@1.2.6)(typescript@6.0.3): + rolldown-plugin-dts@0.28.6(rolldown@1.2.10)(typescript@6.0.3): dependencies: dts-resolver: 3.0.0 - get-tsconfig: 5.0.0-beta.5 - obug: 2.1.4 - rolldown: 1.2.6 - yuku-ast: 0.8.7 - yuku-codegen: 0.8.7 - yuku-parser: 0.8.7 + 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.2.6: + rolldown@1.2.10: dependencies: - '@oxc-project/types': 0.147.0 + '@oxc-project/types': 0.151.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.6 - '@rolldown/binding-android-arm64': 1.2.6 - '@rolldown/binding-darwin-arm64': 1.2.6 - '@rolldown/binding-darwin-x64': 1.2.6 - '@rolldown/binding-freebsd-x64': 1.2.6 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 - '@rolldown/binding-linux-arm64-gnu': 1.2.6 - '@rolldown/binding-linux-arm64-musl': 1.2.6 - '@rolldown/binding-linux-ppc64-gnu': 1.2.6 - '@rolldown/binding-linux-s390x-gnu': 1.2.6 - '@rolldown/binding-linux-x64-gnu': 1.2.6 - '@rolldown/binding-linux-x64-musl': 1.2.6 - '@rolldown/binding-openharmony-arm64': 1.2.6 - '@rolldown/binding-win32-arm64-msvc': 1.2.6 - '@rolldown/binding-win32-x64-msvc': 1.2.6 + '@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: {} @@ -5094,7 +5085,7 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 - smol-toml@1.8.0: {} + smol-toml@1.9.0: {} source-map-js@1.2.1: {} @@ -5111,12 +5102,12 @@ 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.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: @@ -5131,10 +5122,6 @@ snapshots: supports-color@10.2.2: {} - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - supports-hyperlinks@4.5.0: dependencies: has-flag: 5.0.1 @@ -5152,10 +5139,12 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - tinybench@2.9.0: {} + tinybench@6.1.4: {} tinyexec@1.3.0: {} + tinyexec@1.3.1: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.7) @@ -5175,29 +5164,29 @@ snapshots: dependencies: typescript: 6.0.3 - tsdown@0.22.14(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.13)): + 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.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.6 - rolldown-plugin-dts: 0.27.14(rolldown@1.2.6)(typescript@6.0.3) - tinyexec: 1.3.0 + 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.3.2 + verkit: 0.4.1 optionalDependencies: typescript: 6.0.3 unrun: 0.3.1(synckit@0.11.13) transitivePeerDependencies: - '@typescript/native-preview' - '@volar/typescript' + - '@vue/language-core' - oxc-resolver - vue-tsc @@ -5205,26 +5194,26 @@ snapshots: tunnel@0.0.6: {} - turbo@2.10.12: + turbo@2.11.3: optionalDependencies: - '@turbo/darwin-64': 2.10.12 - '@turbo/darwin-arm64': 2.10.12 - '@turbo/linux-64': 2.10.12 - '@turbo/linux-arm64': 2.10.12 - '@turbo/windows-64': 2.10.12 - '@turbo/windows-arm64': 2.10.12 + '@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.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(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.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/parser': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.69.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - eslint: 10.9.1(jiti@2.6.1)(supports-color@10.2.2) + '@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 @@ -5233,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.28.0: {} + undici@6.28.1: {} unicode-emoji-modifier-base@1.0.0: {} @@ -5248,7 +5237,7 @@ snapshots: unrun@0.3.1(synckit@0.11.13): dependencies: - rolldown: 1.2.6 + rolldown: 1.2.10 optionalDependencies: synckit: 0.11.13 @@ -5258,46 +5247,40 @@ snapshots: validator@13.15.35: {} - verkit@0.3.2: {} + verkit@0.4.1: {} - vite@8.2.2(@types/node@26.4.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): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.26 - rolldown: 1.2.6 + postcss: 8.5.28 + rolldown: 1.2.10 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.4.0 + '@types/node': 26.6.2 fsevents: 2.3.3 jiti: 2.6.1 - yaml: 2.9.0 + yaml: 2.9.1 - vitest@4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)): + 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: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.0)(jiti@2.6.1)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 + '@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.4 - pathe: 2.0.3 + magic-string: 1.4.2 + obug: 2.2.1 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.2.2(@types/node@26.4.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.4.0 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + '@types/node': 26.6.2 + '@vitest/coverage-v8': 5.0.1(vitest@5.0.1) transitivePeerDependencies: - msw @@ -5327,12 +5310,12 @@ snapshots: yallist@5.0.0: {} - yaml@2.9.0: + yaml@2.9.1: optional: true yargs-parser@22.0.0: {} - yargs@18.1.0: + yargs@18.2.0: dependencies: cliui: 9.0.1 escalade: 3.2.0 @@ -5345,43 +5328,43 @@ snapshots: yoctocolors@2.2.0: {} - yuku-ast@0.8.7: + yuku-ast@0.10.2: dependencies: - '@yuku-toolchain/types': 0.8.7 + '@yuku-toolchain/types': 0.10.2 - yuku-codegen@0.8.7: + yuku-codegen@0.10.2: dependencies: - '@yuku-toolchain/types': 0.8.7 + '@yuku-toolchain/types': 0.10.2 optionalDependencies: - '@yuku-codegen/binding-android-arm64': 0.8.7 - '@yuku-codegen/binding-darwin-arm64': 0.8.7 - '@yuku-codegen/binding-darwin-x64': 0.8.7 - '@yuku-codegen/binding-freebsd-x64': 0.8.7 - '@yuku-codegen/binding-linux-arm-gnu': 0.8.7 - '@yuku-codegen/binding-linux-arm-musl': 0.8.7 - '@yuku-codegen/binding-linux-arm64-gnu': 0.8.7 - '@yuku-codegen/binding-linux-arm64-musl': 0.8.7 - '@yuku-codegen/binding-linux-x64-gnu': 0.8.7 - '@yuku-codegen/binding-linux-x64-musl': 0.8.7 - '@yuku-codegen/binding-win32-arm64': 0.8.7 - '@yuku-codegen/binding-win32-x64': 0.8.7 - - yuku-parser@0.8.7: - dependencies: - '@yuku-toolchain/types': 0.8.7 - yuku-ast: 0.8.7 + '@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.8.7 - '@yuku-parser/binding-darwin-arm64': 0.8.7 - '@yuku-parser/binding-darwin-x64': 0.8.7 - '@yuku-parser/binding-freebsd-x64': 0.8.7 - '@yuku-parser/binding-linux-arm-gnu': 0.8.7 - '@yuku-parser/binding-linux-arm-musl': 0.8.7 - '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 - '@yuku-parser/binding-linux-arm64-musl': 0.8.7 - '@yuku-parser/binding-linux-x64-gnu': 0.8.7 - '@yuku-parser/binding-linux-x64-musl': 0.8.7 - '@yuku-parser/binding-win32-arm64': 0.8.7 - '@yuku-parser/binding-win32-x64': 0.8.7 - - zod@4.5.4: {} + '@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 5c5c96a..cecfa10 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,35 +8,35 @@ allowBuilds: catalogs: build: cpx2: ^9.0.0 - tsdown: ^0.22.14 - turbo: ^2.10.12 + tsdown: ^0.23.0 + turbo: ^2.11.3 typescript: ^6.0.3 ci: - '@commitlint/cli': ^21.2.2 - '@commitlint/config-conventional': ^21.2.2 + '@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.4.1 + lint-staged: ^17.5.1 cli: - '@inquirer/prompts': ^8.7.0 + '@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/bun': ^1.3.14 - '@types/node': ^26.4.0 - bun: ^1.4.0 + '@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.2 - 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 @@ -44,19 +44,19 @@ catalogs: '@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.9.1 - prettier: ^3.9.6 + eslint: ^10.11.0 + prettier: ^3.9.9 loader: '@nanoforge-dev/loader-client': ^1.5.1 '@nanoforge-dev/loader-server': ^1.5.1 schematics: '@angular-devkit/core': ^22.0.4 - '@angular-devkit/schematics': ^22.1.6 - '@angular-devkit/schematics-cli': ^22.1.6 + '@angular-devkit/schematics': ^22.1.8 + '@angular-devkit/schematics-cli': ^22.1.8 rxjs: ^7.8.2 test: - '@vitest/coverage-v8': ^4.1.11 - vitest: ^4.1.11 + '@vitest/coverage-v8': ^5.0.1 + vitest: ^5.0.1 minimumReleaseAgeExclude: - '@nanoforge-dev/loader-client@1.5.1'