From e43d19907c931d41961a09cddf11e04a712f3561 Mon Sep 17 00:00:00 2001 From: Matthias Hempel <7202441+fotopixel@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:10:52 +0200 Subject: [PATCH] feat(cli): implement fwiz init command Add workspace-aware config scaffolding with nx.json/turbo.json patching, loadFwizConfig foundation, and a minimal nx-mf-demo example fixture. Co-authored-by: Cursor --- apps/cli/package.json | 14 +- apps/cli/src/commands/init.spec.ts | 28 +++ apps/cli/src/commands/init.ts | 66 +++++++ apps/cli/src/main.ts | 6 +- apps/cli/src/program.ts | 16 ++ apps/cli/tsconfig.app.json | 5 + examples/README.md | 2 +- examples/nx-mf-demo/README.md | 18 ++ .../apps/checkout/module-federation.config.ts | 7 + .../nx-mf-demo/apps/checkout/package.json | 15 ++ .../nx-mf-demo/apps/checkout/project.json | 7 + .../nx-mf-demo/apps/checkout/src/Checkout.tsx | 3 + .../apps/shell/module-federation.config.ts | 5 + examples/nx-mf-demo/apps/shell/package.json | 15 ++ examples/nx-mf-demo/apps/shell/project.json | 7 + examples/nx-mf-demo/apps/shell/src/main.tsx | 12 ++ examples/nx-mf-demo/nx.json | 8 + examples/nx-mf-demo/package.json | 12 ++ examples/nx-mf-demo/pnpm-workspace.yaml | 2 + libs/core/package.json | 4 +- libs/core/src/index.ts | 8 +- libs/core/src/lib/config/defaults.ts | 61 +++++++ libs/core/src/lib/config/init.spec.ts | 169 +++++++++++++++++ libs/core/src/lib/config/init.ts | 66 +++++++ libs/core/src/lib/config/load.spec.ts | 40 ++++ libs/core/src/lib/config/load.ts | 44 +++++ libs/core/src/lib/config/schema.ts | 51 ++++++ libs/core/src/lib/config/types.ts | 49 +++++ libs/core/src/lib/core.spec.ts | 7 - libs/core/src/lib/core.ts | 3 - libs/core/src/lib/workspace/detect.ts | 114 ++++++++++++ libs/core/src/lib/workspace/patch-nx.ts | 64 +++++++ libs/core/src/lib/workspace/patch-turbo.ts | 64 +++++++ libs/core/src/lib/workspace/patch.spec.ts | 91 ++++++++++ libs/core/src/lib/workspace/patch.ts | 21 +++ pnpm-lock.yaml | 171 ++++++++++++++---- 36 files changed, 1224 insertions(+), 51 deletions(-) create mode 100644 apps/cli/src/commands/init.spec.ts create mode 100644 apps/cli/src/commands/init.ts create mode 100644 apps/cli/src/program.ts create mode 100644 examples/nx-mf-demo/README.md create mode 100644 examples/nx-mf-demo/apps/checkout/module-federation.config.ts create mode 100644 examples/nx-mf-demo/apps/checkout/package.json create mode 100644 examples/nx-mf-demo/apps/checkout/project.json create mode 100644 examples/nx-mf-demo/apps/checkout/src/Checkout.tsx create mode 100644 examples/nx-mf-demo/apps/shell/module-federation.config.ts create mode 100644 examples/nx-mf-demo/apps/shell/package.json create mode 100644 examples/nx-mf-demo/apps/shell/project.json create mode 100644 examples/nx-mf-demo/apps/shell/src/main.tsx create mode 100644 examples/nx-mf-demo/nx.json create mode 100644 examples/nx-mf-demo/package.json create mode 100644 examples/nx-mf-demo/pnpm-workspace.yaml create mode 100644 libs/core/src/lib/config/defaults.ts create mode 100644 libs/core/src/lib/config/init.spec.ts create mode 100644 libs/core/src/lib/config/init.ts create mode 100644 libs/core/src/lib/config/load.spec.ts create mode 100644 libs/core/src/lib/config/load.ts create mode 100644 libs/core/src/lib/config/schema.ts create mode 100644 libs/core/src/lib/config/types.ts delete mode 100644 libs/core/src/lib/core.spec.ts delete mode 100644 libs/core/src/lib/core.ts create mode 100644 libs/core/src/lib/workspace/detect.ts create mode 100644 libs/core/src/lib/workspace/patch-nx.ts create mode 100644 libs/core/src/lib/workspace/patch-turbo.ts create mode 100644 libs/core/src/lib/workspace/patch.spec.ts create mode 100644 libs/core/src/lib/workspace/patch.ts diff --git a/apps/cli/package.json b/apps/cli/package.json index 15dd855..59cd579 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -95,12 +95,14 @@ } } }, - "test": { - "options": { - "passWithNoTests": true - } - } + "test": {} } }, - "dependencies": {} + "bin": { + "fwiz": "./dist/main.js" + }, + "dependencies": { + "@federation-wizards/core": "workspace:*", + "commander": "^15.0.0" + } } diff --git a/apps/cli/src/commands/init.spec.ts b/apps/cli/src/commands/init.spec.ts new file mode 100644 index 0000000..1327f88 --- /dev/null +++ b/apps/cli/src/commands/init.spec.ts @@ -0,0 +1,28 @@ +import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { initFwizConfig } from '@federation-wizards/core'; +import { parse as parseYaml } from 'yaml'; + +describe('fwiz init command', () => { + it('creates fwiz.config.yaml with sensible defaults', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-cli-init-')); + writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ dependencies: { react: '^19.0.0' } }), + ); + + const result = initFwizConfig(dir); + + expect(result.created).toBe(true); + + const config = parseYaml( + readFileSync(join(dir, 'fwiz.config.yaml'), 'utf8'), + ) as { version: string; hosts: unknown[]; remotes: unknown[] }; + + expect(config.version).toBe('1'); + expect(config.hosts.length).toBeGreaterThan(0); + expect(config.remotes.length).toBeGreaterThan(0); + }); +}); diff --git a/apps/cli/src/commands/init.ts b/apps/cli/src/commands/init.ts new file mode 100644 index 0000000..62eb3da --- /dev/null +++ b/apps/cli/src/commands/init.ts @@ -0,0 +1,66 @@ +import type { Command } from 'commander'; + +import { initFwizConfig } from '@federation-wizards/core'; + +export function registerInitCommand(program: Command): void { + program + .command('init') + .description( + 'Generate a central fwiz.config.yaml for Module Federation 2.0', + ) + .summary('Scaffold fwiz.config.yaml with workspace-aware defaults') + .addHelpText( + 'after', + ` +Examples: + $ fwiz init + $ fwiz init --cwd ./my-monorepo + +The command detects Nx, Turborepo, or plain repositories and creates +sensible host, remote, and shared dependency defaults. Running init +again is safe: an existing fwiz.config.yaml is validated but not overwritten. +`, + ) + .option( + '--cwd ', + 'Directory to initialize (defaults to current working directory)', + process.cwd(), + ) + .action((options: { cwd: string }) => { + const result = initFwizConfig(options.cwd); + + if (result.created) { + console.log(`Created ${result.configPath}`); + console.log( + `Detected ${result.config.workspace.type} workspace with ${result.config.hosts.length} host(s) and ${result.config.remotes.length} remote(s).`, + ); + } else { + console.log(`Config already exists at ${result.configPath}`); + } + + const patchedFiles = result.workspacePatches.filter( + (patch) => patch.patched, + ); + + for (const patch of patchedFiles) { + for (const change of patch.changes) { + console.log(change); + } + } + + if (result.created) { + return; + } + + if (result.validationWarnings.length > 0) { + console.warn('Validation warnings:'); + for (const warning of result.validationWarnings) { + console.warn(` - ${warning}`); + } + process.exitCode = 1; + return; + } + + console.log('Existing config is valid.'); + }); +} diff --git a/apps/cli/src/main.ts b/apps/cli/src/main.ts index 3451e9b..9fed6cb 100644 --- a/apps/cli/src/main.ts +++ b/apps/cli/src/main.ts @@ -1 +1,5 @@ -console.log('Hello World'); +#!/usr/bin/env node + +import { createProgram } from './program.js'; + +createProgram().parse(process.argv); diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts new file mode 100644 index 0000000..1537071 --- /dev/null +++ b/apps/cli/src/program.ts @@ -0,0 +1,16 @@ +import { Command } from 'commander'; + +import { registerInitCommand } from './commands/init.js'; + +export function createProgram(): Command { + const program = new Command(); + + program + .name('fwiz') + .description('Federation Wizards CLI for Module Federation workflows') + .version('0.0.1'); + + registerInitCommand(program); + + return program; +} diff --git a/apps/cli/tsconfig.app.json b/apps/cli/tsconfig.app.json index eca379f..c7c1ab8 100644 --- a/apps/cli/tsconfig.app.json +++ b/apps/cli/tsconfig.app.json @@ -17,5 +17,10 @@ "eslint.config.js", "eslint.config.cjs", "eslint.config.mjs" + ], + "references": [ + { + "path": "../../libs/core/tsconfig.lib.json" + } ] } diff --git a/examples/README.md b/examples/README.md index 1df95a2..de88791 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Examples -Demo projects will be added here. +- [`nx-mf-demo`](./nx-mf-demo) — minimal Nx-style Module Federation workspace for `fwiz init` and future dev/validate tests. diff --git a/examples/nx-mf-demo/README.md b/examples/nx-mf-demo/README.md new file mode 100644 index 0000000..b5c15bb --- /dev/null +++ b/examples/nx-mf-demo/README.md @@ -0,0 +1,18 @@ +# Nx Module Federation Demo + +Minimal Nx-style Module Federation workspace used to exercise `fwiz init`, `fwiz dev`, and `fwiz validate` in future work. + +## Layout + +- `apps/shell` — host application +- `apps/checkout` — remote application + +## Usage + +```bash +cd examples/nx-mf-demo +pnpm install +npx fwiz init +``` + +`fwiz init` generates `fwiz.config.yaml` and patches `nx.json` with host/remote project references. diff --git a/examples/nx-mf-demo/apps/checkout/module-federation.config.ts b/examples/nx-mf-demo/apps/checkout/module-federation.config.ts new file mode 100644 index 0000000..413382b --- /dev/null +++ b/examples/nx-mf-demo/apps/checkout/module-federation.config.ts @@ -0,0 +1,7 @@ +// Placeholder Module Federation 2.0 remote config for fwiz tooling tests. +export default { + name: 'checkout', + exposes: { + './Checkout': './src/Checkout.tsx', + }, +}; diff --git a/examples/nx-mf-demo/apps/checkout/package.json b/examples/nx-mf-demo/apps/checkout/package.json new file mode 100644 index 0000000..70dd6b4 --- /dev/null +++ b/examples/nx-mf-demo/apps/checkout/package.json @@ -0,0 +1,15 @@ +{ + "name": "checkout", + "version": "0.0.0", + "private": true, + "nx": { + "targets": { + "serve": { + "executor": "nx:run-commands", + "options": { + "command": "echo 'checkout dev server placeholder'" + } + } + } + } +} diff --git a/examples/nx-mf-demo/apps/checkout/project.json b/examples/nx-mf-demo/apps/checkout/project.json new file mode 100644 index 0000000..750bb57 --- /dev/null +++ b/examples/nx-mf-demo/apps/checkout/project.json @@ -0,0 +1,7 @@ +{ + "name": "checkout", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/checkout/src", + "projectType": "application", + "tags": ["type:remote", "scope:checkout"] +} diff --git a/examples/nx-mf-demo/apps/checkout/src/Checkout.tsx b/examples/nx-mf-demo/apps/checkout/src/Checkout.tsx new file mode 100644 index 0000000..e2b4a2e --- /dev/null +++ b/examples/nx-mf-demo/apps/checkout/src/Checkout.tsx @@ -0,0 +1,3 @@ +export function Checkout() { + return
Checkout remote placeholder
; +} diff --git a/examples/nx-mf-demo/apps/shell/module-federation.config.ts b/examples/nx-mf-demo/apps/shell/module-federation.config.ts new file mode 100644 index 0000000..ad84699 --- /dev/null +++ b/examples/nx-mf-demo/apps/shell/module-federation.config.ts @@ -0,0 +1,5 @@ +// Placeholder Module Federation 2.0 host config for fwiz tooling tests. +export default { + name: 'shell', + remotes: ['checkout'], +}; diff --git a/examples/nx-mf-demo/apps/shell/package.json b/examples/nx-mf-demo/apps/shell/package.json new file mode 100644 index 0000000..dd5d7df --- /dev/null +++ b/examples/nx-mf-demo/apps/shell/package.json @@ -0,0 +1,15 @@ +{ + "name": "shell", + "version": "0.0.0", + "private": true, + "nx": { + "targets": { + "serve": { + "executor": "nx:run-commands", + "options": { + "command": "echo 'shell dev server placeholder'" + } + } + } + } +} diff --git a/examples/nx-mf-demo/apps/shell/project.json b/examples/nx-mf-demo/apps/shell/project.json new file mode 100644 index 0000000..92f3a95 --- /dev/null +++ b/examples/nx-mf-demo/apps/shell/project.json @@ -0,0 +1,7 @@ +{ + "name": "shell", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/shell/src", + "projectType": "application", + "tags": ["type:host", "scope:shell"] +} diff --git a/examples/nx-mf-demo/apps/shell/src/main.tsx b/examples/nx-mf-demo/apps/shell/src/main.tsx new file mode 100644 index 0000000..0dfa311 --- /dev/null +++ b/examples/nx-mf-demo/apps/shell/src/main.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; + +function ShellApp() { + return

Shell host placeholder

; +} + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/examples/nx-mf-demo/nx.json b/examples/nx-mf-demo/nx.json new file mode 100644 index 0000000..5c9fa56 --- /dev/null +++ b/examples/nx-mf-demo/nx.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/nx/schemas/nx-schema.json", + "defaultBase": "main", + "workspaceLayout": { + "appsDir": "apps", + "libsDir": "libs" + } +} diff --git a/examples/nx-mf-demo/package.json b/examples/nx-mf-demo/package.json new file mode 100644 index 0000000..40ebe70 --- /dev/null +++ b/examples/nx-mf-demo/package.json @@ -0,0 +1,12 @@ +{ + "name": "nx-mf-demo", + "version": "0.0.0", + "private": true, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "typescript": "~5.9.2" + } +} diff --git a/examples/nx-mf-demo/pnpm-workspace.yaml b/examples/nx-mf-demo/pnpm-workspace.yaml new file mode 100644 index 0000000..06b6051 --- /dev/null +++ b/examples/nx-mf-demo/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "apps/*" diff --git a/libs/core/package.json b/libs/core/package.json index ec8777a..5f66821 100644 --- a/libs/core/package.json +++ b/libs/core/package.json @@ -16,6 +16,8 @@ } }, "dependencies": { - "tslib": "^2.3.0" + "joi": "^18.2.1", + "tslib": "^2.3.0", + "yaml": "^2.9.0" } } diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 5335b2d..3485403 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -1 +1,7 @@ -export * from './lib/core.js'; +export * from './lib/config/types.js'; +export * from './lib/config/schema.js'; +export * from './lib/config/defaults.js'; +export * from './lib/config/init.js'; +export * from './lib/config/load.js'; +export * from './lib/workspace/detect.js'; +export * from './lib/workspace/patch.js'; diff --git a/libs/core/src/lib/config/defaults.ts b/libs/core/src/lib/config/defaults.ts new file mode 100644 index 0000000..fe8f602 --- /dev/null +++ b/libs/core/src/lib/config/defaults.ts @@ -0,0 +1,61 @@ +import type { FwizConfig, WorkspaceInfo } from './types.js'; + +const DEFAULT_HOST_PORT = 4200; +const DEFAULT_REMOTE_PORT = 4201; + +function createSharedDependencies(reactVersion?: string): FwizConfig['shared'] { + const requiredVersion = reactVersion ?? '^19.0.0'; + + return { + react: { + singleton: true, + requiredVersion, + eager: false, + }, + 'react-dom': { + singleton: true, + requiredVersion, + eager: false, + }, + }; +} + +function createHosts(appProjects: string[]): FwizConfig['hosts'] { + if (appProjects.length === 0) { + return [{ name: 'shell', port: DEFAULT_HOST_PORT }]; + } + + const [hostProject, ..._] = appProjects; + + return [ + { + name: hostProject, + project: hostProject, + port: DEFAULT_HOST_PORT, + }, + ]; +} + +function createRemotes(appProjects: string[]): FwizConfig['remotes'] { + if (appProjects.length <= 1) { + return [{ name: 'remote', port: DEFAULT_REMOTE_PORT }]; + } + + return appProjects.slice(1).map((project, index) => ({ + name: project, + project, + port: DEFAULT_REMOTE_PORT + index, + })); +} + +export function createDefaultConfig(workspace: WorkspaceInfo): FwizConfig { + return { + version: '1', + workspace: { + type: workspace.type, + }, + hosts: createHosts(workspace.appProjects), + remotes: createRemotes(workspace.appProjects), + shared: createSharedDependencies(workspace.reactVersion), + }; +} diff --git a/libs/core/src/lib/config/init.spec.ts b/libs/core/src/lib/config/init.spec.ts new file mode 100644 index 0000000..525b7ca --- /dev/null +++ b/libs/core/src/lib/config/init.spec.ts @@ -0,0 +1,169 @@ +import { + mkdtempSync, + mkdirSync, + readFileSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; +import { describe, expect, it } from 'vitest'; + +import { createDefaultConfig } from './defaults.js'; +import { initFwizConfig, FWIZ_CONFIG_FILENAME } from './init.js'; +import { validateFwizConfig } from './schema.js'; +import { detectWorkspace } from '../workspace/detect.js'; + +describe('detectWorkspace', () => { + it('detects Nx workspaces', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-nx-')); + writeFileSync(join(dir, 'nx.json'), '{}'); + + expect(detectWorkspace(dir).type).toBe('nx'); + }); + + it('detects Turborepo workspaces', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-turbo-')); + writeFileSync(join(dir, 'turbo.json'), '{}'); + + expect(detectWorkspace(dir).type).toBe('turbo'); + }); + + it('lists Nx app projects from apps/', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-apps-')); + writeFileSync(join(dir, 'nx.json'), '{}'); + mkdirSync(join(dir, 'apps', 'shell'), { recursive: true }); + mkdirSync(join(dir, 'apps', 'checkout'), { recursive: true }); + writeFileSync(join(dir, 'apps', 'shell', 'project.json'), '{}'); + writeFileSync(join(dir, 'apps', 'checkout', 'project.json'), '{}'); + + expect(detectWorkspace(dir).appProjects).toEqual(['checkout', 'shell']); + }); + + it('lists Nx app projects from package.json nx targets', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-apps-pkg-')); + writeFileSync(join(dir, 'nx.json'), '{}'); + mkdirSync(join(dir, 'apps', 'shell'), { recursive: true }); + writeFileSync( + join(dir, 'apps', 'shell', 'package.json'), + JSON.stringify({ name: 'shell', nx: { targets: {} } }), + ); + + expect(detectWorkspace(dir).appProjects).toEqual(['shell']); + }); + + it('respects custom appsDir from nx.json', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-apps-custom-')); + writeFileSync( + join(dir, 'nx.json'), + JSON.stringify({ workspaceLayout: { appsDir: 'applications' } }), + ); + mkdirSync(join(dir, 'applications', 'shell'), { recursive: true }); + writeFileSync(join(dir, 'applications', 'shell', 'project.json'), '{}'); + + expect(detectWorkspace(dir).appProjects).toEqual(['shell']); + }); +}); + +describe('createDefaultConfig', () => { + it('creates placeholder host and remote for plain workspaces', () => { + const config = createDefaultConfig({ + type: 'plain', + appProjects: [], + }); + + expect(config.workspace.type).toBe('plain'); + expect(config.hosts).toEqual([{ name: 'shell', port: 4200 }]); + expect(config.remotes).toEqual([{ name: 'remote', port: 4201 }]); + expect(config.shared.react.singleton).toBe(true); + }); + + it('maps discovered app projects to hosts and remotes', () => { + const config = createDefaultConfig({ + type: 'nx', + appProjects: ['shell', 'checkout', 'profile'], + reactVersion: '^19.0.0', + }); + + expect(config.hosts).toEqual([ + { name: 'shell', project: 'shell', port: 4200 }, + ]); + expect(config.remotes).toEqual([ + { name: 'checkout', project: 'checkout', port: 4201 }, + { name: 'profile', project: 'profile', port: 4202 }, + ]); + expect(config.shared.react.requiredVersion).toBe('^19.0.0'); + }); +}); + +describe('validateFwizConfig', () => { + it('accepts a valid config', () => { + const config = createDefaultConfig({ + type: 'plain', + appProjects: [], + }); + + expect(validateFwizConfig(config).valid).toBe(true); + }); + + it('reports validation warnings for invalid configs', () => { + const result = validateFwizConfig({ version: '1' }); + + expect(result.valid).toBe(false); + if (!result.valid) { + expect(result.warnings.length).toBeGreaterThan(0); + } + }); +}); + +describe('initFwizConfig', () => { + it('creates fwiz.config.yaml in an empty directory', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-init-')); + writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ dependencies: { react: '^19.0.0' } }), + ); + + const result = initFwizConfig(dir); + + expect(result.created).toBe(true); + expect(readFileSync(join(dir, FWIZ_CONFIG_FILENAME), 'utf8')).toContain( + 'version: "1"', + ); + }); + + it('is idempotent when config already exists', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-idempotent-')); + const config = createDefaultConfig({ type: 'plain', appProjects: [] }); + writeFileSync( + join(dir, FWIZ_CONFIG_FILENAME), + `${stringifyYaml(config)}\n`, + 'utf8', + ); + + const first = initFwizConfig(dir); + const second = initFwizConfig(dir); + + expect(first.created).toBe(false); + expect(second.created).toBe(false); + expect( + parseYaml(readFileSync(join(dir, FWIZ_CONFIG_FILENAME), 'utf8')), + ).toEqual(config); + }); + + it('patches nx.json when initializing an Nx workspace', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-init-nx-')); + writeFileSync(join(dir, 'nx.json'), '{}\n', 'utf8'); + mkdirSync(join(dir, 'apps', 'shell'), { recursive: true }); + mkdirSync(join(dir, 'apps', 'checkout'), { recursive: true }); + writeFileSync(join(dir, 'apps', 'shell', 'project.json'), '{}'); + writeFileSync(join(dir, 'apps', 'checkout', 'project.json'), '{}'); + + const result = initFwizConfig(dir); + + expect(result.created).toBe(true); + expect(result.workspacePatches.some((patch) => patch.patched)).toBe(true); + expect(JSON.parse(readFileSync(join(dir, 'nx.json'), 'utf8')).fwiz).toBeDefined(); + }); +}); diff --git a/libs/core/src/lib/config/init.ts b/libs/core/src/lib/config/init.ts new file mode 100644 index 0000000..d00e91c --- /dev/null +++ b/libs/core/src/lib/config/init.ts @@ -0,0 +1,66 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { stringify as stringifyYaml, parse as parseYaml } from 'yaml'; + +import { createDefaultConfig } from './defaults.js'; +import { validateFwizConfig } from './schema.js'; +import type { FwizConfig, InitResult } from './types.js'; +import { detectWorkspace } from '../workspace/detect.js'; +import { patchWorkspaceConfig } from '../workspace/patch.js'; + +export const FWIZ_CONFIG_FILENAME = 'fwiz.config.yaml'; + +function parseExistingConfig(configPath: string): unknown { + const raw = readFileSync(configPath, 'utf8'); + return parseYaml(raw); +} + +function serializeConfig(config: FwizConfig): string { + return stringifyYaml(config, { + indent: 2, + lineWidth: 0, + }); +} + +export function initFwizConfig(cwd: string): InitResult { + const configPath = join(cwd, FWIZ_CONFIG_FILENAME); + const workspace = detectWorkspace(cwd); + + if (existsSync(configPath)) { + const existingConfig = parseExistingConfig(configPath); + const validation = validateFwizConfig(existingConfig); + const config = validation.valid + ? validation.value + : createDefaultConfig(workspace); + + return { + created: false, + configPath, + config, + validationWarnings: validation.valid ? [] : validation.warnings, + workspacePatches: validation.valid + ? patchWorkspaceConfig(cwd, config) + : [], + }; + } + + const config = createDefaultConfig(workspace); + const validation = validateFwizConfig(config); + + if (!validation.valid) { + throw new Error( + `Generated config failed validation: ${validation.warnings.join('; ')}`, + ); + } + + writeFileSync(configPath, `${serializeConfig(config)}\n`, 'utf8'); + + return { + created: true, + configPath, + config, + validationWarnings: [], + workspacePatches: patchWorkspaceConfig(cwd, config), + }; +} diff --git a/libs/core/src/lib/config/load.spec.ts b/libs/core/src/lib/config/load.spec.ts new file mode 100644 index 0000000..88bd9d5 --- /dev/null +++ b/libs/core/src/lib/config/load.spec.ts @@ -0,0 +1,40 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { stringify as stringifyYaml } from 'yaml'; +import { describe, expect, it } from 'vitest'; + +import { createDefaultConfig } from './defaults.js'; +import { FWIZ_CONFIG_FILENAME } from './init.js'; +import { FwizConfigError, loadFwizConfig } from './load.js'; + +describe('loadFwizConfig', () => { + it('loads and validates an existing config', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-load-')); + const config = createDefaultConfig({ type: 'plain', appProjects: [] }); + + writeFileSync( + join(dir, FWIZ_CONFIG_FILENAME), + `${stringifyYaml(config)}\n`, + 'utf8', + ); + + expect(loadFwizConfig(dir)).toEqual(config); + }); + + it('throws when config is missing', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-load-missing-')); + + expect(() => loadFwizConfig(dir)).toThrow(FwizConfigError); + expect(() => loadFwizConfig(dir)).toThrow(/Run fwiz init first/); + }); + + it('throws when config is invalid', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-load-invalid-')); + writeFileSync(join(dir, FWIZ_CONFIG_FILENAME), 'version: "1"\n', 'utf8'); + + expect(() => loadFwizConfig(dir)).toThrow(FwizConfigError); + expect(() => loadFwizConfig(dir)).toThrow(/Invalid config/); + }); +}); diff --git a/libs/core/src/lib/config/load.ts b/libs/core/src/lib/config/load.ts new file mode 100644 index 0000000..21790a2 --- /dev/null +++ b/libs/core/src/lib/config/load.ts @@ -0,0 +1,44 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { parse as parseYaml } from 'yaml'; + +import { FWIZ_CONFIG_FILENAME } from './init.js'; +import { validateFwizConfig } from './schema.js'; +import type { FwizConfig } from './types.js'; + +export class FwizConfigError extends Error { + constructor(message: string) { + super(message); + this.name = 'FwizConfigError'; + } +} + +export function loadFwizConfig(cwd: string): FwizConfig { + const configPath = join(cwd, FWIZ_CONFIG_FILENAME); + + if (!existsSync(configPath)) { + throw new FwizConfigError( + `Config not found at ${configPath}. Run fwiz init first.`, + ); + } + + let parsed: unknown; + + try { + parsed = parseYaml(readFileSync(configPath, 'utf8')); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new FwizConfigError(`Failed to parse ${configPath}: ${message}`); + } + + const validation = validateFwizConfig(parsed); + + if (!validation.valid) { + throw new FwizConfigError( + `Invalid config at ${configPath}: ${validation.warnings.join('; ')}`, + ); + } + + return validation.value; +} diff --git a/libs/core/src/lib/config/schema.ts b/libs/core/src/lib/config/schema.ts new file mode 100644 index 0000000..1cc0169 --- /dev/null +++ b/libs/core/src/lib/config/schema.ts @@ -0,0 +1,51 @@ +import Joi from 'joi'; + +import type { FwizConfig } from './types.js'; + +const sharedDependencySchema = Joi.object({ + singleton: Joi.boolean().required(), + requiredVersion: Joi.string().required(), + eager: Joi.boolean().required(), +}); + +const hostSchema = Joi.object({ + name: Joi.string().required(), + project: Joi.string().optional(), + port: Joi.number().integer().min(1).max(65535).required(), +}); + +const remoteSchema = Joi.object({ + name: Joi.string().required(), + project: Joi.string().optional(), + port: Joi.number().integer().min(1).max(65535).required(), +}); + +export const fwizConfigSchema = Joi.object({ + version: Joi.string().required(), + workspace: Joi.object({ + type: Joi.string().valid('nx', 'turbo', 'plain').required(), + }).required(), + hosts: Joi.array().items(hostSchema).min(1).required(), + remotes: Joi.array().items(remoteSchema).required(), + shared: Joi.object().pattern(Joi.string(), sharedDependencySchema).required(), +}); + +export function validateFwizConfig( + config: unknown, +): { valid: true; value: FwizConfig } | { valid: false; warnings: string[] } { + const result = fwizConfigSchema.validate(config, { + abortEarly: false, + allowUnknown: false, + }); + + if (result.error) { + return { + valid: false, + warnings: result.error.details.map( + (detail) => `${detail.path.join('.')}: ${detail.message}`, + ), + }; + } + + return { valid: true, value: result.value as FwizConfig }; +} diff --git a/libs/core/src/lib/config/types.ts b/libs/core/src/lib/config/types.ts new file mode 100644 index 0000000..c82bfe8 --- /dev/null +++ b/libs/core/src/lib/config/types.ts @@ -0,0 +1,49 @@ +export type WorkspaceType = 'nx' | 'turbo' | 'plain'; + +export interface SharedDependencyConfig { + singleton: boolean; + requiredVersion: string; + eager: boolean; +} + +export interface HostConfig { + name: string; + project?: string; + port: number; +} + +export interface RemoteConfig { + name: string; + project?: string; + port: number; +} + +export interface FwizConfig { + version: string; + workspace: { + type: WorkspaceType; + }; + hosts: HostConfig[]; + remotes: RemoteConfig[]; + shared: Record; +} + +export interface WorkspaceInfo { + type: WorkspaceType; + appProjects: string[]; + reactVersion?: string; +} + +export interface WorkspacePatchResult { + patched: boolean; + filePath: string; + changes: string[]; +} + +export interface InitResult { + created: boolean; + configPath: string; + config: FwizConfig; + validationWarnings: string[]; + workspacePatches: WorkspacePatchResult[]; +} diff --git a/libs/core/src/lib/core.spec.ts b/libs/core/src/lib/core.spec.ts deleted file mode 100644 index da41ca6..0000000 --- a/libs/core/src/lib/core.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { core } from './core.js'; - -describe('core', () => { - it('should work', () => { - expect(core()).toEqual('core'); - }); -}); diff --git a/libs/core/src/lib/core.ts b/libs/core/src/lib/core.ts deleted file mode 100644 index 54913ed..0000000 --- a/libs/core/src/lib/core.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function core(): string { - return 'core'; -} diff --git a/libs/core/src/lib/workspace/detect.ts b/libs/core/src/lib/workspace/detect.ts new file mode 100644 index 0000000..c124568 --- /dev/null +++ b/libs/core/src/lib/workspace/detect.ts @@ -0,0 +1,114 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import type { WorkspaceInfo, WorkspaceType } from '../config/types.js'; + +function detectWorkspaceType(cwd: string): WorkspaceType { + if (existsSync(join(cwd, 'nx.json'))) { + return 'nx'; + } + + if (existsSync(join(cwd, 'turbo.json'))) { + return 'turbo'; + } + + return 'plain'; +} + +function readNxAppsDir(cwd: string): string { + const nxJsonPath = join(cwd, 'nx.json'); + + if (!existsSync(nxJsonPath)) { + return 'apps'; + } + + try { + const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf8')) as { + workspaceLayout?: { appsDir?: string }; + }; + + return nxJson.workspaceLayout?.appsDir ?? 'apps'; + } catch { + return 'apps'; + } +} + +function isNxAppProject(appDir: string): boolean { + const projectJsonPath = join(appDir, 'project.json'); + + if (existsSync(projectJsonPath)) { + return true; + } + + const packageJsonPath = join(appDir, 'package.json'); + + if (!existsSync(packageJsonPath)) { + return false; + } + + try { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { + nx?: unknown; + }; + + return packageJson.nx !== undefined; + } catch { + return false; + } +} + +function listAppProjects(cwd: string, workspaceType: WorkspaceType): string[] { + const appsDirName = + workspaceType === 'nx' ? readNxAppsDir(cwd) : 'apps'; + const appsDir = join(cwd, appsDirName); + + if (!existsSync(appsDir)) { + return []; + } + + const entries = readdirSync(appsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); + + if (workspaceType === 'nx') { + return entries + .filter((name) => isNxAppProject(join(appsDir, name))) + .sort(); + } + + return entries.sort(); +} + +function readReactVersion(cwd: string): string | undefined { + const packageJsonPath = join(cwd, 'package.json'); + + if (!existsSync(packageJsonPath)) { + return undefined; + } + + try { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { + dependencies?: Record; + devDependencies?: Record; + }; + + return ( + packageJson.dependencies?.['react'] ?? + packageJson.devDependencies?.['react'] + ); + } catch { + return undefined; + } +} + +export function detectWorkspace(cwd: string): WorkspaceInfo { + const type = detectWorkspaceType(cwd); + const appProjects = listAppProjects(cwd, type); + const reactVersion = readReactVersion(cwd); + + return { + type, + appProjects, + reactVersion, + }; +} diff --git a/libs/core/src/lib/workspace/patch-nx.ts b/libs/core/src/lib/workspace/patch-nx.ts new file mode 100644 index 0000000..110d594 --- /dev/null +++ b/libs/core/src/lib/workspace/patch-nx.ts @@ -0,0 +1,64 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { FWIZ_CONFIG_FILENAME } from '../config/init.js'; +import type { FwizConfig } from '../config/types.js'; +import type { WorkspacePatchResult } from '../config/types.js'; + +function buildFwizSection(config: FwizConfig) { + return { + config: FWIZ_CONFIG_FILENAME, + hosts: config.hosts.map((host) => ({ + name: host.name, + ...(host.project ? { project: host.project } : {}), + port: host.port, + })), + remotes: config.remotes.map((remote) => ({ + name: remote.name, + ...(remote.project ? { project: remote.project } : {}), + port: remote.port, + })), + }; +} + +export function patchNxJson( + cwd: string, + config: FwizConfig, +): WorkspacePatchResult { + const filePath = join(cwd, 'nx.json'); + + if (!existsSync(filePath)) { + return { patched: false, filePath, changes: [] }; + } + + let nxJson: Record; + + try { + nxJson = JSON.parse(readFileSync(filePath, 'utf8')) as Record< + string, + unknown + >; + } catch { + return { + patched: false, + filePath, + changes: [`Skipped ${filePath}: invalid JSON`], + }; + } + + const fwizSection = buildFwizSection(config); + const existing = nxJson.fwiz; + + if (JSON.stringify(existing) === JSON.stringify(fwizSection)) { + return { patched: false, filePath, changes: [] }; + } + + nxJson.fwiz = fwizSection; + writeFileSync(filePath, `${JSON.stringify(nxJson, null, 2)}\n`, 'utf8'); + + return { + patched: true, + filePath, + changes: ['Updated nx.json fwiz project references'], + }; +} diff --git a/libs/core/src/lib/workspace/patch-turbo.ts b/libs/core/src/lib/workspace/patch-turbo.ts new file mode 100644 index 0000000..930f423 --- /dev/null +++ b/libs/core/src/lib/workspace/patch-turbo.ts @@ -0,0 +1,64 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { FWIZ_CONFIG_FILENAME } from '../config/init.js'; +import type { FwizConfig } from '../config/types.js'; +import type { WorkspacePatchResult } from '../config/types.js'; + +function buildFwizSection(config: FwizConfig) { + return { + config: FWIZ_CONFIG_FILENAME, + hosts: config.hosts.map((host) => ({ + name: host.name, + ...(host.project ? { project: host.project } : {}), + port: host.port, + })), + remotes: config.remotes.map((remote) => ({ + name: remote.name, + ...(remote.project ? { project: remote.project } : {}), + port: remote.port, + })), + }; +} + +export function patchTurboJson( + cwd: string, + config: FwizConfig, +): WorkspacePatchResult { + const filePath = join(cwd, 'turbo.json'); + + if (!existsSync(filePath)) { + return { patched: false, filePath, changes: [] }; + } + + let turboJson: Record; + + try { + turboJson = JSON.parse(readFileSync(filePath, 'utf8')) as Record< + string, + unknown + >; + } catch { + return { + patched: false, + filePath, + changes: [`Skipped ${filePath}: invalid JSON`], + }; + } + + const fwizSection = buildFwizSection(config); + const existing = turboJson.fwiz; + + if (JSON.stringify(existing) === JSON.stringify(fwizSection)) { + return { patched: false, filePath, changes: [] }; + } + + turboJson.fwiz = fwizSection; + writeFileSync(filePath, `${JSON.stringify(turboJson, null, 2)}\n`, 'utf8'); + + return { + patched: true, + filePath, + changes: ['Updated turbo.json fwiz project references'], + }; +} diff --git a/libs/core/src/lib/workspace/patch.spec.ts b/libs/core/src/lib/workspace/patch.spec.ts new file mode 100644 index 0000000..78c1313 --- /dev/null +++ b/libs/core/src/lib/workspace/patch.spec.ts @@ -0,0 +1,91 @@ +import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { createDefaultConfig } from '../config/defaults.js'; +import { patchNxJson } from './patch-nx.js'; +import { patchTurboJson } from './patch-turbo.js'; +import { patchWorkspaceConfig } from './patch.js'; + +describe('patchNxJson', () => { + it('adds fwiz project references to nx.json', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-patch-nx-')); + writeFileSync(join(dir, 'nx.json'), '{}\n', 'utf8'); + + const config = createDefaultConfig({ + type: 'nx', + appProjects: ['shell', 'checkout'], + }); + + const result = patchNxJson(dir, config); + + expect(result.patched).toBe(true); + expect(JSON.parse(readFileSync(join(dir, 'nx.json'), 'utf8'))).toEqual({ + fwiz: { + config: 'fwiz.config.yaml', + hosts: [{ name: 'shell', project: 'shell', port: 4200 }], + remotes: [ + { name: 'checkout', project: 'checkout', port: 4201 }, + ], + }, + }); + }); + + it('is idempotent when nx.json is already patched', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-patch-nx-idempotent-')); + const config = createDefaultConfig({ + type: 'nx', + appProjects: ['shell', 'checkout'], + }); + + patchNxJson(dir, config); + const result = patchNxJson(dir, config); + + expect(result.patched).toBe(false); + }); +}); + +describe('patchTurboJson', () => { + it('adds fwiz project references to turbo.json', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-patch-turbo-')); + writeFileSync(join(dir, 'turbo.json'), '{"tasks":{}}\n', 'utf8'); + + const config = createDefaultConfig({ + type: 'turbo', + appProjects: ['shell', 'checkout'], + }); + + const result = patchTurboJson(dir, config); + + expect(result.patched).toBe(true); + expect(JSON.parse(readFileSync(join(dir, 'turbo.json'), 'utf8'))).toEqual({ + tasks: {}, + fwiz: { + config: 'fwiz.config.yaml', + hosts: [{ name: 'shell', project: 'shell', port: 4200 }], + remotes: [ + { name: 'checkout', project: 'checkout', port: 4201 }, + ], + }, + }); + }); +}); + +describe('patchWorkspaceConfig', () => { + it('patches nx.json and turbo.json for Nx workspaces', () => { + const dir = mkdtempSync(join(tmpdir(), 'fwiz-patch-workspace-')); + writeFileSync(join(dir, 'nx.json'), '{}\n', 'utf8'); + writeFileSync(join(dir, 'turbo.json'), '{}'); + + const config = createDefaultConfig({ + type: 'nx', + appProjects: ['shell', 'checkout'], + }); + + const results = patchWorkspaceConfig(dir, config); + + expect(results.filter((result) => result.patched)).toHaveLength(2); + }); +}); diff --git a/libs/core/src/lib/workspace/patch.ts b/libs/core/src/lib/workspace/patch.ts new file mode 100644 index 0000000..dcd7120 --- /dev/null +++ b/libs/core/src/lib/workspace/patch.ts @@ -0,0 +1,21 @@ +import type { FwizConfig } from '../config/types.js'; +import { patchNxJson } from './patch-nx.js'; +import { patchTurboJson } from './patch-turbo.js'; +import type { WorkspacePatchResult } from '../config/types.js'; + +export function patchWorkspaceConfig( + cwd: string, + config: FwizConfig, +): WorkspacePatchResult[] { + const patches: WorkspacePatchResult[] = []; + + if (config.workspace.type === 'nx') { + patches.push(patchNxJson(cwd, config)); + } + + if (config.workspace.type === 'turbo' || config.workspace.type === 'nx') { + patches.push(patchTurboJson(cwd, config)); + } + + return patches; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64e8b5e..c0d489b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,13 +41,13 @@ importers: version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3))(typescript@5.9.3) '@nx/react': specifier: ^22.3.3 - version: 22.3.3(@babel/core@7.28.5)(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/helpers@0.5.18)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.12)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16)(webpack@5.104.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(esbuild@0.19.12)) + version: 22.3.3(@babel/core@7.28.5)(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/helpers@0.5.18)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.12)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16)(webpack@5.104.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(esbuild@0.19.12)) '@nx/vite': specifier: ^22.3.3 - version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16) + version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16) '@nx/vitest': specifier: 22.3.3 - version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16) + version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16) '@nx/web': specifier: 22.3.3 version: 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) @@ -86,7 +86,7 @@ importers: version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react': specifier: ^4.2.0 - version: 4.7.0(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2)) + version: 4.7.0(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0)) '@vitest/coverage-v8': specifier: ^4.0.0 version: 4.0.16(vitest@4.0.16) @@ -155,20 +155,33 @@ importers: version: 8.52.0(eslint@9.39.2(jiti@2.4.2))(typescript@5.9.3) vite: specifier: ^7.0.0 - version: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) + version: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) vitest: specifier: ^4.0.0 - version: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2) + version: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0) - apps/cli: {} + apps/cli: + dependencies: + '@federation-wizards/core': + specifier: workspace:* + version: link:../../libs/core + commander: + specifier: ^15.0.0 + version: 15.0.0 apps/dashboard: {} libs/core: dependencies: + joi: + specifier: ^18.2.1 + version: 18.2.1 tslib: specifier: ^2.3.0 version: 2.8.1 + yaml: + specifier: ^2.9.0 + version: 2.9.0 libs/mf-plugins: dependencies: @@ -1197,6 +1210,26 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.7': + resolution: {integrity: sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1553,42 +1586,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -1714,21 +1754,25 @@ packages: resolution: {integrity: sha512-NmPeCexWIZHW9RM3lDdFENN9C3WtlQ5L4RSNFESIjreS921rgePhulsszYdGnHdcnKPYlBBJnX/NxVsfioBbnQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@nx/nx-linux-arm64-musl@22.3.3': resolution: {integrity: sha512-K02U88Q0dpvCfmSXXvY7KbYQSa1m+mkYeqDBRHp11yHk1GoIqaHp8oEWda7FV4gsriNExPSS5tX1/QGVoLZrCw==} cpu: [arm64] os: [linux] + libc: [musl] '@nx/nx-linux-x64-gnu@22.3.3': resolution: {integrity: sha512-04TEbvgwRaB9ifr39YwJmWh3RuXb4Ry4m84SOJyjNXAfPrepcWgfIQn1VL2ul1Ybq+P023dLO9ME8uqFh6j1YQ==} cpu: [x64] os: [linux] + libc: [glibc] '@nx/nx-linux-x64-musl@22.3.3': resolution: {integrity: sha512-uxBXx5q+S5OGatbYDxnamsKXRKlYn+Eq1nrCAHaf8rIfRoHlDiRV2PqtWuF+O2pxR5FWKpvr+/sZtt9rAf7KMw==} cpu: [x64] os: [linux] + libc: [musl] '@nx/nx-win32-arm64-msvc@22.3.3': resolution: {integrity: sha512-aOwlfD6ZA1K6hjZtbhBSp7s1yi3sHbMpLCa4stXzfhCCpKUv46HU/EdiWdE1N8AsyNFemPZFq81k1VTowcACdg==} @@ -1897,66 +1941,79 @@ packages: resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.1': resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.1': resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.55.1': resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} @@ -2002,21 +2059,25 @@ packages: resolution: {integrity: sha512-BX9yAPCO0WBFyOzKl9bSXT/cH27nnOJp02smIQMxfv7RNfwGkJg5GgakYcuYG+9U1HEFitBSzmwS2+dxDcAxlg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rspack/binding-linux-arm64-musl@1.7.1': resolution: {integrity: sha512-maBX19XyiVkxzh/NA79ALetCobc4zUyoWkWLeCGyW5xKzhPVFatJp+qCiHqHkqUZcgRo+1i5ihoZ2bXmelIeZg==} cpu: [arm64] os: [linux] + libc: [musl] '@rspack/binding-linux-x64-gnu@1.7.1': resolution: {integrity: sha512-8KJAeBLiWcN7zEc9aaS7LRJPZVtZuQU8mCsn+fRhdQDSc+a9FcTN8b6Lw29z8cejwbU6Gxr/8wk5XGexMWFaZA==} cpu: [x64] os: [linux] + libc: [glibc] '@rspack/binding-linux-x64-musl@1.7.1': resolution: {integrity: sha512-Gn9x5vhKRELvSoZ3ZjquY8eWtCXur0OsYnZ2/ump8mofM6IDaL7Qqu3Hf4Kud31PDH0tfz0jWf9piX32HHPmgg==} cpu: [x64] os: [linux] + libc: [musl] '@rspack/binding-wasm32-wasi@1.7.1': resolution: {integrity: sha512-2r9M5iVchmsFkp3sz7A5YnMm2TfpkB71LK3AoaRWKMfvf5oFky0GSGISYd2TCBASO+X2Qskaq+B24Szo8zH5FA==} @@ -2199,24 +2260,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.5.29': resolution: {integrity: sha512-TERh2OICAJz+SdDIK9+0GyTUwF6r4xDlFmpoiHKHrrD/Hh3u+6Zue0d7jQ/he/i80GDn4tJQkHlZys+RZL5UZg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.5.29': resolution: {integrity: sha512-WMDPqU7Ji9dJpA+Llek2p9t7pcy7Bob8ggPUvgsIlv3R/eesF9DIzSbrgl6j3EAEPB9LFdSafsgf6kT/qnvqFg==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.5.29': resolution: {integrity: sha512-DO14glwpdKY4POSN0201OnGg1+ziaSVr6/RFzuSLggshwXeeyVORiHv3baj7NENhJhWhUy3NZlDsXLnRFkmhHQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.5.29': resolution: {integrity: sha512-V3Y1+a1zG1zpYXUMqPIHEMEOd+rHoVnIpO/KTyFwAmKVu8v+/xPEVx/AGoYE67x4vDAAvPQrKI3Aokilqa5yVg==} @@ -2500,41 +2565,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -3165,6 +3238,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4586,6 +4663,10 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + joi@18.2.1: + resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==} + engines: {node: '>= 20'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6444,8 +6525,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true @@ -7525,6 +7606,22 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.7': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -8449,7 +8546,7 @@ snapshots: '@nx/nx-win32-x64-msvc@22.3.3': optional: true - '@nx/react@22.3.3(@babel/core@7.28.5)(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/helpers@0.5.18)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.12)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16)(webpack@5.104.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(esbuild@0.19.12))': + '@nx/react@22.3.3(@babel/core@7.28.5)(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/helpers@0.5.18)(@types/babel__core@7.20.5)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.12)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(@types/node@20.19.9)(typescript@5.9.3))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16)(webpack@5.104.1(@swc/core@1.5.29(@swc/helpers@0.5.18))(esbuild@0.19.12))': dependencies: '@nx/devkit': 22.3.3(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) '@nx/eslint': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(@zkochan/js-yaml@0.0.7)(eslint@9.39.2(jiti@2.4.2))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) @@ -8467,7 +8564,7 @@ snapshots: semver: 7.7.3 tslib: 2.8.1 optionalDependencies: - '@nx/vite': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16) + '@nx/vite': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16) transitivePeerDependencies: - '@babel/core' - '@babel/traverse' @@ -8527,11 +8624,11 @@ snapshots: - typescript - verdaccio - '@nx/vite@22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16)': + '@nx/vite@22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16)': dependencies: '@nx/devkit': 22.3.3(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) '@nx/js': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) - '@nx/vitest': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16) + '@nx/vitest': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.3) ajv: 8.17.1 enquirer: 2.3.6 @@ -8539,8 +8636,8 @@ snapshots: semver: 7.7.3 tsconfig-paths: 4.2.0 tslib: 2.8.1 - vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) - vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) + vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -8551,7 +8648,7 @@ snapshots: - typescript - verdaccio - '@nx/vitest@22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))(vitest@4.0.16)': + '@nx/vitest@22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18)))(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))(vitest@4.0.16)': dependencies: '@nx/devkit': 22.3.3(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) '@nx/js': 22.3.3(@babel/traverse@7.28.5)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))(nx@22.3.3(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.5.29(@swc/helpers@0.5.18))) @@ -8559,8 +8656,8 @@ snapshots: semver: 7.7.3 tslib: 2.8.1 optionalDependencies: - vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) - vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) + vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -9330,7 +9427,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))': + '@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -9338,7 +9435,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -9355,7 +9452,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2) + vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -9368,13 +9465,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2))': + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) '@vitest/pretty-format@4.0.16': dependencies: @@ -9402,7 +9499,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2) + vitest: 4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0) '@vitest/utils@4.0.16': dependencies: @@ -10099,6 +10196,8 @@ snapshots: commander@11.1.0: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@6.2.1: {} @@ -11953,6 +12052,16 @@ snapshots: jiti@2.4.2: {} + joi@18.2.1: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.7 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.1.0 + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -12346,7 +12455,7 @@ snapshots: tree-kill: 1.2.2 tsconfig-paths: 4.2.0 tslib: 2.8.1 - yaml: 2.8.2 + yaml: 2.9.0 yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: @@ -13732,7 +13841,7 @@ snapshots: vary@1.1.2: {} - vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2): + vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -13745,12 +13854,12 @@ snapshots: fsevents: 2.3.3 jiti: 2.4.2 terser: 5.44.1 - yaml: 2.8.2 + yaml: 2.9.0 - vitest@4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.8.2): + vitest@4.0.16(@types/node@20.19.9)(@vitest/ui@4.0.16)(jiti@2.4.2)(jsdom@22.1.0)(terser@5.44.1)(yaml@2.9.0): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2)) + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -13767,7 +13876,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@20.19.9)(jiti@2.4.2)(terser@5.44.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.9 @@ -13946,7 +14055,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.8.2: {} + yaml@2.9.0: {} yargs-parser@21.1.1: {}