netjes 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -2
  3. package/bin/netjes.mjs +13 -0
  4. package/cli/agents.mjs +66 -0
  5. package/cli/commands.mjs +467 -0
  6. package/cli/help.mjs +89 -0
  7. package/cli/info.mjs +454 -0
  8. package/cli/main.mjs +167 -0
  9. package/cli/packages.mjs +19 -0
  10. package/cli/process-io.mjs +163 -0
  11. package/cli/routes.mjs +31 -0
  12. package/cli/scaffold.mjs +193 -0
  13. package/cli/suggest.mjs +50 -0
  14. package/cli/terminal.mjs +40 -0
  15. package/dist/actions.d.ts +98 -0
  16. package/dist/actions.d.ts.map +1 -0
  17. package/dist/actions.js +176 -0
  18. package/dist/actions.js.map +1 -0
  19. package/dist/client-entry.d.ts +2 -0
  20. package/dist/client-entry.d.ts.map +1 -0
  21. package/dist/client-entry.js +126 -0
  22. package/dist/client-entry.js.map +1 -0
  23. package/dist/deferred.d.ts +39 -0
  24. package/dist/deferred.d.ts.map +1 -0
  25. package/dist/deferred.js +123 -0
  26. package/dist/deferred.js.map +1 -0
  27. package/dist/define-page.d.ts +15 -0
  28. package/dist/define-page.d.ts.map +1 -0
  29. package/dist/define-page.js +27 -0
  30. package/dist/define-page.js.map +1 -0
  31. package/dist/document.d.ts +19 -0
  32. package/dist/document.d.ts.map +1 -0
  33. package/dist/document.js +17 -0
  34. package/dist/document.js.map +1 -0
  35. package/dist/draft-mode.d.ts +5 -0
  36. package/dist/draft-mode.d.ts.map +1 -0
  37. package/dist/draft-mode.js +6 -0
  38. package/dist/draft-mode.js.map +1 -0
  39. package/dist/dynamic.d.ts +20 -0
  40. package/dist/dynamic.d.ts.map +1 -0
  41. package/dist/dynamic.js +37 -0
  42. package/dist/dynamic.js.map +1 -0
  43. package/dist/form-data.d.ts +10 -0
  44. package/dist/form-data.d.ts.map +1 -0
  45. package/dist/form-data.js +10 -0
  46. package/dist/form-data.js.map +1 -0
  47. package/dist/form.d.ts +35 -0
  48. package/dist/form.d.ts.map +1 -0
  49. package/dist/form.js +80 -0
  50. package/dist/form.js.map +1 -0
  51. package/dist/href.d.ts +25 -0
  52. package/dist/href.d.ts.map +1 -0
  53. package/dist/href.js +71 -0
  54. package/dist/href.js.map +1 -0
  55. package/dist/image.d.ts +36 -0
  56. package/dist/image.d.ts.map +1 -0
  57. package/dist/image.js +60 -0
  58. package/dist/image.js.map +1 -0
  59. package/dist/index.d.ts +18 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +14 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/link.d.ts +17 -0
  64. package/dist/link.d.ts.map +1 -0
  65. package/dist/link.js +46 -0
  66. package/dist/link.js.map +1 -0
  67. package/dist/locale.d.ts +3 -0
  68. package/dist/locale.d.ts.map +1 -0
  69. package/dist/locale.js +6 -0
  70. package/dist/locale.js.map +1 -0
  71. package/dist/metadata.d.ts +83 -0
  72. package/dist/metadata.d.ts.map +1 -0
  73. package/dist/metadata.js +100 -0
  74. package/dist/metadata.js.map +1 -0
  75. package/dist/modules.d.ts +74 -0
  76. package/dist/modules.d.ts.map +1 -0
  77. package/dist/modules.js +85 -0
  78. package/dist/modules.js.map +1 -0
  79. package/dist/nonce.d.ts +8 -0
  80. package/dist/nonce.d.ts.map +1 -0
  81. package/dist/nonce.js +12 -0
  82. package/dist/nonce.js.map +1 -0
  83. package/dist/payload.d.ts +171 -0
  84. package/dist/payload.d.ts.map +1 -0
  85. package/dist/payload.js +273 -0
  86. package/dist/payload.js.map +1 -0
  87. package/dist/public-config.d.ts +11 -0
  88. package/dist/public-config.d.ts.map +1 -0
  89. package/dist/public-config.js +10 -0
  90. package/dist/public-config.js.map +1 -0
  91. package/dist/query.d.ts +22 -0
  92. package/dist/query.d.ts.map +1 -0
  93. package/dist/query.js +68 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/render-cleanup.d.ts +6 -0
  96. package/dist/render-cleanup.d.ts.map +1 -0
  97. package/dist/render-cleanup.js +7 -0
  98. package/dist/render-cleanup.js.map +1 -0
  99. package/dist/root.d.ts +12 -0
  100. package/dist/root.d.ts.map +1 -0
  101. package/dist/root.js +26 -0
  102. package/dist/root.js.map +1 -0
  103. package/dist/route-tree.d.ts +56 -0
  104. package/dist/route-tree.d.ts.map +1 -0
  105. package/dist/route-tree.js +86 -0
  106. package/dist/route-tree.js.map +1 -0
  107. package/dist/router.d.ts +118 -0
  108. package/dist/router.d.ts.map +1 -0
  109. package/dist/router.js +938 -0
  110. package/dist/router.js.map +1 -0
  111. package/dist/schema.d.ts +35 -0
  112. package/dist/schema.d.ts.map +1 -0
  113. package/dist/schema.js +74 -0
  114. package/dist/schema.js.map +1 -0
  115. package/dist/script.d.ts +23 -0
  116. package/dist/script.d.ts.map +1 -0
  117. package/dist/script.js +89 -0
  118. package/dist/script.js.map +1 -0
  119. package/dist/server-entry.d.ts +2 -0
  120. package/dist/server-entry.d.ts.map +1 -0
  121. package/dist/server-entry.js +4 -0
  122. package/dist/server-entry.js.map +1 -0
  123. package/dist/server-render.d.ts +19 -0
  124. package/dist/server-render.d.ts.map +1 -0
  125. package/dist/server-render.js +193 -0
  126. package/dist/server-render.js.map +1 -0
  127. package/dist/testing-setup.d.ts +2 -0
  128. package/dist/testing-setup.d.ts.map +1 -0
  129. package/dist/testing-setup.js +8 -0
  130. package/dist/testing-setup.js.map +1 -0
  131. package/dist/testing.d.ts +76 -0
  132. package/dist/testing.d.ts.map +1 -0
  133. package/dist/testing.js +106 -0
  134. package/dist/testing.js.map +1 -0
  135. package/dist/vitals-config.d.ts +4 -0
  136. package/dist/vitals-config.d.ts.map +1 -0
  137. package/dist/vitals-config.js +5 -0
  138. package/dist/vitals-config.js.map +1 -0
  139. package/dist/vitals.d.ts +13 -0
  140. package/dist/vitals.d.ts.map +1 -0
  141. package/dist/vitals.js +60 -0
  142. package/dist/vitals.js.map +1 -0
  143. package/dist/zod.d.ts +19 -0
  144. package/dist/zod.d.ts.map +1 -0
  145. package/dist/zod.js +65 -0
  146. package/dist/zod.js.map +1 -0
  147. package/package.json +147 -4
  148. package/sidecar/server.mjs +325 -0
  149. package/skills/netjes/SKILL.md +93 -0
  150. package/skills/netjes/references/caching-and-streaming.md +68 -0
  151. package/skills/netjes/references/data-and-actions.md +98 -0
  152. package/skills/netjes/references/deployment.md +57 -0
  153. package/skills/netjes/references/routing.md +65 -0
  154. package/skills/netjes/references/testing.md +57 -0
  155. package/src/actions.tsx +241 -0
  156. package/src/client-entry.tsx +126 -0
  157. package/src/deferred.ts +134 -0
  158. package/src/define-page.tsx +41 -0
  159. package/src/document.tsx +49 -0
  160. package/src/draft-mode.ts +6 -0
  161. package/src/dynamic.tsx +64 -0
  162. package/src/form-data.ts +10 -0
  163. package/src/form.tsx +122 -0
  164. package/src/href.ts +79 -0
  165. package/src/image.tsx +91 -0
  166. package/src/index.ts +40 -0
  167. package/src/link.tsx +73 -0
  168. package/src/locale.ts +6 -0
  169. package/src/metadata.tsx +181 -0
  170. package/src/modules.ts +133 -0
  171. package/src/nonce.tsx +14 -0
  172. package/src/payload.ts +405 -0
  173. package/src/public-config.tsx +19 -0
  174. package/src/query.tsx +82 -0
  175. package/src/render-cleanup.ts +7 -0
  176. package/src/root.tsx +39 -0
  177. package/src/route-tree.tsx +139 -0
  178. package/src/router.tsx +1188 -0
  179. package/src/schema.ts +98 -0
  180. package/src/script.tsx +103 -0
  181. package/src/server-entry.tsx +4 -0
  182. package/src/server-render.tsx +223 -0
  183. package/src/testing-setup.ts +8 -0
  184. package/src/testing.tsx +177 -0
  185. package/src/virtual.d.ts +12 -0
  186. package/src/vitals-config.ts +6 -0
  187. package/src/vitals.tsx +68 -0
  188. package/src/zod.ts +72 -0
  189. package/template/.dockerignore +11 -0
  190. package/template/Dockerfile +58 -0
  191. package/template/Features/Home/HomePage.cs +32 -0
  192. package/template/Program.cs +28 -0
  193. package/template/Properties/launchSettings.json +13 -0
  194. package/template/README.md +24 -0
  195. package/template/SiteProps.cs +4 -0
  196. package/template/_AGENTS.md +64 -0
  197. package/template/_CLAUDE.md +1 -0
  198. package/template/__NAME__.csproj +11 -0
  199. package/template/_gitattributes +2 -0
  200. package/template/_gitignore +11 -0
  201. package/template/app/about/page.tsx +17 -0
  202. package/template/app/error.tsx +15 -0
  203. package/template/app/layout.tsx +33 -0
  204. package/template/app/not-found.tsx +14 -0
  205. package/template/app/page.tsx +14 -0
  206. package/template/appsettings.Development.json +3 -0
  207. package/template/appsettings.json +9 -0
  208. package/template/components/Greeting.test.tsx +25 -0
  209. package/template/components/Greeting.tsx +26 -0
  210. package/template/components/HelloForm.test.tsx +27 -0
  211. package/template/components/HelloForm.tsx +34 -0
  212. package/template/docker-compose.override.yml +15 -0
  213. package/template/docker-compose.prod.yml +26 -0
  214. package/template/docker-compose.yml +16 -0
  215. package/template/generated/netjes.ts +73 -0
  216. package/template/package.json +36 -0
  217. package/template/public/favicon.svg +6 -0
  218. package/template/styles/global.css +7 -0
  219. package/template/tsconfig.json +19 -0
  220. package/template/vite.config.ts +7 -0
  221. package/vite/plugin.d.ts +29 -0
  222. package/vite/plugin.mjs +291 -0
@@ -0,0 +1,139 @@
1
+ import { Component, type ReactElement, type ReactNode, Suspense, createContext } from 'react';
2
+ import { type Metadata, MetadataTags, resolveMetadata } from './metadata.tsx';
3
+ import type { ErrorPageProps, LoadedSegment, RouteModules } from './modules.ts';
4
+ import type { PageKind, Params, SegmentRef } from './payload.ts';
5
+
6
+ export type { ErrorPageProps } from './modules.ts';
7
+
8
+ type RouteTreeProps = {
9
+ modules: RouteModules;
10
+ /** The payload's segments, for their loader props; matched to `modules.segments` by position. */
11
+ segments?: readonly SegmentRef[];
12
+ props: Record<string, unknown>;
13
+ params: Params;
14
+ status: number;
15
+ pageKind?: PageKind | undefined;
16
+ /** Remounts the page and templates (not layouts) when it changes, so page state does not leak between URLs. */
17
+ pageKey: string;
18
+ /** What `reset` does on an error page the server chose: load the page again. */
19
+ retry?: () => void;
20
+ /** Metadata from the C# loader. */
21
+ metadata?: Metadata | undefined;
22
+ };
23
+
24
+ /** How many URL segments the layout rendering here covers: what useSelectedLayoutSegment(s) count from. */
25
+ export const LayoutDepthContext = createContext(0);
26
+
27
+ /**
28
+ * URL depth of a segment folder when the server did not send it (special pages): its folders after the router root,
29
+ * route groups not counted.
30
+ */
31
+ function depthOf(segmentId: string): number {
32
+ return segmentId.split('/').slice(1).filter((folder) => !(folder.startsWith('(') && folder.endsWith(')'))).length;
33
+ }
34
+
35
+ type BoundaryProps = { resetKey: unknown; fallback: (error: unknown, reset: () => void) => ReactNode; children: ReactNode };
36
+
37
+ /** Catches render errors below it; resets when `resetKey` changes (a navigation) or `reset` is called. */
38
+ export class ErrorBoundary extends Component<BoundaryProps, { error: unknown; failed: boolean }> {
39
+ override state = { error: null as unknown, failed: false };
40
+
41
+ static getDerivedStateFromError(error: unknown) {
42
+ return { error, failed: true };
43
+ }
44
+
45
+ override componentDidUpdate(previous: BoundaryProps) {
46
+ if (previous.resetKey !== this.props.resetKey && this.state.failed) this.setState({ error: null, failed: false });
47
+ }
48
+
49
+ private readonly reset = () => this.setState({ error: null, failed: false });
50
+
51
+ override render() {
52
+ return this.state.failed ? this.props.fallback(this.state.error, this.reset) : this.props.children;
53
+ }
54
+ }
55
+
56
+ function asError(error: unknown): Error {
57
+ return error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Something went wrong');
58
+ }
59
+
60
+ /** The error the server reported (props of the error page it rendered), as an Error for `error.tsx`. */
61
+ function serverError(props: Record<string, unknown>): ErrorPageProps['error'] {
62
+ const error: ErrorPageProps['error'] = new Error(typeof props.message === 'string' ? props.message : 'Something went wrong');
63
+ if (typeof props.status === 'number') error.status = props.status;
64
+ if (typeof props.stack === 'string') error.stack = props.stack;
65
+ return error;
66
+ }
67
+
68
+ /**
69
+ * The page wrapped in its segments, outermost first. Per segment, as in the Next.js App Router:
70
+ * layout > template > error boundary > Suspense (loading) > the next segment or the page. Layouts keep their state
71
+ * while the page underneath changes; templates remount.
72
+ */
73
+ export function RouteTree({ modules, segments = [], props, params, status, pageKind, pageKey, retry, metadata }: RouteTreeProps): ReactElement {
74
+ const Page = modules.page;
75
+ const pageProps = pageKind === 'error' ? { ...props, error: serverError(props), reset: retry ?? (() => {}) } : props;
76
+ let element: ReactElement = Page ? <Page key={pageKey} {...pageProps} /> : <BuiltInPage key={pageKey} status={status} props={props} />;
77
+ for (let i = modules.segments.length - 1; i >= 0; i--) {
78
+ const { Layout, Template, Loading, Error: SegmentError, slots } = modules.segments[i]!;
79
+ if (Loading) element = <Suspense fallback={<Loading />}>{element}</Suspense>;
80
+ if (SegmentError) {
81
+ const inner = element;
82
+ element = (
83
+ <ErrorBoundary resetKey={pageKey} fallback={(error, reset) => <SegmentError error={asError(error)} reset={reset} />}>
84
+ {inner}
85
+ </ErrorBoundary>
86
+ );
87
+ }
88
+ if (Template) element = <Template key={pageKey}>{element}</Template>;
89
+ if (Layout) {
90
+ element = (
91
+ <LayoutDepthContext value={segments[i]?.depth ?? depthOf(modules.segments[i]!.id)}>
92
+ <Layout {...segments[i]?.props} {...slotElements(slots, params, segments[i]?.slotParams)} params={params}>
93
+ {element}
94
+ </Layout>
95
+ </LayoutDepthContext>
96
+ );
97
+ }
98
+ }
99
+ const layoutsMetadata = modules.segments.map((segment) => segment.metadata);
100
+ const hasMetadata = layoutsMetadata.some(Boolean) || Boolean(modules.pageMetadata) || Boolean(metadata);
101
+ // One shape whether or not this page has metadata: the layouts stay at the same place in the tree, so React keeps
102
+ // them mounted when the user navigates between a page with metadata and one without.
103
+ return (
104
+ <>
105
+ {hasMetadata ? <MetadataTags metadata={resolveMetadata(layoutsMetadata, modules.pageMetadata, metadata)} /> : null}
106
+ {element}
107
+ </>
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Parallel routes: each slot as a prop of its layout, named like the folder without the @ (null renders nothing).
113
+ * Each slot's element gets the slot page's own params when the server sent them (else the page's).
114
+ */
115
+ function slotElements(slots: LoadedSegment['slots'], params: Params, slotParams: Record<string, Params> | undefined): Record<string, ReactElement | null> {
116
+ if (!slots) return {};
117
+ return Object.fromEntries(Object.entries(slots).map(([name, Slot]) => [name, Slot ? <Slot params={slotParams?.[name] ?? params} /> : null]));
118
+ }
119
+
120
+ /** Shown for 404s and errors when the app has no not-found or error page of its own. */
121
+ export function BuiltInPage({ status, props }: { status: number; props: Record<string, unknown> }) {
122
+ const notFound = status === 404;
123
+ const title = notFound ? 'This page could not be found' : 'Something went wrong';
124
+ const message = !notFound && typeof props.message === 'string' ? props.message : null;
125
+ const stack = !notFound && typeof props.stack === 'string' ? props.stack : null;
126
+ return (
127
+ <main style={{ fontFamily: 'system-ui, sans-serif', maxWidth: '42rem', margin: '12vh auto', padding: '0 1.5rem', lineHeight: 1.5 }}>
128
+ <title>{`${status} · ${title}`}</title>
129
+ <p style={{ fontSize: '3.5rem', fontWeight: 700, margin: 0 }}>{status}</p>
130
+ <h1 style={{ fontSize: '1.5rem', margin: '0.25rem 0 1rem' }}>{title}</h1>
131
+ {/* A message can take several lines, such as a compiler's code frame in development. */}
132
+ {message && <p style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{message}</p>}
133
+ {stack && <pre style={{ whiteSpace: 'pre-wrap', fontSize: '0.8rem', background: '#f4f4f5', padding: '1rem', borderRadius: 8 }}>{stack}</pre>}
134
+ <p>
135
+ <a href="/">Go to the home page</a>
136
+ </p>
137
+ </main>
138
+ );
139
+ }