@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.
- package/LICENSE +21 -0
- package/README.md +80 -3
- package/bootstrap/AGENTS.md +35 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +26 -0
- package/bootstrap/app/router.ts +10 -0
- package/bootstrap/app/routes.ts +6 -0
- package/bootstrap/app/ui/document.tsx +21 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/utils/render.tsx +7 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +87 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +124 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/skills.d.ts +6 -0
- package/dist/lib/commands/skills.d.ts.map +1 -0
- package/dist/lib/commands/skills.js +222 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +19 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +408 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +173 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +318 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/skills-cache.d.ts +19 -0
- package/dist/lib/skills-cache.d.ts.map +1 -0
- package/dist/lib/skills-cache.js +89 -0
- package/dist/lib/skills.d.ts +30 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +441 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +48 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +120 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +157 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/skills.ts +306 -0
- package/src/lib/commands/test.ts +25 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +561 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +379 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/skills-cache.ts +140 -0
- package/src/lib/skills.ts +706 -0
- 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
|
-
#
|
|
1
|
+
# cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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,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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|