Skip to content

Commit 30c0788

Browse files
authored
Fix the flaws found in a full-project audit (#7)
* Overhaul generation, samples, and SSR configuration * Generate sample types before JavaScript checks * Async SSR contract, SSR authorization, and package licensing ISvelteSsrEngine.Render becomes RenderAsync, and ISvelteSsrFetchHandler gains HandleAsync. A server render can await remote queries, which hit databases and HTTP services; the old synchronous contract forced sync-over-async at every layer and blocked a request thread for the whole await chain. - CliSsrEngine (Node/Bun) is now genuinely async, and probes its CLI on first render instead of in the constructor. - Jint cannot suspend a running script, so its fetch bridge must block. RenderAsync moves that onto a worker gated by MaxConcurrentRenders, so the request thread is released and blocked threads stay bounded. - Pooled Jint engines outlive a request, so the fetch shim reads a thread-static token for the render in flight rather than closing over the token the engine was created with. - SveltePage prerenders in the page filter, keeping @Model.Svelte() in .cshtml working; SvelteAsync()/SvelteHeadAsync() render on demand. MVC views use @await Html.SvelteAsync(Model). The Jint SSR bridge dispatches descriptors directly and never passes through routing, so RequireAuthorization() on the endpoints did not apply to it: an anonymous page view would run a protected query and bake the result into the HTML. Authorization is now descriptor-level data that both transports read - MapSvelteRemote projects it onto per-method endpoints, and the bridge evaluates the same metadata via IAuthorizationService. Also: MIT LICENSE and package license metadata (the packages shipped with none), Source Link and symbol packages, all three packages packed in CI, ILogger instead of Console for SSR output, ordinal manifest lookup, and JsonDocuments are disposed. Tests: 132 -> 147, covering concurrent SSR renders that await the bridge, pooled-engine cancellation, and the authorization matrix. * Emit [Authorize] from the generator and demonstrate it in a sample Completes descriptor-level authorization: SvelteRemoteGenerator now lifts [Authorize]/[AllowAnonymous] off the service class and its methods into the generated descriptor, matching what the reflection fallback already read. Class-level requirements combine with method-level ones, and [AllowAnonymous] on a method opts it out of the service policy. The RemoteFunctions sample gains an AdminApi guarded by an 'admin' policy with one [AllowAnonymous] method, plus ordinary cookie authentication so the demo is runnable and copy-safe. Tests: generator emission, and the full HTTP matrix (anonymous, wrong role, right role, allow-anonymous opt-out) against the sample. * Serialize long/ulong/decimal as strings so 64-bit values survive JavaScript numbers are IEEE-754 doubles. A long above 2^53 or a decimal fraction emitted as a JSON number is silently rounded in the browser - 9007199254740993 arrives as 9007199254740992, and money loses precision - with no error on either side. The generated TypeScript said 'number', so the compiler approved the corruption. LargeNumberConverter writes these three as JSON strings and reads either strings or numbers, so payloads from other clients still bind. All three TypeScript emitters (metadata generator, remote generator, reflection TypeGen) now say 'string' to match, including dictionary keys. Also fixes enum-keyed dictionaries, which serialize fine but made type generation throw. They now emit Partial<Record<Enum, T>> - an index signature cannot be a union type. The RemoteFunctions sample gains an AccountSummary(long, decimal, int) showing all three cases, asserted end to end on the wire. * Resolve generated TypeScript names so they cannot collide or shadow the DOM Generated interfaces are ambient globals with unqualified short names. Two CLR types called Item produced two 'interface Item' declarations in one block (reflection) or a hard build failure with no escape hatch (generated), and a model called File or Event silently replaced the DOM's across the entire project. Naming is now two-stage. Emitters produce a namespace-qualified placeholder, because no generator can see the whole application and so cannot know whether a short name is free. The scaffolder runs last, across every application assembly, resolves each placeholder to the shortest unambiguous name that does not shadow a built-in global, and substitutes throughout the generated output. [SvelteType("Name")] overrides the choice and is reserved first. Also fixes a bug this made visible: [SvelteComponent] on a SveltePage means 'override the component path', but it was also registering the page as a view model, so type collection descended through PageModel and emitted HttpContext, ModelState, Endpoint and the rest of ASP.NET as ambient globals. The RemoteFunctions sample's models.d.ts drops from 1129 lines to 43 - only its own models. Framework types are now excluded outright rather than named, since they are never declared. Tests: naming policy in isolation, plus collision, DOM-shadowing, [SvelteType], and no-placeholder-leakage through the real scaffolder. * Fix enum form binding, scalar JSON escaping, and async return unwrapping Enum binding went through Enum.Parse, which accepts any numeric string: a form field of "999" bound as (Priority)999 and reached the handler as a value that does not exist. It also used .NET member names while JSON binding used the camelCase names from JsonStringEnumConverter, so the same parameter took different spellings depending on transport. Binding now goes through the serializer first (with the CLR name still accepted for hand-written forms) and rejects anything Enum.IsDefined does not know. Scalar form values were wrapped in quotes by string concatenation, so any field containing a quote, backslash or newline produced malformed JSON and failed to bind for no visible reason. JsonSerializer.Serialize escapes it. Reflection dispatch decided whether a handler returned a value by testing the runtime type for the internal name "VoidTaskResult". It now uses the method's declared return type, which is unambiguous - an async Task method genuinely returns a Task<VoidTaskResult> at runtime, so no inspection of the instance can tell the two apart without that BCL detail. Tests cover every declared return shape through the reflection dispatcher, plus enum spelling parity, undefined-enum rejection, and scalars containing quotes and backslashes. * Send the antiforgery token from enhance(), and close the CORS CSRF hole SveltePage minted an antiforgery token into every data prop, the scaffolder typed it, and the docs described it - but enhance() never sent it. Every user had to hand-add a hidden __RequestVerificationToken input to every form, which is exactly the boilerplate the helper exists to remove; the TodoApp sample did it twice. enhance() now sends the token as the RequestVerificationToken header. AddSvelteNet configures AntiforgeryOptions.HeaderName when the app has not chosen one, because ASP.NET sets none by default and would otherwise reject the post. Forms that already render a hidden input keep working untouched - the value is picked up automatically. The token may be a getter, so a value taken from the data prop cannot go stale after an enhanced response replaces it. enhance() also ignores a re-entrant submit while one is in flight; a double-click used to post the same mutation twice. For remote functions, the X-SvelteNet header was the only CSRF defence, and it holds only while no CORS policy lets a foreign origin send that header - AllowAnyHeader() with AllowAnyOrigin() is common enough that this was a real hole. Commands now also reject any request carrying a foreign Origin. A request with no Origin is not from a browser and cannot be CSRF'd, so server-to-server callers are unaffected. New docs/security.md covers the authorization model, why it lives on the descriptor rather than the endpoint, the CSRF checks, Jint's shared module state, and loopback rendering. Added to the VitePress sidebar. * Stop the scaffolder deleting user files, and make type generation incremental The scaffolder unconditionally deleted Svelte/types.ts, Svelte/types.d.ts, Svelte/routes.d.ts and per-page *.types.ts on every run. Those paths are in the user's source directory; the deletes were leftover migration cleanup from an older layout, so a hand-written file with one of those names was destroyed silently on the next build. Only .svelte-net is cleared now. Stale remote clients were found by scanning every *.remote.ts (and every file literally named remote.ts) for the string "Generated by SvelteNet" and deleting the matches - so a hand-written file that happened to contain that line, such as one copied from generated output, was deleted too. The scaffolder now records the files it writes and removes only those, which also makes cleanup correct when a service is renamed or moved. SvelteNetGenerateTypes had no Inputs/Outputs, so every single build spawned a process, loaded the app assembly into an isolated ALC and rewrote the generated files - which retriggers file watchers and made dotnet watch loops needlessly expensive. It is now stamped against the built assembly: verified to skip when nothing changed and to rerun when a source file does. UseSvelteNet's startup scaffolding is now Development-only. It writes into the content root, and an app rewriting its own source tree at boot on a read-only or shared deployment is not something a stray configuration flag should be able to cause; it logs a warning instead. * Split SvelteNet.Jint into its own package and multi-target net8.0 SvelteNet.Core took a hard PackageReference on Jint, so every consumer shipped a ~3 MB JavaScript engine - including client-only apps and apps that had chosen the Node.js or Bun renderer. That contradicted the "SSR is opt-in" design. JintSsrEngine, SsrModuleLoader and JintSsrOptions now live in SvelteNet.Jint, which brings AddJintSSR with them; SvelteNetBuilder exposes AddInProcessSsrFetchBridge() so any in-process renderer can opt into direct query dispatch. Everything shipped now targets net8.0 and net10.0. net8.0 is LTS and most of the ASP.NET installed base; net10.0-only excluded them. The one .NET 9+ API in use (JsonStringEnumMemberNameAttribute) is behind a version check. SvelteNet.Build targets net8.0 with RollForward=LatestMajor so a single copy of the tool runs under whichever SDK builds the app. The analyzer and build tool were packed from hardcoded bin/$(Configuration)/<tfm> paths, which break under ArtifactsPath, a custom BaseOutputPath, or any build-order variation - and break by silently producing a package with no analyzer rather than failing. They are now resolved from the referenced projects' real outputs, and the pack errors out if either is missing. CI gains a windows-latest matrix leg (path separators, process handling and file-system case sensitivity all differ, and Windows is most of this library's audience), installs the net8.0 runtime so the shipped TFM is actually tested, and packs SvelteNet.Jint too. * API and DX cleanups across the .NET and JavaScript sides Registration: - AddSvelteNet no longer infers dev mode from DOTNET_RUNNING_IN_CONTAINER. Developing in a devcontainer silently disabled HMR with no way to tell why. The hosting environment decides, falling back to the standard environment variables when the environment is not on the collection yet. - The GetCallingAssembly default is documented as the guess it is, and an empty discovery scope now fails loudly instead of scanning nothing. Options: - ClientPublicPath is derived from ClientOutput minus WebRoot. They described the same directory from two sides as independent defaults, so changing one silently broke every asset URL. An explicit value still wins, for CDN hosting. JavaScript: - The Vite plugin no longer mutates the caller's options object when experimentalAsync is set. - @sveltejs/vite-plugin-svelte moves to peerDependencies; as a hard dependency it could install a second copy alongside the app's own. - The "hydratable unavailable" warning fires once per page instead of once per query per render, where it drowned out real console output. - form() takes an `updates` callback to narrow which queries refresh after a submit. The default refreshes everything, which is one request per query on a large page; commands already had explicit .updates(...), so the two now agree. - readPageResponse's `response.ok !== false` is just `response.ok`. * Sync docs, samples and roadmap with the remediation work - docs/mvc.md moves to the async helpers and explains why the synchronous overloads refuse when SSR is on. - docs/getting-started.md notes that SveltePage prerenders in the page filter, which is why the .cshtml accessors stay synchronous. - docs/options.md documents generated type naming, [SvelteType], the derived ClientPublicPath, and the extension-method caveat on discovery scope. - docs/remote-functions.md documents form()'s updates callback. - packages/sveltenet/README.md covers the antiforgery token and double-submit protection. - ROADMAP moves the shipped work across and records what the audit surfaced but did not fix: a persistent SSR worker for Node/Bun, opt-in antiforgery for remote functions, query cancellation, and the page-directory mapping disagreement between the scaffolder and SveltePage. - CLAUDE.md records the new invariants so they are not undone: async SSR, descriptor-level authorization, two-stage type naming, the numerics contract, multi-targeting, and that only .svelte-net may be deleted from. * Fix code-review findings: inherited [Authorize], empty remote services, stamp path Inherited [Authorize] was dropped on the generated path. The generator read symbol.GetAttributes(), which returns only directly-applied attributes, while AuthorizeAttribute is Inherited = true and both MVC and the reflection fallback honour that. A service deriving from a secured base produced a descriptor with no policy at all, so MapSvelteRemote attached no metadata and the SSR bridge let the call through - on the path that is now the default. The generator walks base types (and overridden methods), and the reflection path is aligned to match subclasses of AuthorizeAttribute rather than only the exact type. A [SvelteRemote] class with no remote methods broke the consumer's build: SvelteRemoteGenerator emits no dispatcher for it, but the metadata generator still emitted a call to one (CS0103). That hits anyone who adds the attribute before the first method, or comments the methods out. The incremental build stamp landed in the project's source root, not obj/. The targets file is imported from the csproj body, before the SDK defines IntermediateOutputPath, so the property expanded to nothing - which had already committed three stray stamp files in samples/. Resolved inside a target instead, and the files are removed and ignored. Also, all three pre-existing on this branch and found by the same review: - Origin comparison no longer requires a matching scheme. Behind a TLS-terminating proxy without UseForwardedHeaders, Request.Scheme is http while the browser sends an https Origin, which rejected every POST from a correctly deployed app. Host and port identify the origin. - A command POST with no body is treated as no arguments. ContentLength is null for chunked and HTTP/2 bodies, so it could not distinguish "no body" from "streamed body" and answered 400. - The Node/Bun fetch shim forwards all three HeadersInit forms. It used Object.entries(), which yields nothing for a Headers instance or an array of pairs, silently dropping the caller's headers. Two further findings are recorded on the roadmap rather than patched: no-JS form validation rendering problem JSON (needs errors carried across the redirect) and the metadata generator holding Roslyn symbols in its incremental pipeline (needs equatable value models). Tests: 216 -> 222. The Node header test was verified to fail 2 of 3 cases against the old shim, and the inherited-[Authorize] test against the old generator. * Regenerate the lockfile for the peer-dependency move Moving @sveltejs/vite-plugin-svelte from dependencies to peerDependencies changed the workspace manifest without updating bun.lock, so CI's bun install --frozen-lockfile failed. Also restores the em-dash in the package description, which a JSON rewrite had escaped to —. * Install chromium-headless-shell in CI Vitest browser mode launches the headless shell, which Playwright ships as a separate download from chromium. Installing only chromium left it absent from the runner cache and every browser test failed to launch. * Install Playwright browsers with the workspace's own playwright bunx resolves and downloads its own copy of playwright, installs browsers for that version, and rewrites bun.lock as a side effect. Vitest then launches the pinned 1.61.1, whose chromium build was never downloaded, so every browser test failed with 'Executable doesn't exist'. Running the resolved binary from packages/sveltenet installs the browsers the tests actually use.
1 parent 0d4f6ea commit 30c0788

90 files changed

Lines changed: 3841 additions & 545 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,32 @@ permissions:
1010

1111
jobs:
1212
dotnet:
13-
runs-on: ubuntu-latest
13+
# Windows is most of the ASP.NET installed base, and path separators, process
14+
# handling and file-system case sensitivity all differ there.
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest, windows-latest]
19+
runs-on: ${{ matrix.os }}
1420
steps:
1521
- uses: actions/checkout@v7
1622
- uses: actions/setup-dotnet@v5
1723
with:
18-
dotnet-version: 10.0.x
24+
# net8.0 is a shipped target framework, so its runtime must be present to
25+
# test against.
26+
dotnet-version: |
27+
8.0.x
28+
10.0.x
1929
- run: dotnet restore SvelteNet.slnx
20-
- run: dotnet test SvelteNet.slnx --no-restore --configuration Release
30+
- run: dotnet test SvelteNet.slnx --no-restore --configuration Release --collect:"XPlat Code Coverage"
2131
- run: dotnet format SvelteNet.slnx --no-restore --verify-no-changes
32+
if: matrix.os == 'ubuntu-latest'
33+
# Every shipped package is packed, so missing metadata and broken pack paths
34+
# fail CI rather than the release.
35+
- run: dotnet pack src/SvelteNet.Core/SvelteNet.Core.csproj --no-restore --configuration Release
2236
- run: dotnet pack src/SvelteNet.AspNetCore/SvelteNet.AspNetCore.csproj --no-restore --configuration Release
37+
- run: dotnet pack src/SvelteNet.FluentValidation/SvelteNet.FluentValidation.csproj --no-restore --configuration Release
38+
- run: dotnet pack src/SvelteNet.Jint/SvelteNet.Jint.csproj --no-restore --configuration Release
2339

2440
javascript:
2541
runs-on: ubuntu-latest
@@ -31,7 +47,12 @@ jobs:
3147
- uses: oven-sh/setup-bun@v2
3248
- run: bun install --frozen-lockfile
3349
- run: bun audit
34-
- run: bunx playwright install --with-deps chromium
50+
# Run the workspace's own playwright, not bunx. bunx resolves and downloads its
51+
# own copy, then installs browsers for *that* version — leaving the pinned
52+
# version's browser build absent, so every browser test fails to launch. It also
53+
# rewrites bun.lock as a side effect.
54+
- run: ./node_modules/.bin/playwright install --with-deps chromium
55+
working-directory: packages/sveltenet
3556
- run: bun run test
3657
working-directory: packages/sveltenet
3758
- run: bun run check

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,16 @@ _ReSharper*/
3535
node_modules/
3636
.svelte-net/
3737
.idea/
38+
39+
# Vite / VitePress build output
40+
docs/.vitepress/dist/
41+
docs/.vitepress/cache/
42+
wwwroot/client/
43+
44+
# SvelteNet build stamps
45+
*.typegen.stamp
46+
47+
# NuGet output
48+
*.nupkg
49+
*.snupkg
50+
/artifacts/

CLAUDE.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,19 @@ Code samples in docs must compile against the current API — treat a doc snippe
3232

3333
## Architecture invariants
3434

35-
- **SvelteNet.Core stays host-agnostic** — no MVC/Razor/HTTP dependencies (Blazor hosting is planned). New transports go in `SvelteNet.AspNetCore` as minimal-API endpoints, not MVC filters.
36-
- **One serialization contract**: `SvelteJson.Options` (camelCase props, dict keys, enums). The TS generator (`TypeGen/`) must always match it — a TS type that disagrees with the JSON is a bug.
35+
- **SvelteNet.Core stays host-agnostic** — no MVC/Razor/HTTP dependencies (Blazor hosting is planned). New transports go in `SvelteNet.AspNetCore` as minimal-API endpoints, not MVC filters. Core also carries no JS engine: Jint lives in `SvelteNet.Jint`.
36+
- **Everything ships `net8.0;net10.0`.** Guard .NET 9+ APIs with `#if NET9_0_OR_GREATER`; `SvelteNet.Build` targets net8.0 with `RollForward=LatestMajor` so one copy of the tool runs under any SDK.
37+
- **One serialization contract**: `SvelteJson.Options` (camelCase props, dict keys, enums; `long`/`ulong`/`decimal` as strings, because a JS number cannot hold them). All three TS emitters — `SvelteMetadataGenerator`, `SvelteRemoteGenerator`, and reflection `TypeGen/` — must always match it. A TS type that disagrees with the JSON is a bug.
38+
- **SSR is asynchronous**: `ISvelteSsrEngine.RenderAsync` / `ISvelteSsrFetchHandler.HandleAsync`. A render can await remote queries, so no layer may block on it. A synchronous JS runtime moves the render to a bounded worker (see `JintSsrEngine`) rather than blocking the request thread. `SveltePage` prerenders in the page filter so `.cshtml` stays synchronous; MVC uses `@await Html.SvelteAsync(...)`.
39+
- **Authorization is descriptor-level**: `[Authorize]`/`[AllowAnonymous]` become `RemoteAuthorization` data on the descriptor, which `MapSvelteRemote` projects onto endpoints AND `RemoteSsrFetchHandler` evaluates directly. The bridge bypasses routing, so anything that only guards endpoints leaves SSR open. Generated and reflection paths must agree.
40+
- **TypeScript names are resolved in two stages**: emitters produce a namespace-qualified placeholder (`TypeScriptNaming.Placeholder`); the scaffolder — the only component that sees every assembly — shortens and substitutes. Never emit a final type name from a generator.
3741
- **Validation errors are RFC 9457 problem details** (400, `application/problem+json`, ASP.NET `errors` member) everywhere: remote endpoints via `Results.ValidationProblem`, the SSR fetch bridge, and enhanced SveltePage posts (whose `data` extension member carries fresh props; SSR props expose the same shape as `data.problem`). `SvelteValidationException` is the throwing API. Never invent a bespoke error shape.
3842
- **BYOV validation pipeline**: dispatchers (generated AND reflection — both must emit the same `await args.ValidateBoundAsync()` between binding and invocation) run the registered `ISvelteRemoteValidator`s over `RemoteArguments.Bound`; `DataAnnotationsRemoteValidator` is registered by default. Pages get the equivalent through `ModelState`. New validation sources plug in via DI, never via new wire shapes or dispatcher special cases.
3943
- **Paths contract**: manifest keys are `{PagesPath}/{Component}.svelte`; `SvelteOptions` (C#) and `sveltenet()` vite plugin options must agree (`PagesPath`/`pagesPath`, `ClientOutput`/`clientOutDir`, `ServerOutput`/`serverOutDir`). The SSR bundle is deliberately NOT under wwwroot.
4044
- **Remote dispatch AND registration are descriptor-based**: `SvelteNet.Generators` emits compiled dispatchers (module initializer → `SvelteRemoteDescriptors`); `AddSvelteNet` consumes registered descriptors scoped to the app's assemblies — no runtime reflection scan. Reflection (`FromReflection` + the `TypeDiscovery` fallback) only covers apps without the analyzer and must stay behaviorally identical to generated code. The scaffolder generates one colocated `*.remote.ts` class per service from the same descriptors.
4145
- **Discovery is assembly-scoped**: multiple SvelteNet apps share the test process, so `[SvelteRemote]`/`[SvelteComponent]`/`SveltePage` discovery must respect `SvelteOptions.ApplicationAssemblies` (defaults to the `AddSvelteNet` caller). An unscoped scan leaks one sample's services into another's container.
4246
- **Type generation is build-time**: `SvelteNet.Build.targets` (imported by each sample, shipped in the NuGet's build/ later) runs `SvelteNet.Build` after every `dotnet build` — it loads the built app assembly in an isolated ALC and invokes the scaffolder via reflection (deliberately no SvelteNet project references, so the app's copy is the only copy). Startup scaffolding (`EnableScaffolding = true`) is only a fallback for apps without the targets.
43-
- **Scaffolder rules**: `.svelte-net/types/**/*.d.ts` and colocated `*.remote.ts` classes are regenerated every build ("do not edit" headers); `.svelte` components, `vite.config.ts`, `tsconfig.json`, and `package.json` are write-once and user-owned after creation. Client/SSR entry modules come from the npm package, so apps do not scaffold `mount.ts` or `render.ts`. Each sample commits `wwwroot/.gitkeep` — Development crashes without the directory.
47+
- **Scaffolder rules**: `.svelte-net/types/**/*.d.ts` and colocated `*.remote.ts` classes are regenerated every build ("do not edit" headers); `.svelte` components, `vite.config.ts`, `tsconfig.json`, and `package.json` are write-once and user-owned after creation. **Only `.svelte-net` may be deleted from** — stale clients are tracked in `.svelte-net/generated-clients.txt`, never found by scanning file contents. `SvelteNetGenerateTypes` is incremental; keep its `Inputs`/`Outputs`. Client/SSR entry modules come from the npm package, so apps do not scaffold `mount.ts` or `render.ts`. Each sample commits `wwwroot/.gitkeep` — Development crashes without the directory.
4448
- **SSR queries work**: awaited queries resolve during SSR through the in-process fetch bridge (`ISvelteSsrFetchHandler`) and stash into the head via `hydratable`; `RemoteQuery` instances are per-render on the server (pooled engines keep module state alive). `then`/`catch`/`finally` on queries are property getters — the reactive read must happen during the synchronous `.then` property access. Jint has no Node APIs beyond the served shims (`node:async_hooks`); Vite SSR builds bundle everything (`noExternal`) because production has no node_modules.
4549
- **SSR is opt-in**: `AddSvelteNet()` is client-only; choose exactly one of `AddJintSSR()`, `AddNodeSSR()`, `AddBunJsSSR()`, or `AddCustomRenderer(...)`. Backend-specific settings stay in `JintSsrOptions`, `NodeSsrOptions`, or `BunJsSsrOptions`, not `SvelteOptions`. Node.js/Bun require their configured CLI on PATH; Jint's remote-query bridge is in-process, while CLI engines use authenticated loopback fetches against a server-reported or explicitly trusted `BaseUrl` — never derive that target from the incoming `Host` header.
4650
- **`preserveEntrySignatures` is load-bearing** in the vite plugin — without it Rollup/Rolldown treeshakes entry exports and hydration silently breaks.

Directory.Build.props

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,36 @@
44
<LangVersion>latest</LangVersion>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<!-- Shared NuGet metadata. Harmless on non-packable projects. -->
10+
<PropertyGroup>
11+
<Version>0.1.0</Version>
712
<Authors>Jake Starkey</Authors>
13+
<Company>Jake Starkey</Company>
14+
<Copyright>Copyright (c) 2026 Jake Starkey</Copyright>
815
<PackageProjectUrl>https://github.com/datstarkey/sveltenet</PackageProjectUrl>
916
<RepositoryUrl>https://github.com/datstarkey/sveltenet</RepositoryUrl>
17+
<RepositoryType>git</RepositoryType>
18+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
19+
<PackageReadmeFile>README.md</PackageReadmeFile>
20+
<PackageTags>svelte;aspnetcore;razor-pages;mvc;ssr;islands;vite</PackageTags>
21+
</PropertyGroup>
22+
23+
<!-- Source Link: debuggable, reproducible packages. -->
24+
<PropertyGroup>
25+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
26+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
27+
<IncludeSymbols>true</IncludeSymbols>
28+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
29+
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
30+
</PropertyGroup>
31+
32+
<!-- XML docs ship for IntelliSense; CS1591 stays off so undocumented members
33+
don't turn into build noise. -->
34+
<PropertyGroup>
35+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
36+
<NoWarn>$(NoWarn);CS1591</NoWarn>
1037
</PropertyGroup>
1138

1239
</Project>

Directory.Build.targets

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project>
2+
3+
<!-- Every packable project declares PackageReadmeFile (Directory.Build.props), so
4+
each one needs the repo README at the package root. Imported after the project
5+
file so $(IsPackable) is resolved. -->
6+
<ItemGroup Condition="'$(IsPackable)' != 'false'">
7+
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" Visible="false" />
8+
</ItemGroup>
9+
10+
<ItemGroup Condition="'$(IsPackable)' != 'false'">
11+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
12+
</ItemGroup>
13+
14+
</Project>

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Jake Starkey
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ Run `dotnet build` once. The bundled analyzer and build target generate ambient
4545
- [MVC views](docs/mvc.md)`[SvelteComponent]` typed rendering, `@Html.Svelte`
4646
- [Remote functions](docs/remote-functions.md)`[Query]`/`[Command]`/`[Form]`, the typed client, source-generated dispatch
4747
- [SSR renderers](docs/ssr.md) — opt-in Jint, Node.js, Bun, and custom renderers
48+
- [Security](docs/security.md) — authorization, CSRF, and what the SSR bridge enforces
4849
- [Options](docs/options.md) — shared and renderer-specific configuration
4950

5051
## Repository layout
5152

5253
```
53-
src/ SvelteNet.Core — renderer, Jint SSR engine, TypeScript generation (NuGet)
54+
src/ SvelteNet.Core — renderer, serialization contract, TypeScript generation (NuGet)
5455
SvelteNet.AspNetCore — ASP.NET integration, Node/Bun SSR, remote endpoints (NuGet)
56+
SvelteNet.Jint — in-process Jint server rendering (NuGet, optional)
5557
SvelteNet.Generators — Roslyn source generator for remote dispatch
5658
SvelteNet.Build — MSBuild target: dotnet build generates the TS types
5759
SvelteNet.FluentValidation — FluentValidation adapter for the validation pipeline

0 commit comments

Comments
 (0)