diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f6b65e2..f445744 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,16 +5,51 @@ updates: schedule: interval: weekly open-pull-requests-limit: 5 + groups: + react-runtime: + patterns: + - react + - react-dom + - react-server-dom-webpack + - "@types/react" + - "@types/react-dom" + update-types: + - minor + - patch + ignore: + - dependency-name: "@types/node" + update-types: + - version-update:semver-major + - dependency-name: concurrently + update-types: + - version-update:semver-major - package-ecosystem: npm directory: /docs schedule: interval: weekly open-pull-requests-limit: 3 + groups: + react-documentation: + patterns: + - react + - react-dom + - "@types/react" + - "@types/react-dom" + update-types: + - minor + - patch - package-ecosystem: nuget directory: /dotnet schedule: interval: weekly open-pull-requests-limit: 5 + groups: + opentelemetry: + patterns: + - OpenTelemetry.* + update-types: + - minor + - patch - package-ecosystem: nuget directory: /examples/crud/Server schedule: @@ -25,8 +60,16 @@ updates: schedule: interval: weekly open-pull-requests-limit: 3 + groups: + official-actions: + patterns: + - actions/* - package-ecosystem: docker directory: / schedule: interval: weekly open-pull-requests-limit: 3 + ignore: + - dependency-name: node + update-types: + - version-update:semver-major diff --git a/README.md b/README.md index c9a5116..95522d0 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ DotReact is an experimental full-stack framework that keeps ASP.NET Core in charge of HTTP, authentication, services, and data while React renders the UI on the server and hydrates it in the browser. -This repository contains a reusable pre-release framework, CLI scaffold and three +This repository contains a reusable experimental framework, CLI scaffold and three reference applications. The runtime boundary and production lifecycle are -implemented; coordinated private npm and NuGet packages are available at version `0.1.3`. +implemented; coordinated private npm and NuGet packages are available at version `0.1.4`. Public surfaces follow the repository's [compatibility and deprecation policy](COMPATIBILITY.md). The source is licensed under [MPL 2.0](LICENSE). Applications may use DotReact, @@ -86,22 +86,31 @@ the counter proves that React hydrated the same tree in the browser. ## Create a project -Using the published private CLI package: +Authenticate the private feed once, then install the CLI launcher once: ```powershell -npm exec --yes --package=@rafagouveia/dotreact-cli@0.1.3 -- dotreact create my-app --template starter +npm install --global @rafagouveia/dotreact-cli@0.1.4 --registry=https://npm.pkg.github.com +dotreact create my-app --template starter cd my-app npm install -npm run dev +dotreact doctor +dotreact dev ``` Generated projects pin the CLI, browser runtime, renderer, Vite integration, NuGet hosting libraries and local TypeGen tool to one exact DotReact version. +The global command automatically delegates to that project-local CLI, so a +machine can work on projects using different DotReact versions without drift. Framework source is not copied into product repositories. Use `dotreact upgrade ` to align every runtime surface before reinstalling and running the release gates. -Private prerelease packages are hosted under the `rafagouveia` GitHub Packages +CI should continue using package scripts or `npm exec -- dotreact`: it must not +depend on a mutable global installation. If the shell cannot find the launcher, +verify `Get-Command dotreact` after reopening it; the project-local fallback is +`npm exec -- dotreact doctor`. + +Private 0.x packages are hosted under the `rafagouveia` GitHub Packages namespace. Publishing remains manual and opt-in; normal CI never republishes a package. See [Private GitHub Packages](docs/private-packages.md) for package coordinates, token permissions, local archive validation and cost @@ -126,13 +135,13 @@ resulting lockfile before the first push. Inside a generated project, the complete lifecycle is: ```powershell -npm exec -- dotreact doctor -npm exec -- dotreact dev -npm exec -- dotreact generate page catalog/[slug] -npm exec -- dotreact generate action SaveCatalog -npm exec -- dotreact sync -npm exec -- dotreact check -npm exec -- dotreact build +dotreact doctor +dotreact dev +dotreact generate page catalog/[slug] +dotreact generate action SaveCatalog +dotreact sync +dotreact check +dotreact build dotreact workspace status dotreact workspace reveal dotreact workspace focus diff --git a/docs/adr/005-drx-pages-and-server-actions.md b/docs/adr/005-drx-pages-and-server-actions.md index e45fc29..8e7d5ad 100644 --- a/docs/adr/005-drx-pages-and-server-actions.md +++ b/docs/adr/005-drx-pages-and-server-actions.md @@ -2,7 +2,9 @@ ## Status -Accepted for the first opt-in vertical slice. +Superseded for new applications by ADR 014. This document preserves the first +opt-in slice and its security decisions; current scaffolds use the unified +`app/` router. ## Decision @@ -23,7 +25,7 @@ and emits three deterministic artifacts: TypeScript contracts, typed action clients and a backend manifest. The existing explicit TypeGen and `MapDotReactPage` APIs remain supported. -The Vite plugin scans only `src/pages/**/*.drx`, resolves canonical paths, +The original Vite plugin scanned only `src/pages/**/*.drx`, resolved canonical paths, rejects a pages root outside the application, ignores escaping symlinks and builds `virtual:dotreact/pages` from static imports. Request data is never used to construct a filesystem path or import specifier. diff --git a/docs/adr/006-drx-layouts-and-semantic-checking.md b/docs/adr/006-drx-layouts-and-semantic-checking.md index 5529e20..6fd5ec1 100644 --- a/docs/adr/006-drx-layouts-and-semantic-checking.md +++ b/docs/adr/006-drx-layouts-and-semantic-checking.md @@ -2,7 +2,8 @@ ## Status -Accepted for the second opt-in DRX slice. +Accepted. ADR 014 moved the canonical pages root from `src/pages` to `app/`; +the composition and semantic-checking guarantees in this record remain current. ## Decision diff --git a/docs/adr/006-unified-app-router.md b/docs/adr/014-unified-app-router.md similarity index 92% rename from docs/adr/006-unified-app-router.md rename to docs/adr/014-unified-app-router.md index da4bca7..4e86715 100644 --- a/docs/adr/006-unified-app-router.md +++ b/docs/adr/014-unified-app-router.md @@ -1,9 +1,10 @@ -# ADR 006: Unified app router +# ADR 014: Unified app router ## Status -Accepted for the 0.2 development line. The legacy `src/pages` plus attributed -loader model remains supported for compatibility during the deprecation window. +Accepted and shipped in the 0.1.x scaffold. The legacy `src/pages` plus +attributed loader model remains a compatibility surface, but it is not generated +for new applications. ## Context diff --git a/docs/compatibility.md b/docs/compatibility.md index 029a6b1..9a4fcd7 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -4,11 +4,14 @@ |---|---|---|---|---|---| | 0.1.x experimental | 10.x | 24.x recommended; 22.19+ supported | 19.2.x | 8.1.x | 7.0.x | -Protocol version 1 is currently required on both sides. Generated projects use -private GitHub npm and NuGet packages pinned to the same exact DotReact version; +Buffered SSR uses protocol v1 and streaming SSR negotiates protocol v2. The C# +host and Node renderer must come from the same coordinated DotReact release; +unknown protocol versions fail closed instead of falling back silently. +Generated projects use private GitHub npm and NuGet packages pinned to the same exact DotReact version; the local TypeGen tool is pinned to that version as well. `dotreact upgrade` aligns those three surfaces without rewriting application source. Every upgrade -must regenerate contracts and pass `dotreact check` before deployment. +must regenerate contracts and pass `dotreact check` before deployment. A +one-time global launcher delegates to the exact CLI installed by each project. Supported development hosts are Windows and Linux. The production container is Linux-based and targets Azure App Service custom containers and Azure Container diff --git a/docs/private-packages.md b/docs/private-packages.md index 930f5f6..9ccd130 100644 --- a/docs/private-packages.md +++ b/docs/private-packages.md @@ -1,6 +1,6 @@ # Private GitHub Packages -DotReact prerelease packages are published in the `rafagouveia` GitHub Packages +DotReact private 0.x packages are published in the `rafagouveia` GitHub Packages namespace. Publishing new immutable versions is manual and disabled by default. ## Package coordinates @@ -67,15 +67,27 @@ For another private repository, grant that repository access on each package's GitHub **Manage Actions access** page; `packages: read` alone cannot bypass a package that has not shared access with the consuming repository. -Create a project without cloning the framework repository: +Install the launcher once and create a project without cloning the framework +repository: ```powershell -npm exec --yes --package=@rafagouveia/dotreact-cli@0.1.3 -- dotreact create my-app --template starter +npm install --global @rafagouveia/dotreact-cli@0.1.4 --registry=https://npm.pkg.github.com +dotreact version +dotreact create my-app --template starter cd my-app npm install -npm exec -- dotreact doctor +dotreact doctor ``` +The global executable is only a launcher. Inside a project, it locates the +nearest `package.json` that explicitly declares `@rafagouveia/dotreact-cli` and +executes that installed local version. If dependencies are missing it fails +closed and asks for `npm install`; it never silently runs a different global +version. `npm exec -- dotreact doctor` remains the no-global fallback and is the +recommended form for CI. With nvm/fnm, global packages belong to the active Node +installation, so reinstall the launcher after switching Node versions if +`Get-Command dotreact` no longer resolves it. + ## Cost controls GitHub Free currently includes a private Packages allowance. Configure a GitHub diff --git a/docs/scripts/generate-english-catalog.mjs b/docs/scripts/generate-english-catalog.mjs index 55739b6..9f78d57 100644 --- a/docs/scripts/generate-english-catalog.mjs +++ b/docs/scripts/generate-english-catalog.mjs @@ -103,7 +103,14 @@ async function generate(expected, missing, stale, current) { } function normalizeTechnicalTranslation(source, translation) { + if (/^(?:dotreact(?:\s+.*)?|virtual:dotreact\/[A-Za-z0-9_./-]+|@rafagouveia\/dotreact-[A-Za-z0-9_-]+)$/u.test(source)) + return source; let result = translation; + result = result + .replaceAll("dotreat", "dotreact") + .replaceAll("doreact", "dotreact") + .replaceAll("Dotreat", "DotReact") + .replaceAll("Doreact", "DotReact"); if (/\bbanco|bancos\b/iu.test(source)) result = result.replace(/\bbank(?:ing)?\b/giu, "database").replace(/\bbanks\b/giu, "databases"); if (/\bhidrat/iu.test(source)) @@ -117,6 +124,9 @@ function normalizeTechnicalTranslation(source, translation) { function hasTerminologyError(source, translation) { if (typeof translation !== "string") return false; + if (/\b(?:doreact|dotreat)\b/iu.test(translation)) return true; + const protectedTokens = source.match(/(?:virtual:dotreact\/[A-Za-z0-9_./-]+|@rafagouveia\/dotreact-[A-Za-z0-9_-]+|\bdotreact(?:\s+[a-z][a-z-]*)?)/gu) ?? []; + if (protectedTokens.some(token => !translation.includes(token))) return true; if (/\bbanco|bancos\b/iu.test(source) && /\bbank(?:ing|s)?\b/iu.test(translation)) return true; if (/\bhidrat/iu.test(source) && /\bmoistur/iu.test(translation)) return true; if (/\bporta\b/iu.test(source) && /\bdoor\b/iu.test(translation)) return true; @@ -153,6 +163,7 @@ function shouldKeep(value) { if (/^\d+(?:\.\d+)?$/u.test(value)) return true; if (/^[A-Z0-9_.+/@:[\]()-]{2,}$/u.test(value)) return true; if (/^(?:C#|React|TypeScript|PowerShell|Terminal|Program\.cs|page\.drx|package\.json)$/u.test(value)) return true; + if (/^(?:dotreact(?:\s+.*)?|virtual:dotreact\/|@rafagouveia\/dotreact-)/u.test(value)) return true; if (/^(?:app|src|Server|Domain|Application|Infrastructure|Presentation|public|vendor)\//u.test(value)) return true; return false; } diff --git a/docs/src/components.tsx b/docs/src/components.tsx index 640c26c..570fbdb 100644 --- a/docs/src/components.tsx +++ b/docs/src/components.tsx @@ -115,7 +115,7 @@ export function CliLab() { - + ); } diff --git a/docs/src/content.en.catalog.ts b/docs/src/content.en.catalog.ts index b2f78ea..56d4796 100644 --- a/docs/src/content.en.catalog.ts +++ b/docs/src/content.en.catalog.ts @@ -79,6 +79,7 @@ export const englishText: Readonly> = { ". Ele executa antes da conclusão da request e pode redirecionar, reescrever ou alterar headers. No DotReact essa responsabilidade não fica no renderer: ela usa o pipeline ASP.NET Core, que é a entrada pública e possui DI, autenticação, autorização, rate limiting e observabilidade.": ". It executes before the request is complete and can redirect, rewrite or change headers. In DotReact, this responsibility does not rest with the renderer: it uses the ASP.NET Core pipeline, which is the public input and has DI, authentication, authorization, rate limiting and observability.", ". Ele tem duração absoluta e carrega apenas um ticket protegido. O React recebe somente o DTO público do usuário.": ". It has a fixed lifetime and carries only a protected ticket. React receives only the user's public DTO.", ". Em Azure, termine HTTPS no ingress e configure forwarded headers somente para proxies confiáveis.": ". In Azure, terminate HTTPS in the ingress and configure forwarded headers only for trusted proxies.", + ". Em CI, prefira os scripts do projeto ou": ". In CI, prefer project scripts or", ". Em desenvolvimento, ASP.NET faz proxy de": ". In development, ASP.NET proxy", ". Em múltiplas réplicas, persista e proteja o key ring do ASP.NET Core Data Protection para que autenticação e antiforgery sejam consistentes entre instâncias.": ". Across multiple replicas, persist and secure the ASP.NET Core Data Protection key ring so that authentication and antiforgery are consistent across instances.", ". Em produção, valide paths de": ". In production, validate paths", @@ -92,6 +93,8 @@ export const englishText: Readonly> = { ". O comando cria somente os arquivos públicos da feature e não altera o runtime.": ". The command only creates the feature's public files and does not change the runtime.", ". O entry React usa": ". entry React uses", ". O exemplo": ". The example", + ". O fallback sem instalação global é": ". Fallback without global installation is", + ". O global cria projetos; o local executa doctor, dev, build e generators sem drift.": ". The global creates projects; local runs doctor, dev, build and generators without drift.", ". O Hot Reload incrementa a revisão do backend e o router busca props SSR novas pela mesma origem.": ". Hot Reload increments the backend revision and the router searches for new SSR props from the same source.", ". O mesmo registry virtual é consumido no SSR e na hydration, evitando árvores diferentes.": ". The same virtual registry is consumed in SSR and hydration, avoiding different trees.", ". O nome antigo continua citado porque muitos projetos ainda possuem": ". The old name continues to be mentioned because many projects still have", @@ -126,6 +129,7 @@ export const englishText: Readonly> = { "(shop)/products/page": "(shop)/products/page", "[--routes] --url --out": "[--routes] --url --out", "[--static-only]": "[--static-only]", + "[--url]": "[--url]", "[AllowAnonymous]": "[AllowAnonymous]", "[Authorize(Policy = \"catalog.manage\")]": "[Authorize(Policy = \"catalog.manage\")]", "[Authorize(Policy = \"orders.write\")]": "[Authorize(Policy = \"orders.write\")]", @@ -147,7 +151,7 @@ export const englishText: Readonly> = { "@rafagouveia/dotreact-rsc": "@rafagouveia/dotreact-rsc", "@rafagouveia/dotreact-rsc experimental": "@rafagouveia/dotreact-rsc experimental", "@rafagouveia/dotreact-script": "@rafagouveia/dotreact-script", - "@rafagouveia/dotreact-vite": "@rafazouveia/dotreact-vite", + "@rafagouveia/dotreact-vite": "@rafagouveia/dotreact-vite", "*": "*", "*.server.ts": "*.server.ts", "/": "/", @@ -170,6 +174,7 @@ export const englishText: Readonly> = { " --kind error|not-found|loading|global-error": " --kind error|not-found|loading|global-error", " [--dry-run]": " [--dry-run]", "