@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.
Files changed (110) hide show
  1. package/README.md +0 -3
  2. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  3. package/dist/lib/bootstrap-project.js +32 -9
  4. package/dist/lib/cli.d.ts +29 -0
  5. package/dist/lib/cli.d.ts.map +1 -1
  6. package/dist/lib/cli.js +33 -14
  7. package/dist/lib/commands/completion.d.ts.map +1 -1
  8. package/dist/lib/commands/completion.js +5 -1
  9. package/dist/lib/commands/doctor.js +18 -18
  10. package/dist/lib/commands/help.d.ts.map +1 -1
  11. package/dist/lib/commands/help.js +9 -33
  12. package/dist/lib/commands/routes.js +3 -3
  13. package/dist/lib/commands/test.d.ts +1 -1
  14. package/dist/lib/commands/test.d.ts.map +1 -1
  15. package/dist/lib/commands/test.js +8 -4
  16. package/dist/lib/completion.d.ts.map +1 -1
  17. package/dist/lib/completion.js +4 -106
  18. package/dist/lib/controller-files.d.ts +0 -1
  19. package/dist/lib/controller-files.d.ts.map +1 -1
  20. package/dist/lib/controller-files.js +3 -5
  21. package/dist/lib/controller-ownership.d.ts +9 -9
  22. package/dist/lib/controller-ownership.d.ts.map +1 -1
  23. package/dist/lib/controller-ownership.js +56 -91
  24. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  25. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  26. package/dist/lib/doctor/controller-findings.js +15 -87
  27. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  28. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  29. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  30. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  31. package/dist/lib/doctor/controller-placeholders.js +18 -149
  32. package/dist/lib/doctor/controllers.js +1 -1
  33. package/dist/lib/doctor/project.js +60 -52
  34. package/dist/lib/doctor/types.d.ts +2 -2
  35. package/dist/lib/doctor/types.d.ts.map +1 -1
  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/dist/lib/load-route-map-worker.js +17 -9
  40. package/dist/lib/route-map.d.ts +1 -1
  41. package/dist/lib/route-map.d.ts.map +1 -1
  42. package/dist/lib/route-map.js +29 -17
  43. package/package.json +4 -5
  44. package/src/lib/bootstrap-project.ts +39 -13
  45. package/src/lib/cli.ts +46 -15
  46. package/src/lib/commands/completion.ts +6 -1
  47. package/src/lib/commands/doctor.ts +18 -21
  48. package/src/lib/commands/help.ts +9 -43
  49. package/src/lib/commands/routes.ts +3 -3
  50. package/src/lib/commands/test.ts +10 -4
  51. package/src/lib/completion.ts +4 -151
  52. package/src/lib/controller-files.ts +4 -8
  53. package/src/lib/controller-ownership.ts +78 -141
  54. package/src/lib/doctor/controller-findings.ts +20 -97
  55. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  56. package/src/lib/doctor/controller-placeholders.ts +17 -189
  57. package/src/lib/doctor/controllers.ts +1 -1
  58. package/src/lib/doctor/project.ts +60 -52
  59. package/src/lib/doctor/types.ts +1 -5
  60. package/src/lib/errors.ts +0 -12
  61. package/src/lib/load-route-map-worker.ts +19 -10
  62. package/src/lib/route-map.ts +61 -16
  63. package/template/.agents/skills/remix/SKILL.md +588 -0
  64. package/template/.agents/skills/remix/references/animate-elements.md +195 -0
  65. package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
  66. package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
  67. package/template/.agents/skills/remix/references/component-model.md +282 -0
  68. package/template/.agents/skills/remix/references/create-mixins.md +158 -0
  69. package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
  70. package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  71. package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
  72. package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  73. package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
  74. package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
  75. package/{bootstrap → template}/AGENTS.md +13 -8
  76. package/template/README.md +29 -0
  77. package/template/app/actions/controller.tsx +18 -0
  78. package/template/app/assets/entry.ts +8 -0
  79. package/template/app/assets/prompt-button.tsx +163 -0
  80. package/template/app/assets.ts +20 -0
  81. package/template/app/middleware/render.tsx +43 -0
  82. package/template/app/router.ts +20 -0
  83. package/template/app/routes.ts +6 -0
  84. package/template/app/ui/document.tsx +33 -0
  85. package/template/app/ui/scaffold-home-page.tsx +527 -0
  86. package/template/gitignore +4 -0
  87. package/{bootstrap → template}/package.json +5 -6
  88. package/template/public/favicon.svg +11 -0
  89. package/{bootstrap → template}/server.ts +4 -5
  90. package/{bootstrap → template}/tsconfig.json +3 -3
  91. package/bootstrap/README.md +0 -27
  92. package/bootstrap/app/controllers/auth.tsx +0 -21
  93. package/bootstrap/app/controllers/home.tsx +0 -26
  94. package/bootstrap/app/router.ts +0 -10
  95. package/bootstrap/app/routes.ts +0 -6
  96. package/bootstrap/app/ui/document.tsx +0 -21
  97. package/bootstrap/app/ui/layout.tsx +0 -22
  98. package/bootstrap/app/utils/render.tsx +0 -7
  99. package/dist/lib/commands/skills.d.ts +0 -6
  100. package/dist/lib/commands/skills.d.ts.map +0 -1
  101. package/dist/lib/commands/skills.js +0 -222
  102. package/dist/lib/skills-cache.d.ts +0 -19
  103. package/dist/lib/skills-cache.d.ts.map +0 -1
  104. package/dist/lib/skills-cache.js +0 -89
  105. package/dist/lib/skills.d.ts +0 -30
  106. package/dist/lib/skills.d.ts.map +0 -1
  107. package/dist/lib/skills.js +0 -441
  108. package/src/lib/commands/skills.ts +0 -306
  109. package/src/lib/skills-cache.ts +0 -140
  110. 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,6 @@
1
+ import { get, route } from 'remix/routes'
2
+
3
+ export const routes = route({
4
+ assets: get('/assets/*path'),
5
+ home: '/',
6
+ })
@@ -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
+ }