@remix-run/cli 0.1.0 → 0.2.0
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/README.md +0 -3
- package/bootstrap/.agents/skills/remix/SKILL.md +501 -0
- package/bootstrap/.agents/skills/remix/references/animate-elements.md +195 -0
- package/bootstrap/.agents/skills/remix/references/assets-and-browser-modules.md +122 -0
- package/bootstrap/.agents/skills/remix/references/auth-and-sessions.md +420 -0
- package/bootstrap/.agents/skills/remix/references/component-model.md +282 -0
- package/bootstrap/.agents/skills/remix/references/create-mixins.md +158 -0
- package/bootstrap/.agents/skills/remix/references/data-and-validation.md +363 -0
- package/bootstrap/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/bootstrap/.agents/skills/remix/references/middleware-and-server.md +243 -0
- package/bootstrap/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/bootstrap/.agents/skills/remix/references/routing-and-controllers.md +324 -0
- package/bootstrap/.agents/skills/remix/references/testing-patterns.md +156 -0
- package/bootstrap/AGENTS.md +4 -0
- package/bootstrap/app/assets/entry.ts +19 -0
- package/bootstrap/app/assets.ts +18 -0
- package/bootstrap/app/controllers/auth.tsx +2 -2
- package/bootstrap/app/controllers/home.tsx +3 -18
- package/bootstrap/app/router.ts +6 -0
- package/bootstrap/app/routes.ts +2 -1
- package/bootstrap/app/ui/document.tsx +6 -1
- package/bootstrap/app/ui/prompt-button.tsx +162 -0
- package/bootstrap/app/ui/scaffold-home-page.tsx +526 -0
- package/bootstrap/app/utils/render.tsx +22 -3
- package/bootstrap/server.ts +13 -13
- package/bootstrap/tsconfig.json +0 -1
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +7 -10
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/help.js +9 -33
- package/dist/lib/commands/test.d.ts +1 -1
- package/dist/lib/commands/test.d.ts.map +1 -1
- package/dist/lib/commands/test.js +8 -4
- package/dist/lib/completion.d.ts.map +1 -1
- package/dist/lib/completion.js +3 -101
- package/dist/lib/errors.d.ts +0 -6
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +0 -11
- package/package.json +3 -4
- package/src/lib/cli.ts +7 -11
- package/src/lib/commands/help.ts +9 -43
- package/src/lib/commands/test.ts +10 -4
- package/src/lib/completion.ts +3 -146
- package/src/lib/errors.ts +0 -12
- package/dist/lib/commands/skills.d.ts +0 -6
- package/dist/lib/commands/skills.d.ts.map +0 -1
- package/dist/lib/commands/skills.js +0 -222
- package/dist/lib/skills-cache.d.ts +0 -19
- package/dist/lib/skills-cache.d.ts.map +0 -1
- package/dist/lib/skills-cache.js +0 -89
- package/dist/lib/skills.d.ts +0 -30
- package/dist/lib/skills.d.ts.map +0 -1
- package/dist/lib/skills.js +0 -441
- package/src/lib/commands/skills.ts +0 -306
- package/src/lib/skills-cache.ts +0 -140
- package/src/lib/skills.ts +0 -706
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createAssetServer } from 'remix/assets'
|
|
2
|
+
|
|
3
|
+
export const assets = createAssetServer({
|
|
4
|
+
basePath: '/assets',
|
|
5
|
+
rootDir: process.cwd(),
|
|
6
|
+
fileMap: {
|
|
7
|
+
'app/*path': 'app/*path',
|
|
8
|
+
'node_modules/*path': 'node_modules/*path',
|
|
9
|
+
},
|
|
10
|
+
allow: ['app/assets/**', 'app/ui/prompt-button.tsx', 'node_modules/**'],
|
|
11
|
+
deny: ['app/**/*.server.*'],
|
|
12
|
+
sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
|
|
13
|
+
scripts: {
|
|
14
|
+
define: {
|
|
15
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'development'),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -5,8 +5,8 @@ import { Layout } from '../ui/layout.tsx'
|
|
|
5
5
|
import { render } from '../utils/render.tsx'
|
|
6
6
|
|
|
7
7
|
export const auth: BuildAction<'GET', typeof routes.auth> = {
|
|
8
|
-
handler() {
|
|
9
|
-
return render(<AuthPage
|
|
8
|
+
handler({ request }) {
|
|
9
|
+
return render(<AuthPage />, request)
|
|
10
10
|
},
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import type { BuildAction } from 'remix/fetch-router'
|
|
2
2
|
|
|
3
3
|
import type { routes } from '../routes.ts'
|
|
4
|
-
import {
|
|
4
|
+
import { HomePage } from '../ui/scaffold-home-page.tsx'
|
|
5
5
|
import { render } from '../utils/render.tsx'
|
|
6
6
|
|
|
7
|
-
const APP_DISPLAY_NAME = decodeURIComponent('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
|
|
8
|
-
|
|
9
7
|
export const home: BuildAction<'GET', typeof routes.home> = {
|
|
10
|
-
handler() {
|
|
11
|
-
return render(<HomePage
|
|
8
|
+
handler({ request }) {
|
|
9
|
+
return render(<HomePage />, request)
|
|
12
10
|
},
|
|
13
11
|
}
|
|
14
|
-
|
|
15
|
-
function HomePage() {
|
|
16
|
-
return () => (
|
|
17
|
-
<Layout title="Home">
|
|
18
|
-
<h1>{APP_DISPLAY_NAME}</h1>
|
|
19
|
-
<p>This starter begins with two flat route files so you can start shipping immediately.</p>
|
|
20
|
-
<p>
|
|
21
|
-
Add new routes in <code>app/routes.ts</code>, keep them flat at first, and only split them
|
|
22
|
-
into folders when the route grows.
|
|
23
|
-
</p>
|
|
24
|
-
</Layout>
|
|
25
|
-
)
|
|
26
|
-
}
|
package/bootstrap/app/router.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { createRouter } from 'remix/fetch-router'
|
|
2
2
|
|
|
3
|
+
import { assets } from './assets.ts'
|
|
3
4
|
import { auth } from './controllers/auth.tsx'
|
|
4
5
|
import { home } from './controllers/home.tsx'
|
|
5
6
|
import { routes } from './routes.ts'
|
|
6
7
|
|
|
7
8
|
export const router = createRouter()
|
|
8
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
|
+
|
|
9
15
|
router.map(routes.home, home)
|
|
10
16
|
router.map(routes.auth, auth)
|
package/bootstrap/app/routes.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { RemixNode } from 'remix/ui'
|
|
2
2
|
|
|
3
|
+
import { routes } from '../routes.ts'
|
|
4
|
+
|
|
3
5
|
export interface DocumentProps {
|
|
4
6
|
children?: RemixNode
|
|
5
7
|
title?: string
|
|
@@ -15,7 +17,10 @@ export function Document() {
|
|
|
15
17
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
16
18
|
<title>{title}</title>
|
|
17
19
|
</head>
|
|
18
|
-
<body>
|
|
20
|
+
<body>
|
|
21
|
+
{children}
|
|
22
|
+
<script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
|
|
23
|
+
</body>
|
|
19
24
|
</html>
|
|
20
25
|
)
|
|
21
26
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { clientEntry, css, on, type Handle, type SerializableProps } from 'remix/ui'
|
|
2
|
+
|
|
3
|
+
const FADE_MS = 180
|
|
4
|
+
const HOLD_MS = 1200
|
|
5
|
+
|
|
6
|
+
type CopyState = 'idle' | 'copied' | 'failed' | 'resetting'
|
|
7
|
+
|
|
8
|
+
interface PromptButtonProps extends SerializableProps {
|
|
9
|
+
text: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const PromptButton = clientEntry(
|
|
13
|
+
'/assets/app/ui/prompt-button.tsx#PromptButton',
|
|
14
|
+
function PromptButton(handle: Handle<PromptButtonProps>) {
|
|
15
|
+
let state: CopyState = 'idle'
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
let promptLabel = `\u201C${handle.props.text}\u201D`
|
|
19
|
+
let label =
|
|
20
|
+
state === 'copied' || state === 'resetting'
|
|
21
|
+
? 'Copied to clipboard'
|
|
22
|
+
: state === 'failed'
|
|
23
|
+
? 'Copy failed'
|
|
24
|
+
: promptLabel
|
|
25
|
+
let active = state === 'copied' || state === 'failed' || state === 'resetting'
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
className={state}
|
|
31
|
+
style={{
|
|
32
|
+
background: active ? 'var(--surface-4)' : undefined,
|
|
33
|
+
color: active ? 'var(--brand-blue)' : undefined,
|
|
34
|
+
}}
|
|
35
|
+
mix={[
|
|
36
|
+
buttonStyle,
|
|
37
|
+
on('click', async (_event, signal) => {
|
|
38
|
+
try {
|
|
39
|
+
await navigator.clipboard.writeText(handle.props.text)
|
|
40
|
+
if (signal.aborted) return
|
|
41
|
+
} catch {
|
|
42
|
+
state = 'failed'
|
|
43
|
+
await handle.update()
|
|
44
|
+
await wait(HOLD_MS)
|
|
45
|
+
if (signal.aborted) return
|
|
46
|
+
state = 'resetting'
|
|
47
|
+
await handle.update()
|
|
48
|
+
await wait(FADE_MS)
|
|
49
|
+
if (signal.aborted) return
|
|
50
|
+
state = 'idle'
|
|
51
|
+
handle.update()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
state = 'copied'
|
|
56
|
+
await handle.update()
|
|
57
|
+
await wait(HOLD_MS)
|
|
58
|
+
if (signal.aborted) return
|
|
59
|
+
|
|
60
|
+
state = 'resetting'
|
|
61
|
+
await handle.update()
|
|
62
|
+
await wait(FADE_MS)
|
|
63
|
+
if (signal.aborted) return
|
|
64
|
+
|
|
65
|
+
state = 'idle'
|
|
66
|
+
await handle.update()
|
|
67
|
+
}),
|
|
68
|
+
]}
|
|
69
|
+
>
|
|
70
|
+
<span aria-hidden="true" mix={iconSlotStyle}>
|
|
71
|
+
<CopyIcon />
|
|
72
|
+
</span>
|
|
73
|
+
<span
|
|
74
|
+
mix={css({
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
display: 'flex',
|
|
77
|
+
fontSize: '14px',
|
|
78
|
+
flex: '1 1 0',
|
|
79
|
+
lineHeight: 1.5,
|
|
80
|
+
minWidth: 0,
|
|
81
|
+
position: 'relative',
|
|
82
|
+
transition: 'opacity 180ms ease',
|
|
83
|
+
})}
|
|
84
|
+
style={{ opacity: state === 'resetting' ? 0 : undefined }}
|
|
85
|
+
>
|
|
86
|
+
<span
|
|
87
|
+
aria-hidden={state === 'idle' ? true : undefined}
|
|
88
|
+
style={{
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
display: 'flex',
|
|
91
|
+
inset: 0,
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
visibility: state === 'idle' ? 'hidden' : 'visible',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{label}
|
|
97
|
+
</span>
|
|
98
|
+
<span
|
|
99
|
+
aria-hidden={state === 'idle' ? undefined : true}
|
|
100
|
+
style={{ visibility: state === 'idle' ? 'visible' : 'hidden' }}
|
|
101
|
+
>
|
|
102
|
+
{promptLabel}
|
|
103
|
+
</span>
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
export function CopyIcon() {
|
|
112
|
+
return () => (
|
|
113
|
+
<svg viewBox="0 0 14 16.5" fill="none">
|
|
114
|
+
<path
|
|
115
|
+
d="M0.75 9.188L0.75 4.083C0.75 2.242 2.242 0.75 4.083 0.75L9.188 0.75M5.75 15.75L11.375 15.75C12.41 15.75 13.25 14.91 13.25 13.875L13.25 5.75C13.25 4.714 12.41 3.875 11.375 3.875L5.75 3.875C4.714 3.875 3.875 4.714 3.875 5.75L3.875 13.875C3.875 14.91 4.714 15.75 5.75 15.75Z"
|
|
116
|
+
stroke="currentColor"
|
|
117
|
+
stroke-width="1.5"
|
|
118
|
+
stroke-linecap="round"
|
|
119
|
+
/>
|
|
120
|
+
</svg>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function wait(ms: number) {
|
|
125
|
+
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const buttonStyle = css({
|
|
129
|
+
appearance: 'none',
|
|
130
|
+
font: 'inherit',
|
|
131
|
+
textAlign: 'left',
|
|
132
|
+
cursor: 'pointer',
|
|
133
|
+
width: '100%',
|
|
134
|
+
display: 'flex',
|
|
135
|
+
gap: '16px',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
padding: '16px',
|
|
138
|
+
border: 0,
|
|
139
|
+
borderRadius: '12px',
|
|
140
|
+
color: 'var(--text-primary)',
|
|
141
|
+
background: 'transparent',
|
|
142
|
+
transition: 'background-color 150ms ease, color 150ms ease',
|
|
143
|
+
'&:hover, &:focus-visible': {
|
|
144
|
+
background: 'var(--surface-4)',
|
|
145
|
+
color: 'var(--brand-blue)',
|
|
146
|
+
outline: 'none',
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const iconSlotStyle = css({
|
|
151
|
+
flex: '0 0 24px',
|
|
152
|
+
width: '24px',
|
|
153
|
+
display: 'flex',
|
|
154
|
+
alignItems: 'center',
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
'& svg': {
|
|
157
|
+
width: '20px',
|
|
158
|
+
height: '20px',
|
|
159
|
+
display: 'block',
|
|
160
|
+
transform: 'rotate(180deg)',
|
|
161
|
+
},
|
|
162
|
+
})
|