@remix-run/cli 0.0.0 → 0.1.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 (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -3
  3. package/bootstrap/AGENTS.md +35 -0
  4. package/bootstrap/README.md +27 -0
  5. package/bootstrap/app/controllers/auth.tsx +21 -0
  6. package/bootstrap/app/controllers/home.tsx +26 -0
  7. package/bootstrap/app/router.ts +10 -0
  8. package/bootstrap/app/routes.ts +6 -0
  9. package/bootstrap/app/ui/document.tsx +21 -0
  10. package/bootstrap/app/ui/layout.tsx +22 -0
  11. package/bootstrap/app/utils/render.tsx +7 -0
  12. package/bootstrap/package.json +22 -0
  13. package/bootstrap/server.ts +37 -0
  14. package/bootstrap/tsconfig.json +19 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/bootstrap-project.d.ts +17 -0
  19. package/dist/lib/bootstrap-project.d.ts.map +1 -0
  20. package/dist/lib/bootstrap-project.js +137 -0
  21. package/dist/lib/cli-context.d.ts +11 -0
  22. package/dist/lib/cli-context.d.ts.map +1 -0
  23. package/dist/lib/cli-context.js +19 -0
  24. package/dist/lib/cli.d.ts +6 -0
  25. package/dist/lib/cli.d.ts.map +1 -0
  26. package/dist/lib/cli.js +87 -0
  27. package/dist/lib/commands/completion.d.ts +3 -0
  28. package/dist/lib/commands/completion.d.ts.map +1 -0
  29. package/dist/lib/commands/completion.js +51 -0
  30. package/dist/lib/commands/doctor.d.ts +4 -0
  31. package/dist/lib/commands/doctor.d.ts.map +1 -0
  32. package/dist/lib/commands/doctor.js +293 -0
  33. package/dist/lib/commands/help.d.ts +4 -0
  34. package/dist/lib/commands/help.d.ts.map +1 -0
  35. package/dist/lib/commands/help.js +124 -0
  36. package/dist/lib/commands/new.d.ts +4 -0
  37. package/dist/lib/commands/new.d.ts.map +1 -0
  38. package/dist/lib/commands/new.js +70 -0
  39. package/dist/lib/commands/routes.d.ts +4 -0
  40. package/dist/lib/commands/routes.d.ts.map +1 -0
  41. package/dist/lib/commands/routes.js +161 -0
  42. package/dist/lib/commands/skills.d.ts +6 -0
  43. package/dist/lib/commands/skills.d.ts.map +1 -0
  44. package/dist/lib/commands/skills.js +222 -0
  45. package/dist/lib/commands/test.d.ts +4 -0
  46. package/dist/lib/commands/test.d.ts.map +1 -0
  47. package/dist/lib/commands/test.js +19 -0
  48. package/dist/lib/commands/version.d.ts +4 -0
  49. package/dist/lib/commands/version.d.ts.map +1 -0
  50. package/dist/lib/commands/version.js +29 -0
  51. package/dist/lib/completion.d.ts +12 -0
  52. package/dist/lib/completion.d.ts.map +1 -0
  53. package/dist/lib/completion.js +408 -0
  54. package/dist/lib/contained-path.d.ts +2 -0
  55. package/dist/lib/contained-path.d.ts.map +1 -0
  56. package/dist/lib/contained-path.js +10 -0
  57. package/dist/lib/controller-files.d.ts +14 -0
  58. package/dist/lib/controller-files.d.ts.map +1 -0
  59. package/dist/lib/controller-files.js +53 -0
  60. package/dist/lib/controller-ownership.d.ts +41 -0
  61. package/dist/lib/controller-ownership.d.ts.map +1 -0
  62. package/dist/lib/controller-ownership.js +224 -0
  63. package/dist/lib/display-path.d.ts +2 -0
  64. package/dist/lib/display-path.d.ts.map +1 -0
  65. package/dist/lib/display-path.js +12 -0
  66. package/dist/lib/doctor/controller-findings.d.ts +4 -0
  67. package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
  68. package/dist/lib/doctor/controller-findings.js +132 -0
  69. package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
  70. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
  71. package/dist/lib/doctor/controller-fix-plans.js +130 -0
  72. package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
  73. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
  74. package/dist/lib/doctor/controller-placeholders.js +183 -0
  75. package/dist/lib/doctor/controllers.d.ts +8 -0
  76. package/dist/lib/doctor/controllers.d.ts.map +1 -0
  77. package/dist/lib/doctor/controllers.js +13 -0
  78. package/dist/lib/doctor/environment.d.ts +17 -0
  79. package/dist/lib/doctor/environment.d.ts.map +1 -0
  80. package/dist/lib/doctor/environment.js +226 -0
  81. package/dist/lib/doctor/fixes.d.ts +3 -0
  82. package/dist/lib/doctor/fixes.d.ts.map +1 -0
  83. package/dist/lib/doctor/fixes.js +65 -0
  84. package/dist/lib/doctor/project.d.ts +10 -0
  85. package/dist/lib/doctor/project.d.ts.map +1 -0
  86. package/dist/lib/doctor/project.js +371 -0
  87. package/dist/lib/doctor/types.d.ts +48 -0
  88. package/dist/lib/doctor/types.d.ts.map +1 -0
  89. package/dist/lib/doctor/types.js +15 -0
  90. package/dist/lib/errors.d.ts +173 -0
  91. package/dist/lib/errors.d.ts.map +1 -0
  92. package/dist/lib/errors.js +318 -0
  93. package/dist/lib/help-text.d.ts +13 -0
  94. package/dist/lib/help-text.d.ts.map +1 -0
  95. package/dist/lib/help-text.js +43 -0
  96. package/dist/lib/load-route-map-worker.d.ts +2 -0
  97. package/dist/lib/load-route-map-worker.d.ts.map +1 -0
  98. package/dist/lib/load-route-map-worker.js +92 -0
  99. package/dist/lib/parse-args.d.ts +22 -0
  100. package/dist/lib/parse-args.d.ts.map +1 -0
  101. package/dist/lib/parse-args.js +35 -0
  102. package/dist/lib/remix-version.d.ts +2 -0
  103. package/dist/lib/remix-version.d.ts.map +1 -0
  104. package/dist/lib/remix-version.js +58 -0
  105. package/dist/lib/reporter.d.ts +56 -0
  106. package/dist/lib/reporter.d.ts.map +1 -0
  107. package/dist/lib/reporter.js +357 -0
  108. package/dist/lib/route-map.d.ts +38 -0
  109. package/dist/lib/route-map.d.ts.map +1 -0
  110. package/dist/lib/route-map.js +195 -0
  111. package/dist/lib/skills-cache.d.ts +19 -0
  112. package/dist/lib/skills-cache.d.ts.map +1 -0
  113. package/dist/lib/skills-cache.js +89 -0
  114. package/dist/lib/skills.d.ts +30 -0
  115. package/dist/lib/skills.d.ts.map +1 -0
  116. package/dist/lib/skills.js +441 -0
  117. package/dist/lib/terminal.d.ts +17 -0
  118. package/dist/lib/terminal.d.ts.map +1 -0
  119. package/dist/lib/terminal.js +66 -0
  120. package/package.json +48 -5
  121. package/src/index.ts +1 -0
  122. package/src/lib/bootstrap-project.ts +222 -0
  123. package/src/lib/cli-context.ts +38 -0
  124. package/src/lib/cli.ts +120 -0
  125. package/src/lib/commands/completion.ts +74 -0
  126. package/src/lib/commands/doctor.ts +393 -0
  127. package/src/lib/commands/help.ts +157 -0
  128. package/src/lib/commands/new.ts +104 -0
  129. package/src/lib/commands/routes.ts +251 -0
  130. package/src/lib/commands/skills.ts +306 -0
  131. package/src/lib/commands/test.ts +25 -0
  132. package/src/lib/commands/version.ts +39 -0
  133. package/src/lib/completion.ts +561 -0
  134. package/src/lib/contained-path.ts +13 -0
  135. package/src/lib/controller-files.ts +79 -0
  136. package/src/lib/controller-ownership.ts +397 -0
  137. package/src/lib/display-path.ts +16 -0
  138. package/src/lib/doctor/controller-findings.ts +157 -0
  139. package/src/lib/doctor/controller-fix-plans.ts +184 -0
  140. package/src/lib/doctor/controller-placeholders.ts +242 -0
  141. package/src/lib/doctor/controllers.ts +23 -0
  142. package/src/lib/doctor/environment.ts +289 -0
  143. package/src/lib/doctor/fixes.ts +84 -0
  144. package/src/lib/doctor/project.ts +435 -0
  145. package/src/lib/doctor/types.ts +95 -0
  146. package/src/lib/errors.ts +379 -0
  147. package/src/lib/help-text.ts +88 -0
  148. package/src/lib/load-route-map-worker.ts +110 -0
  149. package/src/lib/parse-args.ts +82 -0
  150. package/src/lib/remix-version.ts +80 -0
  151. package/src/lib/reporter.ts +535 -0
  152. package/src/lib/route-map.ts +303 -0
  153. package/src/lib/skills-cache.ts +140 -0
  154. package/src/lib/skills.ts +706 -0
  155. package/src/lib/terminal.ts +86 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shopify Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,81 @@
1
- # Placeholder Package
1
+ # cli
2
2
 
3
- This package is a placeholder published at `0.0.0` to reserve the npm name and
4
- configure CI publish permissions.
3
+ Command-line interface for creating and managing Remix projects.
4
+
5
+ ## Features
6
+
7
+ - Create new Remix projects with `npx remix@next new` or installed `remix new`
8
+ - Print shell completion scripts with `remix completion`
9
+ - Check project environment and Remix app conventions with `remix doctor`
10
+ - Create low-risk project and controller files with `remix doctor --fix`
11
+ - Inspect the current app route tree with `remix routes`
12
+ - Sync Remix skills into `.agents/skills` with `remix skills`
13
+ - Run project tests with `remix test`
14
+ - Print the current Remix version with `remix version`
15
+ - Use the same CLI through the `remix` package or the `remix/cli` API
16
+ - Scaffold a starter app that matches the Remix project layout conventions
17
+
18
+ ## Installation
19
+
20
+ Use `npx remix@next new <target-dir>` to scaffold a new Remix app. Install `remix` when you want the local `remix` command:
21
+
22
+ ```sh
23
+ npm i remix
24
+ ```
25
+
26
+ ## Shell completion
27
+
28
+ Install bash completion:
29
+
30
+ ```sh
31
+ remix completion bash >> ~/.bashrc
32
+ ```
33
+
34
+ Install zsh completion:
35
+
36
+ ```sh
37
+ remix completion zsh >> ~/.zshrc
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Use `npx remix@next new my-remix-app` to scaffold a new Remix app. After installing Remix, the equivalent local command is `remix new my-remix-app`.
43
+
44
+ The rest of the CLI is available through the installed `remix` command:
45
+
46
+ ```sh
47
+ remix new my-remix-app
48
+ remix completion bash >> ~/.bashrc
49
+ remix doctor
50
+ remix doctor --fix
51
+ remix routes
52
+ remix routes --table
53
+ remix routes --table --no-headers
54
+ remix skills install
55
+ remix test
56
+ remix version
57
+ remix --no-color doctor
58
+ ```
59
+
60
+ You can also run the CLI programmatically:
61
+
62
+ ```ts
63
+ import { runRemix } from 'remix/cli'
64
+
65
+ await runRemix(['new', 'my-remix-app'])
66
+ await runRemix(['completion', 'bash'])
67
+ await runRemix(['doctor'])
68
+ await runRemix(['doctor', '--fix'])
69
+ await runRemix(['routes'])
70
+ await runRemix(['routes', '--table'])
71
+ await runRemix(['routes', '--table', '--no-headers'])
72
+ await runRemix(['skills', 'list'])
73
+ await runRemix(['test'])
74
+ await runRemix(['version'])
75
+ ```
76
+
77
+ `runRemix()` returns the CLI exit code as a promise.
78
+
79
+ ## License
80
+
81
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,35 @@
1
+ # %%RMX_APP_DISPLAY_NAME%% Agent Guide
2
+
3
+ This app was scaffolded with `remix new`. Use these conventions when continuing to build it out.
4
+
5
+ ## Commands
6
+
7
+ ```sh
8
+ npm i
9
+ npm run start
10
+ npm test
11
+ npm run typecheck
12
+ ```
13
+
14
+ ## Starter Layout
15
+
16
+ - `app/controllers/home.tsx` owns the home page
17
+ - `app/controllers/auth.tsx` owns the auth page
18
+ - `app/routes.ts` defines the route contract
19
+ - `app/router.ts` wires routes to route handlers
20
+ - `app/ui/` holds the shared document and layout wrappers
21
+ - `app/utils/render.tsx` centralizes HTML response rendering
22
+
23
+ ## Route Ownership
24
+
25
+ - Start from `app/routes.ts` and map each route to the narrowest owner on disk.
26
+ - Keep simple pages in flat files like `app/controllers/home.tsx` and `app/controllers/auth.tsx`.
27
+ - Promote a route into a controller folder with `controller.tsx` only when it gains nested routes, multiple actions, or route-owned modules.
28
+ - Keep route-owned page modules next to the route that owns them.
29
+ - Move shared UI to `app/ui/`, not `app/controllers/`.
30
+
31
+ ## Build-Out Notes
32
+
33
+ - This starter intentionally begins small; add directories like `app/data/`, `app/middleware/`, `public/`, and `test/` only when you need them.
34
+ - Prefer putting code in the narrowest owner before introducing shared modules.
35
+ - Avoid generic dumping-ground directories like `app/lib/` or `app/components/`.
@@ -0,0 +1,27 @@
1
+ # %%RMX_APP_DISPLAY_NAME%%
2
+
3
+ A minimal Remix application starter with a home page and an auth page.
4
+
5
+ ## Starter Shape
6
+
7
+ - `app/controllers/home.tsx` owns the home page.
8
+ - `app/controllers/auth.tsx` owns the auth page.
9
+ - `app/routes.ts` defines the route contract.
10
+ - `app/router.ts` wires routes to handlers.
11
+ - `app/ui/` holds the shared document and layout wrappers.
12
+ - `app/utils/render.tsx` centralizes HTML response rendering.
13
+
14
+ ## Growing The App
15
+
16
+ - Start with flat route files and only introduce route folders when a route needs multiple actions or route-owned modules.
17
+ - Add directories like `app/data/`, `app/middleware/`, `public/`, or `test/` when the app actually needs them.
18
+ - Move shared UI into `app/ui/` once more than one route needs it.
19
+
20
+ ## Commands
21
+
22
+ ```sh
23
+ npm i
24
+ npm run start
25
+ npm test
26
+ npm run typecheck
27
+ ```
@@ -0,0 +1,21 @@
1
+ import type { BuildAction } from 'remix/fetch-router'
2
+
3
+ import type { routes } from '../routes.ts'
4
+ import { Layout } from '../ui/layout.tsx'
5
+ import { render } from '../utils/render.tsx'
6
+
7
+ export const auth: BuildAction<'GET', typeof routes.auth> = {
8
+ handler() {
9
+ return render(<AuthPage />)
10
+ },
11
+ }
12
+
13
+ function AuthPage() {
14
+ return () => (
15
+ <Layout title="Auth">
16
+ <h1>Auth</h1>
17
+ <p>Use this route to start building sign-in, sign-up, and session flows.</p>
18
+ <p>Keep it as a flat file until the route needs multiple actions or route-owned modules.</p>
19
+ </Layout>
20
+ )
21
+ }
@@ -0,0 +1,26 @@
1
+ import type { BuildAction } from 'remix/fetch-router'
2
+
3
+ import type { routes } from '../routes.ts'
4
+ import { Layout } from '../ui/layout.tsx'
5
+ import { render } from '../utils/render.tsx'
6
+
7
+ const APP_DISPLAY_NAME = decodeURIComponent('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
8
+
9
+ export const home: BuildAction<'GET', typeof routes.home> = {
10
+ handler() {
11
+ return render(<HomePage />)
12
+ },
13
+ }
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
+ }
@@ -0,0 +1,10 @@
1
+ import { createRouter } from 'remix/fetch-router'
2
+
3
+ import { auth } from './controllers/auth.tsx'
4
+ import { home } from './controllers/home.tsx'
5
+ import { routes } from './routes.ts'
6
+
7
+ export const router = createRouter()
8
+
9
+ router.map(routes.home, home)
10
+ router.map(routes.auth, auth)
@@ -0,0 +1,6 @@
1
+ import { route } from 'remix/fetch-router/routes'
2
+
3
+ export const routes = route({
4
+ home: '/',
5
+ auth: '/auth',
6
+ })
@@ -0,0 +1,21 @@
1
+ import type { RemixNode } from 'remix/ui'
2
+
3
+ export interface DocumentProps {
4
+ children?: RemixNode
5
+ title?: string
6
+ }
7
+
8
+ const DEFAULT_TITLE = decodeURIComponent('%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%')
9
+
10
+ export function Document() {
11
+ return ({ title = DEFAULT_TITLE, children }: DocumentProps) => (
12
+ <html lang="en">
13
+ <head>
14
+ <meta charSet="utf-8" />
15
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
16
+ <title>{title}</title>
17
+ </head>
18
+ <body>{children}</body>
19
+ </html>
20
+ )
21
+ }
@@ -0,0 +1,22 @@
1
+ import type { RemixNode } from 'remix/ui'
2
+
3
+ import { routes } from '../routes.ts'
4
+ import { Document } from './document.tsx'
5
+
6
+ export interface LayoutProps {
7
+ children?: RemixNode
8
+ title?: string
9
+ }
10
+
11
+ export function Layout() {
12
+ return ({ title, children }: LayoutProps) => (
13
+ <Document title={title}>
14
+ <header>
15
+ <nav>
16
+ <a href={routes.home.href()}>Home</a> <a href={routes.auth.href()}>Auth</a>
17
+ </nav>
18
+ </header>
19
+ <main>{children}</main>
20
+ </Document>
21
+ )
22
+ }
@@ -0,0 +1,7 @@
1
+ import type { RemixNode } from 'remix/ui'
2
+ import { renderToStream } from 'remix/ui/server'
3
+ import { createHtmlResponse } from 'remix/response/html'
4
+
5
+ export function render(node: RemixNode, init?: ResponseInit) {
6
+ return createHtmlResponse(renderToStream(node), init)
7
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "remix-bootstrap-template",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch server.ts",
7
+ "start": "tsx server.ts",
8
+ "test": "tsx --test",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "engines": {
12
+ "node": ">=24.3.0"
13
+ },
14
+ "dependencies": {
15
+ "remix": "workspace:^",
16
+ "tsx": "catalog:"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "catalog:",
20
+ "typescript": "catalog:"
21
+ }
22
+ }
@@ -0,0 +1,37 @@
1
+ import * as http from 'node:http'
2
+ import { createRequestListener } from 'remix/node-fetch-server'
3
+
4
+ import { router } from './app/router.ts'
5
+
6
+ const server = http.createServer(
7
+ createRequestListener(async (request) => {
8
+ try {
9
+ return await router.fetch(request)
10
+ } catch (error) {
11
+ console.error(error)
12
+ return new Response('Internal Server Error', { status: 500 })
13
+ }
14
+ }),
15
+ )
16
+
17
+ const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 44100
18
+
19
+ server.listen(port, () => {
20
+ console.log(`Server listening on http://localhost:${port}`)
21
+ })
22
+
23
+ let shuttingDown = false
24
+
25
+ function shutdown() {
26
+ if (shuttingDown) {
27
+ return
28
+ }
29
+
30
+ shuttingDown = true
31
+ server.close(() => {
32
+ process.exit(0)
33
+ })
34
+ }
35
+
36
+ process.on('SIGINT', shutdown)
37
+ process.on('SIGTERM', shutdown)
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
5
+ "types": ["node"],
6
+ "module": "ES2022",
7
+ "moduleResolution": "Bundler",
8
+ "target": "ESNext",
9
+ "allowImportingTsExtensions": true,
10
+ "rewriteRelativeImportExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "skipLibCheck": true,
13
+ "jsx": "react-jsx",
14
+ "jsxImportSource": "remix/ui",
15
+ "preserveSymlinks": true,
16
+ "noEmit": true
17
+ },
18
+ "exclude": ["dist"]
19
+ }
@@ -0,0 +1,2 @@
1
+ export { runRemix, type RunRemixOptions } from './lib/cli.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { runRemix } from "./lib/cli.js";
@@ -0,0 +1,17 @@
1
+ import { type StepProgressReporter } from './reporter.ts';
2
+ export declare const MINIMUM_SUPPORTED_NODE_VERSION = "24.3.0";
3
+ export interface BootstrapProjectOptions {
4
+ appName: string | null;
5
+ cwd?: string;
6
+ force: boolean;
7
+ remixVersion?: string;
8
+ targetDir: string;
9
+ }
10
+ export type BootstrapProjectPhase = 'prepare-target-directory' | 'generate-scaffold-files' | 'finalize-package-json';
11
+ export interface BootstrappedProject {
12
+ appDisplayName: string;
13
+ targetDir: string;
14
+ }
15
+ export type BootstrapProgressReporter = StepProgressReporter<BootstrapProjectPhase>;
16
+ export declare function bootstrapProject(options: BootstrapProjectOptions, progress?: BootstrapProgressReporter): Promise<BootstrappedProject>;
17
+ //# sourceMappingURL=bootstrap-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap-project.d.ts","sourceRoot":"","sources":["../../src/lib/bootstrap-project.ts"],"names":[],"mappings":"AAYA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAO1E,eAAO,MAAM,8BAA8B,WAAW,CAAA;AAEtD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,qBAAqB,GAC7B,0BAA0B,GAC1B,yBAAyB,GACzB,uBAAuB,CAAA;AAE3B,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,CAAC,qBAAqB,CAAC,CAAA;AAUnF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,EAChC,QAAQ,CAAC,EAAE,yBAAyB,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAgC9B"}
@@ -0,0 +1,137 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import * as process from 'node:process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import semver from 'semver';
6
+ import { appNameUnavailable, invalidPackageName, targetDirectoryNotEmpty, targetPathNotDirectory, } from "./errors.js";
7
+ import { runProgressStep } from "./reporter.js";
8
+ const BOOTSTRAP_DIRECTORY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../bootstrap');
9
+ const BOOTSTRAP_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules']);
10
+ export const MINIMUM_SUPPORTED_NODE_VERSION = '24.3.0';
11
+ export async function bootstrapProject(options, progress) {
12
+ let cwd = options.cwd ?? process.cwd();
13
+ let targetDir = path.resolve(cwd, options.targetDir);
14
+ let rawAppName = options.appName ?? path.basename(targetDir);
15
+ if (rawAppName.length === 0) {
16
+ throw appNameUnavailable(targetDir);
17
+ }
18
+ let config = {
19
+ appDisplayName: options.appName ?? humanizeName(rawAppName),
20
+ packageName: toPackageName(rawAppName),
21
+ remixVersion: readDefaultRemixVersion(options.remixVersion),
22
+ };
23
+ await runProgressStep(progress, 'prepare-target-directory', () => ensureTargetDirectory(targetDir, options.force));
24
+ await runProgressStep(progress, 'generate-scaffold-files', () => copyBootstrapDirectory({
25
+ sourceDir: BOOTSTRAP_DIRECTORY,
26
+ targetDir,
27
+ templateValues: createTemplateValues(config),
28
+ }));
29
+ await runProgressStep(progress, 'finalize-package-json', () => writeScaffoldPackageJson(targetDir, config));
30
+ return {
31
+ appDisplayName: config.appDisplayName,
32
+ targetDir,
33
+ };
34
+ }
35
+ function readDefaultRemixVersion(runtimeVersion) {
36
+ let overriddenVersion = process.env.REMIX_VERSION?.trim();
37
+ if (overriddenVersion) {
38
+ return overriddenVersion;
39
+ }
40
+ if (runtimeVersion == null) {
41
+ return 'latest';
42
+ }
43
+ let validRuntimeVersion = semver.valid(runtimeVersion);
44
+ return validRuntimeVersion == null ? runtimeVersion : `^${validRuntimeVersion}`;
45
+ }
46
+ function createTemplateValues(config) {
47
+ return {
48
+ '%%RMX_APP_DISPLAY_NAME%%': config.appDisplayName,
49
+ '%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%': encodeURIComponent(config.appDisplayName),
50
+ };
51
+ }
52
+ async function writeScaffoldPackageJson(targetDir, config) {
53
+ let packageJsonPath = path.join(targetDir, 'package.json');
54
+ let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
55
+ packageJson.name = config.packageName;
56
+ packageJson.dependencies = {
57
+ ...packageJson.dependencies,
58
+ remix: config.remixVersion,
59
+ tsx: 'latest',
60
+ };
61
+ packageJson.devDependencies = {
62
+ ...packageJson.devDependencies,
63
+ '@types/node': 'latest',
64
+ typescript: 'latest',
65
+ };
66
+ packageJson.engines = {
67
+ ...packageJson.engines,
68
+ node: `>=${MINIMUM_SUPPORTED_NODE_VERSION}`,
69
+ };
70
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
71
+ }
72
+ async function ensureTargetDirectory(targetDir, force) {
73
+ try {
74
+ let stats = await fs.stat(targetDir);
75
+ if (!stats.isDirectory()) {
76
+ throw targetPathNotDirectory(targetDir);
77
+ }
78
+ let entries = await fs.readdir(targetDir);
79
+ if (entries.length > 0 && !force) {
80
+ throw targetDirectoryNotEmpty(targetDir);
81
+ }
82
+ }
83
+ catch (error) {
84
+ let nodeError = error;
85
+ if (nodeError.code !== 'ENOENT') {
86
+ throw error;
87
+ }
88
+ }
89
+ await fs.mkdir(targetDir, { recursive: true });
90
+ }
91
+ async function copyBootstrapDirectory({ sourceDir, targetDir, templateValues, }) {
92
+ let entries = await fs.readdir(sourceDir, { withFileTypes: true });
93
+ for (let entry of entries) {
94
+ let sourcePath = path.join(sourceDir, entry.name);
95
+ let targetPath = path.join(targetDir, entry.name);
96
+ if (BOOTSTRAP_EXCLUDED_NAMES.has(entry.name)) {
97
+ continue;
98
+ }
99
+ if (entry.isDirectory()) {
100
+ await fs.mkdir(targetPath, { recursive: true });
101
+ await copyBootstrapDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues });
102
+ continue;
103
+ }
104
+ let content = await fs.readFile(sourcePath, 'utf8');
105
+ await fs.writeFile(targetPath, replaceTemplateValues(content, templateValues), 'utf8');
106
+ }
107
+ }
108
+ function replaceTemplateValues(content, templateValues) {
109
+ for (let [token, value] of Object.entries(templateValues)) {
110
+ content = content.split(token).join(value);
111
+ }
112
+ return content;
113
+ }
114
+ function humanizeName(value) {
115
+ let parts = value.split(/[-_\s]+/).filter(Boolean);
116
+ if (parts.length === 0) {
117
+ return 'Remix App';
118
+ }
119
+ return parts
120
+ .map((part) => {
121
+ let head = part.slice(0, 1).toUpperCase();
122
+ let tail = part.slice(1);
123
+ return `${head}${tail}`;
124
+ })
125
+ .join(' ');
126
+ }
127
+ function toPackageName(value) {
128
+ let packageName = value
129
+ .trim()
130
+ .toLowerCase()
131
+ .replace(/[^a-z0-9]+/g, '-')
132
+ .replace(/^-+|-+$/g, '');
133
+ if (packageName.length === 0) {
134
+ throw invalidPackageName(value);
135
+ }
136
+ return packageName;
137
+ }
@@ -0,0 +1,11 @@
1
+ interface ResolveCliContextOptions {
2
+ cwd?: string;
3
+ remixVersion?: string;
4
+ }
5
+ export interface CliContext {
6
+ cwd: string;
7
+ remixVersion?: string;
8
+ }
9
+ export declare function resolveCliContext(options?: ResolveCliContextOptions): Promise<CliContext>;
10
+ export {};
11
+ //# sourceMappingURL=cli-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-context.d.ts","sourceRoot":"","sources":["../../src/lib/cli-context.ts"],"names":[],"mappings":"AAKA,UAAU,wBAAwB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,CAQrB"}
@@ -0,0 +1,19 @@
1
+ import * as path from 'node:path';
2
+ import * as process from 'node:process';
3
+ import { resolveDefaultRemixVersion } from "./remix-version.js";
4
+ export async function resolveCliContext(options = {}) {
5
+ let cwd = resolveCwd(options.cwd);
6
+ let remixVersion = normalizeRemixVersion(options.remixVersion);
7
+ return {
8
+ cwd,
9
+ remixVersion: remixVersion ?? (await resolveDefaultRemixVersion(cwd)),
10
+ };
11
+ }
12
+ function resolveCwd(cwd) {
13
+ let normalizedCwd = cwd?.trim();
14
+ return path.resolve(normalizedCwd == null || normalizedCwd.length === 0 ? process.cwd() : normalizedCwd);
15
+ }
16
+ function normalizeRemixVersion(remixVersion) {
17
+ let normalizedVersion = remixVersion?.trim();
18
+ return normalizedVersion == null || normalizedVersion.length === 0 ? undefined : normalizedVersion;
19
+ }
@@ -0,0 +1,6 @@
1
+ export interface RunRemixOptions {
2
+ cwd?: string;
3
+ remixVersion?: string;
4
+ }
5
+ export declare function runRemix(argv?: string[], options?: RunRemixOptions): Promise<number>;
6
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,QAAQ,CAC5B,IAAI,GAAE,MAAM,EAA0B,EACtC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA4BjB"}