@remix-run/cli 0.2.0 → 0.3.1

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 (87) hide show
  1. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  2. package/dist/lib/bootstrap-project.js +32 -9
  3. package/dist/lib/cli.d.ts +29 -0
  4. package/dist/lib/cli.d.ts.map +1 -1
  5. package/dist/lib/cli.js +26 -4
  6. package/dist/lib/commands/completion.d.ts.map +1 -1
  7. package/dist/lib/commands/completion.js +5 -1
  8. package/dist/lib/commands/doctor.js +18 -18
  9. package/dist/lib/commands/routes.js +3 -3
  10. package/dist/lib/completion.js +1 -5
  11. package/dist/lib/controller-files.d.ts +0 -1
  12. package/dist/lib/controller-files.d.ts.map +1 -1
  13. package/dist/lib/controller-files.js +3 -5
  14. package/dist/lib/controller-ownership.d.ts +9 -9
  15. package/dist/lib/controller-ownership.d.ts.map +1 -1
  16. package/dist/lib/controller-ownership.js +56 -91
  17. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  18. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  19. package/dist/lib/doctor/controller-findings.js +15 -87
  20. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  21. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  22. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  23. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  24. package/dist/lib/doctor/controller-placeholders.js +18 -149
  25. package/dist/lib/doctor/controllers.js +1 -1
  26. package/dist/lib/doctor/project.js +60 -52
  27. package/dist/lib/doctor/types.d.ts +2 -2
  28. package/dist/lib/doctor/types.d.ts.map +1 -1
  29. package/dist/lib/help-text.js +1 -1
  30. package/dist/lib/load-route-map-worker.js +17 -9
  31. package/dist/lib/route-map.d.ts +1 -1
  32. package/dist/lib/route-map.d.ts.map +1 -1
  33. package/dist/lib/route-map.js +29 -17
  34. package/package.json +5 -5
  35. package/src/lib/bootstrap-project.ts +39 -13
  36. package/src/lib/cli.ts +39 -4
  37. package/src/lib/commands/completion.ts +6 -1
  38. package/src/lib/commands/doctor.ts +18 -21
  39. package/src/lib/commands/routes.ts +3 -3
  40. package/src/lib/completion.ts +1 -5
  41. package/src/lib/controller-files.ts +4 -8
  42. package/src/lib/controller-ownership.ts +78 -141
  43. package/src/lib/doctor/controller-findings.ts +20 -97
  44. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  45. package/src/lib/doctor/controller-placeholders.ts +17 -189
  46. package/src/lib/doctor/controllers.ts +1 -1
  47. package/src/lib/doctor/project.ts +60 -52
  48. package/src/lib/doctor/types.ts +1 -5
  49. package/src/lib/help-text.ts +3 -2
  50. package/src/lib/load-route-map-worker.ts +19 -10
  51. package/src/lib/route-map.ts +61 -16
  52. package/{bootstrap → template}/.agents/skills/remix/SKILL.md +162 -77
  53. package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
  54. package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
  55. package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +4 -1
  56. package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
  57. package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +42 -53
  58. package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
  59. package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
  60. package/{bootstrap → template}/AGENTS.md +10 -9
  61. package/template/README.md +29 -0
  62. package/template/app/actions/controller.tsx +18 -0
  63. package/template/app/assets/entry.ts +8 -0
  64. package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
  65. package/{bootstrap → template}/app/assets.ts +5 -3
  66. package/template/app/middleware/render.tsx +44 -0
  67. package/template/app/router.ts +20 -0
  68. package/{bootstrap → template}/app/routes.ts +1 -2
  69. package/{bootstrap → template}/app/ui/document.tsx +11 -4
  70. package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
  71. package/template/gitignore +4 -0
  72. package/{bootstrap → template}/package.json +5 -6
  73. package/template/public/favicon.svg +11 -0
  74. package/template/server.ts +38 -0
  75. package/{bootstrap → template}/tsconfig.json +3 -2
  76. package/bootstrap/README.md +0 -27
  77. package/bootstrap/app/assets/entry.ts +0 -19
  78. package/bootstrap/app/controllers/auth.tsx +0 -21
  79. package/bootstrap/app/controllers/home.tsx +0 -11
  80. package/bootstrap/app/router.ts +0 -16
  81. package/bootstrap/app/ui/layout.tsx +0 -22
  82. package/bootstrap/app/utils/render.tsx +0 -26
  83. package/bootstrap/server.ts +0 -37
  84. /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
  85. /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
  86. /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
  87. /package/{bootstrap → template}/.agents/skills/remix/references/mixins-styling-events.md +0 -0
@@ -9,6 +9,7 @@ the task involves:
9
9
  - Building a fresh router per test for session, storage, or database isolation
10
10
  - Rendering components into a real DOM with `render(...)` or `createRoot(...)`
11
11
  - Configuring `remix test` discovery, excludes, and coverage
12
+ - Using adjacent CLI checks such as `remix routes`, `remix doctor`, and `remix version`
12
13
  - Choosing which layer to test for a given behavior
13
14
 
14
15
  For session and auth test setup, see `auth-and-sessions.md`. For component lifecycle, see
@@ -54,7 +55,7 @@ a known session, swap in `createMemorySessionStorage()` and a test cookie when c
54
55
  router.
55
56
 
56
57
  ```ts
57
- import { createMemorySessionStorage } from 'remix/session/memory-storage'
58
+ import { createMemorySessionStorage } from 'remix/session-storage/memory'
58
59
  import { createCookie } from 'remix/cookie'
59
60
 
60
61
  let router = createBookstoreRouter({
@@ -63,6 +64,21 @@ let router = createBookstoreRouter({
63
64
  })
64
65
  ```
65
66
 
67
+ Use `createTestServer` from `remix/node-fetch-server/test` when the behavior depends on a real
68
+ HTTP origin, redirects, streaming, cookies through a network boundary, or browser-style `fetch`:
69
+
70
+ ```ts
71
+ import { createTestServer } from 'remix/node-fetch-server/test'
72
+
73
+ let server = await createTestServer((request) => router.fetch(request))
74
+ try {
75
+ let response = await fetch(new URL(routes.home.href(), server.baseUrl))
76
+ assert.equal(response.status, 200)
77
+ } finally {
78
+ await server.close()
79
+ }
80
+ ```
81
+
66
82
  ## Test Runner Config
67
83
 
68
84
  Configure discovery and coverage in `remix-test.config.ts` or with CLI flags:
@@ -13,27 +13,28 @@ npm run typecheck
13
13
 
14
14
  ## Building Features
15
15
 
16
- Refer to ./agents/skills/remix/SKILL.md
16
+ Refer to ./.agents/skills/remix/SKILL.md
17
17
 
18
18
  ## Starter Layout
19
19
 
20
- - `app/controllers/home.tsx` owns the home page
21
- - `app/controllers/auth.tsx` owns the auth page
20
+ - `app/actions/controller.tsx` owns the top-level route actions
22
21
  - `app/routes.ts` defines the route contract
23
22
  - `app/router.ts` wires routes to route handlers
24
- - `app/ui/` holds the shared document and layout wrappers
25
- - `app/utils/render.tsx` centralizes HTML response rendering
23
+ - `app/middleware/render.tsx` installs the request-scoped renderer used by actions
24
+ - `app/ui/` holds the shared document shell and home page UI
25
+ - `app/assets.ts` owns the server-side asset pipeline used by the asset route and renderer
26
+ - `public/` contains static files served from the app root
26
27
 
27
28
  ## Route Ownership
28
29
 
29
30
  - Start from `app/routes.ts` and map each route to the narrowest owner on disk.
30
- - Keep simple pages in flat files like `app/controllers/home.tsx` and `app/controllers/auth.tsx`.
31
- - Promote a route into a controller folder with `controller.tsx` only when it gains nested routes, multiple actions, or route-owned modules.
31
+ - Put top-level route actions in `app/actions/controller.tsx`.
32
+ - Add `app/actions/<route-key>/controller.tsx` for nested route maps that need their own actions or middleware.
32
33
  - Keep route-owned page modules next to the route that owns them.
33
- - Move shared UI to `app/ui/`, not `app/controllers/`.
34
+ - Move shared UI to `app/ui/`, not `app/actions/`.
34
35
 
35
36
  ## Build-Out Notes
36
37
 
37
- - This starter intentionally begins small; add directories like `app/data/`, `app/middleware/`, `public/`, and `test/` only when you need them.
38
+ - This starter intentionally begins small; add directories like `app/data/` and `test/` only when you need them.
38
39
  - Prefer putting code in the narrowest owner before introducing shared modules.
39
40
  - Avoid generic dumping-ground directories like `app/lib/` or `app/components/`.
@@ -0,0 +1,29 @@
1
+ # %%RMX_APP_DISPLAY_NAME%%
2
+
3
+ A minimal Remix application starter with a home page.
4
+
5
+ ## Starter Shape
6
+
7
+ - `app/actions/controller.tsx` owns the top-level route actions.
8
+ - `app/routes.ts` defines the route contract.
9
+ - `app/router.ts` wires routes to handlers.
10
+ - `app/middleware/render.tsx` installs the request-scoped renderer used by actions.
11
+ - `app/ui/` holds the shared document shell and home page UI.
12
+ - `app/assets.ts` owns the server-side asset pipeline used by the asset route and renderer.
13
+ - `public/` contains static files served from the app root.
14
+
15
+ ## Growing The App
16
+
17
+ - Put top-level route actions in `app/actions/controller.tsx`.
18
+ - Add `app/actions/<route-key>/controller.tsx` when a nested route map needs its own actions or middleware.
19
+ - Add directories like `app/data/` or `test/` when the app actually needs them.
20
+ - Move shared UI into `app/ui/` once more than one route needs it.
21
+
22
+ ## Commands
23
+
24
+ ```sh
25
+ npm i
26
+ npm run start
27
+ npm test
28
+ npm run typecheck
29
+ ```
@@ -0,0 +1,18 @@
1
+ import { createController } from 'remix/router'
2
+
3
+ import { assetServer } from '../assets.ts'
4
+ import { routes } from '../routes.ts'
5
+ import { HomePage } from '../ui/scaffold-home-page.tsx'
6
+
7
+ export default createController(routes, {
8
+ actions: {
9
+ async assets(context) {
10
+ return (
11
+ (await assetServer.fetch(context.request)) ?? new Response('Not Found', { status: 404 })
12
+ )
13
+ },
14
+ home(context) {
15
+ return context.render(<HomePage />)
16
+ },
17
+ },
18
+ })
@@ -0,0 +1,8 @@
1
+ import { run } from 'remix/ui'
2
+
3
+ run({
4
+ async loadModule(moduleUrl, exportName) {
5
+ let mod = await import(moduleUrl)
6
+ return mod[exportName]
7
+ },
8
+ })
@@ -9,8 +9,9 @@ interface PromptButtonProps extends SerializableProps {
9
9
  text: string
10
10
  }
11
11
 
12
+ // This component hydrates independently; the rest of the page stays static HTML.
12
13
  export const PromptButton = clientEntry(
13
- '/assets/app/ui/prompt-button.tsx#PromptButton',
14
+ import.meta.url,
14
15
  function PromptButton(handle: Handle<PromptButtonProps>) {
15
16
  let state: CopyState = 'idle'
16
17
 
@@ -1,13 +1,15 @@
1
1
  import { createAssetServer } from 'remix/assets'
2
2
 
3
- export const assets = createAssetServer({
3
+ const rootDir = process.cwd()
4
+
5
+ export const assetServer = createAssetServer({
4
6
  basePath: '/assets',
5
- rootDir: process.cwd(),
7
+ rootDir,
6
8
  fileMap: {
7
9
  'app/*path': 'app/*path',
8
10
  'node_modules/*path': 'node_modules/*path',
9
11
  },
10
- allow: ['app/assets/**', 'app/ui/prompt-button.tsx', 'node_modules/**'],
12
+ allow: ['app/assets/**', 'node_modules/**'],
11
13
  deny: ['app/**/*.server.*'],
12
14
  sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
13
15
  scripts: {
@@ -0,0 +1,44 @@
1
+ import * as path from 'node:path'
2
+
3
+ import { renderWith } from 'remix/middleware/render'
4
+ import { createHtmlResponse } from 'remix/response/html'
5
+ import type { RemixNode } from 'remix/ui'
6
+ import { renderToStream } from 'remix/ui/server'
7
+
8
+ import { assetServer } from '../assets.ts'
9
+
10
+ export function render() {
11
+ return renderWith(
12
+ ({ request }) =>
13
+ function render(node: RemixNode, init?: ResponseInit) {
14
+ let stream = renderToStream(node, {
15
+ signal: request.signal,
16
+ // Server rendering turns client entries into browser module URLs.
17
+ async resolveClientEntry(entryId, component) {
18
+ if (!entryId.startsWith('file://')) {
19
+ throw new Error(
20
+ `Expected \`import.meta.url\` for clientEntry ID, received '${entryId}'`,
21
+ )
22
+ }
23
+
24
+ return {
25
+ href: await assetServer.getHref(entryId),
26
+ exportName: entryId.split('#')[1] || component.name || titleCaseFileName(entryId),
27
+ }
28
+ },
29
+ })
30
+
31
+ return createHtmlResponse(stream, init)
32
+ },
33
+ )
34
+ }
35
+
36
+ function titleCaseFileName(fileUrl: string): string {
37
+ let url = new URL(fileUrl)
38
+ let fileName = path.basename(url.pathname, path.extname(url.pathname))
39
+ return fileName
40
+ .split(/[^A-Za-z0-9]+/)
41
+ .filter(Boolean)
42
+ .map((segment) => segment[0]!.toUpperCase() + segment.slice(1))
43
+ .join('')
44
+ }
@@ -0,0 +1,20 @@
1
+ import { createRouter, type MiddlewareContext } from 'remix/router'
2
+ import { staticFiles } from 'remix/middleware/static'
3
+
4
+ import controller from './actions/controller.tsx'
5
+ import { render } from './middleware/render.tsx'
6
+ import { routes } from './routes.ts'
7
+
8
+ type AppContext = MiddlewareContext<[ReturnType<typeof render>]>
9
+
10
+ declare module 'remix/router' {
11
+ interface RouterTypes {
12
+ context: AppContext
13
+ }
14
+ }
15
+
16
+ export const router = createRouter<AppContext>({
17
+ middleware: [staticFiles('./public', { index: false }), render()],
18
+ })
19
+
20
+ router.map(routes, controller)
@@ -1,7 +1,6 @@
1
- import { get, route } from 'remix/fetch-router/routes'
1
+ import { get, route } from 'remix/routes'
2
2
 
3
3
  export const routes = route({
4
4
  assets: get('/assets/*path'),
5
5
  home: '/',
6
- auth: '/auth',
7
6
  })
@@ -1,26 +1,33 @@
1
- import type { RemixNode } from 'remix/ui'
1
+ import { css, type RemixNode } from 'remix/ui'
2
2
 
3
3
  import { routes } from '../routes.ts'
4
4
 
5
5
  export interface DocumentProps {
6
6
  children?: RemixNode
7
+ head?: RemixNode
7
8
  title?: string
8
9
  }
9
10
 
10
- const DEFAULT_TITLE = decodeURIComponent('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
11
+ const DEFAULT_TITLE = readAppDisplayName('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
11
12
 
12
13
  export function Document() {
13
- return ({ title = DEFAULT_TITLE, children }: DocumentProps) => (
14
+ return ({ children, head, title = DEFAULT_TITLE }: DocumentProps) => (
14
15
  <html lang="en">
15
16
  <head>
16
17
  <meta charSet="utf-8" />
17
18
  <meta name="viewport" content="width=device-width, initial-scale=1" />
19
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
18
20
  <title>{title}</title>
21
+ {head}
19
22
  </head>
20
- <body>
23
+ <body mix={css({ margin: 0 })}>
21
24
  {children}
22
25
  <script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
23
26
  </body>
24
27
  </html>
25
28
  )
26
29
  }
30
+
31
+ function readAppDisplayName(value: string): string {
32
+ return value.startsWith('%%') ? 'Remix App' : decodeURIComponent(value)
33
+ }
@@ -1,31 +1,16 @@
1
- // Delete this file and put your own home page in app/controllers/home.tsx
1
+ // Delete this file and put your own home page in app/actions/controller.tsx
2
2
  import { css, type RemixNode } from 'remix/ui'
3
3
 
4
- import { PromptButton } from './prompt-button.tsx'
5
- import { routes } from '../routes.ts'
6
-
7
- const APP_DISPLAY_NAME = decodeURIComponent('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
4
+ import { PromptButton } from '../assets/prompt-button.tsx'
5
+ import { Document } from './document.tsx'
8
6
 
9
7
  const FONT_STACK =
10
8
  "'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace"
11
9
 
12
10
  export function HomePage() {
13
11
  return () => (
14
- <html lang="en">
15
- <head>
16
- <meta charSet="utf-8" />
17
- <meta name="viewport" content="width=device-width,initial-scale=1" />
18
- <meta name="color-scheme" content="light dark" />
19
- <title>Welcome to {APP_DISPLAY_NAME}</title>
20
- <link rel="preconnect" href="https://fonts.googleapis.com" />
21
- <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
22
- <link
23
- rel="stylesheet"
24
- href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap"
25
- />
26
- <script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
27
- </head>
28
- <body
12
+ <Document head={<HomeHead />}>
13
+ <main
29
14
  mix={css({
30
15
  // Light-mode design tokens (default).
31
16
  '--surface-0': '#dee2e6',
@@ -58,7 +43,7 @@ export function HomePage() {
58
43
  justifyContent: 'center',
59
44
  })}
60
45
  >
61
- <main
46
+ <div
62
47
  mix={css({
63
48
  width: '100%',
64
49
  maxWidth: '820px',
@@ -71,9 +56,23 @@ export function HomePage() {
71
56
  <Masthead />
72
57
  <Columns />
73
58
  <Footer />
74
- </main>
75
- </body>
76
- </html>
59
+ </div>
60
+ </main>
61
+ </Document>
62
+ )
63
+ }
64
+
65
+ function HomeHead() {
66
+ return () => (
67
+ <>
68
+ <meta name="color-scheme" content="light dark" />
69
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
70
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
71
+ <link
72
+ rel="stylesheet"
73
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap"
74
+ />
75
+ </>
77
76
  )
78
77
  }
79
78
 
@@ -133,8 +132,8 @@ function Columns() {
133
132
 
134
133
  function GetStartedCard() {
135
134
  return () => (
136
- <div mix={css({ ...CARD_STYLES, flex: '0 0 auto' })}>
137
- <h2 mix={css(CARD_HEADER_STYLES)}>Get started</h2>
135
+ <div mix={[cardStyle, css({ flex: '0 0 auto' })]}>
136
+ <h2 mix={cardHeaderStyle}>Get started</h2>
138
137
  <ul
139
138
  mix={css({
140
139
  listStyle: 'none',
@@ -147,7 +146,7 @@ function GetStartedCard() {
147
146
  })}
148
147
  >
149
148
  <li>
150
- <CardLink href="https://remix.run/docs" icon={<AtomIcon />} label="Remix Docs" />
149
+ <CardLink href="https://api.remix.run" icon={<AtomIcon />} label="Remix API" />
151
150
  </li>
152
151
  <li>
153
152
  <CardLink
@@ -164,14 +163,16 @@ function GetStartedCard() {
164
163
  function CodingWithAiCard() {
165
164
  return () => (
166
165
  <div
167
- mix={css({
168
- ...CARD_STYLES,
169
- width: '540px',
170
- justifyContent: 'center',
171
- '@media (max-width: 720px)': { width: '100%' },
172
- })}
166
+ mix={[
167
+ cardStyle,
168
+ css({
169
+ width: '540px',
170
+ justifyContent: 'center',
171
+ '@media (max-width: 720px)': { width: '100%' },
172
+ }),
173
+ ]}
173
174
  >
174
- <h2 mix={css(CARD_HEADER_STYLES)}>Coding with AI?</h2>
175
+ <h2 mix={cardHeaderStyle}>Coding with AI?</h2>
175
176
  <div
176
177
  mix={css({
177
178
  width: '100%',
@@ -332,7 +333,7 @@ function Footer() {
332
333
  )
333
334
  }
334
335
 
335
- const CARD_STYLES = {
336
+ const cardStyle = css({
336
337
  background: 'var(--surface-3)',
337
338
  borderRadius: '20px',
338
339
  padding: '32px 16px 16px',
@@ -340,9 +341,9 @@ const CARD_STYLES = {
340
341
  flexDirection: 'column',
341
342
  alignItems: 'center',
342
343
  gap: '32px',
343
- } as const
344
+ })
344
345
 
345
- const CARD_HEADER_STYLES = {
346
+ const cardHeaderStyle = css({
346
347
  margin: 0,
347
348
  width: '100%',
348
349
  padding: '0 16px',
@@ -354,7 +355,7 @@ const CARD_HEADER_STYLES = {
354
355
  textTransform: 'uppercase',
355
356
  letterSpacing: '0.1em',
356
357
  color: 'var(--text-primary)',
357
- } as const
358
+ })
358
359
 
359
360
  // ----- SVG icons -----
360
361
  // Inline so the page is fully self-contained with no external icon assets.
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ db/*.sqlite
3
+ tmp/
4
+ .DS_Store
@@ -1,19 +1,18 @@
1
1
  {
2
- "name": "remix-bootstrap-template",
2
+ "name": "remix-template",
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
6
- "dev": "tsx watch server.ts",
7
- "start": "tsx server.ts",
8
- "test": "tsx --test",
6
+ "dev": "node --watch --import remix/node-tsx server.ts",
7
+ "start": "node --import remix/node-tsx server.ts",
8
+ "test": "node --import remix/node-tsx --test",
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "engines": {
12
12
  "node": ">=24.3.0"
13
13
  },
14
14
  "dependencies": {
15
- "remix": "workspace:^",
16
- "tsx": "catalog:"
15
+ "remix": "workspace:^"
17
16
  },
18
17
  "devDependencies": {
19
18
  "@types/node": "catalog:",
@@ -0,0 +1,11 @@
1
+ <svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="144" height="144" rx="72" fill="black"/>
3
+ <g clip-path="url(#clip0_2008_2428)">
4
+ <path d="M102.763 51.2382L102.762 51.2391V51.2401C113.767 51.2402 121.369 56.1787 119.744 62.2714L118.643 66.3973C117.017 72.4901 106.778 77.4286 95.7734 77.4286H94.6475L117.765 92.5868H80.6504L61.9629 78.163C61.2148 77.6833 60.3465 77.4286 59.459 77.4286H28.2197L31.1631 66.3954H85.8799C87.9367 66.3954 89.8519 65.4721 90.1562 64.3329H90.1572C90.4613 63.1935 89.0394 62.2694 86.9814 62.2694H32.2646L35.207 51.2382H102.763ZM54.1865 81.2362C55.2231 81.2363 55.9785 82.2245 55.71 83.2303L53.2139 92.5858H24.1768L27.2041 81.2362H54.1865Z" fill="white"/>
5
+ </g>
6
+ <defs>
7
+ <clipPath id="clip0_2008_2428">
8
+ <rect width="96" height="41.5862" fill="white" transform="translate(24 51.2069)"/>
9
+ </clipPath>
10
+ </defs>
11
+ </svg>
@@ -0,0 +1,38 @@
1
+ import * as http from 'node:http'
2
+ import { createRequestListener } from 'remix/node-fetch-server'
3
+
4
+ import { router } from './app/router.ts'
5
+
6
+ const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 44100
7
+
8
+ const server = http.createServer(
9
+ createRequestListener(async (request) => {
10
+ try {
11
+ return await router.fetch(request)
12
+ } catch (error) {
13
+ if (!(request.signal.aborted && error === request.signal.reason)) {
14
+ console.error(error)
15
+ }
16
+ return new Response('Internal Server Error', { status: 500 })
17
+ }
18
+ }),
19
+ )
20
+
21
+ server.listen(port, () => {
22
+ console.log(`Server listening on http://localhost:${port}`)
23
+ })
24
+
25
+ let shuttingDown = false
26
+
27
+ function shutdown() {
28
+ if (shuttingDown) {
29
+ return
30
+ }
31
+
32
+ shuttingDown = true
33
+ server.close(() => process.exit(0))
34
+ server.closeAllConnections()
35
+ }
36
+
37
+ process.on('SIGINT', shutdown)
38
+ process.on('SIGTERM', shutdown)
@@ -3,12 +3,13 @@
3
3
  "strict": true,
4
4
  "lib": ["ES2024", "DOM", "DOM.Iterable"],
5
5
  "types": ["node"],
6
- "module": "ES2022",
7
- "moduleResolution": "Bundler",
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
8
  "target": "ESNext",
9
9
  "allowImportingTsExtensions": true,
10
10
  "rewriteRelativeImportExtensions": true,
11
11
  "verbatimModuleSyntax": true,
12
+ "isolatedModules": true,
12
13
  "skipLibCheck": true,
13
14
  "jsx": "react-jsx",
14
15
  "jsxImportSource": "remix/ui",
@@ -1,27 +0,0 @@
1
- # %%RMX_APP_DISPLAY_NAME%%
2
-
3
- A minimal Remix application starter with a home page and an auth page.
4
-
5
- ## Starter Shape
6
-
7
- - `app/controllers/home.tsx` owns the home page.
8
- - `app/controllers/auth.tsx` owns the auth page.
9
- - `app/routes.ts` defines the route contract.
10
- - `app/router.ts` wires routes to handlers.
11
- - `app/ui/` holds the shared document and layout wrappers.
12
- - `app/utils/render.tsx` centralizes HTML response rendering.
13
-
14
- ## Growing The App
15
-
16
- - Start with flat route files and only introduce route folders when a route needs multiple actions or route-owned modules.
17
- - Add directories like `app/data/`, `app/middleware/`, `public/`, or `test/` when the app actually needs them.
18
- - Move shared UI into `app/ui/` once more than one route needs it.
19
-
20
- ## Commands
21
-
22
- ```sh
23
- npm i
24
- npm run start
25
- npm test
26
- npm run typecheck
27
- ```
@@ -1,19 +0,0 @@
1
- import { run } from 'remix/ui'
2
-
3
- run({
4
- async loadModule(moduleUrl, exportName) {
5
- let mod = await import(moduleUrl)
6
- return mod[exportName]
7
- },
8
- async resolveFrame(src, signal, target) {
9
- let headers = new Headers({ accept: 'text/html' })
10
- if (target) headers.set('x-remix-target', target)
11
-
12
- let response = await fetch(src, {
13
- credentials: 'same-origin',
14
- headers,
15
- signal,
16
- })
17
- return response.body ?? response.text()
18
- },
19
- })
@@ -1,21 +0,0 @@
1
- import type { BuildAction } from 'remix/fetch-router'
2
-
3
- import type { routes } from '../routes.ts'
4
- import { Layout } from '../ui/layout.tsx'
5
- import { render } from '../utils/render.tsx'
6
-
7
- export const auth: BuildAction<'GET', typeof routes.auth> = {
8
- handler({ request }) {
9
- return render(<AuthPage />, request)
10
- },
11
- }
12
-
13
- function AuthPage() {
14
- return () => (
15
- <Layout title="Auth">
16
- <h1>Auth</h1>
17
- <p>Use this route to start building sign-in, sign-up, and session flows.</p>
18
- <p>Keep it as a flat file until the route needs multiple actions or route-owned modules.</p>
19
- </Layout>
20
- )
21
- }
@@ -1,11 +0,0 @@
1
- import type { BuildAction } from 'remix/fetch-router'
2
-
3
- import type { routes } from '../routes.ts'
4
- import { HomePage } from '../ui/scaffold-home-page.tsx'
5
- import { render } from '../utils/render.tsx'
6
-
7
- export const home: BuildAction<'GET', typeof routes.home> = {
8
- handler({ request }) {
9
- return render(<HomePage />, request)
10
- },
11
- }
@@ -1,16 +0,0 @@
1
- import { createRouter } from 'remix/fetch-router'
2
-
3
- import { assets } from './assets.ts'
4
- import { auth } from './controllers/auth.tsx'
5
- import { home } from './controllers/home.tsx'
6
- import { routes } from './routes.ts'
7
-
8
- export const router = createRouter()
9
-
10
- router.get(routes.assets, async ({ request }) => {
11
- let response = await assets.fetch(request)
12
- return response ?? new Response('Not Found', { status: 404 })
13
- })
14
-
15
- router.map(routes.home, home)
16
- router.map(routes.auth, auth)
@@ -1,22 +0,0 @@
1
- import type { RemixNode } from 'remix/ui'
2
-
3
- import { routes } from '../routes.ts'
4
- import { Document } from './document.tsx'
5
-
6
- export interface LayoutProps {
7
- children?: RemixNode
8
- title?: string
9
- }
10
-
11
- export function Layout() {
12
- return ({ title, children }: LayoutProps) => (
13
- <Document title={title}>
14
- <header>
15
- <nav>
16
- <a href={routes.home.href()}>Home</a> <a href={routes.auth.href()}>Auth</a>
17
- </nav>
18
- </header>
19
- <main>{children}</main>
20
- </Document>
21
- )
22
- }