@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.
- package/dist/lib/bootstrap-project.d.ts.map +1 -1
- package/dist/lib/bootstrap-project.js +32 -9
- package/dist/lib/cli.d.ts +29 -0
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +26 -4
- package/dist/lib/commands/completion.d.ts.map +1 -1
- package/dist/lib/commands/completion.js +5 -1
- package/dist/lib/commands/doctor.js +18 -18
- package/dist/lib/commands/routes.js +3 -3
- package/dist/lib/completion.js +1 -5
- package/dist/lib/controller-files.d.ts +0 -1
- package/dist/lib/controller-files.d.ts.map +1 -1
- package/dist/lib/controller-files.js +3 -5
- package/dist/lib/controller-ownership.d.ts +9 -9
- package/dist/lib/controller-ownership.d.ts.map +1 -1
- package/dist/lib/controller-ownership.js +56 -91
- package/dist/lib/doctor/controller-findings.d.ts +1 -1
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
- package/dist/lib/doctor/controller-findings.js +15 -87
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
- package/dist/lib/doctor/controller-fix-plans.js +13 -24
- package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
- package/dist/lib/doctor/controller-placeholders.js +18 -149
- package/dist/lib/doctor/controllers.js +1 -1
- package/dist/lib/doctor/project.js +60 -52
- package/dist/lib/doctor/types.d.ts +2 -2
- package/dist/lib/doctor/types.d.ts.map +1 -1
- package/dist/lib/help-text.js +1 -1
- package/dist/lib/load-route-map-worker.js +17 -9
- package/dist/lib/route-map.d.ts +1 -1
- package/dist/lib/route-map.d.ts.map +1 -1
- package/dist/lib/route-map.js +29 -17
- package/package.json +5 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +39 -4
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/completion.ts +1 -5
- package/src/lib/controller-files.ts +4 -8
- package/src/lib/controller-ownership.ts +78 -141
- package/src/lib/doctor/controller-findings.ts +20 -97
- package/src/lib/doctor/controller-fix-plans.ts +13 -29
- package/src/lib/doctor/controller-placeholders.ts +17 -189
- package/src/lib/doctor/controllers.ts +1 -1
- package/src/lib/doctor/project.ts +60 -52
- package/src/lib/doctor/types.ts +1 -5
- package/src/lib/help-text.ts +3 -2
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/{bootstrap → template}/.agents/skills/remix/SKILL.md +162 -77
- package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
- package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
- package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +4 -1
- package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
- package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +42 -53
- package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
- package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
- package/{bootstrap → template}/AGENTS.md +10 -9
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
- package/{bootstrap → template}/app/assets.ts +5 -3
- package/template/app/middleware/render.tsx +44 -0
- package/template/app/router.ts +20 -0
- package/{bootstrap → template}/app/routes.ts +1 -2
- package/{bootstrap → template}/app/ui/document.tsx +11 -4
- package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/template/server.ts +38 -0
- package/{bootstrap → template}/tsconfig.json +3 -2
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/assets/entry.ts +0 -19
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -11
- package/bootstrap/app/router.ts +0 -16
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -26
- package/bootstrap/server.ts +0 -37
- /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
- /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
|
|
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
|
|
16
|
+
Refer to ./.agents/skills/remix/SKILL.md
|
|
17
17
|
|
|
18
18
|
## Starter Layout
|
|
19
19
|
|
|
20
|
-
- `app/
|
|
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/
|
|
25
|
-
- `app/
|
|
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
|
-
-
|
|
31
|
-
-
|
|
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/
|
|
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
|
|
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
|
+
})
|
|
@@ -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
|
-
|
|
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
|
-
|
|
3
|
+
const rootDir = process.cwd()
|
|
4
|
+
|
|
5
|
+
export const assetServer = createAssetServer({
|
|
4
6
|
basePath: '/assets',
|
|
5
|
-
rootDir
|
|
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/**', '
|
|
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,26 +1,33 @@
|
|
|
1
|
-
import type
|
|
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 =
|
|
11
|
+
const DEFAULT_TITLE = readAppDisplayName('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
|
|
11
12
|
|
|
12
13
|
export function Document() {
|
|
13
|
-
return ({ title = DEFAULT_TITLE
|
|
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/
|
|
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 '
|
|
5
|
-
import {
|
|
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
|
-
<
|
|
15
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
75
|
-
</
|
|
76
|
-
</
|
|
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({
|
|
137
|
-
<h2 mix={
|
|
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
|
|
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={
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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={
|
|
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
|
|
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
|
-
}
|
|
344
|
+
})
|
|
344
345
|
|
|
345
|
-
const
|
|
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
|
-
}
|
|
358
|
+
})
|
|
358
359
|
|
|
359
360
|
// ----- SVG icons -----
|
|
360
361
|
// Inline so the page is fully self-contained with no external icon assets.
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "remix-
|
|
2
|
+
"name": "remix-template",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "
|
|
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": "
|
|
7
|
-
"moduleResolution": "
|
|
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",
|
package/bootstrap/README.md
DELETED
|
@@ -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
|
-
}
|
package/bootstrap/app/router.ts
DELETED
|
@@ -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
|
-
}
|