@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
@@ -1,306 +0,0 @@
1
- import * as process from 'node:process'
2
-
3
- import type { CliContext } from '../cli-context.ts'
4
- import { getDisplayPath } from '../display-path.ts'
5
- import { renderCliError, toCliError, unknownSkillsCommand } from '../errors.ts'
6
- import { formatHelpText } from '../help-text.ts'
7
- import { parseArgs } from '../parse-args.ts'
8
- import type { SkillChange, SkillsInstallPhase, SkillsProgressReporter } from '../skills.ts'
9
- import { getSkillsOverview, installRemixSkills } from '../skills.ts'
10
- import {
11
- createCommandReporter,
12
- createStepProgressReporter,
13
- type CommandReporter,
14
- } from '../reporter.ts'
15
-
16
- const SKILLS_PROGRESS_LABELS = {
17
- 'compare-local-skills': 'Compare local skills',
18
- 'download-remix-skills-archive': 'Download Remix skills archive',
19
- 'fetch-remix-skills-metadata': 'Fetch Remix skills metadata from GitHub',
20
- 'read-local-skills-cache': 'Read local skills cache',
21
- 'resolve-project-root': 'Resolve project root',
22
- 'write-updated-skills': 'Write updated skills',
23
- } satisfies Record<SkillsInstallPhase, string>
24
-
25
- export async function runSkillsCommand(argv: string[], context: CliContext): Promise<number> {
26
- if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
27
- process.stdout.write(getSkillsCommandHelpText())
28
- return 0
29
- }
30
-
31
- let [subcommand, ...rest] = argv
32
-
33
- try {
34
- if (subcommand === 'install') {
35
- return await runSkillsInstallCommand(rest, context)
36
- }
37
-
38
- if (subcommand === 'list') {
39
- return await runSkillsListCommand(rest, context)
40
- }
41
-
42
- throw unknownSkillsCommand(subcommand)
43
- } catch (error) {
44
- process.stderr.write(
45
- renderCliError(toCliError(error), { helpText: getSkillsCommandHelpText(process.stderr) }),
46
- )
47
- return 1
48
- }
49
- }
50
-
51
- export function getSkillsCommandHelpText(target: NodeJS.WriteStream = process.stdout): string {
52
- return formatHelpText(
53
- {
54
- commands: [
55
- {
56
- description: 'Install Remix skills into a local directory',
57
- label: 'install [--dir <path>]',
58
- },
59
- {
60
- description: 'List Remix skills and local status',
61
- label: 'list [--dir <path>] [--json]',
62
- },
63
- ],
64
- description: 'Manage Remix skills for the current project.',
65
- examples: [
66
- 'remix skills install',
67
- 'remix skills install --dir custom/skills',
68
- 'remix skills list --dir custom/skills',
69
- 'remix skills list --json',
70
- ],
71
- usage: ['remix skills <command>'],
72
- },
73
- target,
74
- )
75
- }
76
-
77
- export function getSkillsInstallCommandHelpText(
78
- target: NodeJS.WriteStream = process.stdout,
79
- ): string {
80
- return formatHelpText(
81
- {
82
- description: 'Install or refresh Remix skills in .agents/skills for the current project.',
83
- examples: ['remix skills install', 'remix skills install --dir custom/skills'],
84
- options: [
85
- {
86
- description: 'Install skills into a custom directory relative to the project root',
87
- label: '--dir <path>',
88
- },
89
- ],
90
- usage: ['remix skills install [--dir <path>]'],
91
- },
92
- target,
93
- )
94
- }
95
-
96
- export function getSkillsListCommandHelpText(target: NodeJS.WriteStream = process.stdout): string {
97
- return formatHelpText(
98
- {
99
- description:
100
- 'List Remix skills and show whether each one is installed, outdated, or missing locally.',
101
- examples: [
102
- 'remix skills list',
103
- 'remix skills list --dir custom/skills',
104
- 'remix skills list --json',
105
- ],
106
- options: [
107
- {
108
- description: 'Read local skills from a custom directory relative to the project root',
109
- label: '--dir <path>',
110
- },
111
- { description: 'Print skill state as JSON', label: '--json' },
112
- ],
113
- usage: ['remix skills list [--dir <path>] [--json]'],
114
- },
115
- target,
116
- )
117
- }
118
-
119
- async function runSkillsInstallCommand(argv: string[], context: CliContext): Promise<number> {
120
- if (argv.includes('-h') || argv.includes('--help')) {
121
- process.stdout.write(getSkillsInstallCommandHelpText())
122
- return 0
123
- }
124
-
125
- let reporter: CommandReporter | null = null
126
- let progress: SkillsProgressReporter | null = null
127
-
128
- try {
129
- let options = parseSkillsInstallCommandArgs(argv)
130
- reporter = createCommandReporter({ remixVersion: context.remixVersion })
131
- progress = createSkillsProgressReporter(reporter)
132
- let cwd = context.cwd
133
-
134
- await reporter.status.commandHeader('skills install')
135
- let result = await installRemixSkills(cwd, globalThis.fetch, {
136
- progress,
137
- skillsDir: options.dir ?? undefined,
138
- })
139
- let skillsDir = getDisplayPath(result.skillsDir, cwd)
140
- progress.writeSummaryGap()
141
- if (result.appliedChanges.length === 0) {
142
- reporter.out.line(`No changes. ${skillsDir} is up to date.`)
143
- reporter.finish()
144
- return 0
145
- }
146
-
147
- reporter.out.line(`Synced Remix skills into ${skillsDir}:`)
148
- reporter.out.bullets(formatAppliedChanges(result.appliedChanges))
149
- reporter.finish()
150
- return 0
151
- } catch (error) {
152
- progress?.writeSummaryGap()
153
- process.stderr.write(
154
- renderCliError(toCliError(error), {
155
- helpText: getSkillsInstallCommandHelpText(process.stderr),
156
- }),
157
- )
158
- reporter?.finish()
159
- return 1
160
- }
161
- }
162
-
163
- async function runSkillsListCommand(argv: string[], context: CliContext): Promise<number> {
164
- if (argv.includes('-h') || argv.includes('--help')) {
165
- process.stdout.write(getSkillsListCommandHelpText())
166
- return 0
167
- }
168
-
169
- let reporter: CommandReporter | null = null
170
- let progress: SkillsProgressReporter | null = null
171
-
172
- try {
173
- let options = parseSkillsDirArgs(argv, { allowJson: true })
174
- let cwd = context.cwd
175
- reporter = options.json ? null : createCommandReporter({ remixVersion: context.remixVersion })
176
- if (reporter != null) {
177
- progress = createSkillsProgressReporter(reporter)
178
- await reporter.status.commandHeader('skills list')
179
- }
180
-
181
- let result = await getSkillsOverview(cwd, globalThis.fetch, {
182
- progress: progress ?? undefined,
183
- skillsDir: options.dir ?? undefined,
184
- })
185
-
186
- if (options.json) {
187
- process.stdout.write(
188
- `${JSON.stringify(
189
- {
190
- entries: result.entries,
191
- projectRoot: result.projectRoot,
192
- skillsDir: result.skillsDir,
193
- },
194
- null,
195
- 2,
196
- )}\n`,
197
- )
198
- return 0
199
- }
200
-
201
- progress?.writeSummaryGap()
202
- if (reporter == null) {
203
- return 0
204
- }
205
-
206
- let listReporter = reporter
207
- listReporter.out.line(
208
- formatSkillsListSummary(result.entries, getDisplayPath(result.skillsDir, cwd)),
209
- )
210
- listReporter.out.bullets(
211
- result.entries.map((entry) => formatSkillListEntry(listReporter, entry)),
212
- )
213
- listReporter.finish()
214
- return 0
215
- } catch (error) {
216
- progress?.writeSummaryGap()
217
- process.stderr.write(
218
- renderCliError(toCliError(error), { helpText: getSkillsListCommandHelpText(process.stderr) }),
219
- )
220
- reporter?.finish()
221
- return 1
222
- }
223
- }
224
-
225
- function createSkillsProgressReporter(reporter: CommandReporter): SkillsProgressReporter {
226
- return createStepProgressReporter(reporter.status, SKILLS_PROGRESS_LABELS)
227
- }
228
-
229
- function formatAppliedChanges(changes: SkillChange[]): string[] {
230
- let showAction = changes.some((change) => change.action !== 'add')
231
- return changes.map((change) =>
232
- showAction ? `${toPastTense(change.action)} ${change.name}` : change.name,
233
- )
234
- }
235
-
236
- function formatSkillListEntry(
237
- reporter: CommandReporter,
238
- entry: { name: string; state: 'installed' | 'missing' | 'outdated' },
239
- ): string {
240
- if (entry.state === 'installed') {
241
- return entry.name
242
- }
243
-
244
- let tone: 'error' | 'warn' = entry.state === 'missing' ? 'error' : 'warn'
245
- return `${entry.name} ${reporter.out.label(entry.state, '', { tone })}`
246
- }
247
-
248
- function formatSkillsListSummary(
249
- entries: Array<{ name: string; state: 'installed' | 'missing' | 'outdated' }>,
250
- skillsDir: string,
251
- ): string {
252
- let installedCount = entries.filter((entry) => entry.state === 'installed').length
253
- let outdatedCount = entries.filter((entry) => entry.state === 'outdated').length
254
- let missingCount = entries.filter((entry) => entry.state === 'missing').length
255
- let detailParts = [
256
- installedCount > 0 ? `${installedCount} installed` : null,
257
- outdatedCount > 0 ? `${outdatedCount} outdated` : null,
258
- missingCount > 0 ? `${missingCount} missing` : null,
259
- ].filter((part) => part != null)
260
-
261
- let detail = detailParts.length === 0 ? '0 installed' : detailParts.join(', ')
262
- return `Checked Remix skills against ${skillsDir}: ${detail}.`
263
- }
264
-
265
- function parseSkillsInstallCommandArgs(argv: string[]): { dir: string | null } {
266
- let { dir } = parseSkillsDirArgs(argv)
267
- return { dir }
268
- }
269
-
270
- function parseSkillsDirArgs(
271
- argv: string[],
272
- options: { allowJson?: boolean } = {},
273
- ): { dir: string | null; json: boolean } {
274
- if (options.allowJson) {
275
- let parsed = parseArgs(
276
- argv,
277
- {
278
- dir: { flag: '--dir', type: 'string' },
279
- json: { flag: '--json', type: 'boolean' },
280
- },
281
- { maxPositionals: 0 },
282
- )
283
-
284
- return {
285
- dir: parsed.options.dir ?? null,
286
- json: parsed.options.json,
287
- }
288
- }
289
-
290
- let parsed = parseArgs(
291
- argv,
292
- {
293
- dir: { flag: '--dir', type: 'string' },
294
- },
295
- { maxPositionals: 0 },
296
- )
297
-
298
- return {
299
- dir: parsed.options.dir ?? null,
300
- json: false,
301
- }
302
- }
303
-
304
- function toPastTense(action: 'add' | 'replace'): string {
305
- return action === 'add' ? 'added' : 'replaced'
306
- }
@@ -1,140 +0,0 @@
1
- import * as crypto from 'node:crypto'
2
- import * as fs from 'node:fs/promises'
3
- import * as os from 'node:os'
4
- import * as path from 'node:path'
5
- import * as process from 'node:process'
6
-
7
- const REMIX_SKILLS_CACHE_VERSION = 1
8
- const REMIX_SKILLS_CACHE_REPO = 'remix-run/remix'
9
- const REMIX_SKILLS_CACHE_REF = 'main'
10
-
11
- export interface SkillsCacheFileEntry {
12
- localHash: string
13
- remoteSha: string
14
- }
15
-
16
- export interface SkillsCacheSkillEntry {
17
- files: Record<string, SkillsCacheFileEntry>
18
- }
19
-
20
- export interface SkillsCacheManifest {
21
- ref: string
22
- repo: string
23
- skills: Record<string, SkillsCacheSkillEntry>
24
- skillsDir: string
25
- version: number
26
- }
27
-
28
- export function createSkillsCacheManifest(
29
- skillsDir: string,
30
- skills: Record<string, SkillsCacheSkillEntry>,
31
- ): SkillsCacheManifest {
32
- return {
33
- ref: REMIX_SKILLS_CACHE_REF,
34
- repo: REMIX_SKILLS_CACHE_REPO,
35
- skills,
36
- skillsDir: path.resolve(skillsDir),
37
- version: REMIX_SKILLS_CACHE_VERSION,
38
- }
39
- }
40
-
41
- export function getSkillsCacheFilePath(skillsDir: string): string {
42
- let cacheKey = crypto.createHash('sha256').update(path.resolve(skillsDir)).digest('hex')
43
- return path.join(getSkillsCacheDir(), `${cacheKey}.json`)
44
- }
45
-
46
- export async function readSkillsCache(skillsDir: string): Promise<SkillsCacheManifest | null> {
47
- try {
48
- let filePath = getSkillsCacheFilePath(skillsDir)
49
- let raw = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown
50
- return isSkillsCacheManifest(raw, skillsDir) ? raw : null
51
- } catch (error) {
52
- if (error instanceof SyntaxError) {
53
- return null
54
- }
55
-
56
- let nodeError = error as NodeJS.ErrnoException
57
- if (nodeError.code === 'ENOENT') {
58
- return null
59
- }
60
-
61
- throw error
62
- }
63
- }
64
-
65
- export async function writeSkillsCache(
66
- skillsDir: string,
67
- manifest: SkillsCacheManifest,
68
- ): Promise<void> {
69
- let filePath = getSkillsCacheFilePath(skillsDir)
70
- let tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`
71
-
72
- await fs.mkdir(path.dirname(filePath), { recursive: true })
73
- await fs.writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
74
- await fs.rename(tempPath, filePath)
75
- }
76
-
77
- function getSkillsCacheDir(): string {
78
- if (process.platform === 'win32') {
79
- let localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local')
80
- return path.join(localAppData, 'remix', 'Cache', 'skills')
81
- }
82
-
83
- if (process.platform === 'darwin') {
84
- return path.join(
85
- process.env.HOME ?? os.homedir(),
86
- 'Library',
87
- 'Caches',
88
- 'remix',
89
- 'cli',
90
- 'skills',
91
- )
92
- }
93
-
94
- let xdgCacheHome = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache')
95
- return path.join(xdgCacheHome, 'remix', 'cli', 'skills')
96
- }
97
-
98
- function isSkillsCacheManifest(value: unknown, skillsDir: string): value is SkillsCacheManifest {
99
- if (typeof value !== 'object' || value == null) {
100
- return false
101
- }
102
-
103
- if (
104
- Reflect.get(value, 'version') !== REMIX_SKILLS_CACHE_VERSION ||
105
- Reflect.get(value, 'repo') !== REMIX_SKILLS_CACHE_REPO ||
106
- Reflect.get(value, 'ref') !== REMIX_SKILLS_CACHE_REF ||
107
- Reflect.get(value, 'skillsDir') !== path.resolve(skillsDir)
108
- ) {
109
- return false
110
- }
111
-
112
- let skills = Reflect.get(value, 'skills')
113
- if (typeof skills !== 'object' || skills == null) {
114
- return false
115
- }
116
-
117
- for (let skillEntry of Object.values(skills)) {
118
- if (typeof skillEntry !== 'object' || skillEntry == null) {
119
- return false
120
- }
121
-
122
- let files = Reflect.get(skillEntry, 'files')
123
- if (typeof files !== 'object' || files == null) {
124
- return false
125
- }
126
-
127
- for (let fileEntry of Object.values(files as object)) {
128
- if (
129
- typeof fileEntry !== 'object' ||
130
- fileEntry == null ||
131
- typeof Reflect.get(fileEntry, 'localHash') !== 'string' ||
132
- typeof Reflect.get(fileEntry, 'remoteSha') !== 'string'
133
- ) {
134
- return false
135
- }
136
- }
137
- }
138
-
139
- return true
140
- }