@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.
Files changed (56) hide show
  1. package/README.md +0 -3
  2. package/bootstrap/.agents/skills/remix/SKILL.md +501 -0
  3. package/bootstrap/.agents/skills/remix/references/animate-elements.md +195 -0
  4. package/bootstrap/.agents/skills/remix/references/assets-and-browser-modules.md +122 -0
  5. package/bootstrap/.agents/skills/remix/references/auth-and-sessions.md +420 -0
  6. package/bootstrap/.agents/skills/remix/references/component-model.md +282 -0
  7. package/bootstrap/.agents/skills/remix/references/create-mixins.md +158 -0
  8. package/bootstrap/.agents/skills/remix/references/data-and-validation.md +363 -0
  9. package/bootstrap/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  10. package/bootstrap/.agents/skills/remix/references/middleware-and-server.md +243 -0
  11. package/bootstrap/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  12. package/bootstrap/.agents/skills/remix/references/routing-and-controllers.md +324 -0
  13. package/bootstrap/.agents/skills/remix/references/testing-patterns.md +156 -0
  14. package/bootstrap/AGENTS.md +4 -0
  15. package/bootstrap/app/assets/entry.ts +19 -0
  16. package/bootstrap/app/assets.ts +18 -0
  17. package/bootstrap/app/controllers/auth.tsx +2 -2
  18. package/bootstrap/app/controllers/home.tsx +3 -18
  19. package/bootstrap/app/router.ts +6 -0
  20. package/bootstrap/app/routes.ts +2 -1
  21. package/bootstrap/app/ui/document.tsx +6 -1
  22. package/bootstrap/app/ui/prompt-button.tsx +162 -0
  23. package/bootstrap/app/ui/scaffold-home-page.tsx +526 -0
  24. package/bootstrap/app/utils/render.tsx +22 -3
  25. package/bootstrap/server.ts +13 -13
  26. package/bootstrap/tsconfig.json +0 -1
  27. package/dist/lib/cli.d.ts.map +1 -1
  28. package/dist/lib/cli.js +7 -10
  29. package/dist/lib/commands/help.d.ts.map +1 -1
  30. package/dist/lib/commands/help.js +9 -33
  31. package/dist/lib/commands/test.d.ts +1 -1
  32. package/dist/lib/commands/test.d.ts.map +1 -1
  33. package/dist/lib/commands/test.js +8 -4
  34. package/dist/lib/completion.d.ts.map +1 -1
  35. package/dist/lib/completion.js +3 -101
  36. package/dist/lib/errors.d.ts +0 -6
  37. package/dist/lib/errors.d.ts.map +1 -1
  38. package/dist/lib/errors.js +0 -11
  39. package/package.json +3 -4
  40. package/src/lib/cli.ts +7 -11
  41. package/src/lib/commands/help.ts +9 -43
  42. package/src/lib/commands/test.ts +10 -4
  43. package/src/lib/completion.ts +3 -146
  44. package/src/lib/errors.ts +0 -12
  45. package/dist/lib/commands/skills.d.ts +0 -6
  46. package/dist/lib/commands/skills.d.ts.map +0 -1
  47. package/dist/lib/commands/skills.js +0 -222
  48. package/dist/lib/skills-cache.d.ts +0 -19
  49. package/dist/lib/skills-cache.d.ts.map +0 -1
  50. package/dist/lib/skills-cache.js +0 -89
  51. package/dist/lib/skills.d.ts +0 -30
  52. package/dist/lib/skills.d.ts.map +0 -1
  53. package/dist/lib/skills.js +0 -441
  54. package/src/lib/commands/skills.ts +0 -306
  55. package/src/lib/skills-cache.ts +0 -140
  56. 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 { Layout } from '../ui/layout.tsx'
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
- }
@@ -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)
@@ -1,6 +1,7 @@
1
- import { route } from 'remix/fetch-router/routes'
1
+ import { get, route } from 'remix/fetch-router/routes'
2
2
 
3
3
  export const routes = route({
4
+ assets: get('/assets/*path'),
4
5
  home: '/',
5
6
  auth: '/auth',
6
7
  })
@@ -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>{children}</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
+ })