@remix-run/cli 0.1.0 → 0.3.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/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 +33 -14
- 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/help.d.ts.map +1 -1
- package/dist/lib/commands/help.js +9 -33
- package/dist/lib/commands/routes.js +3 -3
- 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 +4 -106
- 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/errors.d.ts +0 -6
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +0 -11
- 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 +4 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +46 -15
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/help.ts +9 -43
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/commands/test.ts +10 -4
- package/src/lib/completion.ts +4 -151
- 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/errors.ts +0 -12
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/template/.agents/skills/remix/SKILL.md +588 -0
- package/template/.agents/skills/remix/references/animate-elements.md +195 -0
- package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
- package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
- package/template/.agents/skills/remix/references/component-model.md +282 -0
- package/template/.agents/skills/remix/references/create-mixins.md +158 -0
- package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
- package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
- package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
- package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
- package/{bootstrap → template}/AGENTS.md +13 -8
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/template/app/assets/prompt-button.tsx +163 -0
- package/template/app/assets.ts +20 -0
- package/template/app/middleware/render.tsx +43 -0
- package/template/app/router.ts +20 -0
- package/template/app/routes.ts +6 -0
- package/template/app/ui/document.tsx +33 -0
- package/template/app/ui/scaffold-home-page.tsx +527 -0
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/{bootstrap → template}/server.ts +4 -5
- package/{bootstrap → template}/tsconfig.json +3 -3
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -26
- package/bootstrap/app/router.ts +0 -10
- package/bootstrap/app/routes.ts +0 -6
- package/bootstrap/app/ui/document.tsx +0 -21
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -7
- 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,163 @@
|
|
|
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
|
+
// This component hydrates independently; the rest of the page stays static HTML.
|
|
13
|
+
export const PromptButton = clientEntry(
|
|
14
|
+
import.meta.url,
|
|
15
|
+
function PromptButton(handle: Handle<PromptButtonProps>) {
|
|
16
|
+
let state: CopyState = 'idle'
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
let promptLabel = `\u201C${handle.props.text}\u201D`
|
|
20
|
+
let label =
|
|
21
|
+
state === 'copied' || state === 'resetting'
|
|
22
|
+
? 'Copied to clipboard'
|
|
23
|
+
: state === 'failed'
|
|
24
|
+
? 'Copy failed'
|
|
25
|
+
: promptLabel
|
|
26
|
+
let active = state === 'copied' || state === 'failed' || state === 'resetting'
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
className={state}
|
|
32
|
+
style={{
|
|
33
|
+
background: active ? 'var(--surface-4)' : undefined,
|
|
34
|
+
color: active ? 'var(--brand-blue)' : undefined,
|
|
35
|
+
}}
|
|
36
|
+
mix={[
|
|
37
|
+
buttonStyle,
|
|
38
|
+
on('click', async (_event, signal) => {
|
|
39
|
+
try {
|
|
40
|
+
await navigator.clipboard.writeText(handle.props.text)
|
|
41
|
+
if (signal.aborted) return
|
|
42
|
+
} catch {
|
|
43
|
+
state = 'failed'
|
|
44
|
+
await handle.update()
|
|
45
|
+
await wait(HOLD_MS)
|
|
46
|
+
if (signal.aborted) return
|
|
47
|
+
state = 'resetting'
|
|
48
|
+
await handle.update()
|
|
49
|
+
await wait(FADE_MS)
|
|
50
|
+
if (signal.aborted) return
|
|
51
|
+
state = 'idle'
|
|
52
|
+
handle.update()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
state = 'copied'
|
|
57
|
+
await handle.update()
|
|
58
|
+
await wait(HOLD_MS)
|
|
59
|
+
if (signal.aborted) return
|
|
60
|
+
|
|
61
|
+
state = 'resetting'
|
|
62
|
+
await handle.update()
|
|
63
|
+
await wait(FADE_MS)
|
|
64
|
+
if (signal.aborted) return
|
|
65
|
+
|
|
66
|
+
state = 'idle'
|
|
67
|
+
await handle.update()
|
|
68
|
+
}),
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<span aria-hidden="true" mix={iconSlotStyle}>
|
|
72
|
+
<CopyIcon />
|
|
73
|
+
</span>
|
|
74
|
+
<span
|
|
75
|
+
mix={css({
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
display: 'flex',
|
|
78
|
+
fontSize: '14px',
|
|
79
|
+
flex: '1 1 0',
|
|
80
|
+
lineHeight: 1.5,
|
|
81
|
+
minWidth: 0,
|
|
82
|
+
position: 'relative',
|
|
83
|
+
transition: 'opacity 180ms ease',
|
|
84
|
+
})}
|
|
85
|
+
style={{ opacity: state === 'resetting' ? 0 : undefined }}
|
|
86
|
+
>
|
|
87
|
+
<span
|
|
88
|
+
aria-hidden={state === 'idle' ? true : undefined}
|
|
89
|
+
style={{
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
display: 'flex',
|
|
92
|
+
inset: 0,
|
|
93
|
+
position: 'absolute',
|
|
94
|
+
visibility: state === 'idle' ? 'hidden' : 'visible',
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{label}
|
|
98
|
+
</span>
|
|
99
|
+
<span
|
|
100
|
+
aria-hidden={state === 'idle' ? undefined : true}
|
|
101
|
+
style={{ visibility: state === 'idle' ? 'visible' : 'hidden' }}
|
|
102
|
+
>
|
|
103
|
+
{promptLabel}
|
|
104
|
+
</span>
|
|
105
|
+
</span>
|
|
106
|
+
</button>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
export function CopyIcon() {
|
|
113
|
+
return () => (
|
|
114
|
+
<svg viewBox="0 0 14 16.5" fill="none">
|
|
115
|
+
<path
|
|
116
|
+
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"
|
|
117
|
+
stroke="currentColor"
|
|
118
|
+
stroke-width="1.5"
|
|
119
|
+
stroke-linecap="round"
|
|
120
|
+
/>
|
|
121
|
+
</svg>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function wait(ms: number) {
|
|
126
|
+
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const buttonStyle = css({
|
|
130
|
+
appearance: 'none',
|
|
131
|
+
font: 'inherit',
|
|
132
|
+
textAlign: 'left',
|
|
133
|
+
cursor: 'pointer',
|
|
134
|
+
width: '100%',
|
|
135
|
+
display: 'flex',
|
|
136
|
+
gap: '16px',
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
padding: '16px',
|
|
139
|
+
border: 0,
|
|
140
|
+
borderRadius: '12px',
|
|
141
|
+
color: 'var(--text-primary)',
|
|
142
|
+
background: 'transparent',
|
|
143
|
+
transition: 'background-color 150ms ease, color 150ms ease',
|
|
144
|
+
'&:hover, &:focus-visible': {
|
|
145
|
+
background: 'var(--surface-4)',
|
|
146
|
+
color: 'var(--brand-blue)',
|
|
147
|
+
outline: 'none',
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const iconSlotStyle = css({
|
|
152
|
+
flex: '0 0 24px',
|
|
153
|
+
width: '24px',
|
|
154
|
+
display: 'flex',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
'& svg': {
|
|
158
|
+
width: '20px',
|
|
159
|
+
height: '20px',
|
|
160
|
+
display: 'block',
|
|
161
|
+
transform: 'rotate(180deg)',
|
|
162
|
+
},
|
|
163
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createAssetServer } from 'remix/assets'
|
|
2
|
+
|
|
3
|
+
const rootDir = process.cwd()
|
|
4
|
+
|
|
5
|
+
export const assetServer = createAssetServer({
|
|
6
|
+
basePath: '/assets',
|
|
7
|
+
rootDir,
|
|
8
|
+
fileMap: {
|
|
9
|
+
'app/*path': 'app/*path',
|
|
10
|
+
'node_modules/*path': 'node_modules/*path',
|
|
11
|
+
},
|
|
12
|
+
allow: ['app/assets/**', 'node_modules/**'],
|
|
13
|
+
deny: ['app/**/*.server.*'],
|
|
14
|
+
sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
|
|
15
|
+
scripts: {
|
|
16
|
+
define: {
|
|
17
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'development'),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
() =>
|
|
13
|
+
function render(node: RemixNode, init?: ResponseInit) {
|
|
14
|
+
let stream = renderToStream(node, {
|
|
15
|
+
// Server rendering turns client entries into browser module URLs.
|
|
16
|
+
async resolveClientEntry(entryId, component) {
|
|
17
|
+
if (!entryId.startsWith('file://')) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Expected \`import.meta.url\` for clientEntry ID, received '${entryId}'`,
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
href: await assetServer.getHref(entryId),
|
|
25
|
+
exportName: entryId.split('#')[1] || component.name || titleCaseFileName(entryId),
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return createHtmlResponse(stream, init)
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function titleCaseFileName(fileUrl: string): string {
|
|
36
|
+
let url = new URL(fileUrl)
|
|
37
|
+
let fileName = path.basename(url.pathname, path.extname(url.pathname))
|
|
38
|
+
return fileName
|
|
39
|
+
.split(/[^A-Za-z0-9]+/)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.map((segment) => segment[0]!.toUpperCase() + segment.slice(1))
|
|
42
|
+
.join('')
|
|
43
|
+
}
|
|
@@ -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)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { css, type RemixNode } from 'remix/ui'
|
|
2
|
+
|
|
3
|
+
import { routes } from '../routes.ts'
|
|
4
|
+
|
|
5
|
+
export interface DocumentProps {
|
|
6
|
+
children?: RemixNode
|
|
7
|
+
head?: RemixNode
|
|
8
|
+
title?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TITLE = readAppDisplayName('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
|
|
12
|
+
|
|
13
|
+
export function Document() {
|
|
14
|
+
return ({ children, head, title = DEFAULT_TITLE }: DocumentProps) => (
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charSet="utf-8" />
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
20
|
+
<title>{title}</title>
|
|
21
|
+
{head}
|
|
22
|
+
</head>
|
|
23
|
+
<body mix={css({ margin: 0 })}>
|
|
24
|
+
{children}
|
|
25
|
+
<script type="module" src={routes.assets.href({ path: 'app/assets/entry.ts' })}></script>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readAppDisplayName(value: string): string {
|
|
32
|
+
return value.startsWith('%%') ? 'Remix App' : decodeURIComponent(value)
|
|
33
|
+
}
|