Full Code of kiliman/rmx-cli for AI

main f3b5ba039e6b cached
18 files
47.9 KB
13.7k tokens
23 symbols
1 requests
Download .txt
Repository: kiliman/rmx-cli
Branch: main
Commit: f3b5ba039e6b
Files: 18
Total size: 47.9 KB

Directory structure:
gitextract_vdy00js5/

├── .all-contributorsrc
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── package.json
├── src/
│   ├── cli.ts
│   ├── commands/
│   │   ├── eject-ras.ts
│   │   ├── gen-remix.ts
│   │   ├── get-esm-packages.ts
│   │   ├── svg-sprite.ts
│   │   └── version.ts
│   └── libs/
│       └── svg-parser.ts
├── test/
│   ├── package-copy.json
│   ├── package.json
│   └── server.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .all-contributorsrc
================================================
{
  "projectName": "rmx-cli",
  "projectOwner": "Kiliman",
  "repoType": "github",
  "repoHost": "https://github.com",
  "files": [
    "README.md"
  ],
  "imageSize": 100,
  "commit": false,
  "commitConvention": "none",
  "contributors": [
    {
      "login": "kiliman",
      "name": "Kiliman",
      "avatar_url": "https://avatars.githubusercontent.com/u/47168?v=4",
      "profile": "https://kiliman.dev/",
      "contributions": [
        "code",
        "doc"
      ]
    },
    {
      "login": "revelt",
      "name": "Roy Revelt",
      "avatar_url": "https://avatars.githubusercontent.com/u/8344688?v=4",
      "profile": "https://codsen.com/os/",
      "contributions": [
        "doc"
      ]
    },
    {
      "login": "kentcdodds",
      "name": "Kent C. Dodds",
      "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=4",
      "profile": "https://kentcdodds.com/",
      "contributions": [
        "doc"
      ]
    },
    {
      "login": "kirandash",
      "name": "Kiran Dash",
      "avatar_url": "https://avatars.githubusercontent.com/u/13310363?v=4",
      "profile": "http://bgwebagency.in/",
      "contributions": [
        "doc"
      ]
    },
    {
      "login": "andrewcohen",
      "name": "Andrew Cohen",
      "avatar_url": "https://avatars.githubusercontent.com/u/1016046?v=4",
      "profile": "https://github.com/andrewcohen",
      "contributions": [
        "code"
      ]
    },
    {
      "login": "courdek",
      "name": "Andrew Coppola",
      "avatar_url": "https://avatars.githubusercontent.com/u/319738?v=4",
      "profile": "https://github.com/courdek",
      "contributions": [
        "code"
      ]
    },
    {
      "login": "KnisterPeter",
      "name": "Markus Wolf",
      "avatar_url": "https://avatars.githubusercontent.com/u/327445?v=4",
      "profile": "https://about.me/knisterpeter",
      "contributions": [
        "code"
      ]
    },
    {
      "login": "wKovacs64",
      "name": "Justin Hall",
      "avatar_url": "https://avatars.githubusercontent.com/u/1288694?v=4",
      "profile": "https://justinrhall.dev/",
      "contributions": [
        "code",
        "bug"
      ]
    },
    {
      "login": "fweinaug",
      "name": "Florian Weinaug",
      "avatar_url": "https://avatars.githubusercontent.com/u/17765766?v=4",
      "profile": "http://florianweinaug.de/",
      "contributions": [
        "code",
        "bug"
      ]
    }
  ],
  "contributorsPerLine": 7
}


================================================
FILE: .gitignore
================================================
/node_modules
dist
assets

================================================
FILE: .prettierrc
================================================
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "arrowParens": "avoid"
}

================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG

## 🚀 v0.4.16

- ✨ Add --fill and --stroke option (#24)

## 🚀 v0.4.15

- ✨ Add components-template option (#22)

## 🚀 v0.4.14

- 🐛 Fix svg-sprite component generation (#18)
- 🐛 Fix svg-sprite's sprite option (#20)

## 🚀 v0.4.13

- 🔨 Allow outputFolder to be an absolute path (#15)

## 🚀 v0.4.12

- 🔨 Update CLI to specify sprite and types filenames

## 🚀 v0.4.11

- 🔨 Do not generate files or log to console if files are same as existing

## 🚀 v0.4.10

- 🔨 Export string array of icon names and generate type union from it (#13)

## 🚀 v0.4.9

- 🐛 Fix broken publish

## 🚀 v0.4.8

- 🔨 Export IconName in default svg template #12

## 🚀 v0.4.7

- 🐛 Fix hardcoded sprite import (#11)

## 🚀 v0.4.6

- 🔨 Add --template argument for custom generation
- 🔨 Strip width and height from SVG

## 🚀 v0.4.5

- 🔨 Update component generation with --components flag for named exports

## 🚀 v0.4.4

- 🔨 Update React sprite component import

## 🚀 v0.4.3

- 🔨 Export components for each icon instead of using sprite id

## 🚀 v0.4.2

- 🔨 Update handling of solid vs outline icons to be automatic

## 🚀 v0.4.1

- 🔨 Add support for using `currentColor` for `stroke` and `fill` icons

## 🚀 v0.4.1

- 🔨 Add support for using `currentColor` for `stroke` and `fill` icons

## 🚀 v0.4.0

- ✨ Add new `svg-sprite` command

## 🚀 v0.3.6

- ✨ Add new `version` command
- 🐛 Check if command exists before attempting to load

## 🚀 v0.3.5

- 🔨 Check for `module` property as well as `type === module` for ESM packages

## 🚀 v0.3.4

- 🔨 Add @ts-ignore and eslint-disable to generated file to ignore "errors"

## 🚀 v0.3.3

- 🔨 Remove timestamp on generated file `gen-remix` due to spurious diffs

## 🚀 v0.3.2

- 🐛 Fix override exports [#5](https://github.com/kiliman/rmx-cli/issues/5)
- 🐛 Ensure exports for overrides use the correct export type [#6](https://github.com/kiliman/rmx-cli/issues/6)

## 🚀 v0.3.1

- 🐛 Fix argument parsing

## 🚀 v0.3.0

- ✨ Add `gen-remix` command

## 🚀 v0.2.3

- 🔨 Update `rmx-cli` usage

## 🚀 v0.2.2

- 🐛 Update `get-esm-packages` to check for _package.json_ before loading

## 🚀 v0.2.1

- 🐛 Fix commandPath for Windows [#2](https://github.com/kiliman/rmx-cli/issues/2)

## 🚀 v0.2.0

- ✨ Command `get-esm-packages` to scan for ESM package to add to
  _remix.config.js_ `serverDependenciesToBundle`

## 🚀 v0.1.4

- 🐛 Fix files path for dist folder
- 🔨 Use realpath to resolve symlink
- 🐛 Fix backup copy and use timestamp

## 🚀 v0.1.1

- 🐛 Fix shebang

## 🚀 v0.1.0

- 🎉 Intial version
- ✨ Command to eject a Remix app from Remix App Server to Express


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2022 Michael J. Carter <kiliman@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# rmx-cli

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)

<!-- ALL-CONTRIBUTORS-BADGE:END -->

A CLI tool for Remix applications. Future versions will support adding external
commands.

## 🛠 Installation

```bash
npm install -D rmx-cli
```

# Commands

## 🎁 svg-sprite ✨ NEW

Generate SVG sprites recursively from `SOURCE_FOLDER`. It generates the sprite file,
as well as a React component to create the icon by specifying the fully-typed icon name.
It also exports the `href` of the sprite file to use in the Remix `links` export.

The `OUTPUT_PATH` can be a folder or a filename. If it is a filename, that will be used
as the base name if there are multiple source folders. For example:
_components/icons/icon.tsx_ will generate an _icons.tsx_ and _icons.svg_ file for every
source folder.

If you want to generate a React component for _each_ icon, then add the `--components`
argument. Then you can import the named icon directly.

> NOTE: The React component name will be the filename in TitleCase

You can specify a custom template file that will be used as the base for the generated
React component. The typed `IconNames` and exported components will be be appended to this
template file. An array of icon names is also exported: `export const iconNames = ["..."] as const`

Here's a sample template file:

```ts
import { type SVGProps } from 'react'
import { cn } from '~/utils/misc'
import href from './sprite.svg'
export { href }

const sizeClassName = {
  font: 'w-font h-font',
  xs: 'w-3 h-3',
  sm: 'w-4 h-4',
  md: 'w-5 h-5',
  lg: 'w-6 h-6',
  xl: 'w-7 h-7',
} as const

type Size = keyof typeof sizeClassName

export default function Icon({
  icon,
  size = 'font',
  className,
  ...props
}: SVGProps<SVGSVGElement> & { icon: IconName; size?: Size }) {
  return (
    <svg
      {...props}
      className={cn(sizeClassName[size], 'inline self-center', className)}
    >
      <use href={`${href}#${icon}`} />
    </svg>
  )
}
```

```
npx rmx-cli svg-sprite SOURCE_FOLDER OUTPUT_PATH [--components]
        [--template=TEMPLATE_FILE]
        [--components-template=TEMPLATE_FILE]
        [--fill=COLOR] [--stroke=COLOR]

SOURCE_FOLDER: folder containing .svg files
OUTPUT_PATH: output path for sprite file and components

* If OUTPUT_PATH ends with .tsx, then use this as the base filename
  (default: icon.tsx)

--sprite=FILENAME: base filename of sprite file (default: icon.svg)
--types=FILENAME : base filename of IconType export file
                   if present, will not generate component file
--components     : generate named components for each icon
--template=TEMPLATE_FILE: use custom template file
--fill=COLOR     : specify fill color or "keep" to keep original colors
                   default is "currentColor"
--stroke=COLOR   : specify stroke color or "keep" to keep original colors
                   default is "currentColor"
```

### Usage

_Example:_

```bash
npx rmx-cli svg-sprite assets/svg app/components/icons
```

```ts
// import default Icon component and specify the icon by name
// import the href to the sprite file to use in `links` export
import {
  default as RadixIcon,
  href as radixIcons,
} from "~/components/radixicons";

<RadixIcon icon="bookmark" className="text-red-500 h-6 w-6" />
<RadixIcon icon="envelope-open" className="text-green-500 h-6 w-6" />

// OR import named icon components (using --components flag)
import {
  ArchiveBoxIcon,
  ArrowDownIcon,
  CakeIcon,
  href as outline24Icons,
} from "~/components/heroicons/24/outline";

// generate <link rel="preload"> for the sprite file
export const links: LinksFunction = () => [
  { rel: "preload", href: outline24Icons, as: "image" },
  { rel: "stylesheet", href: tailwindCss },
];

// control color and size using className
<ArchiveBoxIcon className="text-red-500 h-6 w-6" />
<ArrowDownIcon className="text-green-500 h-6 w-6" />
<CakeIcon className="text-blue-500 h-6 w-6" />
```

<img src="./images/svg-sprite.png" style="max-width:400px">

## 🪂 eject-ras

Eject your Remix project from Remix App Server to Express

```bash
npx rmx-cli eject-ras
```

## 📦 get-esm-packages

Scan for ESM package to add to _remix.config.js_ `serverDependenciesToBundle`

```bash
npx rmx-cli get-esm-packages [package-name ...]
```

### Usage

```bash
  Example:
    npx rmx-cli get-esm-packages @remix-run/node @remix-run/react
```

## 🏷️ version

List all Remix package versions installed in node_modules

```bash
npx rmx-cli version
```

## 🚀 gen-remix

THis script will generate a _remix.ts_ file which re-exports all exports
from specified packages. This essentially works like the _magic_ `remix`
package from early Remix.

Why is this useful?

1. Go back to importing from one file instead of adapter specific packages. If you ever switch adapters, just re-generate the _remix.ts_ file.
2. Adds support for overrides. Now you can override a standard Remix export with your own function. Like replacing `json`, `useLoaderData`, etc. with the `remix-typedjson` functions.
3. Add `"postinstall": "rmx gen-remix"` to _package.json_ to ensure the file is regenerated when upgrading Remix packages.

### Usage

```bash
Usage:
    $ npx rmx gen-remix [options]

  Options:
    --config PATH       Config path (default: ./gen-remix.config.json)
    --packages PACKAGES List of packages to export
    --output PATH       Output path (default: ./app/remix.ts)

  Example:
    rmx gen-remix --packages @remix-run/node @remix-run/react
```

### Config

You can also include an optional config (defaults to _gen-remix.config.json_) where you can specify overrides.

```json
{
  "exports": ["packageA", "packageB"],
  "overrides": {
    "<source-package>": [
      "<original-package>": {
        "<original-export>": "<new-source-export>",
        ...
      },
      "<original-package>": {
        "<original-export>": "<new-source-export>",
        ...
      }
    ],
    ...
  }
}
```

### Example config:

This config replaces the Remix `json`, `redirect`, `useActionData`, etc. with the versions for [`remix-typedjson`](https://github.com/kiliman/remix-typedjson).

```json
{
  "exports": ["@remix-run/node", "@remix-run/react", "remix-typedjson"],
  "overrides": {
    "remix-typedjson": {
      "@remix-run/node": {
        "json": "typedjson",
        "redirect": "redirect"
      },
      "@remix-run/react": {
        "useActionData": "useTypedActionData",
        "useFetcher": "useTypedFetcher",
        "useLoaderData": "useTypedLoaderData"
      }
    }
  }
}
```

## 😍 Contributors

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <td align="center"><a href="https://kiliman.dev/"><img src="https://avatars.githubusercontent.com/u/47168?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kiliman</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=kiliman" title="Code">💻</a> <a href="https://github.com/Kiliman/rmx-cli/commits?author=kiliman" title="Documentation">📖</a></td>
    <td align="center"><a href="https://codsen.com/os/"><img src="https://avatars.githubusercontent.com/u/8344688?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roy Revelt</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=revelt" title="Documentation">📖</a></td>
    <td align="center"><a href="https://kentcdodds.com/"><img src="https://avatars.githubusercontent.com/u/1500684?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kent C. Dodds</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=kentcdodds" title="Documentation">📖</a></td>
    <td align="center"><a href="http://bgwebagency.in/"><img src="https://avatars.githubusercontent.com/u/13310363?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kiran Dash</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=kirandash" title="Documentation">📖</a></td>
    <td align="center"><a href="https://github.com/andrewcohen"><img src="https://avatars.githubusercontent.com/u/1016046?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Cohen</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=andrewcohen" title="Code">💻</a></td>
    <td align="center"><a href="https://github.com/courdek"><img src="https://avatars.githubusercontent.com/u/319738?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Coppola</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=courdek" title="Code">💻</a></td>
    <td align="center"><a href="https://about.me/knisterpeter"><img src="https://avatars.githubusercontent.com/u/327445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Markus Wolf</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=KnisterPeter" title="Code">💻</a></td>
  </tr>
  <tr>
    <td align="center"><a href="https://justinrhall.dev/"><img src="https://avatars.githubusercontent.com/u/1288694?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin Hall</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=wKovacs64" title="Code">💻</a> <a href="https://github.com/Kiliman/rmx-cli/issues?q=author%3AwKovacs64" title="Bug reports">🐛</a></td>
    <td align="center"><a href="http://florianweinaug.de/"><img src="https://avatars.githubusercontent.com/u/17765766?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Florian Weinaug</b></sub></a><br /><a href="https://github.com/Kiliman/rmx-cli/commits?author=fweinaug" title="Code">💻</a> <a href="https://github.com/Kiliman/rmx-cli/issues?q=author%3Afweinaug" title="Bug reports">🐛</a></td>
  </tr>
</table>

<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!


================================================
FILE: package.json
================================================
{
  "name": "rmx-cli",
  "version": "0.4.16",
  "description": "A CLI for remix-run",
  "author": "Michael J. Carter <kiliman@gmail.com> (https://kiliman.dev/)",
  "license": "MIT",
  "main": "dist/cli.js",
  "bin": {
    "rmx": "dist/cli.js"
  },
  "files": [
    "dist/**/*.js",
    "README.md",
    "LICENSE.md",
    "CHANGELOG.md"
  ],
  "scripts": {
    "clean": "rimraf dist",
    "prebuild": "npm run clean",
    "build": "esbuild --platform=node --format=cjs src/*.ts src/**/*.ts --outdir=dist",
    "contributors:add": "all-contributors add",
    "contributors:generate": "all-contributors generate",
    "prepublish": "npm run build",
    "prestart": "npm run build",
    "start": "node dist/cli.js"
  },
  "keywords": [
    "remix",
    "cli",
    "development",
    "scaffolding",
    "generator"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kiliman/rmx-cli.git"
  },
  "devDependencies": {
    "@types/node": "^17.0.30",
    "@types/node-fetch": "^2.5.7",
    "all-contributors-cli": "^6.20.0",
    "esbuild": "^0.14.38",
    "esbuild-register": "^3.3.2",
    "prettier": "^2.6.2",
    "rimraf": "^3.0.2",
    "typescript": "^4.6.4"
  },
  "dependencies": {
    "@npmcli/package-json": "^2.0.0",
    "node-fetch": "^2.6.7"
  }
}


================================================
FILE: src/cli.ts
================================================
#!/usr/bin/env node

import * as fs from 'fs'
import * as path from 'path'

main().catch(console.error)

async function main() {
  if (process.argv.length < 3) {
    console.error('Usage: npx rmx-cli <command>')
    process.exit(1)
  }
  const cliPath = path.dirname(fs.realpathSync(process.argv[1]))
  const commandName = process.argv[2]

  if (!fs.existsSync(path.join(cliPath, 'commands', `${commandName}.js`))) {
    console.error('Unknown command: ' + commandName)
    process.exit(1)
  }

  // add file:// prefix for windows imports
  const commandPath =
    'file://' + path.join(cliPath, 'commands', `${commandName}.js`)
  const command = (await import(commandPath)).default
  const args = process.argv.slice(3)
  await command.default(args)
}


================================================
FILE: src/commands/eject-ras.ts
================================================
// @ts-expect-error
import PackageJson from '@npmcli/package-json'
import * as fs from 'fs'
import fetch from 'node-fetch'
import * as path from 'path'

export default async function () {
  console.log('🚀 Ejecting from Remix App Server...')

  // download package.json from express template
  await download(
    'https://raw.githubusercontent.com/remix-run/remix/main/templates/express/package.json',
    './__eject-ras__/package.json',
  )
  const pkgJsonExpress = await PackageJson.load(
    path.resolve(process.cwd(), './__eject-ras__'),
  )

  // backup original package.json
  const ts = new Date().toISOString().substring(0, 19).replace(/[-T:]/g, '')
  fs.copyFileSync(
    path.resolve(process.cwd(), './package.json'),
    path.resolve(process.cwd(), `./package-${ts}.json`),
  )
  // get package.json
  const pkgJson = await PackageJson.load(process.cwd())

  // update dependencies
  const remixVersion = pkgJson.content.dependencies['@remix-run/node']

  // install missing dependencies
  const dependencies = getDependencies(pkgJson.content.dependencies)
  dependencies.uninstall('@remix-run/serve')
  for (const [name, version] of Object.entries(
    pkgJsonExpress.content.dependencies,
  )) {
    if (!dependencies.content[name]) {
      dependencies.install(name, version === '*' ? remixVersion : version)
    }
  }

  // install missing devDependencies
  const devDependencies = getDependencies(pkgJson.content.devDependencies)
  for (const [name, version] of Object.entries(
    pkgJsonExpress.content.devDependencies,
  )) {
    if (!devDependencies.content[name]) {
      devDependencies.install(name, version === '*' ? remixVersion : version)
    }
  }

  // update scripts
  const scripts = { ...pkgJson.content.scripts }
  for (const [name, command] of Object.entries(
    pkgJsonExpress.content.scripts,
  )) {
    if (name.startsWith('dev') || name.startsWith('start')) {
      console.log(`📝 updating script ${name}`)
      scripts[name] = command
    }
  }

  // save package.json
  pkgJson.update({
    dependencies: dependencies.content,
    devDependencies: devDependencies.content,
    scripts,
  })

  await pkgJson.save()

  // download express server startup file
  console.log('📦 downloading express server startup file')
  await download(
    'https://raw.githubusercontent.com/remix-run/remix/main/templates/express/server.js',
    './server.js',
  )
  // cleanup files
  fs.unlinkSync(path.resolve(process.cwd(), './__eject-ras__/package.json'))
  fs.rmdirSync(path.resolve(process.cwd(), './__eject-ras__'))

  console.log('🏁 Ejecting from Remix App Server... Done!\n')
  console.log('🔨 run npm install to update your dependencies')
}

function getDependencies(dependencies: Record<string, string>) {
  const newDependencies = { ...dependencies }
  return {
    uninstall(name: string) {
      console.log(`🔥 uninstalling ${name}`)
      delete newDependencies[name]
    },
    install(name: string, version: string) {
      console.log(`📦 installing ${name}@${version}`)
      newDependencies[name] = version
    },
    content: newDependencies,
  }
}

async function download(url: string, filepath: string) {
  const response = await fetch(url)
  const file = await response.text()
  const filedir = path.dirname(path.resolve(process.cwd(), filepath))
  if (!fs.existsSync(filedir)) {
    fs.mkdirSync(filedir, { recursive: true })
  }
  fs.writeFileSync(filepath, file)
}


================================================
FILE: src/commands/gen-remix.ts
================================================
import fs from 'fs'
import path from 'path'
import packageJson from '../../package.json'

type ConfigType = {
  exports: string[]
  overrides: OverridesType
}

type OverridesType = Record<string, SourcePackageOverrides>
type SourcePackageOverrides = Record<string, TargetPackageOverrides>
type TargetPackageOverrides = Record<string, string>

type PackageExports = {
  version: string
  values: string[]
  types: string[]
}
type ExportOverrides = { source: string; target: string; type: ExportType }
type ExportType = 'value' | 'type'

function usage() {
  console.log(`
  Usage:
    $ gen-remix [options]

  Options:
    --config PATH       Config path (default: ./gen-remix.config.json)
    --packages PACKAGES List of packages to export
    --output PATH       Output path (default: ./app/remix.ts)
  `)
}

export default async function () {
  let configPath = `gen-remix.config.json`
  let outputPath = './app/remix.ts'
  let packages: string[] = []
  let capture = null
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i]
    if (arg.startsWith('--')) {
      capture = null
      switch (arg) {
        case '--config':
          configPath = process.argv[++i]
          break
        case '--output':
          outputPath = process.argv[++i]
          break
        case '--packages':
          capture = packages
          break
        default:
          usage()
          break
      }
    } else if (capture) {
      capture.push(arg)
    }
  }
  let config: ConfigType
  if (!fs.existsSync(configPath)) {
    if (!packages.length) {
      usage()
      return
    }
    config = { exports: packages, overrides: {} }
  } else {
    config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
  }
  console.log('🚀 Generating remix.ts exports...')

  // read package exports
  const exports: Record<string, PackageExports> = {}
  for (const packageName of config.exports) {
    console.log(`📦 ${packageName}`)
    const packageJson = JSON.parse(
      fs.readFileSync(`node_modules/${packageName}/package.json`, 'utf8'),
    )
    exports[packageName] = {
      version: packageJson.version,
      values: [],
      types: [],
    }
    let typings = packageJson.typings
    if (!typings) {
      typings = path.dirname(packageJson.main) + '/index.d.ts'
    }
    const content = fs.readFileSync(
      path.join(`node_modules/${packageName}`, typings),
      'utf8',
    )
    const lines = content
      .replace(/\n/g, ' ')
      .replace(/(export\s+(type\s*)?{)/g, '\n$1')
      .split('\n')
      .filter(line => line.trim().length > 0)
    for (let line of lines) {
      const match = line.match(/^export(\s+type)?\s*{(.*)}/)
      if (match) {
        const list = match[1] ? 'types' : 'values'
        const exportList = match[2]
          .split(',')
          .map(s => s.trim())
          .filter(Boolean)
        for (let exportName of exportList) {
          const alias = exportName.match(/^(\w+)\s+as\s+(\w+)$/)
          if (alias) {
            exportName = alias[2]
          }
          exports[packageName][list].push(exportName)
        }
      }
    }
  }
  // overrides: {
  //   "<source-package>": [
  //     "<original-package>": {
  //       "<original-export>": "<new-source-export>",
  //       ...
  //     },
  //     "<original-package>": {
  //       "<original-export>": "<new-source-export>",
  //       ...
  //     }
  //   ],
  //   ...
  // }"

  let exportOverrides: ExportOverrides[] = []
  const hasOverrides = config.overrides && Object.keys(config.overrides).length

  if (hasOverrides) {
    for (const sourcePackage of Object.keys(config.overrides)) {
      const sourceOverrides = config.overrides[sourcePackage]

      exportOverrides = [
        ...exportOverrides,
        ...Object.values(sourceOverrides)
          .flatMap(overrides => Object.entries(overrides))
          .map(([target, source]) => ({
            source,
            target,
            type: (exports[sourcePackage].values.includes(source)
              ? 'value'
              : 'type') as ExportType,
          })),
      ]
    }
  }

  // write remix.ts
  let output = `// @ts-nocheck
/* eslint-disable */
// This file was generated by gen-remix.ts v${packageJson.version}\n`
  for (const packageName of Object.keys(exports)) {
    output += `\n// ${packageName}@${exports[packageName].version}`
  }
  if (hasOverrides) {
    output += `\n\n// import overrides`
    for (const sourcePackage of Object.keys(config.overrides)) {
      const sourceOverrides = config.overrides[sourcePackage]
      let imports: string[] = []
      // import source values
      for (const originalPackage of Object.keys(sourceOverrides)) {
        imports = [
          ...imports,
          ...Object.values(sourceOverrides[originalPackage]),
        ]
      }
      imports.sort()
      output += `\nimport {\n${imports
        .map(
          i =>
            `  ${exports[sourcePackage].types.includes(i) ? 'type ' : ''}${i},`,
        )
        .join('\n')}\n} from "${sourcePackage}";`
    }
  }
  let allExports: string[] = []
  output += `\n\n// export packages`
  for (const packageName of Object.keys(exports)) {
    console.log(`📦 ${packageName}`)
    const currValues = exports[packageName].values.filter(
      // eslint-disable-next-line no-loop-func
      e =>
        !exportOverrides.some(({ target }) => e === target) &&
        !allExports.includes(e),
    )
    currValues.sort()
    allExports = [...allExports, ...currValues]
    output += `\nexport {\n${currValues
      .map(e => `  ${e},`)
      .join('\n')}\n} from "${packageName}";`

    if (exports[packageName].types.length) {
      const currTypes = exports[packageName].types.filter(
        // eslint-disable-next-line no-loop-func
        e =>
          !exportOverrides.some(({ target }) => e === target) &&
          !allExports.includes(e),
      )
      currTypes.sort()
      allExports = [...allExports, ...currTypes]
      output += `\nexport type {\n${currTypes
        .map(e => `  ${e},`)
        .join('\n')}\n} from "${packageName}";`
    }
  }
  if (hasOverrides) {
    output += `\n\n// export overrides`
    const overrideValues = exportOverrides.filter(
      ({ type }) => type === 'value',
    )
    if (overrideValues.length) {
      overrideValues.sort()
      output += `\nexport {\n${overrideValues
        .map(
          ({ source, target }) =>
            `  ${source === target ? source : `${source} as ${target}`},`,
        )
        .join('\n')}\n};`
    }
    const overrideTypes = exportOverrides.filter(({ type }) => type === 'type')
    if (overrideTypes.length) {
      overrideTypes.sort()
      output += `\nexport type {\n${overrideTypes
        .map(
          ({ source, target }) =>
            `  ${source === target ? source : `${source} as ${target}`},`,
        )
        .join('\n')}\n};`
    }
  }

  console.log(`📝 Writing ${outputPath}...`)
  fs.writeFileSync(outputPath, output, 'utf8')
  console.log('🏁 Done!')
}


================================================
FILE: src/commands/get-esm-packages.ts
================================================
import * as fs from 'node:fs'

export default function (args: string[]) {
  let esmPackages: Set<string> = new Set()

  function getDependencies(packageName: string) {
    const packageJsonFilename = `./node_modules/${packageName}/package.json`
    if (!fs.existsSync(packageJsonFilename)) {
      console.log(`⚠️ ${packageName} package.json not found`)
      return
    }
    const json = fs.readFileSync(packageJsonFilename, 'utf8')
    const packageJson = JSON.parse(json)

    if (packageJson.type === 'module' || packageJson.module) {
      if (esmPackages.has(packageName)) return
      console.log(`📦 ${packageName}`)
      esmPackages.add(packageName)
      const dependencies = packageJson.dependencies || {}
      Object.keys(dependencies).forEach(dependency => {
        getDependencies(dependency)
      })
    }
  }

  args.forEach(packageName => {
    getDependencies(packageName)
  })
  console.log(
    '\n🔨 Add the following dependencies to your serverDependenciesToBundle\n',
  )
  console.log(
    Array.from(esmPackages.values())
      .sort()
      .map((packageName: string) => `"${packageName}"`)
      .join(',\n'),
  )
}


================================================
FILE: src/commands/svg-sprite.ts
================================================
import * as fs from 'node:fs'
import * as path from 'node:path'
// @ts-expect-error
import svgParser from '../libs/svg-parser'

let rootFolder = ''
let outputFolder = ''
let template: string | null = null
let namedComponents = false
let componentsTemplate: string | null = null
let component = 'icon.tsx'
let sprite = 'icon.svg'
let types = ''
let fillColor = 'currentColor'
let strokeColor = 'currentColor'

export default function (args: string[]) {
  // verify arguments
  if (args.length >= 2) {
    rootFolder = normalizeFolder(args[0])
    let outputPath = args[1]
    // if output folder has extension, then treat this as the base name
    if (outputPath.endsWith('.tsx')) {
      outputFolder = path.dirname(outputPath)
      component = path.basename(outputPath)
    } else {
      outputFolder = normalizeFolder(outputPath)
    }
    for (let i = 2; i < args.length; i++) {
      if (args[i].startsWith('--template=')) {
        const templateFilename = args[i].substring('--template='.length)
        template = fs.readFileSync(templateFilename, 'utf8')
      } else if (args[i].startsWith('--component=')) {
        sprite = args[i].substring('--component='.length)
      } else if (args[i].startsWith('--sprite=')) {
        sprite = args[i].substring('--sprite='.length)
      } else if (args[i].startsWith('--types=')) {
        types = args[i].substring('--types='.length)
      } else if (args[i] === '--components') {
        namedComponents = true
      } else if (args[i].startsWith('--components-template=')) {
        const templateFilename = args[i].substring(
          '--components-template='.length,
        )
        componentsTemplate = fs.readFileSync(templateFilename, 'utf8')
      } else if (args[i].startsWith('--fill=')) {
        fillColor = args[i].substring('--fill='.length)
      } else if (args[i].startsWith('--stroke=')) {
        strokeColor = args[i].substring('--stroke='.length)
      }
    }
  } else {
    console.log(
      `
Usage: npx rmx-cli svg-sprite SOURCE_FOLDER OUTPUT_PATH
               [--sprite=FILENAME] [--types=FILENAME]
               [--components] [--template=TEMPLATE_FILE]
               [--fill=COLOR] [--stroke=COLOR]

SOURCE_FOLDER: folder containing .svg files
OUTPUT_PATH: output path for sprite file and components

* If OUTPUT_PATH ends with .tsx, then use this as the base filename
  (default: icon.tsx)

--sprite=FILENAME: base filename of sprite file (default: icon.svg)
--types=FILENAME : base filename of IconType export file
                   if present, will not generate component file
--components     : generate named components for each icon
--template=TEMPLATE_FILE: use custom template file
--fill=COLOR     : specify fill color or "keep" to keep original colors
                   default is "currentColor"
--stroke=COLOR   : specify stroke color or "keep" to keep original colors
                   default is "currentColor"
`.trimStart(),
    )
    process.exit(1)
  }

  // generate sprites for any .svg files in the root folder recursively
  generateSprites(rootFolder)
}

// queue up console.log calls so we can print them after files are "generated"
const logs: any[] = []
function log(...args: any[]) {
  logs.push(args)
}
function flushLogs() {
  if (logs.length === 0) return
  logs.forEach(args => console.log(...args))
  logs.length = 0
}

function normalizeFolder(folder: string) {
  let fullPath = path.resolve(folder)
  // remove cwd from path and leading slash
  const cwd = ensureSlash(process.cwd())
  let normalized = fullPath
  // check if normalized starts with cwd (both have to end with /, otherwise
  // the check might be wrong, e.g. /tmp/test is cwd and /tmp/test2 is fullPath)
  if (normalized.startsWith(cwd)) {
    normalized = normalized.replace(cwd, '')
  }
  return normalized
}

function ensureSlash(input: string): string {
  if (!input.endsWith(path.sep)) {
    input = input + path.sep
  }
  return input
}

function generateSprites(folder: string) {
  // any .svg files in this folder?
  const svgFiles = fs
    .readdirSync(folder)
    .filter(file => file.endsWith('.svg'))
    .map(file => path.join(folder, file))
  if (svgFiles.length > 0) {
    generateSprite(folder, svgFiles)
  }

  // recurse through folders looking for .svg files
  const folders = fs
    .readdirSync(folder, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => path.join(folder, dirent.name))

  folders.forEach(folder => {
    generateSprites(folder)
  })
}

function generateSprite(folder: string, files: string[]) {
  // folder is something like: assets/svg/heroicons/20/solid
  // spriteOutputFolder: components/svg/heroicons/20/solid

  const spriteOutputFolder = folder.replace(rootFolder, outputFolder)
  const spriteOutput = path.join(spriteOutputFolder, path.basename(sprite))

  log(`📝 Generating sprite for ${folder}`)

  // create output folder if it doesn't exist
  if (!fs.existsSync(spriteOutputFolder)) {
    fs.mkdirSync(spriteOutputFolder, { recursive: true })
  }
  const strip = true
  const trim = false

  let { svgElement } = svgParser.iterateFiles(
    files,
    strip,
    trim,
    fillColor,
    strokeColor,
  )

  svgElement = svgParser.wrapInSvgTag(svgElement)

  const exists = fs.existsSync(spriteOutput)
  let hasChanges = !exists // if sprite doesn't exist, then we have changes
  if (exists) {
    // read existing sprite file
    const existingSprite = fs.readFileSync(spriteOutput, 'utf8')
    if (existingSprite !== svgElement) {
      hasChanges = true
    }
  }
  if (hasChanges) {
    // write sprite file
    fs.writeFileSync(spriteOutput, svgElement, 'utf8')
    flushLogs()
  }

  if (types) {
    generateTypesFile(spriteOutputFolder, files)
    return
  }

  generateReactComponent(spriteOutputFolder, files)
}

function generateTypesFile(spriteOutputFolder: string, files: string[]) {
  let icons = files.map(file => path.basename(file, '.svg'))
  let output = `export const iconNames = [
${icons.map(icon => `  "${icon}",`).join('\n')}
] as const;

export type IconName = typeof iconNames[number];
`
  icons.forEach(icon => log(`✅ ${icon}`))

  let typesPath = path.join(spriteOutputFolder, types)
  const exists = fs.existsSync(typesPath)
  let hasChanges = !exists // if types doesn't exist, then we have changes
  if (exists) {
    // read existing types file
    const existing = fs.readFileSync(typesPath, 'utf8')
    if (existing !== output) {
      hasChanges = true
    }
  }
  if (hasChanges) {
    fs.writeFileSync(typesPath, output, 'utf8')
    flushLogs()
  }
}
function generateReactComponent(spriteOutputFolder: string, files: string[]) {
  let icons = files.map(file => path.basename(file, '.svg'))
  const spritePath = path.basename(component, '.tsx') + '.svg'
  let output =
    template ??
    `
import { type SVGProps } from "react";
import href from "./${spritePath}";
export { href };

export default function Icon({ icon, ...props}: SVGProps<SVGSVGElement> & { icon: IconName }) {
  return (
    <svg {...props}>
      <use href={\`\${href}#\${icon}\`} />
    </svg>
  );
}
`
  // if user didn't specify types file then inline them
  if (!types) {
    // add type IconName for each icon file
    output += `
export const iconNames = [
${icons.map(icon => `  "${icon}",`).join('\n')}
] as const;
export type IconName = typeof iconNames[number];`
  }
  icons.forEach(icon => log(`✅ ${icon}`))

  // if user wants named components, generate them
  if (namedComponents) {
    output += '\n'
    icons.forEach(icon => {
      // convert kebab case to title case
      const componentName = icon.replace(/(^|-|_)([a-z0-9])/g, g =>
        g!.at(-1)!.toUpperCase(),
      )
      output += '\n'
      output +=
        componentsTemplate
          ?.replace(/{{icon}}/g, icon)
          .replace(/{{componentName}}/g, componentName) ??
        `export const ${componentName}Icon = (props: SVGProps<SVGSVGElement>) => <Icon icon="${icon}" {...props} />;`
    })
  }
  const componentPath = path.join(spriteOutputFolder, component)
  output = output.trimStart()

  const exists = fs.existsSync(componentPath)
  let hasChanges = !exists // if sprite doesn't exist, then we have changes
  if (exists) {
    // read existing sprite file
    const existing = fs.readFileSync(componentPath, 'utf8')
    if (existing !== output) {
      hasChanges = true
    }
  }
  if (hasChanges) {
    // write sprite file
    fs.writeFileSync(componentPath, output, 'utf8')
    flushLogs()
  }
}


================================================
FILE: src/commands/version.ts
================================================
import * as fs from 'node:fs'

export default function (args: string[]) {
  // read package.json
  const packageJsonFilename = `./package.json`
  const packageJson = fs.readFileSync(packageJsonFilename, 'utf8')
  const packageJsonParsed = JSON.parse(packageJson)

  // get all dependencies
  const allDependencies = [
    ...Object.keys(packageJsonParsed.dependencies || {}),
    ...Object.keys(packageJsonParsed.devDependencies || {}),
  ]
  // filter @remix-run/* and react-router* packages
  const re = /^(remix|@remix-run\/\w+|react-router(-dom)?)$/
  const filteredDependencies = allDependencies.filter(packageName =>
    re.test(packageName),
  )

  filteredDependencies.forEach(packageName => {
    const version = getVersion(packageName)
    if (version) {
      console.log(`📦 ${packageName} version: ${version}`)
    }
  })

  function getVersion(packageName: string) {
    const packageJsonFilename = `./node_modules/${packageName}/package.json`
    if (!fs.existsSync(packageJsonFilename)) {
      console.log(`⚠️ ${packageName} package.json not found`)
      return
    }
    const json = fs.readFileSync(packageJsonFilename, 'utf8')
    const packageJson = JSON.parse(json)

    return packageJson.version
  }
}


================================================
FILE: src/libs/svg-parser.ts
================================================
// @ts-nocheck
// https://github.com/jannicz/svg-icon-sprite/blob/master/scripts/svg-parser.lib.js

const fs = require('fs')
const path = require('path')

/**
 * Library for generating SVG sprites (using symbol technique)
 *
 * @author Jan Suwart
 * @licence MIT
 */
const svgParserLib = {
  fgRed: '\x1b[31m',
  fgGreen: '\x1b[32m',
  fgYellow: '\x1b[33m',
  fgReset: '\x1b[0m',

  /**
   * @param {string} file - full path of current file
   * @param {string} name - name of current file
   * @return {string} the created symbol element as string
   */
  createSymbol: (file, name) => {
    if (!file || !name) {
      throw new Error('No file found at ' + name)
    } else if (!file.includes('<svg')) {
      throw new Error('No SVG node found in ' + name)
    }

    let symbolEl = file
      .replace(/<\?xml.*?\?>/, '')
      .replace(/ id=".*?"/, '')
      .replace(/ version=".*?"/, '')
      .replace(/ xmlns=".*?"/, '')
      .replace(/ xmlns:xlink=".*?"/, '')
      .replace('<svg', `<symbol id="${name}"`)
      .replace('</svg>', '</symbol>\n')

    return symbolEl
  },

  /**
   * Removes fill and stroke attributes while preserving fill="none" to allow hollow elements
   * @param {string} svg - input SVG as string
   * @param {string} fillColor - new fill color (keep to preserve existing color)
   * @param {string} strokeColor - new stroke color (keep to preserve existing color)
   * @return {string} stripped SVG as string
   */
  stripProperties: (svg, fillColor, strokeColor) => {
    const widthHeight = /\s+(width|height)=".*?"/gim

    svg = svg.replace(widthHeight, '')
    if (fillColor !== 'keep') {
      // replace existing fill/stroke attributes with the new color
      const fill = /\s+(fill)="((?!(none|currentColor)).*?)"/gim
      svg = svg.replace(fill, ` $1="${fillColor}"`)
    }
    if (strokeColor !== 'keep') {
      // replace existing fill/stroke attributes with the new color
      const stroke = /\s+(stroke)="((?!(none|currentColor)).*?)"/gim
      svg = svg.replace(stroke, ` $1="${strokeColor}"`)
    }

    return svg
  },

  makeSolid: svg => {
    return svg.replace(/fill="none"/g, 'fill="currentColor"')
  },

  /**
   * Removes all whitespaces to reduce file size
   */
  removeWhitespaces: svg => {
    return svg
      .replace(/\n/g, '')
      .replace(/[\t ]+\</g, '<')
      .replace(/\>[\t ]+\</g, '><')
      .replace(/\>[\t ]+$/g, '>')
  },

  /**
   * Wraps the passed elements string into a SVG structure
   * @param {string} elements - concatenated symbols
   * @return {string}
   */
  wrapInSvgTag: elements => {
    return `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">\n${elements}</svg>\n`
  },

  /**
   * Iterates the file array, retrieves each file and applies lib functions
   * @param {{name: string, path: string}[]} files - List containing file references and names
   * @param {boolean} [strip] - whether to remove fill/stroke attributes
   * @param {boolean} [trim] - whether to remove all whitespace
   * @param {string} fillColor - new fill color (keep to preserve existing color)
   * @param {string} strokeColor - new stroke color (keep to preserve existing color)
   * @return {{elementsChanged: number, svgElement: string}} final SVG sprite as string
   */
  iterateFiles: (files, strip, trim, fillColor, strokeColor) => {
    let svgElement = ''
    let elementsChanged = 0

    files.forEach(file => {
      let name = path.basename(file, '.svg')
      try {
        let svg = fs.readFileSync(file, 'utf8')
        let symbolEl = svgParserLib.createSymbol(svg, name)

        if (strip) {
          symbolEl = svgParserLib.stripProperties(
            symbolEl,
            fillColor,
            strokeColor,
          )
        }

        svgElement += symbolEl
        elementsChanged++
      } catch (e) {
        console.warn(
          svgParserLib.fgRed + 'Could not parse',
          name,
          'because of error:' + svgParserLib.fgReset,
          e.message,
          '- file skipped!',
        )
      }
    })

    if (trim) {
      svgElement = svgParserLib.removeWhitespaces(svgElement)
    }

    return { svgElement, elementsChanged }
  },

  readFile: fileObj => {
    return fs.readFileSync(fileObj.path, 'utf8')
  },

  /**
   * Writes the string into a folder
   * @param {string} fullFileName - folder and filename
   * @param {string} outputString - the output that should be written
   * @return {Promise} holding the write error if failed, true otherwise
   */
  writeIconsToFile: (fullFileName, outputString) => {
    fs.writeFileSync(fullFileName, outputString, 'utf8')
  },

  /**
   * Reads the directory and returns array of files that match filetype (i.e. .svg)
   * @param {string} dirname - full directory name relative to the working directory of the script
   * @param {string} filetype - substring that the file should be tested for, i.e. '.svg'
   * @return {Promise} containing the file list as array
   */
  readDirectory: (dirname, filetype) => {
    return new Promise((resolve, reject) => {
      let fileList = []

      fs.readdir(dirname, (error, files) => {
        if (error) {
          reject(error)
        } else {
          files.forEach(file => {
            if (file.includes(filetype)) {
              fileList.push(file)
            }
          })

          resolve(fileList)
        }
      })
    })
  },
}

module.exports = svgParserLib


================================================
FILE: test/package-copy.json
================================================
{
  "name": "remix-template-remix",
  "private": true,
  "description": "",
  "license": "",
  "sideEffects": false,
  "scripts": {
    "build": "remix build",
    "dev": "remix dev",
    "start": "remix-serve build"
  },
  "dependencies": {
    "@remix-run/node": "^1.4.1",
    "@remix-run/react": "^1.4.1",
    "@remix-run/serve": "^1.4.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@remix-run/dev": "^1.4.1",
    "@remix-run/eslint-config": "^1.4.1",
    "@types/react": "^17.0.24",
    "@types/react-dom": "^17.0.9",
    "eslint": "^8.11.0",
    "typescript": "^4.5.5"
  },
  "engines": {
    "node": ">=14"
  }
}


================================================
FILE: test/package.json
================================================
{
  "name": "remix-template-remix",
  "private": true,
  "description": "",
  "license": "",
  "sideEffects": false,
  "scripts": {
    "build": "remix build",
    "dev": "remix build && run-p dev:*",
    "start": "cross-env NODE_ENV=production node ./server.js",
    "dev:node": "cross-env NODE_ENV=development nodemon ./server.js --watch ./server.js",
    "dev:remix": "remix watch"
  },
  "dependencies": {
    "@remix-run/express": "^1.4.1",
    "@remix-run/node": "^1.4.1",
    "@remix-run/react": "^1.4.1",
    "compression": "^1.7.4",
    "cross-env": "^7.0.3",
    "express": "^4.17.1",
    "morgan": "^1.10.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@remix-run/dev": "^1.4.1",
    "@remix-run/eslint-config": "^1.4.1",
    "@types/react": "^17.0.24",
    "@types/react-dom": "^17.0.9",
    "eslint": "^8.11.0",
    "nodemon": "^2.0.15",
    "npm-run-all": "^4.1.5",
    "typescript": "^4.5.5"
  },
  "engines": {
    "node": ">=14"
  }
}


================================================
FILE: test/server.js
================================================
const path = require("path");
const express = require("express");
const compression = require("compression");
const morgan = require("morgan");
const { createRequestHandler } = require("@remix-run/express");

const BUILD_DIR = path.join(process.cwd(), "build");

const app = express();

app.use(compression());

// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable("x-powered-by");

// Remix fingerprints its assets so we can cache forever.
app.use(
  "/build",
  express.static("public/build", { immutable: true, maxAge: "1y" })
);

// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static("public", { maxAge: "1h" }));

app.use(morgan("tiny"));

app.all(
  "*",
  process.env.NODE_ENV === "development"
    ? (req, res, next) => {
        purgeRequireCache();

        return createRequestHandler({
          build: require(BUILD_DIR),
          mode: process.env.NODE_ENV,
        })(req, res, next);
      }
    : createRequestHandler({
        build: require(BUILD_DIR),
        mode: process.env.NODE_ENV,
      })
);
const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Express server listening on port ${port}`);
});

function purgeRequireCache() {
  // purge require cache on requests for "server side HMR" this won't let
  // you have in-memory objects between requests in development,
  // alternatively you can set up nodemon/pm2-dev to restart the server on
  // file changes, but then you'll have to reconnect to databases/etc on each
  // change. We prefer the DX of this, so we've included it for you by default
  for (let key in require.cache) {
    if (key.startsWith(BUILD_DIR)) {
      delete require.cache[key];
    }
  }
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "lib": ["DOM", "DOM.Iterable", "ES2019"],
    "esModuleInterop": true,
    "moduleResolution": "Node",
    "target": "ES2019",
    "module": "ES2020",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "declaration": true,
    "jsx": "react-jsx",
    "importHelpers": true,
    "resolveJsonModule": true
  },
  "exclude": ["node_modules"],
  "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"]
}
Download .txt
gitextract_vdy00js5/

├── .all-contributorsrc
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── package.json
├── src/
│   ├── cli.ts
│   ├── commands/
│   │   ├── eject-ras.ts
│   │   ├── gen-remix.ts
│   │   ├── get-esm-packages.ts
│   │   ├── svg-sprite.ts
│   │   └── version.ts
│   └── libs/
│       └── svg-parser.ts
├── test/
│   ├── package-copy.json
│   ├── package.json
│   └── server.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (23 symbols across 7 files)

FILE: src/cli.ts
  function main (line 8) | async function main() {

FILE: src/commands/eject-ras.ts
  function getDependencies (line 86) | function getDependencies(dependencies: Record<string, string>) {
  function download (line 101) | async function download(url: string, filepath: string) {

FILE: src/commands/gen-remix.ts
  type ConfigType (line 5) | type ConfigType = {
  type OverridesType (line 10) | type OverridesType = Record<string, SourcePackageOverrides>
  type SourcePackageOverrides (line 11) | type SourcePackageOverrides = Record<string, TargetPackageOverrides>
  type TargetPackageOverrides (line 12) | type TargetPackageOverrides = Record<string, string>
  type PackageExports (line 14) | type PackageExports = {
  type ExportOverrides (line 19) | type ExportOverrides = { source: string; target: string; type: ExportType }
  type ExportType (line 20) | type ExportType = 'value' | 'type'
  function usage (line 22) | function usage() {

FILE: src/commands/get-esm-packages.ts
  function getDependencies (line 6) | function getDependencies(packageName: string) {

FILE: src/commands/svg-sprite.ts
  function log (line 86) | function log(...args: any[]) {
  function flushLogs (line 89) | function flushLogs() {
  function normalizeFolder (line 95) | function normalizeFolder(folder: string) {
  function ensureSlash (line 108) | function ensureSlash(input: string): string {
  function generateSprites (line 115) | function generateSprites(folder: string) {
  function generateSprite (line 136) | function generateSprite(folder: string, files: string[]) {
  function generateTypesFile (line 185) | function generateTypesFile(spriteOutputFolder: string, files: string[]) {
  function generateReactComponent (line 210) | function generateReactComponent(spriteOutputFolder: string, files: strin...

FILE: src/commands/version.ts
  function getVersion (line 27) | function getVersion(packageName: string) {

FILE: test/server.js
  constant BUILD_DIR (line 7) | const BUILD_DIR = path.join(process.cwd(), "build");
  function purgeRequireCache (line 50) | function purgeRequireCache() {
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
  {
    "path": ".all-contributorsrc",
    "chars": 2464,
    "preview": "{\n  \"projectName\": \"rmx-cli\",\n  \"projectOwner\": \"Kiliman\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n "
  },
  {
    "path": ".gitignore",
    "chars": 25,
    "preview": "/node_modules\ndist\nassets"
  },
  {
    "path": ".prettierrc",
    "chars": 94,
    "preview": "{\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"arrowParens\": \"avoid\"\n}"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2555,
    "preview": "# CHANGELOG\n\n## 🚀 v0.4.16\n\n- ✨ Add --fill and --stroke option (#24)\n\n## 🚀 v0.4.15\n\n- ✨ Add components-template option (#"
  },
  {
    "path": "LICENSE.md",
    "chars": 1104,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2022 Michael J. Carter <kiliman@gmail.com>\n\nPermission is hereby granted, free of c"
  },
  {
    "path": "README.md",
    "chars": 10105,
    "preview": "# rmx-cli\n\n<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->\n\n[![All Contributors](https://im"
  },
  {
    "path": "package.json",
    "chars": 1274,
    "preview": "{\n  \"name\": \"rmx-cli\",\n  \"version\": \"0.4.16\",\n  \"description\": \"A CLI for remix-run\",\n  \"author\": \"Michael J. Carter <ki"
  },
  {
    "path": "src/cli.ts",
    "chars": 752,
    "preview": "#!/usr/bin/env node\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nmain().catch(console.error)\n\nasync function "
  },
  {
    "path": "src/commands/eject-ras.ts",
    "chars": 3416,
    "preview": "// @ts-expect-error\nimport PackageJson from '@npmcli/package-json'\nimport * as fs from 'fs'\nimport fetch from 'node-fetc"
  },
  {
    "path": "src/commands/gen-remix.ts",
    "chars": 6989,
    "preview": "import fs from 'fs'\nimport path from 'path'\nimport packageJson from '../../package.json'\n\ntype ConfigType = {\n  exports:"
  },
  {
    "path": "src/commands/get-esm-packages.ts",
    "chars": 1146,
    "preview": "import * as fs from 'node:fs'\n\nexport default function (args: string[]) {\n  let esmPackages: Set<string> = new Set()\n\n  "
  },
  {
    "path": "src/commands/svg-sprite.ts",
    "chars": 8482,
    "preview": "import * as fs from 'node:fs'\nimport * as path from 'node:path'\n// @ts-expect-error\nimport svgParser from '../libs/svg-p"
  },
  {
    "path": "src/commands/version.ts",
    "chars": 1226,
    "preview": "import * as fs from 'node:fs'\n\nexport default function (args: string[]) {\n  // read package.json\n  const packageJsonFile"
  },
  {
    "path": "src/libs/svg-parser.ts",
    "chars": 5490,
    "preview": "// @ts-nocheck\n// https://github.com/jannicz/svg-icon-sprite/blob/master/scripts/svg-parser.lib.js\n\nconst fs = require('"
  },
  {
    "path": "test/package-copy.json",
    "chars": 660,
    "preview": "{\n  \"name\": \"remix-template-remix\",\n  \"private\": true,\n  \"description\": \"\",\n  \"license\": \"\",\n  \"sideEffects\": false,\n  \""
  },
  {
    "path": "test/package.json",
    "chars": 992,
    "preview": "{\n  \"name\": \"remix-template-remix\",\n  \"private\": true,\n  \"description\": \"\",\n  \"license\": \"\",\n  \"sideEffects\": false,\n  \""
  },
  {
    "path": "test/server.js",
    "chars": 1820,
    "preview": "const path = require(\"path\");\nconst express = require(\"express\");\nconst compression = require(\"compression\");\nconst morg"
  },
  {
    "path": "tsconfig.json",
    "chars": 477,
    "preview": "{\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2019\"],\n    \"esModuleInterop\": true,\n    \"moduleResolutio"
  }
]

About this extraction

This page contains the full source code of the kiliman/rmx-cli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (47.9 KB), approximately 13.7k tokens, and a symbol index with 23 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!